本文是《10分鐘快速精通rollup.js——Vue.js源碼打包過程深度分析》的前置學習教程,講解的知識點以理解Vue.js打包源碼爲目標,不會作過多地展開。教程將保持rollup.js系列教程的一向風格,大部分知識點都將提供可運行的代碼案例和實際運行的結果,讓你們經過教程就能夠看到實現效果,省去親自上機測試的時間。javascript
fs模塊是Node.js提供一組文件操做API,用於對系統文件及目錄進行讀寫操做。vue
刪除dist目錄,建立src/vue/fs測試代碼目錄和index.js測試代碼:java
rm -rf dist
mkdir -p src/vue/fs
touch src/vue/fs/index.js
複製代碼
經過異步和同步兩種方式判斷dist目錄是否存在:node
const fs = require('fs')
fs.exists('./dist', result => console.log(result))
const exists = fs.existsSync('./dist')
if (exists) {
console.log('dist目錄存在')
} else {
console.log('dist目錄不存在')
}
複製代碼
經過node執行代碼:算法
$ node src/vue/fs/index.js
dist目錄不存在
false
複製代碼
根據執行結果咱們能夠看到同步的任務先完成,而異步的任務會延後一些,可是同步任務會致使主線程阻塞,在實際應用過程當中須要根據實際應用場景進行取捨。npm
經過異步的方式建立dist目錄:json
const fs = require('fs')
fs.exists('./dist', result => !result && fs.mkdir('./dist'))
複製代碼
經過同步的方式建立dist目錄:bash
const fs = require('fs')
if (!fs.existsSync('./dist')) {
fs.mkdirSync('./dist')
}
複製代碼
檢查dist目錄是否生成:babel
$ ls -al
total 312
drwxr-xr-x 5 sam staff 160 Nov 22 14:15 dist/
複製代碼
咱們先經過rollup.js打包代碼,在dist目錄下會生成index-cjs.js和index-es.js:dom
$ rollup -c
./src/plugin/main.js ./dist/index-cjs.js, ./dist/index-es.js...
created ./dist/index-cjs.js, ./dist/index-es.js in 27ms
複製代碼
經過異步方式讀取index-cjs.js的內容,注意讀取到的文件file是一個Buffer對象,經過toString()方法能夠獲取到文件的文本內容:
const fs = require('fs')
fs.readFile('./dist/index-cjs.js', (err, file) => {
if (!err) console.log(file.toString()) // 打印文件內容
}) // 經過異步讀取文件內容
複製代碼
經過同步方式讀取index-cjs.js的內容:
const fs = require('fs')
const file = fs.readFileSync('./dist/index-cjs.js') // 經過同步讀取文件內容
console.log(file.toString()) // 打印文件內容
複製代碼
運行代碼,能夠看到成功讀取了文件內容:
$ node src/vue/fs/index.js
/**
* ==============================
* welcome to imooc.com
* this is a rollup test project
* ==============================
**/
'use strict';
var a = Math.floor(Math.random() * 10);
var b = Math.floor(Math.random() * 100);
# ...
複製代碼
經過異步方式讀取src/vue/fs/index.js的內容,並寫入dist/index.js:
const fs = require('fs')
fs.readFile('./src/vue/fs/index.js', (err, file) => {
if (!err) fs.writeFile('./dist/index.js', file, () => {
console.log('寫入成功') // 寫入成功的回調
}) // 經過異步寫入文件
}) // 經過異步讀取文件
複製代碼
經過同步方式實現與上面同樣的功能:
const fs = require('fs')
const code = fs.readFileSync('./src/vue/fs/index.js') // 同步讀取文件
fs.writeFileSync('./dist/index.js', code) // 同步寫入文件
複製代碼
須要注意的是writeFile()方法默認狀況下會覆蓋dist/index.js的內容,即先清空文件再寫入。
不少時候咱們須要在文件末尾追加寫入一些內容,能夠增長flag屬性進行標識,當flag的值爲a時,表示追加寫入:
const fs = require('fs')
const code = fs.readFileSync('./src/vue/fs/index.js')
fs.writeFileSync('./dist/index.js', code, { flag: 'a' })
複製代碼
驗證方法很是簡單,你們能夠本身嘗試。
path模塊是Node.js提供的用於處理文件路徑的函數集合。
path.resolve()方法能夠幫助咱們生成絕對路徑,建立src/vue/path測試代碼路徑和index.js測試代碼:
mkdir -p src/vue/path
touch src/vue/path/index.js
複製代碼
寫入以下測試代碼:
const path = require('path')
console.log(path.resolve('./dist/index.js'))
console.log(path.resolve('src', 'vue/path/index.js'))
console.log(path.resolve('/src', '/vue/path/index.js'))
console.log(path.resolve('/src', 'vue/path/index.js'))
複製代碼
測試代碼執行結果:
$ node src/vue/path/index.js
/Users/sam/WebstormProjects/rollup-test/dist/index.js
/Users/sam/WebstormProjects/rollup-test/src/vue/path/index.js
/vue/path/index.js
/src/vue/path/index.js
複製代碼
經過測試結果不難看出path.resolve()的工做機制:
在src/vue/path/index.js寫入以下代碼:
const path = require('path')
const fs = require('fs')
const absolutePath = path.resolve('src', 'vue/path/index.js')
console.log(path.relative('./', absolutePath))
console.log(path.relative(absolutePath, './'))
複製代碼
執行代碼:
$ node src/vue/path/index.js
src/vue/path/index.js
../../../..
複製代碼
經過運行結果咱們能夠看到path.relative(a, b)方法提供了兩個參數,返回的結果是第一個參數到第二個參數的相對路徑,換句話說就是如何從第一個路徑到達第二個路徑:
The blazing fast, batteries-included ES2015 compiler.
buble是一款相似babel的ES編譯器,它的主要特性以下:
for...of
。buble不支持的功能列表:buble.surge.sh/guide/#unsu…全局安裝buble:
npm i -g buble
複製代碼
建立buble的測試代碼:
mkdir -p src/vue/buble
touch src/vue/buble/index.js
複製代碼
在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())
複製代碼
使用buble編譯代碼,並打印出結果:
$ buble src/vue/buble/index.js
var a = 1
var b = 2
var c = function () { return a + b; }
console.log(a, b, c())
複製代碼
相比babel,buble使用起來更加簡便,再也不須要配置。可是bubble對某些語法是不支持的,好比for...of
,咱們修改src/vue/buble/index.js,寫入以下代碼:
const arr = [1, 2, 3]
for (const value of arr) {
console.log(value)
}
複製代碼
使用node運行代碼:
$ node src/vue/buble/index.js
1
2
3
複製代碼
代碼能夠正常運行,咱們再經過buble編譯代碼:
buble src/vue/buble/index.js
---
1 : const arr = [1, 2, 3]
2 : for (const value of arr) {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
for...of statements are not supported. Use `transforms: { forOf: false }` to skip transformation and disable this error, or `transforms: { dangerousForOf: true }` if you know what you're doing (2:0) 複製代碼
能夠看到buble提示for...of statements are not supported
,因此使用buble以前必定要了解哪些語法不能被支持,以避免出現編譯報錯。
除了命令行以外,咱們還能夠經過API來進行編譯,在代碼中引入buble庫:
npm i -D buble
複製代碼
在src/vue/buble目錄下建立buble-build.js文件:
touch src/vue/buble/buble-build.js
複製代碼
咱們在buble-build.js文件中寫入如下代碼,在這段代碼中咱們經過fs模塊獲取src/vue/buble/index.js文件內容,應用buble的API進行編譯,編譯的關鍵方法是buble.tranform(code):
const buble = require('buble')
const fs = require('fs')
const path = require('path')
const codePath = path.resolve('./src/vue/buble/index.js') // 獲取代碼的絕對路徑
const file = fs.readFileSync(codePath) // 獲取緩衝區文件內容
const code = file.toString() // 將緩衝區文件轉爲文本格式
const result = buble.transform(code) // 經過buble編譯代碼
console.log(result.code) // 打印buble編譯的代碼
複製代碼
經過node執行buble-build.js:
$ node src/vue/buble/buble-build.js
var a = 1
var b = 2
var c = function () { return a + b; }
console.log(a, b, c())
複製代碼
編譯成功!這裏須要注意的是buble.transfomr()方法傳入的參數必須是String類型,不能支持Buffer對象,若是將fs.readFileSync()獲取的Buffer對象直接傳入會引起報錯。
$ node src/vue/buble/buble-build.js
/Users/sam/WebstormProjects/rollup-test/node_modules/_magic-string@0.25.1@magic-string/dist/magic-string.cjs.js:187
var lines = code.split('\n');
^
TypeError: code.split is not a function
複製代碼
Flow is a static checker for javascript.
flow是Javascript靜態代碼類型檢查器,Vue.js應用flow進行類型檢查。
咱們在代碼中引入flow:
npm i -D flow-bin
複製代碼
修改package.json,在scripts中添加flow指令:
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"flow": "flow"
}
}
複製代碼
在代碼根路徑下執行如下指令,進行flow項目初始化:
$ npm run flow init
> rollup-test@1.0.0 flow /Users/sam/WebstormProjects/rollup-test
> flow "init"
複製代碼
此時會在項目根路徑下生成.flowconfig文件,接下來咱們嘗試運行flow進行代碼類型的靜態檢查:
$ npm run flow
> rollup-test@1.0.0 flow /Users/sam/WebstormProjects/rollup-test
> flow
Launching Flow server for /Users/sam/WebstormProjects/rollup-test
Spawned flow server (pid=24734)
Logs will go to /private/tmp/flow/zSUserszSsamzSWebstormProjectszSrollup-test.log
Monitor logs will go to /private/tmp/flow/zSUserszSsamzSWebstormProjectszSrollup-test.monitor_log
No errors!
複製代碼
接下來咱們建立flow的測試文件:
mkdir -p src/vue/flow
touch src/vue/flow/index.js
複製代碼
先看一個官方提供的例子,在src/vue/flow/index.js中寫入以下代碼:
/* @flow */ // 指定該文件flow檢查對象
function square(n: number): number { // square的參數必須爲number類型,返回值必須爲number類型
return n * n
}
console.log(square("2"))
複製代碼
flow只會檢查代碼頂部添加了/* @flow */
或// flow
的源碼。這裏square("2")方法傳入的參數是string型,與咱們定義的類型不相符,運行flow進行類型檢查:
$ npm run flow
> rollup-test@1.0.0 flow /Users/sam/WebstormProjects/rollup-test
> flow
Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ src/vue/flow/index.js:6:8
Cannot call square with "2" bound to n because string [1] is
incompatible with number [2].
複製代碼
flow不只檢查出了錯誤,還能精肯定位到出錯位置。咱們將代碼修改成正確類型:
/* @flow */
function square(n: number): number {
return n * n
}
console.log(square(2))
複製代碼
此時咱們嘗試用node運行src/vue/flow/index.js:
$ node src/vue/flow/index.js
/Users/sam/WebstormProjects/rollup-test/src/vue/flow/index.js:2
function square(n: number): number {
^
SyntaxError: Unexpected token :
複製代碼
能夠看到代碼沒法直接運行,由於node不能識別類型檢查,這時咱們能夠經過babel-node來實現flow代碼的運行,首先安裝babel的flow插件:
npm i -D @babel/plugin-transform-flow-strip-types
複製代碼
修改.babelrc配置文件,增長flow插件的支持:
{
"presets": [
"@babel/preset-env"
],
"plugins": [
"@babel/plugin-transform-flow-strip-types"
]
}
複製代碼
嘗試babel-node運行代碼:
$ babel-node src/vue/flow/index.js
4
複製代碼
獲得了正確的結果,這得益於babel的flow插件幫助咱們消除flow檢查部分的代碼,使得代碼能夠正常運行。
flow的強大之處在於能夠進行自定義類型檢查,咱們在項目的根目錄下建立flow文件夾,並添加test.js文件:
mkdir -p flow
touch flow/test.js
複製代碼
在test.js中寫入以下內容:
declare type Test = {
a?: number;
b?: string;
c: (key: string) => boolean;
}
複製代碼
declare type
表示聲明一個自定義類型,這個配置文件的具體含義以下:
?
表示該屬性能夠爲空);接下來咱們修改.flowconfig,在[libs]下添加flow,這樣flow在初始化時會前往項目根目錄下的flow文件夾中尋找並加載自定義類型:
[ignore]
[include]
[libs]
flow
[lints]
[options]
[strict]
複製代碼
接着咱們在src/vue/flow下建立type.test.js文件:
touch src/vue/flow/type-test.js
複製代碼
寫入以下代碼,對自定義類型進行測試:
/* @flow */
const obj : Test = {
a: 1,
b: 'b',
c: (p) => {
return new String(p) instanceof String
}
}
console.log(obj.c("c"))
複製代碼
經過flow指令進行靜態檢查,並經過babel-node運行代碼:
$ npm run flow
> rollup-test@1.0.0 flow /Users/sam/WebstormProjects/rollup-test
> flow
No errors!
$ babel-node src/vue/flow/type-test.js
true
複製代碼
若是代碼中obj對象不定義c屬性:
/* @flow */
const obj : Test = {
a: 1,
b: 'b'
}
複製代碼
運行flow後會出現報錯:
$ npm run flow
> rollup-test@1.0.0 flow /Users/sam/WebstormProjects/rollup-test
> flow
Please wait. Server is initializing (parsed files 3000): -^[[2A^[[Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ src/vue/flow/type-test.js:2:20
Cannot assign object literal to obj because property c is missing
in object literal [1] but exists in Test [2].
複製代碼
zlib是Node.js的內置模塊,它提供經過Gzip和Deflate/Inflate實現的壓縮功能。
Vue.js源碼編譯時僅用到zlib.gzip()方法,先了解一下gzip的用法:
zlib.gzip(buffer[, options], callback)
複製代碼
參數的含義以下:
建立src/vue/zlib目錄,並建立index.js文件,用於zlib測試:
mkdir -p src/vue/zlib
touch src/vue/zlib/index.js
複製代碼
嘗試經過fs模塊讀取dist/index-cjs.js文件的內容,並經過gzip進行壓縮:
const fs = require('fs')
const zlib = require('zlib')
fs.readFile('./dist/index-cjs.js', (err, code) => {
if (err) return
console.log('原文件容量:' + code.length)
zlib.gzip(code, (err, zipped) => {
if (err) return
console.log('gzip壓縮後容量:' + zipped.length)
})
})
複製代碼
經過node執行代碼:
$ node src/vue/zlib/index.js
原文件容量:657
gzip壓縮後容量:329
複製代碼
值得注意的是,傳入buffer進行壓縮與傳入string進行壓縮的結果是徹底一致的。咱們修改代碼:
fs.readFile('./dist/index-cjs.js', (err, code) => {
if (err) return
console.log('原文件容量:' + code.toString().length)
zlib.gzip(code.toString(), (err, zipped) => {
if (err) return
console.log('gzip壓縮後容量:' + zipped.length)
})
})
複製代碼
再次執行,能夠看到一樣的結果:
$ node src/vue/zlib/index.js
原文件容量:657
gzip壓縮後容量:329
複製代碼
因此結論是不管經過buffer仍是string獲取的length都是一致的,經過buffer和string進行gzip壓縮後得到的結果也是一致的。
A JavaScript parser, mangler/compressor and beautifier toolkit for ES6+.
terser是一個Javascript代碼的壓縮和美化工具,選擇terser的緣由有兩點:
全局安裝terser:
npm i -g terser
複製代碼
經過terser壓縮文件:
terser dist/index-cjs.js
複製代碼
若是先輸入參數再輸入文件,建議增長雙短劃線(--
)進行分割:
terser -c -m -o dist/index-cjs.min.js -- dist/index-cjs.js
複製代碼
各參數的含義以下:
對比壓縮結果,普通壓縮:
$ terser dist/index-cjs.js
"use strict";var a=Math.floor(Math.random()*10);var 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=Object.freeze({a:a,b:b,random:random});const a$1=1;const b$1=2;console.log(test,a$1,b$1);var main=random;module.exports=main;
複製代碼
能夠看到代碼間的空格被去除,代碼結構更加緊湊。下面加入-c參數後再次壓縮:
$ terser dist/index-cjs.js -c
"use strict";var a=Math.floor(10*Math.random()),b=Math.floor(100*Math.random());function random(base){return base&&base%1==0?Math.floor(Math.random()*base):0}var test=Object.freeze({a:a,b:b,random:random});const a$1=1,b$1=2;console.log(test,1,2);var main=random;module.exports=main;
複製代碼
加入-c後,產生以下幾個變化:
下面咱們使用-m參數再對比一下:
$ terser dist/index-cjs.js -m
"use strict";var a=Math.floor(Math.random()*10);var b=Math.floor(Math.random()*100);function random(a){if(a&&a%1===0){return Math.floor(Math.random()*a)}else{return 0}}var test=Object.freeze({a:a,b:b,random:random});const a$1=1;const b$1=2;console.log(test,a$1,b$1);var main=random;module.exports=main;
複製代碼
加入-m後,主要修改了變量的名稱,如random函數中的形參base變成了a,同時加入-m和-c後代碼變得更加精簡:
$ terser dist/index-cjs.js -m -c
"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});const a$1=1,b$1=2;console.log(test,1,2);var main=random;module.exports=main;
複製代碼
咱們能夠經過API進行代碼壓縮,這也是Vue.js採用的方法,在項目中安裝terser模塊:
npm i -D terser
複製代碼
建立src/vue/terser目錄,並建立index.js文件:
mkdir -p src/vue/terser
touch src/vue/terser/index.js
複製代碼
在src/vue/terser/index.js中寫入以下代碼,咱們嘗試經過fs模塊讀取dist/index-cjs.js文件內容,並經過terser進行壓縮,這裏關鍵的方法是terser.minify(code, options)
:
const fs = require('fs')
const terser = require('terser')
const code = fs.readFileSync('./dist/index-cjs.js').toString() // 同步讀取代碼文件
const minifyCode = terser.minify(code, { // 經過terser.minify進行最小化壓縮
output: {
ascii_only: true // 僅支持ascii字符,非ascii字符將轉成\u格式
},
compress: {
pure_funcs: ['func'] // 若是func的返回值沒有被使用,則進行替換
}
})
console.log(minifyCode.code)
複製代碼
咱們修改src/plugin/main.js的源碼,用於壓縮測試:
import * as test from 'sam-test-data'
const a = 1
const b = 2
console.log(test, a, b)
function func() {
return 'this is a function'
}
func() // 使用func()函數,但沒有利用函數返回值,用於測試compress的pure_funcs參數
console.log('') // 加入非ascii字符,用於測試output的ascii_only參數
export default test.random
複製代碼
修改rollup.config.js配置文件,這裏值得注意的是咱們加入了treeshake:false的配置,由於默認狀況下冗餘代碼會被rollup.js剔除,這樣咱們就沒法測試terser壓縮的效果了:
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.js',
output: [{
file: './dist/index-cjs.js',
format: 'cjs'
}],
plugins: [
resolve(),
commonjs(),
babel()
],
treeshake: false // 關閉tree-shaking特性,將再也不自動刪除冗餘代碼
}
複製代碼
應用rollup.js進行打包,並對打包後的代碼進行壓縮:
$ rollup -c
./src/plugin/main.js ./dist/index-cjs.js...
created ./dist/index-cjs.js in 436ms
$ node src/vue/terser/index.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}),a$1=1,b$1=2;function func(){return"this is a function"}console.log(test,a$1,b$1),console.log("\ud83d\ude01\ud83d\ude01");var main=random;module.exports=main;
複製代碼
查看壓縮後的文件,發現咱們的配置生效了:
compress: {
pure_funcs: ['func', 'console.log']
}
複製代碼
本教程主要講解了如下知識點: