微信号:FrontDev

介绍:分享 Web 前端相关的技术文章、工具资源、精选课程、热点资讯

冗余代码都走开——前端模块打包利器 Rollup.js 入门

2016-06-02 20:40 前端大全

(点击上方公众号,可快速关注)

作者:VaJoy Larn(@VaJoy_学霸模式重启) 

链接:http://www.cnblogs.com/vajoy/p/5518442.html


之前翻译过一篇文章,介绍了通过 ES2015 的解构赋值语法引入模块,可以让打包工具(browserify)最终编译出来的代码量最小化。


殊不知在 webpack 1.X 版本是无法利用该特性来避免引入冗余模块代码的,导致打出来的 bundle 文件大小难免略有臃肿。


今天则向大家介绍一个当红炸子鸡——Rollup.js,通过它可以让你的 bundle 最小化,有效减少文件请求大小——以至于连 vue 都迅速地转投它来打包模块。


Tree-shaking


在 Rollup 编译模块的过程中,通过 Tree-shacking 的方式来剔除各模块中最终未被引用到的方法,通过仅保留被调用到的代码块来缩减 bundle 的大小。


我们来看下官网的例子。


页面入口文件 main.js:


import { cube } from './maths.js';

console.log( cube( 5 ) ); // 125,即5的立方值


被引如的 math.js 模块如下:


// 注意这个方法在入口文件里没有被调用过

//最终会被 Rollup 剔除

export function square ( x ) {

    return x * x;

}

 

//入口文件需要调用到的求立方值的方法

export function cube ( x ) {

    return x * x * x;

}


通过 Rollup 打包之后如下:


'use strict';

 

function cube ( x ) {

    // rewrite this as `square( x ) * x`

    // and see what happens!

    return x * x * x;

}

 

console.log( cube( 5 ) ); // 125


可以很明显地体会到 Tree-shaking 的作用 —— Math 模块里有个从未用到的 square 方法,咱们在 bundle 文件里把它消灭掉了。


另外 TS 会抽取引用到的模块内容,将它们置于同一个作用域下,进而直接用变量名就可以访问各个模块的接口;而不像 webpack 这样每个模块外还要包一层函数定义,再通过合并进去的 define/require 相互调用。


当然这种方法需要 ES2015 的解构赋值语法来配合,多亏了它,Rollup 才能有效地对模块内容进行可靠的静态分析。


使用方式


安装自然不用说,走 npm 的老套路:


npm i rollup


执行打包的方式也是简单到爆:


rollup src/main.js -o rel/bundle.js


这意味着将入口文件 src/main.js 打包为 rel/bundle.js 文件。


很多时候我们开发走的 ES2015 模块语法,但最终编译出来的模块希望它能走 commonjs 语法,只需要加上 -f cjs 运行时参数(f for format)即可:


rollup src/main.js -o rel/bundle.js -f cjs


当然,如果你想编译为其它格式,可以把 cjs 更换为:


amd /  es6 / iife / umd


我们分别来个参考~ 假设入口文件 src/main.js 如下:


var name = 'VaJoy';

 

function main () {

    console.log(name);

}

 

export default main;


编译为各种模式后的bundle:


//////////////////////////////commonjs(-f cjs)

'use strict';

 

var name = 'VaJoy';

 

function main () {

    console.log(name);

}

 

module.exports = main;

 

 

//////////////////////////////AMD(-f amd)

define(function () { 'use strict';

 

    var name = 'VaJoy';

 

    function main () {

        console.log(name);

    }

 

    return main;

 

});

 

//ES2015/ES6(-f es6)

var name = 'VaJoy';

 

function main () {

    console.log(name);

}

 

export default main;

 

 

//////////////////////////////Global(-f iife)

//注意该方法需要通过配置文件形式来执行(见下一节)

var main = (function () {

    'use strict';

 

    var name = 'VaJoy';

 

    function main () {

        console.log(name);

    }

 

    return main;

 

}());

 

//////////////////////////////UMD(-f umd)

//注意该方法需要通过配置文件形式来执行(见下一节)

(function (global, factory) {

    typeof exports === 'object' & typeof module !== 'undefined' ? module.exports = factory() :

    typeof define === 'function' && define.amd ? define(factory) :

    (global.main = factory());

}(this, function () { 'use strict';

 

    var name = 'VaJoy';

 

    function main () {

        console.log(name);

    }

 

    return main;

 

}));


配置文件


和 webpack 一样,rollup 也支持通过配置文件来实现更灵活的功能。


我们在项目根目录新建一个 rollup.config.js :


export default {

  entry: 'src/main.js',

  format: 'cjs',

  dest: 'rel/bundle.js' // 输出文件

};


然后执行


rollup -c


即可通过默认配置文件(rollup.config.js)所设置的信息来进行打包。


如果你的配置文件另有其名(例如“rollup.config.dev.js”),在后面加上配置文件名即可:


rollup -c rollup.config.dev.js


Rollup 也支持使用插件,写到配置对象的 plugin 里即可,这里我们以 rollup-plugin-babel 为例:


import babel from 'rollup-plugin-babel';

 

export default {

  entry: 'src/main.js',

  format: 'cjs',

  plugins: [ babel() ],

  dest: 'rel/bundle.js'

};


比较不爽的是,babel 的预设不像 webpack 可以直接写在配置文件里,而还是得独立写个“src/.babelrc”(注意我们可以写在 src 下,而不是非得放在项目根目录下):


{

  "presets": ["es2015-rollup"]

}


注意咱得确保安装了 rollup-plugin-babel 和 babel 预设 babel-preset-es2015-rollup:


npm i rollup-plugin-babel babel-preset-es2015-rollup


这时候就能配合 babel 一起把 ES6 的模块编译为 ES5 的 bundle 了。


更多有趣的插件可以在 rollup 项目组织里找,貌似没有 webpack 那样专门有个插件列表页汇总,这点找起来不太方便。


Rollup 也支持直接在模块中来被调用执行,这样很方便跟 grunt/gulp 等工具进行协作。


如我们修改 rollup.config.dev.js 内容为:


var rollup = require( 'rollup' );

var babel = require('rollup-plugin-babel');

 

rollup.rollup({

    entry: 'src/main.js',

    plugins: [ babel() ]

}).then( function ( bundle ) {

    bundle.write({

        format: 'umd',

        moduleName: 'main', //umd或iife模式下,若入口文件含 export,必须加上该属性

        dest: 'rel/bundle.js'

    });

});


然后用 node 直接执行


node rollup.config.dev.js


可以得到一样的执行结果。


注意 “rollup.rollup()”返回一个带着 bundle 作为 resolve 回调参数的 Promise 对象,我们常规直接使用语法糖 bundle.write 来打包输出文件:


bundle.write({

        format: 'umd',

        moduleName: 'main',

        dest: 'rel/bundle.js'

});


其等价于


var result = bundle.generate({ //生成一个 bundle + sourcemap

    format: 'umd',

    moduleName: 'main',

    dest: 'rel/bundle.js',

  });

 

  fs.writeFileSync( 'rel/bundle.js', result.code );


SourceMap


为了方便调试编译后的文件,rollup 肯定不会忘记添加 source map 功能,而且其配置也非常简单:


{

        format: 'umd',

        moduleName: 'main',

        dest: 'rel/bundle.js',

        sourceMap: true   //加上这里即可

}


这样编译后,rollup 会自动生成一个 rel/bundle.js.map 关联到 rel/bundle.js 中。


也可以将其直接内联在 bundle 里而不是独立生成一个 map 文件:


{

        format: 'umd',

        moduleName: 'main',

        dest: 'rel/bundle.js',

        sourceMap: 'inline'

 }


若希望 map 文件可以自定义位置和名称,就得使用上面稍微提到的 bundle.generate 方法了:


var result = bundle.generate({ //生成一个 bundle + sourcemap

    //...

  });

 

  fs.writeFileSync( 'rel/bundle.js', result.code );

  fs.writeFileSync( 'map/bundle.js.map', result.map.toString() );


issue


Rollup 虽然利用 ES6 的特性帮咱节省了不少文件大小,但它并没有类似 webpack 的 -p 参数帮你压缩混淆文件。


因此即使是官方文档也推荐配合使用 UglifyJS 来进一步缩小 bundle 体积。


另外 webpack2 已经出来好几款 beta 版本了,同样也加上了对 Tree-shaking 的支持,相信 webpack2 出来后,Rollup 的热度会大大消减。


共勉~



【今日微信公号推荐↓】

更多推荐请看值得关注的技术和设计公众号


其中推荐了包括技术设计极客 和 IT相亲相关的热门公众号。技术涵盖:Python、Web前端、Java、安卓、iOS、PHP、C/C++、.NET、Linux、数据库、运维、大数据、算法、IT职场等。点击《值得关注的技术和设计公众号》,发现精彩!


 
前端大全 更多文章 详解Javascript中的Object对象 结合个人经历总结的前端入门方法 前端不为人知的一面–前端冷知识集锦 一份优秀的前端开发工程师简历是怎么样的? 浅谈Web缓存
猜您喜欢 重新敲一遍代码,胜过拷贝粘贴 数据库设计的 7 个常见错误 史上最丧心病狂的健身app,老美跑个步也是够拼的... 人工智能专家成香饽饽 看各大公司如何挖墙脚 1号店订单系统的SOA治理落地实践之路