上一章我们学习了快速上手rollup
,学会如何使用rollup
进行打包操作。相信学习一项新的技术,应该都会去查看对应的官方文档进行辅助学习,那么rollup最重要的三个配置是什么? 是的,就是input
、output
和plugins
,这期就来讲讲plugins在rollup是如何运行的。
Rollup
什么是plugins?
让我们先来看看plugins在配置中是如何使用的
//rollup.config.js
import myExample from './rollup-plugin-my-example.js';
export default ({
input: 'index.js', // resolved by our plugin
output: [{
file: './dist/bundle.js',
format: 'cjs'
}],
plugins: [myExample()] // !!!
});
从上述代码中可以看出,首先需要import引入插件
,那么前提是需要安装
对应的插件,然后引入使用,plugins是一个数组
,说明引用的插件可以是多个,基本的使用已经了解,那我们来具体看看插件到底是什么!
这时候当然要结合rollup文档来进行学习啦~
可以看到rollup
插件是一个由一个或多个属性、钩子组成的对象。
可以自行到github上搜索rollup/plugins->packages文件夹下就是各种插件了,以json这个插件为例来看看代码
import { createFilter, dataToEsm } from '@rollup/pluginutils';
export default function json(options = {}) {
const filter = createFilter(options.include, options.exclude);
const indent = 'indent' in options ? options.indent : '\t';
return {
name: 'json',
// eslint-disable-next-line no-shadow
transform(json, id) {
if (id.slice(-5) !== '.json' || !filter(id)) return null;
try {
const parsed = JSON.parse(json);
return {
code: dataToEsm(parsed, {
preferConst: options.preferConst,
compact: options.compact,
namedExports: options.namedExports,
indent
}),
map: { mappings: '' }
};
} catch (err) {
const message = 'Could not parse JSON file';
const position = parseInt(/[\d]/.exec(err.message)[0], 10);
this.warn({ message, id, position });
return null;
}
}
};
}
不用每一行都读懂,只需要看看插件是如何实现的,可以传入一个option
参数,默认空,看return
返回了一个对象
,这个对象中包含了属性name
,和钩子(方法)transform
,暂时先知道这些就足够了!
钩子的作用不知道没事,下面会详细介绍,看看我自己写的plugins
export default {
input: './index.js', //入口
output: { //出口
file: './dist/bundle.js', //文件路径
format: 'cjs' //输出格式
},
plugins: [{
buildStart() {
console.log(1, 'buildStart');
},
a() {
console.log(a);
}
},
{
options() {
console.log(2, 'options');
}
}
]
};
既然已经知道了插件是一个对象,对象中包含属性和钩子,那么我直接添加一个对象,里面写上钩子,是不是也可行呢?原理是不是一样的,如果无法验证,运行就知道结果了
这里输出了2 options
和1 buildStart
, 首先得到一个结论,明明有三个函数,为什么只打印了两个?明自定义写的函数,内部是不存在该钩子的所以不会执行, 第二个结论是什么呢? 这里2比1 先打印,说明执行的顺序和插件引入的顺序无关,因为引入是1 是在前的。
相信大家现在对 rollup 的 plugins 应该有了一些了解了吧,
构建过程
看完这张图,我们得先理一下rollup的打包流程,来结合理解。
options
这是构建前的第一个钩子,可以传入参数,用于替换默认配置参数,如果没有参数配置,就是用rollup.config.js的默认配置进行打包,
buildStart
开始构建
resolveId
解析路径
在打包过程中,一个js文件可能引入不同的依赖,这时候计算机是不知道这些依赖都是什么的,所以需要这个钩子,来先将所有文件的路径都解析出来,举个例子:
import jq from 'jquery'
import b from './b.js'
import JQ from 'http://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js'
console.log(b);
console.log(jq);
console.log(JQ);
一个文件中可能引入本地文件,线上引入和第三方引入,这种时候该如何打包? 思考一下......
你可能会说,直接添加到打包后的文件中就行了,但是计算机是计算机,不像人眼睛看到的一样,所以需要转化成他能识别的路径。
一共有五种引入方式,分别是:虚拟模块
,用户模块
,第三方模块
,内置模块
和data协议
这个钩子会解析这些模块的引入方式路径,例如相对路径最终会转换成绝对路径,都由钩子内部实现。
load
路径解析完,当然就是要加载
了,将所有的模块全部加载进来,因为前面如果没有resolveId的话,路径无法被识别就没有办法加载了。
举个例子
//a.js
import './b.js'
//b.js
import './c.js'
const b = 'b'
console.log(b);
//c.js
console.log('c');
这边a是入口文件,打包a.js
,a.js
中引入了b.js
文件,b.js
文件中引入了c.js
文件,在load
加载的时候,会将全部内容加载到同一个js文件下。看看结果:
//bundle.js
'use strict';
console.log('c');
const b = 'b';
console.log(b);
transform
转换
node 只能解析.js
/ .json
/ .node
的文件,其他文件是无法识别的,但是你一个项目中不可能只有这些文件,最简单的包括图片.jpg
或者.ts
文件等等,都是无法被解析的,所以这个时候就需要transform
这个钩子来实现转换,把无法识别的文件,转换成可以识别的.js文件,这样才可以继续打包。
moduleParsed
模块解析
这里进行判断,如果引入的文件还有其他依赖,则返回·resolveId
,继续执行,直到所有模块都被加载并且转换为止,这时候加载进来的所有内容时字符串,moduleParsed
会将这个字符串,转换成AST
(abstract syntax tree) 抽象语法树
格式。
import a from '12313'
let tips = [
"Click on any AST node with a '+' to expand it",
"Hovering over a node highlights the \
corresponding location in the source code",
"Shift click on an AST node to expand the whole subtree"
];
function printTips() {
tips.forEach((tip, i) => console.log(`Tip ${i}:` + tip));
}
这是一个基本的js文件结构,有引入,有变量,有函数,到这一步就已经将所有内容解析进来了,来看看生成的AST树结构,每个结构都有type
属性和start
end
开始的字符数和结束位置,转换成清晰的树形结构
buildEnd
结束构建
小结
了解完构建阶段的这些钩子后,回过头来继续讲一下,前面的plugins
。plugins是一个对象,包含属性和钩子,而其中的钩子就是构建阶段这些钩子,那什么时候会执行这些钩子呢?如何执行?
这里我自己写了一个例子,加深印象,大家也可以自己动手试试,这样才能更好的记忆。
export default {
input: './a.js', //入口
output: { //出口
file: './dist/bundle.js', //文件路径
format: 'cjs' //输出格式
},
plugins: [{
buildStart() {
console.log(1, 'buildStart');
},
},
{
options() {
console.log(2, 'options');
},
resolveId() {
console.log(2, 'resolveId');
}
},
{
transform() {
console.log(3, 'transform');
},
load() {
console.log(3, 'load');
},
moduleParsed() {
console.log(3, 'moduleParsed');
}
},
{
buildStart() {
console.log(4, 'buildStart');
},
buildEnd() {
console.log(4, 'buildEnd');
}
}
]
};
这里是自己定义的插件,我们测试钩子的执行,就不需要其他内容了。这里基本上的钩子都添加了,并且我加上了顺序,第一个plugins
就输出1 + 钩子名
,以此类推,一共四个plugin
,其中都包含不同的钩子,来看看打包结果:
这里第一个打印的是2 options
,是第二个插件中的options
钩子执行的代码,所以证实了插件引入顺序与执行顺序无关,而是与插件中包含的钩子有关,每执行到一个步骤的时候,都会遍历一次plugins
,查看插件中是否有与之对应的钩子,并且执行他。
简单来说,假设执行到了buildStart
,就会自动遍历插件中的每一个对象,看看他们之中是否有buildStart
的钩子,如果有就执行,没有就继续。并且每一个钩子都是如此的。
vue2是如何使用rollup打包的
学习完rollup
,就让我们来看一看,在别的项目中是如何使用rollup
进行打包的,这里就以vue2
为例,来看看更多rollup
的高级使用或者你不知道的使用方式。
要查看一个项目的代码首先当然是先拉代码啦,这里附上vue2源码,先将代码拉下来,我们继续进行。
package.json
第一步要看一个项目,首先得从README
或者package.json
文件入手,README
查看项目说明和使用,package.json
查看一些命令,这里我们是要看看如何使用rollup
进行打包的,所以我们就从package.json文件入手。
找到了package.json
的scripts
字段,但是如果毫无目的的话,还真是无从下手呢,这指令看的应该头都会大把,不过还好我们是带着目的来的,一般先看两条指令,一个是build
打包指令,另一个是dev
运行指令,这边我们主要研究打包,那就先看看build指令吧。
找到了build
字段,看看指令内容是node scripts/build.js
,很明显的看出,使用node
命令执行了scripts
下的build.js
文件,我们跟随指令来到build.js文件。
build.js
虽然从build
打包指令中还没有看到和rollup
相关的信息,但是一进入build.js
第一眼就看到了,在build.js
中引入了rollup
,看源码不需要一行一行都读懂,只需要看到关键部分,那么就让我们继续往下看。
可以看到这里调用一个函数build(),并且传入了一个参数,build是构建打包的意思,那么这个函数一定和打包有关咯,那就找到这个函数,并且大概看看他的作用是什么
function build (builds) {
let built = 0
const total = builds.length
const next = () => {
buildEntry(builds[built]).then(() => {
built++
if (built < total) {
next()
}
}).catch(logError)
}
next()
}
从代码中可以看出,这边调用了另一个函数buildEntry
函数,并且还是一个异步函数,继续看到buildEntyr
函数。
function buildEntry (config) {
const output = config.output
const { file, banner } = output
const isProd = /(min|prod)\.js$/.test(file)
return rollup.rollup(config)
.then(bundle => bundle.generate(output))
.then(({ output: [{ code }] }) => {
if (isProd) {
const minified = (banner ? banner + '\n' : '') + terser.minify(code, {
toplevel: true,
output: {
ascii_only: true
},
compress: {
pure_funcs: ['makeMap']
}
}).code
return write(file, minified, true)
} else {
return write(file, code)
}
})
}
看一个函数最重要的是什么? 是入参和出参,这边咱们入参可能不是很清楚是什么,那看看函数返回了什么,return
了一个rollup.rollup
的方法,看过rollup文档的话,应该会对这个方法有点熟悉,那我们就来详细看看rollup.rollup这个方法的作用吧!!!
rollup.rollup
rollup.rollup
接收一个配置对象作为参数,并且返回一个promise
对象,去解析有各种属性和方法的bundle
对象,在这个步骤会执行tree-shaking
,
在bundle
对象中,可以调用bundle.generate
方法实现打不同版本的包,
rollup.rollup(config)
.then(bundle => bundle.generate(output))
.then(({ output: [{ code }] }) => {
if (isProd) {
const minified = (banner ? banner + '\n' : '') + terser.minify(code, {
toplevel: true,
output: {
ascii_only: true
},
compress: {
pure_funcs: ['makeMap']
}
}).code
return write(file, minified, true)
} else {
return write(file, code)
}
})
rollup.rollup(config).then(bundle => bundle.generate(output))
这里就可以看出返回了一个bundle对象,后续就是判断是否压缩(isProd)
,那么我们看完函数的出参,回过头我们看一看入参,传入的配置参数是什么?
顺着参数往上找,就可以找到这个一开始定义好的builds参数,这里引入了config.js
文件,那么我们就去config.js
文件中看看,到底定义了什么,而getAllBuilds()
又是什么方法。
这里提示:一定要把vue的源码拉下来,结合着看,不然可能看不懂,因为代码太长了没法一一黏贴过来,所以要自己实际去操作一下,这里主要讲该如何去查看并且学习,vue2如何使用rollup打包的。
config.js
config.js中引入了大量的rollup的插件(plugin),前面也介绍了插件的作用,继续看:
在builds对象里包含了各种不同版本的包,接下来就是找到getAllBuilds这个方法在哪里,因为在build.js中引入了,来到最下面:
这里把使用Object.keys的方法将builds对象进行了遍历,生成map,把配置返回,上有有配置方法就不再看了~ 这样就实现了不同版本的包获取对应的配置传给build.js文件进行打包了。
总结
学习如何查看项目的源码,首先要确定自己的目标是什么,带着目的和问题进行查看,这样才有方向,毕竟一个项目这么大,一个文件一个文件看是不实际的也很浪费时间,学习效率并不高,甚至到最后还可能看不懂,具体的流程我已经一步一步详细的列出来,并且解析了一下,也是初学,还有很多提升的空间,大家可以借鉴。