已經有好長時間不在掘金冒泡了,固然也是事出有因,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];
}
複製代碼