本文是《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']
}
复制代码
本教程主要讲解了如下知识点: