前言
本文是《10分鐘快速精通rollup.js——Vue.js原始碼打包過程深度分析》的前置學習教程,講解的知識點以理解Vue.js打包原始碼為目標,不會做過多地展開。教程將保持rollup.js系列教程的一貫風格,大部分知識點都將提供可執行的程式碼案例和實際執行的結果,讓大家通過教程就可以看到實現效果,省去親自上機測試的時間。
學習本教程需要理解buble、flow和terser模組的用途,還不瞭解的小夥伴可以檢視這篇教程:《10分鐘快速精通rollup.js——前置學習之基礎知識篇》
1. rollup-plugin-buble外掛
Convert ES2015 with buble.
buble外掛的用途是在rollup.js打包的過程中進行程式碼編譯,將ES6+程式碼編譯成ES2015標準,首先在專案中引入buble外掛:
npm i -D rollup-plugin-buble
複製程式碼
修改./src/vue/buble/index.js的原始碼,寫入如下內容:
const a = 1 // ES6新特性:const
let b = 2 // ES6新特性:let
const c = () => a + b // ES6新特性:箭頭函式
console.log(a, b, c())
複製程式碼
修改rollup.plugin.config.js配置檔案:
import resolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs'
import buble from 'rollup-plugin-buble'
export default {
input: './src/vue/buble/index.js',
output: [{
file: './dist/index-plugin-cjs.js',
format: 'cjs'
}],
plugins: [
resolve(),
commonjs(),
buble()
]
}
複製程式碼
應用rollup.js進行編譯:
$ rollup -c rollup.plugin.config.js
./src/vue/buble/index.js ./dist/index-plugin-cjs.js...
created ./dist/index-plugin-cjs.js in 35ms
複製程式碼
檢視生成檔案的內容:
$ cat dist/index-plugin-cjs.js
'use strict';
var a = 1;
var b = 2;
var c = function () { return a + b; };
console.log(a, b, c());
複製程式碼
通過node執行打包後的程式碼:
$ node dist/index-plugin-cjs.js
1 2 3
複製程式碼
2. rollup-plugin-alias外掛
Provide an alias for modules in Rollup
alias外掛提供了為模組起別名的功能,用過webpack的小夥伴應該對這個功能非常熟悉,首先在專案中引入alias外掛:
npm i -D rollup-plugin-alias
複製程式碼
建立src/vue/alias目錄,並建立一個本地測試庫lib.js和測試程式碼index.js:
mkdir -p src/vue/alias
touch src/vue/alias/index.js
touch src/vue/alias/lib.js
複製程式碼
在lib.js中寫入如下內容:
export function square(n) {
return n * n
}
複製程式碼
在index.js中寫入如下內容:
import { square } from '@/vue/alias/lib' // 使用別名
console.log(square(2))
複製程式碼
修改rollup.plugin.config.js的配置檔案:
import alias from 'rollup-plugin-alias'
import path from 'path'
const pathResolve = p => path.resolve(__dirname, p)
export default {
input: './src/vue/alias/index.js',
output: [{
file: './dist/index-plugin-es.js',
format: 'es'
}],
plugins: [
alias({
'@': pathResolve('src')
})
]
}
複製程式碼
這裡我們提供了一個pathResolve方法,用於生成絕對路徑,引入alias外掛中我們傳入一個物件作為引數,物件的key是模組中使用的別名,物件的value是別名對應的真實路徑。使用rollup.js進行打包:
$ rollup -c rollup.plugin.config.js
./src/vue/alias/index.js ./dist/index-plugin-es.js...
created ./dist/index-plugin-es.js in 23ms
複製程式碼
檢視打包後的程式碼,可以看到正確識別出別名對應的路徑:
$ cat dist/index-plugin-es.js
function square(n) {
return n * n
}
console.log(square(2));
複製程式碼
3. rollup-plugin-flow-no-whitespace外掛
flow外掛用於在rollup.js打包過程中清除flow型別檢查部分的程式碼,我們修改rollup.plugin.config.js的input屬性:
input: './src/vue/flow/index.js',
複製程式碼
嘗試直接打包flow的程式碼:
$ rollup -c rollup.plugin.config.js
./src/vue/flow/index.js ./dist/index-plugin-cjs.js...
[!] Error: Unexpected token
src/vue/flow/index.js (2:17)
1: /* @flow */
2: function square(n: number): number {
^
3: return n * n
4: }
Error: Unexpected token
複製程式碼
可以看到rollup.js會丟擲異常,為了解決這個問題我們引入flow外掛:
npm i -D rollup-plugin-flow-no-whitespace
複製程式碼
修改配置檔案:
import flow from 'rollup-plugin-flow-no-whitespace'
export default {
input: './src/vue/flow/index.js',
output: [{
file: './dist/index-plugin-cjs.js',
format: 'cjs'
}],
plugins: [
flow()
]
}
複製程式碼
重新執行打包:
$ rollup -c rollup.plugin.config.js
./src/vue/flow/index.js ./dist/index-plugin-cjs.js...
created ./dist/index-plugin-cjs.js in 40ms
複製程式碼
檢視打包後的原始碼:
$ cat ./dist/index-plugin-cjs.js
'use strict';
/* */
function square(n) {
return n * n
}
console.log(square(2));
複製程式碼
可以看到flow的程式碼被成功清除,但是/* @flow */
被修改為了/* */
,這個問題我們可以使用terser外掛後進行修復,去除註釋。
4. rollup-plugin-replace外掛
Replace content while bundling
replace外掛的用途是在打包時動態替換程式碼中的內容,首先引入replace外掛:
npm i -D rollup-plugin-replace
複製程式碼
建立src/vue/replace資料夾和index.js測試檔案:
mkdir -p src/vue/replace
touch src/vue/replace/index.js
複製程式碼
在index.js檔案中寫入如下內容:
const a = 1
let b = 2
if (__SAM__) { // 使用replace值__SAM__,注意這個值沒有定義,如果直接執行會報錯
console.log(`__SAM__,${a},${b},${c}`) // 使用__SAM__,打包時會被替換
}
複製程式碼
修改rollup.plugin.config.js配置檔案:
import buble from 'rollup-plugin-buble'
import replace from 'rollup-plugin-replace'
export default {
input: './src/vue/replace/index.js',
output: [{
file: './dist/index-plugin-cjs.js',
format: 'cjs'
}],
plugins: [
replace({
__SAM__: true
}),
buble()
]
}
複製程式碼
我們向replace外掛傳入一個物件,key是__SAM__,value是true。所以rollups.js打包時就會將__SAM__替換為true。值得注意的是程式碼使用了ES6了特性,所以需要引入buble外掛進行編譯,我們執行打包指令:
$ rollup -c rollup.plugin.config.js
./src/vue/replace/index.js ./dist/index-plugin-cjs.js...
created ./dist/index-plugin-cjs.js in 28ms
複製程式碼
檢視打包結果:
$ cat dist/index-plugin-cjs.js
'use strict';
var a = 1;
var b = 2;
{
console.log(("true," + a + "," + b + "," + c));
}
複製程式碼
可以看到__SAM__被正確替換為了true。如果大家使用的是WebStorm編輯器,使用flow語法會出現警告,這時我們需要開啟設定,進入Languages & Frameworks > Javascript,將Javascript language version改為Flow。
除此之外,使用__SAM__也會引發警告。 解決這個問題的辦法是開啟flow/test.js,新增__SAM__的自定義變數(declare var),這樣警告就會消除:declare type Test = {
a?: number; // a的型別為number,可以為空
b?: string; // b的型別為string,可以為空
c: (key: string) => boolean; // c的型別為function,只能包含一個引數,型別為string,返回值為boolean,注意c不能為空
}
declare var __SAM__: boolean; // 新增自定義變數
複製程式碼
5. rollup-plugin-terser外掛
Rollup plugin to minify generated es bundle
terser外掛幫助我們在rollup.js打包過程中實現程式碼壓縮,首先引入terser外掛:
npm i -D rollup-plugin-terser
複製程式碼
修改src/vue/replace/index.js的原始碼,這裡我們做一輪綜合測試,將我們之前學習的外掛全部應用進來:
/* @flow */
import { square } from '@/vue/alias/lib' // 通過別名匯入本地模組
import { random } from 'sam-test-data' // 匯入es模組
import { a as cjsA } from 'sam-test-data-cjs' // 匯入commonjs模組
const a: number = 1 // 通過flow進行型別檢查
let b: number = 2 // 使用ES6新特性:let
const c: string = '' // 加入非ascii字元
if (__SAM__) { // 使用replace字串
console.log(`__SAM__,${a},${b},${c}`) // 使用ES6新特性``模板字串
}
export default {
a, b, c, d: __SAM__, square, random, cjsA
} // 匯出ES模組
複製程式碼
我們希望通過上述程式碼驗證以下功能:
- replace外掛:程式碼中的__SAM__被正確替換;
- flow外掛:正確去掉flow的型別檢查程式碼;
- buble外掛:將ES6+程式碼編譯為ES2015;
- alias外掛:將模組中'@'別名替換為'src'目錄;
- commonjs外掛:支援CommonJS模組;
- resovle外掛:合併外部模組程式碼;
- terser外掛:程式碼最小化打包。
修改rollup.plugin.config.js配置檔案:
import resolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs'
import buble from 'rollup-plugin-buble'
import replace from 'rollup-plugin-replace'
import flow from 'rollup-plugin-flow-no-whitespace'
import { terser } from 'rollup-plugin-terser'
import alias from 'rollup-plugin-alias'
import path from 'path'
const pathResolve = p => path.resolve(__dirname, p)
export default {
input: './src/vue/replace/index.js',
output: [{
file: './dist/index-plugin-es.js',
format: 'es'
}],
plugins: [
replace({
__SAM__: true
}),
flow(),
buble(),
alias({
'@': pathResolve('src')
}),
commonjs(),
resolve(),
terser({
output: {
ascii_only: true // 僅輸出ascii字元
},
compress: {
pure_funcs: ['console.log'] // 去掉console.log函式
}
}),
]
}
複製程式碼
terser外掛的配置方法與API模式基本一致,打包程式碼:
$ rollup -c rollup.plugin.config.js
./src/vue/replace/index.js ./dist/index-plugin-es.js...
created ./dist/index-plugin-es.js in 308ms
複製程式碼
檢視打包後的程式碼檔案:
$ cat dist/index-plugin-es.js
function square(a){return a*a}function random(a){return a&&a%1==0?Math.floor(Math.random()*a):0}var a$1=Math.floor(10*Math.random()),b$1=Math.floor(100*Math.random());function random$1(a){return a&&a%1==0?Math.floor(Math.random()*a):0}var _samTestDataCjs_0_0_1_samTestDataCjs={a:a$1,b:b$1,random:random$1},_samTestDataCjs_0_0_1_samTestDataCjs_1=_samTestDataCjs_0_0_1_samTestDataCjs.a,a$2=1,b$2=2,c="\ud83d\ude00",index={a:a$2,b:b$2,c:c,d:!0,square:square,random:random,cjsA:_samTestDataCjs_0_0_1_samTestDataCjs_1};export default index;
複製程式碼
可以看到各項特性全部生效,嘗試通過babel-node執行程式碼:
$ babel-node
> require('./dist/index-plugin-es')
{ default:
{ a: 1,
b: 2,
c: '',
d: true,
square: [Function: square],
random: [Function: random],
cjsA: 4 } }
複製程式碼
程式碼成功執行,說明打包過程成功。
6. intro和outro配置
intro和outro屬性與我們之前講解的banner和footer屬性類似,都是用來為程式碼新增註釋。那麼這四個屬性之間有什麼區別呢?首先了解一下rollup.js官網對這四個屬性的解釋:
- intro:在打包好的檔案的塊的內部(wrapper內部)的最頂部插入一段內容
- outro:在打包好的檔案的塊的內部(wrapper內部)的最底部插入一段內容
- banner:在打包好的檔案的塊的外部(wrapper外部)的最頂部插入一段內容
- footer:在打包好的檔案的塊的外部(wrapper外部)的最底部插入一段內容
簡單地說就是intro和outro新增的註釋在程式碼塊的內部,banner和footer在外部,下面舉例說明,修改src/plugin/main.js程式碼:
const a = 1
console.log(a)
export default a
複製程式碼
修改rollup.config.js的配置:
export default {
input: './src/plugin/main.js',
output: [{
file: './dist/index-cjs.js',
format: 'cjs',
banner: '// this is banner',
footer: '// this is footer',
intro: '// this is a intro comment',
outro: '// this is a outro comment',
}]
}
複製程式碼
打包程式碼:
$ rollup -c
./src/plugin/main.js ./dist/index-cjs.js...
created ./dist/index-cjs.js in 11ms
複製程式碼
輸出結果:
$ cat dist/index-cjs.js
// this is banner
'use strict';
// this is a intro comment
const a = 1;
console.log(a);
module.exports = a;
// this is a outro comment
// this is footer
複製程式碼
可以看到banner和footer的註釋在最外層,而intro和outro的註釋在內層,intro的註釋在'use strict'下方,所以如果要給程式碼最外層包裹一些元素,如module.exports或export default之類的,需要使用intro和outro,在Vue.js原始碼打包過程中就使用了intro和outro為程式碼新增模組化特性。我們還可以將intro和outro配置寫入plugins的方法來實現這個功能:
export default {
input: './src/plugin/main.js',
output: [{
file: './dist/index-cjs.js',
format: 'cjs',
banner: '// this is banner',
footer: '// this is footer'
}],
plugins: [
{
intro: '// this is a intro comment',
outro: '// this is a outro comment'
}
]
}
複製程式碼
該配置檔案生成的效果與之前完全一致。
總結
本教程主要為大家講解了以下知識點:
- rollup-plugin-buble外掛:編譯ES6+語法為ES2015,無需配置,比babel更輕量;
- rollup-plugin-alias外掛:替換模組路徑中的別名;
- rollup-plugin-flow-no-whitespace外掛:去除flow靜態型別檢查程式碼;
- rollup-plugin-replace外掛:替換程式碼中的變數為指定值;
- rollup-plugin-terser外掛:程式碼壓縮,取代uglify,支援ES模組。
- intro和outro配置:在程式碼塊內新增程式碼註釋。