构建器
robuild 的构建器是核心组件,负责实际的构建工作。本文档详细介绍 Bundle 和 Transform 两种构建器的实现。
构建器架构
整体设计
mermaid
graph TB
A[构建引擎] --> B[Bundle 构建器]
A --> C[Transform 构建器]
B --> D[rolldown]
B --> E[插件系统]
B --> F[输出处理]
C --> G[oxc-transform]
C --> H[文件系统]
C --> I[目录处理]
subgraph "核心组件"
J[配置解析]
K[类型检查]
L[错误处理]
end
B --> J
C --> J
B --> K
C --> K
B --> L
C --> L
Bundle 构建器
核心功能
Bundle 构建器负责将多个文件打包成单个文件,主要用于库发布和应用程序构建。
实现原理
typescript
// src/builders/bundle.ts
export class BundleBuilder {
private rolldown: Rolldown
private plugins: Plugin[]
constructor(options: BundleOptions) {
this.rolldown = new Rolldown(options)
this.plugins = options.plugins || []
}
async build(entry: BundleEntry): Promise<BuildResult> {
// 1. 解析入口文件
const inputs = this.resolveInputs(entry.input)
// 2. 配置 rolldown
const config = this.createRolldownConfig(entry)
// 3. 执行构建
const result = await this.rolldown.build(config)
// 4. 后处理
return this.postProcess(result, entry)
}
}
构建流程
1. 入口解析
typescript
private resolveInputs(input: string | string[]): string[] {
if (Array.isArray(input)) {
return input.map(this.resolvePath)
}
return [this.resolvePath(input)]
}
private resolvePath(path: string): string {
return resolve(this.cwd, path)
}
2. 配置生成
typescript
private createRolldownConfig(entry: BundleEntry): RolldownConfig {
return {
input: this.resolveInputs(entry.input),
output: {
dir: entry.outDir || 'dist',
format: entry.rolldown?.output?.format || 'esm',
name: entry.rolldown?.output?.name,
manualChunks: entry.rolldown?.output?.manualChunks
},
external: entry.rolldown?.external || [],
plugins: this.plugins,
platform: entry.rolldown?.platform || 'neutral',
target: entry.rolldown?.target || 'ES2020'
}
}
3. 构建执行
typescript
private async executeBuild(config: RolldownConfig): Promise<RolldownResult> {
try {
// 设置插件
for (const plugin of this.plugins) {
await plugin.setup?.(this.context)
}
// 执行构建
const result = await this.rolldown.build(config)
return result
} catch (error) {
throw new BuildError(`Bundle 构建失败: ${error.message}`, error)
}
}
4. 后处理
typescript
private async postProcess(result: RolldownResult, entry: BundleEntry): Promise<BuildResult> {
const outputFiles: string[] = []
// 处理输出文件
for (const chunk of result.output) {
const outputPath = join(entry.outDir || 'dist', chunk.fileName)
// 写入文件
await writeFile(outputPath, chunk.code, 'utf-8')
outputFiles.push(outputPath)
// 生成源码映射
if (entry.sourcemap && chunk.map) {
const mapPath = outputPath + '.map'
await writeFile(mapPath, JSON.stringify(chunk.map), 'utf-8')
outputFiles.push(mapPath)
}
}
// 生成 TypeScript 声明文件
if (entry.dts) {
const dtsFiles = await this.generateDts(entry)
outputFiles.push(...dtsFiles)
}
return {
outputFiles,
duration: Date.now() - this.startTime,
errors: []
}
}
插件系统
Bundle 构建器支持 rolldown 插件:
typescript
interface BundlePlugin {
name: string
setup?: (build: BuildContext) => void | Promise<void>
transform?: (code: string, id: string) => string | Promise<string>
load?: (id: string) => string | Promise<string>
generateBundle?: (options: GenerateBundleOptions) => void | Promise<void>
}
内置插件
1. Shebang 插件
typescript
export function shebangPlugin(options: ShebangOptions = {}): BundlePlugin {
return {
name: 'shebang',
setup(build) {
build.onLoad({ filter: /\.(js|mjs)$/ }, async (args) => {
const code = await readFile(args.path, 'utf-8')
if (code.startsWith('#!')) {
return {
contents: code,
loader: 'js'
}
}
return null
})
}
}
}
2. JSON 插件
typescript
export function jsonPlugin(): BundlePlugin {
return {
name: 'json',
setup(build) {
build.onLoad({ filter: /\.json$/ }, async (args) => {
const contents = await readFile(args.path, 'utf-8')
const data = JSON.parse(contents)
return {
contents: `export default ${JSON.stringify(data)}`,
loader: 'js'
}
})
}
}
}
Transform 构建器
核心功能
Transform 构建器负责转换目录中的所有文件,保持文件结构,主要用于运行时文件处理。
实现原理
typescript
// src/builders/transform.ts
export class TransformBuilder {
private oxc: OxcTransform
private fileSystem: FileSystem
constructor(options: TransformOptions) {
this.oxc = new OxcTransform(options)
this.fileSystem = new FileSystem(options.cwd)
}
async build(entry: TransformEntry): Promise<BuildResult> {
// 1. 扫描文件
const files = await this.scanFiles(entry.input)
// 2. 并行转换
const results = await Promise.all(
files.map(file => this.transformFile(file, entry))
)
// 3. 写入输出
await this.writeOutput(results, entry.outDir)
return {
outputFiles: results.map(r => r.outputPath),
duration: Date.now() - this.startTime,
errors: results.flatMap(r => r.errors)
}
}
}
构建流程
1. 文件扫描
typescript
private async scanFiles(inputDir: string): Promise<string[]> {
const files: string[] = []
async function scan(dir: string) {
const entries = await readdir(dir, { withFileTypes: true })
for (const entry of entries) {
const fullPath = join(dir, entry.name)
if (entry.isDirectory()) {
await scan(fullPath)
} else if (this.isTargetFile(entry.name)) {
files.push(fullPath)
}
}
}
await scan(inputDir)
return files
}
private isTargetFile(filename: string): boolean {
const extensions = ['.ts', '.js', '.mjs', '.json']
return extensions.some(ext => filename.endsWith(ext))
}
2. 文件转换
typescript
private async transformFile(filePath: string, entry: TransformEntry): Promise<TransformResult> {
try {
// 读取源文件
const sourceCode = await readFile(filePath, 'utf-8')
// 确定输出路径
const relativePath = relative(entry.input, filePath)
const outputPath = join(entry.outDir, relativePath)
// 转换代码
const transformedCode = await this.oxc.transform(sourceCode, {
filename: filePath,
target: entry.oxc?.typescript?.target || 'ES2020',
module: entry.oxc?.typescript?.module || 'ESNext'
})
// 压缩(如果启用)
let finalCode = transformedCode
if (entry.minify) {
finalCode = await this.minify(finalCode, entry.minify)
}
return {
outputPath,
code: finalCode,
errors: []
}
} catch (error) {
return {
outputPath: '',
code: '',
errors: [error]
}
}
}
3. 输出写入
typescript
private async writeOutput(results: TransformResult[], outDir: string): Promise<void> {
for (const result of results) {
if (result.errors.length > 0) continue
// 确保输出目录存在
const outputDir = dirname(result.outputPath)
await mkdir(outputDir, { recursive: true })
// 写入文件
await writeFile(result.outputPath, result.code, 'utf-8')
// 生成源码映射
if (this.options.sourcemap) {
const mapPath = result.outputPath + '.map'
const sourcemap = await this.generateSourcemap(result)
await writeFile(mapPath, JSON.stringify(sourcemap), 'utf-8')
}
}
}
文件系统处理
1. 目录结构保持
typescript
private maintainDirectoryStructure(inputDir: string, outDir: string): void {
// 保持输入目录的结构
const relativePath = relative(this.cwd, inputDir)
const outputPath = join(outDir, relativePath)
// 创建输出目录
mkdirSync(outputPath, { recursive: true })
}
2. 文件过滤
typescript
private shouldProcessFile(filePath: string): boolean {
// 排除测试文件
if (filePath.includes('.test.') || filePath.includes('.spec.')) {
return false
}
// 排除类型文件
if (filePath.endsWith('.d.ts')) {
return false
}
// 检查文件扩展名
const extensions = ['.ts', '.js', '.mjs', '.json']
return extensions.some(ext => filePath.endsWith(ext))
}
构建器配置
Bundle 配置
typescript
interface BundleBuilderConfig {
cwd: string
plugins?: BundlePlugin[]
rolldown?: RolldownOptions
minify?: MinifyOptions
sourcemap?: SourcemapOptions
dts?: DtsOptions
}
Transform 配置
typescript
interface TransformBuilderConfig {
cwd: string
oxc?: OxcOptions
resolve?: ResolveOptions
minify?: MinifyOptions
sourcemap?: SourcemapOptions
}
性能优化
1. 并行处理
typescript
// Transform 构建器中的并行文件处理
const results = await Promise.all(
files.map(file => this.transformFile(file, entry))
)
2. 缓存机制
typescript
private cache = new Map<string, TransformResult>()
private async getCachedResult(filePath: string): Promise<TransformResult | null> {
const stat = await stat(filePath)
const cacheKey = `${filePath}:${stat.mtime.getTime()}`
return this.cache.get(cacheKey) || null
}
3. 增量构建
typescript
private async shouldRebuild(filePath: string): Promise<boolean> {
const outputPath = this.getOutputPath(filePath)
try {
const inputStat = await stat(filePath)
const outputStat = await stat(outputPath)
return inputStat.mtime > outputStat.mtime
} catch {
return true // 输出文件不存在,需要构建
}
}
错误处理
1. 构建错误
typescript
export class BuildError extends Error {
constructor(
message: string,
public originalError?: Error,
public filePath?: string
) {
super(message)
this.name = 'BuildError'
}
}
2. 文件错误
typescript
export class FileError extends Error {
constructor(
message: string,
public filePath: string,
public operation: string
) {
super(`${operation} 失败 (${filePath}): ${message}`)
this.name = 'FileError'
}
}
3. 配置错误
typescript
export class ConfigError extends Error {
constructor(
message: string,
public configPath?: string
) {
super(`配置错误${configPath ? ` (${configPath})` : ''}: ${message}`)
this.name = 'ConfigError'
}
}
扩展性
1. 自定义构建器
typescript
interface CustomBuilder {
name: string
build: (entry: BuildEntry, context: BuildContext) => Promise<BuildResult>
}
export class CustomBuilderPlugin {
constructor(private builder: CustomBuilder) {}
async build(entry: BuildEntry, context: BuildContext): Promise<BuildResult> {
return this.builder.build(entry, context)
}
}
2. 构建器工厂
typescript
export class BuilderFactory {
static create(type: 'bundle' | 'transform', options: any): Builder {
switch (type) {
case 'bundle':
return new BundleBuilder(options)
case 'transform':
return new TransformBuilder(options)
default:
throw new Error(`未知的构建器类型: ${type}`)
}
}
}