webpack tree-shaking & 动态导入

576 阅读2分钟

1. 前论

webpack 的树摇功能早就深入人心,而 动态导入也随着 vue & reactsplitChunks 在各个页面中进入了应用 在 webpack.5 中的 production 模式下,各种优化配置已经自动开启,其中就包括了 splitChunks

但是在学习这两者的过程中,我也产生了一个疑问,

  1. tree-shaking 是基于 esmodule 的功能,因为 esmodule 是静态结构,可以直接分析导入和导出的关系,从而去除没有被使用的“死代码”
  2. 动态导入的语法为 import(xxxx),也是 esmodule 语法的一部分
  3. 所以 tree-shakingimport(xxxx) 会不会产生某种化学反应,

先说结论: 正如第一段所说,import(xxxx)splitChunks 的作用,作为入口文件,默认不走 tree-shaking 的逻辑

2. 代码演示

1. 单独配置 tree-shaking

// webpack.prod.js

const CopyWebpackPlugin = require('copy-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
  /** 方便查看打包结果,使用 development 模式 */
  mode: 'development',
  devtool: false,
  optimization: {
    usedExports: true,
    minimize: true,
    minimizer: [
      new TerserPlugin({
        extractComments: false,
      }),
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new CopyWebpackPlugin({
      patterns: [
        {
          from: 'public',
          globOptions: {
            ignore: ['**/index.html']
          }
        }
      ]
    }),
  ]
}

// index.js
import { foo1 } from './utils/utils.js'

console.log(foo1(10))

// utils.js
export function foo1(num) {
  return console.log('foo1', num)
}

export function foo2(num) {
  return console.log('foo2', num)
}

使用以上代码以及配置执行 build,查看结果

image.png image.png

通过以上两张截图可以发现,utils 的 foo2 函数被 删除 tree-shaking 配置成功

2. 配置 tree-shaking & import(xxx)

webpack 配置不变, index.js 代码做修改

const button = document.createElement('button');
document.body.appendChild(button);
button.textContent = '点我'

button.addEventListener('click', () => {
    import('./utils/utils').then(({foo1}) => {
        console.log('res====', foo1);
    }) 
})

使用以上代码以及配置执行 build,查看结果

image.png

index.2c5c8666.bundle.js

image.png

src_utils_utils_js.2927d5b7.bundle.js

image.png

虽然在 index 中 只使用了 foo1 但是 foo2 也依旧被打包进入,而且在 index 中的使用方式也是 n.foo1 (这里的 n 是 webpack 生成代码之后的 module,包含了导入文件信息)

3. 部分动态导入 & 配置 tree-shaking & import(xxx)

当前代码目录结构如下:

image.png

// utils2.js
export function foo3(num) {
  return console.log('foo3', num)
}

export function foo4(num) {
  return console.log('foo4', num)
}

// index.js
const button = document.createElement('button');
document.body.appendChild(button);
button.textContent = '点我'
const input = document.createElement('input');
document.body.appendChild(input);

button.addEventListener('click', () => {
    const path = input.value;
    import(`./utils/${path}`).then(res => {
        console.log('res====', res);
    }) 
})

使用以上代码以及配置执行 build,查看结果

image.png

其实光从截图 + 上文的分析,就可以看出,webpack 无脑地将 utils 目录下的 所有文件都进行了一个打包,并分别为不同的 chunk 模块

4. 全动态导入 & 配置 tree-shaking & import(xxx)

// index.js
const button = document.createElement('button');
document.body.appendChild(button);
button.textContent = '点我'
const input = document.createElement('input');
document.body.appendChild(input);

button.addEventListener('click', () => {
    const path = input.value;
    import(path).then(res => {
        console.log('res====', res);
    }) 
})

其实不用我截图,到了这一步,所有人也能猜出 webpack 会怎么打包,那就是.....

不打包 image.png

而输入 utils 模块尝试导入的 做法也会导致报错

image.png

结论就是,基础终究是没学扎实。。。