前言
已经有好长时间不在掘金冒泡了,当然也是事出有因,3月份动了一次手术,请了3个月的病假。做完手术就一直躺在床上玩switch,一直玩到现在,什么塞尔达传说,火焰纹章也在这段时间打通关了(其实我很想玩ps4,但是做起来费劲只能躺着玩掌机)。
发生意外之前,在公司正着手准备做一个内部的ui库,于是就研究了一下一些开源的ui库的方案,这里做一个简单总结和分享。
各个组件库是怎么做的?
它们通常都会使用 webpack
或者 rollup
打包生成一个入口的js文件,这个文件通常都是在你不需要按需引入组件库时使用。
比如 iview
组件库中的 dist
目录下的 iview.js
文件。
import ViewUI from 'view-design';
// 引入了整个js文件,里面可能包含了一些你不需要的组件的代码
Vue.use(ViewUI);
复制代码
再比如 rsuite
组件库中 lib
目录下的 index.js
文件。
// 即使你没有使用其他组件,也会引入一整个js文件
import { Button } from 'rsuite';
function App() {
return <Button>Hello World</Button>;
}
复制代码
如果我们不需要引入全部的组件,我们首先就不能将组件的代码,打包到一个js文件中。我们可以直接使用 babel
或者借助 gulp
对组件库源码的 src
目录中各个文件直接进行编译, 并写入到目标的 lib
目录。
// 使用`gulp-babel`对源代码的目录进行编译
function buildLib() {
return gulp
.src(源码目录)
.pipe(babel(babelrc()))
.pipe(gulp.dest(目标lib目录));
}
复制代码
编译后的目录结构和源码的目录结构是完全一致的,但是组件的代码已经是经过babel处理过的了。
这个时候,我们就可以实现按需引入组件库。但是业务代码中的 import
代码得修改一下。我们以 rsuite
组件库为例。如果我们只想使用 Button
组件,我们就需要指明,只引入 lib\Button
目录下 index.js
文件。
// 只引入 node_modules/rsuite/lib/Button/index.js 文件
import Button from 'rsuite/lib/Button';
复制代码
这样做实在是颇为麻烦,好在已经有了现成的解决方案,babel-plugin-import
插件。假设我们打包后的目录结构如下图。
我们只需要在 .babelrc
中做如下的设置。
// .babelrc
{
"plugins": [
[
"import", {
"libraryName": "react-ui-components-library",
"libraryDirectory": "lib/components",
"camel2DashComponentName": false
}
]
]
}
复制代码
babel插件就会自动将 import { Button } from '组件库'
转换为 import Button from '组件库/lib/components/Button'
。
那么 babel-plugin-import
是如何做到的呢?
babel-plugin-import的实现机制
babel-plugin-import 源码我并没有仔细研究,只是大概看了下,很多细节并不是很了解,如有错误还请多多包含。
在了解babel-plugin-import源码前,我们还需要了解ast,访问者的概念,这些概念推荐大家阅读下这篇手册,Babel 插件手册
babel-plugin-import 的源码中定义了 import
节点的访问者(babel
在处理源码时,如果遇到了import
语句,会使用import
访问者对import
代码节点进行处理)
我们先看看,import
代码节点在babel眼中张什么样子
图片右边的树,就是访问者函数中的 path
参数
ImportDeclaration(path, { opts }) {
const { node } = path;
if (!node) return;
const { value } = node.source;
const libraryName = this.libraryName;
const types = this.types;
// 如果value等于我们在插件中设置的库的名称
if (value === libraryName) {
node.specifiers.forEach(spec => {
// 记录引入的模块
if (types.isImportSpecifier(spec)) {
this.specified[spec.local.name] = spec.imported.name;
} else {
this.libraryObjs[spec.local.name] = true;
}
});
// 删除原有的节点,就是删除之前的import代码
path.remove();
}
}
复制代码
在适当的时刻,会插入被修改引入路径的 import
节点
importMethod(methodName, file, opts) {
if (!this.selectedMethods[methodName]) {
const libraryDirectory = this.libraryDirectory;
const style = this.style;
// 修改模块的引入路径,比如 antd -> antd/lib/Button
const path = `${this.libraryName}/${libraryDirectory}/${camel2Dash(methodName)}`;
// 插入被修改引入路径的 import 节点
this.selectedMethods[methodName] = file.addImport(path, 'default');
if (style === true) {
file.addImport(`${path}/style`);
} else if(style === 'css') {
file.addImport(`${path}/style/css`);
}
}
return this.selectedMethods[methodName];
}
复制代码
来源:oschina
链接:https://my.oschina.net/u/4305447/blog/4277794