Files
2026-07-13 21:37:05 +08:00

4.3 KiB
Raw Permalink Blame History

API 设计模式

Options 模式

面向用户的选项及其内部解析后的版本:

export interface Options {
  verbose?: boolean
  include?: string[]
  exclude?: string[]
}

export interface ResolvedOptions extends Required<Options> {
  root: string
}

function resolveOptions(options: Options = {}): ResolvedOptions {
  return {
    verbose: options.verbose ?? false,
    include: options.include ?? ['**/*'],
    exclude: options.exclude ?? ['node_modules'],
    root: process.cwd(),
  }
}

工厂函数

创建配置好的实例:

export function createContext(options: Options = {}) {
  const resolved = resolveOptions(options)
  const filter = createFilter(resolved.include, resolved.exclude)

  return {
    options: resolved,
    filter,
    transform(code: string, id: string) { /* ... */ },
    async scanDirs() { /* ... */ },
  }
}

// 用法
const ctx = createContext({ verbose: true })
await ctx.scanDirs()

Builder 模式

支持类型累积的可链式调用 API

export function createBuilder<TContext = unknown>() {
  return {
    context<T>(): Builder<T, unknown, unknown> {
      return this as any
    },
    input<T>(schema: T): Builder<TContext, T, unknown> {
      return this as any
    },
    output<T>(schema: T): Builder<TContext, unknown, T> {
      return this as any
    },
    build(): Procedure<TContext> { /* ... */ },
  }
}

// 用法 - 类型在链中传递
const procedure = createBuilder()
  .context<{ user: User }>()
  .input(z.object({ id: z.string() }))
  .build()

插件模式(unplugin

从单一实现生成通用插件:

import { createUnplugin } from 'unplugin'

export default createUnplugin<Options>((options) => {
  const ctx = createContext(options)

  return {
    name: 'my-plugin',
    enforce: 'pre',

    transformInclude(id) {
      return ctx.filter(id)
    },

    transform(code, id) {
      return ctx.transform(code, id)
    },

    // 特定于打包工具的钩子
    vite: {
      configResolved(config) { /* Vite 特有 */ },
    },
    webpack(compiler) {
      compiler.hooks.watchRun.tap('my-plugin', () => { /* ... */ })
    },
  }
})

按打包工具分别导出入口:

// src/vite.ts
import unplugin from '.'
export default unplugin.vite

// src/webpack.ts
import unplugin from '.'
export default unplugin.webpack

惰性 GetterTree-shaking

将特定于打包工具的代码延迟到被访问时才加载:

export function createPlugin<T>(factory: PluginFactory<T>) {
  return {
    get vite() { return getVitePlugin(factory) },
    get webpack() { return getWebpackPlugin(factory) },
    get rollup() { return getRollupPlugin(factory) },
  }
}

只有被访问的 getter 会执行,其余部分被 tree-shaking 移除。

智能默认值

检测环境而非要求配置:

import { isPackageExists } from 'local-pkg'

function resolveOptions(options: Options) {
  return {
    vue: options.vue ?? isPackageExists('vue'),
    react: options.react ?? isPackageExists('react'),
    typescript: options.typescript ?? isPackageExists('typescript'),
  }
}

Resolver 模式

通过函数或对象实现灵活解析:

export type Resolver = ResolverFunction | ResolverObject

export type ResolverFunction = (name: string) => ResolveResult | undefined
export interface ResolverObject {
  type: 'component' | 'directive'
  resolve: ResolverFunction
}

export function ElementPlusResolver(): Resolver[] {
  return [
    { type: 'component', resolve: (name) => resolveComponent(name) },
    { type: 'directive', resolve: (name) => resolveDirective(name) },
  ]
}

Fluent API(验证)

通过克隆实现不可变性的方法链:

class Schema<T> {
  private _def: SchemaDef

  min(value: number): Schema<T> {
    return new Schema({ ...this._def, min: value })
  }

  max(value: number): Schema<T> {
    return new Schema({ ...this._def, max: value })
  }

  optional(): Schema<T | undefined> {
    return new Schema({ ...this._def, optional: true })
  }
}

// 用法
const schema = z.string().min(5).max(10).optional()

Barrel 导出

简洁的公开 API

// src/index.ts
export * from './config'
export * from './types'
export { createContext } from './context'
export { default } from './plugin'