# 代码分离

每个入口通常打包出来的都会是一个体积巨大的文件,这样其实时不合理的,我们需要根据功能、复用性、文件大小等角度取分割代码成若干个文件。优先加载需要的模块,而不是一次性加载一个巨大的文件。

当有多个入口文件,且引入同个公共模块时,编译时会重复打包该模块。例如:

// index.js
import _ from "lodash";
console.log("index:" + _.join(["www", "hxin", "link"], "."));

// print.js
import _ from "lodash";
console.log("print:" + _.join(["杨", "信", "鹏"], ""));
1
2
3
4
5
6
7








 
 



 











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",
    print: "./src/print.js",
  },
  output: {
    // [name]:入口的key
    filename: "[name].bundle.js",
    path: path.resolve(__dirname, "dist"),
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: "Webpack example",
      template: "./index.html",
    }),
  ],
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

提示

如果不清楚html-webpack-pluginclean-webpack-plugin,请查阅管理输出

打包结果如下:



 

 


···
          Asset       Size  Chunks             Chunk Names
index.bundle.js    554 KiB   index  [emitted]  index
     index.html  317 bytes          [emitted]
print.bundle.js    554 KiB   print  [emitted]  print
···
1
2
3
4
5
6

index.bundle.jsprint.bundle.js都包含了lodash的代码。显然我们需要将lodash提取出成共享的文件,即使这会变成异步加载。

还有就是有些引入的模块可能到应用结束都不会使用,例如:以下代码,用户打开页面过了一个小时,就会显示消息。

// index.js
import _ from "lodash";

function lookTimer(timer) {
  const url = _.join(["www", "hxin", "link"], ".");
  console.log(`你已经在${url},待了${timer}秒!`);
}

setTimeout(() => {
  lookTimer(60 * 60);
}, 60 * 60 * 1000);
1
2
3
4
5
6
7
8
9
10
11


 







module.exports = {
  mode: "development",
  entry: "./src/index.js",
  output: {
    filename: "[name].bundle.js",
    path: path.resolve(__dirname, "dist"),
  },
  // ...
};
1
2
3
4
5
6
7
8
9
···
         Asset       Size  Chunks             Chunk Names
    index.html  277 bytes          [emitted]
main.bundle.js    554 KiB    main  [emitted]  main
···
1
2
3
4
5

已经将index.js的逻辑和lodash模块打包成一个index.bundle.js。但是里面的lodash模块可能是应用不上的,因为用户可能在一小时内就把页面关闭了,这会增加加载时间,降低用户体验。理想的状态下是将loadsh分离出来,当真正需要时才开始加载该模块。

接下来介绍三种方式进行代码分离:

  1. 入口分割
  2. 防止重复
  3. 动态导入

# 入口分割

注意

webpack v5 支持,webpack v4 不支持

entry里,通过dependOn属性来人为控制。

module.exports = {
  entry: {
    // 共享模块
    index: { import: "./index.js", dependOn: "shared" },
    print: { import: "./print.js", dependOn: "shared" },
    shared: "lodash",
  },
  output: {
    filename: "[name].bundle.js",
    path: path.resolve(__dirname, "dist"),
  },
};
1
2
3
4
5
6
7
8
9
10
11
12

# SplitChunksPlugin

通过optimization属性来配置。

module.exports = {
  // ...
  optimization: {
    splitChunks: {
      // 入口文件、动态引入模块均优化、分割,且之间可以共享块
      chunks: "all",
      // 优化入口文件优化、分割
      // chunks: "initial"
      // 动态引入模块优化、分割
      // chunks: "async"
    },
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13





 


···
                        Asset       Size               Chunks             Chunk Names
              index.bundle.js   6.96 KiB                index  [emitted]  index
                   index.html  370 bytes                       [emitted]
              print.bundle.js   6.95 KiB                print  [emitted]  print
vendors~index~print.bundle.js    550 KiB  vendors~index~print  [emitted]  vendors~index~print
···
1
2
3
4
5
6
7

很明显已经将index.jsprint.js的依赖模块提取出来成单独的文件了,与之前对比,减少了一半的体积。

注意

CommonsChunkPlugin (opens new window)已经从webpack v4 legato中移除了。

# 动态导入

使用 ES6 的语法import(),返回一个Promise。如果你的浏览器不支持Promise,那么还需要polyfill


 


 


 






// index.js
// import _ from "lodash";

function lookTimer(timer) {
  import("lodash").then(({ default: _ }) => {
    const url = _.join(["www", "hxin", "link"], ".");
    console.log(`你已经在${url},待了${timer}秒!`);
  });
}

setTimeout(() => {
  lookTimer(60 * 60);
}, 60 * 60 * 1000);
1
2
3
4
5
6
7
8
9
10
11
12
13


 

 


···
         Asset       Size  Chunks             Chunk Names
   0.bundle.js    550 KiB       0  [emitted]
    index.html  277 bytes          [emitted]
main.bundle.js   8.74 KiB    main  [emitted]  main
···
1
2
3
4
5
6

动态加载的模块都会分割出去,可以合理加载模块来降低加载时间,提高用户体验。

可以使用注释对模块做一些调整,下面列举常用参数,如果感兴趣可以查看更多参数 (opens new window)

// 将单独包命名,而不是[id].bundle.js,可以命名成lodash.bundle.js
/* webpackChunkName: "lodash" */
// 预取:将来可能需要一些导航资源
/* webpackPrefetch: true */
// 预加载:当前导航期间可能需要资源
/* webpackPreload: true */
1
2
3
4
5
6




 











// import _ from "lodash";

function lookTimer(timer) {
  return import(
    /* webpackChunkName: "lodash" */
    "lodash"
  ).then(({ default: _ }) => {
    const url = _.join(["www", "hxin", "link"], ".");
    console.log(`你已经在${url},待了${timer}秒!`);
  });
}

setTimeout(() => {
  lookTimer(60 * 60);
}, 60 * 60 * 1000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15




 


···
                   Asset       Size          Chunks             Chunk Names
              index.html  277 bytes                  [emitted]
          main.bundle.js    8.8 KiB            main  [emitted]  main
vendors~lodash.bundle.js    550 KiB  vendors~lodash  [emitted]  vendors~lodash
···
1
2
3
4
5
6