普通视图

发现新文章,点击刷新页面。
今天 — 2026年4月18日首页

深入浅出 Tree Shaking:Rollup 是如何“摇”掉死代码的?

2026年4月18日 18:35

前言

在前端性能优化中,减小 JS 包体积是重中之重。Tree Shaking(摇树优化) 就像它的名字一样:通过摇晃代码这棵大树,让那些无用的“枯叶”(死代码)掉落。本文将带你揭秘 Rollup 实现 Tree Shaking 的底层原理。

一、 核心基石:为什么是基于ESM?

Tree Shaking 的实现并非偶然,它深度依赖于 ESM (ES Module) 规范。

  • 静态分析:ESM 要求 importexport 必须在代码顶层,不能出现在 if 块或函数内部。
  • 编译时确定:这意味着 Rollup 不需要执行代码,只需扫描一遍源码,就能在编译阶段清晰地知道模块间的依赖关系。
  • 对比 CommonJSrequire 是动态加载的,只有运行到那一行才知道加载了什么,因此 CJS 无法进行彻底的 Tree Shaking。

二、Rollup Tree Shaking 实现原理:从扫描到删除的四步曲

Rollup 的“摇树”过程可以分为以下四个精密步骤:

1. 递归扫描与依赖图构建

从入口文件(如 main.js)开始,递归扫描所有 import/export 语句。Rollup 会记录:

  • 每个模块导出了哪些变量/函数。
  • 每个模块导入了哪些内容。
  • 模块间的引用链路(A 引用了 B 的哪个具体成员)。
  • 基于这些信息,Rollup 会构建出一个完整的模块依赖图,清晰呈现整个项目的代码引用链路。

步骤 2:标记活代码与死代码

在模块依赖图的基础上,Rollup 会从入口文件出发,反向追踪所有被引用的内容,标记出活代码和死代码:

  • 首先标记出哪些导出项被外部(其他模块或入口文件)引用;
  • 接着判断这些被引用的导出项,是否真的在代码中被使用(而非仅导入未使用),若被使用则标记为活代码,未被使用则标记为死代码

步骤 3:AST 分析优化(补充细节)

在标记活代码的过程中,Rollup 会深入分析每个模块的 AST(抽象语法树) ,精准追踪变量、函数的定义和引用关系。这里有一个容易被忽略的点:

  • 即使一个变量、函数在模块内定义了,但既没有被 export 导出,也没有在模块内部被引用,它依然会被判定为死代码,被 Tree Shaking 摇掉——也就是说,Tree Shaking 不仅会处理“导出未使用”的代码,也会清理模块内部“定义未使用”的冗余代码。

步骤 4:删除死代码,生成最终产物

最后,Rollup 会遍历所有模块,只保留标记为活代码的内容,直接删除所有死代码(未被引用的导出项、模块内部未使用的定义等),最终生成精简、无冗余的打包产物。

Rollup 的 Tree Shaking 是原生支持的,无需额外配置,打包时会自动执行上述流程,且输出的代码更接近手写风格,无多余的运行时代码,优化效果直观可见。


三、 实战:不同导出方式的“招魂”效果

Tree Shaking 的效果,很大程度上取决于代码的导出方式——只有静态导出才能被 Rollup 精准分析,动态导出则无法实现 Tree Shaking。以下是常见的导出方式对比:

导出方式 是否支持树摇 深度原因分析
export const a = 1 完美支持 静态导出,引用关系明确。
export function b() {} 完美支持 未被调用时可被精准识别并删除。
export default { a:1 } 不支持/效果差 默认导出是一个对象,工具难以判断你是否会动态访问对象的某个 Key。
export * from './x.js' 支持 按需转发,只会转发那些被下游真正引用的成员。

四、 Tree Shaking 避坑指南

避坑点 1:CommonJS 模块会导致 Tree Shaking 罢工

  • 如果项目中引入了使用 require/module.exports 的第三方库,Tree Shaking 会直接失效。原因如下:

    • CommonJS 模块是动态模块,require 可以接收变量(如 require(./${name}.js)),导入导出关系只能在运行时确定,Rollup 无法在打包前进行静态分析,因此无法识别死代码,Tree Shaking 自然无法生效。

    实战建议:优先使用支持 ESM 规范的第三方库(如 lodash-es 替代 lodash),避免在 ESM 项目中混用 CommonJS 模块。

避坑点 2:副作用代码会干扰 Tree Shaking

  • 如果模块中存在“副作用代码”(即执行后会影响全局环境、修改外部变量、执行 DOM 操作等的代码,如顶层的 console.log、window.xxx = xxx),即使这些代码未被引用,Rollup 也会保守地保留它们,避免影响项目运行逻辑,从而导致部分死代码无法被摇掉。

    解决方案:如果确认模块无副作用,可在 package.json 中添加 "sideEffects": false,告诉 Rollup 该模块可安全删除未引用代码;若有部分文件有副作用(如 CSS 文件),可显式声明:"sideEffects": ["./src/style.css"]

避坑点 3:动态访问会导致 Tree Shaking 失效

  • 如果代码中存在动态访问导出项的情况(如 import * as utils from './utils.js'; utils[dynamicKey]()),Rollup 无法在静态分析阶段确定哪些导出项被使用,会保留整个模块的所有导出项,导致 Tree Shaking 失效。

    实战建议:尽量使用具名导入(import { func } from './utils.js'),避免动态访问导出项。


五、 总结

Rollup Tree Shaking 的核心是“基于 ESM 静态规范,通过静态分析识别并删除死代码”,其流程简洁高效,且原生支持无需额外配置。想要用好 Tree Shaking,关键记住 3 点:

  • 坚持使用 ESM 规范(import/export),避免混用 CommonJS;
  • 优先使用静态具名导出,避免默认导出对象、动态导出;
  • 处理好副作用代码,必要时通过 package.json 的 sideEffects 字段声明。

深度起底 Vite:从打包流程到插件钩子执行时序的全链路解析

2026年4月18日 15:49

前言

Vite 之所以能颠覆 Webpack 的统治地位,不仅是因为它在开发阶段的“快”,更在于它巧妙地结合了 原生 ESMRollup 的生产构建能力。本文将带你拆解 Vite 打包的每一个步骤,并揭秘其插件系统的核心钩子。

一、 Vite 生产打包流水线

Vite 的生产构建完全基于 Rollup 实现,但在 Rollup 打包前后增加了 Vite 特有的预处理和后优化步骤,整个流程可以分为以下 6 个核心阶段:

步骤 1:加载并解析配置

Vite 会优先读取项目根目录的vite.config.js/ts(或vite.config.mjs)配置文件,同时合并命令行参数和默认配置,形成最终的运行配置。

  • 解析核心配置项:root(项目根目录)、base(公共基础路径)、build(打包配置,如输出目录、目标环境、代码分割等)、plugins(插件)、resolve(路径解析)等
  • 同时会读取package.json中的type: module等配置,确定项目的模块规范

配置合并优先级为:命令行参数 > 配置文件导出的配置 > Vite 内置默认配置

步骤 2:预构建与依赖分析

这是 Vite 区别于传统打包工具的关键步骤,主要为了解决第三方依赖的兼容性和打包性能问题。

  • Vite 会扫描项目中的所有依赖(主要是node_modules中的第三方包),对非 ES 模块的依赖(如 CommonJS、UMD 格式)进行预构建,统一转换为标准 ES 模块
  • 分析项目入口文件(默认是根目录的index.html),递归解析所有文件的依赖关系(包括.vue/.js/.ts/.css/.scss等各种类型的文件),构建完整的模块依赖图

补充:预构建的结果会被缓存到node_modules/.vite目录,只有当依赖发生变化时才会重新预构建,极大提升了后续打包速度

步骤 3:插件执行

Vite 会按照配置的顺序依次执行所有插件,并调用相应的插件生命周期钩子,在这个阶段完成各种文件转换、代码预处理和资源处理操作。

  • 插件会在不同的生命周期钩子中执行对应的逻辑,例如:

    • .vue/.jsx/.tsx等非原生 JS 文件进行编译转换
    • 小图片 / 字体等静态资源的 base64 转换(由 Vite 内置的 assets 插件处理)
    • CSS 预处理器编译、PostCSS 处理、CSS Modules 转换等

步骤 4:使用 Rollup 进行核心打包

这是生产构建的核心阶段,Vite 将完全委托给 Rollup 进行代码打包和优化。

  • 按入口文件拆分代码块(chunk),同时支持根据动态导入import()自动拆分代码块,还可以通过配置将第三方依赖单独拆分为 vendor chunk
  • 生成兼容目标环境的代码:默认打包为现代浏览器支持的 ES 模块格式,也可通过build.target配置兼容 ES5 及更低版本
  • 处理模块间的依赖引用:将代码中的相对路径替换为配置的base路径,确保生产环境下所有资源都能正确加载
  • 执行 Rollup 特有的优化:包括 Tree Shaking 移除无用代码、作用域提升(Scope Hoisting)减少代码体积等

步骤 5:Rollup 产物最终优化

在 Rollup 完成基础打包后,Vite 会对输出的产物进行最后一轮的生产环境优化。

  • 对 JavaScript 和 CSS 代码进行压缩混淆(默认使用 Terser 压缩 JS,esbuild 压缩 CSS)
  • 生成资源哈希文件名,实现静态资源的长效缓存
  • 可选生成 sourcemap 文件,方便生产环境调试
  • 可选生成manifest.json文件,记录资源文件名与哈希后的文件名的映射关系,用于后端集成

步骤 6:输出最终产物

将所有打包和优化后的产物输出到指定的目录(默认是dist目录)。

  • 静态资源(JS、CSS、图片、字体等)会输出到dist/assets目录
  • 入口 HTML 文件会输出到dist根目录
  • 其他配置的静态资源会按照原目录结构输出到dist目录

二、 揭秘 Vite 插件钩子 (Hooks)

Vite 的插件系统扩展自 Rollup 的插件系统,同时增加了一些 Vite 特有的钩子函数,以支持开发服务器和热更新等 Vite 独有的功能。

2.1 通用 Rollup 兼容钩子

Vite 在开发阶段会模拟 Rollup 的行为,调用一系列与 Rollup 兼容的钩子;而在生产环境下,由于 Vite 直接使用 Rollup 进行打包,所有 Rollup 插件钩子都会生效。

这些通用钩子主要分为三个阶段:

  1. 服务器启动阶段optionsbuildStart钩子会在开发服务启动时被调用
  2. 请求响应阶段:当浏览器发起请求时,Vite 内部依次调用resolveIdloadtransform钩子
  3. 服务器关闭阶段:Vite 会依次执行buildEndcloseBundle钩子

重要说明:除了以上钩子,其他 Rollup 插件钩子(如moduleParsedrenderChunk等)均不会在 Vite 开发阶段调用。这是因为开发阶段 Vite 采用按需编译的模式,不需要对整个项目进行完整打包。

2.2 Vite 独有钩子

Vite 提供了一些独有的钩子函数,这些钩子只会在 Vite 内部调用,放到纯 Rollup 环境中会被直接忽略。

钩子名称 调用时机 主要用途
config Vite 读取完配置文件之后,最终配置合并之前 对用户导出的配置对象进行自定义修改,例如注入默认值、根据环境动态调整配置、合并插件配置等
configResolved Vite 完成最终配置解析之后 此时配置已完全合并且不可再修改,常用于获取最终配置进行调试,或根据最终配置初始化插件内部状态
configureServer 仅在开发服务器启动时调用 扩展或拦截开发服务器行为,例如添加自定义中间件、模拟 API 接口、修改服务器配置、实现请求代理等
transformIndexHtml 转换原始 HTML 文件内容时调用 动态修改 index.html 内容,例如注入脚本标签、修改 meta 标签、添加全局变量、SSR 内容注入等
handleHotUpdate Vite 服务端处理热更新时调用 自定义热更新逻辑,例如过滤不需要热更新的模块、触发自定义的热更新行为、向客户端发送自定义消息等

三、 核心:钩子函数的执行顺序

理解执行顺序是编写高质量插件的前提。

1. 服务启动阶段

configconfigResolvedoptionsconfigureServerbuildStart

2. 请求响应阶段

  • HTML 请求:仅执行 transformIndexHtml
  • 非 HTML 请求 (JS/CSS等):resolveIdloadtransform

3. 热更新与关闭阶段

  • HMR 触发handleHotUpdate
  • 服务关闭buildEndcloseBundle


四、 知识扩展:开发与生产的差异

  • 开发阶段:Vite 利用浏览器原生 ESM,只有在文件被请求时才触发 transform 钩子,这是其“快”的底层逻辑。
  • 生产阶段:Vite 完完全全变成了一个“Rollup 配置封装器”,所有插件钩子都会遵循 Rollup 的打包逻辑执行。

深度解密 Rollup 插件开发:核心钩子函数全生命周期图鉴

2026年4月17日 23:41

前言

Rollup 的强大在于其精简的插件系统。一个 Rollup 插件本质上就是一个包含各种“钩子函数”的对象。理解这些钩子的执行时序,是编写高性能插件、优化构建流程的关键。本文将带你深度复盘 Rollup 的两大核心阶段:构建 (Build)输出 (Output)


一、构建阶段钩子函数(核心阶段)

构建阶段主要负责模块的解析、加载和转换,最终完成模块依赖图的构建,是Rollup打包的基础。该阶段可细分为5个小阶段,钩子执行顺序固定为:

初始化阶段(options、buildStart)→ 模块加载阶段(resolveId、load)→ 模块转换阶段(transform、moduleParsed)→ 代码生成阶段(augmentChunkHash、resolveDynamicImport)→ 代码构建阶段(buildEnd)

1. 初始化阶段钩子(options、buildStart)

options

  • 执行时机:在读取用户配置之后、构建开始之前执行。

  • 作用:可以添加或修改默认配置项(如调整input、output、plugins等Rollup核心配置)。

  • 注意:仅支持同步执行,无法进行异步操作;此钩子修改的配置会覆盖用户默认配置,需谨慎使用。

buildStart

  • 执行时机:开始解析模块前执行(构建流程启动的第一个核心钩子)。

  • 作用:用于初始化插件状态(如重置计数器、初始化缓存)、读取外部文件(如配置文件、静态资源清单)等。

  • 支持:同步、异步执行(可返回Promise);此钩子可访问传递给rollup.rollup()的最终配置,包含所有options钩子的转换结果和默认值。

2. 模块加载阶段钩子(resolveId、load)

resolveId(source, importer)

  • 执行时机:它是在Rollup遇到一个 import 语句时(如 import foo from './foo.js')执行,是模块解析的核心钩子。

  • 作用:它可以将模块标识符(如 './foo.js''vue')解析为绝对路径或模块 ID,返回一个解析后的路径ID(返回值可以是 null、string 或者一个对象,如果返回false则视为外部模块,不打包)。支持同步、异步执行。

  • 入参说明:

    • source:表示 import 的内容(字符串),即模块标识符;
    • importer:表示导入该模块的文件路径(绝对路径),入口文件的importer为 null。

如果多个插件都定义了resolveId,会按插件配置顺序执行,直到某个插件返回非null/undefined的值(表示解析完成);也可通过配置order: 'pre'调整钩子执行优先级,实现优先解析特定模块。

示例:拦截虚拟模块导入,自定义模块解析逻辑:

resolveId(source) {
  if (source === 'virtual-module') {
    // 表示rollup不应询问其他插件或从文件系统检查此ID
    return source;
  }
  return null; // 其他ID按正常逻辑处理
}

load(id)

  • 执行时机:它在 resolveId 返回一个 ID 后执行,是模块加载的核心钩子。

  • 作用:用于获取对应模块的源码,并返回这个源码给transform钩子进行后续转换。

  • 支持:同步、异步执行;若返回null,Rollup会默认从文件系统读取该ID对应的文件内容,也可通过this.load在其他钩子中触发模块预加载。

示例:自定义虚拟模块的源码加载:

load(id) {
  if (id === 'virtual-module') {
    // 返回虚拟模块的源码
    return 'export default "This is virtual!"';
  }
  return null; // 其他ID按正常逻辑加载
}

3. 模块转换阶段钩子(transform、moduleParsed)

transform(code, id)

  • 执行时机:它在模块源码加载后执行,紧随load钩子之后。

  • 作用:它用于将模块源码中的ts、tsx等非标准JS语法转换为标准的js语法,也可对源码进行压缩、注入代码等自定义处理。支持同步、异步执行

  • 入参说明:

    • code:模块的源码字符串(load钩子返回的内容);
    • id:模块 ID(通常是文件路径,与resolveId返回的ID一致)。
  • 返回值:{ code: '修改后的代码', map: 'sourcemap' },其中sourcemap可选,用于关联转换后的代码与原始源码,方便调试。

moduleParsed

  • 执行时机:在模块被 Rollup 解析为 AST(抽象语法树)后执行。

  • 作用:可以用于分析模块信息(如导入导出关系)、收集元数据(如模块依赖、变量声明),实际开发中较少使用。

  • 支持:同步、异步执行;入参为moduleInfo,包含当前模块的详细信息,执行完成后会并行解析模块中所有静态和动态导入的依赖。

4. 代码生成阶段钩子(augmentChunkHash、resolveDynamicImport)

augmentChunkHash

  • 执行时机:在生成 chunk 哈希前执行(chunk 哈希用于实现静态资源长效缓存)。

  • 作用:可以向 chunk 哈希添加额外信息(如插件版本、配置参数),确保当这些信息变化时,chunk 哈希也会更新,避免缓存失效不及时。

resolveDynamicImport

执行时机:当遇到动态导入语句时(如import('./foo.js'))执行。

作用:处理动态导入的解析,作用和resolveId类似,但专用于动态导入场景,可自定义动态导入的模块解析规则。

5. 代码构建阶段钩子(buildEnd)

  • 执行时机:构建结束(无论成功或失败)执行,是构建阶段的最后一个钩子。

  • 作用:用于清理资源(如关闭文件流、清空缓存)、上报错误(如构建失败日志上报)等。

  • 支持:同步、异步执行。

二、打包阶段钩子函数(产物输出阶段)

打包阶段主要负责将构建阶段处理后的模块,生成最终的可部署产物,并写入磁盘,钩子执行顺序固定为:

输出生成(renderStart→renderChunk→generateBundle)→ 输出写入(writeBundle→closeBundle)

1. 输出生成阶段钩子(renderStart、renderChunk、generateBundle)

renderStart

  • 执行时机:开始生成 chunk 内容前执行,是打包阶段的第一个钩子。

  • 作用:初始化输出相关状态(如初始化产物计数器、设置输出格式相关参数)。

  • 支持:同步、异步执行。

renderChunk(code, chunk, options)

  • 执行时机:它是在每个 chunk 生成后、写入磁盘前执行。

  • 作用:它可以对生成的chunk 中的JS 代码进行最后处理(例如注入版权注释、补充全局变量、代码压缩优化等)。支持同步、异步执行。

  • 入参说明:

    • code:当前chunk生成后的JS代码字符串;
    • chunk:当前chunk的详细信息(如chunk名称、包含的模块、依赖关系等);
    • options:当前的输出配置(与output配置一致)。

generateBundle

  • 执行时机:它是在所有 chunk 和 asset 生成完毕,即将写入磁盘前执行。

  • 作用:这个钩子的入参里面会包含所有的打包产物信息,包括 chunk (打包后的代码)、asset(最终的静态资源文件)。可以在这里检查、修改、添加最终输出文件(例如删除无用 chunk、合并CSS、注入 preload 链接到 HTML)。

  • 支持同步、异步执行;是打包阶段最常用的钩子之一,可用于最终产物的自定义优化。

2. 输出写入阶段钩子(writeBundle、closeBundle)

writeBundle

  • 执行时机:它是在bundle 已写入磁盘后执行,仅在调用bundle.generate()bundle.write() 时触发。

  • 作用:可以在这执行写入后的操作(如将产物上传 CDN、生成产物清单、通知部署服务等)。

  • 支持:同步、异步执行。

closeBundle

  • 执行时机:它是在整个构建完全结束执行,是Rollup打包流程的最后一个钩子。

  • 作用:可以在这做一些全局清理操作(如关闭数据库连接、清空临时文件、终止子进程等)。

  • 支持:同步、异步执行;无论构建成功或失败,都会执行此钩子。

三、钩子执行顺序(核心重点)

// 完整执行顺序
options → buildStart → resolveId → load → transform → moduleParsed → 
augmentChunkHash → resolveDynamicImport → buildEnd → renderStart → 
renderChunk → generateBundle → writeBundle → closeBundle

注意:所有钩子均支持同步执行,标注“支持异步”的钩子可返回Promise,实现异步操作(如读取外部文件、请求接口);多个插件定义同一钩子时,按插件配置顺序执行。

四、补充

  1. 钩子使用场景:开发Rollup插件时,可根据需求选择对应阶段的钩子(如语法转换用transform、产物优化用generateBundle、CDN上传用writeBundle);

  2. 与Vite关联:Vite生产环境基于Rollup打包,Vite插件可直接使用Rollup的所有钩子,同时Vite会自动注入内置插件,无需手动配置基础钩子(如resolveId、load);

  3. 调试技巧:可在钩子中打印日志(如console.log('钩子执行:', id)),查看钩子执行顺序和入参信息,快速排查插件问题。

深度解析 Rollup 配置与 Vite 生产构建流程

2026年4月17日 23:02

前言

为什么 Vite 在生产环境不使用 ESBuild 而是选择 Rollup?为什么 Rollup 打包出来的代码比 Webpack 更纯粹?本文将带你深入 Rollup 的核心配置,并拆解 Vite 是如何驱动 Rollup 完成生产环境构建的。

一、 Rollup 核心配置:构建系统的“方向盘”

1. 核心概念

Rollup 是 Vite 生产环境下的底层打包工具,专注于 ES 模块的打包优化。

注意:在 Vite 项目中,不需要单独编写rollup.config.js文件,所有 Rollup 相关的配置都统一写在vite.config.js/tsbuild.rollupOptions字段中。Vite 会自动将你的配置与内置的 Rollup 配置合并,生成最终的打包配置。

2. 核心特点

  • 具有天然的 Tree Shaking 功能,可以静态分析 ES 模块的导入导出关系,精准移除未使用到的代码
  • 支持Scope Hoisting(作用域提升) ,将多个模块的代码合并到同一个作用域中,减少函数包裹和运行时开销
  • 打包产物体积小、执行效率高,特别适合用于 JavaScript 库和工具的打包
  • 插件系统简洁强大,易于扩展和定制

二、Rollup 核心配置项详解

1. input(打包入口)

用于指定打包的入口文件,支持三种写法:

  • 字符串:单入口,所有代码打包到一个文件中,适合 SPA 单页面应用
  • 数组:多入口,每个入口生成独立的 chunk 文件,公共依赖会自动拆分
  • 对象:多入口,可自定义每个 chunk 的名称,是最灵活的写法
// 单入口
input: 'src/index.js'

// 数组多入口
input: ['src/page1.js', 'src/page2.js']

// 对象多入口(推荐)
input: {
  home: 'src/pages/home.js',
  about: 'src/pages/about.js',
  vendor: 'src/utils/vendor.js'
}

原内容保留:如果是多文件的话,会给每个入口生成独立文件,公共依赖会在打包过程中拆分出来,适用于 MPA(多页应用)、库的多版本。

2. output(打包输出)

该属性为对象或对象数组类型,可以指定打包后的文件输出规则。如果配置为数组,可以将同一份代码打包成多种不同格式的包同时输出。output 核心属性如下:

属性名 类型 说明
dir string 输出目录,当有多个 chunk 时必须使用此属性
file string 单个输出文件的路径,仅适用于单入口单 chunk 的情况
format string 输出格式,支持:- esm:ES Module 格式(Vite 默认)- cjs:CommonJS 格式- umd:通用模块定义- iife:立即执行函数格式
name string 打包为iifeumd格式时必须配置,指定对外暴露的全局变量名
globals object 全局变量声明,用于将外部依赖映射为全局变量示例:{ jquery: '$' }表示项目中可以直接用$代替jquery
sourcemap boolean 是否生成源码映射文件,方便生产环境调试
assetFileNames string 静态资源文件输出文件名模板示例:'assets/[name]-[hash][extname]'

下面的例子代表第一次打包(ESM 格式),将结果输出到dist/es/目录,第二次打包(CJS 格式),将结果输出到dist/cjs/目录

output: [
  {
    dir: "dist/es",    // 输出到 dist/es 目录
    format: "esm",     // ES Module 格式
  },
  {
    dir: "dist/cjs",   // 输出到 dist/cjs 目录
    format: "cjs",     // CommonJS 格式
  },
]

3. external(外部依赖)

用于标记某些模块为外部依赖,告诉 Rollup 这些模块不应该被打包到最终的 bundle 中。对于某些第三方包,有时候我们不想让 Rollup 进行打包,也可以通过 external 进行外部化。external 支持三种写法:

// 字符串写法
external: ['vue', 'react']

// 正则写法
external: [/^lodash/]

// 函数写法(最灵活)
external: (id) => {
  // 所有node_modules中的模块都标记为外部依赖
  return id.includes('node_modules')
}

使用场景

  • 库打包时,将核心依赖(如 Vue、React)外部化,避免重复打包
  • 减少打包体积,提高构建速度
  • 利用 CDN 加载公共依赖

4. plugins(插件系统)

用于扩展和定制 Rollup 的构建流程。该配置项可以与output配置在同一级(全局生效),也可以配置在 output 参数里面(仅对该输出生效)。可使用自定义编写的 Rollup 插件,也可以使用第三方插件。

常用 Rollup 插件整理如下:

插件名称 作用
@rollup/plugin-json 支持.json文件的加载,并配合 Tree Shaking 去掉未使用的部分
@rollup/plugin-babel 使用 Babel 进行 JS 代码的语法转译,兼容低版本浏览器
@rollup/plugin-typescript 支持使用 TypeScript 开发
@rollup/plugin-alias 支持路径别名配置
@rollup/plugin-replace 在打包过程中进行变量字符串的替换
@rollup/plugin-node-resolve 解析 node_modules 中的第三方依赖
@rollup/plugin-commonjs 将 CommonJS 模块转换为 ES 模块,供 Rollup 处理
rollup-plugin-visualizer 对打包产物进行分析,自动生成产物体积可视化分析图

补充说明@rollup/plugin-node-resolve@rollup/plugin-commonjs是 Rollup 处理第三方依赖的必备插件,Vite 已经内置了这两个插件,不需要手动配置。

这篇笔记非常深入地探讨了 Rollup 在 Vite 体系中的地位。作为现代前端打包工具的“幕后英雄”,Rollup 的配置和流程是进阶高级前端的必修课。

为了适配掘金的风格,我为你优化了标题,并引入了“声明式配置图解”和“双阶段构建流”的概念,同时修正了部分关于 input 拼写的小细节。


进阶必备:深度解析 Rollup 配置与 Vite 生产构建流程

前言

为什么 Vite 在生产环境不使用 ESBuild 而是选择 Rollup?为什么 Rollup 打包出来的代码比 Webpack 更纯粹?本文将带你深入 Rollup 的核心配置,并拆解 Vite 是如何驱动 Rollup 完成生产环境构建的。


一、 Rollup 核心配置:构建系统的“方向盘”

Rollup 的配置以简洁著称,其设计的核心目标是打包出最干净的代码库

1. 入口与出口 (Input & Output)

  • input (注意是单数):支持字符串或数组/对象。

    • 单入口:适合单页面应用 (SPA),生成一个主 bundle。
    • 多入口:适合多页面应用 (MPA) 或组件库,Rollup 会自动提取公共依赖。
  • output:支持数组形式,实现一份源码,多种格式输出

    • format:

      • esm: 现代浏览器首选。
      • cjs: Node.js 环境使用。
      • umd: 兼容 AMD/CommonJS/全局变量。
    • globals: 映射外部依赖,如 { jquery: '$' }

2. 外部依赖 (External)

核心作用:标记某些模块不被打包。

  • 场景:在开发组件库时,通常会将 vuereact 设为 external,让宿主环境提供这些依赖,减小打包体积。

3. 常用插件全家桶

插件名称 核心作用
@rollup/plugin-json 让 JS 能直接 import json,并支持 Tree Shaking。
@rollup/plugin-babel 配合 Babel 进行语法降级,解决兼容性问题。
@rollup/plugin-typescript 让 Rollup 具备处理 TS 的能力。
@rollup/plugin-alias 配置路径别名(如 @ 指向 src)。
rollup-plugin-visualizer 神器:生成体积分析图,优化首屏加载必看。

三、 Vite 视角下的 Rollup 构建流程

执行vite build命令后,Vite 会先完成自身的预处理工作,然后将所有打包任务委托给 Rollup 执行。整个流程可以分为以下三个核心阶段:

阶段 1:Vite 配置预处理与 Rollup 配置生成

Vite 读取vite.config.js,先分离 Vite 非 Rollup 相关的配置和 Rollup 相关配置;将 Vite 内置的处理 Vue/TS/CSS/ 静态资源插件注入,再合并用户配置的插件,最终生成标准的包含inputoutputplugins等核心字段的Rollup 配置对象

接着 Vite 调用 Rollup 的rollup.rollup()方法,传入上述配置,启动 Rollup 构建流程。

阶段 2:Rollup 构建阶段(建立模块依赖图)

Rollup 从input指定的入口文件开始,依次执行所有插件的resolveId(解析模块路径)→load(加载文件内容)→transform(转换文件为标准 ES 模块)钩子来处理各类资源。在处理的过程中会递归解析所有导入的模块,直到所有依赖解析完成,建立完整的模块依赖图。

补充:这个阶段只进行模块的解析和转换,不会生成任何输出文件。所有的文件内容都会被加载到内存中,形成一个完整的模块树。

阶段 3:Rollup 生成阶段(产物生成与输出)

在构建打包的过程中,Vite 会基于模块依赖图执行 Tree-shaking 移除未使用代码、Scope Hoisting 作用域提升,并根据output配置进行代码分割将代码拆分成多个独立的 chunks 文件,实现按需加载。Vite 插件在此阶段补充处理(将.vue、.ts 等文件编译为标准的 js 文件、并处理 css 将其压缩成单独的 css 文件等)。

最后调用bundle.generate()在内存中生成编译后的 JS/CSS/HTML 等产物,和bundle.write()将内存中的产物写入磁盘(默认dist目录),最终生成可部署的静态资源文件。

补充bundle.generate()只在内存中生成产物,不写入磁盘;bundle.write()会先调用bundle.generate(),然后将产物写入到指定的输出目录。

总结

Rollup 作为 Vite 生产环境的底层打包工具,是理解 Vite 打包原理的关键。掌握 Rollup 的核心配置和构建流程,不仅能帮助你解决生产环境中的各种打包问题,还能让你更灵活地定制 Vite 的构建流程,实现更高效的打包优化和插件开发。

❌
❌