冗余代碼都走開——前端模塊打包利器 Rollup.js 入門


之前翻譯過一篇文章,介紹了通過 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 的熱度會大大消減。

共勉~

donate

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM