哈喽,大家好,我是 SuperYing。今天我们来聊聊 Rollup 插件开发,整点代码,小试牛刀。
不知道大家是否了解过 Rollup 插件相关的东西,感兴趣的话可以到 Rollup 官网 瞅瞅。
简单点理解,Rollup 的插件就是一个函数,返回值是一个对象,这个对象需要包括 Rollup 规定的属性,编译 hooks 和 输出 hooks 等。这部分规定的内容都可以在官网的 plugin-development 部分找到,我就不多赘述了哈。Rollup 插件可以实现诸如在打包前传输代码,在 node_modules 文件夹中查找第三方模块等功能
最近在做一个远程加载组件库的小玩意,为什么要整它呢?现在比较流程的使用组件库方式就是 npm 模块,但是这种方式需要先将框架代码下载到本地,然后在工程中引用。那么也就是说,我自己开发的组件库,要经过 【打包 - 发布 - 卸载旧版本 - 安装新版本】这样一系列的操作过程,循环往复,略微有些麻烦。因此呢,就想将组件库的引用改为远程加载,每次组件库升级仅需要发布即可,哈哈哈,理想很丰满.....
好啦,废话不多说,开整....
1.创建插件
新增文件 plugins/roll-plugin-remote-ui.js。(不打算将插件单独发布,与远程加载功能放在同一个库中)。
为什么 ts 工程插件要用 js 写插件? 因为要直接在 rolluo.config.js 中使用,而 ts 文件需要经过编译才可用,所以用 js。
2.实现插件
首先我们需要思考一下,远程加载需要什么
1.import 引入
2.请求远程资源,即 http/https
3.export 导出
我想大致就上面那么多吧,那我们就来实现它。
Rollup 提供一个 build hook --- resolveId,我们可以通过该 hook 定义一个解析器,在该解析器中捕获远程加载的请求地址,然后利用 node 的能力将远程的资源下载到本地,最终将 import 的目标地址指向生成的本地资源。
思路很清晰,是不是,但是有一个问题,我们怎么判断当前 import 的是资源是需要远程加载呢?判断 source(import 语句中的导入对象地址,如 import foo from '../foo.js',source 就是 '../foo.js') 是否存在 http/https 吗?可以,但是个人感觉不够精准,因为我们是有上面一套处理流程的,要请求资源并下载到本地的,只要 source 包括 http/https 就一定需要走这一套吗,答案是不一定。这个时候就体现了约定的重要性,我们规定远程加载的组件库地址以 @remote 为前缀,凡是 source 存在该前缀的资源,一律视为远程加载。
下面是插件的代码:
/**
* 远程 UI 资源插件
* 拦截 import,若为远程资源,下载到本地并指向本地资源地址
*/
import path from 'path'
import fs from 'fs-extra'
import request from 'request'
export default function remoteUiPlugin() {
return {
// rollup plugin 约定,需要包含 name 属性,值以 rollup-plugin 开头
name: 'rollup-plugin-remote-ui',
async resolveId(id) {
// 若为 @remote/ 开头,说明引用的是远程资源
if (/@remote\//.test(id)) {
const [url] = id.match(/https?.*?$/igm) || []
if (!url) return id
const timeStamp = new Date().getTime()
const finalUrl = `${url}${url.indexOf('?') > -1 ? '&' : '?'}timestamp=${timeStamp}`
return await generateLocal(finalUrl)
}
}
}
}
// 生成本地资源
function generateLocal(url, localPath = './remote-ui') {
const folder = path.resolve(__dirname, localPath)
// 同步确认该目录是否存在,若不存在,则创建
fs.ensureDirSync(folder)
const queryIndex = path.basename(url).indexOf('?')
const fileName = path.basename(url).substring(0, queryIndex)
const local = path.resolve(folder, `./${fileName}`)
return new Promise((resolve, reject) => {
const stream = fs.createWriteStream(local)
request(url).pipe(stream).on("close", function (err) {
if (err) reject(err)
// 返回本地资源地址
resolve(local)
});
})
}
好啦,以上就是整个插件的实现代码,很简陋有木有,还有需要需要改进的地方。如果各位大佬有更好的思路欢迎评论区分享哈。
3.应用插件
这一步涉及到 Rollup 配置文件 rollup.config.js,引入插件文件后,在 plugins 配置项下添加即可。
代码如下:
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import babel from '@rollup/plugin-babel';
import { DEFAULT_EXTENSIONS } from '@babel/core';
import rollupTypescript from 'rollup-plugin-typescript2';
import { eslint } from 'rollup-plugin-eslint';
import { terser } from 'rollup-plugin-terser'
import remoteUiPlugin from './plugins/rollup-plugin-remote-ui.js'; // 引入插件
import pkg from './package.json';
import path from 'path'
// 环境
const env = process.env.NODE_ENV
const name = 'RemoteUI'
const config = {
input: path.join(__dirname, 'src/index.ts'),
output: [
// umd
{
name,
file: pkg.umd,
format: 'umd'
},
// commomjs
{
file: pkg.main,
format: 'cjs'
},
// esm
{
file: pkg.module,
format: 'es'
}
],
plugins: [
eslint({
throwOnError: true, // lint 结果有错误将会抛出异常
throwOnWarning: true,
include: ['src/*.ts', 'src/**/*.ts'],
exclude: ['node_modules/**', 'dist/**', '*.js'],
}),
resolve(), // so Rollup can find `ms`、
commonjs(), // so Rollup can convert `ms` to an ES module
rollupTypescript(),
remoteUiPlugin(), // 使用插件
babel({
// 编译库使用 runtime
babelHelpers: 'runtime',
// 只转换源代码,不运行外部依赖
exclude: 'node_modules/**',
// babel 默认不支持 ts 需要手动添加
extensions: [
...DEFAULT_EXTENSIONS,
'.ts',
],
}),
],
external: ['vue']
}
export default config
4.测试插件
到此我们的插件就算是用起来了,我们来写点代码测试一下,瞅瞅效果。
在 src/index.ts 中添加如下代码:
// 这里的远程地址我实在本地 nginx 部署的组件库,大家自己测试的时候需要改成可用的地址哦
export * from '@remote/http://localhost:9090/dist/bgy-plus/dist/index.full.mjs'
在 test/test.js 中添加如下代码:
// eslint-disable-next-line @typescript-eslint/no-var-requires
const RemoteUI = require('../dist/index.js')
// RemoteUI 即为最终 import 的组件库对象,我们控制台打印一下其中的 ElButton 组件
console.log('RemoteUI', RemoteUI.ElButton)
打开终端,执行 npm run test(关于整个目录结构及 npm script 等,请参考 从0到1搭建 Rollup + TypeScript 模板工程)。
我们可以看到,组件对象正常打印,左侧 remote-ui 目录中,也生成了本地资源文件。
OK,整完收工!以上便是「 Rollup 插件开发牛刀小试 」的全部内容,感谢阅读。
欢迎各路大佬讨论、批评、指正,共同进步才是硬道理!