Yihui’s Blog

解构 Rollup 生态:四大核心插件协同构建现代 JavaScript 库

在现代前端工程化体系中,构建工具(Bundler)扮演着至关重要的角色。而在众多构建工具中,Rollup 因其对 ES Modules (ESM) 的原生支持和高效的 Tree Shaking 能力,在构建 JavaScript 库时备受青睐。然而,Rollup 的核心非常精简,要构建一个能处理 TypeScript、依赖 npm 包、并区分开发/生产环境的现代化项目,我们需要借助其强大的插件生态。

本文将以一名资深工程师的视角,带你解构 Rollup 生态中最核心的四个插件:@rollup/plugin-node-resolve、@rollup/plugin-commonjs、@rollup/plugin-typescript 和 @rollup/plugin-replace。我们将不仅了解它们各自的功能,更将深入探讨它们如何协同工作,构成一条强大而高效的构建流水线。


插件协同工作流概览

在深入每个插件之前,理解它们在构建过程中的协作顺序至关重要。一个典型的处理流程如下:

  1. @rollup/plugin-replace: 首先执行,替换代码中的环境变量,为后续的逻辑判断和代码剔除(Dead Code Elimination)做好准备。

  2. @rollup/plugin-node-resolve: 定位并解析 import 的第三方模块路径(例如,从 node_modules 中找到 lodash)。

  3. @rollup/plugin-commonjs: 将 node-resolve 找到的 CommonJS 格式的模块转换为 Rollup 支持的 ES Module 格式。

  4. @rollup/plugin-typescript: 最后,将所有的 TypeScript 代码(.ts/.tsx)编译成 JavaScript。

理解这个顺序是写出健壮 rollup.config.js 的第一步。

1. @rollup/plugin-node-resolve: 模块解析的导航员

Rollup 默认只知道如何解析相对路径的模块(如 ./utils.js)。当你 import _ from 'lodash' 时,Rollup 会感到困惑,因为它不知道去哪里寻找这个名为 lodash 的模块。@rollup/plugin-node-resolve 的职责就是教会 Rollup 如何像 Node.js 一样去 node_modules 目录中寻找依赖。

核心功能: 模拟 Node.js 的模块解析算法,定位第三方依赖。

使用方法

安装:

Bash

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

配置 (rollup.config.js):

JavaScript

import { nodeResolve } from '@rollup/plugin-node-resolve';

export default {
  // ...
  plugins: [nodeResolve()]
};

关键配置项解析

  • mainFields: [‘browser’, ‘module’, ‘main’]

    一个包的入口文件由其 package.json 中的字段决定。此选项定义了 Rollup 解析入口文件的优先级。‘module’ 指向 ESM 版本,‘main’ 指向 CommonJS 版本。如果你打包的目标是浏览器,将 ‘browser’ 放在最前面通常是最佳实践,因为它会优先使用为浏览器优化的版本。

  • browser: boolean

    当设置为 true 时,此插件会遵循 package.json 中的 “browser” 字段。该字段允许库作者为浏览器环境提供一个不同于 Node.js 环境的实现(例如,使用浏览器的 fetch 替代 Node 的 http 模块)。

  • dedupe: string[]

    在大型项目中,可能会因为依赖关系菱形问题(diamond dependency problem)而引入同一库的多个版本。dedupe 选项可以强制 Rollup 从项目根目录的 node_modules 解析指定的依赖,确保最终打包结果中只有一个版本的库实例,避免了潜在的版本冲突和体积膨胀。


2. @rollup/plugin-commonjs: 连接 ESM 与 CJS 生态的桥梁

历史原因导致 npm 上绝大多数包仍然是 CommonJS (CJS) 格式。Rollup 作为 ESM 的坚定拥护者,其静态分析能力无法理解 CJS 的动态特性(如动态的 require)。@rollup/plugin-commonjs 就是一座至关重要的桥梁,它能将 CJS 模块转换为 Rollup 可以理解的 ESM 格式。

核心功能: 将 CommonJS 模块转换为 ES Modules。

重要提示: 此插件几乎总是与 @rollup/plugin-node-resolve 协同使用。node-resolve 先“找到”模块,然后 commonjs 再“转换”它。

使用方法

安装:

Bash

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

配置 (rollup.config.js):

JavaScript

import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';

export default {
  // ...
  plugins: [
    nodeResolve(),
    commonjs() // 应该在 nodeResolve 之后
  ]
};

关键配置项解析

  • include / exclude: string | string[]

    用于精确控制哪些文件需要被此插件处理。通常,我们会将其范围限定在 node_modules 目录,因为我们自己的源码应该是 ESM 格式。

    JavaScript

    commonjs({ include: 'node_modules/**' })
  • transformMixedEsModules: boolean

    有些模块可能混合了 import 和 require 语法。开启此选项可以让插件尝试转换这类混合模块,提升兼容性。


3. @rollup/plugin-typescript: 类型安全的保障

要在 Rollup 项目中使用 TypeScript,你需要一个插件来调用 TypeScript 编译器 (tsc) 将 .ts 或 .tsx 文件转换为 JavaScript。@rollup/plugin-typescript 正是为此而生。

核心功能: 集成 TypeScript 编译器,无缝处理 TypeScript 文件。

使用方法

安装:

Bash

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

配置 (rollup.config.js):

JavaScript

import typescript from '@rollup/plugin-typescript';

export default {
  input: 'src/index.ts', // 入口文件现在是 .ts
  // ...
  plugins: [
    // ... 其他插件
    typescript()
  ]
};

关键配置项解析

  • tsconfig: string

    默认情况下,插件会自动查找项目根目录下的 tsconfig.json 文件。你可以通过此选项指定一个不同的配置文件路径,例如 typescript({ tsconfig: ’./tsconfig.build.json’ }),这在需要为构建和开发使用不同编译选项时非常有用。

  • sourceMap 和 declaration:

    这些选项通常建议在 tsconfig.json 的 compilerOptions 中进行管理,以保持配置的单一来源。例如,设置 “declaration”: true 和 “declarationDir”: “dist/types” 来生成类型定义文件。

Pro-Tip (资深开发者提示)

为了提升构建速度,@rollup/plugin-typescript 默认可能只进行代码转换(transpilation)而不执行完整的类型检查。这是一个常见的性能优化策略,但也可能导致带有类型错误的代码被成功打包。

最佳实践是,在 package.json 的 build 脚本中分离这两个关注点:

JSON

"scripts": {
  "build": "npm run type-check && rollup -c",
  "type-check": "tsc --noEmit"
}

这样,每次构建时都会先通过 tsc --noEmit 进行严格的类型检查,通过后再执行 Rollup 打包。


4. @rollup/plugin-replace: 强大的代码替换与环境管理

@rollup/plugin-replace 是一个看似简单但功能强大的插件。它在打包前对代码进行静态的字符串替换。其最核心的应用场景是注入环境变量,特别是 process.env.NODE_ENV,这对于区分生产环境和开发环境、并最终实现 Tree Shaking 至关重要。

核心功能: 在打包过程中执行静态代码替换。

使用方法

安装:

Bash

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

配置 (rollup.config.js):

JavaScript

import replace from '@rollup/plugin-replace';

export default {
  // ...
  plugins: [
    replace({
      preventAssignment: true, // 必须设置,是最佳实践
      'process.env.NODE_ENV': JSON.stringify('production'),
      '__BUILD_VERSION__': JSON.stringify('1.0.0')
    })
    // ... 其他插件
  ]
};

深入理解 JSON.stringify

为什么必须使用 JSON.stringify('production') 而不是直接用 'production'?

  • 'production': 插件会把 process.env.NODE_ENV 替换为 production (一个变量名)。

  • JSON.stringify('production'): 结果是 '"production"' (一个字符串字面量)。

在代码中,我们通常这样写:

JavaScript

if (process.env.NODE_ENV === 'production') {
  // 生产环境逻辑
}

替换后,如果不用 JSON.stringify,代码会变成 if (production === ‘production’),这会导致 ReferenceError: production is not defined。

正确替换后,代码变为 if (“production” === ‘production’),这是一个恒为 true 的表达式。在生产构建中,压缩工具(如 Terser)会识别到这一点,将 if 语句内的代码保留,并移除 else 分支,这就是 Tree Shaking 的一种形式。


终极整合:一个完整的 rollup.config.js

现在,让我们将所有插件整合到一个配置中,展示它们如何协同工作来构建一个现代 TypeScript 库。

JavaScript

// rollup.config.js
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import replace from '@rollup/plugin-replace';
import terser from '@rollup/plugin-terser'; // 一个常用的代码压缩插件

const isProduction = process.env.NODE_ENV === 'production';

export default {
  input: 'src/index.ts',
  output: {
    file: 'dist/bundle.js',
    format: 'esm', // 输出为 ES Module
    sourcemap: !isProduction,
  },
  plugins: [
    // 1. 替换环境变量
    replace({
      preventAssignment: true,
      'process.env.NODE_ENV': JSON.stringify(isProduction ? 'production' : 'development'),
    }),
    // 2. 解析第三方模块
    nodeResolve({
      browser: true, // 优先使用浏览器版本
    }),
    // 3. 将 CommonJS 转换为 ES Modules
    commonjs(),
    // 4. 编译 TypeScript
    typescript({
      tsconfig: './tsconfig.json',
      sourceMap: !isProduction,
    }),
    // 5. 如果是生产环境,则压缩代码
    isProduction && terser(),
  ],
};

结论

Rollup 本身是一个专注的、高性能的 ES Module 打包器。它的真正威力在于其可插拔的架构和繁荣的插件生态。通过组合使用 @rollup/plugin-node-resolve、@rollup/plugin-commonjs、@rollup/plugin-typescript 和 @rollup/plugin-replace,我们构建了一个专业级的构建流程,它不仅功能完备——能够处理 TypeScript、外部依赖和环境变量,而且高度可定制和优化。

理解这些核心插件的工作原理及其协作方式,是掌握现代 JavaScript 库开发的关键一步。希望这篇深度解析能为你驾驭 Rollup、构建高质量的前端项目提供坚实的基础。

参考

Maintained by · YihuiEdit on GitHub

Keep reading

View all posts