Analysis of basic configuration of webpack

For webpack, you (front-end development) will encounter it in daily development. Output it through writing, summarize and learn the small knowledge points learned about front-end engineering, and form your own knowledge system

concept

webpack official website definition:

Webpack is a static module bundler for modern JavaScript applications. When webpack processes an application, it recursively builds a dependency graph that contains each module required by the application, and then packages all these modules into one or more bundles.

Before you begin to understand the configuration of webpack, you need to understand four core concepts:

  1. entry: which module should webpack use as the beginning of building its internal dependency diagram.
  2. Output: where does webpack output the bundles it creates and how to name these files.
  3. loader: able to handle non JavaScript files (webpack itself only understands JavaScript).
  4. plugins: used to perform a wider range of tasks.

Installation and construction

// npm installation
npm install webpack webpack-cli -g

// yarn installation
yarn global add webpack webpack-cli 

After installation, you can package the files directly without using the configuration file method:
webpack <entry> [<entry>] -o <output>

Create a new project and test webpack packaging with an entry file:

Run package command:
webpack index.js

Here we will see a WARNING message. This is because mode is not set. We only need to add a parameter - p:

webpack -p index

In this way, a dist folder will be generated by default, with a main JS file:

With the entry file, we also need to define the input path dist / bundle through the command line js:

 webpack -p index.js -o dist/bundle.js

webpack profile

The packaging construction method of the command line is limited to simple projects. If the project is complex and has multiple entries in production, it is impossible for us to enter a series of entry file addresses every time we package, and it is difficult to remember; Therefore, configuration files are generally used for packaging in projects; The command mode of the configuration file is as follows:

webpack [--config webpack.config.js]

The default name of the configuration file is webpack config. JS, there are often multiple sets of configuration files in a project. We can configure different configuration files for different environments and replace them through -- config:

// development environment 
webpack --config webpack.config.dev.js

// production environment 
webpack --config webpack.config.prod.js

Multiple configuration types

config configuration file through module Exports exports a configuration object:

// webpack.config.js
const path = require('path')

const resolve = function (dir) {
    return path.resolve(__dirname, dir)
}

module.exports = {
    entry: {
        app: resolve('../index.js')
    },
    output: {
        filename: '[name].[hash:8].js',
        path: resolve('../dist')
    },
}

In addition to being exported as an object, it can also be exported as a function, which will bring in the environment variables and other parameters passed in from the command line, so that the environment variables can be configured more conveniently; For example, we can distinguish different environments through ENV:

const path = require('path')

const resolve = function (dir) {
    return path.resolve(__dirname, dir)
}

module.exports = function(ENV, argv) {
    return {
        // Other configurations
        entry: resolve('../index.js'),
        output: {}
    }
}

It can also be exported as a Promise for asynchronous loading configuration. For example, you can dynamically load the entry file:

entry: () => './demo'

or

entry: () => new Promise((resolve) => resolve(['./demo', './demo2']))

entrance

As mentioned above, the entry is the starting point of the whole dependency relationship; Our common single entry configuration is the entry of a page:

module.exports = {
    entry: resolve('../index.js')
}

However, there may be more than one module in our project, so we need to inject multiple dependent files together. At this time, we need to use the array:

module.exports = {
    entry: [
        '@babel/polyfill',
        resolve('../index.js')
    ]
}

If there are multiple entry starting points in our project, we need to use the object form:

// webpack builds two different dependencies
module.exports = {
    entry: {
        app: resolve('../index.js'),
        share: resolve('../share.js')
    }
}

output

The output option is used to control how the webpack inputs the compiled file module; Although there can be multiple entries, only one output can be configured:

module.exports = {
    entry: resolve('../index.js'),
    output: {
        filename: 'index.js',
        path: resolve('../dist')
    },
}

Here, we configure a single entry, and the output is index js; However, if there is a multi entry mode, it will not work. webpack will prompt Conflict: Multiple chunks emit assets to the same filename, that is, multiple file resources have the same file name; webpack provides placeholders to ensure that each output file has a unique name:

module.exports = {
    entry: {
        app: resolve('../index.js'),
        share: resolve('../index.js'),
    },
    output: {
        filename: '[name].bundle.js',
        path: resolve('../dist')
    },
}

In this way, the files packaged by webpack will be packaged according to the name of the entry file to generate three different bundle files; There are also the following different placeholder strings:

placeholder describe
[hash] hash of module identifier
[chunkhash] hash of chunk content
[name] Module name
[id] Module identifier
[query] File name of the module, for example, query? Following string

The concepts of module, chunk and bundle are introduced here. These two nouns often appear in the above code. What is the difference between them? First, we found that module often appears in our code, such as module exports; Chunk often appears with entry, and bundle always appears with output.

  • module: the source code we write, whether commonjs or amdjs, can be understood as modules
  • Chunk: when the module source file we write is transferred to webpack for packaging, webpack will generate chunk files according to the file reference relationship, and webpack will perform some operations on these chunk files
  • Bundle: after webpack processes the chunk file, it will finally output the bundle file, which contains the loaded and compiled final source file, so it can run directly in the browser.

We can deepen our understanding of these three concepts through the following figure:

hash,chunkhash,contenthash

After understanding the concept of chunk, I believe the difference between chunk and hash in the above table is also easy to understand;

  • Hash: it is related to the construction of the whole project. As long as there is a file change in the project, the hash value of the construction of the whole project will change, and all files share the same hash value.
  • Chunk hash: it is related to the construction of the entry file. Build the corresponding chunk according to the entry file and generate the hash corresponding to each chunk; When the entry file changes, the hash value of the corresponding chunk will change.
  • contenthash: it is related to the file content itself. A unique hash is created according to the file content, that is, when the file content changes, the hash changes.

pattern

In webpack2 and webpack3, we need to manually add plug-ins to compress the code and define the environment variables. We also need to pay attention to the judgment of the environment, which is very cumbersome; The configuration mode is directly provided in webpack 4, which is available out of the box; If the configuration is ignored, webpack will also issue a warning.

module.exports = {
    mode: 'development/production'
}

The development mode is to tell webpack that I am in the development status now, that is, the packaged content should be friendly to development, convenient for code debugging and real-time browser update.

Instead of being development friendly, the production mode only needs to focus on the performance of packaging and the generation of smaller bundle s. See that many plugins are used here. Don't panic. Next, we will explain their functions one by one.

I believe many children's shoes have questioned why we use JSON when defining environment variables here Stringify ("production"), isn't it easier to use "production" directly?

Let's first look at JSON What is generated by stringify ("production"); The running result is "production". Note that it's not that your eyes are blurred or there are small black spots on the screen. The result is indeed nested with a layer of quotation marks more than "production".

We can simply understand the plug-in DefinePlugin as all processes in the code env. NODE_ Env is replaced by the content in the string. If we have the following code to judge the environment in the code:

// webpack.config.js
module.exports = {
  plugins: [
    new webpack.DefinePlugin({ 
      "process.env.NODE_ENV": "production"
    }),
  ]
}
// index.js
if (process.env.NODE_ENV === 'production') {
    console.log('production');
}

The generated code will be compiled as follows:

//dist/bundle.js
//The production variable is not defined in the code
if (production === 'production') {
    console.log('production');
}

However, the production variable may not be defined in our code, so the code will directly report an error, so we need to use JSON Stringify to wrap a layer:

//webpack.config.js
module.exports = {
  plugins: [
    new webpack.DefinePlugin({ 
      //"process.env.NODE_ENV": JSON.stringify("production")
      //amount to
      "process.env.NODE_ENV": '"production"'
    }),
  ]
}
//dist/bundle.js
if ("production" === 'production') {
    console.log('production');
}

Generate HTML file (HTML webpack plugin)

In the above code, we found that the index is generated manually html, and then introduce the packaged bundle file, but this is too cumbersome. Moreover, if the generated bundle file introduces hash value, the file name generated each time is different, so we need a plug-in to automatically generate html; First, we need to install this plug-in:
Yarn add HTML webpack plugin - D or NPM install HTML webpack plugin - D

use:

const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    // Other codes
    plugins: [
        new HtmlWebpackPlugin({
            // template file
            template: resolve('../public/index.html'),
            // Generated html name
            filename: 'index.html',
            // icon
            favicon: resolve('../public/logo.ico')
        }),
    ]
}

webpack loader

loader is used to convert the source code of the module. By default, webpack can only recognize commonjs code, but we will introduce files such as vue, ts and less into the code, so webpack can't handle it; loader expands the ability of webpack to deal with multiple file types, and converts these files into js and css that the browser can render.

module.rules allows us to configure multiple loaders, which loaders are applied to the current file type.

module.exports = {
      module: {
            rules: [
                  { test: /\.css$/, use: 'css-loader' },
                  { test: /\.ts$/, use: 'ts-loader' }
            ]
      }
};

loader feature

  • Loader supports chain delivery. Be able to use pipelines for resources. A set of chained loaders will be executed in the reverse order. The first loader in the loader chain returns the value to the next loader. In the last loader, return the JavaScript expected by webpack.
  • The loader can be synchronous or asynchronous.
  • The loader runs on the node JS, and can perform any possible operation.
  • Loader receives query parameters. Used to pass configuration to loader.
  • The loader can also be configured using the options object.
  • In addition to using package JSON is a common main attribute. You can also export ordinary npm modules as loaders in package Define a loader field in JSON.
  • Plug ins can bring more features to loader s.
  • loader can generate additional arbitrary files.

Loader provides more capabilities for JavaScript ecosystem through (loader) preprocessing function. Users now have more flexibility to introduce fine-grained logic, such as compression, packaging, language translation, and more.

babel-loader

I believe many children's shoes have experienced the pain of being compatible with low version browsers. After writing the code, I found that my js code can't run on IE10 or IE11, and then tried to introduce various polyfill; Esbel 6 has even provided us with the convenience of esbel 7; We first install the dependencies required by babel:
yarn add -D babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime

Because the translation speed of babel loader is very slow, we can see the time consumption of each loader after adding the time plug-in later, and babel loader is the most time-consuming; Therefore, we should use babel as little as possible to translate files, use $for exact matching on regular rules, and use exclude to translate nodes_ Exclude the files in modules, and include will only match the files in src; It can be seen that the scope of include is narrower and more accurate than that of exclude, so it is also recommended to use include.

// Omit other codes
module: {
      rules: [
            {
                  test: /\.js$/,
                  exclude: /node_modules/,
                  include: [resolve('src')]
                  use: {
                    loader: 'babel-loader',
                    options: {
                      presets: [
                        ['@babel/preset-env', { targets: "defaults" }]
                      ],
                      plugins: ['@babel/plugin-proposal-class-properties']
                    }
              }
            }
      ]
}

File loader and URL loader

Both file loader and URL loader are used to process images, fonts, icons and other files; URL loader works in two situations: when the file size is less than the limit parameter, URL loader converts the file to base-64 encoding to reduce http requests; When the file size is greater than the limit parameter, call file loader for processing; Therefore, we prefer to use URL loader.

module: {
        rules: [
            {
                test: /\.(jpe?g|png|gif)$/i, //Picture file
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            // 10K
                            limit: 1024,
                            //Resource path
                            outputPath: resolve('../dist/images')
                        },
                    }
                ],
                exclude: /node_modules/
            },
        ]
    }

Build a webpack development environment

In the above, we all generate dist file through command line packaging, and then directly open html or view the page through static server; However, in the process of development, we will package the code every time after writing, which will seriously affect the efficiency of development. What we expect is to see the effect of the page immediately after writing the code; Webpack dev server provides a simple web server that can be reloaded in real time.

The usage of webpack dev server is the same as that of webpack, except that it will start an additional express server. We webpack in the project config. The dev.js configuration file configures the development environment:

module.exports = {
    mode: 'development',
    plugins: [
        new Webpack.HotModuleReplacementPlugin()
    ],
    devtool: 'cheap-module-eval-source-map',
    devServer: {
        // port
        port: 3300,
         // Enable module hot swap
        hot: true,
        // Automatically open browser
        open: true,
        // Set agent
         proxy:{
             "/api/**":{
                 "target":"http://127.0.0.1:8075/",
                 "changeOrigin": true
            }
        }
    }
}

Start the server through the command line webpack dev server. After startup, we found that the root directory did not generate any files, because webpack is packaged into memory. The reason why files are not generated is that accessing the code in memory is faster than accessing the code in the file.

We are in public / index Sometimes some local static files are referenced on the HTML page. If you open the page directly, you will find that the reference of these static files is invalid. We can modify the working directory of the server and specify the directories of multiple static resources at the same time:

contentBase: [
  path.join(__dirname, "public"),
  path.join(__dirname, "assets")
]

Hot module replacement (HMR) is that after modifying and saving the code, webpack repackages the code and sends the new module to the browser. The browser replaces the old module with the new module, so that the page can be updated without refreshing the browser.

webpack plugins

Many plug-ins such as DefinePlugin and HtmlWebpackPlugin have been introduced above. We found that these plug-ins can affect the construction process of webpack to varying degrees. Here are some commonly used plug-ins:

clean-webpack-plugin
Clean webpack plugin is used to clean up the bundle file generated by the last project before packaging. It will be based on output Path automatically cleans up folders; This plug-in is frequently used in the production environment, because the production environment often generates many bundle files through hash. If it is not cleaned, it will generate new files every time, resulting in a very large folder.

const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
    plugins: [
        new CleanWebpackPlugin(),
    ],
}

mini-css-extract-plugin

When using the webpack build tool, we can insert the parsed css into the page through js through style loader. The mini css extract plugin is also used to extract css into a separate file. The plugin has a precondition that it can only be used for webpack 4 and above. Therefore, if the webpack version used is lower than 4, we still use the extract text webpack plugin plugin.

const MiniCssExtractPlugin = require("mini-css-extract-plugin")
module.exports = {
    // Omit other codes
    module: {
        rules: [
            {
                test: /\.less$/,
                use: [
                    {
                        loader: dev ? 'style-loader': MiniCssExtractPlugin.loader
                    },
                    {
                        loader: 'css-loader'
                    },
                    {
                        loader: 'less-loader'
                    }
                ]
            }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: "[name].[hash:8].css",
        })
    ]
}

copy-webpack-plugin
We are in public / index Static resources are introduced into HTML, but webpack won't help us copy to dist directory when packaging, so copy webpack plugin can help me copy well.

const CopyWebpackPlugin = require('copy-webpack-plugin')

module.exports = {
    plugins: [
        new CleanWebpackPlugin(),
        new CopyWebpackPlugin([{
            from: path.resolve(__dirname, '../static'),
            to: path.resolve(__dirname, '../dist/static')
        }])
    ],
}

ProvidePlugin

ProvidePlugin can quickly help us load the modules we want to introduce without requiring. Generally, we need to import jQuery before loading it:

import $ from 'jquery'

$('#layout').html('test')

However, after configuring ProvidePlugin plug-in in config, we can directly use $:

module.exports = {
    plugins: [
        new webpack.ProvidePlugin({
            $: 'jquery',
            jQuery: 'jquery'
        }),
    ]
}

Too many modules have been introduced into the project and it will be confusing if there is no require. Therefore, it is recommended to load some common modules, such as jQuery, vue, lodash, etc.

The difference between loader and plugin (often encountered in interviews)

  • For the loader, it is A converter that compiles the A file to form the B file. The operation here is the file, such as converting A.scss to A.css, A simple file conversion process
  • plugin is an extender, which enriches the webpack itself. For the whole process of webpack packaging after the loader is finished, it does not directly operate files, but works based on the event mechanism. It will listen to some nodes in the process of webpack packaging and perform a wide range of tasks

Tags: Javascript Webpack

Posted by Paulkirkewalker on Wed, 04 May 2022 10:58:44 +0300