原稿:语雀 · 代码分离 · 原目录:Webpack › 官方指南学习 › 代码分离

本篇的代码使用 webpack doc:代码分离提供的 demo

什么是代码分离:把代码分离到不同的 bundle 中,然后按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间

常用的代码分离方法有三种:

  • 入口起点:使用 entry 配置手动地分离代码
  • 防止重复:使用 Entry dependencies 或者 SplitChunksPlugin 去重和分离 chunk
  • 动态导入:通过模块的内联函数调用来分离代码

入口起点(entry point)

这种方法即在出口起点提供额外的出口,在其中放入要分割的代码

创建 src/another-module.js 后修改配置文件

another-module.js

import _ from 'lodash';
console.log(_.join(['Another', 'module', 'loaded!'], ' '));

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

module.exports = {
  mode: "development",
  entry: {
    index: "./src/index.js",
    another: "./src/another-module.js",
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: "Development",
    }),
  ],
  output: {
    filename: "[name].bundle.js",
    path: path.resolve(__dirname, "dist"),
  },
};

这种方法下

  • 如果入口 chunk 之间包含一些重复的模块,那些重复模块都会被引入到各个 bundle 中。比如 index.js 和 another-module.js 中都引入了 lodash
  • 这种方法不够灵活,并且不能动态地将核心应用程序逻辑中的代码拆分出来

防止重复(prevent duplication)

入口依赖

配置 dependOn option 选项,这样可以在多个 chunk 之间共享模块:

下面这段代码将 lodash 作为共享模块,避免重复添加。如果要在一个 HTML 页面上使用多个入口时,还需设置 optimization.runtimeChunk: ‘single’

  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',
  },

+  optimization: {
+    runtimeChunk: 'single',
+  },

尽管可以在 webpack 中允许每个页面使用多入口,应尽可能避免使用多入口的入口,这样在使用 async 脚本标签时,会有更好的优化以及一致的执行顺序。

image.png

SplitChunksPlugin 

SplitChunksPlugin 插件可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。

修改配置文件的 optimization

  optimization: {
    splitChunks: {
            chunks: 'all',
     },
  },

执行 build,此时 index.bundle.js 和 another.bundle.js 中已经移除了重复的依赖模块。插件将 lodash 分离到单独的 chunk,并且将其从 main bundle 中移除,减轻了大小。

image.png

动态导入(dynamic import) 

webpack 提供了两个类似的技术以支持动态代码拆分

第一种,也是推荐选择的方式是,使用符合 ECMAScript 提案 的 import() 语法 来实现动态导入

第二种,则是 webpack 的遗留功能,使用 webpack 特定的 require.ensure

import 语法

首先还原配置文件,移除掉多余的 entry 和 optimization.splitChunks,移除 src/ another-module.js;接下来修改 index.js,不再使用 statically import(静态导入) lodash,而是通过 dynamic import(动态导入) 来分离出一个 chunk:

function getComponent() {  return import("lodash")    .then(({ default: _ }) => {      const element = document.createElement("div");      element.innerHTML = _.join(["Hello", "webpack"], " ");      return element;    })    .catch((error) => "An error occurred while loading the component");}getComponent().then((component) => {  document.body.appendChild(component());});

执行 build,此时 lodash 被分割到一个单独的 bundle

image.png

import() 会返回一个 promise,因此它可以和 async 函数一起使用,简化代码

async function getComponent() {
  const element = document.createElement("div");
  const { default: _ } = await import("lodash");
  element.innerHTML = _.join(["Hello", "webpack"], " ");
  return element;
}

getComponent().then((component) => {
  document.body.appendChild(component());
});