什么是按需打包
Webpack 在打包的过程中会经过一个 tree shaking 的阶段,有两个作用
- 模块的按需打包
- 删除死代码(dead code elimination)
那么按需打包,它的原理是什么?
答:利用 ES6 模块语法的特点:编译时加载模块的内容,而不是像 CommonJs 是运行时加载模块内容。
在编译打包模块时,将非默认引用都转换为默认引用。
// 默认引用
import _ from 'lodash-es'
// 非默认引用
import { cloneDeep } from 'lodash-es'
// 打包前引入模块
import { cloneDeep, debounce } from 'lodash-es'
// 打包后引入模块
import cloneDeep from 'lodash-es/cloneDeep.js'
import debounce from 'lodash-es/debounce.js'
:::info
如果在开发组件库或工具库时,我们可以尽量按功能进行分包,并且采用 ES6 的模块语法。
用户使用工具库开发业务项目时,构建工具在打包时,才可以利用这种特点,对库的功能进行按需引入,减少项目的体积。
:::
如何实现按需打包
利用 AST 对 import
语句进行内容修改,将非默认引用,都改为默认引用。
从下面例子可以看出,设置文件引入路径,这个和库的文件路径设置有关。 在开发组件库或工具库时,可以在 package.json 下设置文件模块的入口。
const babel = require('@babel/core')
const t = require('@babel/types')
// 模块的按需加载
const source = `
import { cloneDeep, debounce } from 'lodash-es'
`
const importPlugin = {
visitor: {
ImportDeclaration: function (path) {
const { node } = path
const pathName = node.source.value
let specifiers = node.specifiers
if (!t.isImportDefaultSpecifier(specifiers[0])) {
specifiers = specifiers.map(specifier => {
return t.importDeclaration(
[t.importDefaultSpecifier(specifier.local)],
t.stringLiteral(`${pathName}/${specifier.local.name}`) // 设置文件引入路径
)
})
path.replaceWithMultiple(specifiers)
}
}
}
}
const result = babel.transform(source, {
plugins: [
importPlugin
]
})
console.log(result.code)