前言
这一篇记录rollup如何开发一个插件。文档参考: 官方文档。
rollup插件概述
首先,rollup插件是一个对象,需要遵循rollup的一些规定。rollup也为插件提供了对应的属性与一些钩子函数。在开发rollup插件之前可以先去插件列表中寻找是否有对应的插件 。
rollup插件的约定
首先来看rollup插件的约定,具体有几下几点 :
- 插件应该有一个带有rollup-plugin-前缀的明确名称。
- 中包含rollup-plugin关键字package.json。
- 应该测试插件。我们推荐mocha或ava,它们支持开箱即用的 promise。
- 尽可能使用异步方法。
- 用英语记录您的插件。
- 如果合适,请确保您的插件输出正确的源映射。
- 如果您的插件使用“虚拟模块”(例如用于辅助函数),请在模块 ID 前加上\0. 这可以防止其他插件尝试处理它。
rollup 插件的属性值
rollup插件的属性值就一个:name,string类型值,插件的名称,用于错误消息和警告。
rollup 插件的hooks(钩子)
rollup 插件hooks 分为 build hooks和output generation hooks。hooks就是在构建和生产时的各个阶段调用的函数。
build hooks
build hooks是在构建阶段运行,我理解就是当rollup.rollup(inputOptions)时运行,这些hooks主要作用就是在处理之前定位,提供和转换输入文件。钩子可以影响构建的运行方式,有不同种类的hooks(以下用钩子来替代hooks了):
-
async : 异步钩子,此类型的hooks需要返回一个promise resolve的值。否则会被标记成sync类型
-
sync : 同步钩子
-
first : 当插件执行此类型钩子时,此钩子一直会顺序的执行,直到返回一个bull或者undefind
-
sequential : 这个类型钩子会按指定顺序运行,如果其中某个钩子是异步的,之后的钩子会等当前钩子解析后再执行
-
parallel:这种类型的钩子和上面的sequential类型一样,不通点在与如果顺序执行时某个钩子时异步的,后续钩子不需等待,可以并行执行。
这几种类型的钩子官方文档上游明确说明,并且有流程图 ,如下所示 :
![](https://img.haomeiwen.com/i13911304/9652eec68bb38e25.png)
构建阶段的第一个钩子是options,最后一个总是buildEnd。如果出现构建错误,closeBundle将在此之后调用。此处不再贴出具体hooks的含义与用法,详见文档。
Output Generation Hooks
可以提供有关生成的包的信息,并在完成后修改构建。它们与Build Hooks 的工作方式和类型相同,但在每次调用bundle.generate(outputOptions)or 时分别调用bundle.write(outputOptions)。仅使用输出生成挂钩的插件也可以通过输出选项传入,因此仅针对某些输出运行。
输出生成阶段的第一钩是outputOptions,generateBundle产生的输出bundle.generate(...),writeBundle输出经成功生成bundle.write(...),或者renderError输出代期间的任何时候发生了错误。
流程图如下 :
![](https://img.haomeiwen.com/i13911304/7d920c96f493a485.png)
插件间的通信
主要的插件通信方式有:
- 在this.resolve中添加自定义内容用于通信
function requestingPlugin() {
return {
name: 'requesting',
async buildStart() {
const resolution = await this.resolve('foo', undefined, {
custom: { resolving: { specialResolution: true } }
});
console.log(resolution.id); // "special"
}
};
}
function resolvingPlugin() {
return {
name: 'resolving',
resolveId(id, importer, { custom }) {
if (custom.resolving?.specialResolution) {
return 'special';
}
return null;
}
};
}
- 通过模块自定义的meta通信
function annotatingPlugin() {
return {
name: 'annotating',
transform(code, id) {
if (thisModuleIsSpecial(code, id)) {
return { meta: { annotating: { special: true } } };
}
}
};
}
function readingPlugin() {
let parentApi;
return {
name: 'reading',
buildEnd() {
const specialModules = Array.from(this.getModuleIds()).filter(
id => this.getModuleInfo(id).meta.annotating?.special
);
// do something with this list
}
};
}
- 直接插件通信
需要注意的是api属性永远不会与任何即将推出的插件hooks发生冲突。
function parentPlugin() {
return {
name: 'parent',
api: {
//...methods and properties exposed for other plugins
doSomething(...args) {
// do something interesting
}
}
// ...plugin hooks
}
}
function dependentPlugin() {
let parentApi;
return {
name: 'dependent',
buildStart({ plugins }) {
const parentName = 'parent';
const parentPlugin = options.plugins
.find(plugin => plugin.name === parentName);
if (!parentPlugin) {
// or handle this silently if it is optional
throw new Error(`This plugin depends on the "${parentName}" plugin.`);
}
// now you can access the API methods in subsequent hooks
parentApi = parentPlugin.api;
}
transform(code, id) {
if (thereIsAReasonToDoSomething(id)) {
parentApi.doSomething(id);
}
}
}
}
通过插件源码分析开发插件
我这里找了rollup-plugin-uglify的源码,如下 :
const { codeFrameColumns } = require("@babel/code-frame");
const Worker = require("jest-worker").default;
const serialize = require("serialize-javascript");
function uglify(userOptions = {}) {
if (userOptions.sourceMap != null) {
throw Error("sourceMap option is removed, use sourcemap instead");
}
const normalizedOptions = Object.assign({}, userOptions, {
sourceMap: userOptions.sourcemap !== false
});
["sourcemap"].forEach(key => {
if (normalizedOptions.hasOwnProperty(key)) {
delete normalizedOptions[key];
}
});
const minifierOptions = serialize(normalizedOptions);
return {
name: "uglify",
renderStart() {
this.worker = new Worker(require.resolve("./transform.js"), {
numWorkers: userOptions.numWorkers
});
},
renderChunk(code) {
return this.worker.transform(code, minifierOptions).catch(error => {
const { message, line, col: column } = error;
console.error(
codeFrameColumns(code, { start: { line, column } }, { message })
);
throw error;
});
},
generateBundle() {
this.worker.end();
},
renderError() {
this.worker.end();
}
};
}
exports.uglify = uglify;
其实可以看出主要就是用了renderStart,renderChunk这两个钩子处理代码,然后再generateBundle与renderError中关闭。
自己简单封装一个插件
这个插件就是把项目中所有的console删除,具体代码如下
export default function removeConsole() {
return {
name: 'remove-console',
transform(code, id) {
const Reg = /console\.log\(.*\)/ig;
return code.replace(Reg, "")
},
}
}
没错,就是这几行代码,当然这这是一个demo级插件,真正的生产级插件还需要丰富的边界判断。
后记
以上就是我在学习开发rollup插件的学习笔记,希望可以对大家有所帮助。
网友评论