假如需要转化的源码是
import { Button } from "antd";
console.log(Button);
配置是
// babel.config.js
const plugins = [
[
"babel-plugin-import",
{
"style": true,
"libraryName": "antd",
"libraryDirectory": "lib",
}
]
]
在执行的时候首先根据配置生成一个 Plugin 实例,源码,初始化的时候会将配置信息挂在到 this 上,源码。
const Program = {
enter(path, { opts = {} }) {
if (!plugins) {
plugins = [
new Plugin(
opts.libraryName,
opts.libraryDirectory,
opts.style,
types,
),
];
}
applyInstance('ProgramEnter', arguments, this);
},
exit() {
applyInstance('ProgramExit', arguments, this);
},
};
之后过滤所有 import xx from “antd” 的 ImportDeclaration,并将组件名添加到自定义的属性 specified 上,源码
ImportDeclaration(path, state) {
const { node } = path;
if (!node) return;
const { value } = node.source;
const { libraryName } = this;
const { types } = this;
const pluginState = this.getPluginState(state);
// libraryName 就是配置中的 antd
if (value === libraryName) {
node.specifiers.forEach(spec => {
if (types.isImportSpecifier(spec)) {
// local.name 是导入进来的别名,比如 import { Button as MyButton } from 'antd' 的 MyButton
pluginState.specified[spec.local.name] = spec.imported.name;
} else {
pluginState.libraryObjs[spec.local.name] = true;
}
});
pluginState.pathsToRemove.push(path);
}
}
在 CallExpression 中将过滤到的组件名,也就是 Button 和其他一些参数传入到 importMethod 进行执行,源码
CallExpression(path, state) {
const { node } = path;
const file = (path && path.hub && path.hub.file) || (state && state.file);
const { name } = node.callee;
const { types } = this;
const pluginState = this.getPluginState(state);
if (types.isIdentifier(node.callee)) {
if (pluginState.specified[name]) {
node.callee = this.importMethod(pluginState.specified[name], file, pluginState);
}
}
node.arguments = node.arguments.map(arg => {
const { name: argName } = arg;
if (
pluginState.specified[argName] &&
path.scope.hasBinding(argName) &&
path.scope.getBinding(argName).path.type === 'ImportSpecifier'
) {
// 找到 specifier,调用 importMethod 方法
return this.importMethod(pluginState.specified[argName], file, pluginState);
}
return arg;
});
}
在 importMethod 中修改组件的引入路径和样式的引入路径为按需引入,源码
importMethod(methodName, file, pluginState) {
if (!pluginState.selectedMethods[methodName]) {
const { style, libraryDirectory } = this;
const transformedMethodName = transCamel(methodName, '');
// 兼容 windows 路径 path$1 == 'antd/lib/button'
const path$1 = winPath(path.join(this.libraryName, libraryDirectory, transformedMethodName));
// 生成 import 语句 import Button from 'antd/lib/button'
pluginState.selectedMethods[methodName] = helperModuleImports.addDefault(file.path, path$1, { nameHint: methodName });
if (style) {
// 生成样式 import 语句 import 'antd/lib/button/style'
helperModuleImports.addSideEffect(file.path, `${path$1}/style`);
}
}
return { ...pluginState.selectedMethods[methodName] };
}
此时如果在编辑器中断点可以发现 AST 中新增两个节点,value 就是我们处理好的路径
执行后生成代码如下
"use strict";
require("antd/lib/button/style");
var _button = _interopRequireDefault(require("antd/lib/button"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
console.log(_button.default);
以上就是 babel-plugin-import 实现按需加载的原理。