Skip to content

插件系统

robuild 基于 rolldown 的插件系统,提供了强大的扩展能力。rolldown 原生支持 JSON、CommonJS、模块解析和 React/JSX 转换,无需额外插件。

🔌 插件兼容性

Rolldown 原生支持

rolldown 内置了许多常用功能,无需额外插件:

typescript
import { defineConfig } from 'robuild'

export default defineConfig({
  entries: [
    {
      type: 'bundle',
      input: './src/index.ts',
      // 以下功能原生支持,无需插件:
      // - JSON 文件导入
      // - CommonJS 模块
      // - Node.js 模块解析
      // - React/JSX 转换
      // - TypeScript 编译
    }
  ]
})

Rollup 插件支持

robuild 兼容大部分 Rollup 插件:

typescript
import { defineConfig } from 'robuild'
import { visualizer } from 'rollup-plugin-visualizer'
import { terser } from 'rollup-plugin-terser'

export default defineConfig({
  entries: [
    {
      type: 'bundle',
      input: './src/index.ts',
      plugins: [
        // 打包分析插件
        visualizer({
          filename: 'dist/stats.html',
          open: true
        }),
        // 代码压缩插件
        terser({
          compress: {
            drop_console: true
          }
        })
      ]
    }
  ]
})

Unplugin 支持

Universal 插件支持,跨平台兼容:

typescript
import { defineConfig } from 'robuild'
import { unpluginAutoImport } from 'unplugin-auto-import/rollup'
import { unpluginIcons } from 'unplugin-icons/rollup'

export default defineConfig({
  entries: [
    {
      type: 'bundle',
      input: './src/index.ts',
      plugins: [
        // 自动导入插件
        unpluginAutoImport({
          imports: ['vue', 'vue-router'],
          dts: true
        }),
        // 图标插件
        unpluginIcons({
          compiler: 'vue3'
        })
      ]
    }
  ]
})

原生功能

1. JSON 文件支持

rolldown 原生支持 JSON 文件导入,无需额外插件:

typescript
// src/config.json
{
  "name": "my-app",
  "version": "1.0.0",
  "features": ["auth", "dashboard"]
}

// src/index.ts
import config from './config.json'

console.log(config.name) // "my-app"
console.log(config.features) // ["auth", "dashboard"]

2. React/JSX 支持

rolldown 原生支持 React 和 JSX 转换:

typescript
// src/App.tsx
import React from 'react'

export function App() {
  return <div>Hello React!</div>
}

// build.config.ts
export default defineConfig({
  entries: [
    {
      type: 'bundle',
      input: './src/App.tsx',
      // JSX 自动转换,无需配置
    }
  ]
})

3. CommonJS 支持

rolldown 原生支持 CommonJS 模块:

typescript
// 可以直接导入 CommonJS 模块
import lodash from 'lodash'
import express from 'express'

// 无需 @rollup/plugin-commonjs

创建自定义插件

1. 基本插件结构

typescript
// my-plugin.ts
interface MyPluginOptions {
  prefix?: string
  suffix?: string
}

export function myPlugin(options: MyPluginOptions = {}) {
  const { prefix = '', suffix = '' } = options

  return {
    name: 'my-plugin',
    setup(build) {
      // 插件初始化逻辑
      console.log('My plugin initialized')
    },
    transform(code, id) {
      // 代码转换逻辑
      if (id.endsWith('.ts') || id.endsWith('.js')) {
        return `${prefix}${code}${suffix}`
      }
      return code
    }
  }
}

2. 使用自定义插件

typescript
import { defineConfig } from 'robuild'
import { myPlugin } from './my-plugin'

export default defineConfig({
  entries: [
    {
      type: 'bundle',
      input: './src/index.ts',
      rolldown: {
        plugins: [
          myPlugin({
            prefix: '// Generated by my-plugin\n',
            suffix: '\n// End of generated code'
          })
        ]
      }
    }
  ]
})

插件生命周期

1. 插件初始化

typescript
{
  name: 'my-plugin',
  setup(build) {
    // 在构建开始时调用一次
    console.log('插件初始化')

    // 可以访问构建上下文
    console.log('构建配置:', build.config)
  }
}

2. 代码转换

typescript
{
  name: 'my-plugin',
  transform(code, id) {
    // 对每个文件调用
    if (id.endsWith('.ts')) {
      // 转换 TypeScript 文件
      return code.replace(/console\.log/g, '// console.log')
    }
    return code
  }
}

3. 构建钩子

typescript
{
  name: 'my-plugin',
  setup(build) {
    build.onStart(() => {
      console.log('构建开始')
    })

    build.onEnd((result) => {
      console.log('构建结束', result)
    })
  }
}

实际插件示例

1. 版本注入插件

typescript
// version-inject-plugin.ts
import { readFileSync } from 'fs'

interface VersionInjectOptions {
  packagePath?: string
  placeholder?: string
}

export function versionInjectPlugin(options: VersionInjectOptions = {}) {
  const { packagePath = './package.json', placeholder = '__VERSION__' } = options

  return {
    name: 'version-inject',
    transform(code: string, id: string) {
      if (code.includes(placeholder)) {
        try {
          const pkg = JSON.parse(readFileSync(packagePath, 'utf-8'))
          return code.replace(
            new RegExp(placeholder, 'g'),
            JSON.stringify(pkg.version)
          )
        } catch (error) {
          console.warn('Failed to read package.json:', error)
        }
      }
      return code
    }
  }
}

2. 文件头注释插件

typescript
// banner-plugin.ts
interface BannerOptions {
  banner?: string
  include?: RegExp
  exclude?: RegExp
}

export function bannerPlugin(options: BannerOptions = {}) {
  const {
    banner = '/* Generated by robuild */',
    include = /\.(js|mjs|ts)$/,
    exclude
  } = options

  return {
    name: 'banner',
    generateBundle(options: any, bundle: any) {
      Object.keys(bundle).forEach(fileName => {
        const chunk = bundle[fileName]

        if (chunk.type === 'chunk') {
          const shouldInclude = include.test(fileName)
          const shouldExclude = exclude && exclude.test(fileName)

          if (shouldInclude && !shouldExclude) {
            chunk.code = `${banner}\n${chunk.code}`
          }
        }
      })
    }
  }
}

3. 条件编译插件

typescript
// conditional-compile-plugin.ts
interface ConditionalCompileOptions {
  conditions: Record<string, boolean>
}

export function conditionalCompilePlugin(options: ConditionalCompileOptions) {
  const { conditions } = options

  return {
    name: 'conditional-compile',
    transform(code: string, id: string) {
      let result = code

      // 处理 #ifdef 条件编译
      Object.entries(conditions).forEach(([condition, enabled]) => {
        const ifdefRegex = new RegExp(
          `\\/\\*\\s*#ifdef\\s+${condition}\\s*\\*\\/([\\s\\S]*?)\\/\\*\\s*#endif\\s*\\*\\/`,
          'g'
        )

        result = result.replace(ifdefRegex, (match, content) => {
          return enabled ? content.trim() : ''
        })
      })

      return result
    }
  }
}

插件配置最佳实践

1. 类型安全

typescript
// 定义插件选项类型
interface MyPluginOptions {
  enabled?: boolean
  config?: Record<string, any>
}

// 使用类型安全的插件
export function myPlugin(options: MyPluginOptions = {}) {
  const { enabled = true, config = {} } = options

  if (!enabled) {
    return { name: 'my-plugin' } // 空插件
  }

  return {
    name: 'my-plugin',
    setup(build) {
      // 使用配置
      console.log('配置:', config)
    }
  }
}

2. 错误处理

typescript
export function safePlugin() {
  return {
    name: 'safe-plugin',
    transform(code, id) {
      try {
        // 插件逻辑
        return modifiedCode
      } catch (error) {
        console.error(`插件错误 (${id}):`, error)
        return code // 返回原始代码
      }
    }
  }
}

3. 性能优化

typescript
export function optimizedPlugin() {
  const cache = new Map()

  return {
    name: 'optimized-plugin',
    transform(code, id) {
      // 使用缓存避免重复处理
      if (cache.has(id)) {
        return cache.get(id)
      }

      const result = processCode(code)
      cache.set(id, result)
      return result
    }
  }
}

插件生态系统

1. 官方插件

robuild 提供了一些官方插件:

typescript
import {
  shebangPlugin,
  jsonPlugin,
  // 更多官方插件...
} from 'robuild/plugins'

2. 社区插件

推荐的社区插件:

typescript
import { defineConfig } from 'robuild'
import { visualizer } from 'rollup-plugin-visualizer'
import { analyzer } from 'rollup-plugin-analyzer'
import { copy } from 'rollup-plugin-copy'

export default defineConfig({
  entries: [
    {
      type: 'bundle',
      input: './src/index.ts',
      plugins: [
        // 打包分析
        visualizer({ filename: 'dist/stats.html' }),
        analyzer({ summaryOnly: true }),
        // 文件复制
        copy({
          targets: [
            { src: 'assets/*', dest: 'dist/assets' }
          ]
        })
      ]
    }
  ]
})

3. 插件开发

开发自己的插件:

bash
# 创建插件项目
mkdir robuild-plugin-example
cd robuild-plugin-example
npm init -y

# 安装开发依赖
npm install --save-dev typescript @types/node

插件调试

1. 调试模式

typescript
export default defineConfig({
  entries: ['./src/index.ts'],
  rolldown: {
    plugins: [
      {
        name: 'debug-plugin',
        setup(build) {
          // 启用调试日志
          if (process.env.DEBUG) {
            console.log('插件调试信息:', build.config)
          }
        }
      }
    ]
  }
})

2. 插件测试

typescript
// 测试插件
import { myPlugin } from './my-plugin'

const plugin = myPlugin({ prefix: '// TEST' })
const result = plugin.transform('console.log("hello")', 'test.js')
console.log(result) // // TEST\nconsole.log("hello")

下一步

Released under the MIT License.