什么是按需打包

Webpack 在打包的过程中会经过一个 tree shaking 的阶段,有两个作用

  1. 模块的按需打包
  2. 删除死代码(dead code elimination)

那么按需打包,它的原理是什么?
答:利用 ES6 模块语法的特点:编译时加载模块的内容,而不是像 CommonJs 是运行时加载模块内容。
在编译打包模块时,将非默认引用都转换为默认引用。

  1. // 默认引用
  2. import _ from 'lodash-es'
  3. // 非默认引用
  4. import { cloneDeep } from 'lodash-es'
  1. // 打包前引入模块
  2. import { cloneDeep, debounce } from 'lodash-es'
  3. // 打包后引入模块
  4. import cloneDeep from 'lodash-es/cloneDeep.js'
  5. import debounce from 'lodash-es/debounce.js'

:::info 如果在开发组件库或工具库时,我们可以尽量按功能进行分包,并且采用 ES6 的模块语法。
用户使用工具库开发业务项目时,构建工具在打包时,才可以利用这种特点,对库的功能进行按需引入,减少项目的体积。 :::

如何实现按需打包

利用 AST 对 import语句进行内容修改,将非默认引用,都改为默认引用。

从下面例子可以看出,设置文件引入路径,这个和库的文件路径设置有关。 在开发组件库或工具库时,可以在 package.json 下设置文件模块的入口。

  1. const babel = require('@babel/core')
  2. const t = require('@babel/types')
  3. // 模块的按需加载
  4. const source = `
  5. import { cloneDeep, debounce } from 'lodash-es'
  6. `
  7. const importPlugin = {
  8. visitor: {
  9. ImportDeclaration: function (path) {
  10. const { node } = path
  11. const pathName = node.source.value
  12. let specifiers = node.specifiers
  13. if (!t.isImportDefaultSpecifier(specifiers[0])) {
  14. specifiers = specifiers.map(specifier => {
  15. return t.importDeclaration(
  16. [t.importDefaultSpecifier(specifier.local)],
  17. t.stringLiteral(`${pathName}/${specifier.local.name}`) // 设置文件引入路径
  18. )
  19. })
  20. path.replaceWithMultiple(specifiers)
  21. }
  22. }
  23. }
  24. }
  25. const result = babel.transform(source, {
  26. plugins: [
  27. importPlugin
  28. ]
  29. })
  30. console.log(result.code)