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

1. Foundation

1.1 webpack advantages

  • Dependency management, dynamic packaging, code separation, on-demand loading, code compression, static resource compression, caching and other configurations;
  • Webpack has strong expansibility and perfect plug-in mechanism. Developers can customize plug-ins and loaders.
  • Webpack has a large community, fast update speed and rich wheels.

1.2 Basic Applications

  • Webpack uses dependency diagrams to retrieve non-code resources, such as images or Web fonts. They are provided to the application as dependencies.
  • Each module can clearly express its own dependencies, and can be packaged according to the dependencies to avoid packaging unused modules.

1.2.1 Entry

An entry is the beginning of a dependency diagram, from which to find dependencies, package builds, and WebPack allows one or more entry configurations;

module.exports = {
	entry: './src/index.js'
}
Copy the code

Multi-entry configuration:

1.2.2 Output (Export)

The output is used to configure the exit of the WebPack build package, such as the package location, the file name of the package;

module.exports = {
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',}};Copy the code

1.2.3 Loader (Conversion)

Webpack has the ability to package and build JavaScript and JSON files without extra configuration. For other types of files, such as CSS, you need to install 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.

module.exports = {
  module: {
    rules: [{test: /\.css$/i,
        use: ["style-loader"."css-loader"],},],},};Copy the code

1.2.4 Plugin

Plug-ins are used to extend webPack’s capabilities;

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

1.2.5 Mode (Mode)

Webpack5 provides mode selection, including development mode, production mode, empty mode, and corresponding built-in optimization for different modes. Configuration mode can be used to optimize project performance;

module.exports = {
  mode: 'development'};Copy the code

1.2.6 Resolve

Resolve is used to set module resolution. The common configurations are as follows:

  • Alias: Configure an alias to simplify module introduction.
  • Extensions: Introduce modules without a suffix;
  • Symlinks: Used to configure whether NPM link takes effect. Disabling NPM link improves compilation speed.
module.exports = {
	resolve: {
    	extensions: ['.js'.'.jsx'.'.ts'.'.tsx'.'.json'.'.d.ts'].alias: {
      	The '@': '/',},symlinks: false,}}Copy the code

1.2.7 Optimization

Optimization is used to customize the built-in optimization configuration of WebPack. It is generally used to improve performance in production mode. Common configuration items are as follows:

  • Minimize: The need to compress bundles;
  • Minimizer: Configures compression tools such as TerserPlugin and OptimizeCSSAssetsPlugin.
  • SplitChunks: splitChunks
  • RuntimeChunk: Whether to split all runtime files that are shared between generated chunks.
module.exports = {
  optimization: {
    minimizer: [
      new CssMinimizerPlugin(),
    ],
    splitChunks: {
      chunks: 'all'.// Repeat packaging problem
      cacheGroups: {vendors: {//node_modules
          test: /[\\/]node_modules[\\/]/,
          chunks: "all".//chunks name
          name: 'vendors'./ / priority
          priority: 10.enforce: true}}},},}Copy the code

1.3 Code in this section

Plug-ins to be installed in this section

npm install webpack webpack-cli --save-dev    
npm install --save-dev html-webpack-plugin
npm install --save-dev style-loader css-loader
npm install css-minimizer-webpack-plugin --save-dev
Copy the code

Code address: gitee.com/linhexs/web…

2. Practice Basis

2.1 Achieving goals

  • Separation of development environment and production environment configuration;
  • Modular development;
  • SourceMap locates warnings and errors;
  • Dynamically generate HTML5 files that introduce bundle.js;
  • Real-time compilation;
  • Encapsulate compile and package commands.

2.2 Basic Configuration

2.2.1 Creating a project Directory

2.2.2 Installing plug-ins

npm i webpack-merge -D
npm install webpack webpack-cli --save-dev
Copy the code

2.2.3 Adding the Webpack code in config Directory

webpack.common.js

// webpack.common.js

module.exports = {} // Do not add the configuration yet
Copy the code

webpack.dev.js

// webpack.dev.js

const { merge } = require('webpack-merge')
const common = require('./webpack.common')

module.exports = merge(common, {}) // Do not add the configuration yet
Copy the code

webpack.prod.js

// webpack.prod.js

const { merge } = require('webpack-merge')
const common = require('./webpack.common')

module.exports = merge(common, {}) // Do not add the configuration yet
Copy the code

2.2.4 Entry (Entrance)

Modify webpack.com mom. Js:

// webpack.common.js

module.exports = {
  / / the entry
  entry: {
    index: './src/index.js',}}Copy the code

2.2.5 Output (Export)

The output attribute outputs the location and name of the bundle it creates.

Output in the production environment needs to distinguish versions and changes by contenthash value, which can achieve the effect of clear cache, while the local environment does not attract contenthash to build efficiency.

Modify the webpack. Dev. Js

// webpack.dev.js

const { merge } = require('webpack-merge')
const common = require('./webpack.common')
const { resolveApp } = require('./paths');

module.exports = merge(common, {
  / / output
  output: {
    // Bundle file name
    filename: '[name].bundle.js'.// Bundle file path
    path: resolveApp('dist'),

    // Clear the directory before compiling
    clean: true}})Copy the code

Modify the webpack. Prod. Js

// webpack.prod.js

const { merge } = require('webpack-merge')
const common = require('./webpack.common')
const { resolveApp } = require('./paths');

module.exports = merge(common, {
  / / output
  output: {
    // Bundle file name
    filename: '[name].[contenthash].bundle.js'.// Bundle file path
    path: resolveApp('dist'),

    // Clear the directory before compiling
    clean: true}})Copy the code

Added paths.js to wrap the path method resolveApp:

const fs = require('fs')
const path = require('path')

const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath= > path.resolve(appDirectory, relativePath);

module.exports = {
  resolveApp
}
Copy the code

Placeholder function

  • [name] – Chunk name (for example, [name].js -> app.js). If the chunk does not have a name, its ID is used as the name
  • [contenthash] – Output md4-hash of the file contents (e.g. [contenthash].js -> 4ea6ff1de66C537eb9B2.js)

2.2.6 Mode (Mode)

Add production environment and development environment:

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

2.2.7 the source – the map

Use source-map to track errors and warnings, and map the compiled code back to the original source;

module.exports =  merge(common, {
  mode: 'development'.// In the development environment, start source map, compile and debug
  devtool: 'eval-cheap-module-source-map',})Copy the code

Try packing it up:

Development:

npx webpack --config config/webpack.dev.js
Copy the code

Production:

npx webpack --config config/webpack.prod.js
Copy the code

2.2.8 HtmlWebpackPlugin

The HtmlWebpackPlugin plugin is introduced to generate an HTML5 file, including all webpack packages in the body using the Script tag;

npm install --save-dev html-webpack-plugin
Copy the code

Modify webpack.com mom. Js:

module.exports = {
  plugins: [
    // Generate HTML to automatically import all bundles
    new HtmlWebpackPlugin({
      title: 'webpack',})],}Copy the code

2.2.9 DevServer

  • Webpack-dev-server provides a basic Web server with real-time reload capabilities;
  • The default configuration for webpack-dev-server is conpress: true and gzip Compression is enabled for each static file.

Installation:

npm install --save-dev webpack-dev-server
Copy the code

Modify the development environment configuration file webpack.dev.js:

module.exports = merge(common, {
   devServer: {
    // Tell the server the location.
    static: {
      directory: path.join(__dirname, 'dist'),},port: 8888.hot: true,}})Copy the code

Proxy configuration:

Enter the command to start:

npx webpack serve --open --config config/webpack.dev.js
Copy the code

2.2.10 Running commands

Configure environment variables with cross-env to distinguish between development and production environments.

Installation:

npm install --save-dev cross-env
Copy the code

Modify the package. The json:

{
    "scripts": {
        "dev": "cross-env NODE_ENV=development webpack serve --open --config config/webpack.dev.js"."build": "cross-env NODE_ENV=production webpack --config config/webpack.prod.js"}},Copy the code

Now you can run the Webpack directive:

  • NPM run dev: build locally;
  • NPM Run Build: Production package;

2.3 Code in this section

Code address: gitee.com/linhexs/web…

3, practice advanced chapter

3.1 Achieving goals

  • Load images;
  • Load font;
  • Load the CSS;
  • Use SASS;
  • Use PostCSS, automatically prefix CSS rules, parse the latest CSS syntax, and introduce CSS-modules to solve global naming conflicts;
  • Use the React;
  • Use the TypeScript.

3.2 Advanced Configuration

3.2.1 Loading Images (Image)

Use in webpack4:

  • url-loader

  • file-loader

After Webpack5: Assetmodules;

Modify the common environment configuration file webpack.mom.js:

const paths = require('./paths');
module.exports = {
    module: {
        rules: [{test: /\.(png|svg|jpg|jpeg|gif)$/i,
            include: [
               paths.resolveApp('src'),,,type: 'asset/resource',},],},}Copy the code

3.2.2 load SASS

Install SASS dependencies:

npm install --save-dev sass-loader sass 
Copy the code

Modify the common environment configuration file webpack.mom.js:

{
   test: /.(scss|sass)$/,
   include: paths.appSrc,
   use: [
      // Generate JS strings as style nodes
      'style-loader'.// Convert CSS to CommonJS modules
      'css-loader'.// Compile Sass to CSS
      'sass-loader',]}Copy the code

3.2.3 use PostCSS

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

  • The CSS rules can be prefixed automatically.
  • Translate the latest CSS syntax into a syntax that most browsers can understand;
  • Css-modules resolves global naming conflicts.

Postcss-loader Loader that uses postCSS to process CSS.

PostCSS installation dependencies:

npm install --save-dev postcss-loader postcss postcss-preset-env
Copy the code

Modify the common environment configuration file webpack.mom.js:

{
        test: /\.module\.(scss|sass)$/,
        include: paths.appSrc,
        use: [
          // Generate JS strings as style nodes
          'style-loader'.// Convert CSS to CommonJS modules
          {
            loader: 'css-loader'.options: {
              modules: true.importLoaders: 2,}},// Compile PostCSS to CSS
          {
            loader: 'postcss-loader'.options: {
              postcssOptions: {
                plugins: [[// postcss-preset-env contains autoprefixer
                    'postcss-preset-env',],],},},},// Compile Sass to CSS
          'sass-loader',]}Copy the code

3.2.4 Using React + TypeScript

Install React related and TypeScript:

To improve performance, select the latest esbuild-Loader.

npm i react react-dom @types/react @types/react-dom -D
npm i -D typescript esbuild-loader
Copy the code

Add TS config tsconfig.json:

{
    "compilerOptions": {
        "outDir": "./dist/"."noImplicitAny": true."module": "es6"."target": "es5"."jsx": "react"."allowJs": true."moduleResolution": "node"."allowSyntheticDefaultImports": true."esModuleInterop": true,}}Copy the code

Modify the common environment configuration file webpack.mom.js:

module.exports = {
    resolve: {
        extensions: ['.tsx'.'.ts'.'.js'],},4
    module: {
        rules: [{test: /\.(js|ts|jsx|tsx)$/,
                include: paths.appSrc,
                use: [{loader: 'esbuild-loader'.options: {
                      loader: 'tsx'.target: 'es2015',},}]},]}}Copy the code

3.3 Code in this section

Code address: gitee.com/linhexs/web…

4. Optimization

4.1 Achieving goals

This article will introduce how to optimize the Webpack project from the perspectives of optimizing the development experience, speeding up the compilation speed, reducing the packaging volume and speeding up the loading speed.

4.2 Efficiency Tools

4.2.1 Compiling a progress bar

www.npmjs.com/package/pro…

Installation:

npm i -D progress-bar-webpack-plugin
Copy the code

Webpack.common. js can be configured as follows:

const chalk = require('chalk')
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
module.exports = {
  plugins: [
    / / the progress bar
    new ProgressBarPlugin({
        format: `  :msg [:bar] ${chalk.green.bold(':percent')} (:elapsed s)`}})],Copy the code

4.2.2 Analysis of compilation speed

www.npmjs.com/package/spe…

Installation:

npm i -D speed-measure-webpack-plugin
Copy the code

Webpack.dev.js can be configured as follows:

const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
module.exports = smp.wrap({
  / /... webpack config...
})
Copy the code

Effect:

4.2.3 Packaging volume analysis

www.npmjs.com/package/web…

Installation:

npm i -D webpack-bundle-analyzer
Copy the code

Webpack.prod. js can be configured as follows:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
  plugins: [
    // Package volume analysis
    new BundleAnalyzerPlugin()
  ],
}
Copy the code

The volume analysis for each bundle is as follows:

4.3 Optimized Development

4.3.1 hot update

Hot update refers to that during the development process, after modifying the code, only the modified part of the content is updated without refreshing the whole page.

Webpack.dev.js can be configured as follows:

module.export = {
    devServer: {
        contentBase: './dist'.hot: true./ / hot update}},Copy the code

4.3.2 Hot Update the React Component

Hot update the React component with react-refresh-webpack-plugin;

Installation:

npm install -D @pmmmwh/react-refresh-webpack-plugin react-refresh
Copy the code

Webpack.dev.js can be configured as follows:

const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');

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

4.4 Optimization of construction speed

4.4.1 cache

Compared with WebPack4, WebPack5 has added optimization such as persistent cache and improved cache algorithm. See Resources for new features of WebPack5.

By configuring webPack persistent cache, cache: Filesystem, to cache generated Webpack modules and chunks, the construction speed can be improved by about 90%.

Webpack.common. js can be configured as follows:

module.exports = {
    cache: {
      type: 'filesystem'.// Use file caching}},Copy the code

When caching is introduced, the first build time increases by 15% and the second build time decreases by 90%, with the following effects:

Before use:

The first build after use is slightly slower:

After use the second build takes off directly:

4.4.2 Reduce loaders and plugins

Each loader and plugin has its startup time. Use tools as little as possible and delete unnecessary Loaders and plugins.

4.4.3 specifies the include

Specify include for loader to reduce the scope of loader application and apply only the minimum number of necessary modules.

module.exports = {
    rules: [{test: /\.(png|svg|jpg|jpeg|gif)$/i,
        include: [
          paths.resolveApp('src')],type: 'asset/resource',}}]Copy the code

4.4.4 Managing Resources

Use the WebPack asset Module to replace old assets Loaders, such as file-loader, url-loader, and raw-loader, to reduce the number of Loaders.

module.exports = {
    rules: [{test: /\.(png|svg|jpg|jpeg|gif)$/i,
        include: [
          paths.resolveApp('src')],type: 'asset/resource',}}]Copy the code

4.4.5 Optimizing the Resolve Configuration

Resolve is used to configure how WebPack resolves modules. You can optimize the Resolve configuration to override the default configuration items and reduce the resolution scope.

1. alias

Alias can create an alias for import or require to simplify module import.

Webpack.common. js can be configured as follows:

module.exports = {
    resolve: {
        alias: {
          The '@': paths.appSrc, // @ represents the SRC path}}},Copy the code

2. extensions

Extensions represent a list of file types that need to be parsed.

According to the file type in the project, define extensions to override the webPack default extensions for faster parsing;

Since WebPack is parsed from left to right, the most frequently used file types should be placed on the left. Here I put TSX on the far left;

Webpack.common. js can be configured as follows:

module.exports = {
    resolve: {
         extensions: ['.tsx'.'.ts'.'.js'].}}Copy the code

3. modules

Modules represents the directory that webpack needs to parse when parsing modules;

Specifying a directory reduces the scope of WebPack resolution and speeds up builds;

Webpack.common. js can be configured as follows:

module.exports = {
  resolve{
    modules: [
      'node_modules',
       paths.appSrc,
    ]
	}
}
Copy the code

4.symlinks

If the project does not use symlinks (such as NPM link or YARN Link), set resolve.symlinks: false to reduce the parsing workload.

Webpack.common. js can be configured as follows:

module.exports = {
    resolve: {
        symlinks: false,}}Copy the code

4.4.6 Multi-Threading (Thread-loader)

The time-consuming loader is run in an independent worker pool through thread-loader to speed up loader construction.

Installation:

npm i -D thread-loader
Copy the code

Configuration:

{
    loader: 'thread-loader'.options: {
        workerParallelJobs: 2}},Copy the code

4.4.7 Environment differentiation

Do not use tools that are only used in the production environment in the development environment. For example, exclude [fullHash], [Chunkhash], and [Contenthash] in the development environment.

In production, you should avoid tools that are only used in development environments, such as plug-ins such as Webpack-dev-server;

4.4.8 devtool

Different DevTool Settings can cause performance differences. In most cases, the best option is eval-cheap-module-source-map;

The webpack.dev.js configuration is as follows:

export.module = {
    devtool: 'eval-cheap-module-source-map',}Copy the code

4.4.9 The output does not carry path information

By default, WebPack generates path information in the output bundle, and removing path information can slightly improve build speed.

module.exports = {
    output: {
        pathinfo: false,}}; }Copy the code

4.4.10 IgnorePlugin

IgnorePlugin directly removes modules that need to be excluded when building a module. Used in moment and internationalization;

new webpack.IgnorePlugin(/\.\/locale/./moment/)
Copy the code

4.4.11 DllPlugin

The core idea is to build and package modules such as the framework that the project depends on separately from the normal build process.

output: {
    filename: '[name].dll.js'.// Put the output files in the dist directory
    path: distPath,
    library: '_dll_[name]',},plugins: [
    / / access DllPlugin
    new DllPlugin({
      // The global variable name of the dynamically linked library needs to be the same as that in output.library
      // The value of this field is the value of the name field in the output manifest.json file
      // For example, react. Manifest.json has "name": "_dll_react"
      name: '_dll_[name]'.// The name of the manifest.json file that describes the output of the dynamic link library
      path: path.join(distPath, '[name].manifest.json'),})],Copy the code

4.4.12 Externals

Externals and DllPlugin in the Webpack configuration address the same kind of problem: removing modules such as dependent frameworks from the build process.

The difference is:

  • In terms of Webpack configuration, externals is simpler, while DllPlugin requires a separate configuration file.
  • DllPlugin contains the independent build process of the dependency package, while the externals configuration does not contain the generation method of the dependency framework, which usually uses the dependency package that has been passed into the CDN.
  • If the externals dependency package is configured, specify the loading mode of the dependency module, such as global object, CommonJS, or AMD.
  • DllPlugin does not change when referring to a dependent package’s submodule, whereas externals pushes the submodule into the project package.
/ / into the CDN
<script
  src="https://code.jquery.com/jquery-3.1.0.js"
  integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk="
  crossorigin="anonymous"
></script>


/ / webpack configuration
module.exports = {
  / /...
  externals: {
    jquery: 'jQuery',}};/ / page
import $ from 'jquery';
$('.my-element').animate(/ *... * /);
Copy the code

4.5 Reduce packing volume

4.5.1 Code compression

1. JS compressed

Use the TerserWebpackPlugin to compress JavaScript;

Webpack5 comes with the latest Terser-webpack-plugin without manual installation.

The terser-webpack-plugin enables parallel: true by default, and the default number of parallel runs is os.cpus().length-1, and the number of parallel runs in this article is 4.

Webpack.prod. js can be configured as follows:

const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
    optimization: {
        minimizer: [
            new TerserPlugin({
              parallel: 4.terserOptions: {
                parse: {
                  ecma: 8,},compress: {
                  ecma: 5.warnings: false.comparisons: false.inline: 2,},mangle: {
                  safari10: true,},output: {
                  ecma: 5.comments: false.ascii_only: true,},},}),]}}Copy the code

2. The CSS compression

CssMinimizerWebpackPlugin compression using CSS files;

Installation:

npm install -D css-minimizer-webpack-plugin
Copy the code

Webpack.prod. js can be configured as follows:

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
  optimization: {
    minimizer: [
      new CssMinimizerPlugin({
          parallel: 4,}),],}}Copy the code

4.5.2 Code separation

Code separation is the ability to separate code into different bundles, which can then be loaded on demand or in parallel. Code separation can be used to obtain smaller bundles and control resource load priorities, which can shorten page load times.

1. Remove duplicate code

The SplitChunksPlugin plug-in, out of the box, extracts a common dependency module into an existing chunk or into a newly generated chunk.

Webpack will automatically split chunks based on the following criteria:

  • New chunks can be shared, or modules can come from the node_modules folder;
  • The new chunk size is larger than 20KB (before min+gz);
  • When chunks are loaded on demand, the maximum number of parallel requests is less than or equal to 30;
  • When loading the initialization page, the maximum number of concurrent requests is less than or equal to 30; The common libraries such as React are extracted through splitChunks, and the occupied volume is not introduced repeatedly.

Note: It is important not to define a fixed name for cacheGroups, because cachegroups.name, when specifying a string or a function that always returns the same string, consolidates all common modules and vendors into one chunk. This leads to larger initial downloads and slower page loading;

Webpack.prod. js can be configured as follows:

module.exports = {
  optimization: {
    splitChunks: {
      // include all types of chunks
      chunks: 'all'.// Repeat packaging problem
      cacheGroups: {// node_modules
        // Third party module
        vendors: {test: /[\\/]node_modules[\\/]/,
          chunks: "all".// Name: 'vendors', so do not define fixed names
          priority: 10./ / priority
          enforce: true 
        },
        // Public module
      	common: {
          name: 'common'./ / the chunk name
          priority: 0./ / priority
          minSize: 0.// Public module size limit
          minChunks: 2  // The public module has been reused at least several times
       	}
      }
    }
  }
}
Copy the code

2. Separate the CSS files

The MiniCssExtractPlugin extracts CSS into a separate file, creates a CSS file for each JS file containing CSS, and supports on-demand loading of CSS and SourceMaps;

Installation:

npm install -D mini-css-extract-plugin
Copy the code

Webpack.common. js can be configured as follows:

Note: MiniCssExtractPlugin. Loader placed behind the style – loader;

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  plugins: [new MiniCssExtractPlugin()],
  module: {
    rules: [{test: /\.module\.(scss|sass)$/,
        include: paths.appSrc,
        use: [
          'style-loader',
          isEnvProduction && MiniCssExtractPlugin.loader, // Production environment only
          {
            loader: 'css-loader'.options: {
              modules: true.importLoaders: 2,}}, {loader: 'postcss-loader'.options: {
              postcssOptions: {
                plugins: [['postcss-preset-env',],],},},}, {loader: 'thread-loader'.options: {
              workerParallelJobs: 2}},'sass-loader',
        ].filter(Boolean),},]},};Copy the code

Effect:

3. Minimize entry chunk

Through configuration optimization. RuntimeChunk = true for runtime code to create an extra chunk, reduce entry the chunk size, improve performance;

Webpack.prod. js can be configured as follows:

module.exports = {
    optimization: {
        runtimeChunk: true,}}; }Copy the code

Effect:

Tree Shaking

A module may have multiple methods, and when one of them is used, the whole file is stored in the bundle. Tree shaking is simply putting used methods into the bundle, while unused methods are erased in the Uglify phase.

1. JS

JS Tree Shaking removes unreferenced Dead Code from JavaScript context and provides cues to compiler via the “sideEffects” attribute of package.json. Indicate which files in the project are “pure “so that unused portions of the file can be safely removed;

Dead Code generally has the following characteristics:

  • Code will not be executed, not reachable;
  • The results of code execution are not used;
  • The code only affects dead variables (just write, not read);

Webpack5 sideEffects set

This is done through the package.json “sideEffects” property;

{
  "name": "your-project"."sideEffects": false
}
Copy the code

Note that when code has sideEffects, we need to change sideEffects to provide an array and add the file path of the code with sideEffects:

{
  "name": "your-project"."sideEffects": ["./src/some-side-effectful-file.js"]}Copy the code

Before the Tree Shaking:

After the Tree Shaking:

Optimization of component library references

Webpack5 sideEffects can only remove references that have no sideEffects. For references that have sideEffects, Tree Shaking can only be done by optimizing references.

loadsh

Import {throttle} from ‘lodash’ is a reference with side effects, packaging the entire lodash file;

Import {throttle} from ‘lodash-es’ instead of import {throttle} from ‘lodash’, lodash-es exports lodash library as es module. Support tree shaking based on ES Modules, implement on demand;

ant-design

Ant design supports tree shaking based on ES Modules by default. For js, import {Button} from ‘antd’ is loaded on demand.

Import {Button} from ‘antd’ is also a side effect if only a few components are introduced in the project. Webpack cannot tree-shaking other components. In this case, you can narrow down the reference and change the import mode to import {Button} from ‘antd/lib/button’ for further optimization.

2. CSS

CSS Tree Shaking using purgecss-webpack-plugin

Installation:

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

CSS is stored in JS files by default when packaging, so it should be used together with the Webpack separation CSS file plug-in mini-CSS-extract-plugin.

Webpack.prod. js can be configured as follows:

const glob = require('glob')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const PurgeCSSPlugin = require('purgecss-webpack-plugin')
const paths = require('paths')

module.exports = {
  plugins: [
    // Package volume analysis
    new BundleAnalyzerPlugin(),
    / / CSS
    new MiniCssExtractPlugin({
      filename: "[name].css",}).// CSS Tree Shaking
    new PurgeCSSPlugin({
      paths: glob.sync(`${paths.appSrc}/ * * / * `,  { nodir: true}})}),]Copy the code

3. CDN

Reduce packing volume through CDN;

Upload large static resources to CDN:

  • Font: compressed and uploaded to CDN;
  • Image: compressed and uploaded to CDN.

4.6 Accelerating the loading speed

4.6.1 Loading on Demand

Through the import() syntax dynamic import function provided by Webpack to separate the code, by loading on demand, greatly improve the speed of web page loading;

export default function App () {
    return (
        <div>
            hello react 111
            <Hello />
            <button onClick={()= >Import (' lodash ')} > load lodash</button>
        </div>)}Copy the code

The effect is as follows:

4.6.2 Browser Caching

Browser cache refers to the static resources loaded by the browser after entering a website. After entering the website again, the browser directly pulls the cached resources to speed up the loading.

Webpack supports creating hash ids based on the resource content. When the resource content changes, a new hash ID will be created.

To configure the JS bundle hash, webpack.common. JS can be configured as follows:

module.exports = {
  / / output
  output: {
    // Add hash only in production environment
    filename: ctx.isEnvProduction ? '[name].[contenthash].bundle.js' : '[name].bundle.js',}}Copy the code

To configure the CSS bundle hash, use webpack.prod.js as follows:

module.exports = {
  plugins: [
    / / CSS
    new MiniCssExtractPlugin({
      filename: "[hash].[name].css",})],}Copy the code

Configure optimization.moduleids so that the hash of the common package splitChunks does not change due to new dependencies to reduce unnecessary hash changes. The configuration of webpack.prod.js is as follows:

module.exports = {
  optimization: {
    moduleIds: 'deterministic',}}Copy the code

By configuring contenthash/hash, the browser caches unchanged files and reloads only modified files, greatly speeding up the loading speed.

4.6.3 use CDN

That’s already been said.

4.7 summarize

  • In small projects, adding too many optimization configurations has little effect, but will increase the build time due to extra loaders and plugins.

  • In terms of speeding up the build time, the cache configuration greatly accelerates the secondary build speed.

  • In terms of reducing the size of the package, compressing code, separating duplicate code, Tree Shaking, can reduce the size of the package.

  • Loading on demand, browser cache and CDN are all significant in speeding up the loading speed.

4.8 Code in this section

Code address: gitee.com/linhexs/web…

5. Write loader

5.1 Implementing a Simple Loader

5.1.1 demand

Develop a loader to convert markdown files to HTML

5.1.2 Preparations

Add a markdown file to the root directory and add a loaders folder to store the custom loader.

5.1.3 configuration webpack

Modify webPack configuration to load custom loader;

5.1.4 Writing loader Files

5.1.5 Printing the Result

Note that the default h1 result is printed with an ID. You can remove the ID attribute by setting options.

5.2 Dependency of Multiple Loaders

5.2.1 demand

According to the previous analysis MD, transform into multiple loader resolution;

5.2.2 changes webpack

5.2.3 requires coding

1. Md – loader. Js code

2. HTML – color – loader. Js code

5.2.4 Implementation effect

6. Write the Webpack-plugin

6.1 Webpack Plug-in Composition

  • A named JavaScript function;

  • Define the apply method on its prototype;

  • Specify an event hook that touches Webpack itself;

  • Manipulate instance specific data within Webpack;

  • The callback provided by Webpack is called after the functionality is implemented.

The plug-in is instantiated by a constructor. The constructor defines the apply method, which is called once by the Webpack Compiler when the plug-in is installed. The Apply method can receive a reference to the Webpack Compiler object, which can be accessed in the callback function.

6.2 requirements

Implement a plugin that outputs folder sizes;

6.3 Configuring Plug-ins

6.3.1 Introducing plug-ins

6.3.2 Instantiating plug-ins

6.4 Plug-in Source Code

const { statSync } = require('fs')
const { resolve } = require('path')

class BundleSizePlugin {
  constructor(options) {
    this.options = options;
  }
  apply(compiler) {
    const { limit } = this.options
    compiler.hooks.done.tap('BundleSizePlugin'.(stats) = > {
      const { path } = stats.compilation.outputOptions
      const bundlePath = resolve(path)
      const { size } = statSync(bundlePath)
      const bundleSize = size
      if (bundleSize < limit) {
        console.log('output success -- -- -- -- -- -- -- bundleSize:', bundleSize, "\n limit:", limit, 'Less than limit size')}else {
        console.error('output error -- -- -- -- -- -- -- bundleSize:', bundleSize, "\n limit:", limit, 'Out of limit size')}})}}module.exports = BundleSizePlugin
Copy the code

6.5 Effect

7. Packaging principle

7.1 Webpack simplifies source code analysis

What does webpack do after executing NPX webPack for packaging?

(function (modules) {
  var installedModules = {};
  function __webpack_require__(moduleId) {
    if (installedModules[moduleId]) {
      return installedModules[moduleId].exports;
    }
    var module = (installedModules[moduleId] = {
      i: moduleId,
      l: false.exports: {}}); modules[moduleId].call(module.exports, module.module.exports, __webpack_require__
    );
    module.l = true; return module.exports;
  }
  return __webpack_require__((__webpack_require__.s = "./index.js")); ({})"./index.js": function (module.exports) {
    eval(
      '// import a from "./a"; \n\nconsole.log("hello word"); \n\n\n//# sourceURL=webpack:///./index.js? ')},"./a.js": function (module.exports) {
    eval(
      '// import a from "./a"; \n\nconsole.log("hello word"); \n\n\n//#sourceURL = webpack:///./index.js? ')},"./b.js": function (module.exports) {
    eval(
      '// import a from "./a"; \n\nconsole.log("hello word"); \n\n\n//#sourceURL = webpack:///./index.js? '); }});Copy the code

__webpack_require__ caches all the code in installedModules. The code file is passed in the form of an object. Key is the path and value is the wrapped code string. And the require in this code has been replaced with webpack_require. It doesn’t matter if you don’t understand it, let’s implement one together;

7.2 WebPack Packaging Principle

const compier = webpack(options) compier.run()

Create a Webpack

  • Receiving a configuration (webpack.config.js)
  • Analyze the location of the entry and exit modules
    • Read the contents of the entry module and analyze the contents
    • What are dependencies
    • What is the source code
      • Es6, JSX, processing needs to be compiled for the browser to be able to perform
    • Analyze other modules
  • Get the object data structure
    • The module path
    • Processed content
  • Create a bundle. Js
    • The launcher function, which complements the possible Module exports require in the code, allows the browser to execute smoothly

7.3 Simple Packaging

7.3.1 preparation

The goal is to package dependent ES6Modules into a single JS file (bundle.js) that can be run in the browser.

// index.js
import { str } from "./a.js";
import { str2 } from "./b.js";
console.log(`${str2} hello ${str}`);


// a.js
export const str = "a";

// b.js
export const str = "b";
Copy the code

bundle.js

7.3.2 Implementation steps

1. Module analysis

Read the entry file and analyze the code

const fs = require("fs");
const parse= entry= > {
  const content = fs.readFileSync(entry, "utf-8"); 
  console.log(content);
}; 

// Import file
parse("./index.js");
Copy the code

The analysis of a module is equivalent to parsing a string of file code read. This step is consistent with the compilation process for a high-level language. The module needs to be parsed into an abstract syntax tree AST. We do this with Babel/Parser.

Add dependencies:

yarn add @babel/parser
yarn add @babel/traverse
yarn add @babel/core
yarn add @babel/preset-env
Copy the code

Convert to ast syntax tree

const ast = parser.parse(content, {
     sourceType: "module"});Copy the code

Collect rely on

/ / rely on
traverse(ast, {
   ImportDeclaration({ node }) {
   path.dirname(entryFile); //./src/index.js
   const newPathName = ". /"+ path.join(path.dirname(entryFile), node.source.value); dependencies[node.source.value] = newPathName; }});Copy the code

Turn es6 es5

const { code } = transformFromAst(ast, null, {
    presets: ["@babel/preset-env"]});Copy the code

Complete code:

 parse(entryFile) {
    // How to read the contents of the module
    const content = fs.readFileSync(entryFile, "utf-8");
    const ast = parser.parse(content, {
      sourceType: "module"});const dependencies = {};
    traverse(ast, {
      ImportDeclaration({ node }) {
        path.dirname(entryFile); //./src/index.js
        
        // Process path
        const newPathName =". /"+ path.join(path.dirname(entryFile), node.source.value); dependencies[node.source.value] = newPathName; }});const { code } = transformFromAst(ast, null, {
      presets: ["@babel/preset-env"]});const res = {
      entryFile,
      dependencies,
      code,
    };
   
   console.log(res)
  }

parse("./src/index.js")
Copy the code

Running results:

2. Collect dependent modules

The function developed in the previous step can parse a module by itself. In this step, we need to develop a function to parse recursively from the entry module based on dependencies. Finally, form Dependency Graph.

run() {
    const info = this.parse(this.entry);
    // Handle all dependencies recursively
    this.modules.push(info);
    for (let i = 0; i < this.modules.length; i++) {
      const item = this.modules[i];
      const { dependencies } = item;
      if (dependencies) {
        for (let j in dependencies) {
          this.modules.push(this.parse(dependencies[j])); }}}// Modify the data structure array to object
    const obj = {};
    this.modules.forEach((item) = > {
      obj[item.entryFile] = {
        dependencies: item.dependencies,
        code: item.code,
      };
    });
    console.log(obj);
  }
Copy the code

Running results:

3. Generate the bundle file

We need to combine the execution function and dependency graph we just wrote to output the final package file.

There are no exports objects or require methods in the browser, so executing directly will definitely return an error.

So we need to emulate exports and require;

We know from the run result graph above that the internal code read is a string, so we use eval to execute the string code;

The require function is relatively simple, which is to load the corresponding module according to the provided file name.

The corresponding code is as follows:

 file(code) {
    const filePath = path.join(this.output.path, this.output.filename);
    const newCode = JSON.stringify(code);
    // Generate bundle code
    const bundle = `(function(modules){ function require(module){ function newRequire(relativePath){ return Module [module]. Dependencies [relativePath]} // Exports does not exist. Var exports = {}; (function(require,exports,code){ eval(code) })(newRequire,exports,modules[module].code) return exports; } require('The ${this.entry}')
    })(${newCode}) `;
    fs.writeFileSync(filePath, bundle, "utf-8");
  }
Copy the code

7.4 Complete Code

const fs = require("fs");
const path = require("path");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const { transformFromAst } = require("@babel/core");
module.exports = class webpack {
  constructor(options) {
    this.entry = options.entry;
    this.output = options.output;
    this.modules = [];
  }
  run() {
    const info = this.parse(this.entry);
    // Handle all dependencies recursively
    this.modules.push(info);
    for (let i = 0; i < this.modules.length; i++) {
      const item = this.modules[i];
      const { dependencies } = item;
      if (dependencies) {
        for (let j in dependencies) {
          this.modules.push(this.parse(dependencies[j])); }}}// Modify the data structure array to object
    const obj = {};
    this.modules.forEach((item) = > {
      obj[item.entryFile] = {
        dependencies: item.dependencies,
        code: item.code,
      };
    });
    console.log(obj);
    // code generation, file generation
    this.file(obj);
  }
  parse(entryFile) {
    // How to read the contents of the module
    const content = fs.readFileSync(entryFile, "utf-8");
    const ast = parser.parse(content, {
      sourceType: "module"});const dependencies = {};
    traverse(ast, {
      ImportDeclaration({ node }) {
        path.dirname(entryFile); //./src/index.js
        const newPathName =
          ". /"+ path.join(path.dirname(entryFile), node.source.value); dependencies[node.source.value] = newPathName; }});const { code } = transformFromAst(ast, null, {
      presets: ["@babel/preset-env"]});return {
      entryFile,
      dependencies,
      code,
    };
  }
  file(code) {
    const filePath = path.join(this.output.path, this.output.filename);
    const newCode = JSON.stringify(code);
    // Generate bundle code
    const bundle = '(function(modules){function require(module){function newRequire(relativePath){// the real path corresponding to the relativePath return require(modules[module].dependencies[relativePath]) } var exports = {}; (function(require,exports,code){ eval(code) })(newRequire,exports,modules[module].code) return exports; } require('The ${this.entry}')
    })(${newCode}) `;
    fs.writeFileSync(filePath, bundle, "utf-8"); }};Copy the code

7.5 Packing Result

Run the node bundle.js command

(function(modules){
        function require(module){
            function newRequire(relativePath){
              return require(modules[module].dependencies[relativePath])
            }    
            var exports = {};
            (function(require.exports,code){
                eval(code)
            })(newRequire,exports,modules[module].code)
            return exports;
        }
        require('./src/index.js') ({})"./src/index.js": {"dependencies": {"./a.js":"./src/a.js"."./b.js":"./src/b.js"},"code":"\"use strict\"; \n\nvar _a = require(\"./a.js\"); \n\nvar _b = require(\"./b.js\"); \n\n// Analyze the contents of the entry module \n// : depend on the module (purpose is the path of the module) \n// Contents: Generate code snippets \n// node\nconsole.log(\"\".concat(_b. Str2, \" hello ").concat(_A.},"./src/a.js": {"dependencies": {},"code":"\"use strict\"; \n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n}); \nexports.str = void 0; \nvar str = \"a\"; \nexports.str = str;"},"./src/b.js": {"dependencies": {},"code":"\"use strict\"; \n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n}); \nexports.str2 = void 0; \nvar str2 = \"b\"; \nexports.str2 = str2;"}})
Copy the code

Import the packaged js file and run it:

8. Other conceptual things

8.1 Module Chunk Bundle Description

  • Module – each source file, all modules in Webpack
  • Chunk – The combination of multiple modules, such as Entry, import(), spliteChunk
  • Bundle – The final output file

8.2 Output Code

Principle:

  • Small volume
  • Reasonable subcontracting, no repeated loading
  • Faster and less memory usage

Practice:

  • Small picture base64 encoding
  • Bundle and hash
  • Lazy loading
  • Extract common code
  • IgnorePlugin
  • Use CDN acceleration
  • The use of production
  • Use the Tree – Shaking
  • Use Scope – Hosting

8.3 Differences between ES6-Module and CommonJS

  • Es6-module is a static reference that is introduced at compile time (cannot be referenced conditionally, cannot be determined by code)
  • Commonjs dynamic reference, reference at runtime
  • Only ES6-Module can statically analyze and implement tree-shaking
// CommonJS
let api = require('./config/api.js')
// It can be dynamically imported at execution time
if(isDev){
    api =  require('./config/api_dev.js')}// ---------------------------------------------

// ES6-Module
import api form './config/api.js'
// It can be dynamically imported at execution time
if(isDev){
   import api form './config/api_dev.js'
}
Copy the code

9, reference

  • Learn the way to Webpack5 (optimization) – nearly 7k words
  • I did animation all night and let everyone understand Webpack in 10 minutes