18. Configurations in different environments

There are two main ways to create different environment configurations. The first method is to add the corresponding criteria to the configuration file, and then export different configurations based on the environment. The second is to have a configuration file for each environment, ensuring that there is a corresponding configuration file for each environment.

First, look at the way you can add judgments to a configuration file. Webpack’s configuration file supports exporting a function that returns the desired configuration object. The function takes two arguments, the first is env, the environment name argument passed through the CLI, and the second is argv, which refers to all arguments passed through the CLI.

We can define the development mode configuration in the config variable and check if env equals production. For production, set the mode property field to Production, set devTool to false, and add cleanWebpackPlugin and CopyWebpackPlugin.

const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = (env, argv) = > {
    const config = {
        entry: './src/main.js'.output: {
            filename: 'bundle.js'.path: path.join(__dirname, 'dist'),
            publicPath: 'dist/'
        },
        devtool: 'cheap-eval-module-source-map'.devServer: {
            hot: true.contentBase: 'public'
        },
        module: {
            rules: [{test: /.js$/,
                    use: {
                        loader: 'babel-loader'.options: {
                            presets: ['@babel/preset-env'}}} {test: /.css$/,
                    use: [
                        'style-loader'.'css-loader'] {},test: /.png$/,
                    use: 'url-loader'
                },
                {
                    test: /.html$/,
                    use: {
                        loader: 'html-loader'.options: {
                            attrs: ['img:src'.'a:href']}}}]},plugins: [
            new HtmlWebpackPlugin({
                title: 'webpack'.template: './src/index.html'
            }),
            new webpack.HotModuleReplacementPlugin(),
        ]
    }
    if (env === 'production') {
        config.mode = 'production';
        config.devtool = false;
        config.plugins = [
            ...config.plugins,
            new CleanWebpackPlugin(),
            new CopyWebpackPlugin(['public'])]}return config;
}
Copy the code
yarn webpack

yarn webpack --env producton
Copy the code

This method of returning different configuration objects by judging the environment name parameter is only suitable for small and medium-sized projects, because as the project becomes complex the configuration file becomes complex. Therefore, for large projects, it is recommended to use different configuration files for different environments, usually at least three WebPack configuration files.

Two of these are used for different environments, and one is a common configuration. Not all configurations in development and production environments are completely different, and a common file is needed to abstract the same configuration between the two.

Start by creating webPack.common.js in the project directory to store the common configuration.

module.exports = {
    entry: './src/main.js'.output: {
        filename: 'bundle.js'.path: path.join(__dirname, 'dist'),
        publicPath: 'dist/'
    },
    devtool: 'cheap-eval-module-source-map'.devServer: {
        hot: true.contentBase: 'public'
    },
    module: {
        rules: [{test: /.js$/,
                use: {
                    loader: 'babel-loader'.options: {
                        presets: ['@babel/preset-env'}}} {test: /.css$/,
                use: [
                    'style-loader'.'css-loader'] {},test: /.png$/,
                use: 'url-loader'
            },
            {
                test: /.html$/,
                use: {
                    loader: 'html-loader'.options: {
                        attrs: ['img:src'.'a:href']}}}]},plugins: [
        new HtmlWebpackPlugin({
            title: 'webpack'.template: './src/index.html'
        }),
        new webpack.HotModuleReplacementPlugin(),
    ]
}
Copy the code

Then create webpack.dev.js and webpack.prod.js to define special configurations for development and production, respectively.

The production configuration (webpack.prod.js) imports the common configuration object first. You can use the webpack-merge method to copy the common configuration object into this configuration object, overwriting some of the common configuration through the last object.

yarn webpack-merge --dev
Copy the code

const common = require('./webpack.common');
const merge = require('webpack-merge');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = merge(common, {
    mode: 'production'.plugins: [
        new CleanWebpackPlugin(),
        new CopyWebpackPlugin()
    ]
})

Copy the code

In the same way, the webpack.dev.js file can implement some additional configuration in such a way that it won’t be repeated here.

When running WebPack, you specify the configuration file to use with the –config parameter.

yarn webpack --config webpack.prod.js
Copy the code

19. DefinePlugin

The new Production mode in Webpack4X automatically enables many common optimization features internally. The first is a plug-in called define-Plugin that injects global members into your code. In production mode, the plugin is enabled by default and injects a constant of process.env.node_env into the code. Many third-party modules use this member to determine the current operating environment and decide whether or not to perform operations.

Define – Plugin is a built-in plug-in, which is imported into the WebPack module first. Plugins are added to the plugins array. The constructor of the plug-in receives an object, and each key in the object is injected into the code.

Here you define API_BASE_URL to inject the API service address for the code, with the value as the string https://api.github.com.

const webpack = require('webpack');

module.exports = {
    mode: 'node'.entry: './src/main.js'.output: {
        filename: 'bundle.js'
    },
    plugins: [
        new webpack.DefinePlugin({
            API_BASE_URL: 'https://api.github.com'}})]Copy the code

Print the API_BASE_URL in the code. Running the Webpack package, you can see that the define-Plugin simply replaces the values of the injected members directly into the code. Define – Plugin is not designed just to replace data; the string passed is actually a snippet of code, that is, a piece of code that complies with JS syntax.

const webpack = require('webpack');

module.exports = {
    mode: 'node'.entry: './src/main.js'.output: {
        filename: 'bundle.js'
    },
    plugins: [
        new webpack.DefinePlugin({
            API_BASE_URL: JSON.stringify('https://api.github.com')]}})Copy the code

20. Tree Shaking

Tree-shaking literally means shaking a Tree. When shaking a Tree, dead branches and leaves fall from it. But this is the part of the code that you don’t use, which is technically called dead-code.

Webpack production mode optimization can automatically detect unreferenced code in your code and remove it. For example, the components.js file exports several functions, each of which emulates a component. The button component function executes a console.log statement after the return, which is clearly unreferenced code.

export const Button = () = > {
    return document.createElement('button')
    console.log('dead-code');
}

export const Link = () = > {
    return document.createElement('a')}export const Heading = level= > {
    return document.createElement('h' + level)
}
Copy the code

Components are imported into the index.js file, except for the button member from components. This results in many areas of the code that are not needed. These areas are redundant for packaging results, and tree-shaking is used to remove the redundant code.

import { Button } from './components'

document.body.appendChild(Button())

Copy the code
yarn webpack --mode production
Copy the code

When bundle.js is packaged, you can see that redundant code is not output at all. Tree-shaking is automatically enabled in production mode.

It is important to note that tree-shaking is not a configuration option in webpack, it is a set of functions that are used together.

Go back to the command line terminal and run the WebPack package, but this time using None means not turning on any of the built-in features and plug-ins. The link function and heading function in the bundle.js file output after the package is completed are exported even though they are not used externally.

yarn webpack
Copy the code

Obviously these exports don’t make sense, you can remove them with some optimization functionality, open the WebPack configuration file and add optimization properties. This property centralizes some of the optimizations within WebPack. You can enable usedExports to export only the external members in the output.

module.exports = {
    mode: 'none'.entry: './src/index.js'.output: {
        filename: 'bundle.js'
    },
    optimization: {
        usedExports: true}}Copy the code

In this case, the corresponding functions of the Components module will not export the two functions of link and heading. You can enable the code compression function of Webpack to compress these unused codes. The configuration file turns on minimize in Optimization.

module.exports = {
    mode: 'none'.entry: './src/index.js'.output: {
        filename: 'bundle.js'
    },
    optimization: {
        usedExports: true.minimize: true}}Copy the code

At this point, unreferenced code in bundle.js is removed, which is tree-shaking implementation. The whole process uses two optimizations, one is usedExports and the other is minimize.

If you really think of code as a big tree, you can usedExports to mark the tree with dead leaves and branches, and you can take care to shake them all off.

The common result of packaging is to put each module in a separate function, and if there are many modules it means that there are many module functions in the output. ConcatenteModules can package all modules into a single function. To enable concatenateModules in configuration files, disable minimize and repack in order to see better results.

module.exports = {
    mode: 'none'.entry: './src/index.js'.output: {
        filename: 'bundle.js'
    },
    optimization: {
        usedExports: true.concatenateModules: true.// minimize: true}}Copy the code

Instead of a module corresponding to a function, bundle.js puts all modules into the same function. The role of concatnateModules is to combine as many modules as possible and output them into a single function. This improves efficiency and reduces code volume. This feature is called Scope reactive, and it was added in webpack3. If you take advantage of it, the code size will decrease a lot.

Because early Webpack was growing so fast and changing so much, the results you get when you look for material are not necessarily applicable to the version you’re currently using, especially for tree-shaking material. Many sources say that using babel-loader causes tree-shaking to fail. A unified explanation for this problem.

The first thing to be clear about is the implementation of tree-shaking. The premise is that code must be organized using ES Modules, that is, the code that is handed to WebPack must be modularized using ES Modules.

Before packaging all modules, Webpack first sends modules to different Loaders for processing according to their configuration, and finally packages the results of all loader processing together. In order to convert new ECMAScript features in code, I often choose babel-loader to handle JS. It is possible to convert ES Modules into Commonjs when Babel converts code, depending on whether there is a plugin that converts ES Modules.

For example, the @babel/preset-env set used in the project has this set, so the ES Modules part of the code is converted to Commonjs for preset-env. Webpack gets code that is organized in Commonjs so tree-shaking does not work.

To make it easier to separate the results, just turn on usedExports and repackage to see bundle.js.

module.exports = {
    mode: 'none'.entry: './src/index.js'.output: {
        filename: 'bundle.js'
    },
    modules: {
        rules: [{test: /\.js$/,
                use: {
                    loader: 'babel-loader'.options: {
                        presets: ['@babel/preset-env']}}}]},optimization: {
        usedExports: true.// concatenateModules: true,
        // minimize: true}}Copy the code

UsedExports does not work as usual. If compression is enabled, unused code will still be removed tree-shaking is not invalid.

In the latest version of babel-loader, ES Modules conversion has been turned off. You can find the babel-Loader module in node_modules, which indicates in the injectCaller file that the current environment supports ES Modules. Then find the preset-env Module, which can be found in 200 + lines, where ES Module conversion is disabled according to the identifier in the object Caller.

So when webPack is finally packaged, it still gets ES Modules code, and tree-shaking is working, of course, just finding the relevant information in the source code. Take a look at the source code for Babel if you need a closer look.

Try forcing the plugin on in Babel’s Preset configuration to try it out. But preset is configured in a special way, by redefining the members of the preset array as an array, and the first member of the array is the name of preset to be used. The second member is an object defined for this preset, which cannot be mistaken as an array.

Set the modules property of the object to Commonjs. The default value of this property is auto. Setting it to CommonJS means that you need to force the use of Babel’s ES Modules plugin to convert the ES Moudle in your code to CommonJS.

module.exports = {
    mode: 'none'.entry: './src/index.js'.output: {
        filename: 'bundle.js'
    },
    modules: {
        rules: [{test: /\.js$/,
                use: {
                    loader: 'babel-loader'.options: {
                        presets: [['@babel/preset-env', { modules: 'commonjs'}],]}}}]},optimization: {
        usedExports: true.// concatenateModules: true,
        // minimize: true}}Copy the code

After the package is packaged, you will find that the usedExports configured just now will not take effect. Tree-shaking does not work even if you turn on the compression code.

21. sideEffects

A new feature called sideEffects has also been added to weboack4. This allows for greater compression for tree-shaking by allowing code to be configured to identify side effects. A side effect is whether the module does anything other than export members, a feature that is typically only used when developing NPM modules.

Because sideEffects is confused with tree-shaking, many people think it is causal. They really don’t have that much to do with each other.

Here is a scenario where Side Effects can work. Based on the example above, components is split into several component files. Then export it centrally in index.js for external import. Go back to the entry file and import the Button member from Components.

This is a problem because it loads index.js in the components directory, which loads all the component modules. This will cause only the Button component to be loaded, but all component modules will be loaded and executed. Looking at the packaging results, you can see that the modules of all components are indeed packaged, and the Side Effects feature can be used to solve such problems.

Open the WebPack configuration file. In Optimization, turn on the property sideEffects: True, which is automatically enabled in Production mode.

{
    optimization: {
        sideEffects: true
        // usedExports: true,
        // concatenateModules: true,
        // minimize: true}}Copy the code

After this function is enabled, webpack will first check the package.json of the current code to see if there is any sideEffects in the module.

If this module has no side effects, the unused modules are no longer packaged. You can open package.json and add a sideEffects field set to false.

{
    "sideEffects": false
}
Copy the code

Look at the bundle.js file after you’ve packaged it, and the unused modules are no longer packaged. That’s what sideEffects does. The prerequisite for using sideEffects is to make sure that the code has no sideEffects, otherwise the code with sideEffects will be deleted by mistake when webpack is packaged.

For example, there is a extend.js file. No members are exported from this file. Just mount a pad method on the prototype of the Number object to add the leading 0 to the Number.

Number.prototype.pad = functuon(size) {
    let result = this + ' ';
    while (result.lengtj < size>) {
        result = '0' + result
    }
    return result;
}
Copy the code

Go back to the entry file and import extend.js, then you can use the extension methods it provides for Number.

import './extend.js';

console.log((8).pad(3));
Copy the code

Extending methods for Number is a side effect of the extend module, because when the module is imported, a method is added to the prototype of Number.

If you also indicate that all code in the project has no side effects, you will find that the extension operation you just did is not packaged. In addition to the code loaded CSS modules, also belong to the side effects of the module, will also face the same kind of problem.

The solution is to turn off the side effects flag in package.json, or to indicate which files in the current project have side effects so that WebPack doesn’t ignore them.

You can change the false of sideEffects to an array by opening package.json. Then add the exten.js file path, and the global.css file path, which can be configured using path wildcards. *. The CSS.

{
    "sideEffects": [
        "./src/extend.js"."./src/global.css"]}Copy the code

22. Code segmentation

A reasonable packaging solution would be to separate the packaging results into bundles according to certain rules, and load these modules according to the application running on demand. This can greatly improve the response time and efficiency of the application. To solve this problem, WebPack supports the ability to package modules into different bundles according to the designed rules to improve application responsiveness.

At present, There are two main ways to realize subcontracting in Webpack. The first way is to configure different packaging entrances according to the business, that is, there will be multiple packaging entrances at the same time, and then multiple packaging results will be output. The second method is to use the dynamic import function of ES Module to load modules on demand. In this case, WebPack will automatically output the Module to a bundle separately.

1. Multi-entry packing

Multi-entry packaging generally applies to traditional multi-page applications. The most common partition rule is that one page corresponds to one entry of packaging, and the common parts between different pages are extracted into the common results. It’s very simple to use.

Here there are two pages respectively index and album. Index. js is responsible for implementing all functions of index page, and albu.js is responsible for implementing all functions of album page. Global.css and fetch.

General entry in the configuration file attributes can only configure a file path, that is to say, can only configure a packaged entrance, if we configure multiple entry, entry can be defined as an object, a property of objects is the entrance to all the way, it is the entrance we attribute name, the name of the value is the entrance of the file path.

Once configured as multiple entries, the output filename also needs to be changed. Two entries means two packing results. You can add [name] to the filename attribute to dynamically output the filename, which will eventually be replaced with the entry name index and album.

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = allModes.map(item= > {
    return {
        mode: 'none'.entry: {
            index: './src/index.js'.album: './src/album.js',},output: {
            filename: `[name].bundle.js`,},module: {
            rules: [{test: /\.css$/,
                    use: [
                        'style-loader'.'css-loader']]}},plugins: [
            new CleanWebpackPlugin(),
            new HtmlWebpackPlugin({
                template: './src/index.html'.filename: `index.html`
            })
            new HtmlWebpackPlugin({
                template: './src/album.html'.filename: `album.html`})]}})Copy the code

The HTML output plug-in in the configuration file needs to specify the bundle used by the output HTML, which can be set using the chunk attribute. Each bundle entry forms an independent chunk, and the two pages are configured with different chunks.

[
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
        template: './src/index.html'.filename: `index.html`.chunk: ['index']})new HtmlWebpackPlugin({
        template: './src/album.html'.filename: `album.html`.chunk: ['album'"})"Copy the code

2. Extract the public module

Multi-entry packaging itself is very easy to understand and use, but there is a small problem that there must be some common parts in the different packaging portals. For example, global.css and fetch. Js are common modules in the index entry and album entry. These common modules need to be extracted into a separate bundle. The way to implement common module extraction in WebPack is as simple as enabling splitChunks in an optimized configuration.

The configuration file adds the splitChunks property in optimization, which requires the chunks property to be set to all to indicate that all common modules will be extracted into a separate bundle.

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = allModes.map(item= > {
    return {
        mode: 'none'.entry: {
            index: './src/index.js'.album: './src/album.js',},output: {
            filename: `[name].bundle.js`,},optimization: {
            splitChunks: {
                chunks: 'all'}}module: {
            rules: [{test: /\.css$/,
                    use: [
                        'style-loader'.'css-loader']]}},plugins: [
            new CleanWebpackPlugin(),
            new HtmlWebpackPlugin({
                template: './src/index.html'.filename: `index.html`.chunk: ['index']})new HtmlWebpackPlugin({
                template: './src/album.html'.filename: `album.html`.chunk: ['album']})]}})Copy the code

After packaging, an additional JS file will be generated in my dist directory, which is the module part common to the two entries index and album.

3. Dynamic import

Loading on demand is a very common requirement in developing browser applications. Generally, loading on demand refers to loading data. The loading on demand here refers to the loading of a module when it is needed during the running of the application, which can greatly save our bandwidth and traffic.

Webpack supports the use of dynamic on-demand loaded into the way of module, all the dynamic import module automatically extracted into a separate bundles so as to realize the subcontract, compared to the entrance to the more dynamic import this way he is more flexible, can through the logic control need to load a module code, or when loading a module. The purpose of subcontracting is to make modules load on demand to improve application responsiveness.

Here we have an on-demand loading scenario. In the main area of the page, if you visit the articles page, you get a list of articles, and if you visit the albums page, you get a list of albums. The article list corresponds to the POST component and the album list corresponds to the album component. Import both modules in the packaging entry. The logic here is to determine which component to display based on the value of the anchor point when the anchor point changes.

Dynamic import is the dynamic import in ES Module standard. Where dynamic import is needed, import the specified path through the import function. This method returns a Promise, and the then method of the Promise takes the module object.

// import posts from './posts/posts';
// import album from './album/album';

const render = () = > {
    const hash = locaton.hash || '#posts';

    const mainElement = document.querySelector('.main');

    mainElement.innerHTML = ' ';

    if (hash === '#posts') {
        // mainElement.appendChild(posts());
        import('./posts/posts').then(({ default: posts}) = >{ mainElement.appendChild(posts()); })}else if (hash === '#album') {
        // mainElement.appendChild(album());
        import('./album/album').then(({ default: album}) = > {
            mainElement.appendChild(album());
        })
    }
}

render();

window.addEventListener('hashchange', render);
Copy the code

After packaging, the dist directory will have three more JS files, which are actually generated by dynamic import automatic subcontracting. These files are bundles extracted from the two modules just imported and from the common parts of the two templates. There is no need to configure any part of the process, just import the Module in the same way that ES Module dynamically imports members. Subcontracting and loading on demand are handled automatically within WebPack.

4. Magic notes

By default, the name of the bundle generated by dynamic import is just a serial number, which is not bad because most of the time in production you don’t care what the name of the resource file is. However, if you need to name these bundles you can do so using webPack-specific template comments.

This is done by adding an in-line comment to the parameter of the import function with a specific format /* webpackChunkName: name */ so that the bundle generated by the subcontract can be named.

// import posts from './posts/posts';
// import album from './album/album';

const render = () = > {
    const hash = locaton.hash || '#posts';

    const mainElement = document.querySelector('.main');

    mainElement.innerHTML = ' ';

    if (hash === '#posts') {
        // mainElement.appendChild(posts());
        import(/* webpackChunkName: posts */'./posts/posts').then(({ default: posts}) = >{ mainElement.appendChild(posts()); })}else if (hash === '#album') {
        // mainElement.appendChild(album());
        import(/* webpackChunkName: album */'./album/album').then(({ default: album}) = > {
            mainElement.appendChild(album());
        })
    }
}

render();

window.addEventListener('hashchange', render);
Copy the code

If the chunknames are the same, the same chunknames will eventually be packaged together.

// import posts from './posts/posts';
// import album from './album/album';

const render = () = > {
    const hash = locaton.hash || '#posts';

    const mainElement = document.querySelector('.main');

    mainElement.innerHTML = ' ';

    if (hash === '#posts') {
        // mainElement.appendChild(posts());
        import(/* webpackChunkName: components */'./posts/posts').then(({ default: posts}) = >{ mainElement.appendChild(posts()); })}else if (hash === '#album') {
        // mainElement.appendChild(album());
        import(/* webpackChunkName: components */'./album/album').then(({ default: album}) = > {
            mainElement.appendChild(album());
        })
    }
}

render();

window.addEventListener('hashchange', render);
Copy the code

23. MiniCssExtractPlugin

The MiniCssExtractPlugin is a plug-in that extracts CSS code from the packaging result, enabling on-demand loading of CSS modules.

yarn add mini-css-extract-plugin
Copy the code

Opening the webPack configuration file requires importing the plug-in first, and after importing it you can add the plug-in to the plugins array of the configuration object. The MiniCssExtractPlugin automatically extracts CSS from your code into a separate file when it works.

In addition to the current use of style modules are first handed to CSS-loader parsing, and then handed to style-loader processing. Style-loader is used to inject style code into the page as a style tag to make the style work. With the MiniCssExtractPlugin, styles are stored separately in the file, so there is no need to import style tags directly, so there is no need for style-loader. Instead, the MiniCssExtractPlugin provides a loader that implements style file injection via link tag.

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = allModes.map(item= > {
    return {
        mode: 'none'.entry: {
            main: './src/index.js',},output: {
            filename: `[name].bundle.js`,},module: {
            rules: [{test: /\.css$/,
                    use: [
                        MiniCssExtractPlugin.loader,
                        // 'style-loader',
                        'css-loader']]}},plugins: [
            new CleanWebpackPlugin(),
            new HtmlWebpackPlugin({
                template: './src/index.html'.filename: `index.html`
            }),
            new MiniCssExtractPlugin()
        ]
    }
})
Copy the code

Repackage the dist directory and see the extracted style file. If the size of the style file is not very large, it is not recommended to extract it into a single file. Generally, CSS files exceed about 150kb and you need to consider whether to extract it into a single file. Otherwise, CSS embedded in the code may be better with less than one request.

24. OptimizeCssAssetsWebpackPlugin

Using the MiniCssExtractPlugin the style files can then be extracted into a separate CSS file but there is also a minor problem. The command line runs packaging in production mode.

yarn webpack --mode production
Copy the code

As you know, WebPack automatically compresses the output in production mode. But here you will find that the style file has not changed at all. This is because webPack’s built-in compression plug-in is only for JS file compression, and additional plug-in support is required for other resource file compression. Optimize – CSS – Assets -webpack-plugin

yarn add optimize-css-assets-webpack-plugin
Copy the code

The plug-in needs to be imported into the configuration file first, and then added to the plugins array.

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = allModes.map(item= > {
    return {
        mode: 'none'.entry: {
            main: './src/index.js',},output: {
            filename: `[name].bundle.js`,},module: {
            rules: [{test: /\.css$/,
                    use: [
                        MiniCssExtractPlugin.loader,
                        // 'style-loader',
                        'css-loader']]}},plugins: [
            new CleanWebpackPlugin(),
            new HtmlWebpackPlugin({
                template: './src/index.html'.filename: `index.html`
            }),
            new MiniCssExtractPlugin(),
            new OptimizeCssAssetsWebpackPlugin()
        ]
    }
})
Copy the code

The packaged style file is then output as a compressed file. There is an additional wrinkle, however. In the official documentation this plug-in is not configured in the plugins array but added to the minimize property of the Optimization property. If you configure this plug-in into the Plugins array, it will work in all cases. Configuration in a minimize array only works when the minimize property is on.

Webpack recommends that plug-ins of this compression class be configured into the minimize array so that they can be controlled uniformly through the minimize option.

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = allModes.map(item= > {
    return {
        mode: 'none'.entry: {
            main: './src/index.js',},output: {
            filename: `[name].bundle.js`,},optimization: {
            minimize: [
                new OptimizeCssAssetsWebpackPlugin()
            ]
        },
        module: {
            rules: [{test: /\.css$/,
                    use: [
                        MiniCssExtractPlugin.loader,
                        // 'style-loader',
                        'css-loader']]}},plugins: [
            new CleanWebpackPlugin(),
            new HtmlWebpackPlugin({
                template: './src/index.html'.filename: `index.html`
            }),
            new MiniCssExtractPlugin(),
            new OptimizeCssAssetsWebpackPlugin()
        ]
    }
})
Copy the code

The plugin won’t work if we don’t have compression on, but if we pack it in a production mode the minimize property will turn on automatically and the compression plugin will work automatically.

However, there is a slight drawback to this configuration. If you look at the output JS file, you will find that the JS that can be compressed automatically cannot be compressed automatically. This is because the array is set to minimize, and Webpack thinks that if it is configured, the js compressor will be overwritten inside the compressor plug-in that is used to customize it, so it needs to be added back manually. The built-in JS compression plugin called Terser-webpack-plugin requires this module to be installed.

yarn add terser-webpack-plugin --dev
Copy the code

Add the plugin to the Minimize array manually after installation.

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const TerserWebpackPlugin = require('terser-webpack-plugin');
module.exports = allModes.map(item= > {
    return {
        mode: 'none'.entry: {
            main: './src/index.js',},output: {
            filename: `[name].bundle.js`,},optimization: {
            minimize: [
                new OptimizeCssAssetsWebpackPlugin(),
                new TerserWebpackPlugin()
            ]
        },
        module: {
            rules: [{test: /\.css$/,
                    use: [
                        MiniCssExtractPlugin.loader,
                        // 'style-loader',
                        'css-loader']]}},plugins: [
            new CleanWebpackPlugin(),
            new HtmlWebpackPlugin({
                template: './src/index.html'.filename: `index.html`
            }),
            new MiniCssExtractPlugin(),
            new OptimizeCssAssetsWebpackPlugin()
        ]
    }
})
Copy the code

25. Hash file name

Generally, static resource caching is enabled when front-end resources are deployed. In this way, users’ browsers can cache static resources in applications. The request server is no longer required. Overall application response speed has been greatly improved.

However, there are some minor problems with enabling client caching for static resources. If the cache policy is set to too short a cache expiration time, the effect is not particularly obvious. If the expiration time is set to a long time, the application cannot be updated to the client in a timely manner once it is updated.

To solve this problem, it is recommended to add a Hash value to the output file name in production mode, so that if the resource file is changed, the file name can be changed with it.

For the client, the new file name is the new request, there is no cache problem, you can set the server cache policy cache time is very long, there is no need to worry about the file update after the problem.

The filename attribute in webpack and the filename attribute in most plug-ins supports setting the hash to the filename as a placeholder, although the three hash effects are different here.

Normal hashes can be set by [hash], which is actually project-wide, meaning that any change in the project changes the hash value during packaging.


const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const TerserWebpackPlugin = require('terser-webpack-plugin');
module.exports = allModes.map(item= > {
    return {
        mode: 'none'.entry: {
            main: './src/index.js',},output: {
            filename: `[name]-[hash].bundle.js`,},optimization: {
            minimize: [
                new OptimizeCssAssetsWebpackPlugin(),
                new TerserWebpackPlugin()
            ]
        },
        module: {
            rules: [{test: /\.css$/,
                    use: [
                        MiniCssExtractPlugin.loader,
                        // 'style-loader',
                        'css-loader']]}},plugins: [
            new CleanWebpackPlugin(),
            new HtmlWebpackPlugin({
                template: './src/index.html'.filename: `index.html`
            }),
            new MiniCssExtractPlugin({
                filename: '[name]-[hash].bundle.css'})]}})Copy the code

The second is chunkhash. This hash is at the chunk level and the chunkhash is the same as long as it is packaged on the same path.


const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const TerserWebpackPlugin = require('terser-webpack-plugin');
module.exports = allModes.map(item= > {
    return {
        mode: 'none'.entry: {
            main: './src/index.js',},output: {
            filename: `[name]-[chunkhash].bundle.js`,},optimization: {
            minimize: [
                new OptimizeCssAssetsWebpackPlugin(),
                new TerserWebpackPlugin()
            ]
        },
        module: {
            rules: [{test: /\.css$/,
                    use: [
                        MiniCssExtractPlugin.loader,
                        // 'style-loader',
                        'css-loader']]}},plugins: [
            new CleanWebpackPlugin(),
            new HtmlWebpackPlugin({
                template: './src/index.html'.filename: `index.html`
            }),
            new MiniCssExtractPlugin({
                filename: '[name]-[chunkhash].bundle.css'})]}})Copy the code

Although there is only one package entry index configured here, two chunks are formed in the code through dynamic import, namely posts and album. The style file is extracted separately from the code, so it is not a separate chunk. Main, Posts, and album are different chunkhash.

The CHUNkhash of the CSS and the corresponding JS file is exactly the same because they are in the same path. When index is changed and repackaged, only the main.bundle file name is changed, and all other files remain unchanged. Make a few changes to the posts.js file, and the output JS and CSS will change, because they are in the same chunk.

The main. Bundle also changed because the names of js files and CSS files generated by posts changed, and the path to import them in the entry file changed, so mian. Chunk was a passive change.

Contenthash is a file-level hash that is generated based on the content of the output file. That is, different files will have different hash values.


const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const TerserWebpackPlugin = require('terser-webpack-plugin');
module.exports = allModes.map(item= > {
    return {
        mode: 'none'.entry: {
            main: './src/index.js',},output: {
            filename: `[name]-[contenthash].bundle.js`,},optimization: {
            minimize: [
                new OptimizeCssAssetsWebpackPlugin(),
                new TerserWebpackPlugin()
            ]
        },
        module: {
            rules: [{test: /\.css$/,
                    use: [
                        MiniCssExtractPlugin.loader,
                        // 'style-loader',
                        'css-loader']]}},plugins: [
            new CleanWebpackPlugin(),
            new HtmlWebpackPlugin({
                template: './src/index.html'.filename: `index.html`
            }),
            new MiniCssExtractPlugin({
                filename: '[name]-[contenthash].bundle.css'})]}})Copy the code

Contenthash is the best way to solve the cache problem, because it precisely targets the hash at the file level and updates the file name only when the file changes, which is actually the best way to solve the cache problem.

Webpack allows you to specify the length of a hash by using a colon and an array [:8] inside the placeholder.

new MiniCssExtractPlugin({
    filename: '[name]-[contenthash:8].bundle.css'
})
Copy the code

In general, contenthash should be the best choice for controlling the cache with 8 bits.