I'm writing a React
, Server Side Rendering
, Router4
, Helmet
, CSS Modules
boilerplate, everything is awesome but one thing hurts my soul:
At first see my webpack.production.config.js
:
const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const StatsPlugin = require('stats-webpack-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const distDir = path.join(__dirname, 'dist');
const srcDir = path.join(__dirname);
module.exports = [
{
name: 'client',
target: 'web',
entry: `${srcDir}/client.jsx`,
output: {
path: distDir,
filename: 'client.js',
publicPath: distDir,
},
resolve: {
extensions: ['.js', '.jsx']
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules\/)/,
use: [
{
loader: 'babel-loader',
}
]
},
{
test: /\.scss$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
modules: true,
importLoaders: 1,
localIdentName: '[hash:base64:10]',
sourceMap: false,
}
},
{
loader: 'sass-loader'
}
]
}
],
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new CleanWebpackPlugin(distDir),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
screw_ie8: true,
drop_console: true,
drop_debugger: true
}
}),
new webpack.optimize.OccurrenceOrderPlugin(),
]
},
{
name: 'server',
target: 'node',
entry: `${srcDir}/server.jsx`,
output: {
path: distDir,
filename: 'server.js',
libraryTarget: 'commonjs2',
publicPath: distDir,
},
resolve: {
extensions: ['.js', '.jsx']
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules\/)/,
use: [
{
loader: 'babel-loader',
}
]
},
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallback: "isomorphic-style-loader",
use: [
{
loader: 'css-loader',
options: {
modules: true,
importLoaders: 1,
localIdentName: '[hash:base64:10]',
sourceMap: false
}
},
{
loader: 'sass-loader'
}
]
})
}
],
},
plugins: [
new ExtractTextPlugin({
filename: 'styles.css',
allChunks: true
}),
new OptimizeCssAssetsPlugin({
cssProcessorOptions: { discardComments: { removeAll: true } }
}),
new StatsPlugin('stats.json', {
chunkModules: true,
modules: true,
chunks: true,
exclude: [/node_modules[\\\/]react/],
}),
]
}
];
And this is my template.js
file that server use this for building basic HTML
:
export default ({ markup, helmet }) => {
return `<!DOCTYPE html>
<html ${helmet.htmlAttributes.toString()}>
<head>
${helmet.title.toString()}
${helmet.meta.toString()}
${helmet.link.toString()}
</head>
<body ${helmet.bodyAttributes.toString()}>
<div id="root">${markup}</div>
<script src="/dist/client.js" async></script>
</body>
</html>`;
};
Just like I write, I set sourceMap
to false
, so all styles loaded as <style>
tag inside <head>
tag.
If I set the soureMap
to true
, the <link>
tag appears inside <head>
tag, Even it is not server side, and has a weird blob url for loading CSS
:
In fact I wanna server side link tag inside head tag with direct pointing to styles.css
, how I can do it ?
My whole project is in THIS LINK
It has not very lot codes, little and simple, it is just a simple boilerplate. take a look
For production builds,inside client
config, you don't use the style loader. You need to use the extract-text-webpack-plugin
instead. You did it correctly in your server
build config. But this config shouldn't be in your server build, as in the server's source code, you never use (s)css files.
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[hash:base64:10]',
}
},
{
loader: 'sass-loader'
}
]
})
}
...
plugins: [
new ExtractTextPlugin({
filename: 'styles.css'
}),
]
Add this to your client
build config.
link={[{rel: "stylesheet", href: "/dist/styles.css"}]}
And add a link
props to your App.jsx
to load a <link>
tag inside your <head>
tag.
So your App.jsx
render methode became:
render() {
return (
<div>
<Helmet
htmlAttributes={{lang: "en", amp: undefined}} // amp takes no value
titleTemplate="%s | React App"
titleAttributes={{itemprop: "name", lang: "en"}}
meta={[
{name: "description", content: "Server side rendering example"},
{name: "viewport", content: "width=device-width, initial-scale=1"},
]}
link={[{rel: "stylesheet", href: "/dist/styles.css"}]}/*ADD THIS*/
/>
<Switch>
<Route exact path='/' component={Homepage}/>
<Route path="/about" component={About}/>
<Route path="/contact" component={Contact}/>
</Switch>
</div>
);
}
Your client build config builds/bundles everything need for frontend. JS, CSS, Images, ... and puts this to dist folder.
Your server only serves this dist folder from root /. That's the only thing your server does (besides providing an api for example)