webpack mini-css-extract-plugin => output multiple css-files on single entry
Asked Answered
R

3

14

How can I setup webpack for my gutenberg blocks to extract multiple css files and bundle these based on the name of the stylesheets.

Zack Gordon used the Extract Text Plugin for this with webpack 3, and that worked like a charm. But with webpack 4 I had to switch to the mini-css-extract-plugin, in which I can't get this to work anymore.

See my current setup below, so you can see what I'm trying to do.

This is my project folder:

Plugin folder
|-- [src]
|   |-- [block1]
|   |   |-- block1.js
|   |   |-- style.scss
|   |   `-- editor.scss
|   |-- [block2]
|   |   |-- block2.js
|   |   |-- style.scss
|   |   `-- editor.scss
|   `-- index.js
`-- [build]
    |-- index.js
    |-- style.build.css
    `-- editor.build.css

In block1.js / block2.js:

import './style.scss'
import './editor.scss'

In index.js:

import './block1'
import './block2'

In webpack.config.js:

const defaultConfig = require("./node_modules/@wordpress/scripts/config/webpack.config");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
    ...defaultConfig,
    optimization: {
        ...defaultConfig.optimization,
        splitChunks: {
            cacheGroups: {
                style: {
                    name: 'style',
                    test: /style\.s?css$/,
                    chunks: 'all',
                    enforce: true,
                },
                editor: {
                    name: 'editor',
                    test: /editor\.s?css$/,
                    chunks: 'all',
                    enforce: true,
                },
            },
        },
    },
    plugins: [
        ...defaultConfig.plugins,
        new MiniCssExtractPlugin({
            filename: 'blocks.[name].build.css'
            }),
    ],
    module: {
        ...defaultConfig.module,
        rules: [
            ...defaultConfig.module.rules,
            {
                test: /\.s?css$/,
                exclude: /node_modules/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    'postcss-loader',
                    'sass-loader'
                ],
            },

        ]
    },
};

Expected output:

[build]
|-- blocks.editor.build.css
|-- index.js
|-- blocks.style.build.css

Current output:

[build]
|-- blocks.editor.build.css
|-- editor.js
|-- index.js
|-- blocks.style.build.css
|-- style.js
`-- (...and indentical map files)

The current setup spits out two extra js-files I don't need (style.js/editor.js), but the big problem is that it also causes the block not to load in Wordpress. It does load when I'm not using splitChunks, but then all css is bundled in a single file... and I need two.

Comparing: index.js without splitChunks:

/******/    // Load entry module and return exports
/******/    return __webpack_require__(__webpack_require__.s = "./src/index.js");
/******/ })

index.js with splitChunks:

/******/    // add entry module to deferred list
/******/    deferredModules.push(["./src/index.js","editor","style"]);
/******/    // run deferred modules when ready
/******/    return checkDeferredModules();
/******/ })
Rest answered 14/4, 2019 at 15:38 Comment(2)
It looks like the SplitChunks optimalisation adds some kind of a dependancy (deferred list), as a result the bundled index.js is useless. Anyway, luckily I found a way to bypass this issue, by adding an extra separate entry-point for the css-files in the src folder: css.js. This extra entry-point handles the imports of the css files in the blocks folders. I than removed the css imports from the index.js files in the block folders. This again adds extra useless filed to the build folder (css.js), but I can ignore that. Maybe this is not the proper way to do this, but it works for me now.Rest
I have the same issue too. For for some reason import scss inside the block1.js doest run unless I removed the import. Also most example uses extract css plugin not mini extract.Loren
R
13

See my comment, changed my setup and now it works: (OK, it's not a single entry anymore, but hey :-)

Project folder:

Plugin folder
|-- [src]
|   |-- [block1]
|   |   |-- block1.js
|   |   |-- style.scss
|   |   `-- editor.scss
|   |-- [block2]
|   |   |-- block2.js
|   |   |-- style.scss
|   |   `-- editor.scss
|   `-- index.js
|   `-- css.js
`-- [build]
    |-- index.js
    |-- style.build.css
    `-- editor.build.css

In css.js:

import './block1/style.scss'
import './block1/editor.scss'
import './block2/style.scss'
import './block2/editor.scss'

In webpack.config.js:

const defaultConfig = require("./node_modules/@wordpress/scripts/config/webpack.config");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
    ...defaultConfig,
    entry: {
        index: path.resolve( process.cwd(), 'src', 'index.js' ),
        css: path.resolve( process.cwd(), 'src', 'css.js' ),
    },
    optimization: {
        ...defaultConfig.optimization,
        splitChunks: {
            cacheGroups: {
                style: {
                    name: 'style',
                    test: /style\.s?css$/,
                    chunks: 'all',
                    enforce: true,
                },
                editor: {
                    name: 'editor',
                    test: /editor\.s?css$/,
                    chunks: 'all',
                    enforce: true,
                },
            },
        },
    },
    plugins: [
        ...defaultConfig.plugins,
        new MiniCssExtractPlugin({
            filename: 'blocks.[name].build.css'
            }),
    ],
    module: {
        ...defaultConfig.module,
        rules: [
            ...defaultConfig.module.rules,
            {
                test: /\.s?css$/,
                exclude: /node_modules/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    'postcss-loader',
                    'sass-loader'
                ],
            },

        ]
    },
};

Output build folder:

[build]
|-- blocks.editor.build.css
|-- index.js
|-- blocks.style.build.css
|-- style.js (ignore)
|-- editor.js (ignore)
|-- css.js (ignore)
`-- (...and indentical map files)
Rest answered 3/5, 2019 at 12:32 Comment(2)
You might want to check out ignore-emit-webpack-plugin to stop the extra .js files from being created. I saw it used in this answer.Overcheck
Thanks for the comment, I looked up the plugin in github: looks promising, I think I will try this out later on, when my project finishes.Rest
A
2

You don't need multiple entries actually. With the configuration in your current solution, you are only missing a single option to make it work the way you want. The filename options has to be set dynamically in base of the chunks names.

plugins: [
    new MiniCssExtractPlugin({
      filename: ({ chunk }) => `${chunk.name.replace('/js/', '/css/')}.css`,
    })
],

This will create a .css file for every chunk. Since you already have the style and editor chunks, it will create style.css and 'editor.css', each one having the corresponding css or scss imported in the js files.

Check the documentations

It would look like this, with the only addition beeing the filename change to the MiniCssExtractPlugin options, and the removal of the css entry. This should work.

module.exports = {
    ...defaultConfig,
    entry: {
        index: path.resolve( process.cwd(), 'src', 'index.js' ),
    },
    optimization: {
        ...defaultConfig.optimization,
        splitChunks: {
            cacheGroups: {
                style: {
                    name: 'style',
                    test: /style\.s?css$/,
                    chunks: 'all',
                    enforce: true,
                },
                editor: {
                    name: 'editor',
                    test: /editor\.s?css$/,
                    chunks: 'all',
                    enforce: true,
                },
            },
        },
    },
    plugins: [
        ...defaultConfig.plugins,
        new MiniCssExtractPlugin({
          filename: ({ chunk }) => `${chunk.name.replace('/js/', '/css/')}.css`,
          // chunkFilename: "[name].css"
        })
    ],
    module: {
        ...defaultConfig.module,
        rules: [
            ...defaultConfig.module.rules,
            {
                test: /\.s?css$/,
                exclude: /node_modules/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    'postcss-loader',
                    'sass-loader'
                ],
            },

        ]
    },
};
Americanism answered 21/1, 2021 at 19:39 Comment(0)
S
1

Applying the answers posted I have designed a configuration that allow package scss files that share the same name and grouping them in <name>.css the remaining are grouped in styles.css.

I defined an array at the beginning of the configuration file that contains all CSS chunk name groups.

const chucksCSS = ['style', 'editor'];

From Bornie and Raba posts, I defined a new entry to allow the usage of the name "main" as the chunk name. This step is optional.

module.exports = {
...defaultConfig,
  entry: {
    index: './src/index.jsx',
  },
...defaultConfig,
}

The properties for each CSS group are destructured inside the cacheGroups setting.

...
optimization: {
    splitChunks: {
      cacheGroups: {
        ...chucksCSS.map((name) => ({
          name,
          test: new RegExp(`${name}\\.s?css$`),
          chunks: 'all',
          enforce: true,
        })).reduce((acc, current) => {
          acc[current.name] = current;
          return acc;
        }, {}),
      },
    },
  },
...

To finish add plugin MiniCssExtractPlugin and config filename to asign the chunk name, and rename default chunk name to style for example to generate a file with the specified name.

plugins: [
    ...
    new MiniCssExtractPlugin({
      // Chunk name 'index' is set by entry point where is not recognized by any cacheGroup
      filename: ({ chunk }) => `${chunk.name === 'index' ? 'styles' : chunk.name}.css`,
    }),
    ...
],

It would look like this

const chucksCSS = ['style', 'editor'];

module.exports = {
    ...defaultConfig,
    entry: {
        index: './src/index.jsx',
    },
    optimization: {
        ...defaultConfig.optimization,
        splitChunks: {
            cacheGroups: {
                ...chucksCSS.map((name) => ({
                     name,
                    test: new RegExp(`${name}\\.s?css$`),
                    chunks: 'all',
                    enforce: true,
                })).reduce((acc, current) => {
                  acc[current.name] = current;
                  return acc;
                }, {}),
            },
        },
    },
    plugins: [
        ...defaultConfig.plugins,
        new MiniCssExtractPlugin({
          // Chunk name 'index' is set by entry point where is not recognized by any cacheGroup
          filename: ({ chunk }) => `${chunk.name === 'index' ? 'styles' : chunk.name}.css`,
        })
    ],
    module: {
        ...defaultConfig.module,
        rules: [
            ...defaultConfig.module.rules,
            {
                test: /\.s?css$/,
                exclude: /node_modules/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    'postcss-loader',
                    'sass-loader'
                ],
            },

        ]
    },
};
Sceptic answered 2/12, 2022 at 12:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.