Github storage address: github.com/Evelynzzz/r…

Version: Webpack 4.39.1

Related dependencies:

  • MiniCssExtractPlugin: 0.8.0
  • Style – loader: 1.0.0
  • Less – loader: 5.0.0
  • Postcss – loader: 3.0.0
  • CSS – loader: 3.2.0

Decide whether it is development mode or production mode

When configuring Webpack, you need to distinguish between development mode and production mode. For example, we only need to compress CSS in production mode; While developing patterns, we want to generate Sourcemap for easy debugging and style hot updates. So, how to determine the development and production mode in webpack.config.js?

I usually define three WebPack configuration files:

  • Webpack.config.base. js: generic configuration, such as entry, exit, plug-in, loader, etc. The following two configuration files import this configuration and then modify it to add additional configurations.
  • Webapck.config.dev. js: In development mode, start webpack-dev-server.
  • Webapck.config.prod. js: compiled and packaged in production mode.

Then configure the start and build scripts in package.json, respectively:

{
    "scripts": {
        "start": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.dev.js --open"."build": "cross-env NODE_ENV=production webpack --config webpack.config.prod.js --progress --colors -p"}}Copy the code

Note that NODE_ENV is defined in the command, so process.env.node_env can be obtained in webpack.config.base.js to determine whether production mode or development mode is used.

const devMode = process.env.NODE_ENV === 'development'; // Is it development mode
Copy the code

Let’s get down to business.

Extract CSS into a separate file

Prior to Webpack 4, we used the extract-text-webpack-plugin to extract the style files introduced in the project and package them into a separate file. Since Webpack 4, this plug-in is obsolete and requires the MiniCssExtractPlugin.

This plugin extracts CSS into separate files. It creates a CSS file per JS file which contains CSS. It supports On-Demand-Loading of CSS and SourceMaps.

This plug-in creates a separate CSS file for each JS file that contains CSS and supports on-demand loading of CSS and SourceMap.

Note: every JS file that contains CSS does not mean the component’s corresponding JS file, but the packaged JS file! More on that later.

The situation a

Let’s start with an example of basic configuration. Webpack. Config. Js:

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css'}),].module: {
    rules: [{test: /\.css$/.use: [
          MiniCssExtractPlugin.loader, 'css-loader'.'postcss-loader' / / postcss - loader is optional
        ],
      },{
        test: /\.less$/.use: [
          MiniCssExtractPlugin.loader, 'css-loader'.'postcss-loader'.'less-loader' / / postcss - loader is optional],}],},};Copy the code

Based on the above configuration, Root introduces Topics if Root is referenced in the entry app.js. The root. js reference style main. CSS and the Topics. Js reference Topics.

// import file app.js
import Root from './components/Root'

// Root.js
import '.. /styles/main.less'
import Topics from './Topics'

// Topics.js
import ".. /styles/topics.less"
Copy the code

In this case, Topics will be on the same chunk as Root, so they will all be packaged together in app.js. As a result, main.less and Topics. Less will be extracted in one file: app.css. Instead of generating two CSS files.

            Asset       Size  Chunks                    Chunk Names
          app.css  332 bytes       1  [emitted]         app
           app.js    283 KiB       1  [emitted]  [big]  app
Copy the code

Scene two

However, if root.js does not introduce Topics directly, but instead has code splitting configured, such as dynamic introduction of modules, then the result is different:

Asset Size Chunks Chunk Names app.css 260 bytes 1 [emitted] app app.js 281 KiB 1 [emitted] [big] app topics.bundle.js 2.55 KiB 4 [emitted] topics topics. CSS 72 bytes 4 [emitted] topicsCopy the code

At this time, there are two chunks corresponding to two JS files, so the CSS in these two JS files will be extracted to generate the corresponding files. This is what “creating a separate CSS file for each JS file that contains CSS” really means.

Scenario 3

But what if you split chunks and still want to generate only one CSS file? It can be done. But needed a Webpack configuration optimization. SplitChunks. CacheGroups.

What does optimization.splitchunks do? Prior to Webpack 4, we used the CommonsChunkPlugin to extract repeatedly introduced third-party dependencies, such as React and Jquery, into a single file. Starting with Webpack 4, the CommonsChunkPlugin was replaced by Optimization.splitchunks. As can be seen from its name, it is used to split chunks. Why do I need this configuration here? Let’s take a look at the configuration:

optimization: {
  splitChunks: {
    cacheGroups: {
      // Extracting all CSS/less in a single file
      styles: {
      	name: 'styles'.test: /\.(c|le)ss$/.chunks: 'all'.enforce: true,},}}},Copy the code

Packing results:

Asset Size Chunks Chunk Names app.js 281 KiB 2 [emitted] [big] app styles.bundle.js 402 bytes 0 [emitted] styles Styles. CSS 332 bytes 0 [emitted] styles toughies.bundle. js 2.38 KiB 5 [emitted] topicsCopy the code

As you can see, styles are indeed extracted into a styles.css file. But at the same time have one more style. The bundle. Js file, this is optimization. SplitChunks. CacheGroups effect. The specific principles are not here to explore, if you are interested in it.

MiniCssExtractPlugin vs. style-loader

The MiniCssExtractPlugin extracts the CSS introduced in JS and packages it into a separate file, which is then added to the header with the tag . Style-loader inserts CSS directly into the DOM with the

In general, the basic CSS configuration looks something like this. First style-loader, then CSS-loader.

module: {
    rules: [{test: /\.css$/.use: [
                'style-loader'.'css-loader'],},],}Copy the code

Later, however, to extract CSS into a separate file, the MiniCssExtractPlugin was used. So the question is, is the following configuration feasible?

{
    test: /\.css$/.use: [
            'style-loader', MiniCssExtractPlugin.loader, 'css-loader'.'postcss-loader'],}Copy the code

Production mode

According to the MiniCssExtractPlugin documentation, this plugin is suitable for production modes without style-loader and development modes that require HMR.

This plugin should be used only on production builds without style-loader in the loaders chain, especially if you want to have HMR in development.

In other words, in production mode, the above configuration using both style-loader and MiniCssExtractPlugin is not appropriate (try style-loader, it doesn’t work).

We can only take one or the other. You can also use a combination of style-loader in development mode and MiniCssExtractPlugin in production mode. Take what you want, and the two are still very different.

{
	test: /\.css$/.use: [
		devMode?'style-loader':MiniCssExtractPlugin.loader,'css-loader'.'postcss-loader']}Copy the code

Style file Hot Update (HMR)

As can be seen from the above quote, in development mode we can use the MiniCssExtractPlugin to implement style HMR (Hot Module Replacement).

What does the HMR of a style file mean? If the HMR is not configured, the page does not automatically refresh when the CSS source file is modified in development mode. You need to manually refresh the page to load the changes. HMR, on the other hand, enables hot updates of modified modules, so that changes are displayed instantly on the page, eliminating the need to refresh the entire page.

But style-loader also implements the HMR interface, as described In the Wepack document In a Module:

HMR is an opt-in feature that only affects modules containing HMR code. One example would be patching styling through the style-loader. In order for patching to work, the style-loader implements the HMR interface; when it receives an update through HMR, it replaces the old styles with the new ones.

Therefore, in the development environment, both plug-ins can be hot updated CSS, but the Configuration of the MiniCssExtractPlugin may be richer. For example: style-loader only hot updates the styles introduced in JS, if index.html introduces a CSS file from the server via :

<link rel="stylesheet" href="/vendors/test.css">
<! Copy /vendors/test.css is copied to the server root directory at packaging time by configuring the copy-webpack-plugin, so it can be so simple -->
Copy the code

If you are in development mode and modify the source code of test.css, style-loader does not update the CSS hot, but needs to refresh the entire page, but MiniCssExtractPlugin automatically reloads all styles. There may be other differences, which are not detailed here.

The MiniCssExtractPlugin can configure the HMR of Less files as follows:

const devMode = process.env.NODE_ENV === 'development'; // Is it development mode
/ /...
module.exports = {
    / /...
    module: {
      rules:[
        {
          test: /\.less$/i.use: [{loader: MiniCssExtractPlugin.loader,
              options: {
                // Enable hot updates only in development mode
                hmr: devMode,
                // If module hot update does not work, reload all styles
                reloadAll: true,}},'css-loader'.'postcss-loader'.'less-loader']},/ /...]}}Copy the code

Refer to the reading

  • MiniCssExtractPlugin document: webpack.js.org/plugins/min…
  • Webpack document Loading of CSS: webpack.js.org/guides/asse…
  • Style – loader documentation: webpack.js.org/loaders/sty…
  • Webpack document Hot Module Replacement: webpack.js.org/concepts/ho…