最近在看lodash按需加载的时候,发现了babel-plugin-lodash这个插件,根据我之前的认知,一般的babel插件可以通过AST来达到修改import的目的,例如:

因为AST可以分析出代码中的ImportDeclaration,然后我们可以获取source('lodash')和specifiers(['defaults','partition']),然后将它们重新拼接,生成新的import就好了。(具体的可以参见 babel插件开发入门)。

但是babel-plugin-lodash中似乎可以做到更厉害:

它可以将你使用过的方法分析变成一个import。我去,太牛了吧。于是赶紧看看源码是如何实现的。

目录结构

首先需要下载babel-plugin-lodash这个插件,然后去node_module下找到这个插件:

其中的index.js就是AST实现的代码。

AST中三种import形式

在index.js中的ImportDeclaration中,会收集所有的specifiers(引用说明),同时识别三种import:

1.import _ from 'lodash'这种的,识别为isImportDefaultSpecifier,就是默认的import。收集的specifiers为[{kind:'named',imported:'default',local:_}]。

2.import {defaults,partition} from 'lodash'这种的,识别为isImportSpecifier,这种是import带有具体的方法,specifier.node.imported.name可以获取名字。收集的specifiers为[{kind:'named',imported:'defaults',local:'defaults'},{kind:'named',imported:'partition',local:'partition'}]。

3.import * as _ from 'lodash'这样的,识别为isImportNamespaceSpecifier,这种是带有命名空间的import。收集的specifiers为[{kind:'namespace',local:'_'}]

通过上面我们可以知道,这一步,主要是拆分from前面的部分,如果是isImportDefaultSpecifier或isImportNamespaceSpecifier这种类型的,只能得到一个数据;如果是isImportSpecifier这种带有具体方法的,则可以得到多个数据。

获取local引用路径

我们将上面得到的specifiers遍历,注意其中有一个local,这个表示变量,通过file.scope.getBinding(local)可以得到当前区域的变量绑定对象,然后binding.referencePaths可以得到和它有关的路径。这个就是可以实现开头说到获取所有使用方法的关键。

例如下面的例子:

首先可以得到specifiers为[{kind:'named',imported:'default',local:_}],那么获取_的referencePaths就得到了_.partition和_.defaults,也就是和_相关的路径。

不过默认的import和指明方法的import还是有些区别:默认的import一开始并不知道方法名,只能通过referencePaths得到相关路径,利用refPath.parent.property.name才能得到正确的路径;而指明方法的import一开始就知道了方法名。

生成新的import

上面我们已经找到了所有的方法名,那么如何生成新的import呢?这个实现主要在importModule.js。它利用@babel/helper-module-imports中的addDefault方法去增加import。

更多@babel/helper-module-imports的方法可以看: @babel/helper-module-imports。addDefault中的path没有特定的含义,它的作用是用来找到顶层的Program:

上面的resolvePath方法甚至可以检查出你当前使用的lodash方法是否存在,例如你用了一个_.abcd的方法,但是lodash并没有提供abcd方法,那么resolvePath方法内部会报错,它的实现原理是另外的js获取了当前lodash的所有方法,然后比对方法名是否存在方法列表中。

实现简易版babel-plugin-lodash功能

上面讲了一些babel-plugin-lodash的主要实现原理,里面还有很多细节没有讲(例如替换变量名字,删除旧的import等),这里我们动手实现一个简单版的,主要的步骤和 babel插件开发入门里面类似,不过需要再安装一个@babel/helper-module-imports插件。

然后index.js中放入我们需要转换的js代码(也可以改成指明方法的那种import):

然后babel-plugin-abcd的index.js中放入如下代码:

最后npx babel index.js运行,得到解析的结果:

其他文章

0
我要评论

评论

返回
×

我要评论

回复:

昵称:(昵称不超过20个字)

图片:

提交
还可以输入500个字