How to emit CSS chunk for styles from HTMLWebpackPlugin chunk?
Asked Answered
S

1

7

I'm trying to use CSS modules for html, produced by html-webpack-plugin.

Code to reproduce error

src/index.ejs

<div class="<%= require('./item.css').foo %>"></div>

src/styles.css

.foo {
  color: red
}

This code, bundled with following config is somehow working (css modules hashed class names find their way to generated index.html):

webpack.config.js

const HTMLWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    {
                        loader: 'css-loader',
                        options: {
                            onlyLocals: true,
                            modules: true
                        }
                    }
                ]
            }
        ]
    },

    plugins: [
        new HTMLWebpackPlugin({
            template: 'src/index.ejs'
        })
    ]
};

But the problem is that CSS chunk is not get emmited.

% npm run build -- --mode production

> [email protected] build /Users/user/Projects/nunjucks-loader/examples/isomorphic
> webpack "--mode" "production"

Hash: 5edf5687d32d114e6469
Version: webpack 4.41.5
Time: 526ms
Built at: 01/02/2020 10:51:04 PM
     Asset       Size  Chunks             Chunk Names
index.html   98 bytes          [emitted]  
   main.js  930 bytes       0  [emitted]  main
Entrypoint main = main.js
[0] ./src/index.js 1.43 KiB {0} [built]
Child html-webpack-plugin for "index.html":
     1 asset
    Entrypoint undefined = index.html
    [0] ./node_modules/html-webpack-plugin/lib/loader.js!./src/index.ejs 283 bytes {0} [built]
    [2] (webpack)/buildin/global.js 472 bytes {0} [built]
    [3] (webpack)/buildin/module.js 497 bytes {0} [built]
    [4] ./src/item.css 68 bytes {0} [built]
        + 1 hidden module

When i add mini-css-extract-plugin to config to get CSS chunks emmited, it throws error:

webpack.config.js

@@ -1,5 +1,4 @@
 const HTMLWebpackPlugin = require('html-webpack-plugin');
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');

 module.exports = {
     module: {
@@ -23,9 +22,6 @@ module.exports = {
             {
                 test: /\.css$/,
                 use: [
+                    {
+                        loader: MiniCssExtractPlugin.loader
+                    },
                     {
                         loader: 'css-loader',
                         options: {
@@ -40,11 +36,7 @@ module.exports = {

     plugins: [
         new HTMLWebpackPlugin({
+            template: 'src/index.ejs',
+        }),
+        new MiniCssExtractPlugin(),
-            template: 'src/index.ejs'
-        })
     ]
 };
% npm run build -- --mode production

> [email protected] build /Users/user/Projects/nunjucks-loader/examples/isomorphic
> webpack "--mode" "production"

/Users/user/Projects/nunjucks-loader/examples/isomorphic/node_modules/neo-async/async.js:16
    throw new Error('Callback was already called.');
    ^

Error: Callback was already called.
    at throwError (/Users/user/Projects/nunjucks-loader/examples/isomorphic/node_modules/neo-async/async.js:16:11)
    at /Users/user/Projects/nunjucks-loader/examples/isomorphic/node_modules/neo-async/async.js:2818:7
    at process._tickCallback (internal/process/next_tick.js:61:11)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] build: `webpack "--mode" "production"`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the [email protected] build script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/user/.npm/_logs/2020-01-02T19_53_48_953Z-debug.log

When loading same template by importing it from JS file, everything works as expected.

UPDATE. Temporary solution is to have 2 rulesets, one for template (w/out mini-css-extract-plugin and onlyLocals), and one for js, with regular set of rules, and duplicate same style import in JS.

It's not ideal, but produce html + css modules.

module.exports = {
    module: {
        rules: [
            {
                test: /\.css$/,
                oneOf: [
                    {
                        issuer: /\.ejs$/,
                        use: [{
                            loader: 'css-loader',
                            options: {
                                onlyLocals: true,
                                modules: true
                            }
                        }]
                    },

                    {
                        issuer: /\.js$/,
                        use: [
                            {
                                loader: MiniCssExtractPlugin.loader
                            },
                            {
                                loader: 'css-loader',
                                options: {
                                    modules: true
                                }
                            }
                        ]
                    }
                ]
            }
        ]
    }
};
import './item.css';
Slavocracy answered 2/1, 2020 at 19:25 Comment(5)
Hm, I got here by googling same error, could it be that there is a bug in a recent mini-css-extract-plugin release v0.9.0? (considering the question was pasted just a week ago)Collin
Related discussion: github.com/webpack-contrib/mini-css-extract-plugin/issues/489Collin
@ArturKlesun seems like "callback" error was first introduced in 0.8.1, at 0.8.0 there is another error thrown.Slavocracy
I have post console output to GitHub issue above.Slavocracy
Adds temporary hack to get something that looks like what i want to achieve.Slavocracy
C
1

I was able to deal with "Callback was already called" error by using file-loader + extract-loader instead of mini-css-extract-plugin:

config.module.rules.push({
    test: /\.html?$/,
    use: [{
        loader: 'html-loader',
        options: {
            attrs: ['link:href', 'img:src']
        },
    }],
});
config.module.rules.push({
    test: /\.css(\?.*)?$/,
    use: [{
        loader: 'file-loader',
        options: {
            name: 'css/[name].[contenthash].[ext]',
        },
    }, 'extract-loader', 'css-loader'],
});

Though my case was a bit different: my entry point was a plain html file with <link href="..."/> references, not ejs, and I sadly did not manage to make it work with your gist due to lack of knowledge of ejs. But I hope my solution may be of help.

Collin answered 9/1, 2020 at 16:56 Comment(1)
It emits CSS, but on the other hand it returns file path, instead of module ids, that i really need to wire file and template.Slavocracy

© 2022 - 2024 — McMap. All rights reserved.