{T}

Rollup 核心插件系统

Rollup 本身只负责核心的打包逻辑,诸如模块解析、语法转换、代码压缩、开发服务器等高级能力,几乎全部通过插件完成

  • 插件是一个符合特定接口规范的普通 JavaScript 对象
  • 插件通过实现不同的 钩子(Hook)函数 介入 Rollup 的完整生命周期,包括:
    • 读取配置与初始化
    • 解析模块依赖
    • 加载与转换源码
    • 生成产物与写入磁盘
  • 大部分“工程化能力”都是通过组合多个插件完成的,例如:
    • 支持 TypeScript、JSX、Vue 单文件组件
    • Tree-shaking 友好的 Babel 转译
    • 处理 JSON、CSS、图片等非 JS 资源
    • 生产环境压缩、混淆与分析

插件基础 API

插件对象结构

一个最小可用的插件通常是一个“工厂函数”,返回包含若干钩子的对象:

javascript
export default function myPlugin(options = {}) {
  return {
    name: "my-plugin",

    buildStart(inputOptions) {
      console.log("build start", inputOptions.input)
    },

    generateBundle(outputOptions, bundle, isWrite) {
      console.log("generate bundle", Object.keys(bundle), isWrite)
    }
  }
}

常见字段:

  • name: 插件名称,必填,用于错误栈和调试输出
  • 各种钩子函数:如 optionsbuildStartresolveIdloadtransformgenerateBundle

使用时,只需要在 rollup.config 中把插件加入 plugins 数组:

javascript
import myPlugin from "./my-rollup-plugin.js"

export default {
  input: "src/main.js",
  output: {
    file: "dist/bundle.js",
    format: "es",
    sourcemap: true
  },
  plugins: [myPlugin()]
}

常用构建阶段钩子

下表列出构建阶段最常用的钩子及其用途(按典型调用顺序排列):

钩子名调用时机函数签名(简化)返回值常见用途
options处理用户输入配置之前(inputOptions)新的或修改后的 inputOptions动态修改或补全配置
buildStart构建开始(inputOptions)void初始化插件状态、打印信息
resolveId解析模块 ID 时(source, importer, options)字符串或对象,或 null自定义模块解析、别名、虚拟模块
load加载模块内容时(id)源码字符串或对象,或 null读取虚拟模块内容或特殊资源
transform每个模块加载完成后(code, id)新代码字符串或 { code, map }代码转换、宏展开、注入辅助函数
moduleParsedAST 解析完成后(moduleInfo)void基于 AST 的分析或收集信息
buildEnd构建完成(无论成功与否)(error)void释放资源、输出日志
watchChangewatch 模式下文件变更时(id, change)void响应文件变更,更新内部缓存

常用输出阶段钩子

输出阶段主要关注生成 bundle、重写代码片段和落盘逻辑:

钩子名调用时机函数签名(简化)返回值常见用途
outputOptions处理每个输出配置之前(outputOptions)新的或修改后的 outputOptions动态修改输出目录、文件名、格式
renderStart开始渲染某次输出时(outputOptions, inputOptions)void输出信息、准备辅助数据
renderChunk生成单个 chunk 代码时(code, chunk, options)新代码字符串或 { code, map }注入运行时代码、按 chunk 做优化
augmentChunkHash计算 chunk hash 时(chunkInfo)字符串控制缓存策略、强制 hash 变更
generateBundle输出 bundle 元信息阶段(outputOptions, bundle, isWrite)void 或修改 bundle生成额外文件、写出清单或分析报告
writeBundle所有文件写入磁盘后(outputOptions, bundle)void输出日志、上传产物、清理临时文件
closeBundle整个构建流程完全结束后()void关闭连接、释放全局资源

插件上下文(this)常用方法

在钩子内部,this 指向插件上下文,可以调用一些工具方法:

  • this.warn(message):输出非致命警告
  • this.error(message):抛出构建错误并终止流程
  • this.emitFile(descriptor):声明资源文件或 chunk,例如 { type: "asset", name, source }
  • this.getFileName(fileId):通过 emitFile 得到的 fileId 获取最终产物文件名
  • this.addWatchFile(id):在 watch 模式下额外监听某个文件
  • this.getModuleInfo(id):获取模块依赖信息、导入导出列表等

这些 API 是实现复杂自定义插件(例如多入口控制、分析工具、资源管理)的基础

示例:在插件中使用 this.error / this.warn

在自定义插件里,推荐使用上下文方法进行错误与警告输出,例如在构建开始阶段检查必要的环境变量:

javascript
export default function checkEnvPlugin() {
  return {
    name: "check-env",
    buildStart() {
      if (!process.env.NODE_ENV) {
        this.warn("NODE_ENV is not set, defaulting to development")
      }
    }
  }
}

插件配置方式与执行顺序

基本配置方式

rollup.config.js 中,通过 plugins 数组配置插件:

javascript
import { nodeResolve } from "@rollup/plugin-node-resolve"
import commonjs from "@rollup/plugin-commonjs"
import json from "@rollup/plugin-json"
import { babel } from "@rollup/plugin-babel"
import typescript from "@rollup/plugin-typescript"
import { terser } from "@rollup/plugin-terser"

export default {
  input: "src/index.ts",
  output: [
    {
      file: "dist/index.cjs",
      format: "cjs",
      sourcemap: true
    },
    {
      file: "dist/index.esm.js",
      format: "es",
      sourcemap: true
    }
  ],
  plugins: [
    nodeResolve({
      extensions: [".mjs", ".js", ".json", ".ts"]
    }),
    commonjs(),
    json(),
    typescript({
      tsconfig: "./tsconfig.json"
    }),
    babel({
      babelHelpers: "bundled",
      extensions: [".js", ".ts"],
      exclude: "node_modules/**"
    }),
    terser()
  ]
}

此配置是一个完整可运行的示例,适合构建一个 TypeScript 编写的通用 JS 库

插件执行顺序规则

  • 构建阶段钩子(如 resolveIdloadtransform)按 plugins 数组顺序依次执行
  • 输出阶段钩子(如 renderChunkgenerateBundlewriteBundle)则按相反顺序执行
  • 某些钩子只在特定阶段被调用,例如:
    • watchChange 只在 --watch 模式下触发
    • closeBundle 在所有输出结束后触发

因此通常的插件排列顺序建议是:

  1. 模块解析类插件:@rollup/plugin-node-resolve、别名插件等
  2. 兼容性处理插件:@rollup/plugin-commonjs
  3. 语法转换插件:@rollup/plugin-babel@rollup/plugin-typescript
  4. 资源处理插件:CSS、图片、JSON、Vue 等
  5. 产物优化插件:@rollup/plugin-terser、分析可视化插件等

按环境启用不同插件

实际项目中通常会根据环境选择不同插件,例如开发环境需要 HMR,生产环境需要压缩:

javascript
import { nodeResolve } from "@rollup/plugin-node-resolve"
import commonjs from "@rollup/plugin-commonjs"
import { terser } from "@rollup/plugin-terser"

const isProd = process.env.NODE_ENV === "production"

export default {
  input: "src/main.js",
  output: {
    file: isProd ? "dist/bundle.min.js" : "dist/bundle.js",
    format: "iife",
    sourcemap: !isProd
  },
  plugins: [nodeResolve(), commonjs(), isProd && terser()].filter(Boolean)
}

常用官方插件详解

本节对常用官方插件的 功能、常用配置项和使用场景 进行说明

@rollup/plugin-node-resolve

@rollup/plugin-node-resolve 支持从 node_modules 中解析第三方依赖,识别 modulemain 等字段

bash
npm install --save-dev @rollup/plugin-node-resolve

常用配置项

  • extensions: 解析时尝试的文件后缀数组,默认 [".mjs", ".js", ".json", ".node"]
  • browser: 为浏览器环境选择 package.json 中的 browser 字段
  • moduleDirectories: 额外的模块查找目录。

示例:

javascript
import { nodeResolve } from "@rollup/plugin-node-resolve"

export default {
  input: "src/index.js",
  output: {
    file: "dist/bundle.js",
    format: "es"
  },
  plugins: [
    nodeResolve({
      browser: true,
      extensions: [".js", ".jsx", ".mjs"]
    })
  ]
}

典型场景:打包浏览器端项目或希望消费第三方库的 ESM 构建产物

@rollup/plugin-alias

@rollup/plugin-alias 用于为模块导入配置简洁的路径别名,避免层层 ../../../

bash
npm install --save-dev @rollup/plugin-alias

常用配置项

  • entries: 别名数组,形如 { find: '@', replacement: 'src' }

示例:

javascript
// rollup.config.js
import alias from "@rollup/plugin-alias"
import { nodeResolve } from "@rollup/plugin-node-resolve"

export default {
  input: "src/main.js",
  output: {
    file: "dist/bundle.js",
    format: "es",
    sourcemap: true
  },
  plugins: [
    alias({
      entries: [
        { find: "@", replacement: "src" } // 支持 import('@/utils/xxx')
      ]
    }),
    nodeResolve()
  ]
}

典型场景:统一管理项目内长路径导入,配合 TypeScript paths 或 Webpack alias 等保持一致

@rollup/plugin-commonjs

@rollup/plugin-commonjs 将 CommonJS 模块转换为 ES 模块,便于 Rollup 进行 Tree-shaking。

bash
npm install --save-dev @rollup/plugin-commonjs

常用配置项

  • include: 指定需要转换的文件范围,支持通配符
  • exclude: 指定不转换的文件
  • ignoreTryCatch: 忽略某些包中的 require 调用

示例:

javascript
import { nodeResolve } from "@rollup/plugin-node-resolve"
import commonjs from "@rollup/plugin-commonjs"

export default {
  input: "src/main.js",
  output: {
    file: "dist/bundle.js",
    format: "iife",
    name: "MyApp"
  },
  plugins: [
    nodeResolve(),
    commonjs({
      include: "node_modules/**"
    })
  ]
}

典型场景:依赖大量 CommonJS 包(例如老版本的工具库、Node 生态库)时

@rollup/plugin-replace

@rollup/plugin-replace 在构建时用指定字面量替换源码中的变量,常用于注入 process.env.NODE_ENV 或开关调试代码。

bash
npm install --save-dev @rollup/plugin-replace

使用时需要注意:替换值应为字符串字面量(通常通过 JSON.stringify 包装)。

示例:

javascript
// rollup.config.js
import replace from "@rollup/plugin-replace"

const isProd = process.env.NODE_ENV === "production"

export default {
  input: "src/main.js",
  output: {
    file: isProd ? "dist/bundle.min.js" : "dist/bundle.js",
    format: "iife",
    name: "MyApp"
  },
  plugins: [
    replace({
      preventAssignment: true,
      "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV || "development")
    })
  ]
}

典型场景:按环境注入开关、删除开发调试代码、控制日志输出等

@rollup/plugin-babel

@rollup/plugin-babel 集成 Babel,将现代 JavaScript 转换为兼容旧环境的代码

bash
npm install --save-dev @rollup/plugin-babel @babel/core @babel/preset-env

关键配置

  • babelHelpers: 指定辅助函数的注入策略,常用值为 "bundled""runtime"
  • extensions: 需要通过 Babel 处理的文件扩展名。
  • exclude: 排除 node_modules 以提升性能。

示例:

javascript
import { babel } from "@rollup/plugin-babel"

export default {
  input: "src/main.js",
  output: {
    file: "dist/bundle.js",
    format: "iife",
    name: "MyApp"
  },
  plugins: [
    babel({
      babelHelpers: "bundled",
      extensions: [".js", ".jsx"],
      exclude: "node_modules/**"
    })
  ]
}

搭配 .babelrc

json
{
  "presets": [["@babel/preset-env", { "modules": false }]]
}

注意"modules": false 可以避免 Babel 把 ES 模块转换成 CommonJS,从而保持 Rollup 的 Tree-shaking 效果

@rollup/plugin-typescript

@rollup/plugin-typescript 直接编译 TypeScript 源码并交给 Rollup 继续处理

bash
npm install --save-dev @rollup/plugin-typescript typescript

常用配置项

  • tsconfig: 指定 tsconfig.json 路径
  • 其他选项大多由 tsconfig.json 控制,例如 targetmodule

示例:

javascript
import typescript from "@rollup/plugin-typescript"

export default {
  input: "src/index.ts",
  output: {
    dir: "dist",
    format: "es"
  },
  plugins: [
    typescript({
      tsconfig: "./tsconfig.json"
    })
  ]
}

搭配 tsconfig.json

json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "declaration": true,
    "outDir": "dist"
  },
  "include": ["src"]
}

@rollup/plugin-json

@rollup/plugin-json.json 文件视为模块导入,允许 import data from "./data.json"

bash
npm install --save-dev @rollup/plugin-json

常用配置项

  • preferConst: 使用 const 声明导出变量
  • compact: 是否压缩生成代码

示例:

javascript
import json from "@rollup/plugin-json"

export default {
  input: "src/main.js",
  output: {
    file: "dist/bundle.js",
    format: "es"
  },
  plugins: [
    json({
      compact: true,
      preferConst: true
    })
  ]
}

@rollup/plugin-terser

@rollup/plugin-terser 使用 Terser 对生成的代码进行压缩与混淆,多用于生产构建

bash
npm install --save-dev @rollup/plugin-terser

示例:

javascript
import { terser } from "@rollup/plugin-terser"

export default {
  input: "src/main.js",
  output: {
    file: "dist/bundle.min.js",
    format: "iife",
    name: "MyApp"
  },
  plugins: [
    terser({
      compress: {
        drop_console: true
      }
    })
  ]
}

社区插件与典型应用场景

除了官方插件,社区还提供了大量插件以支持 CSS、图片、Vue/React 组件等多种场景。推荐的插件列表可以参考 awesome-rollup

处理 CSS:rollup-plugin-postcss

典型能力:

  • 支持导入 CSS、Less、Sass 等
  • 支持自动添加厂商前缀、CSS Modules
  • 可以将 CSS 抽离为独立文件

示例:

javascript
import postcss from "rollup-plugin-postcss"

export default {
  input: "src/main.js",
  output: {
    file: "dist/bundle.js",
    format: "iife",
    name: "MyApp"
  },
  plugins: [
    postcss({
      modules: true,
      extract: "styles.css"
    })
  ]
}

打包 Vue 组件:rollup-plugin-vue

示例(库型 Vue 组件打包):

javascript
import vue from "rollup-plugin-vue"
import { nodeResolve } from "@rollup/plugin-node-resolve"
import commonjs from "@rollup/plugin-commonjs"

export default {
  input: "src/index.js",
  output: {
    file: "dist/my-vue-lib.esm.js",
    format: "es",
    sourcemap: true
  },
  external: ["vue"],
  plugins: [nodeResolve(), commonjs(), vue()]
}

打包 React 组件库

通常使用官方插件组合即可:

javascript
import { nodeResolve } from "@rollup/plugin-node-resolve"
import commonjs from "@rollup/plugin-commonjs"
import { babel } from "@rollup/plugin-babel"

export default {
  input: "src/index.jsx",
  output: {
    file: "dist/my-react-lib.esm.js",
    format: "es",
    sourcemap: true
  },
  external: ["react", "react-dom"],
  plugins: [
    nodeResolve({
      extensions: [".js", ".jsx"]
    }),
    commonjs(),
    babel({
      babelHelpers: "bundled",
      extensions: [".js", ".jsx"],
      exclude: "node_modules/**"
    })
  ]
}

使用社区插件的最佳实践

  1. 检查维护状态:优先选择活跃维护、下载量高、文档完善的插件
  2. 关注插件顺序:解析类插件在前,转换类插件居中,压缩类插件在最后
  3. 阅读 README:很多插件有非直观的选项,必须查看官方文档
  4. 避免功能重叠:同类插件只选择一个,避免在 transform 阶段做重复工作

自定义插件实践

当现有插件无法满足需求时,可以编写自定义插件。下面通过一个简单示例展示完整开发流程

示例:替换 process.env.* 环境变量

插件实现:

javascript
export default function envReplacePlugin(env = {}) {
  const replacements = Object.entries(env).map(([key, value]) => ({
    key: `process.env.${key}`,
    value: JSON.stringify(value)
  }))

  return {
    name: "env-replace",

    transform(code) {
      let transformed = code
      for (const item of replacements) {
        transformed = transformed.split(item.key).join(item.value)
      }
      return {
        code: transformed,
        map: null
      }
    }
  }
}

在配置中使用:

javascript
import envReplacePlugin from "./my-env-replace-plugin.js"

export default {
  input: "src/main.js",
  output: {
    file: "dist/bundle.js",
    format: "es"
  },
  plugins: [
    envReplacePlugin({
      NODE_ENV: "production",
      API_BASE_URL: "https://api.example.com"
    })
  ]
}

示例:构建日志与产物统计

示例,在构建前后打印信息和统计产物:

javascript
export default function buildReporter() {
  let startTime = 0

  return {
    name: "build-reporter",

    buildStart() {
      startTime = Date.now()
      console.log("Rollup build started")
    },

    generateBundle(outputOptions, bundle) {
      const files = Object.keys(bundle)
      console.log("Generated files:", files.length)
      for (const file of files) {
        const asset = bundle[file]
        const size = Buffer.byteLength(asset.code || asset.source, "utf8")
        console.log(file, size, "bytes")
      }
    },

    closeBundle() {
      const duration = Date.now() - startTime
      console.log("Rollup build finished in", duration, "ms")
    }
  }
}

该插件利用了 buildStartgenerateBundlecloseBundle 三个钩子,覆盖了从开始构建到输出结束的完整生命周期