本篇文章是对 实现 Rollup 插件 alias | 使用 TypeScript 实现库的基本流程 | 使用单元测试提高开发效率 的总结。其中涉及到开发一个组件库的诸多知识点。
实现一个经常用的 rollup 插件 alias
首先执行npm init
命令初始化一个package.json文件,因为插件使用了typescript作为类型校验,所以需要执行tsc --init
命令去生成一个ts的配置文件tsconfig.json,执行完上述的命令之后安装项目依赖。
pnpm i rollup typescript @rollup/plugin-typescript tslib -D
先简单实现一下这个插件,插件要求导出一个方法并且返回一个对象
import { Plugin } from 'rollup'
export function alias(): Plugin {
return {
name: 'alias',
resolveId(source: string, importer: string | undefined) {
console.log('resolveId', source, importer)
return source
}
}
}
接下来需要将index.ts编译成可执行的js文件,新增一个rollup配置文件rollup.config.js
,指定输入以及输出
import { defineConfig } from 'rollup'
import typescript from "@rollup/plugin-typescript"
export default defineConfig({
input: './src/index.ts',
output: {
file: './dist/index.js',
format: 'es'
},
plugins: [
typescript({
module:'esnext'
})
]
})
在package.json里面新增一条命令,并执行pnpm build
"scripts": {
"build": "rollup -c rollup.config.js"
},
一般执行到这里会有一个CommonJS和ES module的类型冲突,如图所示
我们只需要在package.json指定类型即可
"type": "module",
再次运行pnpm build
可以发现在dist目录下会生成打包完成之后的index.js文件。
当别人去安装你的包的时候需要所指定执行的文件在哪,即修改package.json
里面的main字段,由"main": "index.js"
改为"main": "./dist/index.js"
使用开发的 alias 插件
新增一个example文件夹新增index.js、add.js写入相关的测试代码
import { add } from './utils/add.js'
console.log(add(1, 2))
export function add (a, b) {
return a + b;
}
在example里面执行初始化并且新增rollup配置文件rollup.config.js
,并且补充build
命令,具体操作和上文类似
import { defineConfig } from 'rollup'
export default defineConfig({
input: 'index.js',
output: {
file: './dist/index.js',
format: 'es'
}
})
通过在example目录下执行如下命令就可以使用我们开发的插件
// ../上层目录
pnpm i ../ -D
执行完之后会新增如下代码
"devDependencies": {
"rollup": "^3.26.2",
"rollup-alias": "link:.." // 新增的依赖
}
在example/rollup.config.js
里面引入我们编写的 alias 插件,完整的代码如下
import { defineConfig } from 'rollup'
import { alias} from 'rollup-alias'
export default defineConfig({
input: 'index.js',
output: {
file: './dist/index.js',
format: 'es'
},
plugins: [
alias()
]
})
在此执行pnpm build
可以发现已经成功的打印出了log
为插件添加TS类型提示
首先补充插件的参数类型提示并且完善一下插件逻辑
import { Plugin } from 'rollup'
interface AliasOptions {
entries: { [key: string]: string }
}
export function alias(options: AliasOptions): Plugin {
const { entries } = options
return {
name: 'alias',
resolveId(source: string, importer: string | undefined) {
console.log('resolveId', source, importer)
const key = Object.keys(entries).find((e) => {
return source.startsWith(e)
})
if (!key) return source
return source.replace(key, entries[key]) + '.js'
}
}
}
执行build之后在我们会发给 alias 传参的时候并没有对应的参数类型提示
这里需要在tsconfig.json
文件中开启 "declaration": true
功能,以及设置"outDir": "./dist"
在package.json
里面添加"types": "./dist/index.d.ts"
,执行完上述操作之后再次执行 pnpm build
补充单元测试
安装vitest,补充单元测试文件index.spec.ts,添加测试命令
pnpm i vitest -D
// index.spec.ts
import { describe, it, expect } from 'vitest'
import { alias } from '.'
describe('alias', () => {
it('should replace when match successful', () => {
const aliasObj:any = alias({
entries: {
'@': './utils'
}
})
expect(aliasObj.resolveId('@/add')).toBe('./utils/add.js')
})
it('should not replace when match fail', () => {
const aliasObj: any = alias({
entries: {
'@': './utils'
}
})
expect(aliasObj.resolveId('!/add')).toBe('!/add')
})
})
"scripts": {
"test": "vitest"
},
我们需要在 build 的时候排除掉我们的测试文件可以在 tsconfig.json
补充如下代码 "exclude": ["./src/*.spec.ts"]
然后执行pnpm test
可以看到这里的测试用例是通过的也可以证明我们写的代码是没问题的。
entries 支持数组格式
这里直接贴完成之后的代码
import { Plugin } from 'rollup'
interface AliasOptions {
entries: { [key: string]: string } | { find: string, replacement: string }[]
}
export function alias(options: AliasOptions): Plugin {
const entries = normalizeEntries(options.entries)
return {
name: 'alias',
resolveId(source: string, importer: string | undefined) {
console.log('resolveId', source, importer)
const entry = entries.find((e) => e.match(source))
if (!entry) return source
return entry.replace(source)
}
}
}
function normalizeEntries(entries: AliasOptions["entries"]) {
if (Array.isArray(entries)) {
return entries.map(({ find, replacement }) => {
return new Entry(find, replacement)
})
} else {
return Object.keys(entries).map((key) => {
return new Entry(key, entries[key])
})
}
}
class Entry {
constructor(private find: string, private replacement: string) { }
match(filePath: string) {
return filePath.startsWith(this.find)
}
replace(filePath: string) {
return filePath.replace(this.find, this.replacement)
}
}
以上就简单的实现了一个rollup插件开发的大致流程