The foregoing

The content of this article is a summary of some knowledge points about Webpack optimization, which is fragmentary, if you need to help yourself:

I. Webpack optimization of “Resource Management”

~ style-loader CSS-loader Parses CSS files. The loader parses CSS files from bottom to top and from right to left. First use CSS-loader to compile CSS code, and then use style-loader to add the CSS code to the style label of the web page. So CSS-loader is on the right and style-loader is on the left, in the specified order.

 const path = require('path');

 module.exports = {
   entry: './src/index.js',
   output: {
     filename: 'bundle.js',
     path: path.resolve(__dirname, 'dist'),
   },
+  module: {
+    rules: [
+      {
+        test: /.css$/i,
+        use: ['style-loader', 'css-loader'],
+      },
+    ],
+  },
 };
Copy the code

No additional loader configuration is required to process images because WP5 has Asset Modules built in

 const path = require('path');

 module.exports = {
   entry: './src/index.js',
   output: {
     filename: 'bundle.js',
     path: path.resolve(__dirname, 'dist'),
   },
   module: {
     rules: [
       {
         test: /.css$/i,
         use: ['style-loader', 'css-loader'],
       },
+      {
+        test: /.(png|svg|jpg|jpeg|gif)$/i,
+        type: 'asset/resource',
+      },
     ],
   },
 };
Copy the code

So what about other resources like fonts? With Asset Modules, you can receive and load any file, and then output it to the build directory. That is, we can use them for any type of file, including fonts. Process the font file webpack.config.js by updating webpack.config.js

 module.exports = {
   entry: './src/index.js',
   output: {
     filename: 'bundle.js',
     path: path.resolve(__dirname, 'dist'),
   },
   module: {
     rules: [
       {
         test: /.css$/i,
         use: ['style-loader', 'css-loader'],
       },
       {
         test: /.(png|svg|jpg|jpeg|gif)$/i,
         type: 'asset/resource',
       },
+      {
+        test: /.(woff|woff2|eot|ttf|otf)$/i,
+        type: 'asset/resource',
+      },
     ],
   },
 };
Copy the code

Json also does not require loader configuration, import or require will eventually be translated by Baber into webpack–require

Ii. Webpack optimization of “Output management”

HtmlWebpackPlugin (update index.html)

If we mock out an index. HTML file in the dist folder and import the package file, if the name of the output package file changes or multiple packages are output, but the package file introduced in the index. HTML file is still old, Don’t panic. Webpack provides us with the HtmlWebpackPlugin, which means that even in the dist/ folder if we already have the index.html file, HtmlWebpackPlugin will still generate its own index.html file by default. In other words, it will replace our original file with a newly generated index.html file. Let’s see how to configure this plugin:

First install the plugin and adjust the webpack.config.js file:

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

Webpack. Config. Js in the configuration

const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: { index: './src/index.js', print: './src/print.js', }, + plugins: [+ new HtmlWebpackPlugin({+ title: 'output management ', +}), +], Output: {filename: '[name].bundle.js', path: path.resolve(__dirname, 'dist'), }, };Copy the code

After repackaging, you’ll see that HtmlWebpackPlugin creates a new file, and all bundles are automatically added to the HTML.

Clear the /dist folder

Dist can be cluttered if there is some useless code in the dist folder, but we only want useful code, and WebPack doesn’t keep track of which files are actually used in the project. We can clean the dist folder with output.clean as follows:

 const path = require('path');
 const HtmlWebpackPlugin = require('html-webpack-plugin');

 module.exports = {
   entry: {
     index: './src/index.js',
     print: './src/print.js',
   },
   plugins: [
     new HtmlWebpackPlugin({
       title: 'Output Management',
     }),
   ],
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
+    clean: true,
   },
 };
Copy the code

Manifest Runtime (important for understanding WBP packaging, more on this later)

In a typical application or site built using WebPack, there are three main types of code: 1. Source code written by you and your team. Your source code will depend on any third party library (package) or” vendor” code. The WebPack Runtime and manifest manage the interaction of all modules.

runtime

Runtime, along with the accompanying Manifest data, basically refers to all the code webPack uses to connect modular applications while the browser is running. It contains the loading and parsing logic needed to connect modules when they interact. This includes connection module logic that has been loaded in the browser and lazy-loading logic for modules that have not been loaded.

manifest

Once your application is opened in the browser as an index.html file, some bundles and various resources needed by the application need to be loaded and linked in some way. After webpack optimization for packaging, compression, and breaking down into tiny chunks for lazy loading, the file structure of your carefully arranged/SRC directory is gone. So how does WebPack manage the interactions between all the required modules? That’s where the manifest data use comes in… When Compiler starts executing, parsing, and mapping the application, it retains the detailed essentials of all modules. This data set is called the “manifest”, which is used by the Runtime to parse and load modules when packaged and sent to the browser. Regardless of which module syntax you choose, those import or require statements are now converted to the __webpack_require__ method, which points to the module identifier. Using the data in the MANIFEST, Runtime will be able to retrieve these identifiers and find the corresponding module behind each identifier.

But runtime and Manifest, how does that affect me?

You may ask. The answer is mostly no. Runtime does the work: Once your application loads into the browser, use the Manifest, and everything will work magically. However, if you decide to improve the performance of your project by using browser caching, understanding this process suddenly becomes extremely important. By using the Content hash as the name of the bundle file, the new hash is computed when the content of the file is modified, and the browser loads the file with the new name, invalidating the cache. Once you start doing this, you will immediately notice some interesting behavior. Even if something obviously hasn’t changed, some hashes will change. This is because the injected Runtime and manifest (collectively referred to as the bootstrap template, described below) change with each build.

If the bundle fails, it can help locate the map to the specific source code eval-source-map(which compiles faster) hidden-source-map

 const path = require('path');
 const HtmlWebpackPlugin = require('html-webpack-plugin');

 module.exports = {
   mode: 'development',
   entry: {
     index: './src/index.js',
     print: './src/print.js',
   },
+  devtool: 'inline-source-map',
   plugins: [
     new HtmlWebpackPlugin({
       title: 'Development',
     }),
   ],
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
     clean: true,
   },
 };
Copy the code

3, Webpack optimization of “automatic compilation”

It would be troublesome to manually execute NPM build every time the code is compiled after modification. Is there any configuration in Webpack that can automatically compile?

NPM scripts for Webpack Watch mode can be configured in package.json

{" name ":" webpack - demo ", "version" : "1.0.0", "description" : ""," private ": true," scripts ": {" test", "echo" Error: no test specified" && exit 1", + "watch": "webpack --watch", "build": "webpack" }, "keywords": [], "author": "" and" license ", "ISC", "devDependencies" : {" HTML - webpack - plugin ":" ^ 4.5.0 ", "webpack" : "^ 5.4.0", "webpack - cli" : "^ 4.2.0"}, "dependencies" : {" lodash ":" ^ 4.17.20 "}}Copy the code

Run NPM run watch from the command line and see how Webpack compiles the code. However, you will notice that there is no exit from the command line. This is because the script is still in your file, but the Watch mode can only monitor the changes of the file, and cannot automatically refresh the display of the browser page after completing the automatic compilation. The emergence of Webpack-dev-server solved this problem.

2. Webpack-dev-server (most commonly used)

Webpack-dev-server gives you a basic Web server with live reloading. The Settings are as follows:

First you need to install it (watch mode does not need to be installed, and is directly configured into package.json file)

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

Modify the webpack.config.js configuration file to tell dev Server where to look for files

 const path = require('path');
 const HtmlWebpackPlugin = require('html-webpack-plugin');

 module.exports = {
   mode: 'development',
   entry: {
     index: './src/index.js',
     print: './src/print.js',
   },
   devtool: 'inline-source-map',
+  devServer: {
+    static: './dist',
+  },
   plugins: [
     new HtmlWebpackPlugin({
       title: 'Development',
     }),
   ],
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
     clean: true,
   },
 };
Copy the code

The above configuration tells webpack-dev-server to serve the files in dist to localhost:8080. (Note: serve, the resource as the server’s accessible file)

Webpack-dev-server provides bundle files for the service from the directory defined in output.path, i.e., The file will be accessible through http://[devserver.host]:[devserver.port]/[output.publicpath]/[output.filename].

Let’s add a script that will run dev Server directly:

{" name ":" webpack - demo ", "version" : "1.0.0", "description" : ""," private ": true," scripts ": {" test", "echo" Error: no test specified" && exit 1", "watch": "webpack --watch", + "start": "webpack serve --open", "build": "webpack" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "html-webpack-plugin": "^ 4.5.0 webpack", ""," ^ 5.4.0 ", "webpack - cli" : "^ 4.2.0", "webpack - dev - server" : "^ 3.11.0"}, "dependencies" : {" lodash ": "^ 4.17.20}}"Copy the code

Run NPM start and we should see the browser automatically load the page. If you change any source files and save them, Web Server will automatically reload the code after it is compiled.

3. Webpack-dev-middleware

Webpack-dev-middleware is a wrapper that sends webPack-processed files to a server. Webpack-dev-server uses it internally, but it can also be used as a separate package for more customization as needed. Here is an example of Webpack-dev-Middleware with Express Server. First, install Express and Webpack-dev-Middleware:

npm install --save-dev express webpack-dev-middleware
Copy the code

Now we need to adjust the Webpack configuration file to make sure the Middleware functionality is enabled correctly:

const path = require('path');
 const HtmlWebpackPlugin = require('html-webpack-plugin');

 module.exports = {
   mode: 'development',
   entry: {
     index: './src/index.js',
     print: './src/print.js',
   },
   devtool: 'inline-source-map',
   devServer: {
     static: './dist',
   },
   plugins: [
     new HtmlWebpackPlugin({
       title: 'Development',
     }),
   ],
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
     clean: true,
+    publicPath: '/',
   },
 };
Copy the code

We will use publicPath in the server script to ensure that the file resources are properly served at http://localhost:3000, and we will specify the port number later. Next, set up your custom Express Server: Project

 webpack-demo
  |- package.json
  |- webpack.config.js
+ |- server.js
  |- /dist
  |- /src
    |- index.js
    |- print.js
  |- /node_modules
Copy the code

server.js

const express = require('express'); const webpack = require('webpack'); const webpackDevMiddleware = require('webpack-dev-middleware'); const app = express(); const config = require('./webpack.config.js'); const compiler = webpack(config); // Tell Express to use webpack-dev-middleware, // and use the webpack.config.js configuration file as the base configuration. app.use( webpackDevMiddleware(compiler, { publicPath: config.output.publicPath, }) ); // Serve the file to port 3000. app.listen(3000, function () { console.log('Example app listening on port 3000! \n'); });Copy the code

Now, add an NPM script to make it easier to run the server:

{" name ":" webpack - demo ", "version" : "1.0.0", "description" : ""," private ": true," scripts ": {" test", "echo" Error: no test specified" && exit 1", "watch": "webpack --watch", "start": "webpack serve --open", + "server": "node server.js", "build": "webpack" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "express": "^ 4.17.1 HTML - webpack -", "plugins" : "^ 4.5.0", "webpack" : "^ 5.4.0", "webpack - cli" : "^ 4.2.0", "webpack - dev - middleware" : "^ 4.0.2 webpack - dev -", "server" : "^ 3.11.0"}, "dependencies" : {" lodash ":" ^ 4.17.20 "}}Copy the code

When you run the NPM run server command in terminal, information similar to the following is displayed:

Example app listening on port 3000! . Emitted by [webpack-dev-middleware] asset index.bundle.js 1.38 MiB [emitted] (name: Emitted by asset print.bundle.js 6.25 KiB [emitted] (name: Emitted) < I > asset index. HTML 274 bytes [emitted] < I > Runtime modules 9.0 9.0 modules < I > Cacheable modules 9.0 KiB < I > ./src/index.js 406 bytes [built] [code generated] <i> ./src/print.js 83 bytes [built] [code generated] <i> /node_modules/lodash/lodash. Js 530kib [built] [code generated] < I > webpack 5.4.0 compiled successfully in 709 ms < I > [webpack-dev-middleware] Compiled successfully. <i> [webpack-dev-middleware] Compiling... < I > [webpack-dev-middleware] assets by status 1.38 MiB [cached] 2 Assets < I > cached Modules 530 KiB (javascript) 1.9 KiB (Runtime) [cached] 12 Modules < I > webpack 5.4.0 compiled successfully in 19 ms < I > [webpack-dev-middleware successfully.Copy the code

Open your browser and visit http://localhost:3000. You can see that the WebPack application is already running

4. Webpack optimization of “Code 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.

There are three common ways to separate code:

  • 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.

This is the simplest and most intuitive way to separate code, but there are some hidden dangers due to manual configuration. The following steps separate another Module from the main bundle

project

webpack-demo
|- package.json
|- webpack.config.js
|- /dist
|- /src
  |- index.js
+ |- another-module.js
|- /node_modules
Copy the code

another-module.js

import _ from 'lodash';

console.log(_.join(['Another', 'module', 'loaded!'], ' '));
Copy the code

webpack.config.js

 const path = require('path');

 module.exports = {
-  entry: './src/index.js',
+  mode: 'development',
+  entry: {
+    index: './src/index.js',
+    another: './src/another-module.js',
+  },
   output: {
-    filename: 'main.js',
+    filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
   },
 };
Copy the code

The construction results are as follows:

...
[webpack-cli] Compilation finished
asset index.bundle.js 553 KiB [emitted] (name: index)
asset another.bundle.js 553 KiB [emitted] (name: another)
runtime modules 2.49 KiB 12 modules
cacheable modules 530 KiB
  ./src/index.js 257 bytes [built] [code generated]
  ./src/another-module.js 84 bytes [built] [code generated]
  ./node_modules/lodash/lodash.js 530 KiB [built] [code generated]
webpack 5.4.0 compiled successfully in 245 ms
Copy the code

The disadvantage of this approach is that if there are duplicate modules between the entry chunks, those duplicate modules will be imported into each bundle, which is obviously not flexible enough to dynamically split the code from the core application logic. For example, we introduced Lodash in./ SRC /index.js and in another. Js, which created double references in the two generated bundles.

2. Prevent duplication entry relies on configuring the dependOn option option in webpack.config.js so that the module: webpack.config.js can be shared among multiple chunks

const path = require('path');

 module.exports = {
   mode: 'development',
   entry: {
-    index: './src/index.js',
-    another: './src/another-module.js',
+    index: {
+      import: './src/index.js',
+      dependOn: 'shared',
+    },
+    another: {
+      import: './src/another-module.js',
+      dependOn: 'shared',
+    },
+    shared: 'lodash',
   },
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
   },
 };
Copy the code

If we want to use multiple entry on an HTML page, still need to set up optimization. RuntimeChunk: ‘single’.

 const path = require('path');

 module.exports = {
   mode: 'development',
   entry: {
     index: {
       import: './src/index.js',
       dependOn: 'shared',
     },
     another: {
       import: './src/another-module.js',
       dependOn: 'shared',
     },
     shared: 'lodash',
   },
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
   },
+  optimization: {
+    runtimeChunk: 'single',
+  },
 };
Copy the code

The construction results are as follows:

. [webpack-cli] Compilation finished asset shared.bundle.js 549 KiB [compared for emit] (name: Shared) asset Runte.bundle.js 7.79 KiB [Compared for emit] (name: Runtime) Asset index.bundle.js 1.77 KiB [Compared for emit] (name: Index) asset other.bundle.js 1.65kib [Compared for emit] (name: Another) Entrypoint index 1.77 KiB = index.bundle.js Entrypoint another 1.65 KiB = another.bundle.js Entrypoint shared 557 KiB = Runtime.bundle. js 7.79 KiB shared.bundle.js 549 KiB Runtime modules 3.76 KiB 7 modules cacheable modules 530 KiB ./node_modules/lodash/lodash.js 530 KiB [built] [code generated] ./src/another-module.js 84 bytes [built] [code Generated]./ SRC /index.js 257 bytes [built] [code generated] webpack 5.4.0 compiled successfully in 249 msCopy the code

As you can see, in addition to shared.bundle.js, index.bundle.js and another.bundle.js, a runtime.bundle.js file is generated.

SplitChunksPlugin the SplitChunksPlugin plug-in can extract a common dependency module into an existing inbound chunk or into a newly generated chunk. We can use this plug-in to remove the duplicate Lodash module from the previous example: webpack.config.js

  const path = require('path');

  module.exports = {
    mode: 'development',
    entry: {
      index: './src/index.js',
      another: './src/another-module.js',
    },
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist'),
    },
+   optimization: {
+     splitChunks: {
+       chunks: 'all',
+     },
+   },
  };
Copy the code

Other ways to separate code can be found on the WebPack website without going into details.

5. Webpack optimization of “Cache Strategy”

We use WebPack to package our modular application, which generates a deployable /dist directory and places the packaged content in that directory. As soon as the contents of the /dist directory are deployed to the server, the client (usually a browser) can access the Server’s web site and its resources. The last step to retrieve the resource is time consuming, which is why browsers use a technique called caching. Caching can be hit to reduce network traffic and make websites load faster, however, if we do not change the file name of a resource when we deploy a new version, the browser may assume that it has not been updated and will use its cached version. Because of the cache, it can be tricky when you need to get new code.

Here’s the configuration necessary to ensure that the files generated by webPack compilation can be cached by the client and that new files can be requested if the file contents change.

We can define the name of the output file by substituting substitutions in output.filename. Webpack provides a way to template file names through bracketed strings using something called Substitution template Strings. In this, the [Contenthash] Substitution creates a unique hash based on the resource content. When the content of the resource changes, the [Contenthash] changes.

project

webpack-demo
|- package.json
|- package-lock.json
|- webpack.config.js
|- /dist
|- /src
  |- index.js
|- /node_modules
Copy the code

webpack.config.js

 const path = require('path');
  const HtmlWebpackPlugin = require('html-webpack-plugin');

  module.exports = {
    entry: './src/index.js',
    plugins: [
      new HtmlWebpackPlugin({
-       title: 'Output Management',
+       title: 'Caching',
      }),
    ],
    output: {
-     filename: 'bundle.js',
+     filename: '[name].[contenthash].js',
      path: path.resolve(__dirname, 'dist'),
      clean: true,
    },
  };
Copy the code

Then run our build script NPM run build and output the following:

. Asset Size Chunks Chunk Names main.7e2c49a622975ebd9b7e.js 544 kB 0 [emitted] [big] main index.html 197 bytes [emitted] .Copy the code

As you can see, the name of the bundle is a mapping of its content (via hash). If we make no changes and run the build again, we expect the file name to remain the same. However, if we actually run it, we might find that this is not the case:

. Asset Size Chunks Chunk Names main.205199ab45963f6a62ec.js 544 kB 0 [emitted] [big] main index.html 197 bytes [emitted] .Copy the code

This is because webpack contains certain Boilerplates, especially the Runtime and manifest, in the portal chunk. Extracting the bootstrap template (Runtime and manifest) SplitChunksPlugin can be used to separate modules into separate bundles. Webpack also provides an optimization function, can use optimization. RuntimeChunk option to the runtime code into a separate chunk. Set it to single to create a Runtime bundle for all chunks: webpack.config.js

  const path = require('path');
  const HtmlWebpackPlugin = require('html-webpack-plugin');

  module.exports = {
    entry: './src/index.js',
    plugins: [
      new HtmlWebpackPlugin({
      title: 'Caching',
      }),
    ],
    output: {
      filename: '[name].[contenthash].js',
      path: path.resolve(__dirname, 'dist'),
      clean: true,
    },
+   optimization: {
+     runtimeChunk: 'single',
+   },
  };
Copy the code

Build again, and then look at the extracted Runtime bundle:

Hash: 82C9C385607b2150FAB2 Version: webPack 4.12.0 Time: 3027 ms Asset Size Chunks Chunk Names runtime. Cc17ae2a94ec771e9221. Js 1.42 KiB 0 [emitted] the runtime Main. E81de2cf758ada72f306. Js 69.5 KiB 1 [emitted] the main index, 275 bytes of HTML [emitted] [1] (webpack)/buildin/module. Js 497 bytes {1} [built] [2] (webpack)/buildin/global.js 489 bytes {1} [built] [3] ./src/index.js 309 bytes {1} [built] + 1  hidden moduleCopy the code

Extracting third-party libraries (such as Lodash or React Vue) into separate Vendor Chunk files is recommended because they are rarely modified as frequently as native source code. Therefore, through the implementation of the above steps, the long-term cache mechanism of the client is used to eliminate the request by hitting the cache, and reduce the resource acquisition from the server. At the same time, the client code and the server code version are consistent. This can be done by using the cacheGroups option of SplitChunksPlugin plug-in as demonstrated in SplitChunksPlugin Example 2 on the WebPack website. We add the following cacheGroups parameter to optimization.splitchunks and build webpack.config.js

 const path = require('path');
  const HtmlWebpackPlugin = require('html-webpack-plugin');

  module.exports = {
    entry: './src/index.js',
    plugins: [
      new HtmlWebpackPlugin({
      title: 'Caching',
      }),
    ],
    output: {
      filename: '[name].[contenthash].js',
      path: path.resolve(__dirname, 'dist'),
      clean: true,
    },
    optimization: {
      runtimeChunk: 'single',
+     splitChunks: {
+       cacheGroups: {
+         vendor: {
+           test: /[\/]node_modules[\/]/,
+           name: 'vendors',
+           chunks: 'all',
+         },
+       },
+     },
    },
  };
Copy the code

Build again, and then look at the new Vendor bundle:

. Asset Size Chunks Chunk Names runtime. Cc17ae2a94ec771e9221. Js 1.42 KiB 0 [emitted] the runtime Vendors. A42c3ca0d742766d7a28. Js 69.4 KiB 1 [emitted] vendors. The main abf44fedb7d11d4312d7. Js 240 bytes 2 [emitted] main index.html 353 bytes [emitted] ...Copy the code

You can see that the Vendors module in Nodemodules has been separated from the main module, and the size of main has been reduced to the 240bytes module identifier add a print.js file to the project

webpack-demo
|- package.json
|- package-lock.json
|- webpack.config.js
|- /dist
|- /src
  |- index.js
+ |- print.js
|- /node_modules
Copy the code

print.js

export default function print(text) { + console.log(text); +};Copy the code

src/index.js

import _ from 'lodash'; + import Print from './print'; function component() { const element = document.createElement('div'); // Lodash is element. InnerHTML = _. Join (['Hello', 'webpack'], '"); + element.onclick = Print.bind(null, 'Hello webpack! '); return element; } document.body.appendChild(component());Copy the code

Run the build again, and then we expect only the hash of the main bundle to change, but…

. Asset Size Chunks Chunk Names runtime. 1400 d5af64fc1b7b3a45. Js 5.85 kB 0 [emitted] runtime vendor. A7561fb0e9a071baadb9. Js 1 [emitted 541 kB] [big] vendor. The main b746e3eb72875af2caa9. Js 1.22 kB 2 [emitted] the main index. The HTML 352 bytes (emitted) .Copy the code

… We can see that the hash of all three files has changed. This is because each module.id is incremented by default based on the resolve order. That is, when the parse order changes, the ID changes with it. Brief summary:

  • The Main bundle changes as its new content changes.
  • Vendor bundle will follow itselfmodule.idChange, and change.
  • The Manifest Runtime changes because it now contains a reference to a new module.

It’s easy to know that the first and last are understandable behaviors, but the second vendor hash change is what we’re fixing, in which case we need to set optimization.moduleids to ‘deterministic’ : webpack.config.js

const path = require('path');
  const HtmlWebpackPlugin = require('html-webpack-plugin');

  module.exports = {
    entry: './src/index.js',
    plugins: [
      new HtmlWebpackPlugin({
        title: 'Caching',
      }),
    ],
    output: {
      filename: '[name].[contenthash].js',
      path: path.resolve(__dirname, 'dist'),
      clean: true,
    },
    optimization: {
+     moduleIds: 'deterministic',
      runtimeChunk: 'single',
      splitChunks: {
        cacheGroups: {
          vendor: {
            test: /[\/]node_modules[\/]/,
            name: 'vendors',
            chunks: 'all',
          },
        },
      },
    },
  };
Copy the code

Now, regardless of whether any new local dependencies are added, vendor Hash should remain the same for both builds:

. Asset Size Chunks Chunk Names main.216e852f60c8829c2289.js 340 bytes 0 [emitted] main vendors.55e79e5927a639d21a1b.js 69.5 KiB 1 [emitted] vendors runtime. 725 a1a51ede5ae0cfde0. Js 1.42 KiB 2 [emitted] runtime index. The HTML 353 bytes (emitted)  Entrypoint main = runtime.725a1a51ede5ae0cfde0.js vendors.55e79e5927a639d21a1b.js main.216e852f60c8829c2289.js ...Copy the code

Then, modify SRC /index.js to temporarily remove additional dependencies:

import _ from 'lodash'; - import Print from './print'; + // import Print from './print'; function component() { const element = document.createElement('div'); // Lodash is element. InnerHTML = _. Join (['Hello', 'webpack'], '"); - element.onclick = Print.bind(null, 'Hello webpack! '); + // element.onclick = Print.bind(null, 'Hello webpack! '); return element; } document.body.appendChild(component());Copy the code

Finally, run our build again:

. Asset Size Chunks Chunk Names main.ad717f2466ce655fff5c.js 274 bytes 0 [emitted] main vendors.55e79e5927a639d21a1b.js 69.5 KiB 1 [emitted] vendors runtime. 725 a1a51ede5ae0cfde0. Js 1.42 KiB 2 [emitted] runtime index. The HTML 353 bytes (emitted)  Entrypoint main = runtime.725a1a51ede5ae0cfde0.js vendors.55e79e5927a639d21a1b.js main.ad717f2466ce655fff5c.js ...Copy the code

You can see that in both builds, the vendor bundle file name is 55e79e5927A639D21a1b

Webpack creates library

In addition to packaging applications, Webpack can also be used to package JS libraries. Let’s write a small library called Webpack-numbers that converts the numbers 1 through 5 to a textual representation and vice versa, such as 2 to ‘two’. The basic project structure might look like this: project

  |- webpack.config.js
+  |- package.json
+  |- /src
+    |- index.js
+    |- ref.json
Copy the code

Initialize the project using NPM, then install Webpack, webPack-CLI and LoDash:

npm init -y
npm install --save-dev webpack webpack-cli lodash
Copy the code

We installed LoDash as devDependencies instead of Dependencies because we didn’t need to package it into our library, which could easily grow in size. src/ref.json

[
  {
    "num": 1,
    "word": "One"
  },
  {
    "num": 2,
    "word": "Two"
  },
  {
    "num": 3,
    "word": "Three"
  },
  {
    "num": 4,
    "word": "Four"
  },
  {
    "num": 5,
    "word": "Five"
  },
  {
    "num": 0,
    "word": "Zero"
  }
]
Copy the code

src/index.js

import _ from 'lodash'; import numRef from './ref.json'; export function numToWord(num) { return _.reduce( numRef, (accum, ref) => { return ref.num === num ? ref.word : accum; } "); } export function wordToNum(word) { return _.reduce( numRef, (accum, ref) => { return ref.word === word && word.toLowerCase() ? ref.num : accum; }, 1); }Copy the code

Webpack configuration

We can start with the following basic webpack configuration: webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'webpack-numbers.js',
  },
};
Copy the code

In the example above, we’ll tell Webpack to pack SRC /index.js into dist/webpack-numbers.js.

Expose the Library

By now, everything should be packaged the same as the application. Here is the different part – we need to expose the content exported from the entry through the output.library configuration item. webpack.config.js

  const path = require('path');

  module.exports = {
    entry: './src/index.js',
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: 'webpack-numbers.js',
+     library: "webpackNumbers",
    },
  };
Copy the code

We named the entry point WebPackageNumber, so users can use it through script references

<script src="https://example.org/webpack-numbers.js"></script>
<script>
  window.webpackNumbers.wordToNum('Five');
</script>
Copy the code

However, it only works by being referenced by script tags, and it does not run in CommonJS, AMD, Node.js, etc.

As a library author, we want it to be compatible with different environments, that is, users should be able to use the packaged library by: CommonJS Module require:

const webpackNumbers = require('webpack-numbers');
// ...
webpackNumbers.wordToNum('Two');
Copy the code

AMD module require:

require(['webpackNumbers'], function (webpackNumbers) {
  // ...
  webpackNumbers.wordToNum('Two');
});
Copy the code

script tag:

<! DOCTYPE html> <html> ... <script src="https://example.org/webpack-numbers.js"></script> <script> // ... // Global variable webpackNumbers.wordToNum('Five'); // Property in the window object window.webpackNumbers.wordToNum('Five'); / /... </script> </html>Copy the code

We update the output.library configuration item with its type set to ‘umd’ (umd is generic) :

 const path = require('path');

 module.exports = {
   entry: './src/index.js',
   output: {
     path: path.resolve(__dirname, 'dist'),
     filename: 'webpack-numbers.js',
-    library: 'webpackNumbers',
+    library: {
+      name: 'webpackNumbers',
+      type: 'umd',
+    },
   },
 };
Copy the code

Webpack will now pack a library that can be used with CommonJS, AMD, and Script tags. Externalize LoDash Now, if you execute Webpack, you’ll find that you’ve created a fairly large file. If you look at this file, you’ll see that LoDash is also packaged into the code. In this scenario, we tend to think of LoDash as peerDependency. In other words, the consumer should already have LoDash installed. Thus, you give up control of the other library and give control to the consumer who uses the library. This can be done using the externals configuration: webpack.config.js

  const path = require('path');

  module.exports = {
    entry: './src/index.js',
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: 'webpack-numbers.js',
      library: {
        name: "webpackNumbers",
        type: "umd"
      },
    },
+   externals: {
+     lodash: {
+       commonjs: 'lodash',
+       commonjs2: 'lodash',
+       amd: 'lodash',
+       root: '_',
+     },
+   },
  };
Copy the code

This means that your library needs a dependency called LoDash, which must exist and be available in the consumer environment for libraries that want to invoke multiple files from a dependency:

import A from 'library/one';
import B from 'library/two';

// ...
Copy the code

You cannot exclude the entire library from the bundle by specifying it in externals. Instead, you need to rule them out one by one, or use a regular expression.

module.exports = { //... Externals: [' library/one ', 'the library/two', / / match begins with a "library/" all dependent / ^ library /. + $/,],};Copy the code

Publishing follows the steps described in the Production environment guide to optimize the output in production. We also need to add the path of the bundle generated file to the main field in package.json. package.json

{... "main": "dist/webpack-numbers.js", ... }Copy the code

Or, following this guide, add it as a standard module:

{... "module": "src/index.js", ... }Copy the code

The key main here refers to the package.json standard, while module refers to a proposal that allows the JavaScript ecosystem to upgrade with ES2015 modules without breaking backward compatibility. Now you can publish it as an NPM package, find it at unpkg.com, and distribute it to your users. Note: To expose the stylesheets associated with the library, you should use the MiniCssExtractPlugin. The user can then use and load these stylesheets just like any other.

Finally, build the project performance

Updating Node.js to the latest version of Webpack can also help improve performance. In addition, updating your package management tools (such as NPM or YARN) to the latest version can also help improve performance. Newer versions allow for more efficient tree of modules and faster parsing. Loader applies loader to the minimum number of modules necessary through include, each additional Loader /plugin has its start time, otherwise it will waste computer resources, as follows:

const path = require('path'); module.exports = { //... module: { rules: [ { test: /.js$/, include: path.resolve(__dirname, 'src'), loader: 'babel-loader', options: {// presets: ['@babel/preset-env'], // Presets are set to the current VERSION of THE JS plugins: [require('@babel/plugin-transform-object-rest-spread')] // plugin is required}},],},};Copy the code

Where babel-loader is used to convert ES6 into JS syntax that computers can compile. CacheDirectory: specifies the resolvable cache path for webpack’s table-loader. The default is false. When this value is set, the specified directory will be used to buffer the loader execution results. Subsequent WebPack builds will attempt to read the buffer to avoid a high-performance compilation process when executed every time. If provided with the spacetime value or passed true, then loader uses the default buffer directory node_modules/. Cache /babel-loader to optimize the compilation speed of able-loader can be used:

  1. Such as excludingnode_modulesRefer to the loaders configuration in the documentationexcludeOptions.
  2. Or by usingcacheDirectory Option to speed up babel-Loader at least twice as fast. This caches the results of the translation into the file system.

Extracurricular tips:

Nodejs NPM webpack: NodeJS NPM webpack: nodeJS NPM webpack: nodeJS NPM webpack NPM is a built-in package management tool for NodeJS, and Webpack is one of the many “packages” in the NPM repository. NPM ‘m’ = ‘Management’; NPX ‘x’ = ‘eXecute’; when executing NPX XXX, check whether XXXZ is in $PATH. If not, check whether it is in node_modules. NPX can also be interpreted as one script in package.json.

What is Chunk? The JS files generated by webpack are called chunk. Each chunk is a code block. Name refers to the name of the file before the package.

Module /chunk/bundle difference (easy to confuse)

  • Module: js module
  • Chunk: a file consisting of multiple Jsmodule files during webpack compilation
  • Bundle: Is the final state of the chunk file, which is the result of webpack compilation

Webpack hash strategy (cache dependent) :

Hash Chunkhash Contenthash (Name/Difference) Hash policy of webpack and the difference between the three hashes

Here are three hash expressions:

  • hash: unique hash generated for every build
  • chunkhash: hashes based on each chunks’ content
  • contenthash: hashes generated for extracted content