A quick review of webpack principles

Webpack can be thought of as a module packer, turning all parsed modules into an object, loading our stuff through the entry module, and then implementing recursive dependencies in turn, running all files through the entry module. Since WebPack only knows JS, it needs to be converted into a suitable format for the browser to run through a series of loaders and plugins.

  • Loader mainly preprocesses the loading and translation of resources. In essence, loader is a function that converts the received content and returns the converted result. Multiple Loaders can be used for a certain type of resource, executed from right to left and from bottom to top.

  • Plugins extend the functionality of WebPack and essentially listen for the entire packaging life cycle. Webpack is based on the event flow framework Tapable, which broadcasts many events during its life cycle. The Plugin can listen for these events and change the output when appropriate through the API provided by WebPack.

This article mainly introduces webpack5 configuration, according to the steps of the configuration side package comparison will be more impressive ~ later will attach the complete source code.

Webpack installation

Create a new directory, go to the directory to initialize package.json, and install the WebPack dependencies

NPM init -y installation dependency NPM I webpack webpack-cli -dCopy the code

Basic configuration

The default configuration file name for webpack is webpack.config.js, so create a new file named webpack.config.js in the root directory of the project and write the simplest single-page configuration in the configuration file:

let path = require("path");

module.exports = {
  mode: "development",
  entry: "./src/js/index.js", 
  output: {
    filename: "js/bundle.js", 
    path: path.resolve("dist"),
    publicPath: "http://cdn.xxxxx"
  }
}
Copy the code

The configuration,

  • Mode – Packaging mode
    • developmentFor development mode, the code is not compressed after packaging
    • productionFor production mode, the packaged code is compressed code
  • Entry – Entry file
  • Output – Package file configuration
    • filename: After the file is packed, the value of filename can be set to bandhashStamped file:js/bundle.[hash].js / js/bundle.[hash:8].js(Only 8-bit hash stamp is displayed)
    • path: Specifies the path of the package file. The value must be an absolute path
    • publicPath: Indicates the online CDN address

TIP: Path is a built-in module in the above code. You don’t need to install it.

After creating the file, you need to create the index.js file in the SRC /js directory under the root directory of the project, and then enter a line of JS code.

After the configuration, you can use the webpack command to try to pack. If an error occurs and the command cannot be found, you can pack NPM I webpack -g after the global installation. After the package is successfully packaged, it will be output to the dist directory under the root directory of the project.

The project directory structure is roughly as follows

├ ─ package. Json ├ ─ webpack. Config. Js ├ ─ SRC | ├ ─ js | | └ index. The js ├ ─ distCopy the code

HTML file packaging

Since Webpack only knows JS, HTML files need to be packaged through the HTMl-webpack-plugin

npm i html-webpack-plugin -D
Copy the code

After installation in the webpack.config.js configuration file

let HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    plugins: [
        new HtmlWebpackPlugin({
          template: "./src/index.html"
        })
    ]
}
Copy the code

Production mode enables compression of HTML files:

plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html",
      minify: { removeAttributeQuotes: true, collapseWhitespace: true }, 
      hash: true
    })
]
Copy the code

The configuration,

  • Plugins-webpack plug-in configuration
    • HTML – wepack – plugin configuration
      • Template-html Relative/absolute path to the template file
      • Minify – Compression configuration
        • removeAttributeQuotes: Deletes the attribute double quotation marks
        • collapseWhitespace: Code compressed into a single line
      • Hash – Imports files with hash stamps

TIP: If you do not specify the template configuration, it will be the default HTML file of the plug-in, not the HTML file in the project

Open the service

Webpack starts the service by installing webpack-dev-server

npm i webpack-dev-server -D
Copy the code

Configuration webpack. Config. Js

devServer: {
    port: 5000,
    compress: true,
    open: true,
    client: { progress: true }
}
Copy the code

The configuration,

  • devServer – webpack-dev-serverconfiguration
    • Port – Indicates the port number
    • Open compress –gzipThe compression
    • Open – Automatically opens the page after startup
    • client
      • progress: Displays compilation progress as a percentage in the browser

Run the webpack-dev-server command to view the configuration. If the command cannot be found, run the NPM I webpack-dev-server -g command to install the server globally

Cross domain

You can configure devServer.proxy to solve cross-domain interface problems

Assumes that the interface address for http://localhost:3000/api/users, right/API/users can request the following configuration

devServer: {
    proxy: {
      '/api': 'http://localhost:3000',
    },
},
Copy the code

But the address of the interface in the actual project there are many possible, generally will not have a directory/API, namely the general interface address for http://localhost:3000/users, thus enumeration configuration will be very trouble, can be solved through agent request

The request http://localhost:3000/api/users interface address first, then by devServer agent to http://localhost:3000/users

In this article, through express open interface service, interface address for http://localhost:3000/user, interface code are outside, the late upload the complete source code, The interface service can be started by using node “project path \ webPack5 \ SRC \js\server.js” and then configuring webpack.config.js

devServer: {
    proxy: {
      "/api": {
        target: "http://localhost:3000/",
        pathRewrite: {
          "/api": ""
        },
      },
    }
}
Copy the code

devServerThe configuration,

  • Proxy-proxy configuration
    • Target – Indicates the interface domain name
    • PathRewrite – interface pathRewrite to proxy requests to the interface server

Mock interface data

When the back-end interface is not written properly and you do not want to block progress, you can use the interface data format agreed with the back-end to simulate the debug page. Can use a custom function and application. The ability to customize middleware configuration devServer setupMiddlewares, in middlewares. Unshift callback function in the use of res. Send the need to mock data passed in:

devServer: { setupMiddlewares: (middlewares, devServer) => { if (! devServer) { throw new Error("webpack-dev-server is not defined"); } middlewares. Unshift ({name: "user-info", // path is optional, path: "/user", middleware: (req, res) => {// mock data res.send({name: "moon mock"}); }}); return middlewares; }},Copy the code

Style to deal with

Loaders for style processing and their functions:

  • less-loaderLoad and translate LESS files
  • postcss-loaderUse:PostCSSLoad and translate CSS/SSS files as can be handledautoprefixerCSS package to add browser prefixes to CSS
  • css-loader: parsing@import and url()Syntax, use import to load parsed CSS files and return CSS code
  • mini-css-extract-pluginloader: Extracts the CSS file and imports the HTML file with the link tag

If sASS is used, change the less less-loader to Node-sass sass-loader

npm i mini-css-extract-plugin css-loader postcss-loader autoprefixer less-loader less -D
Copy the code

Configuration webpack. Config. Js

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

You also need to create and configure postcss.config.js

module.exports = {
  plugins: [require("autoprefixer")]
};
Copy the code

After the above files are configured, you will find that the CSS3 style is still not prefixed after packaging, and you need to configure browserList of package.json to take effect

"browserslist": [
    "last 1 version",
    "> 1%",
    "IE 10"
],
Copy the code

Js processing and grammar check

Es6 or higher syntaxes need to be converted to ES5 with esLint specification code:

  • babel-loader: Load the ES2015+ code and use itBabelTranslated into ES5
  • @babel/preset-env: Basic ES syntax analysis package, unified setting of various translation rules, the purpose is to tell loader what rules to convert into the corresponding JS version
  • @babel/plugin-transform-runtime: Parses advanced syntax, such as generator, but excludes include syntax, which must be installed@babel/polyfill. The official document says you need to bring it online@babel/runtimeIn this patch, the package has also been optimized for method extraction, such as class syntax extraction (extraction of the classCallCheck method).
  • @babel/polyfill: Parses more advanced syntax, such aspromise.includeEtc., in the JS filerequireIntroduction to
  • eslint-loader: check whether JS conforms to the specificationEslint websiteConfiguration download

Install dependencies

npm i @babel/core babel-loader @babel/preset-env @babel/plugin-transform-runtime -@babel/polyfill -D

npm i @babel/runtime eslint-loader eslint -S
Copy the code

webpack.config.js

{ test: /\.js$/, use: { loader: "eslint-loader", options: { enforce: },},}, {test: /\.js$/, // Enforce default normal normal loader use: {loader: "Babel-loader ", options: {presets: ["@babel/preset-env"], // Set es6 to ES5 plugins: ["@babel/plugin-transform-runtime"], // }, }, include: path.resolve(__dirname, "src"), exclude: /node_modules/, },Copy the code

To configure the source – the map

Source map configuration source-map value:

  • Source-map source code will be generated separately source-map file error will indicate the current error row and column large and complete
  • Eval-source-map does not produce a separate file; rows and columns can be displayed
  • Rowy-module-source-map does not identify columns and generates a separate mapping file
  • Cheap -module-eval-source-map does not generate files Integration does not generate columns in packaged files

webpack.config.js

  devtool: "eval-source-map",
Copy the code

Introduce JS global variables

There are three ways to introduce global variables

expose-loader

You can expose variables to global window objects, using jquery as an example

npm i jquery expose-loader -D
Copy the code

Then configure loader in webpack.config.js to expose $to the window global object

module: {
  rules: [{
    test: require.resolve('jquery'),
    use: [{
      loader: 'expose-loader',
      options: '$'
    }]
  }]
}
Copy the code

In addition to the above methods can also be exposed in the entry JS file

require("expose-loader? $! jquery");Copy the code

providePlugin

You can use the webapck built-in providePlugin to inject variables into each module, again using jquery as an example

Configure it in webapck.config.js

const webpack = require("webpack");
module.exports = {
    plugins: [
        new webpack.ProvidePlugin({
          $: 'jquery'
        });
    ]
}
Copy the code

You can then use the $call directly in any JS module without importing the jquery package

// in a module
$('#item'); // <= works
// $ is automatically set to the exports of module "jquery"
Copy the code

Import through CDN

You can also import global variables via CDN links, but if you add import $from ‘jquery’ to the js file, jquery will be included. External can be used to prevent some imported packages from being packaged into bundles

index.html

< script SRC = "https://code.jquery.com/jquery-3.1.0.js" integrity = "sha256 - slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk =" crossorigin="anonymous" ></script>Copy the code

webpack.config.js

module.exports = {
  //...
  externals: {
    jquery: 'jQuery',
  },
};
Copy the code

In other words, the code shown below works fine:

import $ from 'jquery'; $('.my-element').animate(/* ... * /);Copy the code

The example above. The attribute name is jquery, indicating that the jquery module in import $from ‘jquery’ should be excluded. To replace this module, the jQuery value will be used to retrieve a global jQuery variable. In other words, when set to a string, it is treated as global (defined above and below).

Style compression and JS compression

In production mode, csS-minimizer-webpack-plugin can be used to compress CSS. However, if CSS is compressed using this plugin, JS compression will not be compressed, so terser-webpack-plugin needs to be installed

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin"); module.exports = { optimization: { minimize: true, minimizer: [new CssMinimizerPlugin (), / / compression js new TerserPlugin ({test: / \. Js (\? *)? $/ I}),],}},Copy the code

The image processing

Need loader to parse image resources:

  • file-loader: parse the import/require () of the file into a URL, send the file to the output folder (dist folder), and return the (relative) URL
  • url-loader: likefile-loaderWorks the same, but can be returned if the file is smaller than the limitdata URLThat is, change the image to Base64
  • html-loader: Can parse images introduced by HTML tags, and specify which tag-attribute combination should be processed by querying attrs. Default value: attrs=img: SRC

Install dependencies

npm i file-loader url-loader html-loader -D
Copy the code

Configuration webpack. Config. Js

module: { rules: [ { test: /\.jpg|png|jpeg$/, use: { loader: "file-loader", options: { outputPath: "images/", name: "[name]. [ext]", / / if you don't write the file name, will generate random name / / publicPath: "http://cdn.xxx.com/images", / / can be configured in a production environment of CDN address prefix},,}}, {test: /\.(html)$/, use: { loader: "html-loader", options: { esModule: false, }, }, }, ] }Copy the code

TIP: Url-loader can be limited with options.limit. If the size is smaller than k, use base64 conversion. If the size is larger than k, use file-loader packaging

The html-loader configuration is incorrect

The HTml-loader must disable es6 modularization and use CommonJS for parsing. Otherwise, an error will be reported. The main reason is that the two Loaders parse images in different ways.

The project directory structure is roughly as follows

├ ─ eslintrc. Json ├ ─ package - lock. Json ├ ─ package. The json ├ ─ postcss. Config. Js ├ ─ webpack. Config. Js ├ ─ SRC | ├ ─ index. The HTML | ├ ─ js | | ├ ─ index. Js | | ├ ─ for server js | | └ test. The js | ├ ─ image | | └ logo. The PNG | ├ ─ CSS | | ├ ─ a.c ss | | └ index. The CSS ├ ─ distCopy the code

Resolve configuration

Resolve Common attribute configuration:

  • modules: tells WebPack which directory to search for when parsing modules. Both absolute and relative paths can be used, but be aware that there is a slight difference between them.
    • Using an absolute path, searches are performed only in a given directory. Use relative paths by viewing the current directory as well as the ancestor path.
    • If you want to search ahead of a target directory, you need to place that directory in front of the target directory
  • alias: Set the alias for easy use. The following example applies tosrcDirectory
  • mainFields: When importing a module from an NPM package (for example, import * as D3 from ‘D3’), this option determines which field to use in package.json to import the module. The default value varies depending on the target specified in the WebPack configuration. Here the Browser property is preferred because it is the first entry of mainFields
  • extensions: Try to resolve these suffixes in order. When an imported file has no suffix, and there are multiple files with the same name but different extensions, WebPack parses the file with the suffix listed at the top of the array and skips the rest.
let path = require("path");

module.exports = {
    resolve: {
        modules: [path.resolve("node_modules")], 
        alias: {
          "@": path.resolve(__dirname, "src"),
        },
        mainFields: ["browser", "module", "main"], 
        extensions: [".js", ".json", ".vue"], 
    },
}
Copy the code

Multi-page configuration

Multiple pages, as the name implies, are multiple HTML pages, so there are usually multiple JS entry files.

In the following configuration, the key value of entry corresponds to the [name] value of the output property, and the property chunks in HtmlWebpackPlugin represent the js code file corresponding to [name]. Not specifying chunks will import all packaged JS files.

In this example, [name] is home and other respectively, that is, the package is home.js and other.js. The final package effect is that home.html imports home.js and other.html imports other.js

Configuration webpack. Config. Js

let path = require("path");
let HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  mode: "development",
  entry: {
      home: "./src/js/index.js",
      other: "./src/js/other.js",
  }, 
  output: {
    filename: "js/[name].js", 
    path: path.resolve("dist")
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html",
      filename: "home.html",
      chunks: ['home']
    }),
    new HtmlWebpackPlugin({
      template: "./src/other.html",
      filename: "other.html",
      chunks: ['other']
    }),
  ],
}
Copy the code

Webpack applet

clean-webpack-plugin

Cleanup plug-in, which can be used to clean up the last package file, and clear the value of the directory output.path

Install dependencies

npm i clean-webpack-plugin -D
Copy the code

Configuration webpack. Config. Js

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

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

copy-webpack-plugin

Copy the plug-in and export a folder to a package folder, such as a document folder (such as a doc folder)

Install dependencies

npm i copy-webpack-plugin -D
Copy the code

Configuration webpack. Config. Js

const CopyWebpackPlugin = require("copy-webpack-plugin"); Module.exports = {plugins: [new CopyWebpackPlugin({patterns: [{from: "./doc", to: "./doc",},],}),]}Copy the code

Plug-in configuration Properties

  • patterns
    • From: indicates the source file, relative to the current directory path
    • To: The target file, relative to the output.path file path, will be generated in the dist/doc directory

webpack.BannerPlugin

Copyright notice plug-in, webpack built-in plug-in, no installation required

Configuration webpack. Config. Js

const webpack = require("webpack");

module.exports = {
    plugins: [
        new webpack.BannerPlugin("copyright by Moon in 2022"),
    ]
}
Copy the code

The packaged file begins with a copyright notice that goes something like this:

watch

You can listen for file changes and recompile as they change, which can be used in real-time packaging scenarios

Configuration webpack. Config. Js

Watch: true, watchOptions: {poll: 1000, // Check changes every second aggregateTimeout: 600, // Ignore: /node_modules/,},Copy the code

Configuration properties

  • watchOptionsMonitoring parameters
    • poll: Checks for changes every n milliseconds
    • aggregateTimeout: buffeting, when the first file changes, adds delay before rebuilding. This option allows WebPack to aggregate any other changes made during that time into a rebuild. In milliseconds
    • ignored: On some systems, listening for a large number of files can result in a large CPU or memory footprint. You can use the re to exclude imagesnode_modulesSuch a huge folder

After configuration, enter NPM run build in the command window to monitor and package in real time, as shown in the picture

The environment variable

Define DEV environment variables through the webPack built-in plug-in DefinePlugin.

You can also separate webpack configurations that differ between development and production modes by splitting the webpack.config.js file into three

  • Common configuration is placed inwebpack.config.base.jsfile
  • The development mode configuration is placedwebpack.config.dev.jsFile, approvedwebpack-mergemergewebpack.config.base.jsFiles andwebpack.config.dev.jsFile configuration
  • The production mode configuration is placedwebpack.config.prod.jsFile (logically consistent with development mode configuration file)

The complete code of the webpack.config.dev.js file is as follows:

let { merge } = require("webpack-merge"); let base = require("./webpack.config.base.js"); let HtmlWebpackPlugin = require("html-webpack-plugin"); const webpack = require("webpack"); module.exports = merge(base, { mode: "development", devtool: "eval-source-map", plugins: [ new HtmlWebpackPlugin({ template: "./src/index.html", }), new webpack.DefinePlugin({ ENV: JSON.stringify("dev"), }), ], devServer: { compress: true, client: { progress: true }, port: Mock setupMiddlewares: (Middlewares, devServer) => {if (! devServer) { throw new Error("webpack-dev-server is not defined"); } middlewares. Unshift ({name: "fist-in-array", // 'path' is optional path: "/user", middleware: (req, res) => { res.send({ name: "moon mock" }); }}); return middlewares; ,}}});Copy the code

Using environment variables, the directory structure is roughly as follows

├─.eslintrc.json ├─ Package.lock. json ├─ Package.config.js ├─ WebPack.config.base.js ├─ WebPack.config.dev ├ ─ webpack. Config. Prod. Js ├ ─ SRC | ├ ─ index. The HTML | ├ ─ js | | ├ ─ index. The js | | ├ ─ for server js | | └ test. The js | ├ ─ image | | └ logo. PNG | ├ ─ CSS | | ├ ─ a.c ss | | └ index. The CSS ├ ─ doc | └ notes. The md ├ ─ distCopy the code

After the configuration file is changed, the packaging command should also be adjusted appropriately. You need to specify the configuration file when packaging:

// Webpack --config webpack.config.dev.js // webpack --config webpack.config.prod.jsCopy the code

Production mode configuration file and public configuration file source uploaded later

Hot update

Hot updates to Webpack are also known as Hot Module Replacement, or HMR. This mechanism allows you to replace the old module with the new one without refreshing the browser. Enabled by default hot update, no configuration, it will be automatically applied webpack. HotModuleReplacementPlugin, it is necessary to enable the HMR.

To optimize the

The following configuration code is in the WebPack configuration file and will not be described here

module.noParse

Since WebPack parses import files, requires referenced packages, and analyzes package dependencies, but some packages don’t have dependencies, noParse can improve build performance by not resolving dependencies in a referenced package. Suitable for packages without dependencies, such as jquery

module: {
    noParse: /jquery/,
}
Copy the code

webpack.IgnorePlugin

The built-in WebPack plug-in IgnorePlugin can prevent the generation of modules for import or require the invocation of modules that match regular expressions or filter functions. For example, the moment package contains many language packages, which are stored in the locale folder. However, most scenarios only reference one language package, so you can ignore the locale language package in the moment directory

 new webpack.IgnorePlugin({
  resourceRegExp: /^\.\/locale$/,
  contextRegExp: /moment$/,
}), 
Copy the code

It works if you omit it and reintroduce a language package into the JS file

import "moment/locale/zh-cn";
moment.locale("zh-cn");
Copy the code

Pull out the common code

It is usually used in multi-page application scenarios or when a single JS file is too large and requires a long time to request. It is necessary to split several JS files and optimize the request speed by using Optimization’s splitChunks attribute.

Splitchunks. cacheGroups The cache group can inherit and/or override any option from splitchunks. *. But test, Priority, and reuseExistingChunk can only be configured at the cache group level. Set them to false to disable any default cache groups.

Understand several properties of splitChunks before looking at the following configuration:

  • priority: Indicates the priority of the pull-out code. The higher the value is, the higher the value is, the higher the value is, the higher the value is, the higher the value is, the higher the value is, the higher the value is, the higher the value is, the higher the value is, the higher the value is, the higher the value is, the higher the value isvendorModule iscommonThe module is pulled out and not pulled out
  • name: The file name of each chunk. It is not defined as a random name
  • test: Matching directory
  • chunks: Selects which chunks to optimize
    • initial: Extracts the code from the entry, and considers the latter two values if there are asynchronous modules
    • async: Asynchronous module
    • all: Both asynchronous and non-asynchronous modules can exist
  • minSize: Indicates the minimum size of chunk to be generated. Set this parameter to 0 for testing
  • minChunks: The minimum number of chunks that must be shared before splitting, and the number of times that the current code block must be referenced before being removed. Set this parameter to 1 for convenience

In this example, chunk Common and Chunk Vendor are divided

Optimization: {// splitChunks: {// cacheGroups: {// common module Commons: {name: "common", chunks: "initial", minSize: 0, minChunks: 1, }, vendor: { name: "vendor", priority: 1, test: /[\\/]node_modules[\\/]/, chunks: "All ", // includes both asynchronous and non-asynchronous code blocks},},},},Copy the code

To facilitate your understanding, present the packaged directory tree structure

├ ─ index. The HTML ├ ─ js | ├ ─ common. Js | ├ ─ common. Js. LICENSE. TXT | ├ ─ main. Js | ├ ─ main. Js. LICENSE. TXT | ├ ─ vendor. Js | └ vendor. Js. LICENSE. TXT ├ ─ images | └ logo. The PNG ├ ─ doc | └ notes. The md ├ ─ CSS | └ main. The CSSCopy the code

This piece is more difficult to understand, it is suggested to try packaging several times to compare the difference to understand

Lazy loading

Lazy loading is implemented through ES6’s import() syntax, dynamic loading of files is implemented through JSONp, and import functions return promise objects. Vue lazy loading, react lazy loading is implemented in this way. For a simple example, some JS files are loaded after a button is clicked.

The code that gets the button DOM element object is omitted here. Button.addeventlistener ('click', function(){ import('./test.js').then(data => { console.log(data); })})Copy the code

Webpack comes with optimizations

tree-shaking

Import automatically removes unnecessary code in production, that is, tree shaking. The require syntax does not support tree-shaking

scope hosting

Scope hosting, for example:

let a = 1
let b = 2
let c = 3
let d = a+b+c
console.log(d)
Copy the code

The code is packaged with only the last sentence, and webpack automatically omits any code that can be simplified.

Hand-written simple less-loader

less-loader.js

Import the less plug-in in the /loaders/less-loader.js directory file

const less = require("less");

function loader(source) {
  let css = "";
  less.render(source, function (err, res) {
    css = res.css;
  });
}
module.exports = loader;
Copy the code

webpack.config.js

Write the following configuration

resolveLoader: {
    alias: {
        "lessLoader": path.resolve(__dirname, "loaders", "less-loader"))
    }
},
module: {
    rules: [
        {
            test: /\.less/,
            use: ["style-loader", "lessLoader"]
        }
    ]
}
Copy the code

The last

Complete source code post upload, to be continued…