Webpack5.0 learning summary-basic articles

Webpack5.0 learning summary-basic articles

Preface

The scaffolding that the Vue project has always used for development, I know very little about the black box of Webpack, and I am always confused when encountering problems, so I learned it more completely after the release of Webpack5.0. This article summarizes the learning results. The overall outline is shown in the figure below. This article is the basic article. For the advanced article, please click the portal Webpack5.0 learning summary-advanced article .

Meet Webpack

What is Webpack?

Webpack is a front-end resource construction tool, a static module packer.

  • Front-end resource construction tool: Mainly understand what kind of front-end resource is. These front-end resources are web resources that the browser does not recognize, such as sass, less, ts, and advanced syntax in js. For these resources to work properly in the browser, they must be compiled and processed one by one. And webpack is a total build tool that can integrate these compilation tools.
  • Static module packager: Static modules are various resource files in the web development process. Webpack builds a dependency graph based on the reference relationship, and then uses this relationship graph to package all static modules into one or more bundles for output.

Why do we need Webpack

The answer to this question can be clearly felt by comparing it with when there is no Webpack and no build tools. Here is a list of pain points when not using build tools.

  • In web development, the back-end interface is called cross-domain, which requires other tool agents or other ways to avoid it.
  • After changing the code, you need to refresh the browser manually. If you do a cache, you need to clear the cache and refresh.
  • Because of the compatibility issue of js and css, many new syntaxs are learned but cannot be used, both development efficiency and personal growth are affected.
  • Packaging issues. You need to use additional platforms such as jekins to package, write your own packaging scripts, and process each link, such as compressing pictures, packaging js, and packaging css. ......

For these problems, Webpack provides solutions, you only need to do some simple configuration to get started. Of course, Webpack does more than that, let's introduce them one by one below.

Use Webpack

Webpack core configuration

This part introduces the common configuration of Webpack, mainly in the form of code and comments.
As a reminder, there are fewer configurations written in the article, and the detailed configuration can be found in the official Webpack documentation. Especially for loaders and plugins, most of them are integrated by third parties, and the content is often updated, so when you need to use them, go directly to the corresponding official website to find their integration and usage methods.

entry

Entry: Instruct Webpack to start packaging with which file as the entry point, and analyze and build the internal dependency graph.

//string mode: Single entry, packaged to form a trunk, and output a buldle file. The name of the trunk is main.js by default entry: "./src/index.js", //Array mode: multiple entries, all entry files will eventually form a trunk, and only one bundle file will be output entry: ["./src/index.js", "./src/test.js"], //object: multi-entry, there are several entry files to form several trunks, and output several bundle files. At this time, the name of the trunk is the object key value entry:{ index:"./src/index.js", test:"./src/test.js", } Copy code

output

Output: Indicate where and how to name the resource bundles packaged by Webpack.

output: { //Output file directory (the public directory for all resources output in the future, including css and static files, etc.) path : path.resolve(__dirname, "dist" ), //default //File name (specified name + directory ) Filename : "[name].js" , //default //All resources introduce a common path prefix, generally used in a production environment, use publicPath carefully : "" , /* The name of the non-entry file chunk. The so-called non-entry refers to the trunk formed by the import dynamic import or the public trunk extracted by the splitChunks in the optimization It supports built-in variables consistent with filename */ chunkFilename : "[contenthash:10].chunk.js" , /* Library is needed when using Webpack to build a library that can be imported and used by other modules */ library : { name : "[name]" , //The variable name exposed by the entire library type : "window" //Library exposed } }, Copy code

loader

Loader: Webpack itself can only understand JavaScript and json files. Loader allows Webpack to process other files.
Here are the loader configurations of several types of common files.

rules: [ { //Which files are matched test : /\.css$/ , //Which loader is used for processing. Execution sequence, from right to left, bottom to top use: [ //Create a style tag, take the style resource in js (the string converted by css-loader) and add it to the page head tag to take effect "style-loader" , //convert the css file into commonjs and load it into a module In js, the content inside is the style string "css-loader" , { //css is compatible with postcss. Note that browserslist loader : "postcss-loader" , options : { postcssOptions : { ident : "postcss" , //postcss-preset-env plug-in: help postcss find package.json in package.json The browserslist configuration in, loads the specified compatibility style plugins according to the configuration : [ require ( "postcss-preset-env" )()], }, }, }, ], }, { test : /\.js$/ , //Note that browserslist needs to be configured in package.json, otherwise babel-loader will not take effect //js compatible processing babel loader: "babel-loader" , //Recommended writing when the rule uses only one loader options : { presets : [ [ "@babel/preset-env" , //Preset: instruct babel to do compatible processing { useBuiltIns : "usage" , //Load corejs on demand : { version : "3" , }, targets : "defaults" , } ] ] } }, /* Webpack5.0 adds an asset module, which is a type of module that allows the use of resource files (fonts, icons, etc.) without the need to configure additional loader. Support the following four configurations asset/resource sends a separate file and exports the URL. Previously achieved by using file-loader. asset/inline exports the data URI of a resource. Previously achieved by using url-loader. asset/source Export the source code of the resource. Previously achieved by using raw-loader. asset automatically chooses between exporting a data URI and sending a separate file. Previously, it was achieved by using url-loader and configuring resource volume limits. */ //Webpack4 is implemented using file-loader { test : /\.(eot|svg|ttf|woff|)$/ , type: "asset/resource" , generator : { //output file location and file name filename : "fonts/[name][ext]" }, }, //Webpack4 is implemented using url-loader { //Process image resource test : /\.(jpg|png|gif|)$/ , type: "asset" , generator : { //output file location and file name filename : "images/[name][ext]" }, parser : { dataUrlCondition : { maxSize : 10 * 1024 //No base64 conversion over 10kb } } }, ], Copy code

plugin

Plugins: can be used to perform a wider range of tasks. From packaging optimization and compression, to redefining variables in the environment, etc.

//CleanWebpackPlugin helps you automatically clear the dist file when you are packaging, and it is more convenient to use when learning const {CleanWebpackPlugin} = require ( "clean-webpack-plugin" ); //HtmlWebpackPlugin helps you create html files and automatically introduces bundles files that are packaged and output. Support html compression. const HtmlWebpackPlugin = require ( "html-webpack-plugin" ); //The plugin extracts the CSS into a separate file. It will create a css file for each trunk. Need to be used with loader const MiniCssExtractPlugin = require ( "mini-css-extract-plugin" ); //The plugin will search for CSS resources during the Webpack build process, and optimize\minimize CSS const OptimizeCssAssetsWebpackPlugin = require ( "optimize-css-assets-webpack-plugin" ); //For vue-loader V15 or above, you need to introduce the VueLoaderPlugin plug-in. Its function is to apply the js, css and other rules you have defined to the vue file. const {VueLoaderPlugin} = require ( 'vue-loader' ) module .exports = { module : { rules : [ { test : /\.vue$/ , loader: "vue-loader" }, { test : /\.css$/ , use: [ //The function of MiniCssExtractPlugin.loader is to extract the style resources processed by the css-loader (in the js file) separately to become the css style file MiniCssExtractPlugin.loader, // Used in the production environment, the development environment is still recommended to use style-loader " css-loader" , ], }, ], }, plugins : [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ template : "index.html" }), new MiniCssExtractPlugin({ filename : "css/built.css" , }), new OptimizeCssAssetsWebpackPlugin(), new VueLoaderPlugin(), ] } Copy code

mode

Mode: Instruct Webpack to use the configuration of the corresponding mode. The default is production.
Moving the form on the official website, it is still necessary to know what Webpack has done for the two modes we usually use the most.

Optionsdescription
developmentWill set the value of process.env.NODE_ENV in DefinePlugin to development. Enable valid names for modules and chunks.
productionThe value of process.env.NODE_ENV in DefinePlugin will be set to production. Enable deterministic obfuscated names for modules and chunks, FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin and TerserPlugin.
noneDo not use any default optimization options
  • DefinePlugin: Define the global variable process.env.NODE_ENV to distinguish the running status of the program.
  • FlagDependencyUsagePlugin: Flag dependencies that are not used.
  • FlagIncludedChunksPlugin: Flags chunks to prevent chunks from being loaded multiple times.
  • ModuleConcatenationPlugin: scope hosting (scope hosting), precompilation function, promotion or precompilation of all modules into a closure, to improve the execution speed of the code in the browser.
  • NoEmitOnErrorsPlugin: Prevent the program from reporting errors and continue to compile even if there are errors.
  • TerserPlugin: Compress js code.

Other common configurations

module .exports = { //Rules for parsing modules: resolve : { //Configure path aliases for parsing modules: the path can be abbreviated. alias : { "@" : path.resolve(__dirname, "src" ) }, //Configure the suffix name of the omitted file path. Js and json are omitted by default. There are also two file types extensions recognized by webpack by default : [ ".js" , ".json" , ".css" ], //add a new css file //Tell webpack which directory the parsing module is looking for //The configuration is clear Tell webpack to go directly to the upper layer to find node_modules. modules : [path.resolve(__dirname, "../node_modules" )], }, //devServer (configuration in the development environment): devServer : { //directory contentBase for running code : path.resolve(__dirname, "build" ), //enable gzip compression for each static file compress : true , host : "localhost " , port : 5000 , open : true , //automatically open the browser hot : true , //enable HMR function //set proxy proxy : { //once devServer (port 5000) receives the request of/api/xxx, Will use the service from devServer to forward the request to another server (3000) //To solve the cross-domain problem in development api: { target : "htttp://localhost:3000" , //When sending a request, the request path is rewritten:/api/xxx -->/xxx (remove/api) pathRewrite : { "^api" : "" , }, }, }, }, //optimization (configuration in a production environment) optimization : { //extract common code splitChunks : { chunks : "all" , }, minimizer : [ //Configure the compression scheme of the production environment: js and css new TerserWebpackPlugin({ //Multi-process packaging parallel : true , terserOptions : { //start source-map sourceMap : true , }, }), ], }, }; Copy code

webpack packaging optimization

Development environment optimization

1. Use source-map

source-map: A technology that provides mapping from source code to post-construction code. If there is an error in the post-construction code, the source code error can be traced through the mapping. Optimize code debugging.
Enabling the source-map configuration is very simple: devtool: "source-map". There are many types of source-map values, which are explained briefly.
Common components of source-map options: [inline-|eval-][cheap-[module-]]source-map

  • inline: Inline, a trunk generates a total source-map
  • eval: Inline, each file generates a source-map
  • cheap: External, the error position can only be accurate to the line.
  • cheap-module: Display the source-map of the third-party library

The difference between inline and external: Inline does not generate a map.js file, but directly injects it into the chunk in the form of data-url; inline construction is faster.

2. HMR (module hot replacement)

devServer starts a proxy server. Modifying the code after startup will automatically refresh the browser, but this is not an HMR.
HMR: Module hot replacement can also be understood as partial replacement. Replace, add or delete modules without reloading the entire page. Configure the following to enable HMR

devServer: { contentBase : path.resolve(__dirname, "dist" ), hot : true , //Enable HMR function }, //Note: After Webpack is upgraded to 5.0, the default value of target will be changed according to the browserslist in package.json, causing the automatic update of devServer to fail. Therefore, it is directly configured as web in the development environment. target : "web" , copy the code

After turning on HMR, some configurations are required to take effect.

  • Style file: internal implementation of style-loader, so as long as style-loade is configured in loader, HMR function can be used directly
  • Vue file: internal implementation of vue-loader, similarly configure vue-loader to directly use HMR.
  • js file: need to modify the source code to receive update notifications, the code is as follows
import test from "./test.js" if ( module .hot){ module .hot.accept( "./test.js" , ()=> { console .log( 'Accepting the updated test module!' ); }) } Copy code

When the test file is changed, the update event will be passed up layer by layer until it is passed to the entry file. In the process of delivery, any place that receives this update event, that is, the module.hot.accept method above, will stop delivery and execute the callback. If it has not been received, Webpack will finally be notified to refresh the entire page.

Production environment optimization

1. oneOf

By default, the file will match each rule under the rules, even if a certain rule has been matched, it will continue to match downward. If you put the rule in the oneOf attribute, once a rule is matched, the matching will stop.

rules:[ { test : /\.js$/ , exclude: /node_modules/ , loader: "eslint-loader" , }, { //The following loader file will only match one oneOf : [ //Two configurations cannot handle the same type of file. If there are, another rule must be placed outside. { test : /\.js$/ , exclude: /node_modules/ , use: [ { loader : "babel-loader" , }, ], }, { test : /\.css$/ , use: [ "style-loader" , "css-loader" , ], }, ], }, ] Copy code

The rule placed in the oneOf attribute will only be matched once, so if there is a type of file that needs to use multiple loaders, either use the use array or put it outside of oneOf.

2. the cache

There are two ways to cache files when compiling and packaging. One is that the loader that parses the file itself has a cache function (such as babel-loader, vue-loader), and the second is to use a dedicated loader (cache-loader). ).
After the cache is turned on, webpack reads the unchanged files directly from the cache without recompiling, which greatly speeds up the build.

{ test : /\.js$/ , use: [ //Use cache-loader and put it before babel-loader to cache the js files compiled by babel. "cache-loader" , { loader : "babel-loader" , options : { presets : [ [ "@babel/preset-env" , //Preset: instruct babel to do compatible processing ] ], //Turn on the babel cache, and the previous cache will be read during the second build. cacheDirectory : true , } } } Copy code
3. Multi-process packaging (thread-loader)

Generally, thread-loader needs to be used only when it takes a long time to compile, because the loader startup and communication are costly. If the time is short, the use of this loader will outweigh the gains.

//"thread-loader" is placed before babel-loader, it will perform multi-process work when babel-loader is working. { loader : "thread-loader" , options : { workers : 2 , //The number of startup processes, the default is the number of computer cpu cores -1 }, }, { loader : "babel-loader" , options : { presets : [ [ "@babel/preset-env" , ], ], }, }, Copy code
4. external extensions (externals)

externals is used to tell Webpack which modules are not used in the code to be built. These modules may be introduced through an external environment (such as CDN).

module .export = { externals : { //Replace the jquery in the import statement with the global variable jQuery in the running environment jquery : 'jQuery' } } //source code Import $ from "jQuery" Copy the code

After configuring externals, even if you include this library in your code, Webpack will not pack the library into the bundle, but directly use global variables.

5. DLL

dll (Dynamic Link Library): Use dll technology to pack the public library in advance, which can greatly improve the construction speed. The public library is generally unchanged, so these modules only need to be compiled once, and can be packaged in advance. In the subsequent construction of the main program, if it is detected that the public library has been packaged by dll, it will no longer be compiled but directly obtained from the dynamic link library. To achieve dll packaging requires the following three steps:

  1. Extract public libraries and pack them into one or more dynamic link libraries.
  2. Introduce the packaged dynamic link library into the page.
  3. When the main program uses the public library in the dynamic link library, it cannot be packaged into the bundle and should be directly obtained from the dynamic link library.

Code for this step

1 Create a new webpack.dll.js to pack the dynamic link library in advance

//webpack.dll.js module .exports = { //JS execution entry file entry : { //Put the vue related modules in a separate dynamic link library vendor : [ 'vue' , 'axios' ], // Put other modules in another dynamic link library other : [ 'jquery' , 'lodash' ], }, output : { //The file name of the output dynamic link library, [name] represents the name of the current dynamic link library ("vendor" and "other") filename : '[name].dll.js' , //the output file All are placed in the dll folder under the dist directory path : path.resolve(__dirname, 'dist' , "dll" ), //Store the exposed variable name of the dynamic link library, for example, the corresponding vendor is _dll_vendor library : '_dll_[name]' , }, plugins : [ //Package and generate a mainfest.json file. Tell webpack which libraries do not participate in subsequent packaging, which have been packaged in advance through dll. new webpack.DllPlugin({ //The library name of the dynamic link library needs to be consistent with the output.library //The value of this field is the value of the name field in the output manifest.json file //For example vendor.manifest.json There is "name": "_dll_vendor" name : '_dll_[name]' , //The file name that describes the output of the manifest.json file of the dynamic link library path : path.join(__dirname, 'dist' , "dll" , '[name].manifest.json' ), }), ], }; Copy code
  1. Introduce the packaged dynamic link library in the template page index.html
<!DOCTYPE html > < html lang = "en" > < head > < meta charset = "UTF-8" > < meta http-equiv = "X-UA-Compatible" content = "IE=edge" > < meta name = "viewport" content = "width=device-width, initial-scale=1.0" > < title > Webpack </title > <script src = "./dll/vendor.dll.js" > </ script > < script src = "./dll/other.dll.js" > </script > </head > < body > < div id = "app" > </div > </body > </html > Copy code
  1. Use the webpack.DllReferencePlugin plug-in in the Webpack configuration of the main program to read the manifest.json file generated by webpack.DllPlugin and obtain the dependencies from it.
//webpack.config.js module .exports = { mode : "production" , plugins : [ new HtmlWebpackPlugin({ template : "./index.html" }), //Tell Webpack which dynamic link libraries are used new webpack.DllReferencePlugin({ //The manifest file tells webpack which libraries have been pre-packaged with dlls, and subsequent builds are directly obtained from the dynamic link library. manifest : path.resolve(__dirname, "dist" , "./dll/vendor.manifest.json" ), }), new webpack.DllReferencePlugin({ manifest : path.resolve(__dirname, "dist" , "./dll/other.manifest.json" ), }), ], } Copy code
6. Tree Shaking

Tree Shaking: Remove dead-code from the JavaScript context. Imagine the entire application as a tree. The green leaves represent the source code and library that are actually used, and the gray leaves represent unused code, which is withered leaves. In order to remove these dead useless leaves, you need to shake the tree to make it fall. This is where the name of Tree Shaking comes from.

//Entry file index.js import test from "./test.js" console .log(test.add( 2 , 3 )); //Test file test.js const add = ( x, y ) => x + y const print = ( msg ) => { console .log(msg); } export default {add, print} //The final packaged output bundle: main.js file ! function () { "Use strict" ; Console .log ( 2 + . 3 )} (); duplicated code

As can be seen from the above example, although the test file is introduced in index.js, because the print method exposed by the test file is not used, it is removed in the final packaging.
This is not possible in Webpack4, which will only remove modules that have never been used. Incorporating the above example, if test is not used in the index.js file, it will be Tree Shaking. The reason for this is that Webpack4 considers all file codes to have side effects by default. How to tell Webpack whether your code has side effects can be through the sideEffects field in package.json.

//All files have side effects { "sideEffects" : true } //All files have no side effects, { "sideEffects" : false } //Only these files have side effects, all other files can be Tree Shaking, but these files will be retained { "sideEffects" : [ "./src/file1.js" , "./src/file2.js" ] } Copy code

For example, in the default settings of Webpack5.0, the style file is considered to have side effects, so although the imported style file is not used (the style file is definitely not used), it will not be removed, but if sideEffects: false is set Tree Shaking removes the code.
Having said so much, how to set up Tree Shaking? In fact, no special configuration is required, as long as the mode is set to "production", Webpack will automatically enable Tree Shaking. There are two explanations:

  • The source code must use static ES6 modular syntax. The reason is that Webpack analyzes the dependencies between the codes through static analysis during construction. The dynamic import such as require syntax only knows which module has been imported when it is executed, so Tree Shaking cannot be done.
  • Tripartite libraries cannot do Tree Shaking. The reason is that Webpack cannot guarantee whether the third party library import will directly affect the program.
7. Code Split (code split)

Webpack bundle.js bundle.js trunk

optimization trunk

optimization: { splitChunks : { //Choose which chunks to optimize, the default is async, that is, only the trunks formed by dynamic import are optimized. chunks : 'all' , //The minimum size of the extracted chunk minSize : 20000 , //The minimum number of citations of the chunk to be extracted minChunks : 1 , //Group the trunk to be extracted cacheGroups : { //Match the tripartite library in node_modules , Package it into a trunk defaultVendors : { test : /[\\/]node_modules[\\/]/ , //trunk name name: 'vendors' , priority :-10, }, default: { // trunk trunk minChunks: 2, name: 'default', priority: -20, }, }, }, },

import
bundle import Webpack trunk

import( /* webpackChunkName: 'test' */ './test.js').then((result) => { console.log(result); }).catch(() => { console.log(' '); });