rollup 插件开发
tips: 本文带有强烈的个人理解,所以有些地方我理解可能有误,读者如有质疑不用怀疑,绝对是我的错,是我水平太次。
上一篇文章讲了讲 rollup
的简单配置,对于插件这块没有怎么讲,那么本篇开启插件的旅程~
插件生命周期
密密麻麻的一堆堆的API
真让人讨厌,遇到问题不要慌。
下面我将列出一些我个人觉得常用的API
,咱们先熟悉大概知道API
干啥的。
name
: 插件的名称,用于错误消息和警告。
version
: 插件的版本,用于插件间通信场景。
来看下下面两张图
编译阶段钩子
输出生成钩子
看完了? OK,那么我们开始进入实战……
你肯定好奇我为啥不细讲,因为我看到这些API
我自己也懵逼,我讲了你更懵逼……
自动上传构建包与自动生成文档
在某某科技公司,我们男主角”小明”正在快乐的摸鱼划水。
突然领导拍着他的肩膀说:小火砸,现在有个任务交给你,公司的项目比较多。有很多的重复工具相关的代码,现在你负责把这些工具类代码抽出来,到时候给其他的开发使用。你这个包回头要发到cdn
上,体积最好小点,不要影响用户体验。
小明这条老咸鱼三下五除二,很快的就把工作做完了,也用rollup
把代码打包好了,手动上传代码至cdn
服务器上。
但是小明呢喜欢摸鱼,每次上传cdn
服务器都要手动上传也挺麻烦的,万一哪天整错了,出问题了呢?
小明就去翻翻rollup
文档,发现 输出生成钩子 有个叫writeBundle
的 API
,小明突然灵光乍现,可以rollup
输出生成代码后调用接口直接将代码上传至cdn
服务器呀。
tips: 知识点 writeBundle
钩子,会在构建完成后调用
// upload-source.js
import FormData from "form-data";
import axios from "axios";
export default function buildEndUploadBundle() {
return {
name: "uploadBundle", // this name will show up in warnings and errors
async writeBundle() {
const formData = new FormData();
const filePath = path.join(process.cwd(), "/dist/index.js");
// 构建完成后读取本地文件
formData.append("files[]", fs.createReadStream(filePath));
const formHeaders = formData.getHeaders();
// const { code } = await axios.post("xxxx.com/upload", formData, {
// headers: {
// ...formHeaders,
// },
// });
if (code) {
// 调用钉钉通知
}
},
};
}
小明写完这个插件之后每次只需要执行构建命令,打包后的代码就会自动的上传至cdn
服务器,又能节省出时间来摸鱼了。
过了半个月,小明写的工具类已经应用到公司的所有项目中。
但是现在小明很苦恼,同事需要什么工具方法的时候,小明就要往里头加,写文档,打包,最后告诉某位同事你需要的方法加好了,有时候忙小明忘了通知……搞得开发非常的不愉快。
这个小问题还不简单,小明分分钟就搞定了。只需要在上传完成的阶段调用钉钉机器人通知同事就完事了。
可接着小明突然发现一个事情,现在工具类已经很大了,有不少的方法函数,虽然每个方法都加有注释,但是缺少文档!可就算现在去写文档,文档后期维护也是一个问题!
现在问题来了 文档怎么办?
// doc-generate.js
import * as parser from "@babel/parser";
import traverse from "@babel/traverse";
import doctrine from "doctrine";
// 省略一些与本文相关性不太大的代码
...
export default function docGenerate() {
const docs = [];
return {
name: "docGenerate",
async transform(code, id) {
const sourceCode = code;
const babelParsedAst = parser.parse(sourceCode, {
sourceType: "unambiguous",
});
traverse.default(babelParsedAst, {
ExportNamedDeclaration(path) {
const funcDeclaration = path.get("declaration");
docs.push({
type: "function",
name: funcDeclaration.get("id").toString(),
params: funcDeclaration.get("params").map((paramPath) => {
return {
name: paramPath.toString(),
type: resolveType(paramPath.getTypeAnnotation()),
};
}),
return: resolveType(
funcDeclaration.get("returnType").getTypeAnnotation()
),
doc:
path.node.leadingComments &&
parseComment(path.node.leadingComments[0].value),
});
},
});
return {
code,
map: null,
};
},
writeBundle() {
fs.writeFile("./文档.md", generateDoc(docs), function (err) {
if (err) {
return console.log("文件写入失败!" + err.message);
}
console.log("文件写入成功!");
});
},
};
}
tips: 知识点 transform
钩子,会把源码和源码文件地址传递过来。
正当小明正在苦思冥想文档怎么的时候,某位神秘的大佬来给小明讲了讲神奇的AST
与babel
。
小明突发奇想利用 rollup
构建阶段 transform
,这个 API
会返回代码文件内容和文件地址,这个时候拿到 code
代码,再去使用 babel
分析 AST
树拿到我们想要的信息存储,最终等待编译阶段的时候重新生成一篇新的文档,由于文档内容来源于注释,所以每次构建都是最新的文档。
完结
这一篇文章给大家相对粗浅的讲了一下插件开发,讲了两个 API
,其实我个人用的多还是 writeBundle
,写过一个上传source map
的插件。
第一个插件 用了 writeBundle
钩子,这个插件做的事情大家都应该能看得懂,无非就是读取文件上传然后再去调用接口。
第二个插件用了 transform
钩子难度直线飙升,大家应该看的很懵逼把? 不要慌! 其实大部分的场景我们使用 npm
上的rollup
插件已经能够满足我们的开发需求,之所以写第二个插件是为了让大家认识下 babel
和 AST
。
可能你们会好奇,为什么我不细讲讲第二个插件,因为第二个插件的所需要的知识点不是一两句话能够讲清楚的,单说 babel
复杂的配置项,让人脑壳痛,更别提 AST
树(好像 AST
树简单点……
当然如果你们对 babel
感兴趣的话,有机会出”十几篇”文章聊聊也不是不可以哦!这里提一嘴,我们用的 vite
webpack
等等背后都有 babel
参与!
下一篇文章,来聊聊 cli
工具开发。