以下文章來源于前端森林,作者 前端森林(微信公衆号)
如有侵權,請聯系作者删除!
前言
項目中一直用的都是
webpack
, 前一段需要開發幾個類庫供其他平台使用本來打算繼續使用
webpack
的,但感覺
webpack
用來開發
js
庫,不僅繁瑣而且打包後的檔案體積比較大。正好之前看
vue
源碼,知道
vue
也是通過
rollup
打包的。這次又是開發類庫的,于是就快速上手了
rollup
。
本篇文章是我有一定的項目實踐後,回過來給大家分享一下如何從零快速上手
rollup
。
什麼是 rollup ?
系統的了解
rollup
之前,我們先來簡單了解下
What is rollup
?
關于
rollup
的介紹,官方文檔已經寫得很清楚了:
Rollup 是一個 JavaScript 子產品打包器,可以将小塊代碼編譯成大塊複雜的代碼,例如 library 或應用程式。
與
webpack
偏向于應用打包的定位不同,
rollup.js
更專注于
JavaScript
類庫打包。
我們熟知的
Vue
、
React
等諸多知名架構或類庫都是通過
rollup.js
進行打包的。
為什麼是 Rollup ?
webpack
我相信做前端的同學大家都用過,那麼為什麼有些場景還要使用
rollup
呢?這裡我簡單對
webpack
和
rollup
做一個比較:
總體來說
webpack
和
rollup
在不同場景下,都能發揮自身優勢作用。
webpack
對于代碼分割和靜态資源導入有着“先天優勢”,并且支援熱子產品替換(
HDR
),而
rollup
并不支援。
是以當開發應用事可以優先選擇
webpack
,但是
rollup
對于代碼的
Tree-shaking
和 ES6 子產品有着算法優勢上的支援,若你項目隻需要打包出一個簡單的
bundle
包,并是基于 ES6 子產品開發的,可以考慮使用
rollup
。
其實
webpack
從
2.0
開始就已經支援
Tree-shaking
,并在使用
babel-loader
的情況下還可以支援
module
的打包。實際上,
rollup
已經在漸漸地是去了當初的優勢了。但是它并沒有被抛棄,反而因其簡單的
API
、使用方式被許多庫開發者青睐,如
React
、
Vue
等,都是使用
rollup
作為建構工具的。
快速上手
我們先話大概十分鐘左右的時間來了解下
rollup
的基本使用以及完成一個
hello world
。
安裝
首先全局安裝
rollup
:
npm i rollup -g
目錄準備(hello world)
接着,我們初始化一個如下所示的項目目錄
├── dist # 編譯結果
├── example # HTML引用例子
│ └── index.html
├── package.json
└── src # 源碼
└── index.js
首先我們在
src/index.js
中寫入如下代碼
然後在指令行執行以下指令
rollup src/index.js -f umd -o dist/bundle.js
執行指令,我們即可在
dist
目錄下生成
bundle.js
檔案
(function (factory) {
typeof define === 'function' && define.amd ? define(factory) :
factory();
}((function () { 'use strict';
console.log("柯森");
})));
這時,我們再在
example/index.html
中引入上面打包生成的
bundle.js
檔案,打開浏覽器
如我們所料的,控制台輸出了
柯森
。
到這裡,我們就用
rollup
打包樂意個最最簡單的
demo
。
可能很多同學看到這裡對于上面指令行中的參數不是很明白,我依次說明下:
-
。-f
參數是-f
的縮寫,它表示生成代碼的格式,--format
表示采用amd
标準,AMD
為cjs
标準,CommonJS
(或 es )為 ES 子產品标準。esm
的值可以為-f
、amd
、cjs
、system
(‘esm
’也可以)、es
或life
中的任何一個。umd
-
。-o
指定了輸出的路徑,這裡我們将打包後的檔案輸出到-o
目錄下的dist
bundle.js
-
。指定-c
配置檔案rollup
-
。監聽原檔案是否有改動,如果有改動,重新打包。-w
使用配置檔案(rollup.config.js)
使用指令行的方式,如果選項少沒什麼問題,但是如果添加更多的選項,這種指令行的方式就顯得麻煩了。
為此,我們可以建立配置檔案來囊括所需的選項
在項目中建立一個名為
rollup.config.js
的檔案,增加如下代碼:
export default {
input: ["./src/index.js"],
output: {
file: "./dist/bundle.js",
format: "umd",
name: "experience",
},
};
然後指令行執行:
rollup -c
打開
dist/bundle.js
檔案,我們就會發現和上面采用指令行的方式打包出來的結果是一樣的。
這裡,我對配置檔案的選項做下簡單的說明:
-
表示入口檔案的路徑(老版本為 entry,已經廢棄)input
-
output
表示輸出檔案的内容,它允許傳入一個對象或一個數組,當為數組時,一次輸出多個檔案,它包含一下内容:
1.
output.file
: 輸出檔案的路徑(老版本為 dest,已經廢棄)
2.
output.format
:輸出檔案的格式
3.
output.banner
:檔案頭部添加的内容
4.
:檔案末尾添加的内容output.footer
到這裡,相信你已經差不多上手
rollup
了
進階
但是,這對于真實的業務場景是遠遠不夠的。
下面,我将介紹
rollup
中的幾種常用的插件以及
external
屬性、
tree-shaking
機制。
resolve 插件
為什麼要使用 resolve 插件
在上面的入門案例中,我們打包的對象是本地的 js 代碼和庫,但實際開發中,不太可能所有的庫都位于本地,我們大多會通過 npm 下載下傳遠端的庫。
與
webpack
和
browserify
這樣的其他捆綁包不同,
rollup
不知道如何打破正常去處理這些依賴。是以我們需要添加一些配置。
resolve 插件使用
首先在我們的項目中添加一個依賴 the-answer ,然後修改 src/index.js 檔案:
import answer from "the-answer";
export default function () {
console.log("the answer is " + answer);
}
執行
npm run build
.
這裡為了友善,我将原本的添加到了
rollup -c -w
的
package.json
中:
scripts
"build":"rollup -c -w"
會得到以下報錯:
打包後的
bundle.js
仍然會在
Node.js
中工作,但是
the-answer
不包含在包中。為了解決這個問題,将我們編寫的源碼與依賴的第三方庫進行合并,
rollup.js
為我們提供了
resolve
插件。
首先,安裝
resolve
插件:
npm i -D @rollup/plugin-node-resolve
修改配置檔案
rollup.config.js
:
import resolve from "@rollup/plugin-node-resolve";
export default {
input: ["./src/index.js"],
output: {
file: "./dist/bundle.js",
format: "umd",
name: "experience",
},
plugins: [resolve()],
};
這時再次執行
npm run build
,可以發現報錯已經沒有了:
打開
dist/bundle.js
檔案:
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.experience = factory());
}(this, (function () { 'use strict';
var index = 42;
function index$1 () {
console.log("the answer is " + index);
}
return index$1;
})));
打封包件
bundle.js
中已經包含了引用的子產品。
有些場景下,雖然我們使用了
resolve
插件,但可能我們仍然想要某些庫保持外部引用狀态,這時我們就需要使用
external
屬性,來告訴
rollup.js
哪些是外部的類庫。
external 屬性
修改
rollup.js
的配置檔案
import resolve from "@rollup/plugin-node-resolve";
export default {
input: ["./src/index.js"],
output: {
file: "./dist/bundle.js",
format: "umd",
name: "experience",
},
plugins: [resolve()],
external: ["the-answer"],
};
重新打包,打開
dist/bundle.js
檔案:
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('the-answer')) :
typeof define === 'function' && define.amd ? define(['the-answer'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.experience = factory(global.answer));
}(this, (function (answer) { 'use strict';
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var answer__default = /*#__PURE__*/_interopDefaultLegacy(answer);
function index () {
console.log("the answer is " + answer__default['default']);
}
return index;
})));
這時我們看到
the-answer
已經是作為外部庫被引入了。
commonjs 插件
為什麼需要 commonjs 插件
rollup.js
編譯源碼中的子產品引用預設隻支援
ES6+
的子產品方式
import/export
。然而大量的
npm
子產品是基于
CommonJS
子產品方式,這就導緻了大量
npm
子產品不能直接編譯使用。
是以使得
rollup.js
編譯支援
npm
子產品和
CommonJS
子產品方式的插件就應運而生:
@rollup/plugin-common.js
。
commonjs 插件使用
首先,安裝該子產品:
npm i -D @rollup/plugin-commonjs
然後修改 rollup.config.js 檔案:
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
export default {
input: ["./src/index.js"],
output: {
file: "./dist/bundle.js",
format: "umd",
name: "experience",
},
plugins: [resolve(), commonjs()],
external: ["the-answer"],
};
babel 插件
為什麼需要 babel 插件
我們在
src
目錄下添加
es6.js
檔案(這裡我們使用了 es6 中的箭頭函數)
const a = 1;
const b = 2;
console.log(a, b);
export default () => {
return a + b;
};
然後修改
rollup.config.js
配置檔案:
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
export default {
input: ["./src/es6.js"],
output: {
file: "./dist/esBundle.js",
format: "umd",
name: "experience",
},
plugins: [resolve(), commonjs()],
external: ["the-answer"],
};
執行打包,可以看到 dist/esBundle.js 檔案内容如下:
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.experience = factory());
}(this, (function () { 'use strict';
const a = 1;
const b = 2;
console.log(a, b);
var es6 = () => {
return a + b;
};
return es6;
})));
可以看到箭頭函數被保留下來,這樣的代碼在不支援
ES6
的環境下将無法運作。我們期望在
rollup.js
打包的過程中就能使用
babel
完成代碼轉換,是以我們需要
babel
插件。
babel 插件使用
首先,安裝
npm i -D @rollup/plugin-babel
同樣修改配置檔案
rollup.config.js
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import babel from "@rollup/plugin-babel";
export default {
input: ["./src/es6.js"],
output: {
file: "./dist/esBundle.js",
format: "umd",
name: "experience",
},
plugins: [resolve(), commonjs(), babel()],
external: ["the-answer"],
};
然後打包,發現會出現報錯
提示我們缺少
@babel/core
,因為
@babel/core
是
babel
的核心。我們來進行安裝
npm i @babel/core
再次執行打包,發現這次沒有報錯了,但是我們嘗試打開
dist/esBundle.js
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.experience = factory());
}(this, (function () { 'use strict';
const a = 1;
const b = 2;
console.log(a, b);
var es6 = (() => {
return a + b;
});
return es6;
})));
可以發現箭頭函數仍然存在,顯然這是不正确的,說明我們的
babel
插件沒有起到作用。這是為什麼呢?
原因是由于我們缺少了
.babelrc
檔案,添加該檔案:
{
"presets": [
[
"@babel/preset-env",
{
"modules": false,
// "useBuiltIns": "usage"
}
]
]
}
我們看
.babelrc
配置了
preset env
,是以先安裝這個插件
npm i @babel/preset-env
這次再次執行打包,我們打開
dist/esBundle.js
檔案
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.experience = factory());
}(this, (function () { 'use strict';
var a = 1;
var b = 2;
console.log(a, b);
var es6 = (function () {
return a + b;
});
return es6;
})));
可以看到箭頭函數被轉換成了
function
,說明
babel
插件正常工作。
json 插件
為什麼需要 json 插件
在
src
目錄下建立
json.js
檔案
import json from "../package.json";
console.log(json.author);
内容很簡單,就是引入
package.json
,然後去列印
author
字段。
修改
rollup.config.js
配置檔案
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import babel from "@rollup/plugin-babel";
export default {
input: ["./src/json.js"],
output: {
file: "./dist/jsonBundle.js",
format: "umd",
name: "experience",
},
plugins: [resolve(), commonjs(), babel()],
external: ["the-answer"],
};
執行打包,發現會發生如下錯誤:
提示我們缺少
@rollup/plugin-json
插件來支援
json
檔案。
json 插件的使用
來安裝該插件:
npm i -D @rollup/plugin-json
同樣修改下配置檔案,将插件加入
plugins
數組即可。
然後再次打包,發現打包成功了,我們打開生成的
dist/jsonBundle.js
目錄
(function (factory) {
typeof define === 'function' && define.amd ? define(factory) :
factory();
}((function () { 'use strict';
var name = "rollup-experience";
var version = "1.0.0";
var description = "";
var main = "index.js";
var directories = {
example: "example"
};
var scripts = {
build: "rollup -c -w",
test: "echo \"Error: no test specified\" && exit 1"
};
var author = "Cosen";
var license = "ISC";
var dependencies = {
"@babel/core": "^7.11.6",
"@babel/preset-env": "^7.11.5",
"the-answer": "^1.0.0"
};
var devDependencies = {
"@rollup/plugin-babel": "^5.2.0",
"@rollup/plugin-commonjs": "^15.0.0",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^9.0.0"
};
var json = {
name: name,
version: version,
description: description,
main: main,
directories: directories,
scripts: scripts,
author: author,
license: license,
dependencies: dependencies,
devDependencies: devDependencies
};
console.log(json.author);
})));
完美!!!
tree-shaking 機制
這裡我們以最開始的
src/index.js
為例進行說明
import answer from "the-answer";
export default function () {
console.log("the answer is " + answer);
}
修改上述檔案
const a = 1;
const b = 2;
export default function () {
console.log(a + b);
}
執行打包。打開
dist/bundle.js
檔案
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.experience = factory());
}(this, (function () { 'use strict';
var a = 1;
var b = 2;
function index () {
console.log(a + b);
}
return index;
})));
再次修改
src/index.js
檔案
const a = 1;
const b = 2;
export default function () {
console.log(a);
}
再次執行打包,打開打封包件
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.experience = factory());
}(this, (function () { 'use strict';
var a = 1;
function index () {
console.log(a);
}
return index;
})));
發現了什麼?
我們發現關于變量
b
的定義沒有了,因為源碼中并沒有用到這個變量。這就是
ES
子產品著名的
tree-shaking
機制,它動态的清除沒有被使用過的代碼,使得代碼更加精簡,進而可以使得我們的類庫獲得更快的加載速度。
總結
本文大緻向大家介紹了什麼是
rollup
以及如何快速上手
rollup
。文中提到的這些其實隻是冰山一角。
rollup
能玩的東西還有很多,關于更多可以去
rollup
官網查詢。