在當前的 npm 生態中,大家發布一個包時常見的做法是先將其編譯成 ES5 版本,然後將 bundle 配置為 package.json 中的 main 欄位,於是用戶在 npm i 一下以後基本上就能開箱即用了。但我現在在 npm 發布項目時,卻傾向於同時發布源文件甚至只發布源文件。這意味著依賴的代碼將和業務項目代碼一同編譯。

為何發布源碼?

  1. 減少重複代碼如果每個 npm 包都自行打包編譯後發布,會造成很多模塊被重複引入。舉個簡單的例子,如果兩個包都引入了 lodash.get, 那麼即使各自在打包時都正確配置了 babel-plugin-lodash 甚至 webpack-plugin-lodash,在最終用戶使用的項目里這個功能還是會被重複引入。同樣一個 [...array] 語法用不同的工具轉譯(Babel/Bublé),生成的 runtime helper 引用更是完全不一樣。
  2. 轉譯結果是固定的,而瀏覽器的功能和市場份額、產品需要支持的平台都可能不同,為每個平台提供同一份代碼,那麼這份代碼只能充分保守。對於 CSS 來說,這就是需要提前編譯好盡量多的瀏覽器前綴。但對於最終產品,我們實際上可以用 PostCSS + Browserslist 來進行靈活配置。這也正是 babel-preset-env 希望解決掉的問題。
  3. 編譯工具的 bug 可能會被固化到生態中的大量項目,即使工具進行了升級,已編譯的項目不會自動得到更新。這對於依賴了這些項目的開發者來說,會相對麻煩。即使不是 bug,新版本編譯工具的新功能也可能對輸出的內容有了新的優化。

現實中的問題

  1. 業務項目和依賴一起編譯,導致編譯時間飆升。這個問題在每次冷啟動編譯時挺讓人頭疼,後續增量其實沒有太大問題。希望 webpack 5 新的持久化緩存能進一步緩解這個問題。
  2. 配置複雜度增高。目前的一些框架都會在編譯時默認 excludenode_modules 的代碼, 包用戶需要自己手動修改配置來把依賴加入編譯過程中。就我的經驗來看,這會為用戶帶來一定的困擾,我為 Vue.js 生態開發的多個包都有用戶諮詢相關的問題。雖然正確配置以後編譯效果的提升有時候很明顯,但是代價卻是「開箱即用」。例如,即使我已經為 Vue-Awesome 項目的 README 增加了在 Vue CLI 2/3、Nuxt.js、Jest 等環境下的配置說明,依然會遇到不知如何正確在 TypeScript 環境下配置的用戶。
  3. 可能是最困難的問題:我們發布的究竟是什麼樣的源碼。現代化工具鏈給我們的選擇太多了:我們不但可以使用已經定稿的 ES 功能,也可以使用尚在討論中的特性,甚至 TypeScript。Babel 7 的 override 功能可以在使用時為特定路徑從外部指定編譯配置,其實就是為了強行從外部覆蓋編譯配置。但不同項目中必要的那部分編譯配置可能各不相同,讓業務使用方來了解每一個項目的編譯需求是不現實的。那麼我們是否能有一個反向的策略,讓每個包各自進行聲明自己用了哪些功能呢?很難。這和 webpack/Rollup 等工具區分 mainmodule 入口的邏輯不同。聲明一個 "es6": true 很容易,但是由於當前語法、特性的碎片化,這個機制將很難擴展。

一些實踐

我目前在維護的一些 Vue.js 生態下的 npm 包,基本上都會提供源碼版本,而且會在 README 中推薦使用源碼版本來獲得更好的輸出效果。

目前的一些經驗:

  1. 儘可能詳盡的配置指南
  2. 盡量減少草案/私有語法

目前我已經採取源碼發布的一些項目:

Vue-ECharts:UMD bundle + 源碼

Vue-Awesome:UMD bundle + 源碼

vue-awesome-material-icons:僅源碼

<vue-clamp>:UMD bundle + 源碼

ResizeDetector:UMD bundle + 源碼

VEUI:僅源碼


Babel 團隊的 Henry Zhu 去年也曾在 Babel Blog 上發布過相同主題的文章,推薦大家閱讀:

On Consuming (and Publishing) ES2015+ Packages · Babel?

babeljs.io

這個話題目前沒有完美的解法,也一定有人覺得複雜化了問題,但我依然覺得這是一個值得探討的問題。希望對此問題有想法的同學不吝賜教。

推薦閱讀:

相关文章