实现 Rollup 插件alias 并使用单元测试提高开发效率

本篇文章是对 实现 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的类型冲突,如图所示
实现 Rollup 插件alias 并使用单元测试提高开发效率_第1张图片
我们只需要在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:.." //  新增的依赖
}

实现 Rollup 插件alias 并使用单元测试提高开发效率_第2张图片
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
实现 Rollup 插件alias 并使用单元测试提高开发效率_第3张图片

为插件添加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 传参的时候并没有对应的参数类型提示
实现 Rollup 插件alias 并使用单元测试提高开发效率_第4张图片
这里需要在tsconfig.json文件中开启 "declaration": true 功能,以及设置"outDir": "./dist"
package.json里面添加"types": "./dist/index.d.ts",执行完上述操作之后再次执行 pnpm build
实现 Rollup 插件alias 并使用单元测试提高开发效率_第5张图片
实现 Rollup 插件alias 并使用单元测试提高开发效率_第6张图片

补充单元测试

安装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 可以看到这里的测试用例是通过的也可以证明我们写的代码是没问题的。
实现 Rollup 插件alias 并使用单元测试提高开发效率_第7张图片

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插件开发的大致流程

你可能感兴趣的:(rollup)