背景
项目全量引入了Ant-Design-Vue,需要修改为按需引入,需要统计项目中使用了哪些Ant-Design-Vue组件。不想肉眼去搜索,所以写一个webpack插件,去统计。
在开发中,有考虑写babel插件去处理,但探索的过程中发现Vue单文件的template部分不经过babel-loader处理。
webpack 插件基本结构
class WebpackCustomPlugin {
apply(compiler) {
// ...
}
}
确定执行时机
通过多次尝试,最终选择在compiler的compilation钩子、compilation的succeedModule钩子下执行。
如果在compilation的buildModule钩子下执行,此时的resource._source为null,resource._source为模块对应的代码;
在compilation的succeedModule钩子下执行,此时的temple模版已经被Vue编译成了render函数。
class WebpackCustomPlugin {
apply(compiler) {
complier.hooks.compilation.tap('CustomPluginCompiler', ( compilation, compilationParams) => {
compilation.hooks.succeedModule.tap('CustomPluginCompilation', (module) => {
// ...
})
})
}
}
确定执行范围
我们不需要处理所有的模块,通过一些判断缩小处理范围。
compilation.hooks.succeedModule.tap的参数是module;
通过 if (module.resource.includes('node_modules')) return
排除node_modules下的模块;
通过 if (!module.resource.includes('.vue?vue&type=template')) return
排除不是template模块的代码。
获取模块对应的脚本
通过 module._source._value
获取
使用 @babel/parser 转换为ast
const source = module._source._value
const ast = require('@babel/parse').parse(source, {
sourceType: 'module'
})
使用 @babel/traverse 遍历AST
const traverse = require('@babel/traverse')
traverse(ast, {
// ...
})
通过 astexplorer 辅助获取节点类型
在这里我们需要选择@babel/parser,astexplorer默认是acorn,它们转换的结果不一样。
astexplorer: astexplorer.net/
babel types: babeljs.io/docs/babel-…
统计Ant组件的核心代码
const _this = this
traverse(ast, {
StringLiteral(path) {
const tag = path.node.value
if (tag.startsWith('a-')) {
// #tags是在类中定义的私有属性,用来存储所有用到的Ant组件
if (_this.#tags.indexOf(tag) === -1) {
_this.#tags.push(tag)
}
}
}
})
在新的compiler钩子下输出统计结果
compiler.hooks.afterCompile.tap('afterCompile', () => {
console.log(this.#tags)
})
最后#tags输出为:
['a-alert', 'a-table']
优化输出:
compiler.hooks.afterCompile.tap('afterCompile', () => {
const tags = this.#tags.map(tag => {
return tag.split('-').slice(1).map(item => item.charAt(0).toUpperCase() + item.slice(1))
console.log(tags)
})
})
去除无效组件
在开发过程中发现tags收集的部分tag,不是对外的组件,不需要导入,比如FormModelItem,只需要导入FormModel就可以了。所以需要过滤。通过判断是否是有效路径来过滤。
const fs = require('fs')
const path = require('path')
function isValidPath(tag) {
let folderName = tag.split('-').slice(1).join('-')
let fulPath = `node_modules/ant-design-vue/es/${folderName}`
fullPath = path.resolve(process.cmd(), fullPath)
try {
fs.statSync(fullPath)
return true
} catch(error) {
return false
}
}
实现Ant-Design-Vue按需加载
import {
// 复制粘贴上面tags打印的结果
}
from 'ant-design-vue'
const components = [ // 复制粘贴上面tags打印的结果 ]
components.forEach(component => Vue.use(component))
上面这种方式导入,依然会全量导入,需要配合babel-plugin-import 来进行按需加载。