Webpack中文:
代码分离 | webpack 中文文档

我们在使用像 Vue 这样的框架进行开发的时候会发现Vue所有的配置文件都是放在build文件夹下的,build文件夹主要存放的是编译的配置文件,所以我们也可以做出更改:

  1. --projectName
  2. ----build
  3. -----webconfig.common.js
  4. -----webconfig.dev.js
  5. -----webconfig.prod.js

但是现在当运行 npm run build 的时候会发现dist文件夹被打包到了build的目录下,这是因为我们把配置文件放到了build的文件夹下却没有对配置做出变更,而配置文件依然执行的是当前目录下(也就是和build平级的目录)。

  1. // ...
  2. module.exports = {
  3. output: {
  4. filename: "main.js",
  5. // 输出到上级目录
  6. path: path.resolve(__dirname, "../dist"),
  7. },
  8. // ...
  9. }

场景

lodash是一个功能集合的工具包,它提供了很多方法方便开发者高效的进行开发,我们在这里也是用一下这个代码库。

安装:

  1. $ npm install loadsh --save

index.js中进行使用:

  1. import _ from "lodash";
  2. console.log(_.join(["a", "b", "c"], "--"));

join方法接收了三个字符串的数组且用 — 进行拼接,这个时候进行npm run build进行项目打包的时候就会发现Webpacklodash和业务代码都被打包到了main.js文件中。

假设我们的业务代码非常的庞大,比如:

  1. import _ from "lodash";
  2. console.log(_.join(["a", "b", "c"], "--"));
  3. // 此处假设有 100 行业务代码

这个再进行打包肯定还会把loadsh库和业务逻辑统一打包到main.js文件中,但是这样存在一个潜在的问题就是文件非常的庞大,每次更改业务逻辑代码后都需要重新进行打包,用户就需要重新加载这庞大的mian.js文件,相应的加载时间就会很长,另外loadsh库我们基本是不会进行更改的,所以我们不希望每次更改src/index.js文件loadsh也重新进行打包。

手动进行文件分割

新建loadsh.js文件,把loadsh挂在到window对象上:

  1. import _ from "lodash";
  2. window._ = _;

index.js只负责我们的业务逻辑:

  1. console.log(_.join(["a", "b", "c"], "--"));
  2. // 此处假设有 1000 行业务代码

在配置文件中新增一个打包入口loadsh

  1. // ...
  2. module.exports = {
  3. entry: {
  4. main: "./src/index.js",
  5. // 新增打包入口 lodash
  6. loadsh: "./src/loadsh.js"
  7. },
  8. output: {
  9. filename: "[name].js",
  10. path: path.resolve(__dirname, "../dist"),
  11. },
  12. // ...
  13. }

新增一个build:test脚本命令,配置一个开发环境的打包(因为dev命令打包生产的dist文件夹是在电脑内存当中我们看不到):

  1. {
  2. // ...
  3. "scripts": {
  4. "dev": "webpack serve --config ./build/webpack.dev.js",
  5. "build:test": "webpack --config ./build/webpack.dev.js",
  6. "build": "webpack --config ./build/webpack.prod.js"
  7. }
  8. // ...
  9. }

此时我们再进行打包就会看到index.jsloadsh.js就分别被打包了出来了:

  1. projectName
  2. ├─babel.config.js
  3. ├─package-lock.json
  4. ├─package.json
  5. ├─postcss.config.js
  6. ├─tree.txt
  7. ├─src
  8. | ├─index.html
  9. | ├─index.js
  10. | loadsh.js
  11. ├─dist
  12. | ├─index.html
  13. | ├─loadsh_2d3d6f255a1e834f9349.js
  14. | main_2d3d6f255a1e834f9349.js
  15. ├─build
  16. | ├─webpack.common.js
  17. | ├─webpack.dev.js
  18. | webpack.prod.js

这个时候用户访问页面的时候浏览器就会加载两个js文件,且两个JS文件会同时加载,这样会比加载一个JS文件快(非绝对性),当我们修改src/index.js文件的业务代码后,用户就只加载新的main.js文件既可。

splitchunksplugin 配置

Webpack中文:
代码分离 | webpack 中文文档

虽然代码被分开打包了但是这时还有个问题,就是每次index.js文件被打包后浏览器都需要重新加载两个文件(index.jsloadsh.js),这很显示是没有必要的。

那什么是Code Splitting?它是一种用于代码分割的工具。
其实在Webpack之前,我们可以手动的进行代码分割这样也能提高项目的性能,所以Code SplittingWebpack没有本质的关系,而现在webpack4中已经绑定了Code Splitting的插件 SplitChunksPlugin

新增optimization.splitChunks配置项:

  1. module.exports = {
  2. // ...
  3. // optimization表示优化
  4. optimization: {
  5. usedExports: true,
  6. splitChunks: {
  7. // 默认配置为 chunks: 'acync',只分割异步加载的代码
  8. chunks: 'all',
  9. },
  10. },
  11. // ...
  12. }

这样webpack就自动帮我们进行了代码分割。
image.png
可以看到打包后dist文件夹有三个文件,main.js文件是src/index.js文件的业务逻辑,vendors~main.js文件是对lodash库的分割打包。

例如我们再新增一个src/math.js文件进行引入:

  1. import _ from "lodash";
  2. import math from "./math.js";
  3. const res = _.join([1, 2, 3, 4, 5, 6]);
  4. console.log(res);

接着再进行打包,我们发现dist文件夹下并没有多出math.js文件,而是把math.js的逻辑打包到了main.js文件中:
image.png :::info 可见Webpack并不是每引入一个文件就会打包产出一个单独的文件,它由一个默认配置来决定的,也就是根据optimization.splitChunks来决定如何进行分割。 :::

异步引入

以上的配置是基于同步代码引入实现的代码分割,如何实现异步的代码分割呢?
假如我们在index.js文件异步引入lodash包:

  1. function getComponent() {
  2. // 之所以需要 default,是因为 Webpack4 在导入 CommonJS 模块时,将不再解析为 module.exports 的值
  3. // 而是为 CommonJS 模块创建一个 artificial namespace 对象
  4. return import("lodash").then(({ default: _ }) => {
  5. console.log(_.join(["a", "b", "c"], "--"));
  6. })
  7. }
  8. getComponent();

此时运行npm run build:test打包就会看到异步加载的lodash代码被分割了出来。
image.png
其中0.js就是对lodash文件异步加载的分割产出,main.js文件是src/index.js业务逻辑的产出,vendors~main.js是对lodash同步加载的产出(个人认为,此说法不一定准确)。

其实做异步代码分割的时候是不需要配置splitChunks的,这是因为optimization.splitChunks.chunls的默认值就是async,表示异步加载,例如我们删除optimization.splitChunks.chunls的配置:

  1. module.exports = {
  2. // ...
  3. // optimization表示优化
  4. optimization: {
  5. usedExports: true,
  6. // 删除 splitChunks
  7. // splitChunks: {
  8. // 默认配置为 chunks: 'acync',只分割异步加载的代码
  9. // chunks: 'all',
  10. // },
  11. },
  12. // ...
  13. }

image.png