已经有好长时间不在掘金冒泡了,固然也是事出有因,3月份动了一次手术,请了3个月的病假。作完手术就一直躺在床上玩switch,一直玩到如今,什么塞尔达传说,火焰纹章也在这段时间打通关了(其实我很想玩ps4,可是作起来费劲只能躺着玩掌机)。css
发生意外以前,在公司正着手准备作一个内部的ui库,因而就研究了一下一些开源的ui库的方案,这里作一个简单总结和分享。node
它们一般都会使用 webpack
或者 rollup
打包生成一个入口的js文件,这个文件一般都是在你不须要按需引入组件库时使用。react
好比 iview
组件库中的 dist
目录下的 iview.js
文件。webpack
import ViewUI from 'view-design';
// 引入了整个js文件,里面可能包含了一些你不须要的组件的代码
Vue.use(ViewUI);
复制代码
再好比 rsuite
组件库中 lib
目录下的 index.js
文件。git
// 即便你没有使用其余组件,也会引入一整个js文件
import { Button } from 'rsuite';
function App() {
return <Button>Hello World</Button>;
}
复制代码
若是咱们不须要引入所有的组件,咱们首先就不能将组件的代码,打包到一个js文件中。咱们能够直接使用 babel
或者借助 gulp
对组件库源码的 src
目录中各个文件直接进行编译, 并写入到目标的 lib
目录。github
// 使用`gulp-babel`对源代码的目录进行编译
function buildLib() {
return gulp
.src(源码目录)
.pipe(babel(babelrc()))
.pipe(gulp.dest(目标lib目录));
}
复制代码
编译后的目录结构和源码的目录结构是彻底一致的,可是组件的代码已是通过babel处理过的了。web
这个时候,咱们就能够实现按需引入组件库。可是业务代码中的 import
代码得修改一下。咱们以 rsuite
组件库为例。若是咱们只想使用 Button
组件,咱们就须要指明,只引入 lib\Button
目录下 index.js
文件。gulp
// 只引入 node_modules/rsuite/lib/Button/index.js 文件
import Button from 'rsuite/lib/Button';
复制代码
这样作实在是颇为麻烦,好在已经有了现成的解决方案,babel-plugin-import
插件。假设咱们打包后的目录结构以下图。babel
咱们只须要在 .babelrc
中作以下的设置。antd
// .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源码前,咱们还须要了解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];
}
复制代码