10分鐘快速進階rollup.js

前言

上一篇教程中,爲你們介紹了rollup.js的入門技巧,沒有讀過的小夥伴能夠點擊這裏,本次咱們將繼續對rollup.js的進階技巧進行探討,想直接看結論的小夥伴能夠直接看最後一章。node

rollup.js插件

rollup.js的插件採用可拔插設計,它幫助咱們加強了rollup.js的基礎功能,下面我將重點講解四個rollup.js最經常使用的插件。webpack

resolve插件

爲何須要resolve插件?

上一篇教程中,咱們打包的對象是本地的js代碼和庫,但實際開發中,不太可能全部的庫都位於本地,咱們會經過npm下載遠程的庫。這裏我專門準備了一些測試庫,供你們學習rollup.js使用,首先下載測試庫:es6

npm i -S sam-test-data
複製代碼

sam-test-data庫默認提供了一個UMD模塊,對外暴露了兩個變量a和b以及一個random函數,a是0到9之間的一個隨機整數,b是0到99之間的一個隨機整數,random函數的參數是一個整數,如傳入100,則返回一個0到99之間的隨機整數,在本地建立測試插件代碼的文件夾:web

mkdir src/plugin
複製代碼

建立測試代碼:npm

touch src/plugin/main.js
複製代碼

寫入如下代碼:json

import * as test from 'sam-test-data'
console.log(test)
export default test.random
複製代碼

先不使用rollup.js打包,直接經過babel-node嘗試運行代碼:api

babel-node
> require('./src/plugin/main.js')
{ a: 1, b: 17, random: [Function: random] }
{ default: [Function: random] }
> require('./src/plugin/main.js').default(100)
41
複製代碼

能夠看到代碼能夠正常運行,下面咱們嘗試經過rollup.js打包代碼,添加一個新的配置文件:瀏覽器

touch rollup.plugin.config.js
複製代碼

寫入如下內容:bash

import { comment } from './comment-helper-es'

export default {
  input: './src/plugin/main.js',
  output: [{
    file: './dist/index-plugin-cjs.js',
    format: 'cjs',
    banner: comment('welcome to imooc.com', 'this is a rollup test project'),
    footer: comment('powered by sam', 'copyright 2018')
  }, {
    file: './dist/index-plugin-es.js',
    format: 'es',
    banner: comment('welcome to imooc.com', 'this is a rollup test project'),
    footer: comment('powered by sam', 'copyright 2018')
  }]
}
複製代碼

這裏我提供了一個comment-helper-es模塊,暴露了一個comment方法,自動讀取咱們的參數,並幫助生成註釋,同時會在註釋上方和下方添加等長的分隔符,感興趣的小夥伴能夠直接拿去用:babel

export function comment() {
  if (arguments.length === 0) {
    return // 若是參數爲0直接返回
  }
  let maxlength = 0
  for (let i = 0; i < arguments.length; i++) {
    const length = arguments[i].toString().length
    maxlength = length > maxlength ? length : maxlength // 獲取最長的參數
  }
  maxlength = maxlength === 0 ? maxlength : maxlength + 1 // 在最長參數長度上再加1,爲了美觀
  let seperator = ''
  for (let i = 0; i < maxlength; i++) {
    seperator += '=' // 根據參數長度生成分隔符
  }
  const c = []
  c.push('/**\n') // 添加註釋頭
  c.push(' * ' + seperator + '\n') // 添加註釋分隔符
  for (let i = 0; i < arguments.length; i++) {
    c.push(' * ' + arguments[i] + '\n') // 加入參數內容
  }
  c.push(' * ' + seperator + '\n') // 添加註釋分隔符
  c.push(' **/') // 添加註釋尾
  return c.join('') // 合併參數爲字符串
}
複製代碼

經過rollup.js打包:

$ rollup -c rollup.plugin.config.js 

./src/plugin/main.js → ./dist/index-plugin-cjs.js, ./dist/index-plugin-es.js...
(!) Unresolved dependencies
https://rollupjs.org/guide/en#warning-treating-module-as-external-dependency
sam-test-data (imported by src/plugin/main.js)
created ./dist/index-plugin-cjs.js, ./dist/index-plugin-es.js in 13ms
複製代碼

能夠看到代碼生成成功了,可是sam-test-data被當作一個外部的模塊被引用,咱們查看dist/index-plugin-es.js源碼:

/** * ============================== * welcome to imooc.com * this is a rollup test project * ============================== **/
import * as test from 'sam-test-data';
import { random } from 'sam-test-data';

console.log(test);

var main = random;

export default main;
/** * =============== * powered by sam * copyright 2018 * =============== **/
複製代碼

和咱們本來寫的代碼幾乎沒有區別,只是經過es6的解構賦值將random函數單獨從sam-test-data獲取,而後賦給變量main並暴露出來。你們試想,若是咱們正在編寫一個Javascript類庫,用戶在引用咱們庫的時候,還須要手動去下載這個庫全部的依賴,這是多麼糟糕的體驗。爲了解決這個問題,將咱們編寫的源碼與依賴的第三方庫進行合併,rollup.js爲咱們提供了resolve插件。

resolve插件的使用方法

首先,安裝resolve插件:

npm i -D rollup-plugin-node-resolve
複製代碼

修改配置文件rollup.plugin.config.js:

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

export default {
  input: './src/plugin/main.js',
  output: [{
    file: './dist/index-plugin-cjs.js',
    format: 'cjs'
  }, {
    file: './dist/index-plugin-es.js',
    format: 'es'
  }],
  plugins: [
    resolve()
  ]
}
複製代碼

從新打包:

$ rollup -c rollup.plugin.config.js 

./src/plugin/main.js → ./dist/index-plugin-cjs.js, ./dist/index-plugin-es.js...
created ./dist/index-plugin-cjs.js, ./dist/index-plugin-es.js in 28ms
複製代碼

能夠看到警告消除了,咱們從新查看dist/index-plugin-es.js源碼:

const a = Math.floor(Math.random() * 10);
const b = Math.floor(Math.random() * 100);
function random(base) {
  if (base && base % 1 === 0) {
    return Math.floor(Math.random() * base) 
  } else {
    return 0
  }
}

var test = /*#__PURE__*/Object.freeze({
  a: a,
  b: b,
  random: random
});

console.log(test);

var main = random;

export default main;
複製代碼

很明顯sam-test-data庫的源碼已經與咱們的源碼集成了。

tree-shaking

下面咱們修改src/plugin/main.js的源碼:

import * as test from 'sam-test-data'
export default test.random
複製代碼

源碼中去掉了console.log(test),從新打包:

rollup -c rollup.plugin.config.js
複製代碼

再次查看dist/index-plugin-es.js源碼:

function random(base) {
  if (base && base % 1 === 0) {
    return Math.floor(Math.random() * base) 
  } else {
    return 0
  }
}

var main = random;

export default main;
複製代碼

咱們發現關於變量a和b的定義沒有了,由於源碼中並無用到這兩個變量。這就是ES模塊著名的tree-shaking機制,它動態地清除沒有被使用過的代碼,使得代碼更加精簡,從而可使得咱們的類庫得到更快的加載速度(容量小了,天然加載速度變快)。

external屬性

有些場景下,雖然咱們使用了resolve插件,但咱們仍然某些庫保持外部引用狀態,這時咱們就須要使用external屬性,告訴rollup.js哪些是外部的類庫,修改rollup.js的配置文件:

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

export default {
  input: './src/plugin/main.js',
  output: [{
    file: './dist/index-plugin-cjs.js',
    format: 'cjs'
  }, {
    file: './dist/index-plugin-es.js',
    format: 'es'
  }],
  plugins: [
    resolve()
  ],
  external: ['sam-test-data']
}
複製代碼

從新打包:

rollup -c rollup.plugin.config.js
複製代碼

查看dist/index-plugin-es.js源碼:

import { random } from 'sam-test-data';

var main = random;

export default main;
複製代碼

能夠看到雖然使用了resolve插件,sam-test-data庫仍被當作外部庫處理

commonjs插件

爲何須要commonjs插件?

rollup.js默認不支持CommonJS模塊,這裏我編寫了一個CommonJS模塊用於測試,該模塊的內容與sam-test-data徹底一致,差別僅僅是前者採用了CommonJS規範,先安裝這個模塊:

npm i -S sam-test-data-cjs
複製代碼

新建一個代碼文件:

touch src/plugin/main-cjs.js
複製代碼

寫入以下代碼:

import test from 'sam-test-data-cjs'
console.log(test)
export default test.random
複製代碼

這段代碼很是簡單,接下來修改rollup.js的配置文件:

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

export default {
  input: './src/plugin/main-cjs.js',
  output: [{
    file: './dist/index-plugin-cjs.js',
    format: 'cjs'
  }, {
    file: './dist/index-plugin-es.js',
    format: 'es'
  }],
  plugins: [
    resolve()
  ]
}
複製代碼

執行打包:

rollup -c rollup.plugin.config.js 

./src/plugin/main-cjs.js → ./dist/index-plugin-cjs.js, ./dist/index-plugin-es.js...
[!] Error: 'default' is not exported by node_modules/_sam-test-data-cjs@0.0.1@sam-test-data-cjs/index.js
https://rollupjs.org/guide/en#error-name-is-not-exported-by-module-
src/plugin/main-cjs.js (1:7)
1: import test from 'sam-test-data-cjs'
          ^
複製代碼

能夠看到默認狀況下,rollup.js是沒法識別CommonJS模塊的,此時咱們須要藉助commonjs插件來解決這個問題。

commonjs插件的使用方法

首先安裝commonjs插件:

npm i -D rollup-plugin-commonjs
複製代碼

修改rollup.js的配置文件:

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

export default {
  input: './src/plugin/main-cjs.js',
  output: [{
    file: './dist/index-plugin-cjs.js',
    format: 'cjs'
  }, {
    file: './dist/index-plugin-es.js',
    format: 'es'
  }],
  plugins: [
    resolve(),
    commonjs()
  ]
}
複製代碼

從新執行打包:

rollup -c rollup.plugin.config.js
複製代碼

打包成功後,咱們查看dist/index-plugin-es.js源碼:

const a = Math.floor(Math.random() * 10);
const b = Math.floor(Math.random() * 100);
function random(base) {
  if (base && base % 1 === 0) {
    return Math.floor(Math.random() * base) 
  } else {
    return 0
  }
}
var _samTestDataCjs_0_0_1_samTestDataCjs = {
  a, b, random
};

console.log(_samTestDataCjs_0_0_1_samTestDataCjs);
var mainCjs = _samTestDataCjs_0_0_1_samTestDataCjs.random;

export default mainCjs;
複製代碼

能夠看到CommonJS模塊被集成到代碼中了,經過babel-node嘗試執行打包後的代碼:

babel-node 
> require('./dist/index-plugin-es')
{ a: 7, b: 45, random: [Function: random] }
{ default: [Function: random] }
> require('./dist/index-plugin-es').default(1000)
838
複製代碼

代碼執行成功,說明咱們的代碼打包成功了。

CommonJS與tree-shaking

咱們修改src/plugin/main-cjs.js的源碼,驗證一下CommonJS模塊是否支持tree-shaking特性:

import test from 'sam-test-data-cjs'
export default test.random
複製代碼

與resolve中tree-shaking的案例同樣,咱們去掉console.log(test),從新執行打包後,再查看打包源碼:

const a = Math.floor(Math.random() * 10);
const b = Math.floor(Math.random() * 100);
function random(base) {
  if (base && base % 1 === 0) {
    return Math.floor(Math.random() * base) 
  } else {
    return 0
  }
}
var _samTestDataCjs_0_0_1_samTestDataCjs = {
  a, b, random
};

var mainCjs = _samTestDataCjs_0_0_1_samTestDataCjs.random;

export default mainCjs;
複製代碼

能夠看到源碼中仍然定義了變量a和b,說明CommonJS模塊不能支持tree-shaking特性,因此建議你們使用rollup.js打包時,儘可能使用ES模塊,以得到更精簡的代碼。

UMD與tree-shaking

UMD模塊與CommonJS相似,也是不可以支持tree-shaking特性的,這裏我提供了一個UMD測試模塊sam-test-data-umd,感興趣的小夥伴能夠本身驗證一下。有的小夥伴可能會問,sam-test-data也是一個UMD模塊,爲何它可以支持tree-shaking?咱們打開sam-test-data的package.json一探究竟:

{
  "name": "sam-test-data",
  "version": "0.0.4",
  "description": "provide test data",
  "main": "dist/sam-test-data.js",
  "module": "dist/sam-test-data-es.js"
}
複製代碼

能夠看到main屬性指向dist/sam-test-data.js,這是一個UMD模塊,可是module屬性指向dist/sam-test-data-es.js,這是一個ES模塊,rollup.js默認狀況下會優先尋找並加載module屬性指向的模塊。因此sam-test-data的ES模塊被優先加載,從而可以支持tree-shaking特性。咱們看一下rollup.js官方的說明:

在 package.json 文件的 main 屬性中指向當前編譯的版本。若是你的 package.json 也具備 module 字段,像 Rollup 和 webpack 2 這樣的 ES6 感知工具(ES6-aware tools)將會直接導入 ES6 模塊版本。

babel插件

爲何須要babel插件?

在src/plugin目錄下建立一個新文件main-es.js:

touch src/plugin/main-es.js
複製代碼

寫入以下代碼:

import { a, b, random } from 'sam-test-data-es'

console.log(a, b, random)
export default (base) => {
  return random(base)
}
複製代碼

代碼中採用了ES6的新特性:箭頭函數,修改配置文件:

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

export default {
  input: './src/plugin/main-es.js',
  output: [{
    file: './dist/index-plugin-cjs.js',
    format: 'cjs'
  }, {
    file: './dist/index-plugin-es.js',
    format: 'es'
  }],
  plugins: [
    resolve(),
    commonjs()
  ]
}
複製代碼

從新執行打包:

rollup -c rollup.plugin.config.js 
複製代碼

查看dist/index-plugin-es.js源碼:

var mainEs = (base) => {
  return random(base)
};

export default mainEs;
複製代碼

能夠看到箭頭函數被保留下來,這樣的代碼在不支持ES6的環境下將沒法運行。咱們指望在rollup.js打包的過程當中就能使用babel完成代碼轉換,所以咱們須要babel插件。

babel插件的使用方法

首先安裝babel插件:

npm i -D rollup-plugin-babel
複製代碼

修改配置文件,增長babel插件的引用:

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

export default {
  input: './src/plugin/main-es.js',
  output: [{
    file: './dist/index-plugin-cjs.js',
    format: 'cjs'
  }, {
    file: './dist/index-plugin-es.js',
    format: 'es'
  }],
  plugins: [
    resolve(),
    commonjs(),
    babel()
  ]
}
複製代碼

從新打包:

rollup -c rollup.plugin.config.js
複製代碼

再次查看dist/index-plugin-es.js源碼:

var mainEs = (function (base) {
  return random(base);
});

export default mainEs;
複製代碼

能夠看到箭頭函數被轉換爲了function,babel插件正常工做。

json插件

爲何須要json插件

在src/plugin下建立一個新文件main-json.js:

touch src/plugin/main-json.js
複製代碼

把package.json當作一個模塊來引入,並打印package.json中的name和main屬性:

import json from '../../package.json'

console.log(json.name, json.main)
複製代碼

使用bable-node嘗試執行main-json.js:

$ babel-node src/plugin/main-json.js 
rollup-test index.js
複製代碼

能夠看到name和main字段都被打印出來了,babel-node能夠正確識別json模塊。下面修改rollup.js的配置文件:

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

export default {
  input: './src/plugin/main-json.js',
  output: [{
    file: './dist/index-plugin-cjs.js',
    format: 'cjs'
  }, {
    file: './dist/index-plugin-es.js',
    format: 'es'
  }],
  plugins: [
    resolve(),
    commonjs(),
    babel()
  ]
}
複製代碼

從新打包:

$ rollup -c rollup.plugin.config.js 

./src/plugin/main-json.js → ./dist/index-plugin-cjs.js, ./dist/index-plugin-es.js...
[!] Error: Unexpected token (Note that you need rollup-plugin-json to import JSON files)
複製代碼

能夠看到默認狀況下rollup.js不支持導入json模塊,因此咱們須要使用json插件來支持。

json插件的使用方法

下載json插件:

npm i -D rollup-plugin-json
複製代碼

修改配置文件:

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

export default {
  input: './src/plugin/main-json.js',
  output: [{
    file: './dist/index-plugin-cjs.js',
    format: 'cjs'
  }, {
    file: './dist/index-plugin-es.js',
    format: 'es'
  }],
  plugins: [
    resolve(),
    commonjs(),
    babel(),
    json()
  ]
}
複製代碼

從新打包:

rollup -c rollup.plugin.config.js
複製代碼

查看dist/index-plugin-cjs.js源碼,能夠看到json文件被解析爲一個對象進行處理:

var name = "rollup-test";
var version = "1.0.0";
var description = "";
var main = "index.js";
var scripts = {
	test: "echo \"Error: no test specified\" && exit 1"
};
var author = "";
var license = "ISC";
var devDependencies = {
	"@babel/core": "^7.1.6",
	"@babel/plugin-external-helpers": "^7.0.0",
	"@babel/preset-env": "^7.1.6",
	rollup: "^0.67.3",
	"rollup-plugin-babel": "^4.0.3",
	"rollup-plugin-commonjs": "^9.2.0",
	"rollup-plugin-json": "^3.1.0",
	"rollup-plugin-node-resolve": "^3.4.0"
};
var dependencies = {
	epubjs: "^0.3.80",
	loadsh: "^0.0.3",
	"sam-test-data": "^0.0.4",
	"sam-test-data-cjs": "^0.0.1",
	"sam-test-data-es": "^0.0.1",
	"sam-test-data-umd": "^0.0.1"
};
var json = {
	name: name,
	version: version,
	description: description,
	main: main,
	scripts: scripts,
	author: author,
	license: license,
	devDependencies: devDependencies,
	dependencies: dependencies
};

console.log(json.name, json.main);
複製代碼

uglify插件

uglify插件能夠幫助咱們進一步壓縮代碼的體積,首先安裝插件:

npm i -D rollup-plugin-uglify
複製代碼

修改rollup.js的配置文件:

import resolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs'
import babel from 'rollup-plugin-babel'
import json from 'rollup-plugin-json'
import { uglify } from 'rollup-plugin-uglify'

export default {
  input: './src/plugin/main.js',
  output: [{
    file: './dist/index-plugin-cjs.js',
    format: 'cjs'
  }],
  plugins: [
    resolve(),
    commonjs(),
    babel(),
    json(),
    uglify()
  ]
}
複製代碼

這裏要注意的是uglify插件不支持ES模塊和ES6語法,因此只能打包成非ES格式的代碼,若是碰到ES6語法則會出現報錯:

$ rollup -c rollup.plugin.config.js 

./src/plugin/main.js → ./dist/index-plugin-cjs.js, ./dist/index-plugin-es.js...
  19 | var main = random;
  20 | 
> 21 | export default main;
     |       ^ Unexpected token: keyword (default)
[!] (uglify plugin) Error: Unexpected token: keyword (default)
複製代碼

因此這裏咱們採用sam-test-data進行測試,由於這個模塊採用了babel進行編譯,其餘幾個模塊uglify都不支持(由於其餘幾個模塊使用了const,const也是ES6特性,uglify不能支持),因此你們在本身編寫類庫的時候要注意使用babel插件進行編譯。配置完成後從新打包:

$ rollup -c rollup.plugin.config.js 

./src/plugin/main.js → ./dist/index-plugin-cjs.js...
created ./dist/index-plugin-cjs.js in 679ms
複製代碼

查看dist/index-plugin-cjs.js源碼:

"use strict";var a=Math.floor(10*Math.random()),b=Math.floor(100*Math.random());function random(a){return a&&a%1==0?Math.floor(Math.random()*a):0}var test=Object.freeze({a:a,b:b,random:random});console.log(test);var main=random;module.exports=main;
複製代碼

能夠看到代碼被最小化了,體積也減少了很多。

rollup.js watch

命令行模式

rollup.js的watch模式支持監聽代碼變化,一旦修改代碼後將自動執行打包,很是方便,使用方法是在打包指令後添加--watch便可:

$ rollup -c rollup.plugin.config.js  --watch

rollup v0.67.1
bundles ./src/plugin/main-json.js → dist/index-plugin-cjs.js, dist/index-plugin-es.js...
created dist/index-plugin-cjs.js, dist/index-plugin-es.js in 24ms

[2018-11-20 22:26:24] waiting for changes...
複製代碼

API模式

rollup.js支持咱們經過API來啓動watch模式,在項目根目錄下建立如下文件:

  • rollup-watch-input-options.js:輸入配置
  • rollup-watch-output-options.js:輸出配置
  • rollup-watch-options.js:監聽配置
  • rollup-watch.js:調用rollup.js的API啓動watch模式

爲了讓node可以執行咱們的程序,因此採用CommonJS規範,rollup-watch-input-options.js代碼以下:

const json = require('rollup-plugin-json')
const resolve = require('rollup-plugin-node-resolve')
const commonjs = require('rollup-plugin-commonjs')
const babel = require('rollup-plugin-babel')
const uglify = require('rollup-plugin-uglify').uglify

module.exports = {
  input: './src/plugin/main.js',
  plugins: [
    json(),
    resolve({
      customResolveOptions: {
        moduleDirectory: 'node_modules' // 僅處理node_modules內的庫
      }
    }),
    babel({
      exclude: 'node_modules/**' // 排除node_modules
    }),
    commonjs(),
    uglify() // 代碼壓縮
  ]
}
複製代碼

rollup-watch-output-options.js代碼以下:

module.exports = [{
  file: './dist/index-cjs.js',
  format: 'cjs',
  name: 'sam-cjs'
}]
複製代碼

rollup-watch-options.js代碼以下:

module.exports = {
  include: 'src/**', // 監聽的文件夾
  exclude: 'node_modules/**' // 排除監聽的文件夾
}
複製代碼

rollup-watch.js代碼以下:

const rollup = require('rollup')
const inputOptions = require('./rollup-watch-input-options')
const outputOptions = require('./rollup-watch-output-options')
const watchOptions = require('./rollup-watch-options')

const options = {
  ...inputOptions,
  output: outputOptions,
  watchOptions
} // 生成rollup的options

const watcher = rollup.watch(options) // 調用rollup的api啓動監聽

watcher.on('event', event => {
  console.log('從新打包中...', event.code)
}) // 處理監聽事件

// watcher.close() // 手動關閉監聽
複製代碼

經過node直接啓動監聽:

$ node rollup-watch.js 
從新打包中... START
從新打包中... BUNDLE_START
從新打包中... BUNDLE_END
從新打包中... END
複製代碼

以後咱們再修改src/plugin/main.js的源碼,rollup.js就會自動對代碼進行打包。

總結

本教程詳細講解了rollup.js的插件、tree-shaking機制和watch模式,涉及知識點整理以下:

  • rollup.js插件
    • resolve插件:集成外部模塊
    • commonjs插件:支持CommonJS模塊
    • babel插件:編譯ES6語法,使低版本瀏覽器能夠識別
    • json插件:支持json模塊
    • uglify:代碼最小化打包(不支持ES模塊)
  • tree-shaking:只有ES模塊才支持,大幅精簡代碼量
  • watch模式:支持命令行和API模式,實時監聽代碼變動
相關文章
相關標籤/搜索