1 Code Fragments

Code splitting is a technology unique to Webpack, which can split the code in a specific form. Instead of loading all at once, code splitting can be loaded on demand, which can effectively reduce the size of resources loaded on the first screen.

1.1 CommonsChunkPlugin

CommonsChunkPlugin is a built-in plugin for Webpack4 that extracts the common parts of multiple chunks. Thus reduce the repeated packaging of modules in the development process and improve the development speed. The overall size of the resource is reduced and the client cache can be effectively utilized.

CommonsChunkPlugin can be configured with the following attributes.

  • nameWill:chunksAttribute correspondingsource chunkThe public module is extracted fromname, if not specifiedchunks, will be extracted by defaultentry chunksPublic modules in
  • chunks: specifysource chunk, that is, from whatchunkTo find public modules, omitting this option defaults toentry chunks
  • filename: Indicates the extracted resource file name, which can be dynamically generated in template language
  • minChunks: can be a number, function, orInfinity

1.1.1 Non-plug-in packaging

The root directory contains package.json, webpack.config.js, SRC, etc. The SRC directory contains foo.js, bar.js, utils.js.

// package.json{..."scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "webpack": "^ 3.10.0"
  },
  "dependencies": {
    "jquery": "^ 3.2.1." "}}// webpack.config.js
module.exports = {
    entry: {
        foo: './src/foo.js'.bar: './src/bar.js'
    },
    output: {
        filename: './dist/[name].js'}}// src/bar.js
import jquery from 'jquery'
import { log } from './utils.js'
console.log(jquery, log, 'bar')

// src/foo.js
import jquery from 'jquery'
import { log } from './utils.js'
console.log(jquery, log, 'foo')

// src/utils.js
export function log(){
    console.log('log')}Copy the code

Running the package will generate bar.js and foo.js in the root dist folder, where jquery and utils are packaged into these two files.

Finally, you need to add script tags to the page to introduce foo.js and bar.js.

// src/index.html
<html lang="zh-CN">... <body><script src="./bar.js"></script>
        <script src="./foo.js"></script>
    </body>
</html>
Copy the code

1.1.2 Extract common code

Modify webpack.config.js and add CommonsChunkPlugin to extract common modules.

// webpack.config.js
const webpack = require("webpack");

module.exports = {
    entry: {
        foo: './src/foo.js'.bar: './src/bar.js'
    },
    output: {
        filename: './dist/[name].js'
    },
    plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name: 'vendor'.filename: './dist/[name].js']}}),Copy the code

The third-party modules jquery in foo.js and bar.js as follows, as well as the public modules utils and webpack running files in the project, have been packed into vendor.js, and the size of foo.js and bar.js has been significantly reduced.

Vendor.js should be introduced before any other JS on the page.

// dist/index.js
<html lang="zh-CN">... <body><script src="./vendor.js"></script>
        <script src="./bar.js"></script>
        <script src="./foo.js"></script>
    </body>
</html>
Copy the code

1.1.3 Extract the runtime

When extracting a common module using a plug-in, the extracted resource contains not only the module code but also the WebPack runtime. The WebPack runtime refers to the code that initializes the environment, such as creating module cache objects, declaring module load functions, and so on.

The first CommonsChunkPlugin instance below extracts the third-party jquery module, local utils module, and Webpack runtime files from foo.js and bar.js into vendor.

Then each CommonsChunkPlugin instance extracts the runtime file from vendor to Runtime. The final vendor includes jquery and utils, and runtime contains the WebPack runtime file.

Note that the CommonsChunkPlugin for Runtime must appear at the end of plugins, otherwise WebPack will not extract the module properly.

	// webpack.config.js
 const webpack = require("webpack");

module.exports = {
    entry: {
        foo: './src/foo.js'.bar: './src/bar.js'
    },
    output: {
        filename: './dist/[name].js'
    },
    plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name: 'vendor'.filename: './dist/[name].js'
        }),
        new webpack.optimize.CommonsChunkPlugin({
            name: 'runtime'.filename: './dist/[name].js'.chunks: ['vendor']})]}Copy the code

Running the package will generate files such as Runtime.js and rendor.js in the dist directory.

These plugins are equivalent to the following.

plugins: [
    new webpack.optimize.CommonsChunkPlugin({
        name: ['vendor'.'runtime'].filename: './dist/[name].js'})]Copy the code

When minChunks are set to N, it means that a module can be extracted only when n chunks are referenced simultaneously. The CommonsChunkPlugin defaults to extracting a module as long as it is referenced by two entry chunks, i.e. minChunk defaults to 2.

If set to Infinity, the extraction threshold is infinitely high, that is, all modules will not be extracted.

Infinity is used to make webPack extract only specific modules, and to generate a file runtime that contains only the WebPack runtime without any modules.

The following represents extracting the outbound files from the entry chunk, and then extracting the third-party modules and local utils modules from Foo and bar.

plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name: 'runtime'.filename: './dist/[name].js'.minChunks: Infinity
        }),
        new webpack.optimize.CommonsChunkPlugin({
            name: 'vendor'.filename: './dist/[name].js'.chunks: ['foo'.'bar'"})"Copy the code

1.1.4 Extracting third-party modules and local modules

The first CommonsChunkPlugin instance has minChunks set to Infinity, which means that none of the modules will be extracted. In this case, the Name is set to Runtime and the WebPack runtime file can be extracted. However, since name is specified as vendor and vendor is declared in the entry entry, it means that only the module (jquery) corresponding to the Vendor array and the Webpack runtime file are extracted.

The next CommonsChunkPlugin instance extracts the WebPack runtime file from vendor, which only contains the third-party module jquery, and runtime which contains the runtime file.

The last CommonsChunkPlugin instance finally extracted the local modules from Foo and bar into utils.

// webpack.config.js
const webpack = require("webpack");

module.exports = {
  entry: {
    foo: "./src/foo.js".bar: "./src/bar.js".vendor: ["jquery"],},output: {
    filename: "./dist/[name].js",},plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: "vendor".filename: "./dist/[name].js".minChunks: Infinity,}).new webpack.optimize.CommonsChunkPlugin({
      name: "runtime".filename: "./dist/[name].js".chunks: ["vendor"],}).new webpack.optimize.CommonsChunkPlugin({
      name: "utils".filename: "./dist/[name].js".chunks: ["foo"."bar"],}),]};Copy the code

Running the package will generate vendor.js, utils.js, and runtime.js in the dist directory.

These plugins are equivalent to the following.

plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: ["vendor"."runtime"].filename: "./dist/[name].js".minChunks: Infinity,}).new webpack.optimize.CommonsChunkPlugin({
      name: "utils".filename: "./dist/[name].js".chunks: ["foo"."bar"],})]Copy the code

In the minChunks function of the first CommonsChunkPlugin instance, module.resource is the complete path containing the module name, count is the number of times the module is referenced, By traversing the entry file and its dependent modules, if the module is in node_modules, it will be extracted to vendor. In essence, this method allows vendor to keep only third-party modules.

// webpack.config.js
const webpack = require("webpack");

module.exports = {
  entry: {
    foo: "./src/foo.js".bar: "./src/bar.js",},output: {
    filename: "./dist/[name].js",},plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: "vendor".filename: "./dist/[name].js".minChunks: function (module, count) {
        return module.resource && module.resource.includes("node_modules"); }}),new webpack.optimize.CommonsChunkPlugin({
      name: "runtime".filename: "./dist/[name].js".chunks: ["vendor"],}).new webpack.optimize.CommonsChunkPlugin({
      name: "utils".filename: "./dist/[name].js".chunks: ["foo"."bar"],}),]};Copy the code

Each JS mode is referenced as follows.

<html lang="zh-CN">... <body><script src="./runtime.js"></script>
      <script src="./utils.js"></script>
      <script src="./vendor.js"></script>
      <script src="./foo.js"></script>
      <script src="./bar.js"></script>
    </body>
</html>
Copy the code

1.1.5 Extracting third-party Modules from single-page Applications

Create a separate entry for the single-page app. The following vendor contains the third-party module Vue and webPack runtime files.

// webpack.config.js
const webpack = require("webpack");

module.exports = {
  entry: {
    main: "./src/main.js".vendor: ["vue"],},output: {
    filename: "./dist/[name].js",},plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: "vendor".filename: "./dist/[name].js",})]};Copy the code

After packaging, main.js and Vendor.js are generated in the dist directory.

The method of referencing JS is as follows.

<html lang="zh-CN">... <body><script src="./vendor.js"></script>
      <script src="./main.js"></script>
    </body>
</html>
Copy the code

1.1.6 Loading Resources asynchronously

If the number of modules is too large or the resource volume is too large, some modules that are not used temporarily are loaded lazily. Make the first rendering of the page download resources as small as possible, subsequent modules wait until the appropriate time to trigger the load, this way is loaded on demand.

Webpack officially recommends using the import function to asynchronously load a module and return a Promise object.

The following root directories include webpack.config.js and SRC folders. SRC contains main.js and utils.js, and import is used to load utils.js asynchronously.

// src/main.js
setTimeout(() = > {
  import(/* webpackChunkName: "utils-chunk" */ "./utils.js");
}, 2000);
console.log("main");

// src/utils.js
import jquery from "jquery";
console.log(jquery, "utils");

// webpack.config.js
module.exports = {
  entry: "./src/main.js".output: {
    filename: "./dist/[name].js".chunkFilename: "./dist/[name].js".publicPath: ".. /",}};Copy the code

Output. chunkFilename specifies the file name of asynchronous chunk. The template language is supported. Asynchronous chunk does not have a name by default, and its default value is [id].

WebpackChunkName /* webpackChunkName: “utils-chunk” */, and the chunk name is utils-chunk.

After packaging, main.js is used as the resource loaded on the first screen, which is referenced by script tag in the page, while the request path of indirect resource (utils-chunk.js) is specified by output.publicPath.

The page is referenced as follows.

<html lang="zh-CN">... <body><script src="./main.js"></script>
    </body>
</html>
Copy the code

After 2s, a script tag is dynamically inserted inside the page head tag, which references utils-chunk.js.

1.2 optimization. SplitChunks

The CommonsChunkPlugin can extract common modules in many scenarios, but its limitations are obvious.

  • A singleCommonsChunkPluginInstances can only be extracted individuallyvendor, if you want to extract multiplevendorNeed to add moreCommonsChunkPluginExample, easy to cause configuration code duplication
  • multipleCommonsChunkPluginThere may be logical relationships between instances, and only a correct logical relationship can ensure that the extracted code is as expected, and some configurations are not easy to understand and cannot be done out of the box

Webpack4 removes CommonsChunkPlugin, improves CommonsChunkPlugin and redesigns and implements code fragmentation features to simplify the configuration of code fragmentation through the optimization.splitchunks attribute.

1.2.1 Extract common code

Update the Webpack version, replace CommonsChunkPlugin with splitChunks for packaging asynchronous resources, and adjust webpack.config.js.

// webpack.config.js
module.exports = {
  entry: "./src/main.js".output: {
    filename: "[name].js",},optimization: { splitChunks: { chunks: "all"}},mode: "none"};// package.json{..."devDependencies": {
    "webpack": "4.29.4"."webpack-cli": "3.2.3"}}Copy the code

The result of the packaging run is as follows, where main.js and utils.js are packaged separately, and the third-party module jquery referenced in utils.js is packaged into vendors~utils-chunk.js.

After the page references main.js, vendors~utils-chunk.js and Utils-chunk. js are inserted inside the page head tag 2 seconds later, and the number of parallel requests is 2.

The CommonsChunkPlugin scenario does not extract the jquery module from utils, and if only one line of utils is modified, the client will have to re-download the whole utils-chunk.js. While splitChunks extract jquery modules, jquery will not change frequently and modify one line of utils code. The client only needs to re-download utis-chunk. js, which is less than 1 KiB in size. Vendor-utils-chunk. js also makes good use of the browser cache.

1.2.2 Extraction conditions

The CommonsChunkPlugin is more imperative in that it extracts specific modules through configuration items. The difference of splitChunks is that only part of the extraction conditions, such as module volume and module location, are set. When certain modules meet these conditions, they will be automatically extracted in a more declarative way. The default extraction conditions for splitChunks are as follows.

  • After extraction of thechunkfromnode_modulesDirectory, in thenode_modulesThe module is generally a general module, more suitable to be extracted
  • After extraction of thejavascript chunkVolume is greater than the30KB.CSS chunkVolume is greater than the50KBGenerally, the resource volume after extraction is too small, and the optimization effect is relatively general
  • During the on-demand loading process, the maximum value of resources requested by parallel requests is less than or equal to5
  • During the first load, the maximum number of resources in parallel requests must be smaller than or equal to3

1.2.3 Extracting Multiple Asynchronous Resources

The root directory contains webpack.config.js and SRC folders, while SRC contains main.js, foo.js and bar.js.

// src/main.js
setTimeout(() = > {
  import(/* webpackChunkName: "bar-chunk" */ "./bar.js");
  import(/* webpackChunkName: "foo-chunk" */ "./foo.js");
}, 2000);
console.log("main");

// src/bar.js
import react from "react";
console.log(react, "bar");

// src/foo.js
import vue from "vue";
import react from "react";
console.log(vue, react, "foo");
Copy the code

After packaging is run, Webpack creates a code block containing vUE (vendor~foo-chunk), which Foo-chunk relies on, as well as a code block containing React (vendors~bar-chunk~foo-chunk), Foo-chunk and bar-chunk depend on this code block.

Vendors ~bar-chunk~foo-chunk.js and bar-chunk.js are loaded in parallel when import(‘./bar.js) is called. Vendor-chunk. js and foo-chunk.js are loaded in parallel when import(‘./foo.js) is called, and vendors~bar-chunk~foo-chunk.js are loaded instead of vendors~bar-chunk~foo-chunk.js, and are simply loaded from the cache.

1.3 splitChunks Configuration parameters

The default values for splitChunks given by Webpack are as follows.

optimization: {
    splitChunks: {
      chunks: 'async'.minSize: 30000.maxSize: 0.minChunks: 1.maxAsyncRequests: 5.maxInitialRequests: 3.automaticNameDelimiter: '~'.name: true.cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2.priority: -20.reuseExistingChunk: true}}}}Copy the code

1.3.1 chunks

SplitChunks can be configured in splitChunks mode, including async (the default value), INITIAL (the default value), and ALL. Async only extracts asynchronous chunks, initial only extracts synchronous chunks, and ALL enables both modes.

The root directory contains webpack.config.js and SRC, and SRC contains main.js, bar.js, and foo.js.

// src/main.js
import jquery from 'jquery'
setTimeout(() = > {
    import(/* webpackChunkName: "bar-chunk" */ "./bar.js");
    import(/* webpackChunkName: "foo-chunk" */ "./foo.js");
}, 2000);
console.log(jquery, "main");

// src/bar.js
import react from "react";
console.log(react, "bar");

// src/foo.js
import vue from "vue";
import react from "react";
console.log(vue, react, "foo");

// webpack.config.js
module.exports = {
    entry: './src/main.js'.output: {
        filename: "[name].js",},optimization: { splitChunks: { chunks: "all"}},mode: "none",}Copy the code

After running the package, the third-party module jquery in main.js is extracted to vendor-main. js, the rest code is retained to main.js, and the third-party module Vue in foo.js is extracted to vendor-chunk. js. The rest of the code is stored in foo-chunk.js, the third-party modules in foo.js and bar.js are extracted to vendor-chunk-bar-chunk. js, and the rest of the code in bar.js is stored in bar-chunk.js.

Change the chunks property to Initial and package again. Only the third-party module jquery in main.js is extracted to vvendor ~main.js, and the rest code is reserved to main.js, while the third-party modules in foo.js and bar.js are not extracted. Just save it to foo-chunk.js and bar-chunk.js.

The chunks property is changed to async and then packaged again. The code in main.js is saved to main.js, but the third-party module is not extracted. The third-party module vue in foo.js is extracted to Vendor ~foo-chunk.js, and the remaining code is saved to foo-chunk.js. The react modules in foo.js and bar.js are extracted to vendor-chunk ~foo-chunk.js, and the remaining codes in bar.js are reserved to bar-chunk.js.

1.3.2 Matching conditions

  • minSize: Minimum size of extracted code block before compression. Default is30000(30KB)
  • maxSize: The maximum size of the extracted code block before compression. Default is0, that is, the size is not limited
  • minChunks: Number of times the module is referenced. The default value is1
  • maxAsyncRequests: Maximum load times on demand. The default is5
  • maxInitialRequests: Maximum number of initial initial loading times. The default value is3
  • automaticNameDelimiter: The extracted code block automatically generates a separator for the name. The default is ~
  • name: Specifies the name of the extracted code block filetrue, that is, the file name is automatically generated

1.3.3 cache group

The cacheGroups default includes vendors and default rules, vendors being used to extract eligible modules from node_modules, and default acting on modules that are referenced multiple times. To disable a rule, set it to false.

Each item in a cacheGroups inherits or overrules the values of the parameters in the outer splitChunks, so, for example, if there is no minChunks attribute in the cacheGroups. Vendors item, it inherits the values of the outer splitchunk. minChunks attribute, Namely cacheGroups. Vendors. MinChunks to 1. If there is a minChunks attribute in the cacheGroups.default, it overwrites the attribute of the outer splitchunks. minChunks.

In addition to the above parameter values, cacheGroups provides three additional configuration properties.

  • test: can match the module path orchunkName, default to all modules
  • priority: indicates the extraction weight. A larger value indicates a higher priority. A module may satisfy multiple requirementscacheGroups, which is extracted is controlled by the one with the highest weight
  • reuseExistingChunk: Whether to use the existing onechunk.trueRepresents if the currentchunkThe contained module has already been extracted, and no new modules will be generated

The root directory contains the webpack.config.js and SRC folders, while SRC contains main.js and utils.js.

// src/main.js
import Vue from "vue";
import Vuex from "vuex";
import VueRouter from "vue-router";
import { log } from "./utils.js";
console.log(Vue, Vuex, VueRouter, log, "main");

// src/utils.js
export function log() {
  console.log("log");
}

// webpack.config.js
module.exports = {
  entry: "./src/main.js".output: {
    filename: "[name].js",},optimization: {
    splitChunks: {
      chunks: "all",}},mode: "none"};Copy the code

After running the package, the third party modules Vue, vuex, and VUE-Router are extracted to vendors~main.js, and the remaining main.js code and utils.js code are retained in main.js.

If you want to extract utils.js separately, you can customize the following cacheGroups, where module.resource is the full path containing the module name.

// webpack.config.js
splitChunks: {
      chunks: "all".cacheGroups: {
        utils: {
          test: (module) = > {
            return /src\\utils/.test(module.resource);
          },
          priority: -20.minSize: 0,},default: false,}}Copy the code

After packaging, third-party modules Vue, vuex, vuE-Router are extracted to vendors~main.js, codes from utils.js are extracted to vendors~main.js, and remaining codes from main.js are retained.

2 Production Environment

Production environments are different from development environments. Production environments focus on the user experience, how to make users load resources faster, including how to compress resources, adding environment variables to optimize packaging, and how to maximize browser caching.

2.1 Environment Configuration

2.1.1 Single configuration

Single configuration means using webpack.config.js regardless of the environment, simply passing in the environment variable parameters at the beginning of the build, and then using conditions in webpack.config.js to decide which configuration to use.

Note that Windows does not support ENV=development. The command will block and cause an error. The third-party plug-in cross-env can solve this problem.

cnpm i cross-env --save-dev
Copy the code

The root directory contains webpack.config.js and SRC folders, where SRC contains index.html and main.js.

// package.json{..."scripts": {
    "dev": "cross-env ENV=development webpack-dev-server"."build": "cross-env ENV=production webpack"
  },
  "devDependencies": {
    "webpack": "4.29.4"."webpack-cli": "3.2.3"."webpack-dev-server": "^ 3.1.14"."html-webpack-plugin": "^ 3.2.0"}}// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');

const ENV = process.env.ENV;
const isProd = ENV === 'production';

module.exports = {
  entry: './src/main.js'.output: {
    filename: isProd ? "./[name].[chunkhash:8].js" : "./[name].js",},plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',}})]// src/index.html
<html lang="zh-CN">

    <head>
        <title>hello world</title>
    </head>

    <body>
        <p>hello world</p>
    </body>
</html>

// src/main.js
console.log("hello world");
Copy the code

Run the dev script command, and the console can view the packaged output file of the development environment.

Run the build script command to view the packaged output file.

2.1.2 Configuring multiple Environments

You can create a separate configuration file for each environment, for example, the development environment is webpack.dev.config.js, and the production environment is webpack.prod.config.js. However, the two configuration files usually have duplicate parts, which is not conducive to maintenance.

You can also create a separate webpack.config.js file, and then two other JS files reference that file and add their own environment configuration. However, the third-party plug-in Webpack-merge is generally used to merge webPack configurations, facilitating configuration management in different environments.

The root directories are build, package.json, and SRC folders. SRC contains index.html and main.js.

// package.json{..."scripts": {
    "dev": "webpack-dev-server --config=./build/webpack.dev.config.js"."build": "webpack --config=./build/webpack.prod.config.js"}}Copy the code

The build includes webpack.config.js, webpack.dev.config.js, and webpack.prod.config.js.

// build/webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/main.js".plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html",]}})// build/webpack.dev.config.js
const webpackConfig = require("./webpack.config.js");

module.exports = { ... webpackConfig,output: {
    filename: "./[name].js",}}// build/webpack.prod.config.js
const webpackConfig = require("./webpack.config.js");

module.exports = { ... webpackConfig,output: {
    filename: "./[name].[chunkhash:8].js",}}Copy the code

Run the dev and build scripts respectively, and the output is the same as the single configuration.

2.2 production mode

Earlier versions of WebPack had too many configuration items for different environments to use out of the box. Webpack4 has a new mode configuration item that allows you to switch the packaging mode.

If the following configuration items are in production, WebPack automatically adds the configuration items suitable for production.

// webpack.config.js
module.exports = {
  ...
  mode: "production"
}
Copy the code

2.3 Environment Variables

DefinePlugin can be used in Webpack to add different environment variables for production and development environments, i.e. webpack.DefinePlugin is used to set global variables in the browser environment (which are not mounted to the window).

Webpack.defineplugin applies to all modules and completely replaces the environment variables in the module with the values set.

The root directories are webpack.config.js, package.json, and SRC, and SRC includes main.js.

// package.json{..."scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "webpack": "4.29.4"."webpack-cli": "3.2.3"}}// webpack.config.js
const webpack = require('webpack')

module.exports = {
  entry: './src/main.js'.output: {
    filename: "./[name].js",},plugins: [
    new webpack.DefinePlugin({
      ENV: JSON.stringify('production'),})],mode: 'development'.devtool: 'none',}// src/main.js
console.log(ENV)
Copy the code

After packaging, look at the main.js file in the output directory dist, where ENV is completely replaced with “production”.

// dist/main.js
 "./src/main.js": (function(module.exports) {
    console.log("production");
})
Copy the code

Note that json. stringify is added to strings or objects containing strings. If json. stringify is not added, it will be the variable name instead of the string value after substitution, i.e. the ENV described above will be replaced with production (no string quotes).

In addition to string values, other types of environment variables can be set.

new webpack.DefinePlugin({
      ENV: JSON.stringify('production'),
      ENVIR: "\"development\"".IS_PRODUCTION: true.ENV_ID: 1038.CONSTANTS: JSON.stringify({
        TYPES: ['foo'.'bar']})})Copy the code

Many frameworks and libraries use process.env.node_env as a variable to distinguish the development environment from the production environment. The value of production indicates that the current environment is the production environment. Libraries and frameworks will package without the development environment code such as warnings and logs. This will help reduce the size of the code and speed it up.

The process.env.node_env variable can be configured as follows. Note that if mode: ‘production’ is enabled, webapck automatically sets the value of process.env.node_env.

new webpack.DefinePlugin({
    "process.env.NODE_ENV": JSON.stringify('development')})Copy the code

2.4 Differentiating environment modes

Against 2.4.1 scripts

Webpackage 4 introduces the mode attribute, including development, Production, and None modes.

The development mode sets the value of process.env.node_env in the module to development and enables the webpack plug-in for the development environment. Production mode sets the value of process.env.node_env in the module to production and enables the webpack plug-in for the production environment.

You can set mode in webpack.config.js or –mode in the scripts script command in package.json.

// package.json
"scripts": {
    "build-dev": "webpack --mode=development"."build-prod": "webpack --mode=production"
}
Copy the code

Special scripts also default to mode, such as “dev”: “webpack-dev-server” mode is development, “build”: “webpack” mode is production.

// package.json
"scripts": {
    "dev": "webpack-dev-server"."build": "webpack"
}
Copy the code

NODE_ENV (process.env.node_env, process.env.node_env, process.env.node_env, process.env.node_env, process.env.node_env, process.env.node_env) However, the current environment variable cannot be retrieved from the Node environment (webPack configuration file).

// package.json
"scripts": {
    "build": "webpack"
}

// src/mian.js
console.log(process.env.NODE_ENV); // production

// webpack.config.js
console.log(process.env.NODE_ENV); // undefined

module.exports = {
  entry: "./src/main.js". }Copy the code

In webpack.config.js, we can get environment variables as functions, but we can’t get environment variables outside the function either. The production in main.js is due to the special script “build”: “Webpack” defaults to Production mode.

// package.json
"scripts": {
    "build-dev": "webpack --env=development"."build-prod": "webpack --env=production"
}

// webpack.config.js
console.log(process.env.NODE_ENV); // undefined

module.exports = (env) = > {
  console.log(env); // development | production

  return {
    entry: "./src/main.js". }}// src/main.js
console.log(process.env.NODE_ENV); // production
Copy the code

Webpack 2.4.2. DefinePlugin

Scripts mode module process.env.node_env can only have a fixed number of values.

Webpack.defineplugin can set process.env.node_env to any value in the module, but can’t get the current environment variable in the Node environment.

// package.json
"scripts": {
  "build": "webpack"
}

// webpack.config.js
const webpack = require("webpack")
console.log(process.env.NODE_ENV); // undefined

module.exports = {
  entry: "./src/main.js".output: {
    filename: "./[name].js",},plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('dev'),}})]// src/main.js
console.log(process.env.NODE_ENV); // dev
Copy the code

2.4.3 cross – env

In the above two methods, environment variables cannot be obtained from the Node environment (Webpak configuration file), scripts can only be obtained from the function, and the current environment variables cannot be obtained from webpack.config.js.

Cross-env, a third-party plug-in, can fetch the current environment variable from the Node environment and set the value of the environment variable arbitrarily.

// package.json
"scripts": {
    "build": "cross-env ENVIR=prod webpack"
},
"devDependencies": {
    "cross-env": "^ 7.0.3." ". }// webpack.config.js
console.log(process.env.ENVIR); // prod

module.exports = {
  entry: "./src/main.js". }// src/main.js
console.log(process.env.ENVIR); // undefined
Copy the code

2.5 the source map

Source map refers to the process of mapping compiled, compressed, and packaged code back to source code. Webpack packed and compressed code is almost unreadable, and the code throws errors that make it difficult to trace back to the call stack.

2.5.1 Configuration Not Enabled

The root directory contains package.json, webpack.config.js and SRC folders, while SRC contains index.html, main.js and style. SCSS. The main function of MiniCssExtractPlugin is to extract CSS style files.

// package.json{..."scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "css-loader": "^ 0.28.9"."html-webpack-plugin": "^ 3.2.0"."node-sass": "^ 4.7.2." "."sass-loader": "^ 6.0.7"."mini-css-extract-plugin": "^ 0.5.0"."style-loader": "^ 0.19.0"."webpack": "4.29.4"."webpack-cli": "^ 3.2.3"}}// webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  entry: "./src/main.js".plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html",}).new MiniCssExtractPlugin({
      filename: "[name].css".chunkFilename: "[id].css",})],module: {
    rules: [{test: /\.scss$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
          },
          'css-loader'.'sass-loader'],}]}}// src/index.html
<html lang="zh-CN">
  <body>
    <p>hello world</p>
  </body>
</html>

// src/main.js
import './index.scss'
console.log('source-map')

// src/index.scss
$color: red;

p {
  color: $color;
}
Copy the code

After executing the build script package, output index.html, main. CSS, main.js in the root directory, use the VS Code editor plug-in Live Server, inside index. See index.html deployed to the server.

View the output on the following console.

Formatted :formatted:76 Click mian.js:formatted:76 to jump to the following location, but you can only view the position of the output statement in the packaged code, not the source code.

Then look at the styles on the console.

Click mian.css:1 to jump to the following location. You can only view the packaged style code, but cannot go back.

2.5.2 Enabling the Configuration

In webpack.config.js add devTool to open js file source map, and for SCSS, CSS need to add additional Source map configuration items.

// webpack.config.js
module.exports = {
  ...
  module: {
    rules: [{test: /\.scss$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
          },
          {
            loader: 'css-loader'.options: {
              sourceMap: true}}, {loader: 'sass-loader'.options: {
              sourceMap: true}}]}]},devtool: 'source-map'
}
Copy the code

Since the devtool configuration item is enabled, the source map will be passed along with the source code step by step until the final map file is generated. By default, the file name is output file with the. Map suffix, such as main.js.map. By default, a comment is appended to the end of main.js to identify the location of the map file.

// main.js.//# sourceMappingURL=main.js.map
Copy the code

The console looks at the output again and can already see the exact number of lines of this statement in the source code.

Click main.js:2 to jump to the following to see the details of this statement.

Styles can be traced back to the SCSS file.

Click index.scss:3 to jump to the following.

2.5.3 Security performance

After source Map is enabled, the parsed project source code can be found in the Developer tools webpack:// directory.

Note that when you open the browser’s developer tools, the map file is loaded at the same time, and the browser then uses it to parse the corresponding output file to figure out the directory structure and content of the source code.

Map files are usually large and cannot be loaded without opening developer tools, but using Source Map can be a security risk.

Webpack provides hidden-source-map and Nosource-source-map strategies to improve the security of source map.

Hidden-source-map still generates the full map file, but no reference to the map file is added to the output file. To trace the source, upload the map file using a third-party service, such as Sentry.

The nosource-source-map directory structure of the source code can be viewed in the developer tools, but the details of the file are hidden. You can see the exact number of lines in the console log, which is basically enough for backtracking errors.

2.6 DevTool Configuration Items

Devtool is used for debugging, including the following dozen configurations.

  • none
  • eval
  • eval-source-map
  • cheap-eval-source-map
  • cheap-module-eval-source-map
  • source-map
  • cheap-source-map
  • cheap-module-source-map
  • inline-source-map
  • inline-cheap-source-map
  • inline-cheap-module-source-map
  • hidden-source-map
  • nosources-source-map

These keywords include eval, cheap, module, source-map, etc. Most of them are combined, and their specific features are as follows.

  • evalUse:evalPackage module code, and existssourceURL.sourceURLPoint to original file
  • cheapPack:mapFile, does not save the column position information of the original code, only contains the row position information. ignoreloaderthesource mapAnd only the translated code is displayed
  • moduleIncluding:loaderthesourcemap
  • source-mapGenerated by:.mapfile

2.6.1 none

The root directory contains package.json, webpack.config.js, and SRC folders, while SRC contains index.html and main.js.

// package.json{..."scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "@babel/core": "^ 7.2.2." "."@babel/preset-env": "^ 7.3.1"."babel-loader": "^ 8.0.5"."html-webpack-plugin": "^ 3.2.0"."webpack": "4.29.4"."webpack-cli": "^ 3.2.3"}}// webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/main.js".plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html",})],module: {
    rules: [{test: /\.js$/,
        loader: "babel-loader".exclude: /node_modules/,
        options: {
          cacheDirectory: true.presets: [["@babel/preset-env", { modules: false}]],},},],},devtool: "none".mode: "development"
}

// src/index.html
<html lang="zh-CN">
  <body>
    <p>hello world</p>
  </body>
</html>

// src/main.js
const fn = () = >{
    console.log('source map');
}
fn();
Copy the code

After running the build script command, the console views the output.

Click mian.js:97 to jump to the following.

The None configuration item cannot be traced back to the source code and simply translates the module code based on the configured Loader.

2.6.2 eval

Change the devTool property in webpack.config.js to eval, and run the build script command to see the packaged output.

The console view the output and click to jump to.

In the EVAL configuration item, the packaged module code is wrapped in EVAL, including the sourceURL, and the traceable source code is converted by the Loader (the arrow function is converted to a normal function) with cursor column information.

2.6.3 source – the map

Change the devtool property in webpack.config.js to source-map, run the build script again, and more. Map files are generated in dist directory.

View the code for the packaged output.

The console view the output and click to jump to.

SourceMappingURL is appended to the end of the packaged output code, and the backtracked source code is the original code with cursor column information.

2.6.4 being – the source – the map

Change the devtool property in webpack.config.js to cheap-source-map, run the build script command, also generate. Map file in dist directory, package the output code is the same as source-map.

The console view the output and click to jump to.

The sourceMappingURL is appended to the end of the packaged output code. The traceback source code is converted by the Loader (the arrow function is converted to a normal function) without cursor column information (the cursor is at the beginning of the line).

2.6.5 eval – source – the map

Change the devtool property in webpack.config.js to eval-source-map and run the build script command to view the packaged output.

The console view the output and click to jump to.

In the eval-source-map configuration item, the packaged module code is wrapped in eval, including sourceURL and sourceMappingURL, without generating a. Map file. Instead, convert the map file contents to Base64 encoding and insert them after the sourceMappingURL, tracing back to the original source code with cursor column information.

2.6.6 being – eval – source – the map

Change the devtool property in webpack.config.js to cheap-eval-source-map and run the build script command to view the packaged output.

The console view the output and click to jump to.

In the cheap-eval-source-map configuration item, the packaged module code is wrapped in eval, including sourceURL and sourceMappingURL, without generating a. Map file. Instead, the map file content is converted to Base64 encoding and inserted after the sourceMappingURL. The backtracked source code is converted by loader (arrow function to normal function) without cursor column information (cursor at the beginning of line).

2.6.7 being – the module – the eval – source – the map

Change the devtool property in webpack.config.js to cheap-module-eval-source-map and run the build script command to view the packaged output.

The console view the output and click to jump to.

In the cheap-module-eval-source-map configuration item, the module code is wrapped in eval, including sourceURL and sourceMappingURL, without generating a. Map file. Instead, convert the map file content to Base64 encoding and insert it after the sourceMappingURL, tracing back to the original source code without cursor column information (the cursor is at the beginning of the line).

2.6.8 being – the module – the source – the map

Change the devtool property in webpack.config.js to cheap-module-source-map and run the build script command to view the packaged output.

The console view the output and click to jump to.

The.map file is also generated in the cheap-module-source-map configuration item, and the sourceMappingURL is appended to the end of the packaged output code. The backtraced source code is the original code without cursor column information (the cursor is at the beginning of the line).

2.6.9 inline – source – the map

Change the devtool property in webpack.config.js to inline-source-map, run the build script command, and see the package output.

The console view the output and click to jump to.

In the inline-source-map configuration item, a. Map file is not generated, but the map file content is converted to base64 encoding and inserted after the sourceMappingURL, with the source code backtracked to the original code with cursor column information.

2.6.10 hidden – source – the map

Change the devtool property in webpack.config.js to hidden-source-map, run the build script command, which will also generate a. Map file in the dist directory, and view the packaged output code.

The console view the output and click to jump to.

In the inline-source-map configuration item, a. Map file is generated, but no reference to the map file is kept (no sourceMappingURL) and no source code can be traced back.

2.6.11 nosources – source – the map

Change the devtool attribute in webpack.config.js to nosource-source-map, run the build script command, and view the output on the console.

Click main.js:2 to jump to the following.

View the code for the packaged output.

The sourceMappingURL is appended to the end of the packaged output code, which cannot be traced back to the source code (the directory structure of the source code can be viewed, the specific content is hidden). However, you can view the exact number of lines in the console log.

2.6.12 Comparison of differences

Slowest > fast > OK > slow > slowest > slowest are the differences among configuration items shown below.

devtool Build speed The map way The eval package sourceMappingURL Whether there is cursor column information Traceable or not Back in the code
none fastest no
eval fast evalWithin the functionsourceURLReference the source file path is There are is loaderTranslated code
source-map slowest Module tail appendsourceMappingURLreferencemap no linkmapThe file name There are is The original code
cheap-source-map ok Module tail appendsourceMappingURLreferencemap no linkmapThe file name no is loaderTranslated code
eval-source-map slowest evalWithin the functionsourceURLReference the source file path and insert the function at the endsourceMappingURL is mapcontentbase64coding is is The original code
cheap-eval-source-map ok evalWithin the functionsourceURLReference the source file path and insert the function at the endsourceMappingURL is mapcontentbase64coding no is loaderTranslated code
cheap-module-eval-source-map slow evalThe sourceURL in the function refers to the source file path, and the end of the function is insertedsourceMappingURL is mapcontentbase64coding no is The original code
cheap-module-source-map slow Module tail appendsourceMappingURLreferencemap no linkmapThe file name no is The original code
inline-source-map slowest Module tail appendsourceMappingURL no mapcontentbase64coding is is The original code
hidden-source-map slowest no
nosources-source-map slowest Module tail appendsourceMappingURLreferencemap no linkmapThe file name

2.7 Resource Compression

Generally, resources published to the online environment are compressed (uglified), that is, redundant whitespace, line breaks and unexecutable code are removed, variable names are shortened, comments are removed, and code is replaced with a shorter form without changing the execution result.

The overall volume of the code will be significantly reduced after compression, and it will be basically unreadable, which improves the security of the code to a certain extent.

2.7.1 compressed JavaScript

Webpack3 and below by webpack. Optimize. UglifyJsPlugin implementation code compression.

// package.json{..."scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "webpack": "^ 3.10.0"}}// webpack.config.js
const webpack = require("webpack")

module.exports = {
  entry: "./src/main.js".output: {
    filename: "./dist/[name].js"
  },
  plugins: [new webpack.optimize.UglifyJsPlugin()]
}

// src/main.js
const fn = function () {
  console.log("hello world")
}
fn()
Copy the code

Webpack4 uses terser-webpack-plugin as the built-in compression plug-in by default, which supports compression of ES6+ code. In Webpack4, optimization. Minimize can be used to control whether compression is enabled, which is disabled by default in the development environment and enabled by default in the production environment.

// webpack.config.js
module.exports = {
    ...
    optimization: {        
        minimize: true}}Copy the code

You can also customize the optimization. Minimizer compression plug-in and its configuration items, which automatically remove console.log when packaged as follows.

// webpack.config.js
const TerserPlugin = require("terser-webpack-plugin")

module.exports = {
  entry: "./src/main.js".output: {
    filename: "./[name].js"
  },
  optimization: {
    minimize: true.minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            pure_funcs: ["console.log"]}})]}}Copy the code

2.7.2 compress CSS

The CSS is first extracted using extract-text-webpack-plugin or mini-CSS-extract-plugin, and then compressed using optimize- CSS-assets-webpack-plugin.

The root directories are package.json, webpack.config.js and SRC folders. SRC contains main.js and index.css.

// package.json{..."scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "css-loader": "^ 0.28.7"."mini-css-extract-plugin": "^ 0.5.0"."optimize-css-assets-webpack-plugin": "^ 4.0.1." "."webpack": "4.29.4"."webpack-cli": "^ 3.2.3"}}// webpack.config.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = {
  entry: "./src/main.js".output: {
    filename: "./[name].js",},module: {
    rules: [{test: /\.css$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader
          },
          "css-loader"]]}},plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css".chunkFilename: "[id].css"}),].optimization: {
    minimize: true.minimizer: [new OptimizeCSSAssetsPlugin()]
  },
}

// src/main.js
import "./index.css";
console.log("hello world");

// src/index.css
p {
  color: red;
}
Copy the code

After the build script is packaged, view the output CSS file.

// dist/main.css
p{color:red}
Copy the code

2.8 the cache

Caching is the reuse of resources that the browser has acquired. Detailed caching policies (such as cache duration) are determined by the server, and the browser responds with the local cache until the resource expires.

However, there is a problem with this approach. If your code is bug-fixed and you want to update it to all users’ browsers at once, your best bet is to change the resource URL, forcing the client to re-download the resource.

The common approach is to hash the resource content once per package and store it as the version number in the file name.

2.8.1 version number

The following uses chunkhash as the file version number, and separately calculates a hash value for each chunk.

// package.json{..."scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "webpack": "4.29.4"."webpack-cli": "^ 3.2.3"}}// webpack.config.js
module.exports = {
  entry: "./src/main.js".output: {
    filename: "./[name]@[chunkhash:8].js"}}// src/main.js
console.log("hello world");
Copy the code

Run the build script package as follows.

The change of resource file name means the change of reference path in HTML. The html-webpack-plugin can be used to automatically synchronize the reference resource name after wrapping.

// package.json
"devDependencies": {..."html-webpack-plugin": "3.2.0"
}

// webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  ...
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html"}})]// src/html
<html lang="zh-CN">
  <body>
    <p>hello world</p>
  </body>
</html>
Copy the code

Run the build script package again and open index.html under dist where the referenced resource paths are synchronized.

2.8.2 CommonsChunkPlugin

The CommonsChunkPlugin allows you to separate out infrequently changing code from frequently iterating business code, and these resources can be cached on the client side.

However, the IDS specified for each module in Webpack3 and the following are numerically increasing. When a new module is inserted, the IDS of other modules will change, thus affecting the contents of chunk, that is, the value of chunkhash, causing the resources that need not be downloaded to be downloaded again.

The root directories are package.json, webpack.config.js and SRC folders, and SRC contains main.js.

// package.json{..."scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "webpack": "^ 3.10.0"
  },
  "dependencies": {
    "jquery": "^ 3.2.1." "}}// webpack.config.js
const webpack = require("webpack")

module.exports = {
  entry: {
    main: "./src/main.js".vendor: ["jquery"]},output: {
    filename: "./dist/[name]@[chunkhash:8].js"
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: "vendor".filename: "./dist/[name]@[chunkhash:8].js"}})]// src/main.js
import "jquery";
console.log("main.js");
Copy the code

Run the build script package to see the output resources, where vendor contains only third-party modules jquery and WebPack runtime files.

SRC > utils. Js, main.js.

// src/main.js
import "jquery";
import "./utils";
console.log("main.js");

// src/utils.js
console.log("utils.js");
Copy the code

Execute the build script package again and view the output resources.

This creates a problem where the module in Vendor does not change, but its path name does.

By comparing [email protected] and [email protected] in dist directory, only the following two places have changed.

2.8.3 HashedModuleIdsPlugin

The solution is to change the way module IDS are generated. The HashedModuleIdsPlugin plugin comes with WebPack3, which can generate a string hash ID for each module based on its path.

// src/main.js
import "jquery";
console.log("main.js");

// webpack.config.js
const webpack = require("webpack")

module.exports = {
  entry: {
    main: "./src/main.js".vendor: ["jquery"]},output: {
    filename: "./dist/[name]@[chunkhash:8].js"
  },
  plugins: [
    new webpack.HashedModuleIdsPlugin(),
    new webpack.optimize.CommonsChunkPlugin({
      name: "vendor".filename: "./dist/[name]@[chunkhash:8].js"}})]Copy the code

Run the build script package to view the output resources.

Main.js introduces utils.js.

import "jquery";
import "./utils";
console.log("main.js");
Copy the code

Run the Build package again.

Since vendor only includes the third-party module jquery and webPack runtime, and the path of jquery is always fixed, its hash ID is always fixed.

Versions below Webpack3, which do not support string ids, can use the webpack-hashed-module-id-plugin. Webpackage 4+ has modified the way the module ID is generated, so this problem is no longer present.

2.9 Monitoring Analysis

You can use third-party plug-ins to monitor and analyze the volume of the bundle output to prevent unnecessary redundant modules from being added.

2.9.1 Import Cost

The Import Cost in Vs Code checks the size of imported modules in real time. When a new module is referenced in Code (mainly in node_modules), the compressed and gzip size of the module is calculated.

2.9.2 webpack – bundle – analyzer

Another visual analysis tool is Webpack-bundle-Analyzer, which analyzes the composition of a bundle.

// package.json{..."scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "vue": "^ 2.6.12." "."vue-router": "^ 3.5.1 track of"."vuex": "^ 3.6.2." "."webpack": "^ 3.10.0"."webpack-bundle-analyzer": "^ 4.4.1." "}}// webpack.config.js
const webpack = require("webpack");
const Analyzer = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;

module.exports = {
  entry: {
    main: "./src/main.js".vendor: ["vue"."vuex"."vue-router"]},output: {
    filename: "./dist/[name].js"
  },
  plugins: [
    new Analyzer(),
    new webpack.optimize.CommonsChunkPlugin({
      name: "vendor".filename: "./dist/[name].js"}})]// src/main.js
import Vue from "vue";
import Vuex from "vuex";
import VueRouter from "vue-router";
import { log } from "./utils.js";
console.log(Vue, Vuex, VueRouter, log, "main.js");

// src/utils.js
export function log() {
  console.log("utils.js");
}
Copy the code

Run the build script and the result is as follows.

3 Packaging Optimization

Do not take any optimization points into the project at the beginning of the project, which will increase the complexity and the optimization effect is not ideal. Generally, the project develops to a certain scale, and then specific optimization occurs when performance problems occur.

3.1 HappyPack

HappyPack is a plugin that uses multiple threads to speed up webpack packaging.

The time-consuming part of the packaging process is to translate various resources through loader, such as Babel ES6+, etc. The specific workflow is as follows.

  1. Get the package entry from the configuration
  2. matchingloaderRules, and translate the entry module
  3. Dependency lookup is performed on translated modules
  4. Repeat the steps for the newly found module2And the steps3Until there are no new dependency modules

Steps 2 through 4 are a recursive process in which WebPack takes the deeper resources step by step and then translates them one by one. The fundamental problem is that WebPack is single-threaded, and if a module depends on several other modules, WebPack must translate them one by one. Although these translation tasks are not task dependent on each other, they must be executed serially. HappyPack’s core feature is the ability to start multiple threads to translate different modules in parallel, making full use of local resources to speed up packaging.

3.1.1 single loader

To use the HappyPack plug-in, replace the original loader with the loader provided by HappyPack and send the original Loader to the HappyPack plug-in.

The root directories are package.json, webpack.config.js, and SRC, where SRC includes main.js, bar.js, and foo.js.

// package.json{..."scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "@babel/core": "^ 7.2.2." "."@babel/preset-env": "^ 7.3.1"."babel-loader": "^ 8.0.5"."vue": "^ 2.6.12." "."vuex": "^ 3.6.2." "."webpack": "4.29.4"."webpack-cli": "3.2.3"."happypack": "^ 5.0.0"}}// src/main.js
import "./foo.js";
import "./bar.js";
console.log("main.js");

// src/foo.js
import vue from "vue";
console.log(vue, "foo.js");

// src/bar.js
import Vuex from "vuex";
console.log(Vuex, "bar.js");
Copy the code

The initial WebPack configuration is as follows.

// webpack.config.js
module.exports = {
  entry: "./src/main.js".output: {
    filename: "./[name].js",},module: {
    rules: [{test: /\.js$/,
        loader: "babel-loader".exclude: /node_modules/,
        options: {
          cacheDirectory: true.presets: [["@babel/preset-env", { modules: false}}]}}Copy the code

Change the WebPack configuration after introducing HappyPack.

// webpack.config.js
const HappyPack = require("happypack");

module.exports = {
  entry: "./src/main.js".output: {
    filename: "./[name].js"
  },
  module: {
    rules: [{test: /\.js$/,
        exclude: /node_modules/,
        loader: "happypack/loader"}},plugins: [
    new HappyPack({
      loaders: [{loader: "babel-loader".options: {
            cacheDirectory: true.presets: [["@babel/preset-env", { modules: false}]}}]})]}Copy the code

3.1.2 multiple loader

When HappyPack optimizes multiple Loaders, it needs to configure an ID for each loader. Otherwise, HappyPack cannot know how rules and plugins correspond one by one.

The root directories are package.json, webpack.config.js, and SRC. SRC contains main.js and index.css.

// package.json{..."scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "@babel/core": "^ 7.2.2." "."@babel/preset-env": "^ 7.3.1"."babel-loader": "^ 8.0.5"."webpack": "4.29.4"."webpack-cli": "3.2.3"."happypack": "^ 5.0.0"."css-loader": "^ 0.28.9"."style-loader": "^ 0.19.0"}}// src/main.js
import "./index.css";
console.log("main.js");

// src/index.css
p {
  color: red;
}
Copy the code

The initial WebPack configuration is as follows.

// webpack.config.js
module.exports = {
  entry: "./src/main.js".output: {
    filename: "./[name].js",},module: {
    rules: [{test: /\.js$/,
        loader: "babel-loader".exclude: /node_modules/,
        options: {
          cacheDirectory: true.presets: [["@babel/preset-env", { modules: false}]]}}, {test: /\.css$/,
        use: ["style-loader"."css-loader"}]}}Copy the code

Change the WebPack configuration after introducing HappyPack.

// webpack.config.js
const HappyPack = require("happypack");

module.exports = {
  entry: "./src/main.js".output: {
    filename: "./[name].js"
  },
  module: {
    rules: [{test: /\.js$/,
        loader: "happypack/loader? id=js".exclude: /node_modules/
      },
      {
        test: /\.css$/,
        loader: "happypack/loader? id=css"}},plugins: [
    new HappyPack({
      id: "js".loaders: [{loader: "babel-loader".options: {
            cacheDirectory: true.presets: [["@babel/preset-env", { modules: false}]]}}]}),new HappyPack({
      id: "css".loaders: ["style-loader"."css-loader"]]}})Copy the code

3.2 Narrowing the packaging scope

There are two ways to improve performance: increase resources or narrow the scope. Increasing resources means using more CPU and memory and more computing power to shorten the execution time of tasks. Narrow it down to the task itself, such as eliminating redundant processes or not doing repetitive work.

3.2.1 exclude/include

For JS modules, the node_modules directory is usually excluded.

3.2.2 noParse

Some third-party libraries do not want WebPack to parse at all, that is, they do not want to apply any Loader rules and have no internal dependencies on other modules, so they can be ignored using noParse.

Ignore all modules whose file names include Lodash. These modules will still be packaged into the resource, but WebPack will not parse them.

// webpack.config.js
module.exports = {
  ...
  module: {
    noParse: /lodash/
}
Copy the code

3.2.3 IgnorePlugin

The IgnorePlugin plug-in is able to exclude modules completely, and excluded modules are not packaged into resource files even if they are referenced.

Moment.js is a time-processing library. It loads a lot of language packages for localization, which is generally not useful and takes up a lot of volume.

The root directory contains package.json, webpack.config.js, and SRC, and the SRC directory contains main.js.

// package.json{..."scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "moment": "^ 2.29.1"."webpack": "4.29.4"."webpack-cli": "3.2.3"}}// webpack.config.js
module.exports = {
  entry: "./src/main.js".output: {
    filename: "./[name].js",}}// src/main.js
import "moment";
console.log("main.js");
Copy the code

Run the build script package to view the output resources.

Modify webpack.config.js, where resourceRegExp matches resource files and contextRegExp matches search directories.

// webpack.config.js
const webpack = require("webpack");

module.exports = {
  entry: "./src/main.js".output: {
    filename: "./[name].js",},plugins: [
    new webpack.IgnorePlugin({
      resourceRegExp: /^\.\/locale$/,
      contextRegExp: /moment$/}})]Copy the code

Run the build script package again to see the output resources.

3.3 DllPlugin

Early Windows system due to the limited computer memory space and smaller problems appeared a kind of memory optimization method called dynamic link library, when a same subroutine is called multiple programs, in order to reduce memory consumption, this joke program can be stored as an executable file, when called by multiple programs in memory only to generate and use the same instance.

The DllPlugin builds on this idea by pre-compiling and packaging third-party modules, or modules that change infrequently, and then using them during the actual build process in a project. Pre-packaging also generates a list of modules, which will act as a link and index when engineering business modules are packaged.

Similar to Code Splitting, DllPlugin can be used to extract common modules, but there are also essential differences. Code Splitting is setting up specific rules and extracting modules based on those rules during the packaging process. DllPlugin completely separates vendor, has its own set of Webpack configuration and packages it independently, no longer handles it in the actual project construction, and can be directly used. Theoretically, DllPlugin can be faster in packaging than Code Splitting, but it also increases the complexity of configuration and resource management. It can be understood that DllPlugin replaces one packaging with two packaging, which is theoretically faster, the first packaging is for modules that don’t change often, and the second packaging is for business modules.

3.3.1 DLL packaging

The root directory contains webpack.config.js, package.json, and SRC, while SRC contains main.js and index.html.

// package.json{..."scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "vue": "^ 2.6.12." "."webpack": "4.29.4"."webpack-cli": "3.2.3"}}// src/main.js
import "vue";
console.log("main.js");
Copy the code

Start by creating a separate WebPack configuration file for the dynamic link library and name it webpack.dll.config.js. Filename is the name of the dynamic link library, output.path is the output path of the dynamic link library, and output.library must be the same as name in DllPlugin. In DllPlugin, path is the output path of the module list of the dynamic link library, and path is the value of the name field in the manifest.json file.

// webpack.dll.config.js
const path = require("path");
const webpack = require("webpack");

const dllAssetPath = path.join(__dirname, "dist"."dll");
const dllLibraryName = "dll";

module.exports = {
  entry: ["vue"].output: {
    path: dllAssetPath,
    filename: "dll.js".library: dllLibraryName
  },
  plugins: [
    new webpack.DllPlugin({
      name: dllLibraryName,
      path: path.join(dllAssetPath, "manifest.json")]}})Copy the code

Then configure package.json and add a script command.

// package.json{..."scripts": {
        "dll": "webpack --config webpack.dll.config.js"}}Copy the code

Running the DLL script command will generate dlL. js and manifest.json in the DLL folder in the dist directory. The variable name in dlL. js is output.library in the above configuration, and the name in manifest.json is the name in the above DllPlugin.

// dist/dll/dll.js
var dll = (function (params) {... }) (...).// dist/dll/manifest.json
{
  "name": "dll"."content": {... }}Copy the code

Finally, you need to link dlL.js in your business code.

// webpack.config.js
const path = require("path");
const webpack = require("webpack");

module.exports = {
  entry: "./src/main.js".output: {
    filename: "./[name].js"
  },
  plugins: [
    new webpack.DllReferencePlugin({
      manifest: require(path.join(__dirname, "dist/dll/manifest.json"))]}})Copy the code

The global variable DLL will be declared when the page is executed to dlL. js, and the manifest is equivalent to injecting the resource map of main.js. Main.js will first find the library named DLL through the name field. Therefore, output.library in webpack.dll.config.js must be the same as the name in DllPlugin.

// src/index.html
<html lang="zh-CN">
  <body>
    <p>hello world</p>
    <script src="./dll/dll.js"></script>
    <script src="./main.js"></script>
  </body>
</html>
Copy the code

3.3.2 rainfall distribution on 10-12 id problem

Look at manifest.json, where each module has an ID that increases in numerical order, and the main.js code references a numerical ID when referring to a module in dlL.js.

DLL. Js (built by DllPlugin), utils.j@ [chunkhash].js, main.js may exist after the project is packaged. DLL. Js contains vue, whose ID is 5. When trying to add more modules to dlL.js, the Vue id changes to 6 after the rebuild.

Since Utils also references the vue module, its chunkhash will change after the rebuild, but the content itself will not change, and the client user will only have to re-download the resource.

The solution to this problem is to use the HashedModuleIdsPlugin plug-in.

// webpack.dll.config.js
module.exports = {
  plugins: [
    new webpack.DllPlugin({
      ...
    }),
    new webpack.HashedModuleIdsPlugin()
  ]
}
Copy the code

3.4 the tree shaking

ES6 Module dependencies are built at compile time, not run time. Based on this feature, WebPack provides Tree Shaking, which detects modules that are not referenced in the project during packaging. This part of the code will never be executed, hence the name “dead code”.

Webpack marks this code and removes it from the final bundle when the resource is compressed.

The following Webpack package adds a tag to the bar function and then uses the compression tool to remove dead code.

// src/main.js
import { foo } from "./utils.js";
foo();

// src/utils.js
export function foo() {
  console.log("foo");
}

export function bar() {
  console.log("bar");
}
Copy the code

In the previous

The next article