Skip to content

Rollup 架构分析

整体架构

Rollup 采用基于 AST 和插件系统的架构,通过解析、转换和生成三个阶段打包代码。

┌─────────────────────────────────────────────┐
│              Rollup Bundler                │
├─────────────────────────────────────────────┤
│  1. Parser (解析器)                          │
│     - Acorn Parser (Acorn 解析器)           │
│     - AST Generation (AST 生成)             │
│     - Module Resolution (模块解析)          │
├─────────────────────────────────────────────┤
│  2. Transform (转换器)                       │
│     - Plugin System (插件系统)               │
│     - Tree-shaking (树摇)                   │
│     - Code Splitting (代码分割)             │
├─────────────────────────────────────────────┤
│  3. Generator (生成器)                       │
│     - Code Generation (代码生成)            │
│     - Source Map (源映射)                    │
│     - Output Formats (输出格式)             │
└─────────────────────────────────────────────┘

核心组件

1. Parser

解析器负责解析模块和生成 AST。

职责:

  • 解析模块
  • 生成 AST
  • 解析依赖

关键方法:

javascript
class Parser {
  constructor(options) {
    this.options = options
    this.acornOptions = {
      sourceType: 'module',
      ecmaVersion: 'latest',
      ...options.acorn
    }
  }
  
  // 解析模块
  parse(source, id) {
    const ast = acorn.parse(source, this.acornOptions)
    const dependencies = this.extractDependencies(ast)
    
    return {
      id,
      ast,
      dependencies,
      source
    }
  }
  
  // 提取依赖
  extractDependencies(ast) {
    const dependencies = []
    
    traverse(ast, {
      ImportDeclaration(path) {
        dependencies.push(path.node.source.value)
      },
      ExportNamedDeclaration(path) {
        if (path.node.source) {
          dependencies.push(path.node.source.value)
        }
      },
      ExportAllDeclaration(path) {
        dependencies.push(path.node.source.value)
      }
    })
    
    return dependencies
  }
}

优势:

  • 准确:准确的 AST 生成
  • 快速:快速的解析速度
  • 完整:支持完整的 ES6 语法

2. Transform

转换器负责应用插件和转换代码。

职责:

  • 应用插件
  • Tree-shaking
  • 代码分割

关键方法:

javascript
class Transform {
  constructor(options) {
    this.options = options
    this.plugins = options.plugins || []
  }
  
  // 转换模块
  async transform(module) {
    const context = {
      id: module.id,
      code: module.source,
      ast: module.ast
    }
    
    // 应用插件
    for (const plugin of this.plugins) {
      if (plugin.transform) {
        const result = await plugin.transform.call(context, context.code, context.id)
        if (result) {
          context.code = result.code || context.code
          context.ast = result.ast || context.ast
        }
      }
    }
    
    // Tree-shaking
    const usedExports = this.analyzeUsedExports(context.ast)
    context.ast = this.pruneUnusedExports(context.ast, usedExports)
    
    return context
  }
  
  // 分析使用的导出
  analyzeUsedExports(ast) {
    const usedExports = new Set()
    
    traverse(ast, {
      Identifier(path) {
        if (path.isReferencedIdentifier()) {
          usedExports.add(path.node.name)
        }
      }
    })
    
    return usedExports
  }
  
  // 移除未使用的导出
  pruneUnusedExports(ast, usedExports) {
    // 移除未使用的导出
    return ast
  }
}

优势:

  • 插件化:支持插件扩展
  • Tree-shaking:移除未使用的代码
  • 代码分割:支持代码分割

3. Generator

生成器负责生成最终代码。

职责:

  • 生成代码
  • 生成 Source Map
  • 支持多种输出格式

关键方法:

javascript
class Generator {
  constructor(options) {
    this.options = options
    this.format = options.format || 'es'
  }
  
  // 生成代码
  generate(modules) {
    const chunks = this.createChunks(modules)
    const output = []
    
    for (const chunk of chunks) {
      const code = this.generateChunk(chunk)
      output.push({
        code,
        fileName: chunk.fileName
      })
    }
    
    return output
  }
  
  // 创建代码块
  createChunks(modules) {
    const entryPoints = modules.filter(m => m.isEntry)
    const chunks = []
    
    for (const entry of entryPoints) {
      const chunk = {
        id: entry.id,
        modules: [entry],
        fileName: this.getFileName(entry.id)
      }
      
      // 添加依赖模块
      this.addDependencies(chunk, entry, modules)
      
      chunks.push(chunk)
    }
    
    return chunks
  }
  
  // 添加依赖
  addDependencies(chunk, module, modules) {
    for (const depId of module.dependencies) {
      const dep = modules.find(m => m.id === depId)
      if (dep && !chunk.modules.includes(dep)) {
        chunk.modules.push(dep)
        this.addDependencies(chunk, dep, modules)
      }
    }
  }
  
  // 生成代码块
  generateChunk(chunk) {
    let code = ''
    
    for (const module of chunk.modules) {
      code += this.generateModule(module)
    }
    
    return code
  }
  
  // 生成模块
  generateModule(module) {
    const magicString = new MagicString(module.source)
    
    // 移除导入导出语句
    this.removeImports(module.ast, magicString)
    this.removeExports(module.ast, magicString)
    
    return magicString.toString()
  }
  
  // 移除导入
  removeImports(ast, magicString) {
    traverse(ast, {
      ImportDeclaration(path) {
        magicString.remove(path.node.start, path.node.end)
      }
    })
  }
  
  // 移除导出
  removeExports(ast, magicString) {
    traverse(ast, {
      ExportNamedDeclaration(path) {
        magicString.remove(path.node.start, path.node.end)
      },
      ExportDefaultDeclaration(path) {
        magicString.remove(path.node.start, path.node.end)
      },
      ExportAllDeclaration(path) {
        magicString.remove(path.node.start, path.node.end)
      }
    })
  }
  
  // 获取文件名
  getFileName(id) {
    const base = path.basename(id, path.extname(id))
    return `${base}.js`
  }
}

优势:

  • 灵活:支持多种输出格式
  • 高效:高效的代码生成
  • 准确:准确的代码生成

插件系统

插件结构

javascript
export default function myPlugin(options = {}) {
  return {
    name: 'my-plugin',
    
    // 解析钩子
    resolveId(source, importer) {
      // 解析模块 ID
    },
    
    load(id) {
      // 加载模块
    },
    
    transform(code, id) {
      // 转换代码
    },
    
    // 生成钩子
    renderChunk(code, chunk) {
      // 转换代码块
    },
    
    generateBundle(options, bundle) {
      // 生成包
    }
  }
}

插件执行

Entry File

resolveId

load

transform

generate

Output Files

Tree-shaking

原理

Tree-shaking 通过分析代码的使用情况,移除未使用的代码。

实现

javascript
function treeShake(ast) {
  // 分析使用的导出
  const usedExports = analyzeUsedExports(ast)
  
  // 移除未使用的导出
  return pruneUnusedExports(ast, usedExports)
}

function analyzeUsedExports(ast) {
  const usedExports = new Set()
  
  traverse(ast, {
    Identifier(path) {
      if (path.isReferencedIdentifier()) {
        usedExports.add(path.node.name)
      }
    }
  })
  
  return usedExports
}

代码分割

原理

代码分割将代码分割成多个块,按需加载。

实现

javascript
function codeSplit(modules) {
  const entryPoints = modules.filter(m => m.isEntry)
  const chunks = []
  
  for (const entry of entryPoints) {
    const chunk = createChunk(entry)
    chunks.push(chunk)
  }
  
  return chunks
}

性能优化

1. 增量构建

javascript
function buildIncremental(changedFiles) {
  const affected = getAffectedModules(changedFiles)
  
  for (const module of affected) {
    rebuildModule(module)
  }
}

2. 并行处理

javascript
async function buildParallel(modules) {
  const chunks = chunkArray(modules, os.cpus().length)
  
  const promises = chunks.map(chunk => 
    Promise.all(chunk.map(m => buildModule(m)))
  )
  
  return Promise.all(promises)
}

3. 缓存

javascript
const cache = new Map()

function build(module) {
  if (cache.has(module.id)) {
    return cache.get(module.id)
  }
  
  const result = buildInternal(module)
  cache.set(module.id, result)
  return result
}

总结

Rollup 的架构体现了以下特点:

  1. ES6 优先:专注于 ES6 模块
  2. Tree-shaking:移除未使用的代码
  3. 插件化:支持插件扩展
  4. 高效:高效的打包性能

理解 Rollup 的架构有助于更好地使用和优化 Rollup。

参考资源

架构师AI杜公众号二维码

扫描二维码关注"架构师AI杜"公众号,获取更多技术内容和最新动态