1. 代码分割 #

2. 入口点分割 #

{
  entry: {
   page1: "./src/page1.js",
   page2: "./src/page2.js"
  }
}

https://static.zhufengpeixun.com/http_xie_yi_rfc2616_zhong_ying_wen_shuang_ban_1637749432228.zip

3 动态导入和懒加载 #

3.1 hello.js #

hello.js

module.exports = "hello";

index.js

document.querySelector('#clickBtn').addEventListener('click',() => {
    import('./hello').then(result => {
        console.log(result.default);
    });
});

index.html

<button id="clickBtn">点我</button>

3.2 preload(预先加载) #

$ npm install --save-dev @vue/preload-webpack-plugin

prefetchpreload

<link rel="preload" as="script" href="utils.js">
import(
  `./utils.js`
  /* webpackPreload: true */
  /* webpackChunkName: "utils" */
)

3.3 prefetch(预先拉取) #

<link rel="prefetch" href="utils.js" as="script">
button.addEventListener('click', () => {
  import(
    `./utils.js`
    /* webpackPrefetch: true */
    /* webpackChunkName: "utils" */
  ).then(result => {
    result.default.log('hello');
  })
});

3.4 preload vs prefetch #

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="prefetch" href="prefetch.js?k=1" as="script">
    <link rel="prefetch" href="prefetch.js?k=2" as="script">
    <link rel="prefetch" href="prefetch.js?k=3" as="script">
    <link rel="prefetch" href="prefetch.js?k=4" as="script">
    <link rel="prefetch" href="prefetch.js?k=5" as="script">

</head>
<body>

</body>
<link rel="preload"  href="preload.js" as="script">
</html>

3.5 preload-webpack-plugin.js #

plugins\preload-webpack-plugin.js

const HtmlWebpackPlugin = require('html-webpack-plugin');
/**
 * 1.查找当前产出代码块有哪些异步代码块
 * 2.针对每个异步代码块生成一个link标签
 * 3.把生成的link标签插入到结果的HTML文件中
 */
class PreloadWebpackPlugin {
  constructor(options) {
    this.options = options;
  }
  apply(compiler) {
    //<link href="title.js" rel="prefetch"></link><
    compiler.hooks.compilation.tap('PreloadWebpackPlugin', (compilation) => {
      //在准备生成资源标签之前执行
      HtmlWebpackPlugin.getHooks(compilation).beforeAssetTagGeneration.tapAsync(
        'PreloadWebpackPlugin',
        (htmlData, callback) => {
          this.generateLinks(compilation, htmlData, callback);
        }
      );
    });
    compiler.hooks.compilation.tap('PreloadWebpackPlugin', (compilation) => {
      //在准备生成资源标签之前执行
      HtmlWebpackPlugin.getHooks(compilation).alterAssetTags.tap(
        'PreloadWebpackPlugin',
        (htmlData) => {
          const { resourceHints } = this;
          if (resourceHints) {
            htmlData.assetTags.styles = [
              ...resourceHints,
              ...htmlData.assetTags.styles
            ]
          }
          return htmlData;
        }
      );
    });

  }
  generateLinks(compilation, htmlData, callback) {
    const { rel, include } = this.options;
    //本次编译产出的代码块
    let chunks = [...compilation.chunks];
    //如果说包括的是异步的代码块
    if (include === undefined || include === 'asyncChunks') {
      //如果chunk.canBeInitial()为true,说明这是一个入口代码块 main.canBeInitial()
      //过滤一下,只留下异步代码块
      chunks = chunks.filter(chunk => !chunk.canBeInitial());
    }
    let allFiles = chunks.reduce((accumulated, chunk) => {
      return accumulated.add(...chunk.files);
    }, new Set());
    const links = [];
    for (const file of allFiles.values()) {
      links.push({
        tagName: 'link',
        attributes: {
          rel,//preload prefetch
          href: file
        }
      });
    }
    this.resourceHints = links;
    callback()
  }
}

module.exports = PreloadWebpackPlugin;

4. 提取公共代码 #

4.1 为什么需要提取公共代码 #

4.2 如何提取 #

4.3 module chunk bundle #

4.4 splitChunks #

splitChunks

4.4.1 webpack.config.js #

const HtmlWebpackPlugin = require('html-webpack-plugin');
const AssetPlugin = require('./asset-plugin');
module.exports = {
    mode: 'development',
    devtool: false,
    entry: {
        page1: "./src/page1.js",
        page2: "./src/page2.js",
        page3: "./src/page3.js",
    },
    optimization: {
        splitChunks: {
            // 表示选择哪些 chunks 进行分割,可选值有:async,initial和all
            chunks: 'all',
            // 表示新分离出的chunk必须大于等于minSize,默认为30000,约30kb。
            minSize: 0,//默认值是20000,生成的代码块的最小尺寸
            // 表示一个模块至少应被minChunks个chunk所包含才能分割。默认为1。
            minChunks: 1,
            // 表示按需加载文件时,并行请求的最大数目。默认为5。
            maxAsyncRequests: 3,
            // 表示加载入口文件时,并行请求的最大数目。默认为3
            maxInitialRequests: 5,
            // 表示拆分出的chunk的名称连接符。默认为~。如chunk~vendors.js
            automaticNameDelimiter: '~',
            cacheGroups: {
                defaultVendors: {
                    test: /[\\/]node_modules[\\/]/, //条件
                    priority: -10 ///优先级,一个chunk很可能满足多个缓存组,会被抽取到优先级高的缓存组中,为了能够让自定义缓存组有更高的优先级(默认0),默认缓存组的priority属性为负值.
                },
                default: {
                    minChunks: 2,////被多少模块共享,在分割之前模块的被引用次数
                    priority: -20
                },
            },
        },
        runtimeChunk: true
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html',
            chunks: ["page1"],
            filename: 'page1.html'
        }),
        new HtmlWebpackPlugin({
            template: './src/index.html',
            chunks: ["page2"],
            filename: 'page2.html'
        }),
        new HtmlWebpackPlugin({
            template: './src/index.html',
            chunks: ["page3"],
            filename: 'page3.html'
        }),
        new AssetPlugin()
    ]
}

4.4.2 webpack-assets-plugin.js #

plugins\webpack-assets-plugin.js

class WebpackAssetsPlugin {
  constructor(options) {
    this.options = options;
  }
  apply(compiler) {
    //每当webpack开启一次新的编译 ,就会创建一个新的compilation
    compiler.hooks.compilation.tap('WebpackAssetsPlugin', (compilation) => {
      //每次根据chunk创建一个新的文件后会触发一次chunkAsset
      compilation.hooks.chunkAsset.tap('WebpackAssetsPlugin', (chunk, filename) => {
        console.log(chunk.id, filename);
      });
    });
  }
}
module.exports = WebpackAssetsPlugin;

4.4.3 page1.js #

let module1 = require('./module1');
let module2 = require('./module2');
let $ = require('jquery');
console.log(module1,module2,$);
import( /* webpackChunkName: "asyncModule1" */ './asyncModule1');

4.4.4 page2.js #

let module1 = require('./module1');
let module2 = require('./module2');
let $ = require('jquery');
console.log(module1,module2,$);

4.4.5 page3.js #

let module1 = require('./module1');
let module3 = require('./module3');
let $ = require('jquery');
console.log(module1,module3,$);

4.4.6 module1.js #

module.exports = 'module1';

4.4.7 module2.js #

console.log("module2");

4.4.8 module3.js #

console.log("module3");

4.4.9 asyncModule1.js #

import _ from 'lodash';
console.log(_);

4.4.10 打包后的结果 #

//入口代码块
page1.js
page2.js
page3.js
//异步加载代码块
src_asyncModule1_js.js
//defaultVendors缓存组对应的代码块
defaultVendors-node_modules_jquery_dist_jquery_js.js
defaultVendors-node_modules_lodash_lodash_js.js
//default代缓存组对应的代码块
default-src_module1_js.js
default-src_module2_js.js

4.4.11 计算过程 #

let page1Chunk= {
    name:'page1',
    modules:['A','B','C','lodash']
}

let page2Chunk = {
    name:'page2',
    module:['C','D','E','lodash']
}

let  cacheGroups= {
    vendor: {
      test: /lodash/,
    },
    default: {
      minChunks: 2,
    }
};

let vendorChunk = {
    name:`vendor~node_modules_lodash_js`,
    modules:['lodash']
}
let defaultChunk = {
    name:`default~page1~page2`,
    modules:['C']
}

4.5 reuseExistingChunk #

4.5.1 index.js #


4.5.2 webpack.config.js #

const HtmlWebpackPlugin = require('html-webpack-plugin');
const AssetPlugin = require('./asset-plugin');
module.exports = {
    mode: 'development',
    devtool: false,
+   entry: './src/index.js',
    optimization: {
        splitChunks: {
            // 表示选择哪些 chunks 进行分割,可选值有:async,initial和all
            chunks: 'all',
            // 表示新分离出的chunk必须大于等于minSize,默认为30000,约30kb。
            minSize: 0,//默认值是20000,生成的代码块的最小尺寸
            // 表示一个模块至少应被minChunks个chunk所包含才能分割。默认为1。
            minChunks: 1,
            // 表示按需加载文件时,并行请求的最大数目。默认为5。
            maxAsyncRequests: 3,
            // 表示加载入口文件时,并行请求的最大数目。默认为3
            maxInitialRequests: 5,
            // 表示拆分出的chunk的名称连接符。默认为~。如chunk~vendors.js
            automaticNameDelimiter: '~',
+           cacheGroups: {
+               defaultVendors: false,
+               default: false,
+               common: {
+                   minChunks: 1,
+                   reuseExistingChunk: false
+               }
+           }
        },
+       runtimeChunk: false
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: 'index.html'
        })
        new AssetPlugin()
    ]
}

4.5.3 结果 #

//reuseExistingChunk: false
main main.js
common-src_index_js common-src_index_js.js

//reuseExistingChunk: true
main main.js