Tree-shaking 深入:摇树原理、副作用边界与产物瘦身

深入 Vite/Rollup 的 Tree-shaking 机制:摇树的原理(按使用标记 + 删除未用导出)、为什么必须 ESM(静态可分析)、副作用标记 sideEffects 的作用与配置、package.json 的 exports/module 字段、摇树的边界(动态导入/运行时/条件导入)、常见摇树失败与排查、库作者的摇树优化、与代码分割的配合、以及验证摇树效果的产物分析方法,帮助你写出「能摇动」的代码并诊断摇不掉的包体。

引言

「打包出来的文件怎么这么大」往往是同一个原因:Tree-shaking 没生效。摇树(Tree-shaking)是 Rollup/Vite 的核心优化,能把「没用的导出」从产物里删掉,但它的生效有一整套前提:ESM 的静态分析、副作用标记、package.json 字段、代码的可摇性。本文讲透摇树:先讲原理(按使用标记 + 删除未用)、为什么必须 ESM(静态可分析,CommonJS 摇不动)、副作用标记 sideEffects("sideEffects": false 让包可摇)、package.json 的 exports 与 module 字段(入口该指向 ESM)、摇树的边界(动态导入/运行时/条件导入摇不掉)、常见摇树失败与排查(副作用误判/入口错误/黑魔法)、库作者的摇树优化(怎么写可摇的库)、与代码分割的配合(摇树 + 分割的组合策略)、最后是验证摇树效果(产物分析看包体)。

前置:/vite-build-optimization/(构建与代码分割)、/vite-import-graph-internals/(导入图与依赖)、/vite-dependency-pre-bundling/(依赖预打包)。


目录


1. Tree-shaking 的原理

摇树的本质:

Tree-shaking = 删除「从未被使用」的导出代码

两步:
  ① 标记(Mark):从入口出发,沿 import 追踪「用到哪些导出」
  ② 删除(Sweep):未标记的导出函数/变量/副作用被移除

→ 只保留「可达且被使用」的代码 = 摇树

一个可摇的例子:

// utils.js
export function used() { return 1 }        // 保留
export function unused() { return 2 }      // 删除

// main.js
import { used } from './utils'
console.log(used())
// 产物只含 used 的实现

为什么摇树节省真实收益:

- 大型库(如 lodash、dayjs):几百个导出,只用几个
- 不摇:整库进产物(几百 KB)
- 摇树:只用到的函数进产物(几 KB)
- UI 库按需:组件库摇树后只含用到的组件
→ 摇树 = 从「全量引入」到「按需引入」的自动版

Rollup 的实现机制:

- Rollup 是「模块打包器」:分析依赖图 + 按导出使用构建
- 使用追踪:入口导入的导出 → 反向确定「需要哪些实现」
- 副作用分析:无法确定「无副作用」的模块可能被整体保留
- 产物:只包含「被引用链」覆盖的代码
→ Rollup 的摇树 = 使用追踪 + 副作用判断

摇树的单位:

- 函数/变量导出:可精确删除(未用即删)
- 模块副作用:无法删除(见第 3 节)
- 类方法:如果类被使用,未用方法难删(整类保留)
- 导出重导:re-export 要「透传」使用(见第 6 节)
→ 摇树粒度 = 到「导出绑定」级别,副作用是障碍

心智:Tree-shaking = 从入口沿 import 追踪使用、删除未用导出(标记+清扫两步);收益是把「全量引入」变「按需引入」的自动版(大型库只用几个导出时收益巨大);Rollup 的实现 = 依赖图分析 + 导出使用追踪 + 副作用判断;摇树粒度到「导出绑定」,副作用与 re-export 处理不当是障碍。


2. 静态分析:为什么需要 ESM

摇树的前提 = 静态可分析:

- ESM:import/export 是「顶层静态语句」
  import { a } from './x'  → 编译期已知 a 用没用
  → 可静态分析,可摇树

- CommonJS:require() 是「运行时函数」
  const m = require('./x') → 运行时才知道用哪个
  → 静态无法分析,难以摇树
→ 摇树 = ESM 专属能力(静态语法)

CommonJS 为什么摇不动:

// CJS:整体加载,无法按成员分析
const lodash = require('lodash')
lodash.map(arr, fn)
// → 只能保留「整个 lodash」,无法删除未用的 map?…

// 特殊技巧:babel-plugin-lodash 等手动按需
const { map } = require('lodash/map')  // 子路径引入
// → 这是「手动子路径按需」,不是摇树

ESM 的静态性质:

// ESM:导入绑定静态声明
import { map } from 'lodash-es'
// → 编译器确定「只用 map」,可删除其他
// → lodash-es 版本是 ESM,才能摇树

Vite 中的依赖格式:

- Vite 预打包把 CJS 依赖转成 ESM(esbuild)
  (cjs → esm:近似转换)
- 但「转出来的 ESM」摇树能力有限
  (转译丢静态信息,如动态访问)
- 真摇树需要「原生 ESM 库」(package.json module/exports)
→ 预打包让 CJS 可用,但「可摇性」靠原生 ESM

判断依赖是否可摇:

- 有 exports.module 或 main 指向 .esm/.mjs → 可摇
- 只有 cjs/main.js → 需转译(摇树受限)
- 无 exports 的旧包 → 看 main 字段
- 用 CDN/unpkg 的 module 字段判断(面向 ESM)
→ 可摇性检查 = 查依赖的 ESM 入口是否存在

为什么 esbuild 也要 ESM:

- esbuild 本身也做 tree-shaking(按 ESM 分析)
- 预打包时:把依赖转 ESM + 尽量摇(bundle 内)
- Rollup 最终产物再摇一次(跨模块)
- 双层摇树:esbuild(依赖内)+ Rollup(项目内)
→ 摇树是「ESM 生态」的共同语言

心智:摇树前提 = ESM 静态可分析(import/export 顶层静态,编译期确定使用);CommonJS 是运行时 require 无法静态分析,只能靠手动子路径按需或 babel 插件;Vite 预打包把 CJS 转 ESM(esbuild 近似),但真摇树靠「原生 ESM 库」(module/exports 指向 .esm);可摇性检查 = 查依赖 ESM 入口;双层摇树:esbuild 依赖内 + Rollup 项目内。


3. 副作用标记:sideEffects

为什么摇树会被「副作用」挡住:

// 假设某库的 polyfill.js:
globalThis.__FOO__ = 'foo'   // 副作用(改了全局)

// 即使没人 import,导入它就有「副作用」
// → 打包器不敢删它(删了全局就没了)
→ 副作用 = 「导入即执行且影响外部」的代码

模块副作用的判断:

- 纯函数导出:无副作用(可摇)
- 顶层执行语句(console.log/全局赋值):有副作用
- 有副作用的模块:即使导出未用,也可能「整体保留」
- 打包器无法确定 → 保守保留(安全 > 体积)
→ 无法判断副作用 = 摇树的最大「黑箱」

sideEffects 字段:

// package.json 声明「本包无副作用」
{
  "sideEffects": false
}
// → 打包器可放心摇:未用的导出/模块都可删

// 或声明「仅某些文件有副作用」
{
  "sideEffects": ["./src/polyfill.js", "./src/global.css"]
}
// → 其他文件可摇,列出的文件保留

Vite/Rollup 对 sideEffects 的读取:

- 打包器读取依赖包的 package.json 的 sideEffects
- sideEffects: false → 依赖完全可摇
- sideEffects 数组 → 按列表判定
- 无该字段 → 默认保守(按代码分析)
→ sideEffects 是「包的摇树许可声明」

为什么默认导入会被整体保留:

import 'animate.css'   // 默认导入 = 只要副作用
// → 打包器认为「导入即要副作用」→ 整体保留

// 使用具名导入才触发「摇树」
import { fadeIn } from 'animate-es'
// → 只保留 fadeIn 相关

写代码时的副作用纪律:

- 避免「导入即执行副作用」的模块(很难摇)
- 顶层副作用移到「显式导入点」
- CSS 导入保留(sideEffects 列表里声明)
- 纯工具模块声明 sideEffects: false
→ 副作用纪律 = 让代码「可摇」

sideEffects 的常见坑:

- 库声明 false 但实际有副作用(导入即崩)
- CSS 文件被当副作用删掉(要在列表声明)
- 正则写错(匹配不到副作用文件)
- 应用自身的 sideEffects 也要配(src 下 CSS)
→ 坑:声明与实际的错配

心智:副作用 = 导入即执行且影响外部(全局赋值/console),打包器无法确定就保守整体保留(安全>体积),是摇树最大黑箱;sideEffects 字段是「包的可摇性声明」:false = 全可摇、数组 = 仅列表文件保留副作用(CSS/polyfill 声明于此)、无字段 = 保守分析;写代码纪律:避免导入即副作用、纯模块声明 false、CSS 显式声明;常见坑:声明与实际错配、CSS 被误删、正则不匹配。


4. package.json 的 exports 与 module

入口字段决定「包是否可摇」:

// 传统:main 指向 CJS
{ "main": "dist/index.cjs.js" }

// 加分:module 指向 ESM(旧 ESM 约定)
{ "main": "dist/index.cjs.js", "module": "dist/index.esm.js" }

// 现代:exports 精确声明(替代 main/module)
{
  "exports": {
    ".": {
      "import": "./dist/index.esm.js",
      "require": "./dist/index.cjs.js"
    }
  }
}

各字段的作用:

- main:默认入口(CJS/包根)
- module:ESM 入口(打包器优先读,可摇)
- exports:现代条件入口(import/require/browser/node 分环境)
  → 打包器按「条件」选入口
- type: module:包默认 ESM(.js 即 ESM)
→ 可摇性的入口三件套:module / exports.import / type:module

为什么 exports 更重要:

- main/module 是「遗留约定」(module 非标准)
- exports 是官方标准(Node 条件导出)
- exports 还控制「子路径可导入性」
  (限制哪些子路径能 import,封装内部)
- Vite/Rollup 优先读 exports
→ 现代包都用 exports,module 是兼容

exports 与摇树的关系:

- exports.import 指向 ESM → 打包器用 ESM 入口 → 可摇
- exports.require 指向 CJS → 非 ESM 环境用
- 打包器选 import 条件(面向 bundler)
- 无 exports 只有 main(CJS)→ 难摇
→ 摇树 = 打包器「选中 ESM 入口」的能力

子路径导出(tree-shake 友好的库结构):

{
  "exports": {
    ".": { "import": "./dist/index.esm.js" },
    "./button": {
      "import": "./dist/button.esm.js"
    },
    "./use-modal": {
      "import": "./dist/use-modal.esm.js"
    }
  }
}
// → 用户可 import('@lib/button')(子路径按需)
// → 比「具名导出 + 摇树」更精确(路径级按需)

入口写错的常见问题:

- module 指向但无 exports:部分工具不认 module
- exports 缺 import 条件:打包器 fallback 到 require(CJS 不可摇)
- exports 指向「一个 bundle」而非「分模块」:子路径用不了
- type: module 与 .cjs 混淆:入口判断错乱
→ 入口问题 = 摇树失败的头号外因

心智:包的入口字段决定可摇性:main(CJS 默认)、module(ESM 入口,可摇)、exports(现代条件入口,import/require 分环境,优先被读)、type:module(默认 ESM);exports 是官方标准、控制子路径可导入性;摇树 = 打包器选中 exports.import 的 ESM 入口;子路径导出(./button)实现「路径级按需」比具名导出更精确;入口写错(缺 import 条件/指向 bundle)是摇树失败头号外因。


5. 摇树的边界:动态导入与运行时

动态导入:不能「全摇」但要分割:

// 动态导入:运行时才知道用哪个模块
const mod = await import('./heavy')

// 打包器做法:
//   - heavy 不进入「首包」(独立 chunk)
//   - heavy 内部的未用导出仍可摇(Rollup 分析)
// 但 heavy 本身必须保留(可能被用)
→ 动态导入 = 摇不了「模块级」,但可「分包 + 内部摇」

运行时依赖:完全摇不了:

// 运行时构造路径
const name = `./locales/${locale}.js`
await import(name)
// → 打包器无法静态分析
// → 用 glob 显式声明候选:
const files = import.meta.glob('./locales/*.js')
// → glob 让打包器「枚举候选」,做 chunk 预生成

条件导入:全部保留:

// 运行时才判断
if (process.env.NODE_ENV === 'development') {
  import './dev-only.js'
}
// → 打包器无法删(开发环境可能真用)
// → 但可用 define/环境替换让打包器「静态化」

副作用的模块:边界内保留:

- 有副作用模块:即使导出未用也保留
- 入口副作用:import 'polyfill' 无法摇
- 全局 CSS:import './app.css' 保留
- 表达式副作用:++/赋值 保留
→ 副作用 = 摇树的「合法保留区」

TypeScript 类型:不占产物:

// 类型导入在编译后消失
import type { Foo } from './types'
import { type Bar, realFn } from './module'
// → type 关键字让打包器知道「这是类型,可删」
// → 不加 type 可能被当真实导入(保留?不,TS 也会删类型)

摇树边界的本质:

- 可摇:静态具名导入 + 无副作用
- 半可摇:动态导入(分包 + 内部摇)
- 不可摇:运行时路径、条件真分支、副作用模块
→ 边界 = 「静态可分析 + 无副作用」之外的区域

心智:摇树边界三层:可摇(静态具名 + 无副作用)、半可摇(动态导入 = 分包 + 内部摇,glob 枚举候选 import.meta.glob)、不可摇(运行时构造路径、条件导入全保留、副作用模块);边界本质 = 「静态可分析 + 无副作用」之外的区域;优化方向:把动态/条件导入「静态化」(glob/define 替换)让打包器可分析。


6. 常见摇树失败与排查

常见失败 1:副作用误判:

症状:未用导出还在产物里
原因:模块有副作用(顶层执行/重导出副作用)
排查:
  - 看模块顶层是否有执行语句
  - 查依赖 package.json sideEffects(是否 false/数组)
  - 尝试把「纯函数」模块标 sideEffects: false
→ 失败 1 = 副作用未标记

常见失败 2:入口是 CJS:

症状:整个依赖进产物(几百 KB)
原因:exports/main 指向 CJS,打包器用了 CJS 入口
排查:
  - 查依赖 package.json 的 exports.import / module
  - 用 lodash-es 替代 lodash(ESM 版)
  - 检查是否有原生 ESM 的替代包
→ 失败 2 = 用了 CJS 入口(换 ESM 包)

常见失败 3:re-export 的副作用:

// barrel 文件(index.js):re-export 所有
export * from './a'
export * from './b'
// → 打包器对 barrel 的摇树有限
// → 从 barrel 具名导入可能连带所有子模块

// 优化:直接导入子路径
import { a } from './lib/a'
// 或 barrels-of-wisdom 模式(每模块建 barrel)
→ 失败 3 = barrel re-export 摇不动(改子路径导入)

常见失败 4:黑魔法与 this 绑定:

// 动态访问/反射:无法静态分析
const fn = obj[methodName]()   // 摇树放弃

// 类字段/装饰器:有隐藏副作用
// Babel 转换的代码:_classCallCheck 等保留
// 顶层 this 引用:打包器保守保留
→ 失败 4 = 运行时访问(重写为静态)

常见失败 5:CSS 被误删:

症状:样式丢失(CSS 被摇掉)
原因:CSS 在 sideEffects: false 的包里被当无副作用
排查:
  - 库声明 sideEffects: false 但含 CSS
  - 把 CSS 文件加进 sideEffects 数组
  - 或直接显式 import CSS(应用侧)
→ 失败 5 = CSS 与 sideEffects 错配

排查摇树的标准流程:

1. 用 vite-plugin-inspect 看「转换后」产物
2. 用 rollup-plugin-visualizer 看「谁占了包体」
3. 定位大模块:是依赖还是自己的代码
4. 对依赖:查 ESM 入口 + sideEffects
5. 对自己:查副作用/barrel/运行时
→ 排查 = 可视化定位 → 入口/副作用/写法三查

心智:五大摇树失败:副作用误判(标 sideEffects)、CJS 入口(换 ESM 包)、barrel re-export 摇不动(改子路径导入)、黑魔法/动态访问(重写静态)、CSS 误删(sideEffects 数组声明);排查流程 = visualizer 定位大模块 → 查依赖 ESM 入口 + sideEffects → 查自己代码的副作用/barrel/运行时。


7. 库作者的摇树优化

写一个「可摇」的库:

1. 输出 ESM:提供 exports.import 的 .esm.js
2. 声明副作用:sideEffects: false(纯函数库)
3. 保留 CSS:有 CSS 就 sideEffects: ["./**/*.css"]
4. 提供子路径:exports["./button"] 分模块
5. 避免顶层副作用:不导入即执行
→ 库可摇 = ESM 输出 + 副作用声明 + 子路径

库构建的产物结构:

dist/
  index.esm.js    (ESM 全量,可摇)
  index.cjs.js    (CJS 兼容)
  button.esm.js   (子路径模块)
  use-modal.esm.js
  index.d.ts      (类型)
→ 产物 = ESM 为主 + CJS 兼容 + 子路径 + dts

库构建的配置(Vite lib 模式):

// vite.config.ts(库模式)
import { defineConfig } from 'vite'
export default defineConfig({
  build: {
    lib: {
      entry: 'src/index.ts',
      formats: ['es', 'cjs']
    },
    rollupOptions: {
      // 外置依赖:不打包 React 等 peer 依赖
      external: ['react', 'react-dom']
    }
  }
})

库作者的副作用纪律:

- 顶层只做「纯声明」(无全局执行)
- 初始化放函数内(使用时执行)
- 全局 polyfill 独立文件(副作用列表声明)
- 导出函数保持纯(无隐藏外部状态)
→ 副作用纪律 = 让消费者「能摇你的库」

子路径与 dts 的配合:

{
  "exports": {
    ".": { "import": "./dist/index.esm.js", "types": "./dist/index.d.ts" },
    "./button": {
      "import": "./dist/button.esm.js",
      "types": "./dist/button.d.ts"
    }
  }
}
// 子路径 + 对应类型声明

验证自己库的可摇性:

- 写个最小消费者:只 import 一个导出
- 构建并看产物:未用导出是否被删
- 检查 sideEffects 是否生效(CSS 保留、函数删除)
- 检查子路径能否独立构建
→ 自测 = 最小消费者 + 产物检查

心智:库作者的可摇清单:输出 ESM(exports.import 指向 .esm)、sideEffects: false(纯库)/数组(含 CSS)、子路径 exports 分模块(./button)、避免顶层副作用、外置 peer 依赖;lib 模式 build 用 formats [’es’,‘cjs’] + external;验证 = 最小消费者只 import 一个导出、构建看未用导出是否删除、CSS 是否保留、子路径能否独立构建。


8. 与代码分割的配合

摇树 + 代码分割的组合:

- 摇树:删除「未用」的导出(模块内部瘦身)
- 代码分割:把「不同加载时机」的模块分开(chunk)
- 配合:先摇树(去掉死代码)再分割(分 chunks)
→ 摇树是「删」,分割是「分」,两者互补

分割让摇树更有效:

- 动态导入 → 独立 chunk(首包不载)
- 公共依赖提取 → 共享 chunk(避免重复)
- 摇树后剩余代码再分 chunk(更精准)
- 首包 = 入口静态依赖(摇树后)最精简
→ 先摇后分 = 每个 chunk 都是「摇过的精华」

公共 chunk 与摇树:

// 多个页面共用的大依赖
import { hugeLib } from 'huge-lib'
// 页面 A、B 都用 → 提公共 chunk
// 摇树后:只用到的导出进公共 chunk

按路由分割 + 摇树的实践:

// React 路由懒加载 → 每路由一个 chunk
const About = lazy(() => import('./pages/About'))
// About chunk 内部摇树:未用导出删除
// 首包只有:入口 + 公共 chunk(无 About 代码)

分割的策略选择:

- 全部分割:chunk 多、请求多(HTTP 开销)
- 大依赖独立:库单独 chunk(缓存友好)
- 动态导入必分割:懒加载要求
- 手动控制:manualChunks 自定义
→ 分割策略 = 加载性能与请求数的平衡

配合的陷阱:

- 过度分割:几十个小 chunk(请求爆炸)
- 公共提取过度:首包引入公共大库(违背懒加载)
- 摇树 + 分割顺序:先摇后分(否则死代码进 chunk)
- tree-shaking 与 sideEffects 在分割后仍生效(Rollup 全局分析)
→ 陷阱 = 分割粒度与公共提取的平衡

心智:摇树是「删死代码」、分割是「分加载时机」,互补组合;先摇后分让每个 chunk 都是摇过的精华;动态导入必分割 + 内部摇树、公共依赖提取共享 chunk、首包只含入口静态依赖;陷阱:过度分割(请求爆炸)、公共提取过度(首包带大库);Rollup 全局分析让摇树在分割后仍生效。


9. 验证摇树效果:产物分析

验证工具:

- rollup-plugin-visualizer:产物可视化(包体占比)
- vite-plugin-inspect:转换后代码(看摇前/摇后)
- vite build --report:构建报告(产物统计)
- source-map-explorer:按 sourcemap 分析包体
→ 验证 = visualizer + inspect + report 三件套

visualizer 的读法:

# 安装并配置
npm i -D rollup-plugin-visualizer
// vite.config.ts
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
  plugins: [visualizer({ open: true })]
})
// 构建后生成 stats.html:按模块/按 chunk 看占比

inspect 的读法:

- 访问 /__inspect 查看每个模块的转换链
- 对比「导入前源码」与「转换后」:看是否被摇
- 查看模块的 deps(导入图)确认依赖关系
- 大模块定位:哪个文件占了大头
→ inspect = 代码级摇树验证

构建报告指标:

- 总产物大小 / chunk 数 / 最大 chunk
- 首包大小(入口相关 chunk 和)
- 重复模块(同一依赖多 chunk)
- 每个 chunk 的来源(入口/动态/公共)
→ 报告 = 包体健康度的「体检单」

验证摇树的标准检查:

1. 只 import 一个导出 → 构建看体积
   (预期:显著小于全量)
2. visualizer 看最大的 chunk 是否含「未用的大库」
3. inspect 看未用导出是否在转换后消失
4. 对比「改 import 前后」的体积差(A/B 验证)
→ 验证 = 最小用例 + 可视化 + 代码级 + A/B

包体预算(性能门禁):

// CI 里检查包体(超预算即失败)
// 用 vite build + 自定义脚本解析产物大小
"scripts": {
  "check-bundle": "vite build && node scripts/check-size.mjs"
}
// 阈值:首包 < 200KB(gzip)、总包 < 1MB

心智:验证三件套:visualizer(产物可视化占比)、inspect(转换链看摇前摇后)、build –report(产物统计);标准检查:只 import 一个导出看体积、visualizer 找未用大库、inspect 确认未用导出消失、A/B 对比 import 前后;包体预算 CI 门禁(首包阈值 + 超限失败)让摇树成为可持续实践。


10. 速查表与一句话记忆

全篇速查:

主题结论
原理标记使用 + 删除未用
前提ESM 静态可分析
副作用sideEffects 字段声明可摇性
入口exports.import / module 指向 ESM
边界动态/运行时/条件/副作用不可全摇
常见失败副作用/CJS/barrel/黑魔法/CSS
库作者ESM 输出 + sideEffects + 子路径
配合先摇后分,每 chunk 摇过的精华
验证visualizer + inspect + A/B
门禁首包预算 CI 检查

一句话记忆:Tree-shaking = 从入口沿 import 追踪使用、删除未用导出的自动「按需引入」(标记+清扫两步),前提是 ESM 静态可分析(CommonJS 运行时 require 摇不动,只能手动子路径按需);副作用是最大黑箱——模块「导入即执行且影响外部」时打包器保守保留,靠 package.json 的 sideEffects 字段声明可摇性(false = 全可摇、数组 = 仅列表文件保留副作用,CSS/polyfill 声明于此,坑是声明与实际错配);包的可摇性由入口决定:exports.import / module 指向 ESM(CJS 入口则整个依赖进产物),现代包用 exports 条件入口并可用子路径导出(./button)实现「路径级按需」比具名导出更精确;摇树边界:动态导入 = 分包 + 内部摇(glob 枚举候选 import.meta.glob)、运行时路径/条件导入全保留、副作用模块合法保留;五大失败与排查:副作用误判(标 sideEffects)、CJS 入口(换 ESM 包)、barrel re-export 摇不动(改子路径导入)、黑魔法动态访问(重写静态)、CSS 被误删(sideEffects 数组);库作者清单:ESM 输出 + sideEffects + 子路径 + 外置 peer 依赖,lib 模式 formats [’es’,‘cjs’];与分割配合 = 先摇后分让每个 chunk 都是摇过的精华;验证 = visualizer(占比)+ inspect(转换链)+ A/B 对比 import 前后,配首包预算 CI 门禁让摇树成为可持续实践。


延伸阅读

  • /vite-build-optimization/ — 构建优化与代码分割
  • /vite-import-graph-internals/ — 导入图与依赖追踪
  • /vite-rollup-build-pipeline/ — 构建管线与产物结构
  • /vite-bundle-analysis-performance/ — 包体分析与性能监控
  • 前端工程专题 — 前端工程化与构建优化

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「vite」更多文章

  1. 包体分析与性能监控:Bundle Analyzer、性能预算与门禁
  2. 组件库开发指南:Vite 库模式、发布 npm 与按需加载
  3. React 应用架构模式:目录结构、状态管理与性能优化