前言
已經有好長時間不在掘金冒泡了,固然也是事出有因,3月份動了一次手術,請了3個月的病假。作完手術就一直躺在牀上玩switch,一直玩到如今,什麼塞爾達傳說,火焰紋章也在這段時間打通關了(其實我很想玩ps4,可是作起來費勁只能躺着玩掌機)。css
![WechatIMG237.jpeg](http://static.javashuo.com/static/loading.gif)
發生意外以前,在公司正着手準備作一個內部的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目錄)); } 複製代碼
![WX20200512-223522@2x.png](http://static.javashuo.com/static/loading.gif)
![WX20200512-223544@2x.png](http://static.javashuo.com/static/loading.gif)
編譯後的目錄結構和源碼的目錄結構是徹底一致的,可是組件的代碼已是通過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
![WX20200512-225547@2x.png](http://static.javashuo.com/static/loading.gif)
咱們只須要在 .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 源碼我並無仔細研究,只是大概看了下,不少細節並非很瞭解,若有錯誤還請多多包含。
在瞭解babel-plugin-import源碼前,咱們還須要瞭解ast,訪問者的概念,這些概念推薦你們閱讀下這篇手冊,Babel 插件手冊
babel-plugin-import 的源碼中定義了 import
節點的訪問者(babel
在處理源碼時,若是遇到了import
語句,會使用import
訪問者對import
代碼節點進行處理)
咱們先看看,import
代碼節點在babel眼中張什麼樣子
![image.png](http://static.javashuo.com/static/loading.gif)
圖片右邊的樹,就是訪問者函數中的 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]; } 複製代碼