首先对插件的实现效果进行分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 以下形式的语法由于已经引入了整个module所以不需要作处理
import defaultExport from "module-name";
import * as name from "module-name";
import defaultExport, { export , [...] } from "module-name";
import defaultExport, * as name from "module-name";
import "module-name";
var promise = import("module-name");

// 需要作处理的语法
import { export } from "module-name";
import { export as alias } from "module-name";
import { export1 , export2 } from "module-name";
import { export1 , export2 as alias2 , [...] } from "module-name";

可以看出,需要作处理的语句有以下特征:

  1. impoort 语句
  2. 语句中有大括号且大括号外无其他引入的导出

我们想要的效果:

1
2
3
4
5
6
// transfrom之前
import {export1 , export2 as alias2} from "module-name"

// transfrom之后
import export1 from "module-name/export1"
import alias2 from "module-name/export2"

语句结构图

插件代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
module.exports = function ({ types: t }) {
const genImportDeclaration = (specifier, libraryName) => {
return t.importDeclaration(
[t.importDefaultSpecifier(specifier.local)],
t.stringLiteral(`${libraryName}/${specifier.imported.name.replace(/^./, s => s.toLowerCase())}`)
)
}
return {
// 访问者模式, 通过对AST的遍历对访问到的ImportDeclaration作处理
visitor: {
// 筛选特征1
ImportDeclaration(path, { opts: { libraryName } }) {
// 如果没有指定moduleName
if (!libraryName) return
// 如果语句的source value不为libraryName
if (path.node.source.value !== libraryName) return
const { specifiers } = path.node
// 筛选特征
const allAreImportSpecifier = specifiers.every(ele => ele.type === 'ImportSpecifier')
if (!allAreImportSpecifier) return
// 遍历所有specifier, 将其属性传入genImportDeclaration函数生成一个或多个ImportDeclaration
const ImportDeclarations = specifiers.map(ele => genImportDeclaration(ele, libraryName))
// 替换原来的ImportDeclaration
path.replaceWithMultiple(ImportDeclarations)
}
}
}
}

至此,便实现了一个基本的按需加载插件(完整的代码),可以通过 babel index.js 查看转换后的效果