An overview of the

Webpack is a static module packaging tool for modern JavaScript applications. When Webpack processes an application, it internally builds a dependency diagram that maps to each module required for the project and generates one or more bundles

Dependencies and Installation

Webpack is dependent on the Node environment to run, so you need Node installed on your computer

When installing Webpack, install WebPack-CLI at the same time

Yarn add webpack webpack-cli -g # Global installation YARN add webpack webpack-cli -d # Local installationCopy the code

Initial project

Run yarn init -y to initialize the project and create the SRC /index.js file in the root directory

Yarn add webpack webpack-cli -d # Local installationCopy the code
// src/index.js
class Person{
  constructor (name, age) {
    this.name = name
    this.age = age
  }
  sayHi () {
    console.log(this.name)
  }
}

const p = new Person('zs', 18)
p.sayHi()
Copy the code

Execute the webpack command for default packaging

webpack
Copy the code

A dist directory is automatically generated in the root directory, which contains a packaged JS file -main.js

The default package

When we run webpack, webpack looks for SRC /index.js in the current directory as an entry point and automatically generates dist/main.js

We also found that the code in the main.js file had been tampered with because the default packaging mode for WebPack is Production mode

Webpack.config.js configuration file

The default configuration must not meet the requirements of project packaging, we can create a webpack.config.js file in the root directory to configure webpack, and by default, please ensure that the file name is only webpack.config. Of course, the configuration file name can also be changed through other configurations

In the webpack.config.js file, we do an initial base configuration of WebPack. Webpack relies on Node, so we follow the CommonJs modular specification

Entrance and exit configuration

// webpack.config.js const {resolve} = require('path') module.exports = {// webpack.config.js const {resolve} = require('path') module. './ SRC /index.js', // export configuration, output: {// filename: Resolve path: resolve(__dirname, 'build')}}Copy the code
webpack
Copy the code

After executing the webpack command to pack, we will find that the build/bundle.js file is generated in the root directory, where we specify the import and export through the configuration file

Script execution

In the package.json file in the root directory, we can add scripts to pack fixed commands specified by Webpack

The "{" name" : "08," version ":" 1.0.0 ", "main" : "index. Js", "license" : "MIT", "devDependencies" : {" webpack ": "^ 5.14.0 webpack -", "cli" : "^ 4.3.1"}, "scripts" : {" build ":" webpack "}}Copy the code

Package using YARN Run Build

yarn run build
Copy the code

Specifying a configuration file

By default, the Webpack configuration file name can only be webpack.config. For some reason, it is sometimes necessary to use a different configuration file depending on the specific situation. We can configure the execution command to specify the Webpack configuration file

The "{" name" : "08," version ":" 1.0.0 ", "main" : "index. Js", "license" : "MIT", "devDependencies" : {" webpack ": "^ 5.14.0 webpack -", "cli" : "^ 4.3.1"}, "scripts" : {" build ":" webpack - config prod. Config. Js "}}Copy the code

Add –config prod.config.js to the webpack command, and webpack will be packaged with the prod.config.js file in the root directory as the configuration file

Style to deal with

Create a CSS file, SRC /style/common.css, and write some styles

// src/style/common.css
.container {
   width: 300px;
   height: 300px;
   background-color: pink;
}
Copy the code

In the import file, we import CSS through the EsModule module import

// src/index.js
import './src/style/common.css'
Copy the code

Execute yanr run build to package. Console error, because Webpack can only understand JavaScript and JSON files, Webpack can not directly process CSS, need to use loader, You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file

loader

What is loader

Loader enables WebPack to process other types of files and convert them into valid modules for use by applications and to be added to dependency diagrams

CSS – loader and style – loader

  • Css-loader loads the CSS file, parses the imported CSS file, and returns the CSS code
  • Style-loader takes the exported content of the module as a style and adds it to the DOM

Install dependencies before use

yarn add css-loader style-loader -D
Copy the code

In the WebPack configuration file

const {resolve} = require('path') module.exports = { entry: './src/index.js', output: { filename: 'build.js', path: The resolve(__dirname, 'build')}, module: {// rules attribute is an array that configates information for different resources. Rules: [{// Is used to match resources, and is usually set to the regular expression test: / \. CSS $/, / / configure loader use: [/ / {loader: 'style - loader'} / / {loader: 'css-loader', 'css-loader']}]}}Copy the code

After the yarn Run build command is executed, create build/index.html and import the build.js file using the script tag. Then create a label named Container and open the browser to view the effect

Note that the loader order should be guaranteed: ‘style-loader’ first, and ‘CSS-loader’ last. If this convention is not followed, WebPack may throw an error

less-loader

In development, we usually use less, SASS and other preprocessors to write CSS. The CSS compiled by less and SASS needs to be converted to common CSS using tools. The following uses less as an example

Create a less file, SRC /style/common.less, and write some styles

@color: #666;
@font-weight: 700;
.title {
  color: @color;
  font-weight: @font-weight;
}
Copy the code

In the import file, we import CSS through the EsModule module import

// src/index.js
import './src/style/common.less'
Copy the code

First we need less tool to transform CSS. Less-loader automatically uses less tool

Install dependencies

yarn add less less-loader -D
Copy the code

In the WebPack configuration file

// webpack.config.js const {resolve} = require('path') module.exports = { entry: './src/index.js', output: { filename: 'build.js', path: resolve(__dirname, 'build') }, module: { rules: [ { test: /\.css$/i, use: ['style-loader', 'css-loader']}, {// match less file resources test: /\. Less $/ I, use: [// Convert from less to CSS, csS-loader loads CSS, style-loader inserts CSS into Dom {loader: 'style-loader'}, {loader: 'css-loader'}, {loader: 'less-loader'} ] } ] } }Copy the code

PostCSS

Postcss is a tool for converting CSS code with JavaScript tools and plug-ins

When installing PostCSS, install PostCSS-CLI at the same time

yarn add postcss postcss-cli -D
Copy the code

autoprefixer

The Autoprefixer plugin automatically captures the browser’s popularity and supported attributes, and automatically prefixes CSS rules based on this data

yarn add autoprefixer -D
Copy the code

We can add some properties to the CSS file

// src/style/common.css

:fullscreen {

}
.test {
  user-select: none
}
Copy the code

Use the Postcss tool from the command line, and specify autoprefixer

npx postcss --use autoprefixer -o ./src/style/commoncopy.css ./src/style/common.css
Copy the code

After executing this command, we can see that there is a commoncopy. CSS file in the SRC /style directory, and the CSS properties are automatically prefixed

// src/style/commoncopy.css

:-webkit-full-screen {

}
:-ms-fullscreen {

}
:fullscreen {

}
.test {
  -webkit-user-select: none;
     -moz-user-select: none;
      -ms-user-select: none;
          user-select: none
}
Copy the code

postcss-loader

In the development, we will not directly use the command tool to process CSS, we can use the PostCSS tool with Webpack, can be done by postCSS-loader

yarn add postcss-loader -D
Copy the code

In the WebPack configuration file

const {resolve} = require('path') module.exports = { entry: './src/index.js', output: { filename: 'build.js', path: resolve(__dirname, 'build') }, module: { rules: [ { test: /.css$/i, use: [ 'style-loader', 'css-loader', { loader: 'postcss-loader', options: {postcssOptions: {postcssloader: {postcssloader: {postcssloader: {postcssloader: {postcssloader: {postcssloader: {postcssloader: { [ require('autoprefixer') ] } } } ] }, ] } }Copy the code

postcss-preset-env

The postCSS-Preset -env plugin helps us turn some of the modern CSS features into CSS that most browsers know, and adds the needed polyfills based on the target browser or runtime environment, and automatically helps us to add autoprefixer

yarn add postcss-preset-env -D
Copy the code

In the WebPack configuration file

const {resolve} = require('path')
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'build.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /.css$/i,
        use: [
          'style-loader',
          'css-loader',
          {
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                plugins: [ require('postcss-preset-env') ]
              }
            }
          }
        ]
      },
    ]
  }
}
Copy the code

Browserslist

Features supported by different browsers: CSS features, JS syntax, compatibility between browsers, whether all browsers in the market need to be compatible

Browserslist is a configuration that shares the target browser and node.js version between different front-end tools

Browserslist writes rules

In the package. The json configuration

{" devDependencies ": {" autoprefixer" : "^ 10.3.1", "CSS - loader" : "^ 6.2.0", "less" : "^ 4.4.1", "less - loader" : "^ 10.0.1 postcss", ""," ^ 8.3.6 ", "postcss - cli" : "^ 8.3.1", "postcss - loader" : "^ 6.1.1", "postcss - preset - env" : "^ 6.7.0 style -", "loader" : "^ 3.2.1", "webpack" : "^ 5.14.0", "webpack - cli" : "^ 4.3.1"}, "browserslist" : [ "> 1%", "last 2 versions", "no dead" ] }Copy the code

.browserslist file configuration

In addition to configuration in package.json, you can also create a.browserslist file in the root directory for configuration

> 1%
last 2 versions
no dead
Copy the code

Browserslist data comes from Can I Use, and our previous autoprefixer and PostCSS-Preset -env, as well as other plug-in tools, are compatible with these configurations

If we don’t configure Browserslist, browserslist will have a default configuration

Working with other resources

In the development process, sometimes we rely on other resources, such as images, fonts, videos, etc., and use corresponding Loaders to process these resources

file-loader

yarn add file-loader -D
Copy the code

File-loader will parse the import/require() on the file into a URL and send the file to the output directory

Import the pre-prepared image into the entry file

// src/index.js

const img = new Image()
img.src = require('./images/1.png).default
document.body.appendChildren(img)
Copy the code

In the WebPack configuration file

const {resolve} = require('path')

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif|svg)$/i,
        use: [
          {
            loader: 'file-loader',
          },
        ],
      }
    ]
  }
}
Copy the code

After yarn Run build is executed, we can see that there is an additional image in the build folder, and the name is similar to the hash value. We create an index. HTML file and import the bundle.js file with the script tag

Setting the file name

It would work with a document’s original name or placeholder extension

File naming rules

The commonly used placeholder

  • [ext] handles the extension of the file
  • [name] Specifies the name of the processing file
  • The contents of the [hash] file, processed using the hash function of MD4
  • [hash:

    ] Specifies the length of the computed hash
  • [path] The path of the file relative to the WebPack configuration file

In the WebPack configuration file

const {resolve} = require('path') module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: resolve(__dirname, 'build') }, module: { rules: [ { test: /\.(png|jpe?g|gif|svg)$/i, use: [ { loader: 'file-loader', options: {name: '[name].[hash:8].[ext]', // Use outputPath to specify the folder to save the file. 'img / / or in the following way, the specified file after packaging storage folder at the same time, set the name / / name:' img / [name] [8] hash: [ext] '}},]],}}}Copy the code

url-loader

yarn add url-loader -D
Copy the code

Url-loader is similar to file-loader. It converts a file to a Base64 URI and returns a DataURL if the file is smaller than the byte limit

Import the pre-prepared image into the entry file

// src/index.js

const img = new Image()
img.src = require('./images/1.png)
document.body.appendChildren(img)
Copy the code

In the WebPack configuration file

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif|svg)$/i,
        use: [
          {
            // loader: 'file-loader',
            loader: 'url-loader',
            options: {
              name: '[name].[hash:8].[ext]',
              outputPath: 'img'
              // name: 'img/[name].[hash:8].[ext]'
            }
          },
        ],
      }
    ]
  }
}
Copy the code

After removing the previous build file and executing yarn Run Build, we can see that there are no images in the build folder. Create an index. HTML file and import the bundle.js file with the script tag. Images can still be viewed through a browser, because by default urL-loader converts all image files to Base64 encoding

Url – limit of loader

After developing base64, the miniaturized transformation can be requested along with the page, reducing the unnecessary request process. Large images can be used directly, if large images are also converted, but will affect the speed of the page request

Limit Allows you to set the conversion limit

const {resolve} = require('path') module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: resolve(__dirname, 'build') }, module: { rules: [ { test: /\.(png|jpe?g|gif|svg)$/i, use: [ { // loader: 'file-loader', loader: 'url-loader', options: { name: '[name].[hash:8].[ext]', outputPath: "Img", / / limit in bytes limit: 1024}},]],}}}Copy the code

Asset Modules Type

In Webpack5, asset Modules Type allows you to load other resources (fonts, ICONS, etc.) without having to configure additional loaders such as file-loader, url-loader, raw-loader.

Asset Modules Type replaces all of these Loaders by adding four new module types

  • asset/resourceEmit a separate file and export the URL. It used to be possible to usefile-loader.
  • asset/inlineExport the data URI of the resource. It used to be possible to useurl-loader.
  • asset/sourceExport the source code of the resource. It used to be possible to useraw-loader.
  • assetAutomatically choose between exporting the data URI or issuing a separate file. It used to be possible to useurl-loader, and configure volume size limits to achieve this.

Import an image

// src/index.js
const img = new Image()

img.src = require('./images/1.png')
document.body.appendChild(img)
Copy the code

Previously we need to configure file-loader for processing, webpack5 can directly set type to process resources

const {resolve} = require('path')

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif|svg)$/i,
        type: 'asset/resource'
      }
    ]
  }
}
Copy the code

Customize the output path and file name of the file

  • Modify Output and add the assetModuleFilename property
const {resolve} = require('path') module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: Resolve (__dirName, 'build'), // Add assetModuleFilename attribute assetModuleFilename: 'img/[name].[Hash :8]. { rules: [ { test: /\.(png|jpe?g|gif|svg)$/i, type: 'asset/resource' } ] } }Copy the code
  • Add a Generator property and set filename
const {resolve} = require('path') module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: Resolve (__dirName, 'build'), // Add assetModuleFilename attribute // assetModuleFilename: 'img/[name].[hash:8].[ext]'}, module: {rules: [{test: / \. (PNG | jpe? G | | GIF SVG) $/ I type: 'asset/resource', / / add the generator, set the filename attribute the generator: { filename: 'img/[name].[hash:8].[ext]' } } ] } }Copy the code

Volume conversion limit

The volume conversion was previously restricted by limit in urL-loader

  1. Set type to asset
  2. Add the Parser attribute, specify the condition for the dataUrl, and add the maxSize attribute
const {resolve} = require('path') module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: resolve(__dirname, 'build'), }, module: { rules: [ { test: /\.(png|jpe?g|gif|svg)$/i, // type: 'asset/resource', type: 'asset', generator: {filename: 'img/[name].[hash:8].[ext]'}, Parser: {dataUrlCondition: {// Set maxSize: 100000}}}]}}Copy the code

The font is loaded

When using a particular font or font icon, some font files are introduced, and these font files are handled in much the same way

// src/font/font.css @font-face { font-family: "calculatrix-7"; src: url(.. /font/calculatrix-7.ttf); } @font-face { font-family: "pang-men-zheng-dao"; src: url(.. /font/pang-men-zheng-dao.ttf); } @font-face { font-family: "you-she-biao-ti-hei"; src: url(.. /font/you-she-biao-ti-hei.ttf); }Copy the code
// src/style/index.css
.title {
  font-family: 'calculatrix-7';
}
.title2 {
  font-family: 'pang-men-zheng-dao';
}
.title3 {
  font-family: 'you-she-biao-ti-hei';
}
Copy the code

Import styles and add Dom elements and set class names

// src/index.js
import './font/font.css'
import './style/index.css'

const createSpan = (className) => {
  const el = document.createElement('p')
  el.classList.add(className)
  el.innerText = 'hello webpack'
  return el
}

document.body.appendChild(createSpan('title1'))
document.body.appendChild(createSpan('title2'))
document.body.appendChild(createSpan('title3'))
Copy the code

Webpack configuration file

const {resolve} = require('path') module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: resolve(__dirname, 'build'), }, module: { rules: [ { test: /\.css$/i, use: [' style - loader ', 'CSS - loader]}, / / configuration rules font package {test: / \. (woff2? | eot | the vera.ttf) $/, type:' asset/resource, the generator: { filename: 'font/[name].[hash:8].[ext]' } } ] } }Copy the code

Plugins

Plugins are the backbone of Webpack. Webpack itself is built on top of the same plugin system you use in your Webpack configuration!

Plugins can also do things that loaders cannot do. Plugins can be used to perform a wider range of tasks, such as packaging optimization, resource management, environment variable injection, and so on

CleanWebpackPlugin

After each configuration change, we need to package again, because each package may generate different files, some original files will be retained, so we need to manually delete the last package folder

The CleanWebpackPlugin plugin helps you do this automatically

yarn add clean-webpack-plugin -D
Copy the code

Webpack configuration file

Const {resolve} = require('path') // import CleanWebpackPlugin module.exports = { entry: './src/index.js', output: { filename: 'bundel.js', path: Resolve (__dirname, 'build')}, // Use plugins in plugins: [new CleanWebpackPlugin()]}Copy the code

This way the CleanWebpackPlugin will automatically delete the last package for us each time we pack

HtmlWebpackPlugin

After each package, we need to manually create an index. HTML file, and also need to manually import bundle.js, which is quite troublesome. For HTML packaging, we can use HtmlWebpackPlugin

yarn add html-webpack-plugin -D
Copy the code

Webpack configuration file

Const {resolve} = require('path') // import CleanWebpackPlugin // import HtmlWebpackPlugin const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = {entry: './ SRC /index.js', output: {filename: 'bundel.js', path: resolve(__dirname, 'build')}, [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ title: 'hello webpack' }) ] }Copy the code

When we pack again, we’ll find an index. HTML file in the build folder and automatically import bundle.js. The content in

hello webpack is the title parameter we passed into the new HtmlWebpackPlugin

Customize HTML templates

Sometimes the default generated HTML template content is not what we want. In this case, we can create our own template HTML file by customizing the template. Here we use the index.html template file of the Vue project as an example

Root record create public folder and create index.html

<! -- public/index.html --> <! -- Vue index.html template file --> <! DOCTYPE html> <html lang=""> <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"> <link rel="icon" href="<%= BASE_URL %> <title><%= htmlWebpackPlugin.options.title %></title> </head> <body> <noscript> <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id="app"></div> <! -- built files will be auto injected --> </body> </html>Copy the code

Webpack configuration file

Const {resolve} = require('path') // import CleanWebpackPlugin // import HtmlWebpackPlugin const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = {entry: './src/index.js', output: { filename: 'bundel.js', path: resolve(__dirname, 'build') }, plugins: [new CleanWebpackPlugin(), new HtmlWebpackPlugin({title: 'hello webpack', // specify the location of the HTML template file: './public/index.html' }) ] }Copy the code

At this point, we will find that the console reported an error

ERROR in Template execution failed: ReferenceError: BASE_URL is not defined

ERROR in ReferenceError: BASE_URL is not defined

The reason is that in our template file DefinePlugin

An error occurred while compiling and packaging the template HTML file. The reason is that we used a BASE_URL constant in the template file, but we did not define this constant value, so there was an undefined error

DefinePlugin allows configured global constants to be created at compile time and does not need to be installed separately because it is a built-in plug-in for WebPack

Webpack configuration file

const {resolve} = require('path') const {CleanWebpackPlugin} = require('clean-webpack-plugin') const HtmlWebpackPlugin = // import DefinePlugin const {DefinePlugin} = require('webpack') module.exports = {entry: './src/index.js', output: { filename: 'bundel.js', path: resolve(__dirname, 'build') }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ title: 'hello webpack', template: ' '. / public/index. HTML}), new DefinePlugin ({/ / configuration BASE_URL constants BASE_URL: '". / "})]}Copy the code

CopyWebpackPlugin

yarn add copy-webpack-plugin -D
Copy the code

In the process of packaging, sometimes we need to copy some files into the build folder, such as the favicon.ico icon

Ico icon file in the public directory. We want to have Favicon. ico in the packaged build folder as well. CopyWebpackPlugin can copy existing individual files or entire directories to the build directory

Webpack configuration file

const {resolve} = require('path') const {CleanWebpackPlugin} = require('clean-webpack-plugin') const HtmlWebpackPlugin = // import CopyWebpackPlugin const {DefinePlugin} = require('webpack') // import CopyWebpackPlugin const CopyWebpackPlugin = require('copy-webpack-plugin') module.exports = { entry: './src/index.js', output: { filename: 'bundel.js', path: resolve(__dirname, 'build') }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ title: 'hello webpack', // specify the location of the HTML template file template: './public/index.html'}), new DefinePlugin({BASE_URL: New CopyWebpackPlugin({patterns: [{// set copy source from: 'public', // allows configuration of the glob pattern matching library used by the plugin globOptions: {// Supports files to be excluded from the options list. Ignore: [//.ds_store MAC directory automatically generated file, do not need to copy '**/.ds_store ', // index.html template file, do not need to copy '**/index.html']}}]})]}}Copy the code

Mode

The Mode configuration option tells WebPack to use its built-in optimizations accordingly

Production mode is used by default

Configurable mode are: ‘none’ | ‘development’ | ‘production’

options describe
development Set up theDefinePluginIn theprocess.env.NODE_ENV 为 developmentTo enable valid names for modules and chunks
production Set up theDefinePluginIn theprocess.env.NODE_ENV 为 productionTo enable deterministic obfuscated names for modules and chunks,FlagDependencyUsagePlugin.FlagIncludedChunksPlugin.ModuleConcatenationPlugin.NoEmitOnErrorsPlugin 和 TerserPlugin
none Opt out of any default optimization options

Webpack configuration file

Const {resolve} = require('path') module.exports = {// config mode: 'development', // config mode: 'production', // Default packaging mode is production // mode: 'none', entry: './ SRC /index.js', output: {filename: 'bundle.js', path: resolve(__dirname, 'build') } }Copy the code

It also supports specifying the packaging mode on the command line

webpack --mode=development
Copy the code

Source Map

In daily development, source code and webpack compressed construction code is not the same, for example, in the production environment to write the source code error and the corresponding line after compilation must be inconsistent, at this time is very inconvenient debugging

The Source Map is explained in the MDN documentation to enable the browser to refactor the Source and render the refactor Source in the debugger

To borrow teacher Ruan Yifeng’s explanation, the Source map is an information file, which stores location information. That is, every position in the transformed code corresponds to the position before the transformation. With this, when something goes wrong, the debugger will display the original code instead of the converted code. This undoubtedly brings great convenience to developers

In Webpack, devtool controls whether and how Source maps are generated

Devtool has a lot of values to work with, different values generate different Source maps, and the build speed can vary depending on which values you choose

Production

In production, devtool is default and does not generate Source maps

Development

In the development environment, devtool defaults to eval and does not generate a Source Map

Also, if we set devtool to false, the Source Map will not be generated

eval

Eval mode, which converts lines of code into a string, is passed to the eval function with a comment appended to the end //# sourceURL

// SRC /index.js // Print a non-existent variable console.log(ABC)Copy the code
// webpack.config.js const {resolve} = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') const {CleanWebpackPlugin} = require(' cleanwebpack-plugin ') module.exports = {devtool defaults to eval mode: 'development', entry: './src/index.js', output: { filename: 'bundle.js', path: resolve(__dirname, 'build') }, plugins: [ new HtmlWebpackPlugin(), new CleanWebpackPlugin() ] }Copy the code

Although Eval does not generate the Source Map, eval performs the comments appended to the code to restore the corresponding file

source-map

When devtoop is source-map, a separate Source map file is generated

//# sourceMappingURL=bundle.js.map is appended to the end of the bundle.js build file with a comment pointing to the Source map file

// webpack.congif.js module.exports = {mode: 'development', // set source-map devtool: 'source-map' // set webpack.congif.js module.exports = {mode: 'development', // set source-map devtool: 'source-map'... }Copy the code

The browser will find the source-map file based on this comment

eval-source-map

When devtoop is eval-source-map, the source map is appended to the eval function with DataUrl

inline-source-map

When devtoop is inline-source-map, the source map is converted to the DataUrl and added to the bundle

The differences in the Source Map generated for different values can be seen in the official example, which is not illustrated here

recommended

Of the many devtoop values officially available, some are suitable for development environments, some are suitable for production environments, and quick Source Maps are often required for development

Development environment: Source-map or cheap-module-source-map is recommended

Production environment: default devtool options, source-map, hidden-source-map, nosource-source-map

Babel

Development typically involves using ES5+ ‘s advanced syntax or TypeScript and writing JSX, both of which are conversions to Babel, a toolchain, It is primarily used to convert code written in ECMAScript 2015+ syntax into backward-compatible JavaScript syntax so that it can run in current and older versions of browsers or other environments

The main features include syntax conversion, source conversion, Polyfill to add missing features to the target environment, and so on

use

Using Babel alone from the command line requires downloaded dependencies

yarn add @babel/core @babel/cli @babel/preset-env -D
Copy the code
  // src/test.js
const sum = (a, b) => a + b
console.log(sum(1, 1))
Copy the code

The command line compiles all code from the SRC directory to the lib directory

/node_modules/. Bin/Babel SRC -- out-of-dir lib --presets=@babel/ env - # or NPX Babel SRC -- out-of-dir lib --presets=@babel/preset-envCopy the code
 // lib/test.js
"use strict";

var sum = function sum(a, b) {
  return a + b;
};

console.log(sum(1, 1));
Copy the code

At this point we have converted the ES2015+ syntax

In the dependencies we downloaded, the core functions of Babel are contained in the @babel/core module, @babel/ CLI is a tool that can be used from a terminal (command line), and @babel/preset-env is an intelligent preset for Babel

babel-loader

Babel-loader allows JavaScript files to be translated using Babel and Webpack

yarn add babel-loader -D
Copy the code
  // src/index.js
  
console.log([1, 2, 3, 4].map(n => n * 2))
Copy the code
// webpack-config.js const {resolve} = require('path') module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: resolve(__dirname, 'build') }, module: { rules: [ { test: /\.m?js$/, use: {// Use babel-loader loader: 'babel-loader', options: {// Pass Babel presets: ['@babel/preset-env']}}}]}}Copy the code
/ / build/bundle. Js console. The log ([1, 2, 3, 4]. The map ((function (n)} {return 2 * n)));Copy the code

With babel-Loader, we also converted the ES2015+ syntax

By default, @babel/preset-env uses.browserslist as the configuration source. Browserslist was mentioned earlier in postCSS

If browserslist is not used as the configuration source, you can also use Targets to set the target browser

const {resolve} = require('path') module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: Resolve (__dirname, 'build')}, module: {rules: [{test: /\.m? Js $/, use: { 'babel-loader', options: {// Preset to Babel // presets: ['@babel/preset-env'] presets: [[' @ Babel/preset - env, {/ / set the targets the targets: [" chrome 58 ", "11" ie]}]]]}}}}}Copy the code

The configuration file

The Configuration File Types of Babel can be seen in the official Configuration File Types (babel.config with the js or JSON suffix as an example)

Create babel.config.js or babel.config.json in the root directory

  // babel.config.js
 module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        targets: [
          "chrome 58",
          "ie 11"
        ]
      }
    ]
  ]
}
Copy the code
 // babel.config.json
 {
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": [
          "chrome 88",
          "ie 11"
        ]
      }
    ]
  ]
}
Copy the code

At this point we do not need to configure presets in the WebPack configuration file

// webpack.config.js module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: Resolve (__dirname, 'build')}, module: {rules: [{test: /\.m? Js $/, use: { 'babel-loader', } } ] } }Copy the code

Babel-polyfill, core-js, and Regenerator-Runtime

By default, Babel only converts new JavaScript syntax, not new apis, such as Iterator, Generator, Set, Maps, Proxy, Reflect, Symbol, Promise, etc. Before Babel 7.4.0, @babel/polyfill was used for conversion. Since Babel 7.4.0, this package has been deprecated. Instead, include core-js/stable(to populate ECMAScript features) and Regenerator-Runtime/Runtime (transpiled generator function required) directly to accomplish polyfill

yarn add core-js regenerator-runtime
Copy the code

Set the useBuiltIns

In the babel.config.js file in the root directory, we configure useBuiltIns

UseBuiltIns properties

  • Usage: Polyfill is used locally without global pollution

  • entry: Introduce core-js and Regenerator-Runtime via require or import. Introducing them more than once will give you an error, such as if one of the libraries we rely on itself uses some of polyfill’s features. All polyfills are imported based on the browserslist target browser, and the corresponding packaging file gets bigger

  • False: default value, do not use polyfill

Exports = {presets: [['@babel/preset-env', {// set useBuiltIns to usage // useBuiltIns: 'usage', // set useBuiltIns to entry useBuiltIns: 'entry'}]]}Copy the code

corejs

Corejs: A modular standard library for JavaScript. Includes ECMAScript to 2021 polyfills: Promises, Symbols, Collections, Iterators, typed Arrays, many other features, ECMAScript proposals, some cross-platform WHATWG/W3C features and proposals

Exports = {presets: [['@babel/preset-env', {// set useBuiltIns to usage // useBuiltIns: 'Usage ', // set useBuiltIns to entry useBuiltIns:' Entry ', // set corejs corejs: 3.17}]]}Copy the code

Exclude files that do not require polyfill, such as node_modules

const {resolve} = require('path') module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: Resolve (__dirname, 'build')}, module: {rules: [{test: /\.m? Js $/, // exclude: /node_modules/, use: Babel-loader loader: 'babel-loader'}}]}}Copy the code

Convert the following code with different configurations

 // src/index.js
new Promise((resolve,reject) => {})
Copy the code

usage

Set useBuiltIns to Usage

// babel.config.js module.exports = {presets: [['@babel/preset-env', {// Set useBuiltIns to usage useBuiltIns: 'usage', // set corejs version corejs: 3.8}]]}Copy the code

Bundled bundel.js, I did a rough count and it’s about… I don’t know how many lines of code there are

Entry Set useBuiltIns to Entry

Core-js /stable and regenerator-Runtime/Runtime need to be introduced in the entry file

// SRC /index.js // introduce core-js/stable and regenerator-Runtime /runtime import 'core-js/stable' import 'regenerator-runtime/runtime' new Promise((resolve,reject) => {})Copy the code
Exports = {presets: [['@babel/preset-env', {// set useBuiltIns to usage // useBuiltIns: 'Usage ', // set useBuiltIns to entry useBuiltIns:' Entry ', // set corejs corejs: 3.17}]]}Copy the code

The bundel.js file is significantly larger than the Usage package

babel/preset-react

Convert JSX through Babel

yarn add @babel/preset-react -D
Copy the code

Webpack configuration file

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

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

module.exports = {
  entry: './src/index.jsx',
  output: {
    filename: 'bundle.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        }
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'hello react',
      template: './index.html'
    })
  ]
}
Copy the code

Entry JSX file

 // src/index.jsx
import React, {Component} from 'react'
import ReactDom from 'react-dom'

class App extends Component {
  constructor (props) {
    super(props)
    this.state = {
      message: 'hello react'
    }
  }
  render () {
    return (
      <div>
        <h1>{this.state.message}</h1>
      </div>
    )
  }
}

ReactDom.render(<App />, document.querySelector('#app'))
Copy the code

Index.html template file

<! -- 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 > Document < / title > < / head > < body > < div id =" app "> < / div > < / body > < / HTML >Copy the code

Babel configuration file

// babel.config.js module.exports = { presets: [ [ '@babel/preset-env', { useBuiltIns: 'entry', corejs: 3.17}], // set @babel/preset-react ['@babel/preset-react']]}Copy the code

Compile the TypeScript

Typescript can be converted to Javascript using either TS-Loader or Babel-loader

ts-loader

Ts-loader is converted to JavaScript through TypeScript’s compiler

Download the dependent

yarn add typescript ts-loader -D
Copy the code

Generate the tsconfig.json file

tsc --init
Copy the code

Configuration information

module: {
    rules: [
      {
        test: /\.ts$/,
        exclude: /node_modules/,
        use: 'ts-loader'
      }
    ]
  }
Copy the code

babel-loader

yarn add @babel/preset-typescript -D
Copy the code

Using this default, Babel can convert Typescript to Javascript

    // babel.config.js
module.exports = {
  presets: [
    // ...
    ['@babel/preset-typescript']
  ]
}
Copy the code

Eslint use

Install ESLint

yarn add eslit -D
Copy the code

Generating a Configuration File

npx eslint --init
Copy the code

The current configuration I generated is Airbnb-style Elsint. You can refer to the official website for detailed configuration

    // .eslintrc.js
    
module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    'airbnb-base',
  ],
  parserOptions: {
    ecmaVersion: 13,
  },
  rules: {
  },
};
Copy the code

EslintWebpackPlugin This plugin is used to find and fix problems in JavaScript code

yarn add eslint-webpack-plugin -D
Copy the code

Configuration information

const {resolve} = require('path')
const ESLintPlugin = require('eslint-webpack-plugin')

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    new ESLintPlugin()
  ]
}
Copy the code

DevServer

Webpack-dev-server can be understood as a small static file server that provides Web services for resource files generated by WebPack packaging

yarn add webpack-dev-server -D
Copy the code

Define package.json script to execute Webpack serve

{" name ":" serve ", "version" : "1.0.0", "main" : "index. Js", "license" : "MIT", "scripts" : {" build ":" webpack ", "serve" : "webpack serve" } }Copy the code

The mode is changed to development, using HtmlWebpackPlugin and providing templates to create HTML files

    // webpack.config.js
    
const {resolve} = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html'
    }),
    new CleanWebpackPlugin()
  ]
}
Copy the code

Write a piece of code in the entry file

    // src/index.js
    
class Persion {
  constructor(name) {
    this.name = name
  }
  sayHi() {
    console.log(`Hello My Name is ${this.name}`)
  }
}
const James = new Persion('James')
James.sayHi()
Copy the code

On the CLI, run the yarn serve script and access the browser using the address prompted by webpack-dev-server

We can see that the browser outputs what we want, and when we modify the code, we save it and the browser refreshes

HMR

Hot Module replacement (or HMR) is one of the most useful features webPack provides. It allows all types of modules to be updated at run time without a complete refresh.

Configure devServer to enable HMR

const {resolve} = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') const {CleanWebpackPlugin} = require('clean-webpack-plugin') module.exports = { mode: 'development', entry: // SRC /index.js', // configure devServer devServer: {// enable hot update hot: true}, output: {filename: 'bundle.js', path: resolve(__dirname, 'build') }, plugins: [ new HtmlWebpackPlugin({ template: './public/index.html' }), new CleanWebpackPlugin() ] }Copy the code

Specify which modules use HMR. When the specified module is updated, the status of the remaining modules is preserved to improve development efficiency

import './test' class Persion { constructor(name) { this.name = name } sayHi() { console.log(`Hello My Name is ${this.name} ')}} const James = new Persion('James') james.sayhi () if (module.hot) {// specify module Module. Hot. Accept ('. / test. Js', () = > {the console. The log (' test module updated ')})}Copy the code

In real development, there are so many module files that it is impossible to specify one module by one. For example, VUE-Loader supports THE HMR of VUE components to provide out-of-the-box experience

host

Specify the host address to use

The default value is localhost

If you want your server to be externally accessible, specify it like this

module.exports = { //... DevServer: {host: '0.0.0.0'}}Copy the code

port

Specify the port number to listen on requests:

module.exports = {
  //...
  devServer: {
    port: 9527
  }
}
Copy the code

Gzip compression

module.exports = {
  //...
  devServer: {
    compress: true
  }
}
Copy the code

Compressed from 294KB to 70.9KB

proxy

Proxying certain urls can be useful when you have separate API back-end development servers and want to send API requests on the same domain

module.exports = {
  //...
  devServer: {
    proxy: {
      '/api': 'http://localhost:3000'
    }
  }
}
Copy the code

Now, on/API/users request will request broker to http://localhost:3000/api/users. If you don’t want to pass/API, you need to rewrite the path:

module.exports = {
  //...
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        pathRewrite: { '^/api': '' }
      }
    }
  }
}
Copy the code

More information about proxy configuration can be found on the official website

resolve

How is the configuration module resolved

alias

Create an alias for import or require to make module introduction easier. For example, some common modules are located under the SRC/folder

    // webpack.config.js

module.exports = {
    // ...
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src')
    }
  }
}
Copy the code

This approach is handy when the module level is deep

    // src/index.js

// import {sum} from './util.js'
import {sum} from '@/util.js'

console.log(sum(1, 1))
Copy the code

extensions

The ability to introduce modules without extensions and resolve these suffixes in order. If there are multiple files with the same name but different suffixes, WebPack will parse the files with the suffixes listed at the top of the array and skip the rest

    // webpack.config.js

module.exports = {
    // ...
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src')
    },
    extensions: ['.js', '.vue', '.json']
  }
}
Copy the code
    // src/index.js

// import {sum} from './util.js'
import {sum} from '@/util'

console.log(sum(1, 1))
Copy the code

The configuration file is removed. Procedure

. Currently we are through a webpack config. The configurations of js file, when the configuration information is more and more, this configuration file will be difficult to maintain, we should be divided according to the current environment configuration, such as the development environment and production environment can be differentiate different configuration, and do not need to use some configuration production environment, Some configurations are not required by the development environment, and some configurations are used by both the development and production environment. We need to make a common separation but separate configurations for the current environment

Creating a Configuration File

Create a config folder under the project root

Create three configuration files under this folder

  • webpack.base.conf— Common infrastructure configuration for development and production environments
  • webpack.dev.conf— Development environment configuration
  • webpack.prod.conf– Production environment configuration

Configuring the Execution Script

In the chapter on Specifying configuration files, I described how to configure a file with the –config command so that different files can be executed for different commands

Configure the execution script in package.json

Build — Project packaging

Serve — Start local service with DevServe

{
  "scripts": {
    "build": "webpack --config config/webpack.prod.conf",
    "serve": "webpack serve --config config/webpack.dev.conf"
  }
}
Copy the code

webpack-merget

Webpack-merge merges the common base configuration with the configuration partitioned by the current environment

yarn add webpack-merge -D
Copy the code

Common Base configuration

/*
* @Description base
*/
const {join} = require('path')

const resolve = (dir) => {
  return join(__dirname, '..', dir)
}

module.exports = (env) => {
  return {
    entry: './src/index.js',
    output: {
      filename: 'bundle.js',
      path: resolve('build')
    }
    // ...
  }
}
Copy the code

The development environment

/*
* @Description dev
*/

const {merge} = require('webpack-merge')
const baseConfig = require('./webpack.base.conf')

module.exports = merge(baseConfig, {
  mode: 'development',
  //...
})
Copy the code

The production environment

/*
* @Description prod
*/

const {merge} = require('webpack-merge')
const baseConfig = require('./webpack.base.conf')

module.exports = merge(baseConfig, {
  mode: 'production',
  //...
})
Copy the code

The separation

Code separation is one of the most compelling features of WebPack. This feature enables you to separate code into different bundles and load these files on demand or in parallel. Code separation can be used to obtain smaller bundles and control resource load priorities, which, when used properly, can greatly affect load times.

  • Entry starting point: Manually detach code using the Entry configuration.
  • To prevent duplication, use Entry Dependencies or SplitChunksPlugin to delete and separate chunks.
  • Dynamic import: Separation of code through inline function calls to modules.

Entry Point

Manually separating code through the Entry entry is the easiest and most intuitive way to separate code. However, this method of manual configuration is more, and there are some pitfalls

// webpack.config.js const {resolve} = require('path') module.exports = {mode: 'production', {index: './ SRC /index.js', main: './ SRC /main.js'}, output: {// name is the index of the entry object and main filename: '[name].bundle.js', path: resolve(__dirname, 'build') } }Copy the code

To prevent the repeat

We introduced the same third-party library in multiple modules at the same time, which resulted in repeated references in the packaged bundles

Entry dependencies

Configure the dependOn option option so that modules can be shared between multiple chunks

Here’s lodash as an example

 // src/index.js
 
import _ from 'lodash'
console.log(_.join(['index', 'loaded!']))
Copy the code
 // src/main.js

import _ from 'lodash'
console.log(_.join(['main', 'loaded!']))
Copy the code
 // webpack.config.js

const {resolve} = require('path')

module.exports = {
  mode: 'production',
  entry: {
    index: {import: './src/index.js', dependOn: 'shared'},
    main: {import: './src/main.js', dependOn: 'shared'},
    shared: ['lodash']
  },
  output: {
    filename: '[name].bundle.js',
    path: resolve(__dirname, 'build')
  }
}
Copy the code

SplitChunksPlugin

The SplitChunksPlugin plug-in can extract a common dependency module into an existing chunk of entry or into a newly generated chunk. Using this plug-in, you can also remove duplicate LoDash modules. Webpack is installed and integrated by default and does not need to be installed separately

The splitchunks. chunks property has three values

  • Async – Asynchronously imported modules
  • Initial — non-asynchronous imported modules
  • All — contains modules for both asynchronous and non-asynchronous imports
// webpack.config.js

const {resolve} = require('path')

module.exports = {
  mode: 'production',
  entry: {
    // index: {import: './src/index.js', dependOn: 'shared'},
    // main: {import: './src/main.js', dependOn: 'shared'},
    // shared: ['lodash']
    index: './src/index.js',
    main: './src/main.js'
  },
  output: {
    filename: '[name].bundle.js',
    path: resolve(__dirname, 'build')
  },
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  }
}
Copy the code

More configuration properties of splitChunks can be found on the official website

Dynamic import

Webpack provides two similar techniques when it comes to dynamic code splitting. The first, and recommended option, is to implement dynamic imports using the import() syntax that conforms to the ECMAScript proposal. The second, which is a webPack legacy, uses webPack-specific require.ensure, and we use the first as an example

Export a function as EsModule

  // src/util.js
 
 export const test = () => {
  console.log('test')
}
Copy the code

Import through import()

Since import() returns a promise, we get the util. Js exported function via.then, which can also be used with async functions

    // src/index.js
import('./util').then(res => {
  console.log(res)
})
Copy the code
    // webpack.config.js
const {resolve} = require('path')

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    filename: '[name].bundle.js',
    path: resolve(__dirname, 'build')
  }
}
Copy the code

We have now isolated a chunk using dynamic imports

Prefetch/Preload Module

When importing modules dynamically through import(), we need to execute imPRt () before the separated chunks are downloaded and parsed by the browser

Add a click event to the body to dynamically import the test module when the body is clicked

 // src/index.js
document.body.addEventListener('click', () => {
 import('./test').then(({default: _default}) => {
    _default()
  })
})
Copy the code
// src/test.js
export default () => {
  console.log('test')
}
Copy the code

For the first time to load

After I hit the body

If the module is not very important and the chunk size is large, it will inevitably cause performance problems when the browser downloads and parses it

Webpack V4.6.0 + adds support for prefetching and preloading.

When declaring an import, you can tell webPack to print a “resource hint “to tell the browser, using these built-in directives:

Prefetch: Resources that may be required for some future navigation

Preload: Resources may be required under the current navigation

  • Prefetch is performed using prefetch via magic annotations

/* webpackPrefetch: true */

    // src/index.js
document.body.addEventListener('click', () => {
  import(/* webpackPrefetch: true */'./test').then(({default: _default}) => {
    _default()
  })
})
Copy the code

You can see that the browser has prefetched chunk, and when you click on the body again, a JS file appears again, only this time the browser is parsing the prefetched JS file

  • Preload using preload via magic annotations

/* webpackPreload: true */

    // src/index.js
document.body.addEventListener('click', () => {
  import(/* webpackPreload: true */'./test').then(({default: _default}) => {
    _default()
  })
})
Copy the code

The effects of preloading cannot be demonstrated in a browser

conclusion

The preload directive differs from the prefetch directive in a number of ways:

  • Preload Chunk starts loading in parallel when the parent chunk loads. Prefetch Chunk starts loading after the parent chunk finishes loading.
  • Preload Chunk has medium priority and is downloaded immediately. Prefetch Chunk downloads while the browser is idle.
  • Preload chunk is immediately requested in the parent chunk for the present moment. Prefetch Chunk will be used at some point in the future.
  • Browser support varies.

Terser

A JavaScript parser for ES6+ and the Mangler/Compressor toolkit Terser help compress the code and make the bundle smaller

yarn add terser
Copy the code

Command execution

Terser is a separate plug-in that can be used from the command line

// src/index.js

class Animal {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
}

const dog = new Animal('Fish Ball', 2)
console.log(dog)
Copy the code
npx terser ./src/index.js -o tersermini.js
Copy the code

You can see that a tersermini-js file is output in the root directory

More configurations can be found in the official documentation

terser-webpack-plugin

In Webpack, you can use the TerserPlugin, which uses Terser to compress JavaScript

Webpack V5 comes out of the box with the latest version of the Terser-Webpack-plugin. If you are using WebPack V5 or later and want to customize the configuration, you still need to install the Terser-webpack-plugin. If you use Webpack V4, you must install the version of Terser-webpack-Plugin V4.

yarn add terser-webpack-plugin -D
Copy the code
    // webpack.config.js
const {resolve} = require('path')
const TerserPlugin = require('terser-webpack-plugin')

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: resolve(__dirname, 'build')
  },
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin(
        {
          terserOptions: {
            compress: {
              arrows: false,
              collapse_vars: false,
              comparisons: false,
              computed_props: false,
              hoist_funs: false,
              hoist_props: false,
              hoist_vars: false,
              inline: false,
              loops: false,
              negate_iife: false,
              properties: false,
              reduce_funcs: false,
              reduce_vars: false,
              switches: false,
              toplevel: false,
              typeofs: false,
              booleans: true,
              if_return: true,
              sequences: true,
              unused: true,
              conditionals: true,
              dead_code: true,
              evaluate: true
            },
            mangle: {
              safari10: true
            }
          },
          parallel: true,
          extractComments: false
        }
      )
    ]
  }
}    
Copy the code

You can refer to the official documentation for detailed configuration

Tree Shaking

Tree shaking is a term used to describe removing dead-code from a JavaScript context. It relies on the statically structured features of ES2015 module syntax, such as import and export. This term and concept was actually popularized by the ES2015 module packaging tool rollup

Webpack implements Tree Shaking in two different ways

  • usedExports
  • sideEffects

usedExports

UsedExports marks whether certain functions are used and then optimizes them using Terser

SRC add a main entry index.js and a math.js module

The Math module exports two functions

    // src/math.js
export function sum(n1, n2) {
  return n1 + n2
}

export function square(x) {
  return x * x
}
Copy the code

Index main entry file We introduce one of the methods of the Math module

    // src/index.js
import {sum} from './math'
console.log(sum(1, 2))
Copy the code

In webpack.config.js, you need to set the mode configuration to development to make sure that the bundle will not be compressed for the most intuitive effect

    // webpack.config.js
const {resolve} = require('path')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')

module.exports = {
  mode: 'development',
  devtool: 'source-map',
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    new CleanWebpackPlugin()
  ]
}
Copy the code

Once packaged, we can see that both methods are included in bundle.js, but we have never referenced the Square method

UsedExports defaults to true in Production mode. Now we manually set usedExports to true in Development mode

    // webpack.config.js
module.exports = {
  mode: 'development',
  devtool: 'source-map',
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: resolve(__dirname, 'build')
  },
  optimization: {
    usedExports: true
  },
  plugins: [
    new CleanWebpackPlugin()
  ]
}
Copy the code

If usedExports is set to true, unused harmony export XXX will be used. Tell Terser that this code can be removed during optimization

UsedExports implements Tree Shaking in conjunction with Terser. Set optimization.minimize to true(the default is true in production mode) and remove dead code.

    // webpack.config.js
const {resolve} = require('path')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')

module.exports = {
  mode: 'development',
  devtool: 'source-map',
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: resolve(__dirname, 'build')
  },
  optimization: {
    usedExports: true,
    minimize: true
  },
  plugins: [
    new CleanWebpackPlugin()
  ]
}    
Copy the code

sideEffects

SideEffects, which provides hints to the Webpack Compiler about which files in the project are “pure “so that unused parts of the file can be safely removed

In the main entry file, we simply introduce a test module, but do nothing with it

    // src/index.js
import test from './test'
Copy the code

You can see that the test module was introduced in the packaged bundle.js, even though we didn’t use it

If none of the code contains sideEffects, we can tell webpack that it can safely remove the unused export by simply labeling its property value false via package.json’s “sideEffects”

{
  "name": "demo",
  "sideEffects": false
}
Copy the code

A side effect is defined as code that performs special behavior at import time, rather than just exposing one export or more exports, similar to assigning attributes to global objects.

For example, the following module has side effects

export default {
  name: 'test'
}
window._name = 'test'
Copy the code

If your code does have some sideEffects, you can supply the values of sideEffects as an array

{
  "name": "demo",
  "sideEffects": ["./src/test.js"]
}
Copy the code

This array supports simple Glob pattern matching related files. It uses glob-to-regexp internally (support: *, **, {a,b}, [a-z]). If the matching mode is *. CSS and does not contain /, it is regarded as **/*.css.

All imported files are affected by Tree Shaking. This means that if you use something like CSS-Loader in your project and import a CSS file, you need to add it to the Side Effect list to avoid accidentally removing it in production mode

{
  "name": "demo",
  "sideEffects": ["./src/test.js", "*.css"]
}
Copy the code

Or set “sideEffects” in the Module. rules configuration option.

export default {
 // ...
 module: {
   rules: [
     {
       test: /\.css$/i,
       use: [
        'style-loader',
        'css-loader'
       ],
       sideEffects: true
     }  
   ]  
 }
}
Copy the code

conclusion

SideEffects and usedExports (more commonly known as Tree shaking) are two different ways to optimize

UsedExports relies on Terser to detect side effects in statements. It’s a JavaScript task and it’s not as straightforward as sideEffects. And it cannot jump the rotor tree/dependency because the bylaws say side effects need to be evaluated

SideEffects is more efficient because it allows skipping entire modules/files and entire file subtrees

Some plug-ins for handling CSS

We’ve already used a few loaders for CSS handling, so let’s optimize our CSS with a few plug-ins

MiniCssExtractPlugin

Previously, we used CSS-loader, style-loader and other loaders to inject CSS into DOM instead of extracting CSS into a separate file. Mini-css-extract-plugin This plug-in extracts CSS into a separate file. It creates a CSS file for each JS file that contains CSS. It supports loading CSS and SourceMap on demand

yarn add mini-css-extract-plugin -D
Copy the code
    // src/style.index.css
html,
body {
  width: 100%;
  height: 100%;
  overflow: hidden;
}

.caption {
  display: flex;
  align-items: center;
  justify-content: center;
  color: #ddd;
}
Copy the code
    // src/index.js
import './style/index.css'    
Copy the code

The MiniCssExtractPlugin needs to be used in two places in the WebPack configuration, one is plugins, Another place is in the module. The rules used in the configuration options MiniCssExtractPlugin. Loader replace our previous style – loader

// webpack.config.js const {resolve} = require('path') const MiniCssExtractPlugin = require('mini-css-extract-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') const {CleanWebpackPlugin} = require('clean-webpack-plugin') module.exports = { mode: 'production', entry: './src/index.js', output: { filename: 'bundle.js', path: resolve(__dirname, 'build') }, module: { rules: [ { test: /\.css$/i, use: [/ / use MiniCssExtractPlugin. Replace style - loader loader MiniCssExtractPlugin. Loader, // 'style-loader' 'css-loader']}]}, plugins: [// Use MiniCssExtractPlugin new MiniCssExtractPlugin({// Determine the name of each output CSS file filename: 'CSS /[name][contenthash:6].css', // Determine the name of each output CSS block file chunkFilename: 'css/[id][contenthash:6].css' }), new HtmlWebpackPlugin({ template: './src/index.html' }), new CleanWebpackPlugin() ] }Copy the code

According to the above configuration, we can find that there is a CSS folder under the build package file, and the CSS file under the CSS folder is the CSS file we just removed through the configuration package

MiniCssExtractPlugin also has many configuration parameters. For more parameters, please refer to the official website

PurgeCSS

PurgeCSS is a tool for removing unused CSS code. Use it as part of your development process. When you are building a website, you may decide to use a CSS framework such as TailwindCSS, Bootstrap, MaterializeCSS, Foundation, etc. However, you only use a small part of the framework and a lot of CSS styles are not used. PurgeCSS analyzes your content and CSS files. First it matches the selectors used in the CSS file with the selectors in the content file. Then it removes the unused selectors from the CSS to produce smaller CSS files

PurgeCSS is a separate tool for optimizing CSS in our projects using purgecss-webpack-Plugin in webPack configuration

yarn add purgecss-webpack-plugin -D
Copy the code
    // src/style/index.css
html,
body {
  width: 100%;
  height: 100%;
  overflow: hidden;
}

.caption {
  display: flex;
  align-items: center;
  justify-content: center;
  color: #ddd;
}
.wrapper {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
Copy the code

Add a Div tag with the Caption class style name to the index.html template file

// src/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, "> <title>Document</title> </head> <body> <div class=" Caption ">PurgeCSS</div> </body> </ HTML >Copy the code
    // src/index.js
import './style/index.css'
Copy the code

With the WebPack plug-in, you can specify the content to be parsed by purgecSS by setting an array of file names. These can be HTML, PUG, Blade, and so on. You can also use modules like glob or glob-all to easily get a list of files

yarn add glob -D
Copy the code
// webpack.config.js const {resolve} = require('path') const MiniCssExtractPlugin = require('mini-css-extract-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') const {CleanWebpackPlugin} = require('clean-webpack-plugin') const PurgecssPlugin = require('purgecss-webpack-plugin') const glob = require('glob') module.exports = { mode: 'production', entry: './src/index.js', output: { filename: 'bundle.js', path: resolve(__dirname, 'build') }, module: { rules: [ { test: /\.css$/i, use: [ MiniCssExtractPlugin.loader, 'css-loader' ] } ] }, plugins: [ new MiniCssExtractPlugin({ filename: 'css/[name][contenthash:6].css', chunkFilename: 'CSS /[id][contenthash:6].css'}), new PurgecssPlugin({// Match file paths: glob.sync(' ${__dirname}/**/*', {nodir: true}) }), new HtmlWebpackPlugin({ template: './src/index.html' }), new CleanWebpackPlugin() ] }Copy the code

After packaging, we can see that if we don’t use the CSS, we won’t be packaging because we don’t use the.wrapper class style name at all, so it won’t be included

CssMinimizerPlugin

This plugin uses CSsnano to optimize and shrink your CSS

yarn add css-minimizer-webpack-plugin -D
Copy the code

In the WebPack configuration, configure it in optimization.minimizer

// webpack.config.js const {resolve} = require('path') const MiniCssExtractPlugin = require('mini-css-extract-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') const {CleanWebpackPlugin} = require('clean-webpack-plugin') const PurgecssPlugin = require('purgecss-webpack-plugin') const glob = require('glob') const CssMinimizerPlugin = require('css-minimizer-webpack-plugin') module.exports = { mode: 'production', entry: './src/index.js', output: { filename: 'bundle.js', path: resolve(__dirname, 'build') }, module: { rules: [ { test: /\.css$/i, use: [ MiniCssExtractPlugin.loader, 'css-loader' ] } ] }, optimization: { minimizer: New CssMinimizerPlugin()]}, plugins: [new MiniCssExtractPlugin({filename: 'css/[name][contenthash:6].css', chunkFilename: 'css/[id][contenthash:6].css' }), new PurgecssPlugin({ paths: glob.sync(`${__dirname}/**/*`, {nodir: true}) }), new HtmlWebpackPlugin({ template: './src/index.html' }), new CleanWebpackPlugin() ] }Copy the code

HTTP compression

HTTP compression refers to the method of transferring compressed text content between a Web server and a browser. HTTP compression usually uses the GZIP compression algorithm to compress HTML, JavaScript, and CSS files. The biggest benefit of compression is that it reduces the amount of data transferred over the network, thus improving the access speed of the client browser. Of course, it also adds a bit of burden to the server.

HTTP compression process

  1. The browser sends an Http request to the Web server, which contains accept-Encoding: gzip, deflate. (Tell the server that the browser supports gzip compression)
  2. After receiving the request, the Web server generates the original Response, including the original content-type and content-Length
  3. The Web server encodes the Response using Gzip. The header contains content-Type and content-Length, and the content-encoding: Gzip. The Response is then sent to the browser
  4. When the browser receives the Response, it decodes it according to Content-Encoding:gzip. After getting the original response, the page is displayed

What can WebPack do about HTTP compression

The server needs to consume certain resources to compress files. In fact, webpack can compress files and generate corresponding compressed files when packaging. If gzip compression is enabled on the server, the server does not need to compress and just returns the corresponding compressed file that we have packaged, which also takes a little bit of the burden off the server

CompressionPlugin

The CompressionPlugin can do this

yarn compression-webpack-plugin -D
Copy the code
    // src/index.js
import _ from 'lodash'
console.log(_.add(6, 4))
Copy the code
// webpack.config.js const {resolve} = require('path') const CompressionPlugin = require('compression-webpack-plugin') module.exports = { mode: 'production', entry: './src/index.js', output: { filename: '[name].js', path: Resolve (__dirname, 'build')}, plugins: [new CompressionPlugin ({matching need compressed file test: / / / \ | js (CSS) $/})]}Copy the code

CompressionPlugin has many configuration options available on the CompressionPlugin website