![9aaac569cd4b702a1b1fc4f105ac7247.png](https://i-blog.csdnimg.cn/blog_migrate/5f29c09897b662d0a8ad7ea40d8af0dc.jpeg)
在当前的 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 · Babelbabeljs.io这个话题目前没有完美的解法,也一定有人觉得复杂化了问题,但我依然觉得这是一个值得探讨的问题。希望对此问题有想法的同学不吝赐教。