一.放弃webpack的原因
1.webpack模块可读性太低

// 引用模块var _myModule1 = __webpack_require__(0);var _myModule2 = __webpack_require__(10);var _myModule3 = __webpack_require__(24);// 模块定义/* 10 *//***/function (module, exports, __webpack_require__) {...}// 源码_myModule2.default.xxx()

这种代码读起来相当费劲,先找到_myModule2对应的__webpack_require__id,再找对应的模块定义,最后看该模块exports身上挂了什么东西。模块定义这个部分很讨厌,延长了阅读引用链

当然,一般不需要读bundle,这一点并不致命

2.文件很大
如上面提到的,这些额外的bundle代码(子模块定义、子模块引用等等)导致文件体积膨胀,因为:

源码每个独立文件外面都包了一层模块定义

模块内对其它模块的引用都插了一条__webpack_require__声明

__webpack_require__工具函数自身的体积

文件体积不但会带来传输负担,还会影响Compile时间,打包方案的bundle size是一项重要指标

3.执行很慢
子模块定义和运行时依赖处理(__webpack_require__),不仅导致文件体积增大,还会大幅拉低性能,如下图:

从webpack到rollup

(图片来自webpack_require is too slow)

打包方案对性能产生大幅影响,这是一点最为致命,无法忍受

二.rollup的优势
1.文件很小
几乎没什么多余代码,除了必要的cjs, umd头外,bundle代码基本和源码没什么差异,没有奇怪的__webpack_require__, Object.defineProperty

bundle大小对比如下:

webpack 132KBrollup  82KB

2.执行很快
因为没什么多余代码,如上文提到的,webpack bundle不仅体积大,非业务代码(__webpack_require__, Object.defineProperty)执行耗时也不容小视

rollup没有生成这些额外的东西,执行耗时主要在于Compile Script和Evaluate Script上,其余部分可以忽略不计,如下图:

【rollup performance】

3.es模块及iife格式支持

// rollupamd – Asynchronous Module Definition, used with module loaders like RequireJScjs – CommonJS, suitable for Node and Browserify/Webpackes – Keep the bundle as an ES module fileiife – A self-executing function, suitable for inclusion as a <script> tag. (If you want to create a bundle for your application, you probably want to use this, because it leads to smaller file sizes.)umd – Universal Module Definition, works as amd, cjs and iife all in one// webpack"var" - Export by setting a variable: var Library = xxx (default)"this" - Export by setting a property of this: this["Library"] = xxx"commonjs" - Export by setting a property of exports: exports["Library"] = xxx"commonjs2" - Export by setting module.exports: module.exports = xxx"amd" - Export to AMD (optionally named - set the name via the library option)"umd" - Export to AMD, CommonJS2 or as property in root

支持打包es6模块,对于基础库之类的东西很合适,因为es6项目一般会用babel转一遍,这样保证一次统一的babel翻译

支持打包成iife,非常小。另外,单从最终bundle大小来看:

        default uglifycjs     81KB    34Kamd     81KB    30KBiife    81KB    30KBumd     82KB    30KB

umd比cjs有优势,看起来很奇怪,但实际结果确实是这样。看bundle差异主要在于函数名简化,cjsbundle中很多长函数名保留下来了,没有被混淆掉

三.rollup的缺陷
目前最新版本(0.50.0)仍然处于0.x的不稳定状态,版本相关的问题比较多(甚至某些问题还需要通过版本降级来解决)

插件生态相对较弱,一些常见需求无法满足

比如打包多个依赖库,把公共依赖项提出来(webpack的CommonsChunkPlugin)

早些版本(0.43)循环依赖处理得不好,会出现打包/执行出错

文档相对较少,遇到问题无法快速解决

比如常见错误'foo' is not exported by bar.js (imported by baz.js),Troubleshooting算是FAQ,但没有提供详细可靠的解决方案(即照做了也不一定能解决)

四.babel配置
babel翻译一般是必不可少的,作为rollup/webpack打包过程的一个中间处理环节,都提供了相应的包装插件,可以把babel配置嵌进来,实际需要掌握的是babel配置

babel presetIn Babel, a preset is a set of plugins used to support particular language features.

常见的有:

es2015:仅支持ES6特性,如果preset里含有该项,会把ES6语法转换为ES5

stage-0:还支持最新的es7甚至es8特性,实际上是指ES Stage 0 Proposals,如果preset里含有该项,会把ESn转换为ES6

react:支持React JSX

stage-0是最激进的做法,表示想要用babel能转的所有JS新特性,无论是否稳定。es2015最保守,规范已经发布了,没有特性不稳定的风险。像stage-0一样能打的还有4个(TC39规范制定流程):

stage-0 – Strawman: just an idea, possible Babel plugin.stage-1 – Proposal: this is worth working on.stage-2 – Draft: initial spec.stage-3 – Candidate: complete spec and initial browser implementations.stage-4 – Finished: will be added to the next yearly release.P```.S.最近babel提供了babel-preset-env,根据目标平台环境来自动添加preset,就不需要装一堆esxxx了,但只提供ES支持,react和polyfill并不会内置,也不应该内置。关于env的更多信息,请查看babel-preset-env: a preset that configures Babel for you注意,各preset仅负责一步转换,比如stage-0能把ESn转ES6,而不是ES5,也就是说,对于一个语法很激进的项目,想要转换成ES5的话,需要这样的babel配置:

{
"presets": [
["stage-0"],
["es2015", {"modules": false}]
],
"plugins": [
"external-helpers"
]
}

P.S.其中,{"modules": false}是rollup需要,用来代替babel-preset-es2015-rollup,external-helpers的作用后面介绍如果想保留ES6风格,需要这样的babel配置:

{
"presets": [
["stage-0"]
],
"plugins": [
"external-helpers"
]
}

转换后得到的是把项目各模块文件拼在一起的ES6模块,代码里的class、const、let都会保留,因为ES6支持这些特性,但async&await之类的更高级特性会被转换到ES6**babel plugin**在babel的3个处理环节中:

parsing -> transforming -> generation

插件作用于第2个环节(transforming),即解析完源语法之后,把它转换为等价的目标语法,在这个阶段可以通过插件做进一步处理,例如简单的:// 把标识符成员访问转换为字面量形式,例如a.catch -> a['catch']es3-member-expression-literals// 把标识符成员声明转换为字面量形式,例如{catch: xxx} -> {'catch': xxx}es3-property-literals还有常用的:

// 支持class静态属性和实例属性,例如class A{instanceProp = 1; static staticProp = 2;}
transform-class-properties
// 把babel自己用的公共方法提出来,例如_createClass, _inherits等等
external-helpers
// 常量修改检查,const声明的常量被修改时报错
check-es2015-constants

所以babel plugin大致分3类:ES5/ES6补丁,修补更低环境相关的问题(es3-xxx,es2015-xxx)静态检查,比如const修改报错提前到“编译”阶段风险特性,比如class-properties等不适合放在stage里的争议特性补丁针对生产环境,静态检查是质量保证的一部分,风险特性则是更激进的一些JS语法**babel polyfill**babel把ESn高级语法转换到ES5/ES3会遇到4种情况:简单语法糖。无脑转换,例如for...of, arrow function复杂语法糖。需要工具函数处理,例如createClass, inheritslow环境缺少的基础特性。需要polyfill,例如Symbol, Promise, String.repeat无法被polyfill的特性。例如Proxy对于low环境缺少的基础特性,babel默认不提供polyfill(babel翻译结果不含polyfill),可以引入babel-polyfill,或者引入想要的特殊polyfill(更轻量小巧的,或者更可靠的重量级的)**babelHelpers**babel有一些转换相关的工具函数,例如:

_typeof
_instanceof
_createClass
_interopRequireDefault
_classCallCheck
_inherits
asyncGenerator

这些工具函数都属于babelHelpers,完整的helpers可以通过命令生成:

npm install babel-cli --save-dev
// type可选global/umd/var
./node_modules/.bin/babel-external-helpers -t umd > helpers.js

P.S.关于生成babelHelpers的更多信息,请查看External helpers默认配置下,这些工具函数会被生成多份,也就是说bundle中会存在多个_createClass声明,是冗余代码。可以通过插件配置优化或去掉默认配置,bundle中存在多份helper声明:

{
"presets": [
["es2015"]
]
}

添上external-helpers插件,把helper声明提到bundle顶部,不存在多份声明:

{
"presets": [
["es2015"]
],
"plugins": [
"external-helpers"
]
}

引用外部babelHelpers,bundle中不含helper声明:

{
"presets": [
["es2015"]
],
"plugins": [
"external-helpers"
],
externalHelpers: true
}

一般添上external-helpers把helper提到bundle顶部就能满足优化要求,所以babel配置都建议至少添上external-helpers插件去除冗余helper代码externalHelpers: true是针对多bundle(multi entry)的情况,不添的话每个bundle顶部都有一份helper声明,添上之后bundle都引用外部helper,例如:

babelHelpers.createClass(xxx)
babelHelpers在bundle里是未定义的,需要提前引入,比如web环境:

<script src="babelHelpers.js"></script>
<script src="bundle.js"></script>

**五.总结**相比webpack,rollup拥有无可比拟的性能优势,这是由依赖处理方式决定的,编译时依赖处理(rollup)自然比运行时依赖处理(webpack)性能更好,但对循环依赖的处理不是百分百可靠。尽量通过内部实现(或设计)来避免,解决循环依赖的常用技巧有:依赖提升,把需要相互依赖的部分提升一层依赖注入,运行时从模块外部注入依赖依赖查找,运行时由模块内部查找依赖依赖提升针对不合理的设计,此类循环依赖是本能够避免的,例如A->B, B->A可能可以通过提出C来转换为A->C, B->C对于无法避免的循环依赖,可以通过运行时依赖注入和依赖查找来解决,例如factory->A, A->factory,一种简单的依赖注入方案是:

// factory.js
import A from './A';
export create() {
// 构造函数注入
return new A(create);
// 属性注入
// let a = new A();
// a._createFromFactory = create;
// return a;
}

// A.js
class A {
constructor(create) {
this._createFromFactory = create;
}
// Will be injected from factory
_createFromFactory() {
return null;
}
}

所以循环依赖是可以从设计/实现上解决的,不是大问题就应用场景而言,rollup最适合打包成单文件,因为目前rollup对multi entry不很友好(公共依赖项都提不出来)。另外,稳定性及插件生态、文档等还不如webpack,但在苛求性能的场景,rollup是唯一的选择参考资料rollup-plugin-babelPolyfillWhat are Babel “plugins” and “presets”? (And how to use them)Rollup.js Tutorial, Part 1: How to Set Up Smaller, More Efficient JavaScript Bundling Using Rollup

更多相关文章

  1. 详解HDFS3.x新特性-纠删码
  2. 讲给前端的正则表达式(3):使用 ES6 特性[每日前端夜话0x104]
  3. Spring Boot 揭秘与实战 关于配置文件有哪些很棒的特性
  4. 你可能错过的现代 JavaScript 特性 [每日前端夜话0xE0]
  5. Java 的版本历史与特性
  6. 每日学习-ansible replace模块
  7. html5: 新特性(表单)
  8. 如何在git特性分支工作流中处理xml/html ?
  9. 总结一些更多的针对webkit的HTML, CSS和Javascript方面的特性.

随机推荐

  1. Android(安卓)CDMA分支
  2. Android 2.2 单点触摸支持的问题
  3. cocos creator 编译通不过, 出现 The "and
  4. 安卓版本和Api Level
  5. android > 布局文件 > 背景圆角
  6. Android-中常用方法集锦
  7. [转]Iperf tool for Android
  8. Android实现画虚线的控件
  9. 【Android Demo】Android中取得手机屏幕
  10. android wegit 组件