在 npm 上發布源碼
在當前的 npm 生態中,大家發布一個包時常見的做法是先將其編譯成 ES5 版本,然後將 bundle 配置為 package.json
中的 main
欄位,於是用戶在 npm i
一下以後基本上就能開箱即用了。但我現在在 npm 發布項目時,卻傾向於同時發布源文件甚至只發布源文件。這意味著依賴的代碼將和業務項目代碼一同編譯。
為何發布源碼?
- 減少重複代碼如果每個 npm 包都自行打包編譯後發布,會造成很多模塊被重複引入。舉個簡單的例子,如果兩個包都引入了
lodash.get
, 那麼即使各自在打包時都正確配置了babel-plugin-lodash
甚至webpack-plugin-lodash
,在最終用戶使用的項目里這個功能還是會被重複引入。同樣一個[...array]
語法用不同的工具轉譯(Babel/Bublé),生成的 runtime helper 引用更是完全不一樣。 - 轉譯結果是固定的,而瀏覽器的功能和市場份額、產品需要支持的平台都可能不同,為每個平台提供同一份代碼,那麼這份代碼只能充分保守。對於 CSS 來說,這就是需要提前編譯好盡量多的瀏覽器前綴。但對於最終產品,我們實際上可以用 PostCSS + Browserslist 來進行靈活配置。這也正是
babel-preset-env
希望解決掉的問題。 - 編譯工具的 bug 可能會被固化到生態中的大量項目,即使工具進行了升級,已編譯的項目不會自動得到更新。這對於依賴了這些項目的開發者來說,會相對麻煩。即使不是 bug,新版本編譯工具的新功能也可能對輸出的內容有了新的優化。
現實中的問題
- 業務項目和依賴一起編譯,導致編譯時間飆升。這個問題在每次冷啟動編譯時挺讓人頭疼,後續增量其實沒有太大問題。希望 webpack 5 新的持久化緩存能進一步緩解這個問題。
- 配置複雜度增高。目前的一些框架都會在編譯時默認
exclude
掉node_modules
的代碼, 包用戶需要自己手動修改配置來把依賴加入編譯過程中。就我的經驗來看,這會為用戶帶來一定的困擾,我為 Vue.js 生態開發的多個包都有用戶諮詢相關的問題。雖然正確配置以後編譯效果的提升有時候很明顯,但是代價卻是「開箱即用」。例如,即使我已經為 Vue-Awesome 項目的 README 增加了在 Vue CLI 2/3、Nuxt.js、Jest 等環境下的配置說明,依然會遇到不知如何正確在 TypeScript 環境下配置的用戶。 - 可能是最困難的問題:我們發布的究竟是什麼樣的源碼。現代化工具鏈給我們的選擇太多了:我們不但可以使用已經定稿的 ES 功能,也可以使用尚在討論中的特性,甚至 TypeScript。Babel 7 的
override
功能可以在使用時為特定路徑從外部指定編譯配置,其實就是為了強行從外部覆蓋編譯配置。但不同項目中必要的那部分編譯配置可能各不相同,讓業務使用方來了解每一個項目的編譯需求是不現實的。那麼我們是否能有一個反向的策略,讓每個包各自進行聲明自己用了哪些功能呢?很難。這和 webpack/Rollup 等工具區分main
、module
入口的邏輯不同。聲明一個"es6": true
很容易,但是由於當前語法、特性的碎片化,這個機制將很難擴展。
一些實踐
我目前在維護的一些 Vue.js 生態下的 npm 包,基本上都會提供源碼版本,而且會在 README 中推薦使用源碼版本來獲得更好的輸出效果。
目前的一些經驗:
- 儘可能詳盡的配置指南
- 盡量減少草案/私有語法
目前我已經採取源碼發布的一些項目:
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
這個話題目前沒有完美的解法,也一定有人覺得複雜化了問題,但我依然覺得這是一個值得探討的問題。希望對此問題有想法的同學不吝賜教。
推薦閱讀: