JS打包工具rollup——徹底入門指南

前言

最近在作一個提供給瀏覽器和node同時使用的js的url模板工具類,在用什麼打包工具上糾結了一段時間,正好有一天在知乎上看到了關於rollup的介紹,在本身試了試以後,就決定用rollup.js來打包本身的工具類了。javascript

這篇文章主要是爲了讓對rollup.js也有興趣的同窗可以快速入門rollup的使用方式而寫的,文章除了開始對rollup.js的基本介紹以外,主要用多個demo來介紹rollup.js的不一樣使用方法,以及介紹一些比較經常使用的rollup插件。讀者能夠選擇本身有興趣的部分查看。css

文章博客連接html

本教程相關的全部demo都已上傳到github,rollup-demos,歡迎star。java

rollup.js簡介

rollup.js

首先簡單介紹一下rollup.JS。根據官方的介紹,rollup.js是一個模塊打包工具,能夠幫助你從一個入口文件開始,將全部使用到的模塊文件都打包到一個最終的發佈文件中(極其適合構建一個工具庫,這也是我選擇用rollup來打包的緣由)。node

rollup.js有兩個重要的特性,其中一個就是它使用ES6的模塊標準,這意味着你能夠直接使用importexport而不須要引入babel(固然,在如今的項目中,babel能夠說是必用的工具了)。jquery

rollup.js的另外一個重要特性叫作'tree-shaking',這個特性能夠幫助你將無用代碼(即沒有使用到的代碼)從最終的生成文件中刪去。舉個例子,我在A.js文件中定義了A1和A2兩個方法,同時在B文件中引入了這兩個方法,可是在B文件中只引入了A文件中的A1方法,那麼在最後打包B文件時,rollup就不會將A2方法引入到最終文件中。(這個特性是基於ES6模塊的靜態分析的,也就是說,只有export而沒有import的變量是不會被打包到最終代碼中的)webpack

rollup.js實例

demo0 開始使用rollup

初始化一個工程,建立一個依賴模塊文件lib.js和入口文件index.js。git

export function logA() {
    console.log('function logA called')
}

export function logB() {
    console.log('function logB called')
}
import { logA } from './lib'

logA()

如今咱們要把lib.js和index.js打包成dist.js,首先要作的就是安裝rollup.js。es6

在這裏咱們有兩種安裝方法:github

  1. 全局安裝:

打開你的命令行,輸入npm install rollup -g,等待rollup安裝完畢。安裝完成以後,試着輸入rollup -v來查看一下rollup是否安裝成功了

查看rollup版本

成功安裝完rollup以後,進入到工程目錄下,輸入打包命令rollup index.js -o dist.js,index.js 是咱們的入口文件, -o 表示輸出文件的路徑,在 -o 後面跟着的 dist.js 就是咱們要生成的最終打包文件了。(其實這裏原本應該加上一個參數-i,用來表示入口文件的路徑,但rollup是會把沒有加參數的文件默認爲是入口文件,所以咱們在這裏省略了這個參數)

使用全局rollup進行打包

顯示出這條信息以後,咱們發現目錄下已經多出了一個 dist.js 文件,打開文件,咱們發現裏面的代碼是這樣的

function logA() {
    console.log('function logA called');
}

logA();

此時咱們就已經完成了打包做業,能夠將dist.js引入到HTML文件或是node模塊中了

  1. 項目本地安裝:

進入到項目目錄,打開命令行輸入npm install rollup --save-dev,把rollup加入到開發依賴中,而後在命令行中輸入./node_modules/.bin/rollup index.js -o dist.js

使用項目本地rollup進行打包

或者在package.json文件中添加npm scripts命令"build": "rollup index.js -o dist.js",在命令行中輸入npm run build來進行打包

使用項目本地rollup進行打包

在打包完成以後,咱們查看一下效果,新建一個index.html文件,在這個文件中引入咱們打包出來的dist.js文件

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>rollup 打包測試</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
    </head>
    <body>
        <script src="./dist.js"></script>
    </body>
</html>

用瀏覽器打開index.html文件,打開控制檯,咱們能夠看到控制檯上輸出了一行文字

rollup打包文件測試

使用命令行運行dist.js文件,咱們也能夠看到命令行中輸出了一行文字

rollup打包文件測試

這說明咱們的打包文件dist.js是能夠運行的,打包成功。

PS:

  1. 接下來的demo中,默認在項目內安裝了rollup
  2. 接下來的demo中,非必要狀況下不會對打包結果進行運行結果測試,讀者若須要驗證打包效果,請本身編寫其餘測試代碼。

demo1 使用rollup進行模塊化打包

在以前打包的過程當中,命令行中輸出了一行No format option was supplied – defaulting to 'es',這表示rollup並無收到任何模塊化的格式指令,所以會用默認的es模塊標準來對文件進行打包。

若是在demo0中的index.js文件中把logA()改爲export default logA(),那麼rollup最後的打包結果就會是

function logA() {
    console.log('function logA called');
}

var index = logA();

export default index;

顯然這樣的代碼是不能直接在瀏覽器端和node端運行的,咱們須要把原先的ES6模塊轉化成瀏覽器和node支持的形式。

那麼要去哪裏找rollup把ES6代碼轉化成其餘形式的方法呢?這裏有兩個方案,一是去rollup的官網找相關的資料,二是使用rollup命令行的幫助命令,看看能不能找到相關的參數

咱們使用rollup命令行的幫助命令,在命令行中輸入rollup -h

rollup命令行幫助

在這裏咱們能夠看到相似版本號,幫助,使用配置文件等一系列參數。在這裏咱們能夠找到-f這個參數,他的說明是輸出的類型(amd,cjs,es,iife,umd),從括號裏的內容咱們能夠看出,使用這個參數能夠肯定打包完後的文件的模塊處理方式。(若是你還不知道這幾種模塊之間的區別,建議先去找一下相關的資料學習一下)

接下來咱們用rollup來打包一下,在demo0中的index.js文件裏將logA()改爲export default logA(),在package.json文件中寫好不一樣模塊的打包命令

"build:amd": "rollup index.js -f amd -o ./dist/dist.amd.js",
"build:cjs": "rollup index.js -f cjs -o ./dist/dist.cjs.js",
"build:es": "rollup index.js -f es -o ./dist/dist.es.js",
"build:iife": "rollup index.js -f iife -n result -o ./dist/dist.iife.js",
"build:umd": "rollup index.js -f umd -n result -o ./dist/dist.umd.js",
"build:all": "npm run build:amd && npm run build:cjs && npm run build:es && npm run build:iife && npm run build:umd"

在這裏咱們發如今設置模塊爲iife(當即執行函數)和umd時,還加上了一個參數-n,這是由於咱們將logA()的結果設置爲模塊的輸出結果,那麼在使用iife和umd時,須要事先設定模塊的名稱,才能讓其餘人經過這個模塊名稱引用到你的模塊的輸出結果。

在命令行中輸入npm run build:all,運行全部打包命令,查看效果

demo1打包結果

能夠看到已經輸出了5種不一樣模塊標準的打包文件,因爲字數緣由,在這裏咱們只查看一個打包文件(dist.iife.js)的內容

var result = (function () {
'use strict';

function logA() {
    console.log('function logA called');
}

var index = logA();

return index;

}());

能夠看到全部代碼都被打包到了一個當即執行函數中,而且將函數的返回值(模塊的輸出內容)賦值給了一個全局變量,而這個全局變量的名稱就是咱們以前設置的模塊名稱。

PS: 使用amd模塊打包方式時,若不指定模塊名稱,則會打包成匿名函數,若想打包成一個具名函數,則須要使用-u--id來指定具名函數名稱。

除了-f以外,還有許多其餘的參數可使用,看到這裏可能有些同窗會以爲麻煩了,這麼多參數用起來好麻煩,每次都要輸一長串的命令,那麼有沒有更好的方法來控制rollup的參數配置呢?

固然有,接下來咱們就嘗試使用配置文件來控制rollup打包。

demo2 使用配置文件來進行rollup打包

建立一個demo2,沿用以前demo1的內容,咱們在demo2的項目下建立一個文件,取名爲rollup.config.js,這個文件就是rollup的配置文件了,rollup根據配置文件的輸出配置來進行打包,接下來咱們在配置文件中輸入配置代碼:

export default {
  entry: 'index.js',
  format: 'cjs',
  dest: './dist/dist.js'
}

entry表示打包的入口文件,format表示要打包成的模塊類型,dest表示輸出文件的名稱路徑

PS: 若使用iife或umd模塊打包,須要添加屬性moduleName,用來表示模塊的名稱;若用amd模塊打包,能夠配置amd相關的參數(使用umd模塊模式時,也會使用到amd相關配置參數):

amd: {
    id: 'amd-name',   // amd具名函數名稱
    define: 'def'     // 用來代替define函數的函數名稱
}

在這裏咱們發現配置文件也是使用了ES6語法,這是由於rollup能夠本身處理配置文件,因此可以直接用ES6的模塊輸出(固然,你也能夠選擇使用node的module.exports方式來輸出配置。

在package.json文件中編寫npm scripts命令

"build": "rollup -c"

-c這個參數表示使用配置文件來進行打包,若後面沒有指定使用的配置文件路徑,則使用默認的配置文件名稱rollup.config.js

在命令行中輸入npm run build,執行打包,能夠看到生成了打包文件dist.js

'use strict';

function logA() {
    console.log('function logA called');
}

var index = logA();

module.exports = index;

進階: 當rollup配置文件最終輸出的不是一個對象而是一個數組時,rollup會把每個數組元素當成一個配置輸出結果,所以能夠在一個配置文件內設置多種輸出配置

例如,咱們添加一個indexB.js文件,在這個文件中咱們將logA替換爲logB,並將rollup配置文件改成:

export default [{
  entry: 'index.js',
  format: 'cjs',
  dest: './dist/distA.js'
},{
  entry: 'indexB.js',
  format: 'iife',
  moduleName: 'indexB',
  dest: './dist/distB.js'
}]

運行打包命令,發如今dist目錄下生成了distA.js和distB.js兩個文件,說明多項配置打包成功。

除了上面這種輸出一個配置數組以外,你還能夠經過配置target屬性來輸出多個打包文件:

export default {
  entry: 'index.js',
  targets: [{
      dest: 'dist/bundle.cjs.js',
      format: 'cjs'
    },
    {
      dest: 'dist/bundle.umd.js',
      moduleName: 'res',
      format: 'umd'
    },
    {
      dest: 'dist/bundle.es.js',
      format: 'es'
    },
  ]
}

這樣配置會在dist目錄下面輸出bundle.cjs.jsbundle.umd.jsbundle.es.js三個打包文件,同時umd模塊的名稱會被定義成res。

demo3 監聽文件變化,隨時打包

咱們在開發過程當中,須要頻繁對源文件進行修改,若是每次都本身手動輸一遍打包命令,那真的是要煩死。所以,咱們選擇使用rollup提供的監聽功能,安裝rollup-wacth模塊,再在rollup命令後面加上-w參數,就能讓rollup監聽文件變化,即時打包。

安裝watch包:

npm i rollup-watch --save-dev
// or
yarn add rollup-watch --dev

編寫npm scripts:

"dev": "rollup -c -w"

執行npm run dev,看到下面的提示:

rollup 監聽文件變化

好了,這個時候你就能夠隨便修改你的源文件了,rollup會自動爲你打包的。

PS: 如果你不想監聽某些文件,只要在配置文件中加上

watch: {
    exclude: ['path/to/file/which/you/want/to/ignore']
}

就好了,其中的exclude表示你想要忽略的文件的路徑(支持glob模式匹配)

demo4 是時候寫ES6了

ES6能夠說是現代JS開發100%會用到的技術了,rollup雖然支持瞭解析importexport兩種語法,可是卻不會將其餘的ES6代碼轉化成ES5代碼,所以咱們在編寫ES6代碼的時候,須要引入插件來支持ES6代碼的解析。

  1. 安裝插件和你須要的babel preset:
npm i rollup-plugin-babel babel-preset-es2015 --save-dev
// or
yarn add rollup-plugin-babel babel-preset-es2015 --dev
  1. 建立.babalrc文件:
{
  "presets": [
    ["es2015", {
        "modules": false
    }]
  ]
}

之因此使用modules:false這個參數,是由於rollup默認是經過ES6模塊語法來解析文件間的依賴,rollup默認是不支持解析common.js的模塊規範的(怎麼讓rollup支持我會在接下來的demo中講解),所以須要讓babel不轉化模塊相關的語法,否則rollup在使用過程當中會報錯。

  1. 編寫rollup配置文件:
import babel from 'rollup-plugin-babel';

export default [{
  entry: 'index.js',
  format: 'iife',
  dest: './dist/dist.js',
  plugins: [
    babel({
      exclude: 'node_modules/**'
    })
  ]
}]

rollup的配置文件的plugins屬性可讓你添加在rollup打包過程當中所要用到的插件,可是要注意的是,插件的添加順序決定了它們在打包過程當中的使用順序,所以要注意配置文件的插件使用順序。

  1. 編寫ES6代碼

在這裏咱們新建三個文件,兩個類Person和Man和一個入口文件index.js

export default class Person {
    constructor (name, gender = '男') {
        this.name = name
        this.gender = gender
    }

    say () {
        console.log(`個人名字是${this.name},是一個${this.gender}生`)
    }
}
import Person from './Person'

export default class Man extends Person {
    constructor (name) {
        super(name, '男')
    }
}
import Man from './src/Man'

new Man('KainStar').say()
  1. 運行打包命令npm run build

rollup babel打包1

能夠看到rollup輸出了一段提示文字,咱們先不去管它,先看看打包出來的文件能不能運行,執行node dist/dist.js

rollup babel打包2

能夠看到代碼運行成功了,那麼咱們回來繼續看以前的提示文字,它的意思是'classCallCheck'這個babel helper函數使用了屢次,rollup推薦咱們使用external-helpers這個插件或es2015-rollup這個babel-preset來簡化打包出來的代碼。

咱們查看一下打包出來的dist.js文件,發現_classCallCheck這個函數被定義了兩次,分別被取名爲_classCallCheck和_classCallCheck$1,這樣的代碼確定是能夠簡化的,所以咱們引入external-helpers這個插件:

npm i babel-plugin-external-helpers --save-dev
// or
yarn add babel-plugin-external-helpers --dev

修改.babelrc文件爲

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

或者在配置文件中使用babel配置

plugins: [
    babel({
        plugins: ['external-helpers']
    })
]

注意! 在rollup-plugin-babel的官方github倉庫中有一段配置是這樣的:

plugins: [
    babel({
      plugins: ['external-helpers'],
      externalHelpers: true
    })
]

這段配置的使用要求是你須要設置全局的babelHelpers對象,以此來將打包文件中的babel相關代碼刪除,因此通常狀況下不須要使用externalHelpers這個屬性。

PS: 你也可使用babel-preset-es2015-rollup這個包(搭配babel-core),它集成了babel-preset-es2015,babel-plugin-transform-es2015-modules-commonjs和babel-plugin-external-helpers三個模塊,使用起來更加方便,只要將.babelrc文件修改爲{ "presets": ["es2015-rollup"] }就可使用了。

demo5 解析cjs,打包第三方模塊

有時候咱們會引入一些其餘模塊的文件(第三方的或是本身編寫的),可是這些第三方的模塊爲了可以直接使用,每每不是ES6模塊而是用commonjs的模塊方式編寫的,這個時候咱們須要將commonjs的模塊轉化爲ES6模塊,這樣才能讓rollup進行正確的解析。

  1. 解析commonjs

解析commonjs須要引入一個rollup插件——rollup-plugin-commonjs

安裝插件

npm i rollup-plugin-commonjs --save-dev
// or
yarn add rollup-plugin-commonjs --dev

在配置文件中配置插件

import commonjs from 'rollup-plugin-commonjs'

export default {
  entry: 'index_cjs.js',
  format: 'iife',
  dest: './js/dist_cjs.js',
  plugins: [
    commonjs()
  ]
}

編寫cjs模塊的文件

exports.logA = function logA() {
    console.log('function logA called')
}

exports.logB = function logB() {
    console.log('function logB called')
}

執行打包,能夠看到打包成功,也沒有輸出任何提示信息

rollup cjs打包

  1. 打包第三方模塊

在打包第三方模塊的過程當中,rollup沒法直接解析npm模塊,所以須要引入插件rollup-plugin-node-resolve並配合以前的commonjs插件來解析這些第三方模塊

安裝插件和第三方模塊

npm i rollup-plugin-node-resolve lodash --save-dev
// or
yarn add rollup-plugin-node-resolve lodash --dev

在配置文件中配置插件

import commonjs from 'rollup-plugin-commonjs'
import resolve from 'rollup-plugin-node-resolve'

export default {
  entry: 'index_module.js',
  format: 'iife',
  dest: './js/dist_module.js',
  plugins: [
    resolve({
      jsnext: true,
      main: true,
      browser: true
    }),
    commonjs()
  ]
}

jsnext表示將原來的node模塊轉化成ES6模塊,main和browser則決定了要將第三方模塊內的哪些代碼打包到最終文件中。

因爲commonjsnode-resolve中的配置屬性不少,所以不一一解釋,但願瞭解更多的同窗能夠去官方倉庫查看說明。

編寫入口文件

import compact from 'lodash/compact'

const array = [0, 1, false, 2, '', 3]
const compctedArray = compact(array)
console.log(compctedArray)

在這裏咱們只引用了lodash中的compact方法,那麼在最終代碼裏,應該也只會添加compact方法的代碼。

執行打包命令,查看打包出來的文件:

(function () {
'use strict';

/**
 * Creates an array with all falsey values removed. The values `false`, `null`,
 * `0`, `""`, `undefined`, and `NaN` are falsey.
 *
 * @static
 * @memberOf _
 * @since 0.1.0
 * @category Array
 * @param {Array} array The array to compact.
 * @returns {Array} Returns the new array of filtered values.
 * @example
 *
 * _.compact([0, 1, false, 2, '', 3]);
 * // => [1, 2, 3]
 */
function compact(array) {
  var index = -1,
      length = array == null ? 0 : array.length,
      resIndex = 0,
      result = [];

  while (++index < length) {
    var value = array[index];
    if (value) {
      result[resIndex++] = value;
    }
  }
  return result;
}

var compact_1$1 = compact;

const array = [0, 1, false, 2, '', 3];
const compctedArray = compact_1$1(array);
console.log(compctedArray);

}());

確實只添加了compact方法的代碼,而沒有將lodash所有引入。

demo6 不要打包到一個文件,爲rollup設置外部模塊和全局變量

在平時的開發中,咱們常常會引入其餘的模塊,可是在使用的時候,咱們又不想把它們打包到一個文件裏,想讓他們做爲單獨的模塊(或文件)來使用,方便瀏覽器端進行緩存,這個時候就須要使用配置文件中的external屬性了

咱們在demo5的基礎上,把jquery安裝到第三方模塊中

npm i jquery --save-dev
// or
yarn add jquery --dev

將配置文件改爲

import commonjs from 'rollup-plugin-commonjs'
import resolve from 'rollup-plugin-node-resolve'

export default {
  entry: 'index.js',
  format: 'iife',
  dest: './js/dist.js',
  external: ['jquery'],
  plugins: [
    resolve({
      jsnext: true,
      main: true,
      browser: true
    }),
    commonjs()
  ]
}

external用來表示一個模塊是否要被當成外部模塊使用,屬性的值能夠是一個字符串數組或一個方法,當傳入的是一個字符串數組時,全部數組內的模塊名稱都會被當成是外部模塊,不會被打包到最終文件中

當傳入的是一個方法時,方法有一個參數id,表示解析的模塊的名稱,咱們能夠自定義解析方式,如果要當作外部模塊不打包到最終文件中,則返回true,若要一塊兒打包到最終文件中,則返回false

在這裏咱們把jquery當成一個外部模塊,執行打包命令:

rollup 添加外部模塊

檢查打包出來的文件,咱們發現lodash的compact方法依舊被打包進了最終文件中,可是jquery卻沒有被打包進去,而是以$的全局變量形式被傳入到了當即執行函數中。

在這裏rollup又給咱們輸出了一條提示信息,意思是咱們沒有在配置文件中給外部模塊jquery設置全局變量名稱,所以rollup本身猜想了一個名稱$,當成是依賴的全局變量名。

若是直接使用全局的$的話,可能會由於變量$被其餘引入的代碼覆蓋而報錯,所以咱們要將$替換爲不容易衝突的jQuery變量,在配置文件中添加globals屬性:

globals: {
    jquery: 'jQuery'
}

globals的值是一個對象,key表示使用的模塊名稱(npm模塊名),value表示在打包文件中引用的全局變量名,在這裏咱們就是把jquery模塊的全局變量名設置爲jQuery,從新打包

在從新打包出來的文件中,咱們發現最後傳入的參數已經由$變爲了jQuery,並且rollup也沒有輸出提示信息。

demo7 打包node內置模塊

有時候咱們想要在瀏覽器端使用node自帶的一些內置模塊,通常狀況下會使用browserify這個工具來打包,可是browserify打包出來的文件實在太大,所以咱們用rollup選擇性地導入咱們須要的node內置模塊

安裝插件

npm i rollup-plugin-node-builtins --save-dev
// or
yarn add rollup-plugin-node-builtins --dev

PS: node-builtins對不一樣的node內置模塊支持不一樣,有些模塊可能須要使用其餘的插件(例如rollup-plugin-node-globals)才能正常打包,具體的支持狀況能夠查看node-builtins的官方倉庫

編寫配置文件

import builtins from 'rollup-plugin-node-builtins'

export default {
  entry: 'index.js',
  format: 'iife',
  dest: './dist/dist.js',
  plugins: [
    builtins()
  ]
}

編寫入口文件

import { join } from 'path'

const path_base = 'E://node'
const path_joined = join(path_basem, 'bin')
console.log(path_joined)

在這裏咱們使用node內置的path模塊,運行打包命令,發現dist.js文件中引入了額外的100多行代碼,這100多行代碼就實現了path模塊的join方法供咱們使用。

PS: 我建議,若是不是必要的狀況,最好可以使用其餘人編寫的第三方實現庫或本身造輪子實現,而不是使用node內置的模塊,由於在引用某些模塊時,node-builtins可能會引入過多的代碼,這樣會大大增長最後打包的文件的大小,使用他人的第三方庫或本身的實現可控性更高

demo8 配合CDN來使用rollup

有時候咱們可能會使用CDN服務器上的js文件,可是又不想在本地安裝一個相同的模塊(也有可能沒有對應的模塊),可能在版本升級的時候會產生一些問題,這個時候咱們就須要使用rollup的paths屬性了,這個屬性能夠幫助你把依賴的代碼文件地址注入到打包以後的文件裏。

編寫配置文件

export default {
  entry: 'index.js',
  format: 'amd',
  dest: './dist/dist.js',
  external: ['jquery'],
  paths: {
    jquery: 'https://cdn.bootcss.com/jquery/3.2.1/jquery.js'
  }
}

在這裏咱們要使用cdn上的jquery文件,paths屬性的值能夠是一個對象或用法與external屬性方法類似的方法(只是返回的不是boolean值而是文件的地址)。若使用對象來表示,則key值爲須要引入的模塊名稱,value值爲對應的文件地址

編寫源文件

import $ from 'jquery'

$('#p').html('rollup 使用paths屬性配合CDN')

執行打包命令,最後打包出來的文件內容是:

define(['https://cdn.bootcss.com/jquery/3.2.1/jquery.js'], function ($) { 'use strict';

$ = $ && $.hasOwnProperty('default') ? $['default'] : $;

$('#p').html('rollup 使用paths屬性配合CDN');

});

能夠看到rollup已經把咱們須要的CDN地址做爲依賴加入到了打包文件中。

demo9 最小化你的代碼

代碼發佈時,咱們常常會把本身的代碼壓縮到最小,以減小網絡請求中的傳輸文件大小。

rollup的插件rollup-plugin-uglify就是來幫助你壓縮代碼的,咱們接下來就用這個插件來壓縮一下咱們的代碼

npm i rollup-plugin-uglify --save-dev
// or
yarn add rollup-plugin-uglify --dev

編寫配置文件

import uglify from 'rollup-plugin-uglify'

export default {
  entry: 'index.js',
  format: 'iife',
  dest: './dist/dist.js',
  plugins: [
    uglify()
  ]
}

運行打包命令,查看dist.js文件,發現代碼已經被壓縮了

可是,壓縮過的代碼在debug時會帶來很大的不便,所以咱們須要在壓縮代碼的同時生成一個sourceMap文件

幸運的是,rollup本身就支持sourceMap文件的生成,不須要咱們去引入其餘插件,只須要在配置文件中加上:

sourceMap: true

就能夠了。

從新打包,咱們發現不只生成了dist.js.map文件,並且dist文件最後加上了一行//# sourceMappingURL=dist.js.map,而且在瀏覽器中能夠正確加載源文件

rollup sourceMap

PS: 如果將sourceMap屬性的值設置爲inline,則會將sourceMap的內容添加到打包文件的最後。

demo10 爲你的代碼添eslint檢查

在大型工程的團隊開發中,咱們須要保證團隊代碼風格的一致性,所以須要引入eslint,並且在打包時須要檢測源文件是否符合eslint設置的規範,如果不符合則拋出異常並中止打包。在這裏咱們使用rollup的eslint插件rollup-plugin-eslint:

安裝插件

npm i eslint rollup-plugin-eslint --save-dev
// or
yarn add eslint rollup-plugin-eslint --dev

編寫eslint配置文件.eslintrc

{
    "env": {
        "browser": true,
        "commonjs": true,
        "es6": true,
        "node": true
    },
    "parserOptions": {
        "ecmaFeatures": {
            "jsx": false
        },
        "sourceType": "module"
    },
    "rules": {
        "semi": ["error","never"]
    }
}

在這裏咱們強制要求不使用分號,而後在源文件中加上一個分號

foo(element);

編寫rollup配置文件

import eslint from 'rollup-plugin-eslint';

export default {
  entry: './src/index.js',
  format: 'iife',
  dest: './dist/dist.js',
  plugins: [
    eslint({
      throwOnError: true,
      throwOnWarning: true,
      include: ['src/**'],
      exclude: ['node_modules/**']
    })
  ]
}

eslint插件有兩個屬性須要說明:throwOnError和throwOnWarning設置爲true時,若是在eslint的檢查過程當中發現了error或warning,就會拋出異常,阻止打包繼續執行(若是設置爲false,就只會輸出eslint檢測結果,而不會中止打包)

執行打包命令,發現eslint在輸出了檢查結果以後拋出了異常,並且dist.js文件也沒有生成

rollup eslint拋出異常

刪除index.js文件中的分號,從新打包,發現打包成功

進階: 在平時的開發過程當中,咱們常常會使用IDE或編輯器的eslint插件,以便提前發現問題,可是有時候這些插件會去檢查打包完的文件,致使你的提示框裏一直會有eslint檢測到錯誤的消息

咱們如今有兩種解決方案,一是建立一個.eslintignore文件,將打包文件加進去,讓eslint忽略這個文件

還有一種就是讓rollup在打包文件的開始和最後自動生成註釋來阻止eslint檢測代碼,使用這種方法時,須要使用rollup配置文件的兩個屬性:banner和footer,這兩個屬性會在生成文件的開頭和結尾插入一段你自定義的字符串。咱們利用這個屬性,在打包文件的開頭添加/*eslint-disable */註釋,讓eslint不檢測這個文件。

添加banner和footer屬性

banner: '/*eslint-disable */'

從新打包,咱們發現打包文件的開頭被插入了這段註釋字符串,並且eslint插件也不報dist.js文件的錯了

/*eslint-disable */
(function () {
'use strict';

// 具體代碼

}());

demo11 控制開發環境和生產環境下的配置

  1. 配置文件的開發/生產環境配置

有時候咱們會須要區分開發環境和生產環境,針對不一樣的打包要求輸出不一樣的打包配置,可是咱們又不想寫rollup.config.dev.jsrollup.config.prod.js兩個文件,由於可能二者之間的區別只是一個uglify插件。

所以,咱們就須要用變量來控制配置文件的輸出內容,rollup命令行給咱們提供了一個設置環境變量的參數--environment,在這個參數後面加上你須要設置的環境變量,不一樣變量間用逗號分隔,用冒號後面的字符串表示對應變量的值(若不加冒號,則默認將值設爲字符串true):

在package.json文件中編寫對應的npm scripts命令:

"dev": "rollup -c --environment NODE_ENV:development",
"build": "rollup -c --environment NODE_ENV:production"

最後修改咱們的rollup配置文件

import uglify from 'rollup-plugin-uglify'

let isProd = process.env.NODE_ENV === 'production'

// 通用的插件
const basePlugins = []
// 開發環境須要使用的插件
const devPlugins = []
// 生產環境須要使用的插件
const prodPlugins = [uglify()]

let plugins = [...basePlugins].concat(isProd ? prodPlugins:devPlugins)
let destFilePath = isProd ? './dist/dist.min.js': './dist/dist.js'

export default {
  entry: 'index.js',
  format: 'iife',
  dest: destFilePath,
  sourceMap: isProd,
  plugins: plugins
}

咱們分別運行兩個npm scripts命令,查看打包的結果:

rollup 開發環境和生產環境打包結果

  1. 源文件開發/生產環境信息注入

上面是在配置文件裏經過變量來改變輸出的配置類型,可是咱們有時候須要將生產環境信息添加到源文件裏,這個時候就須要使用rollup的配置屬性intro和outro了

若是說banner和footer是在文件開始和結尾添加字符串,那麼intro和outro就是在被打包的代碼開頭和結尾添加字符串了,以iife模式來舉例,若是咱們配置了這四個屬性,那麼輸出結果就會是:

// banner字符串
(function () {
'use strict';
// intro字符串

// 被打包的代碼

// outro字符串
}());
// footer字符串

這樣的形式

下面咱們實際使用一下,在index.js文件里加上一段須要依賴的代碼

if (DEVELOPMENT) {
    console.log('處於開發環境')
} else {
    console.log('處於生產環境')
}

而後在咱們的rollup配置文件裏添加:

intro: 'var DEVELOPMENT = ' + !isProd,

這樣,當咱們最後生成的代碼時,就會輸出開發環境或生產環境的提示:

rollup 開發環境和生產環境信息打包結果

  1. 源文件開發/生產環境信息替換

有時候咱們會把開發/生產環境的信息直接寫在源文件裏面,這個時候用intro來注入代碼的方式就不適合了。這個時候咱們就須要使用rollup-plugin-replace插件來對源代碼的變量值進行替換:

安裝插件

npm i rollup-plugin-replace --save-dev
// or
yarn add rollup-plugin-replace --dev

編寫配置文件

const basePlugins = [replace({
  DEVELOPMENT: !isProd
})]

// 將intro屬性註釋掉
// intro: 'var DEVELOPMENT = ' + !isProd,

這裏咱們使用replace插件,以key-value對象的形式,將DEVELOPMENT的值替換爲!isProd的值

執行打包命令,並檢查打包結果:

rollup 開發環境和生產環境信息打包結果

進階: replace除了直接使用key-value的形式替換對應key同名變量的方法以外,還能夠經過配置delimiters參數來實現模板功能:

配置replace插件參數

VERSION: '1.0.0',
delimiters: ['{{', '}}']

經過這個配置,在打包過程當中,{{VERSION}}會被替換成1.0.0

在index.js文件內添加相關代碼

var version = '{{VERSION}}'
console.log('版本 v' + version)

打包的結果

var version = '1.0.0';
console.log('版本 v' + version);

demo12 使用rollup的API

有時候咱們會須要在打包的先後執行一些其餘的代碼,可是又不想引入其餘構建工具(例如gulp),那麼就可使用rollup提供的node API來編寫你本身的打包流程。

rollup模塊只提供了一個rollup函數,這個函數的參數和咱們編寫配置文件時導出的參數不一樣,減小了不少配置屬性,留下來的主要是一些輸入相關的配置。(具體的配置屬性能夠查看rollup wiki的javascript API一節)

執行這個函數返回的是一個Promise,而且在then方法中提供一個bundle對象做爲參數,這個對象保存了rollup對源文件編譯一次以後的結果,並且提供了generatewrite兩個方法

write方法提供了編譯並將打包結果輸出到文件裏的功能,返回的是一個沒有參數的Promise,可讓你自定義接下來執行的代碼

generate方法是隻提供了編譯的功能,返回一個Promise,這個Promise有一個對象參數,包含了code(編譯完以後的代碼)和map(分析出來的sourceMap對象)兩個屬性,通常用在插件開發中

write和gengerate方法都接受有編譯相關屬性的對象做爲傳入的編譯參數,而write方法還額外接受dset屬性做爲導出文件的名稱。

在這裏咱們只使用write方法來編寫一個爲全部模塊類型打包,並輸出打包完畢提示的文件,至於generate的使用方法咱們會放在編寫插件一節中介紹。

const rollup = require('rollup').rollup

rollup({
    entry: 'index.js'
}).then(bundle => {

    // 保存全部Promise的列表
    let writePromiseList = []
    // 聲明全部須要打包的模塊類型
    let moduleTypesList = ['es','cjs','amd','umd','iife']

    moduleTypesList.forEach(function(moduleType) {
        writePromiseList.push(bundle.write({
            dest: './dist/dist.' + moduleType + '.js',
            format: moduleType,
            sourceMap: true
        }))
    })

    return Promise.all(writePromiseList)

}).then(() => {
    console.log('所有模塊格式打包完畢')
    // 其餘代碼
})

將package.json文件內的npm scripts命令修改成

"build": "node rollup.js"

執行打包命令,查看打包結果

rollup 自定義打包結果1

rollup 自定義打包結果2

在這裏咱們能夠看到,一個bundle能夠被重複使用屢次,所以咱們能夠用Promise.all方法來等待全部模塊打包完成後再輸出打包完畢的提示。

demo13 除了打包JS,咱們還能……

一個web項目內確定不會只有js文件,還有css、html(也多是模板文件)和其餘類型的文件,那麼咱們在打包的時候能不能把這些文件一塊兒打包呢?

咱們須要區分一下,在這裏的打包有兩種意思,一種是讓這些文件能夠像JS文件同樣,在源代碼中被import並使用;還有一種是經過在源文件中import這些文件,最後將它們合併到一塊兒並導出到一個最終文件內。

不一樣的rollup插件有不一樣的效果,在使用的時候必定要查看插件的相關說明

安裝插件

npm i rollup-plugin-scss --save-dev
// or
yarn add rollup-plugin-scss --dev

編寫配置文件

import scss from 'rollup-plugin-scss'

export default {
  entry: './src/js/index.js',
  format: 'iife',
  dest: './dist/js/dist.js',
  sourceMap: true,
  plugins: [
    scss({
      output: './dist/css/style.css'
    })
  ]
}

在這裏咱們嘗試編譯和打包scss文件,將其合併成一個style.css文件,並輸出到dist/css目錄下

編寫scss文件

$blue: #69c4eb;

.bg-blue {
    background-color: $blue
}
$white: #fff;

.text-white {
    color: $white;
}

而後在源文件中引用這兩個scss文件

import '../scss/text.scss'
import '../scss/bg.scss'

var html = `
    <div class="bg-blue">
        <p class="text-white">測試文字</p>
    </div>
`

document.body.innerHTML = html

執行打包命令,查看效果

rollup 打包scss效果

extra 編寫你本身的rollup插件

有時候咱們可能須要本身編寫rollup插件來實現需求,rollup官方在wiki上提供了關於編寫插件的一些介紹,下面咱們就根據這些介紹來寫一個本身的rollup插件。

咱們在這裏仿照scss插件編寫一個stylus的rollup插件,讓使用者能夠import stylus文件,並編譯打包導出到指定的目錄下(爲了節省代碼量,只寫了輸出到指定路徑的功能代碼,其餘的功能能夠參考scss插件的具體代碼)。

首先建立項目,在package.json文件中,除了通常信息以外,還要加上

"main": "index.cjs.js",
"module": "index.es.js",
"jsnext:main": "index.es.js"

這些信息用來區分使用不一樣模塊規範時使用的文件

安裝咱們須要用到的模塊

npm i rollup rollup-plugin-babel babel-preset-es2015-rollup babel-core --save-dev
npm i rollup-pluginutils stylus --save
// or
yarn add rollup rollup-plugin-babel babel-preset-es2015-rollup babel-core --dev
yarn add rollup-pluginutils stylus

rollup-pluginutils和stylus是咱們運行時須要的兩個模塊,stylus用來解析stylus文件,pluginutils則提供給了咱們一些編寫插件經常使用的函數

編寫rollup配置文件

import babel from 'rollup-plugin-babel'

export default {
    entry: './index.es.js',
    dest: './index.cjs.js',
    format: 'cjs',
    plugins: [
        babel()
    ]
}

rollup插件須要一個含有指定屬性的對象做爲插件內容,rollup官方建議咱們在編寫插件的時候,export一個返回值爲插件對象的函數,這樣能夠方便使用者指定插件的參數。

rollup會將解析的部分結果做爲參數調用插件返回的對象中的一些函數屬性,這些函數會在合適的時候被rollup調用(至關於rollup在執行各個操做時的鉤子函數),下面咱們介紹一些經常使用的屬性:

  • name:插件的名稱,提供給rollup進行相關信息的輸出
  • load:不指定這個屬性時,解析模塊會默認去讀取對應路徑文件的內容;而當該值爲函數(id => code)時,能夠將函數最後的返回值做爲文件的內容提供給rollup(能夠用來生成自定義格式的代碼)
  • resolveId:一個( (importee, importer) => id)形式的函數,用來解析ES6的import語句,最後須要返回一個模塊的id
  • transform:最常使用的屬性,是一個函數,當rollup解析一個import時,會獲取到對應路徑文件的內容,並將內容和模塊的名稱做爲參數提供給咱們;這個函數執行完畢以後,須要返回一個做爲代碼的字符串或是相似{ code, map }結構的對象,用來表示解析完以後該模塊的實際內容,map指的是sourceMap,而若是咱們沒有要導出的sourceMap,就能夠將返回的map值設爲{mappings: ''}
  • ongenerate:當咱們或rollup調用generate方法時,會被調用的一個鉤子函數,接受generate的option做爲參數
  • onwrite:和ongenerate同樣,調用write方法時,會被調用的一個鉤子函數,接受write的option做爲參數

通常狀況下,咱們經過transform函數來獲取文件的id和內容,並對內容作一些處理,若須要輸出文件則使用ongenerate或onwrite在rollup打包的最後階段來作相應的輸出。

load和resolveId在通常狀況下不會使用,除非你有特殊的需求(例如對路徑、模塊id進行修改等)

根據上面這些內容,咱們編寫具體的插件內容

import { createFilter } from 'rollup-pluginutils'
import fs from 'fs'
import path from 'path'
import stylus from 'stylus'

// 遞歸建立文件夾
function mkdirs(dir) {
    return new Promise((resolve, reject) => {
        fs.exists(dir, (exist) => {
            if (exist) {
                resolve()
            } else {
                mkdirs(path.dirname(dir)).then(() => {
                    fs.mkdir(dir, (err) => {
                        if (err) {
                            reject()
                        } else {
                            resolve()
                        }
                    })
                })
            }
        })
    })
}

// 導出一個function
export default function stylusPlugin(options = {}) {
    // 建立一個文件過濾器,過濾以css,styl結尾的文件
    const stylusFilter = createFilter(options.include || ['**/*.css', '**/*.styl'], options.exclude)

    // dest用來保存指定的輸出路徑
    let dest = options.output,
    // styleNodes用來暫存不一樣文件的css代碼
        styleNodes = {}

    // 編譯stylus文件
    function complier(str, stylusOpt) {
        return new Promise((resolve, reject) => {
            stylus.render(str, stylusOpt, (err, css) => {
                if (err) {
                    reject(err)
                } else {
                    resolve(css)
                }
            })
        })
    }

    return {
        // 插件名稱
        name: 'rollup-plugin-stylus',

        // 解析import時調用,獲取文件名稱和具體代碼,將它們保存起來
        transform (code, id) {
            if (!stylusFilter(id)) {
                return
            }

            styleNodes[id] = code
            return ''
        },
        // generate時調用,用stylus解析代碼,並輸出到指定目錄中
        async ongenerate (genOpt) {
            let css = ''
            for (let id in styleNodes) {
                // 合併全部css代碼
                css += styleNodes[id] || ''
            }

            // 編譯stylus代碼
            if (css.length) {
                try {
                    css = await complier(css, Object.assign({}, options.stylusOpt))
                } catch (error) {
                    console.log(error)
                }
            }

            // 沒有指定輸出文件路徑時,設置一個默認文件
            if (typeof dest !== 'string') {
                if (!css.length) {
                    return
                }

                dest = genOpt.dest || 'bundle.js'
                if (dest.endsWith('.js')) {
                    dest = dest.slice(0, -3)
                }
                dest = dest + '.css'
            }

            // 建立目錄,並將css寫入到結果文件內
            await mkdirs(path.dirname(dest))
            return new Promise((resolve, reject) => {
                fs.writeFile(dest, css, (err) => {
                    if (err) {
                        reject(err)
                    } else {
                        resolve()
                    }
                })
            })
        }
    }
}

這樣,一個解析並打包stylus文件的rollup插件就寫好了,你能夠在你的工程中引用這個文件,也能夠將其做爲一個模塊發佈,以便於分享給其餘人使用。

總結 and 一個完整的rollup項目的模板

rollup在打包JS上是一個十分快捷方便的工具,但和webpack相比,他的生態圈仍是不夠強大,對於大型web工程的適應度相對不足

rollup的優勢在於方便的配置,自然的ES6模塊支持讓咱們能夠直接使用import和export語法,在打包JS上,不實現本身的模塊機制,而是使用目前常見的模塊規範有助於其餘工具(例如requirejs)來引用打包文件;tree-shaking的特性也有助於減小代碼量,所以我認爲rollup比起構建應用工程項目,更適合用來構建一個JS庫或node模塊

我將上面介紹的插件集合到一塊兒,添加了測試的支持,製做了一個較爲完整的rollup工程模板。放在rollup-project-template目錄下,須要的同窗能夠自取(你也能夠增長或刪除任意你須要的模塊,來組建屬於你本身的rollup項目模板)

參考資料

相關文章
相關標籤/搜索