“This is the seventh day of my participation in the First Challenge 2022. For details: First Challenge 2022.”

🧨 Hi, I’m Smooth, a sophomore SCAU front er

🏆 This article will get you started with Webpack and give you an easy-to-understand introduction to basic and advanced configurations!

🙌 if the article is wrong, please comment section correction, thank you!

Custom Loader content updated on 2022/02/25 Custom Plugin content updated on 2022/02/26

Webpack

This tutorial uses NPM to explain package management

Study background

The vUE cli and create-React-app scaffolding have already configured webPack parameters by default, so there is no additional need to configure webPack.

But in a project development, an optimized packaging suddenly for the items you want to increase the speed and volume of demand, so I began to learn the webpack, gradually understand the power of packaging tools, when looking at the scaffold to our default configured webpack configuration file, also understood that the scaffold such thing as a convenient place.

Of course, after the system learning webpack, you can also do to castrate scaffolding webpack configuration files do not use some configuration options, and add some configuration you need, such as optimization of packaging volume, improve packaging speed and so on.

This article will walk you through the basics of WebPack

PS: For the webpack configuration file, vue-cli can be modified by modifying vue.config.js. Create-react-app needs to be overwritten by Craco or exposed by eject.

Webpack introduction

webpackWhat is the

Bundler: a module packaging tool

webpackrole

Package the project, specify project entry, file hierarchy, translate code (translate code into code that the browser knows, such as import/export)

webpackEnvironment configuration

Webpack installation prerequisite: Node has been installed, use the commands node -v and NPM -v to test whether the node installation is successful

NPM install webpack webpack-cli --save-dev // --save-dev NPM install webpack webpack-cli -g // not recommended, end with -g, global installation (if two projects use two versions of webpack, there will be a conflict of versions) NPX webpack -v: NPM init -y: NPM init -y: NPM init -y: NPM init -y: NPM info webpack initializes the NPM repository with the -y suffix meaning that all options are set to yes by default when creating package.json files: NPM install webpack@ webpack-cli -d: install webpack NPX webpack; packageCopy the code

Webpack – CLI and Webpack differences:

Webpack-cli allows us to run webpack-related commands from the command line, such as webpack, NPX webpack, etc

webpackThe configuration file

The default configuration file is webpack.config.js

const path = require('path'); Module. Exports = {mode: "production", / / the environment, the default production or production environment, packaging the file compressed (can write), development not compressed entry: // exit filename: 'bundle.js', // exit filename: 'bundle.js', // exit filename: 'bundle.js', // exit filename: 'bundle.js' Path. resolve(__dirname, 'bundle'), // Export file to which folder to pack, argument (absolute path root, filename)}}Copy the code

If you want webPack to be packaged according to other configuration file rules, for example, called webPackConfig.js

npx webpack --config webpackconfig.js
Copy the code

Small problem:

Why use react/Vue frameworks to package project files instead of NPX webpack, type NPM start/ NPM run dev, etc?

Reason: Changed scripts script instructions in package.json file (this file: description of the project, including required dependencies, runnable scripts, project name, version number, etc.)

{"scripts": {"bundle": "webpack"}}Copy the code

Review:

NPX webpack index.js // After installing webpack locally (within the project), NPM run bundle -> webpack NPM run bundle -> webpack NPM run bundle -> webpackCopy the code

NPM Run Bundle replaces webpack

Basic concepts of Webpack

Concepts section of Webpack

The official documentation

Loader

What is Loader?

Webpack only knows and supports packing JS files by default. If you want to expand its ability to pack CSS files and image files, you need to install Loader to expand it

The use of the Loader

Do this in the webpack.config.js configuration file

New module field in the file, new rules array in the module, a series of rules configuration, each rule object has two fields

Test: matches the package of all files with the suffix XXX and uses the regular expression to match

Use: indicates the name of the loader to be used and to be installed

Name specifies the name of the packaged file.[name].[ext] indicates the name and suffix of the packaged file

{module: {rules: [{test: /.jpg$/, use: {loader: 'file-loader', options: {// placeholder placeholder name: '[name].[ext]' } } } ] } }Copy the code

Use the NPM install Loader name or YARN add Loader name in the root directory of the project to install the required loader

Common Loaders

Babel-loader, style-loader, CSS-loader, less-loader, sass-loader, postCSs-loader, URl-loader, file-loader, etc

Image (Images)

Pack image files

Image static resources, so they correspond to file-loader, and in general projects these static resources are placed in the images folder, using the use field to configure additional parameters

{ module: { rules: [ { test: /.(jpg|png|gif)$/, use: { loader: 'file-loader', options: { name: '[name].[ext]', outputPath: 'images/' // the folder path to which files with the suffix above are packaged}}}]}}Copy the code

Of course, for file-loader, url-loader is more extensible

It is recommended to use url-loader instead, because the limit parameter can be set. If the image size is larger than the corresponding byte size, it will be packed to the specified folder directory. If the image size is smaller, it will generate Base64 (it will not be packed to the folder, but to generate base64 into the OUTPUT JS file).

{ module: { rules: [ { test: /.(jpg|png|gif)$/, use: { loader: 'url-loader', options: { name: '[name].[ext]', outputPath: 'images/', // If matched to files with the suffix above, package to this folder path LIMIT: 2048 // specified size}}}]}}Copy the code




Style (CSS)

Package style file

Css-loader and style-loader need to be configured in the use field

Note: After setting the CSS style, mount it to the style property, so need two

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

For SCSS files, configure the sass-Loader in use in addition to the above two loaders, and then install the two files

npm install sass-loader node-sass webpack --save-dev

Matters needing attention:

The loader array in use is packaged from right to left, top to bottom (SCSS), style first, then CSS, and sASS from right to left

use: ['style-loader', 'css-loader', 'sass-loader']
Copy the code

postcss.loader

Postcss. loader: postcss.loader: postcss.loader: postcss.loader: postcss.loader: postcss.loader: postcss.loader: postcss.loader Create a postcss.config.js file to configure the loader

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

Style development

How do I get WebPack to recognize and package reimported LESS files within less files?

How to modularize export and use styles? (css in js)

{ module: { rules: [ { test: /.scss$/, use: ['style-loader', { loader: 'css-loader', options: { importLoaders: 2, // Allow less file modules: true // allow modular import/export using CSS in JS}}, 'sass-loader, 'postCSs-loader']}]}}Copy the code




Font (Fonts)

Package font files (with iconfont)

After downloading the font files of the corresponding ICONS from the iconfont website and compressing them to the directory, it is found that the downloaded iconfont. CSS file also introduces eOT, TTF, and SVG files, which cannot be recognized by Webpack. Therefore, you need to configure packaging rules for the files with these three suffivities. Use file – loader

{ module: { rules: [ { test: /.scss$/, use: ['style-loader', { loader: 'css-loader', options: { importLoaders: 2, // Allow less modules to be added to less files: True / / modular import and export permission to use CSS, CSS in js similarly}}, 'sass - loade,' postcss - loader ']}, {/ / configure the rules to the test: /. (eot | the vera.ttf | SVG) $/, use: { loader: 'file-loader' } } ] } }Copy the code




Custom Loader

First and foremost, writing a Loader is simply writing a function and exposing it to Webpack

For example, write a replaceLoader that replaces one character with another when encountered, such as hi when encountered with a Hello string

// Replaceloader. js in the loaders folder of the root directory: './loaders/replaceLoader.js' module.exports = function(source) { return source.replace('hello', 'hi'); }Copy the code

A simple Loader is now written

Pay attention to

The exposed function cannot be written as an arrow function, i.e., as follows:

// replaceLoader.js
​
module.exports = (source) => {
    return source.replace('hello', 'hi');
}
Copy the code

Since the arrow function does not have this pointer, Webpack will make some changes when using Loader to bind some methods to this, so it will not be able to call some methods that belong to this.

For example, the parameters passed into the Loader are obtained through this.query

Of course, to use your custom Loader, in addition to the above write Loader, also need to use it, inWebpackConfiguration file For related configuration

// webpack.config.js const path = require('path'); module.exports = { mode: 'development', entry: { main: './src/index.js', }, module: { rules: [{ test: /.js/, use: [path. Resolve (__dirname, '. / loaders/replaceLoader. Js') / / here to write the js file path]}}], the output: {path: path.resolve(__dirname, 'dist'), filename: '[name].js' } }Copy the code

If you want to pass some parameters to your custom Loader, pass them as follows:

// webpack.config.js const path = require('path'); module.exports = { mode: 'development', entry: { main: './src/index.js', }, module: { rules: [{ test: /.js/, use: [{loader: path. Resolve (__dirname, '. / loaders/replaceLoader. Js'), / / here to write the js file path options: {name: 'hi' } } ] }] }, output: { path: path.resolve(__dirname, 'dist'), filename: '[name].js' } }Copy the code

As a result, Webpack passes the {name: ‘hi’} argument to replaceloader.js when it is packaged

In replaceloader.js, the parameters are received as follows:

// replaceLoader.js
​
module.exports = (source) => {
    return source.replace('hello', this.query.name);
}
Copy the code

In this way, the Hello string in all js files of the original project is replaced with hi

This completes a simple Loader

More and more

loader-utils

However, sometimes it is weird to pass parameters to a custom Loader. For example, the passed object may become only a string. In this case, the loader-utils module is used to analyze the passed parameters and parse them into correct contents

Method of use

First run NPM install loader-utils –save-dev install and then

// replaceLoader.js const loaderUtils = require('loader-utils'); Module.exports = function(source) {const options = loaderutils.getoptions (this); // Use return source.replace('hello', options.name); }Copy the code




callback()

Sometimes, in addition to making changes to the original project with a custom Loader, if sourceMap is enabled, you want the mapping of sourceMap to change as well.

Since this function only returns changes to the project content and not to sourceMap, some configuration is done with callback

this.callback( err: Error | null, content: string | Buffer, sourceMap? : SourceMap, meta? : any )Copy the code

Callbacks from this function can return sourceMap, error, meta changes in addition to project content changes

Since I only need to return the contents of the project and the sourceMap changes, an example configuration is as follows:

// replaceLoader.js const loaderUtils = require('loader-utils'); Module.exports = function(source) {const options = loaderutils.getoptions (this); // Use const result = source.replace('hello', options.name); this.callback(null, result, source); }Copy the code




async()

Sometimes there will be asynchronous operations in the custom Loader, such as setting the delay timer for 1s and then packing (convenient for fishing), so if you directly setTimeout(), setting a delay timer and then returning will certainly not work, error will be reported, because normally it is not allowed to return content in the delay timer.

We can do this with async() as follows:

// replaceLoader.js const loaderUtils = require('loader-utils'); Module.exports = function(source) {const options = loaderutils.getoptions (this); // Use const callback = this.async(); setTimeout(() => { const result = source.replace('hello', options.name); callback(null, result); Callback ()}, 1000); }Copy the code

Async () is similar to callback(), except that it is used for asynchronous returns

Customize multiple Loaders at the same time

For example, you want to implement a requirement that the packaged project first replaces all strings in the project with Hello and then with hi to Wow

Write two Loaders, the first to replace Hello with hi and the second to replace hi with Wow

The first replaceLoader. Js

// replaceLoader.js const loaderUtils = require('loader-utils'); Module.exports = function(source) {const options = loaderutils.getoptions (this); // Use const callback = this.async(); setTimeout(() => { const result = source.replace('hello', options.name); callback(null, result); Callback ()}, 1000); }Copy the code

The second replaceLoader2. Js

// replaceLoader2.js
​
module.exports = function(source) {
    return source.replace('hi', 'wow');
}
Copy the code

Also configure webpack.config.js

// webpack.config.js const path = require('path'); module.exports = { mode: 'development', entry: { main: './src/index.js', }, module: { rules: [{ test: /.js/, use: [ { loader: path.resolve(__dirname, './loaders/replaceLoader2.js') }, { loader: Path. The resolve (__dirname, '. / loaders/replaceLoader. Js') / / here to write the js file path, the options: {name: 'hi'}},]}}], the output: { path: path.resolve(__dirname, 'dist'), filename: '[name].js' } }Copy the code

Something to watch out for

The Loader executes from bottom to top and from right to left, so write the first one at the bottom and the second one at the top

Loader import is converted to the official import mode

In the above example, the loader is imported in the same way

loader: path.resolve(__dirname, './loaders/replaceLoader2.js')
Copy the code

Too long, too troublesome, not beautiful, want to change official introduction means, how should do

loader: 'replaceLoader2'
Copy the code

Configure the resolveLoader field in the Webpack configuration file

// webpack.config.js const path = require('path'); module.exports = { mode: 'development', entry: { main: './src/index.js', }, resolveLoader: { modules: ['node_modules', './loaders'] }, module: { rules: [{ test: /.js/, use: [ { loader: 'replaceLoader2' }, { loader: Options: {name: 'hi'}},]}]}, output: {path: path.resolve(__dirname, 'dist'), filename: '[name].js' } }Copy the code

If no Loader can be found in node_modules, the loaders folder of the same directory will be searched

More design considerations for Loader

Recommend some custom and practical loaders

  1. Global exception monitoring, idea: to all functions outside the envelopetry{} catch(err) {console.log(err)}statements
  2. style-loader
module.exports = function(source) {
    const style = `
        let style = document.createElement("style");
        style.innerHTML = ${JSON.stringify(source)};
        document.head.appendChild(style)
    `
    return style;
}
Copy the code




Plugins

  • Use plug-ins to make packaging faster and more diverse

  • At some point in the packaging lifecycle, plug-ins help you do something

Here are a few common plug-ins

html-webpack-plugin

Function: Because webpack does not generate index.html file by default, htmlWebpackPlugin automatically generates an HTML file after packaging, and automatically introduces the JS generated by packaging into this HTML file

Plug-in run life cycle: after packaging

Parameter: Object

new HtmlWebpackPlugin({
    template: 'index.html'
})
Copy the code

clean-webpack-plugin

Function: Deletes all contents in a directory before packaging to prevent duplication

Plug-in run life cycle: before packaging

Parameter: Array format

[‘ Name of folder to delete ‘]

new HtmlWebpackPlugin(['dist'])
Copy the code




Customize a Plugin

First of all, at the most basic level, writing a plugin is simply writing a class and exposing it to Webpack to perform operations on it during a packaged lifecycle.

For example, write a copyright-webpack-plugin

Class CopyrightWebpackPlugin {constructor() {// copyright-webpack-plugin.js class CopyrightWebpackPlugin {constructor() { Console. log(' plugins used ')} apply(compiler) {}} module.exports = CopyrightWebpackPlugin;Copy the code

webpack.config.js

// webpack.config.js
​
const path = require('path');
const CopyRightWebpackPlugin = require('./plugins/copyright-webpack-plugin');
​
module.exports = {
    mode: 'development',
    entry: {
        main: './src/index.js',
    },
    plugins: [
        new CopyRightWebpackPlugin()
    ],
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    }
}
Copy the code

A simple Plugin is now complete

More and more

Pass the parameter to the plugin

When you create the plug-in instance in the Webpack configuration file, you just pass in the parameters

plugins: [
    new CopyRightWebpackPlugin({
        name: 'Smoothzjc'
    })
],
​
Copy the code

This way, the parameter is received in the constructor of the class

Class CopyrightWebpackPlugin {constructor(options) {console.log(' I am ', options.name) } apply(compiler) { } } module.exports = CopyrightWebpackPlugin;Copy the code




Different life cycle

As I mentioned earlier, plug-ins help you do things during the packaging lifecycle,

So we can write things that we want Webpack to do for us during the different life cycles of Webpack

Common life cycles:

  • emitAsynchronous hooks that are ready to be packaged to the build directory, at the last moment when packaging is complete
  • compileSync hook, ready for packing

Some arguments for the following example:

Compiler configuration, including packaging-related content

Compilation of all the contents of this packaging

If you want to add a file to the compilation directory before the compilation is complete, you can configure the Assets property of compilation

The relevant code runs inapplyProperties of the

Class CopyrightWebpackPlugin {constructor(options) {console.log(' I am ', Options. Name)} the apply (compiler) {/ compile/synchronization hook compiler.hooks.com running. Tap (' CopyrightWebpackPlugin ', () => {console.log(' compile hooks into effect '); }) / / asynchronous hook emit compiler. The hooks. Emit. TapAsync (' CopyrightWebpackPlugin '(compilation, Cb) => {compilation. Assets ['copyright. TXT '] = {compilation.assets['copyright. Funciton () {return 'copyright write by Smoothzjc'}, // The file size: Function () {return 28}} // Call cb(); }) } } module.exports = CopyrightWebpackPlugin;Copy the code




Debug as you write your plug-in

Most debugging tools are written based on Node. Here is an example: How to use the debugging tool to debug plugin when writing

  1. Add script instruction first, passnodeRun the debug tool
// package.json
​
{
    "scripts": {
        "debug": node --inspect --inspect-brk node_modules/webpack/bin/webpack.js,
        "build": "webpack"
    }
}
Copy the code
  1. Break points where debugging is needed
Class CopyrightWebpackPlugin {constructor(options) {console.log(' I am ', options.name) } apply(compiler) { compiler.hooks.compile.tap('CopyrightWebpackPlugin', () => { console.log('compiler'); }) compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin', (compilation, cb) => { debugger; // The compilation. Assets ['copyright.txt'] = {// The contents of the file are configured in the source property. This example means that the contents of the file are a function and the return value is source: Funciton () {return 'copyright write by Smoothzjc'}, // The file size: Function () {return 28}} // Call cb(); }) } } module.exports = CopyrightWebpackPlugin;Copy the code
  1. The console runs the debug command NPM run debug

  2. Open your browser and press F12 to open the console. You’ll see the Node icon in the upper left corner of the developer tools. Click to go to some of the pages that webPack passes through

  1. You can place the mouse over the properties of the variable you want to view

  1. Or on the rightWatchProperties Enter the name of the property you want to view




Entry

Package the entry file, and specify the js file name generated after the package

Argument: string or an object. The default generated file name is main. The generated file name is’ entry file path ‘.

Entry: './ SRC /index.js' or entry: {main: './ SRC /index.js'} both above and below are equivalentCopy the code

It can also be packaged into multiple JS files, i.e., multi-entry

entry:{
    main: './src/index.js',
    sub: './src/index.js'
}
Copy the code




Output

Output JS file name

Parameters:

  • filenameFinally package out of the JS file name, can be directlybundle.jsSpecify, or you can[name].jsThe name specified by entry can also be used[hash].jsHash value specified by entry
  • chunkFilenameName of the file imported asynchronously
  • pathThe folder and path of the package
  • publicPathAdd a prefix to the SRC import path of the packaged JS file, usually used for CDN configuration

PublicPath,

Such as:

Index.html to introduce js plate code

<script type="text/javascript" src="main.js"></script>
Copy the code

If you want to put all the packed JS files on the CDN, reduce the packed volume (in this case, the JS files need not be placed in the packed folder), for example

<script type="text/javascript" src="http://cdn.com.cn/main.js"></script>
Copy the code

The configuration can be as follows

publicPath: 'http://cdn.com.cn'
Copy the code

Output Configuration Example

output: {
    publicPath: 'http://cdn.com.cn',
    filename: '[name].js',
    chunkFilename: '[name].chunk.js',
    path: path.resolve(__dirname, 'dist')
}
Copy the code




resolve

More expansion operations when modules are introduced

  • extensionsSuffix name lookup when a module is introduced
  • aliasConfigure an alias for the path
Resolve: {extensions: [' CSS ', 'JPG', 'js',' JSX '], alias: {' @ ', '/ SRC/pages / / when the input @, automatically replaced by/SRC/pages}}Copy the code

extensions

Introduce modules in a project as follows

import Child from './child'
Copy the code

JSX = child.css = child.css = child.css = child.css = child.css = child.css = child.css = child.css = child.css = child.css = child.css = child.css = child.css = child.css = child.css = child.css = child.css = child.css = child.css = child.css = child.css = child.css = child.css = child.css = child.css = child.css

alias

Configure an alias for the path

alias: {
    '@': '/src/pages'
}
Copy the code

Explanation: When @ is typed, it is automatically replaced with/SRC /pages

Common scenario: When you frequently use the root path to refer to certain files, such as:

import a from '/src/pages/a.js';
import b from '/src/pages/b.js';
import c from '/src/pages/c.js';
import d from '/src/pages/d.js';
Copy the code

Writing/SRC /pages multiple times can be cumbersome. Using @ instead of/SRC /pages simplifies typing and improves development efficiency

Note:resolveYou have to configure it properly, otherwise it degrades performance, because if you’re looking forchild.jsxFollowing the above configuration, you need to go through the previous three unnecessary steps




SourceMap

If the packaged file has mapping enabled, he knows the code mapping between the packaged file and the pre-packaged source file

For example, we know that line 96 of the main.js file in dist actually corresponds to the first line of the index.js file in SRC

Usually not enabled, defaultnoneClose, because it will slow down the packing speed and increase the packing volume

parameter

devtool: 'none' // source-map' source-map' // source-map' inline-source-map' // source-map' inline-source-map' // source-map' inline-source-map' // Eval is the most efficient way to start mapping, but it is not comprehensiveCopy the code

Recommendation:

Devtool: ‘cheap-module-eval-source-map’

Devtool: ‘cheap-module-source-map’

You do not need to configure devtool in production environments. However, you can enable devtool to quickly locate errors. You are advised to use the preceding parameters

Mode: ‘development’ is the development environment

Mode: ‘production’ is the production environment

See the following table for more other parameters

SourceMap configuration example

devtool: 'cheap-module-source-map'
Copy the code




WebpackDevServer

Enabling a local Web server improves development efficiency

Webpack directive (usually just configure the second script)

2. Webpack-dev-server Starts a Web server and opens the corresponding directory resources. After the directory resources are modified and saved, it will be repackaged and the web page will be refreshed automaticallyCopy the code

Each project we download goes through two instructions (install dependency + package run). Here is an example of a React project generated by the create-React app scaffolding

npm install
npm run start
Copy the code

The second step, in fact, is to run WebpackDevServer, you will find that the start will directly open the browser, and every time you save it will automatically repackage and refresh the page.

Hidden feature of WebpackDevServer: packaged resources do not generate a dist folder. Instead, packaged resources are placed in the computer’s memory, which can greatly improve packaging speed

parameter

  • contentBaseWhich directory to put the file on the Web server to open
  • openWhether to open it at the same time as packingThe browserAccess the project corresponding preview URL
  • portThe port number
  • proxySet the agent

WebpackDevServer configuration example

webpackDevServer: {
    contentBase: './dist',
    open: true,
    port: 8080,
    proxy: {
        'api': 'xxxxx'
    }
}
Copy the code

Expand the content

In fact, it is equivalent to writing a webpack-dev-server by hand, but the family official has helped us to write a good configuration items are complete one, I do not need to write, just take you to expand, understand the source code behind webpack-dev-server is how to match node implementation

Use Webpack in Node

Check out the Node.js API section of the official documentation

Use WebPack on the command line

Check the Command Line Interface section of the official documentation

Hot Module Replacement

Hot Module UpdateHMR

When the content changes, only the changed part changes, other loaded parts are not reloaded (e.g. CSS style changes, only the corresponding style changes, js does not change)

parameter

  • hotEnable hot module update
  • hotOnlyWhen set to true, hot update is disabled regardless of whether it is enabledwebpackDevServerAutomatically refresh the browser after saving

Also configured to webpackDevServer

Example of HMR configuration

const webpack = require('webpack');
devServer: {
    hot: true,
    hotOnly: true
}
plugins: [
    new webpack.HotModuleReplacementPlugin()
]
Copy the code

Above is to enable HMR, below is to use HMR

// Example: when number.js changes, the function import number from './number' is called; number(); if(module.hot) { module.hot.accept('./number', () => { number(); })}Copy the code

CSS csS-loader is written for you, Vue vuE-loader is written for you, react babel-loader is written for preset. (preset)

If you’re introducing obscure data files that don’t have HMR built in, you’ll need to write.

Use Babel for ES6 syntax

Babel-loader, @babel/preset-env, @babel/polyfill

  • Configuration options for babel-loader can be written in separately.babelrcIn the file
  • In addition to converting ES6 to ES5 is not enough, some older browsers need to inject Promise and array.map with additional code that needs to be introduced@babel/polyfill
UseBuiltIns: 'usage' // For the used code, just convert to ES5 and pack it into dist targets folder: Chrome: '67' // Google Chrome version is larger than 67, ES6 can be directly compiled normally, so there is no need to do ES5 conversion}Copy the code

Example:

Module: {rules: [{test: /.js$/, exclude: /node_modules/, loader: "babel-loader", options: {// [["@babel/preset-env", {targets: {edge: '17', Firefox: '60', Chrome: '67', Safari: '11.1'}, useBuiltIns: [[" Babel /plugin-transform-runtime", {"corejs": {"corejs": {"corejs": {"corejs": { 2, "helpers": true, "regenerator": true, "useESModules": false }]] } } ] }Copy the code

If you want to use Babel in React

Implement the React framework code package

Download @ Babel/preset – react

. Babelrc file

{
    presets: [
        [
            "babel/preset-env", {
                targets: {
                    chrome: "67",
                },
                useBuiltIns: "usage"
            }
        ],
        "@babel/preset-react"
    ]
}
Copy the code

Advanced Concepts for Webpack

Webpack’s Guides section

The official documentation

Tree Shaking

Only code that has been introduced for use will be packaged, and code that has not been introduced for use will not be packaged (to reduce the size of the code). This feature is enabled by default after WebPack 2.0

  • ES Module only supported (static import)
  • Common JS not supported (dynamic import)
Import {} from "// ESM supports const XXX = require(") // Common JS does not support thisCopy the code

Tree Shaking is not open in Development by default

If your development environment is Tree Shaking every time you recompile the packaged code, it will cause lines of code to not match when debugging

If you want to open the development environment

// package.json { "sideEffects": For example, {"sideEffects": ["*.css"]} if you don't want to Tree Shaking CSS filesCopy the code




Differentiated packaging of Development and Production modes

In general, the WebPack configuration files for the two environments do not change, but they can be in different file forms if you must

Webpack.dev.js is a development environment.

Webpack.proud. Js is a production environment.

The packaging script also needs to be changed if the difference is made

// package.json {"scripts": {"dev-build": webpack --config webpack.dev.js, // relative path or "proud-build": Webpack --config webpack.proud. Js, // relative path}}Copy the code




Webpack and Code Splitting

Why code split?

If the user needs to load a large JS file, fully 2MB, then each time the user visits this page, the user must load 2MB resources page can be displayed normally, but there may be a lot of code blocks are the current page does not need to use, so the unused code is divided into other JS files. Loading only when needed and reloading only when the page changes can greatly speed up page loading.

This means that proper code splitting through configuration can make the file structure clearer and the project run faster. For example, if loDash library is used, it can be split up.

Here’s an example:

Suppose we now have main.js(2MB) with lodash.js(1MB) in it. When accessing the page for the first time, load main.js(2MB). When the service logic of the page changes, load 2MB content again. Due to the browser's parallelism mechanism, it is faster to render two 1MB files in parallel when first accessing a page than to render only one 2MB file. Second, when the page business logic changes, simply reload main.js(1MB).Copy the code

At the same time, if both files use a module, if both files write this module once, there will be duplication. At this time, if the public module is removed and the two files reference it separately, then the module writing will be reduced once (reducing the package size).

That is, reasonable segmentation of code can also speed up the first screen loading speed, speed up repackaging speed (package volume reduction)

Code segmentation: The popular explanation is to divide a lump of code into multiple JS files

Code Splitting itself can be done manually, for example by pulling out common components, but why does Webpack almost come with Code Splitting now?

The SplitChunksPlugin plug-in in WebPack makes code splitting very easy, which is also a strong competitive point of WebPack

// webpack.config.js
{
    optimization: {
        splitChunks: {
            chunks: 'all'
        }
    }
}
Copy the code

To summarize

Webpack does Code Splitting in two ways

1. Configure the plug-inSplitChunksPlugin

// webpack.config.js {optimization: {splitChunks: {chunks: 'all'}}} ' 'import _ from 'lodash'; // Write the business codeCopy the code

2. Dynamically import data asynchronously

function getComponent() {
    return import('lodash').then(({ default: _ }) => {
        let element = document.createElement('div');
        element.innerHTML = _.join(['zjc', 'handsome'], '-');
        return element;
    })
}
​
getComponent().then(element => {
    document.body.appendChild(element);
})
Copy the code

Of course, to support the asynchronous dynamic introduction of a module, you need to download babel-plugin-dynamic-import-webpack first

then

// .babelrc
{
    presets: [
        [
            "@babel/preset-env", {
                targets: {
                    chrome: "67"
                },
                useBuiltIns: 'usage'
            }
        ],
        "@babel/preset-react"
    ],
    plugins: ["dynamic-import-webpack"]
}
Copy the code




SplitChunksPlugin

This section explains the plug-in configuration parameters in detail

The reuseExistingChunk attribute in the cache group is important for reducing packet size: with true enabled, modules to be split into the group will not be cached if they have already been cached in a group

Configuration of the sample

module.exports = { //... Optimization: {splitChunks: {chunks: 'all', {chunks: 'all', {async: 'all',} MinRemainingSize: 0, minChunks: MaxAsyncRequests: 3, maxInitialRequests: 3, // How many js files can be split automaticNameDelimiter: '~', // 'true' // When true, the following group's filename attribute takes effect enforceSizeThreshold: 50000, // cacheGroups, which, when packaging synchronized code, go through the setup above, and then further into the groups below, dividing the code into the following qualifying groups: {vendors: {test: /[\/]node_modules[\/]/, // Only when the library in node_modules is imported, the code is split to vendor group priority: -10, // The higher the priority, the higher the priority. True, filename: 'vendors. Js ', // Vendors group code splits are all split into filename name: 'Vendors' // generate vendors. Chunk. js, which should be set to filename}, default: {minChunks: 2, priority: -20, reuseExistingChunk: },},},},},},};Copy the code

See the official documentation for more configuration items

Lazy Loading

Dynamically importing a module asynchronously, usually on a routing configuration page, and lazily loading the imported components

function getComponent() {
    return import('lodash').then(({ default: _ }) => {
        let element = document.createElement('div');
        element.innerHTML = _.join(['zjc', 'handsome'], '-');
        return element;
    })
}
​
getComponent().then(element => {
    document.body.appendChild(element);
})
Copy the code




Packaging analysis

Apply webPack’s official tool Bundle Analysis

Preloading, Prefetching

Preloading Lazy loading. A component is imported only when an event is performed. For example, a component is imported only when an element is clicked

When core functions and interactions on the main page are loaded, a component will be preloaded if the network is idle so that it can be opened when you fetch it at any moment

Implement preloading:

Webpack is accompanied by magic comments, and webpackPrefetch: XXX is appended to the imported path

document.addEventListener('click', () => {
    import(/* webpackPrefetch: true */ './click.js').then((func) => {
        func()
    })
})
Copy the code

This is also webpack’s most recommended first-screen loading optimization method, asynchronous introduction + preloading

Code splitting of CSS files

The Code splitting mentioned above is for JS files, which means the Code splitting of JS files, and the packaged CSS is in JS files

If you want to split CSS files into code, you can use the MiniCssExtractPlugin, which relies on hot updates and can only run in an online packaging environment

// webpack.config.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 
​
module.exports = {
  plugins: [ new MiniCssExtractPlugin() ],
  module: {
    rules: [
      {
        test: /.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },
};
Copy the code

By default, all imported CSS files are merged into one CSS file

The OptimizeCSSAssetsPlugin plugin can be used if you want to compress the CSS files from the code and merge the repeated properties together

// webpack.config.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
​
module.exports = {
    optimization: {
        minimizer: [
            new OptimizeCSSAssetsPlugin({})
        ]
    },
    plugins: [ new MiniCssExtractPlugin() ],
    module: {
      rules: [
        {
          test: /.css$/,
          use: [MiniCssExtractPlugin.loader, "css-loader"],
        },
      ],
    },
}
Copy the code

If you want multi-entry CSS files to be merged together, you also need to use code splitting

// webpack.config.js const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); module.exports = { optimization: { splitChunks: { cacheGroups: { styles: { name: 'styles', // wrap all of these into the styles. CSS file test: /.css$/, chunks: 'all', enforce: }}}, plugins: [new MiniCssExtractPlugin()], module: {rules:}}, plugins: [new MiniCssExtractPlugin()], module: {rules: [ { test: /.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"], }, ], }, } }Copy the code




Webpack and the Browser Cache

When the browser loads a resource, it caches it locally. When the user accesses the page next time to load the resource, the browser can quickly load the resource based on the cached file. The browser does not know that the file has been changed and needs to render again until the file name is changed.

The role of the browser feature

You can speed up the loading, and when a part of the page changes, you can only re-render the changed part, so that partial rendering is done

Question: How do I ensure that only the file names of the changed files are changed each time I pack, and the file names of the unchanged files remain the same?

If the Output property in the Webpack configuration file is set to the following

output: {
    filename: '[name].js',
    chunkFilename: '[name].chunk.js'
}
Copy the code

If made a change, the project file and the file name unchanged, packaged filename unchanged, because the browser has to load the file, cache the file name, when the file is changed and the file name you don’t change, the browser will not render again, every time in order to make the changed file name change, you can use the hash value

output: {
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].chunk.js'
}
Copy the code

Have WebPack create a separate hash based on the contents of the file and change the file name because the hash value changes when the contents of the file change

expand

In older versions of WebPack (webPack 4.0 and below), if you find that the hash value of files changes every time you repack them, even if they haven’t changed, you can solve this problem by configuring runtimeChunk

Optimization: {runtimeChunk: {name: 'runtime'}} // A runtime.hash.js file is generatedCopy the code

RuntimeChunk principle

Suppose I have two JS files packaged by Webpack. File A is the code related to business logic, and file B is the library code for code segmentation (such as Lodash). Since there are operations to introduce into the library in business logic, they will be related. The code associated with this association exists in both A and B files (commonly known as manifest), and the relationship between packages and js files and the nested relationship between JS and JS files changes slightly each time the MANIFEST is packaged, so even if the A and B files are not changed, The hash that’s packaged still changes.

By configuring runtimeChunk, you can separate the manifest-related code from the runtimeChunk, so that every time you repackage, only runtimeChunk changes. A file only has business logic, B file only has library files. There will be no manifest code in either A or B files, so the A and B files will not change, so the hash will not change

Shimming

Package compatible, automatic introduction

When you use a library on your page but don’t import it, the webPack code will help you import it automatically and “stealthily”

Plugins: [new webpack.ProvidePlugin({$: 'jquery', // When using $, jquery library is automatically introduced in that page _: _join: ['lodash', 'join'] // When _join is entered, the lodash library join method is introduced})]Copy the code




More and more

Use of environment variables

For development and production environments, there may be times when you really need to write separate WebPack profiles for configuration

But if you write it separately, there are a lot of attributes that are repetitive, and you don’t want to write more, what do you do?

For example, file A and file B are different configuration parameters in two environments, and file C is A common configuration file. In this case, you need to follow the A+C packing rules in the development environment and B+C packing rules in the production environment

Can be achieved bywebpack-mergeConfigure different configuration files for development and production environments

// webpack.common.js const merge = require('webpack-merge'); const devConfig = require('./webpack.dev.js'); Const prodConfig = require('./webpack.prod.js'); } module.exports = (env) => {// If env is available, run the following command: If (env && env.production) {return merge(commonConfig, prodConfig); } else {return merge(commonConfig, devConfig); }}Copy the code

Modify the configuration in package.json file

{ scripts: { "dev": "webpack-dev-server --config webpack.common.js", "build": "Webpack --env.production --config webpack.common.js" --env.production --config webpack.common.jsCopy the code

Of course, during script configuration, you can also pass parameters to the configuration file as follows:

"build": "webpack --env production --config webpack.common.js"
Copy the code

At the same time, change the parameter to webpack.common.js

Module. exports = (env, production) => { If (env && production) {return merge(commonConfig, prodConfig); } else {return merge(commonConfig, devConfig); }}Copy the code




🎁 thank you for reading this article, I hope you can help, if you have any questions welcome to point out.

🎁 I’m Smoothzjc, please like it if you think it’s all right ❤

🎁 I will also work hard to produce more good articles in the future.

🎁 interested partners can also pay attention to my public account: Smooth front-end growth record, public account synchronous update

Writing is not easy, thanks for your support ❤

Phase to recommend

React Hook: How to Learn React Hook in 2022

Github + Hexo Implement your own personal blog, Configuration theme (super detailed)

10 minutes to thoroughly Understand how to configure subdomains to deploy multiple projects

This article explains how to configure pseudo static to solve the 404 problem of deployment project refresh page

Learn common horizontal and vertical Centered interview questions in 3 minutes

React: Emoji input with Antd+EMOJIALL

【 Suggested collection 】 a summary of git common instructions!! Suitable for small white and want to understand the basic instructions of Git at work.

A brief introduction to javascript prototypes and prototype chains