wepack 支持externals,比如 react 不使用 npm 包,使用window 中的 React。找了比较多的库,发现vite-plugin-externals库实现不错,思路清晰,使用简单。那么是怎么实现的呢。核心原理如下:
// 源代码
import Vue from 'vue'
// 转换后
const Vue = window['Vue']
怎么做到转换的呢,主要是用了两个库, ES Module Lexer 和 Acorn
找出 import 语句
为什么使用ES Module Lexer呢,因为速度快,先做一次初筛,提高效率,然后用Acorn做替换
ES Module Lexer的例子,会返回 imports 和 exports 两个数组,包含了 import 的信息
import { init, parse } from 'es-module-lexer';
(async () => {
await init;
const source = `
import { name } from 'mod\u1011';
`;
const [imports, exports] = parse(source, 'optional-sourcename');
// Returns "modထ"
imports[0].n
// Returns "mod\u1011"
source.slice(imports[0].s, imports[0].e);
// "s" = start
// "e" = end
// Returns "import { name } from 'mod'"
source.slice(imports[0].ss, imports[0].se);
// "ss" = statement start
// "se" = statement end
})();
使用 parse找出 imports,和配置比较,没有要替换的直接返回
const [imports] = parse(code)
imports.forEach(({
d: dynamic,
n: dependence,
ss: statementStart,
se: statementEnd,
}) => {})
替换为变量
使用Acorn把 imprt 语句转化为 ast 结构,替换为变量。 astexplorer.net/ 这个网址可以看 ast 结构,初次看,比较震撼的。在这里面写个 demo 会容易理解。
const specifiers = (ast.body[0] as (ESTree.ImportDeclaration))?.specifiers as Specifiers
if (!specifiers) {
return ''
}
return specifiers.reduce((s, specifier) => {
const { local } = specifier
if (specifier.type === 'ImportDefaultSpecifier') {
/**
* source code: import Vue from 'vue'
* transformed: const Vue = window['Vue']
*/
s += `const ${local.name} = ${transformModuleName(externalValue)}\n`
} else if (specifier.type === 'ImportSpecifier') {
/**
* source code:
* import { reactive, ref as r } from 'vue'
* transformed:
* const reactive = window['Vue'].reactive
* const r = window['Vue'].ref
*/
const { imported } = specifier
s += `const ${local.name} = ${transformModuleName(externalValue)}.${imported.name}\n`
}
这样转换就完成了,短小精悍。