Search code examples
reactjswebpackserver-side-renderingcss-modulesreact-helmet

CSS Modules export CSSes as <style> tag in head tag with disabled sourceMap


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.

Shitty Loading CSS as style tag inside head tag because of setting sourceMaps to false

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:

Results after setting sourceMaps to true

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


Solution

  • 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)