React & Npm 元件庫維護經驗

ascoders發表於2016-09-18

我們先來回顧一下 React ,Facebook 是這麼描述的:

A JavaScript library for building user interfaces

官方定義其為 UI 庫,這名字似乎太低調了些。從 React-Native 的發展就能看出來其野心勃勃,但官方的定義反而使其成為了開發者的寵兒 —— “為什麼要用React?” “它只是個UI庫”。

從 jQuery 開始,前端元件遍地花開,有jQuery官方提供的成套元件,也有活躍社群提供的第三方元件,從最簡單的文字截斷功能,到複雜的拖拽排序都應有盡有。業務使用的時候,經常會給 window 掛上 $,程式碼中組織也非常靈活,在需要複雜 dom 操作時,jQuery 總能幫忙輕鬆完成。

React 這個 UI 庫進入大家的視野後,我們猛然發現『萬物皆元件』,就連最不加修飾的業務程式碼也可以作為元件被其它模組所引用,這極大的激發了大家的熱情。寫程式碼的時候感覺在造輪子,在寫導航欄、稍微通用點兒的功能時都自覺的將其拆了出來,剛要把標題寫死,猛然想到 “如果這裡用傳參變數,UI加個引數配置,這不就成通用元件了嗎!”。最早、最徹底的把後端模組思維引入到前端,所以 React 元件生態迅速壯大。

應該說 React 的出現加快了前端發展的程式,拉近了前端與後端開發的距離,之後各個框架便紛紛效仿,逐漸青睞對 Commonjs 規範的支援。業務開發中,將元件化思想徹底貫徹其中,許多人都迫不及待的希望釋出自己平時積累的元件,下面就來談談如何從零開始構建元件庫。

如何從零構建元件庫

元件庫的教程不只對 React 適用,其中提到的思想,對大多數通用元件編寫都有效。

本篇介紹的全部構建指令碼程式碼都可以在 https://github.com/fex-team/fit/blob/master/scripts 找到。

分散維護 VS 集中維護

準備搭建元件庫之初,這估計是大家第一個會考慮到的問題:到底把元件庫的程式碼放在一起,還是分散在各個倉庫?

調查發現 Antd 是將所有元件都寫入一個專案中,這樣方便元件統一管理,開發時不需要在多個倉庫之間切換,而且預覽效果只需執行跟專案,而不是為每個元件開啟一個埠進行預覽。其依賴的 react-components 元件庫中的元件以 rc 開頭,不過這個專案沒有進行集中管理。

Material-UI、 React-UI 採用集中式管理等等。

但是集中管理有一些弊端。

  • 引用預設是載入全部,雖然可以通過配置方式避免,(Antd 還提供了 webpack 外掛做這個事情),但安裝時必須全量。
  • 無法對每個元件做更細粒度的版本控制。
  • 協作開發困難,每個人都要搭建一套全環境,提 pr 也具有不少難度。

分散維護的弊端更明顯,無法在同一個專案中觀察全域性,修改元件後引發的連帶風險無法觀察,元件之間引用需要釋出或者 mock,不直觀,甚至元件之間的版本關聯、依賴分析都沒法有效進行管理。

因此 Fit 元件庫在設計時,也經歷了一番醞釀,最後採用了兩者結合的方案,分散部署+集中維護的折中方式,而且竟能結合了兩者各自的優點:

  • 建立根專案 Root,用來做整體容器,順便還可以當對外網站
  • 建立 Group,並在其中建立多個元件倉庫
  • 開發時只要用到專案 Root,根據依賴檔案編寫指令碼自動拉取每個倉庫中的內容
  • 主要負責人拉取全部子專案倉庫,子元件維護者只需要下載對應元件
  • 釋出時獨立釋出每個元件
  • 管理時,統一管理所有元件

package 版本統一

元件的依賴版本號需要統一,比如 fit-input ,fit-checkbox,fit-auto-complete 都依賴了 lodash,但因為先後開發時隔久遠,安裝時分別依賴了 2.x 3.x 4.x,當別人一起使用你最新版的時候,就會無辜的額外增加了兩個 lodash 檔案大小。

更可怕的是,連 React 的版本都不可靠,之前就遇到過一半元件留在 0.14.x ,一半新元件裝了 15.x 的情況,直接導致了線上編譯後專案出錯,因為多個 React 元件不能同時相容,這只是不能並存的其中一個例子。

因為專案開發時元件在一起,使統一版本號成為可能。我們將所有依賴到的元件都安裝在 Root 專案中,每個元件的 package.json 由指令碼自動生成,這個指令碼需要靜態掃描每個元件的 Import 或 require 語法,分析到依賴的模組後,使用根目錄的版本號,填寫在元件的 package.json 中,核心程式碼如下:

084160e0-536c-11e6-9b09-6cfb243e49ec

先收集每個元件中的依賴, 如果在根目錄的 package.json 中找到了,就使用根目錄的版本號。

完整程式碼倉庫:https://github.com/fex-team/fit/blob/master/scripts/module-manage/utils/upgrade-dependencies.js

依賴聯動

依賴聯動是指,fit-button 更新了程式碼,如果 fit-table 依賴了 fit-button,那麼其也要釋出一個版本,更新 fit-button 依賴的版本號。

除了依賴第三方模組,元件之間可能也有依賴,如果將模組分散維護,想更新一下依賴模組都需要釋出+下載,非常消耗時間,而且依賴聯動根本沒法做。集中維護使用 webpack 的 alias 方案,在 typescript 找不到引用,總之不想找麻煩就不能寫 hack 的程式碼。

回到 Fit 元件庫結構,因為所有元件都被下載到了 Root 倉庫下,因此元件之間的引用也自然而然的使用了相對路徑,這樣元件更新麻煩的問題迎刃而解,唯一需要注意的是,釋出後,將所有引入非本元件目錄的引用,替換成為 npm 名稱,例如:

依賴聯動,需要在釋出時,掃描所有元件,找出所有有更新的元件,並生成一項依賴配置,最後將所有更新、或者被依賴的元件統一升級版本號加入釋出佇列。

完整程式碼倉庫:https://github.com/fex-team/fit/blob/master/scripts/module-manage/utils/version.js

inline Or ClassName?

React 元件使用 inline-style 還是 className 是個一直有爭論的話題,在此我把自己的觀點擺出:className 比 inline-style 更具有擴充性。

首先 className 更符合 css 使用習慣,inline-style 無疑是一種退步,既拋棄了 sass less post-css 等強大預編譯工具的支援,也大大減弱了對內部樣式的控制能力,它讓 css 退化到了沒有優先順序,沒有強大選擇器的荒蠻時代。

其次沒有預編譯工具的支援,別忘了許多 css 的實驗屬性都需要加上瀏覽器字首,除非用庫把強大的 autoprefixer 再實現一遍。

使用 className 可以很好的加上字首,在追查檔案時能得到清晰的定位,下面是我們對 CSS 名稱空間的一種實現 ——html-path-loader css-path-loader 外掛 配合 webpack 後得到的除錯效果:

檔案結構

e90e0b6a-536b-11e6-8842-ad8104d69012

DOM結構對應 className

(https://cloud.githubusercontent.com/assets/7970947/17137106/edee7cc8-536b-11e6-9e5f-3dda7a87cf39.png)

直接從 dom 結構就能順藤摸瓜找到檔案,上線時再將路徑 md5 處理。

這個外掛會自動對當前目錄下的 scss或less 檔案包一層目錄名,在 jsx 中,使用 className="_namespace",html-path-loader 會自動將 _namespace 替換為與 css 一致的目錄名稱。

typescript 支援

既然前端模組化向後端看齊,強型別也成為了無可阻擋的未來趨勢,我們需要讓開發出的元件原生支援 typescript 的專案,得到更好的開發體驗,同時對 js 專案也能優雅降級。

由於現在 typescript 已原生支援 npm 生態,如果元件本身使用 typescript 開發,我們只需要使用 tsc -d 命令在目錄下生成對應的 d.ts 定義檔案,當業務專案使用 typescript 的時候,會自動解析 d.ts 作為元件的定義。

再給 package.json 再上 typings 定義指向入口檔案的 d.ts ,那麼整體工作基本就完成了。

最後,對於某些沒有定義檔案的第三方模組,我們在根專案 Root 中寫上定義檔案後, 匯入時將檔案拷貝一份到元件目錄內,並修正相對引用的位置,保證元件獨立釋出後還可以找到依賴檔案。

完整程式碼倉庫:https://github.com/fex-team/fit/blob/master/scripts/module-manage/push.js

更強的擴充性

React 元件的擴充性似乎永遠也爭論不休,無論你怎樣做元件,都會有人給你抱怨:要是這裡支援 xxx 引數就好了。

畢竟使用了元件,就一定不如自己定製的擴充性更強,節省了勞動力,就要付出被約束的代價,Fit 作為一個大量被業務線使用的元件庫,使用了透傳方式儘可能的增強元件擴充性。

我們寫了一個很簡單的透傳元件:fit-transmit-transparently,使用方法如下:

它會將 this.props 中,除了 defaultProps 定義了的欄位抽到 _others 中,直接透傳給外圍元件,因為 defaultProps 中定義了的欄位預設是有含義的,因此不會對其進行操作,避免多次定義產生的風險。

現在 fit-input 就將 props 透傳到了原生 Input 元件上,因此雖然我沒有處理各類事件,但依然可以響應任意的 onKeyDown onKeyUp onChange onClick 等事件,也可以定義 style 來覆蓋樣式等等。

fit-number 繼承了 fit-input,因此依然支援所有原生事件,fit-auto-complete 也繼承了 fit-input,對其新增的例如 onBlur 等事件依然會被透傳到 input 框中。

元件的 dom 結構要儘量精簡,透傳屬性一般放置在最外層,但對於 input 這種重要標籤,透傳屬性最好放置與其之上,因為使用者的第一印象是 onChange 應該被 input 觸發。

同構模組引用技巧

當依賴的模組不支援 node 環境,但還必須載入它的時候,我們希望在後端忽略掉它,而在前端載入它;當依賴模組只處理了後端邏輯,在前端沒必要載入時,我們希望前端忽略它,後端載入它,下面是實現的例子:

前端載入&後端不載入的原理是,前端靜態掃描到了這個模組,因此無條件載入了它(前端引用是靜態掃描),後端會因為判斷語句而忽略掉這個引用(後端引用是執行時)。

後端載入&前端不載入的原理是,將模組引用拆成非字面量,前端靜態掃描發現,這是什麼鬼?忽略掉吧,而 node 會老老實實的把模組拼湊起來,發現還真有 module-only-support-in-node 這個模組,因此引用了它。

一份程式碼 Demo & 原始碼顯示

webpack 提供瞭如下 api 擴充 require 行為:
2cbf4ff4-536c-11e6-8279-9d1512abbaed

  • ! 打頭的,忽略配置檔案的 preLoaders 設定
  • !!打頭的,忽略所有配置檔案的設定
  • -! 打頭的,忽略 preLoaders 和 loaders ,但 postLoaders 依然有效

一般來說,我們都在配置檔案設定了對 js 檔案的 loader,如果想引用原始碼,正好可以用 !! 打頭把所有 loaders 都幹掉,然後直接用 text-loader 引用,這樣我們就得到了一份純原始碼以供展示。

元件編寫一些注意點

理解 value 與 defaultValue

defaultValue 屬性用於設定元件初始值,之後元件內部觸發的值的改變,不會受到這個屬性的影響,當父級元件觸發 render 後,元件的值應當重新被賦予 defaultValue。

value 是受控屬性,也用來設定值,但除了可以設定初始值(優先順序比 defaultValue 高)之外,還應滿足只要設定了 value,元件內部就無法修改狀態的要求,這個元件的狀態只能由父級授予並控制,所以叫受控屬性。

value 與 defaultValue 不應該同時存在,最好做一下檢查。

render 函式中最小化程式碼邏輯

React 的宗旨是希望通過修改狀態來修改渲染內容,儘量不要在 render 函式中編寫過多的業務邏輯和判斷語句,最好將能抽離成狀態的放在 state 中,在 componentWillReceiveProps 中改變它

使用 auto-bind

如果你也使用 ES6 寫法,那麼最好注意使用 auto-bind 外掛,將所有成員函式自動繫結 this,否則.bind(this) 會返回一個新的函式,一來損耗效能,二來非常影響子元件的 shouldComponentUpdate 判斷!

慎用 componentWillMount

對於同構模組,React 元件的生命週期 componentWillMount 會在 node 環境中執行,而componentDidMount 不會。

要避免在 willMount 中操作瀏覽器的 api,也要避免將可有可無的邏輯寫在其中,導致後端伺服器渲染吃力(目前 React 渲染是同步的),無關初始化邏輯應當放在 didMount 中,由客戶端均攤計算壓力。對於影響到頁面渲染的邏輯還是要放在 willMount 中,不然後端渲染就沒有意義。

巧用 key 做效能優化

React 元件生命週期中 shouldComponentUpdate 方法是控制元件狀態改變時是否要觸發渲染的,但當同級元件量非常龐大時,即便在每個元件做是否渲染的判斷都會花費幾百毫秒,這時我們就要選擇更好的優化方式了。

新的優化方式還是基於 shouldComponentUpdate ,只不過判斷條件非常苛刻,我們設定為只有 state 發生變化才會觸發 render,其它任何情況都不會觸發。這種方式排除了對複雜 props 條件的判斷,當 props 結構非常複雜時,對沒有使用 immutable 的程式碼簡直是一場災難,我們現在完全忽略 props 的影響,元件變成為了完完全全封閉的王國,不會聽從任何人的指揮。

當我們實在需要更新它時,所有的 props 都不起作用,但是可以通過 key 的改變來繞過shouldComponentUpdate 進行強制重新整理,這樣元件的一舉一動完全被我們控制在手,最大化提升了渲染效率。

元件級 Redux 如何應用

元件級 Redux 使用場景主要在於元件邏輯非常複雜、或使用時,父子 dom 強依賴,但可能不會被用於直接父子級的場景,例如 fit-scroll-listen 元件,用來做滾動監聽:

ScrollListenBox 是需要監聽滾動的區域,ScrollListenNail 是滾動區域中需要被標記的節點,ScrollListen 是顯示滾動監聽狀態的 dom 結構。

由於業務需求,這三個節點很可能無法滿足直接父級子關係,而且上圖應用中,ScrollListen 就與ScrollListenBox 是同級關係,兩者也無辦法通訊,因此需要使用 Redux 作資料通訊。

我們從 createStore 例項化了一個 store,並傳遞給每一個 fit-scroll-listen,這樣他們即便隔著千山萬水,也能暢快無阻的通訊了。

npm 資源載入簡析

webpack&fis 最核心的功能可以說就是對 npm 生態的支援了,社群是編譯工具的衣食父母,支援了生態才會有未來。

為了解決業務線可能遇到的各種 npm 環境問題,我們要有刨根問底的精神,瞭解 npm 包載入原理。下面會一步一步介紹一個 npm 模組是如何被解析載入的。

檔案查詢

無論是 webpack、fis,還是其它構建工具,都有檔案查詢的鉤子,當解析了類似 import '../index.js'時,會優先查詢相對路徑,但解析到了 import 'react' 便無從下手,因為這時構建工具還不知道這種模組應該從哪查詢,我們就從這裡開始截斷,當出現無法找到的模組時,就優先從 node_modules 資料夾下進行查詢(node_modules 下查詢模組放到後面講)。

由於 npm 模組打平&巢狀兩種方案可能並存,每次都遞迴查詢的效率太低,因此我們首先會把 node_modules 下所有模組快取起來,這裡分為兩種方案:

  1. 根據node_modules 下資料夾遍歷讀取,優點是掃描全面,缺點是效率低。
  2. 根據 package.json 中 deps(可以設定忽略devDeps)進行掃描,優先是效率高,缺點是忘記 –save 模組會被忽略。

將所有模組存到 map 後,我們直接就能 get 到想要的模組,但是要注意版本問題:如果這個模組是打平安裝的,那毫無疑問不會存在同模組多版本號問題,npm@3.x 後即便是打平安裝,但遇到依賴模組已經在根目錄存在,但版本號不一致,還是會採用巢狀方式,而 npm@2.x 無論如何都會用巢狀的方式。

因此我們的目的就明確了,不用區分 npm 的版本,如果這個當前檔案位於非 node_modules 資料夾中,直接從根目錄引用它需要的模組,如果這個當前位於 node_modules 中,優先從當前資料夾中的 node_modules 獲取,如果當前資料夾的 node_modules 不存在依賴檔案,就從根目錄取。

解讀 package.json

找到了依賴在 node_modules 裡的根目錄,我們就要解析 package.json 進行引用了,main 這個屬性是我們的指明燈,告訴我們在複雜的包結構中,哪個檔案才是真正的入口檔案。

我們還要注意 package.json 裡設定了 browser 屬性的模組,由於我們做的是前端檔案載入,所以這個屬性對我們有效,將依賴模組的路徑用 browser 做修正即可,一般都是同構模組使用它,特意將前端實現重寫了一遍。所以當 browser 屬性為字串時我們就放棄對 main 信任,轉而使用 browser 屬性來代替入口路徑。

當 browser 屬性為物件時,情況複雜一些,因為此時 browser 指代的含義不是入口檔案的相對路徑,而是對這個模組內部使用的包引用的重定向,此時我們還不能信任 main 對入口的引導,初始化時將 browser 物件儲存,整體查詢順序是:優先查詢當前模組的 browser 設定,替換 require 路徑,找到模組後,如果 browser 是字串,優先用其路徑,否則使用 main 的路徑。

環境變數

npm 生態非常慣著使用者,我們希望直接在模組中使用 Buffer process.env.NODE_ENV 等變數,而且通常會根據當前傳入的變數環境做判斷,可能開發過程中載入了不少影響效能,但方便除錯的外掛,當NODE_ENVproduction 時會自動幹掉,如果我們不對這種情況做處理,上線後無法達到模組的最佳效能(甚至報錯,因為 process 沒有定義)。

編譯指令碼要根據使用者的設定,比如 CLI 使用了 NODE_ENV=production ,或者在外掛中申明,就將程式碼中process.env.NODE_ENV 替換為對應的字串,對與 Buffer 這類模組也要單獨拎出來替換成 require。

模組載入

為了讓瀏覽器識別 module.exports (es6 的 export 語法交給 babel 或者 typescript 轉換為 module.exports)、define、require,需要給模組包一層 Define,同時把模組名快取到 map 中,可以根據檔案路徑起名字,也可以使用 hash,最後 require 就從這裡取即可。

由於是簡析,不做更深入的分析,剩下的工作基本上是優化快取、對更多功能語法的支援。

同構方案

為了保證傳統的首屏體驗,同時維持單頁應用的優勢,替代方案走了不少彎路。從單獨寫一份給爬蟲看的頁面,到使用 phantomjs 抓取靜態頁面資訊,現在已經步入了後端渲染階段,由於其可維護性與使用者體驗兩者兼顧,所以才快速壯大起來。

後端渲染

無論何種後端渲染方案,其本質都是在後端使用 nodejs 執行前端的 js 程式碼,有的庫使用同步渲染,也有非同步,React 目前官方實現屬於同步渲染,關於同步渲染遇到的問題與解決方案,會在 “同構請求” 這一節說明。

使用 React 進行後端渲染程式碼如下:

稍稍改造,將其與 Redux 結合,只需要將 Provider 作為元件傳入,並傳入 store 來儲存頁面資料,最後獲得的 initialState 就是頁面的初始資料:

這樣,將頁面初始資料打在 window 全域性變數中,前端 Redux 初始化直接用後端傳來的初始資料,就可以將頁面狀態與後端渲染銜接上。

對於 Redux,是專案資料結構的抽象,最好按照 state 樹結構拆分資料夾,將 Redux 資料流與頁面、元件完全解耦。

同構請求

同構請求是對後端渲染的進一步處理,使後端渲染不僅僅能生成靜態頁面資料,還可以首屏展現依賴網路請求資料所渲染出的 dom 結構。

同構請求的優化主要體現在後端處理,因為前端沒有選擇,只能體現在 Http 請求。現在有兩種比較理想的方案:

http 請求

這種方案依賴同構的請求庫,例如 axios,在後端渲染時,能和前端一樣發出請求並獲取資料。主要注意一下,如果使用的是同步渲染的框架,例如 React,我們需要將請求寫在生命週期之外,在其執行之前抽出來使用 Promise 呼叫,待請求 Ready 之後再執行一遍渲染即可。

這種方案修改成本中等,需要把所有同構請求從元件例項中抽離出來,可能獲取某些依賴元件例項的資料來源比較困難,不過可以滿足大部分簡單資料請求。

這種方案稍加改造,可以產生一套修改成本幾乎為零的方案,缺點是需要渲染兩遍。第一遍渲染,將所有元件例項中的請求例項抽取出來,第二步類似使用 Promise.all 等資料獲取完畢,最後再執行一遍渲染即可,缺點是渲染兩遍,而且網路請求耗費 IO,訪問外網資料速度很慢,和直接呼叫函式的速度完全不在一個數量級。所以我們在想,能不能將前端的 http 請求在後端轉換為直接呼叫函式?

直接命中函式

這個方案基於上一套方案優化而來,唯一的缺點是渲染了兩遍,對專案改動極小,後端請求效率最大化。

希望後端直接命中函式,需要對整體專案框架進行改造,因為我們要提前收集全部的後端方法儲存在 Map 中,當後端請求執行時,改為從 Map 中抽取方法並直接呼叫。

後端響應請求的方法,我們採用裝飾器定義路由與收集到 Map:

fit-isomorphic-redux-tools 元件匯出的 routerDecorator 方法做了兩件事,第一件是繫結路由,第二件是將收集到的函式塞到 Map 中,key 就是 url,用於同構請求在後端定位查詢。

前端程式碼中,action 中呼叫 fit-isomorphic-redux-tools 提供的 fetch 方法,這個方法也做了兩件事,第一件是前端模組根據配置發請求,第二件在後端環境下,通過 url 查詢上一段程式碼在 routerDecorator 註冊的函式,如果命中了,會直接執行該函式。

上面的 fetch 方法內部封裝了對瀏覽器與node環境的判斷,如果是瀏覽器環境則直接傳送請求,node環境則直接呼叫 promise。在前後端都經過 redux 處理,為了讓 reducer 拿到 promise 後的資料,我們封裝一個 redux 中介軟體:

上述程式碼對所有包含 promise 的 action 起作用,在前端會在 promise 執行完畢後觸發[actionName]_PROMISE_DONE ,在 reducer 裡監聽這個字串即可。後端會直接呼叫方法,因為方法可能是同步也可能是非同步的,比如下面就是非同步的:

所以做了兩套處理,async 最終返回一個 promise,如果不用 async 包裹住則沒有,因此 result.then === 'function' 便是判斷這個方法是否是 async 的。

給出一套上述理論的完整實現,有興趣的同學可以安裝體驗下:https://github.com/ascoders/isomorphic-react-redux-app

編譯優化

為了避免模組太大導致的載入變慢問題,我們通過 require.ensure 動態載入模組,這也對 HTTP2.0 併發請求相當友好。

webpack&Fis 按需載入

使用了 require.ensure 的模組,webpack&fis會將其拆分後單獨打包,並在引用時轉換為 amd 方式載入,下面是與 react-router 結合的例子:


這樣遍做到了對業務模組的按需載入,而且業務模組程式碼不多,可以忽略編譯時對效能的影響:

35097ca2-536c-11e6-972a-432195ea60dd

如果是同構的模組,需要在 node 端對 require.ensure 做 mock 處理,因為 nodejs 可不知道 require.ensure 是什麼!

現在訪問 /home 這個 url,前端模組會先載入基礎庫檔案,再動態請求 Home 這個元件,獲取到元件後再執行其中程式碼,渲染到頁面,但對於後端渲染,希望直接獲取到動態載入的元件,並根據元件設定頁面標題就變得困難,因此上面程式碼中, callback(require) 將 require.ensure 在後端改為的同步載入,因此可以直接獲取到元件中靜態成員變數,我們可以將例如頁面標題寫在頁面級元件的靜態成員變數中,例如:

在 node 端這樣處理:

並將獲取到的 title 插入到模板的 title 中,讓頁面初始化時標題就是動態元件載入後就要設定的,而且更利於搜尋引擎對頁面初始狀態的抓取,實現了前端對後端的控制反轉。

相比業務程式碼,npm 生態的模組比起來真是龐然大物,動輒 2000+ 細檔案的引用,雖然開啟了增量 build,但檔案的整合打包依然非常影響開發體驗,因此有必要在開發時忽略 npm 模組。

webpack 的編譯優化

編譯優化的最終目的是將大型第三方模組拆開,在編譯時直接跳過對其的編譯,並直接在頁面中引用編譯好的指令碼,因此第一步需要將所有不順眼的模組全部打包到 vendor.js 檔案中:

entry 定義了哪些檔案需要抽出,output 中,library 定義了暴露在 window 的 namespace, plugins 注意將 name 設定為與 library 相同,因為引用時參考的是這個名字。

我們執行 webpack,執行結果如下:

447cd044-536c-11e6-8993-31d6b393b426

產出了 dll 與 mainfest 兩種檔案,dll 是打包後的檔案,mainfest 是配置檔案

46c48bc6-536c-11e6-8939-544dd188f9ca

發現配置了兩個重要屬性,一個是暴露在 window 的 namespace ,另一個是所有相對路徑引用的模組名,webpack 打包後會轉化為數字進行查詢,防止路徑過長在 windows 下報錯。

下面開始配置開發配置 webpack.config.js:

執行結果只有很小的大小:

51dab4ea-536c-11e6-93a0-4cf24c1d8dcc

再將所有檔案引用到頁面中,這樣初始化構建時先執行 dll 指令碼,生成打包檔案後再僅對當前專案打包&監聽,這就解決了開發時體驗問題。

視覺化拖拽平臺元件

最後分享一下我們的終極解決方案 fit-gaea,它是一個元件,是視覺化拖拽平臺,安裝方式如下:


Gaea 是編輯器本身,它主要負責拖拽檢視,並生成對應 json 配置。 Preview 是部署元件,將 Gaea 生成的 json 配置傳入,可以自動生成與拖拽編輯時一模一樣的頁面。

最大特色在於元件自定義,右側選單欄羅列了可供拖拽的元件,我們也可以自己編寫 React 元件在 Gaea 初始化時傳入自定義元件,自由設定這個元件可以編輯的欄位,並且在元件中使用它。

對於粗粒度的運營招聘頁,甚至可以將整個頁面作為一個自定義元件傳入,因為每個頁面非常雷同,只需要定義幾處文字修改即可,生成一個新頁面,只需要將自定義元件拖拽出來例項化,並且簡單修改自己欄位即可。

同時 fit-gaea 也提供了很多細粒度的通用元件,例如 按鈕、段落、輸入框、佈局元件 等等,我們也可以自己編寫一些細粒度元件,通過任意巢狀組合的方式,生成更加複雜的組合,平臺也支援將任意組合成組,打成一個元件儲存在工具欄,我們可以通過巢狀組合的方式生成新的元件。

這個平臺本質就是一個元件,業務線不需要花費大量精力重複編寫非常複雜的拖拽平臺,只需要將精力關注在編寫與業務緊密結合的定製元件,再傳入 fit-gaea,就可以讓寫的元件變得可以拖拽編輯。

fit-gaea api 文件地址:http://fit.baidu.com/components/pc/gaea
fit-gaea demo 體驗地址: http://fit.baidu.com/designer

總結

分享進入了尾聲,對以上經驗做一個總結。通過對元件庫靈活分散的管理,同時透傳暴露更多 api 提高元件可用性,提供從元件,到同構方案,最後到開發體驗優化與打包效能優化,可以說提供了一套完整的開發方案。

同時通過 React-Native 方案提高三端開發的效率,開發出 web、native 通用的元件,通過 fit-gaea 視覺化編輯元件的支援,讓編輯器生成橫跨三端的頁面,並且不受發版、前端人力資源限制,運營&產品都可以快速建立任何定製化頁面。

最後,Fit 元件我們一直在努力維護中,我也希望將編寫元件的經驗分享給更多人,讓更多人蔘與到構建元件生態的隊伍中,願元件社群這棵大樹枝繁葉茂。

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

任選一種支付方式

React & Npm 元件庫維護經驗 React & Npm 元件庫維護經驗

相關文章