阅读视图

发现新文章,点击刷新页面。

别再写改名脚本了,一个 Vite 插件搞定压缩、校验、自动哈希命名vite-plugin-pack-orchestrator

📦 Vite 构建压缩插件:vite-plugin-pack-orchestrator

🤔 为什么又造一个轮子?

市面上已经有一些 Vite 打包插件,比如 vite-plugin-zip-packvite-plugin-compress 等,能用,但总差那么点意思 — 大多只支持 ZIP,功能也比较单一。

实际项目里,打包这个环节往往没那么简单:

  1. 多种压缩格式 🗜️ — ZIP 方便分享给同事,TAR.GZ 部署到 Linux 服务器,7Z 追求更高压缩比存档归档,不同场景需要不同格式
  2. 文件校验 🔐 — 打包后需要 MD5/SHA1 校验值来确认版本一致性,尤其是发布给客户的场景
  3. 灵活命名 ✏️ — 版本号、时间戳、哈希值,文件名里能带的信息越多越好
  4. CI/CD 友好 🚀 — 流水线里每次构建产物都应该是唯一可追溯的,压缩后自动带哈希改名,省去人工处理的麻烦(写脚本去改也麻烦一些)

现有插件基本没法同时满足这些,所以写了 vite-plugin-pack-orchestrator

⚡ 和其他插件有什么不同

功能 大多数打包插件 本插件
压缩格式 仅 ZIP ZIP / TAR / TAR.GZ / 7Z (RAR需要商业授权,所以没搞)
校验和 MD5 / SHA1 / SHA256
文件名模板 固定命名 支持 [name] [version] [timestamp] [hash] 占位符
Hook 扩展 onBeforeBuild / onAfterBuild / onError 等钩子
文件过滤 部分支持 include + exclude glob 模式
7Z 支持 需要系统安装 7z 内置,零依赖
输出目录控制 固定位置 archiveOutDir 自定义

📥 安装

npm install vite-plugin-pack-orchestrator

🚀 快速上手

最基本的用法,两行配置搞定:

// vite.config.ts
import { defineConfig } from 'vite';
import orchestrator from 'vite-plugin-pack-orchestrator';

export default defineConfig({
  plugins: [
    orchestrator({
      pack: {
        outDir: 'dist',          // 要打包的目录,默认就是 'dist'
        format: 'zip',           // 压缩格式:zip | tar | tar.gz | 7z
        fileName: 'myapp',       // 压缩包文件名
      },
    }),
  ],
  build: { outDir: 'dist' },
});

执行 vite build 后,会在项目根目录生成 myapp.zip

⚙️ 配置项详解

pack — 打包配置

pack: {
  outDir: 'dist',              // 要打包的源目录(相对于项目根目录),默认 'dist'
  fileName: 'myapp',           // 文件名,支持占位符(见下方说明)
  format: 'zip',               // 压缩格式:'zip' | 'tar' | 'tar.gz' | '7z'
  compressionLevel: 9,         // 压缩级别 0-9,默认 9(最高压缩率)
  archiveOutDir: './releases', // 压缩包输出目录,不写默认项目根目录
  exclude: ['**/*.map'],       // 排除的文件(glob 匹配)
  include: ['**/*.js'],        // 只包含的文件(可选,不设置则包含全部)
}

fileName 占位符

文件名支持以下占位符,打包时自动替换:

占位符 说明 示例值
[name] package.json 中的 name my-awesome-app
[version] package.json 中的 version 1.2.0
[timestamp] 当前时间戳 1714012345678
[hash] 构建内容 MD5 哈希(完整 32 位) a1b2c3d4e5f6...
[hash:8] MD5 哈希前 N 位(自定义长度) a1b2c3d4
// 示例:fileName 设为 'release-[version]-[timestamp]'
// 输出:release-1.2.0-1714012345678.zip

// 示例:fileName 设为 '[name]-v[version]'
// 输出:my-awesome-app-v1.2.0.zip

// 示例:fileName 设为 '[name]-[hash]'
// 输出:my-awesome-app-a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6.zip

// 示例:fileName 设为 '[name]-[hash:8]'
// 输出:my-awesome-app-a1b2c3d4.zip

如果 fileName 不包含扩展名,插件会根据 format 自动追加 .zip.tar.gz 等后缀。

🔗 hooks — 钩子函数

onBeforeBuild — 构建前执行

在 Vite 开始打包之前执行,适合做一些前置清理工作:

hooks: {
  onBeforeBuild: async () => {
    // 构建前的一些处理
  },
}
onBundleGenerated — bundle 生成后执行

Vite bundle 生成后、压缩包创建前执行,可以拿到构建产物信息:

hooks: {
  onBundleGenerated: (bundle) => {
    console.log('生成的文件:', Object.keys(bundle));
  },
}
onAfterBuild — 压缩完成后执行(核心)

这是本插件最强大的功能。 压缩包创建完成后,插件会自动计算 MD5 / SHA1 / SHA256 三种校验和,然后传给 onAfterBuild。你可以利用这些校验和来重命名压缩包

返回一个新路径(和原路径不同),插件会自动重命名文件:

hooks: {
  onAfterBuild: (path, format, checksums) => {
    // path      — 当前压缩包的完整路径
    // format    — 压缩格式('zip' | 'tar' | 'tar.gz' | '7z')
    // checksums — 校验和对象:{ md5: string, sha1: string, sha256: string }
    return path; // 返回原路径则不重命名
  },
}

实际案例:

// 案例 1:在扩展名前插入 SHA1 短哈希(最常用)
// myapp.zip → myapp-3a7b2c1d.zip
onAfterBuild: (path, format, checksums) =>
  path.replace(/(\.(?:zip|tar\.gz|tar|7z))$/, `-${checksums.sha1.slice(0, 8)}$1`);

// 案例 2:用 MD5 全量替换文件名
// myapp.zip → a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6.zip
onAfterBuild: (path, format, checksums) =>
  path.replace(/^.+(?=\.\w+$)/, checksums.md5);

// 案例 3:追加格式和哈希到原始文件名
// myapp.zip → myapp-zip-a1b2c3d4.zip
onAfterBuild: (path, format, checksums) =>
  path.replace(/(\.\w+)$/, `-${format}-${checksums.sha256.slice(0, 8)}$1`);

// 案例 4:完全自定义文件名,用 format 参数自动适配后缀
// myapp.zip → release-a1b2c3d4e5f6.zip
onAfterBuild: (path, format, checksums) =>
  `release-${checksums.md5.slice(0, 12)}.${format}`;

// 案例 5:不重命名,只是拿校验和做点其他事(比如写入文件)
onAfterBuild: async (path, format, checksums) => {
  fs.writeFileSync('checksums.json', JSON.stringify(checksums));
  // 不 return 或 return 原路径 = 不重命名
}
onError — 出错时执行

打包失败时回调,适合接入告警通知:

hooks: {
  onError: async (error) => {
    console.error('打包出错了:', error.message);
    // 可以在这里接入钉钉/企业微信告警
  },
}

🔄 为什么说压缩后自动改名对 CI/CD 很重要?

在持续集成/持续部署的流水线中,每次构建的产物都需要是唯一可追溯的。如果压缩包文件名固定叫 dist.zip,你怎么知道这次构建和上次有什么区别?回滚的时候该拿哪个版本?

本插件通过 onAfterBuild 钩子拿到校验和后,可以自动在文件名中插入哈希值:

hooks: {
  onAfterBuild: (path, format, checksums) =>
    path.replace(/(\.zip)$/, `-${checksums.sha1.slice(0, 8)}$1`);
}

构建后输出:

myapp-1.0.2-3a7b2c1d.zip
myapp-1.0.2-7f9e4b2a.zip

文件名本身就是指纹 🔑,一眼就能区分不同构建,部署脚本直接按文件名定位版本,不需要额外维护版本映射表。回滚也简单 — 找到上一个哈希文件名部署即可。配合 [version] [timestamp] 占位符,追溯性更强。

🎯 完整示例

把前面的配置合在一起,就是一个完整的生产级配置:

// vite.config.ts
import { defineConfig } from 'vite';
import orchestrator from 'vite-plugin-pack-orchestrator';

export default defineConfig({
  plugins: [
    orchestrator({
      pack: {
        outDir: 'dist',                    // 打包 dist 目录
        fileName: 'myapp-[version]',       // 文件名带版本号
        format: 'zip',                     // ZIP 格式
        archiveOutDir: './releases',       // 输出到 releases 目录
        exclude: ['**/*.map'],             // 排除 sourcemap
      },
      hooks: {
        // 压缩完成后自动加上 SHA1 哈希
        onAfterBuild: (path, format, checksums) =>
          path.replace(/(\.(?:zip|tar\.gz|tar|7z))$/, `-${checksums.sha1.slice(0, 8)}$1`),
        // 出错时打印日志
        onError: (error) => console.error('打包失败:', error.message),
      },
    }),
  ],
  build: { outDir: 'dist' },
});

vite build 一次搞定,不需要额外的打包脚本。


插件很轻量,代码开源,欢迎试用和提建议 🎉

ba30da3a555101d1dd06ef37b59c22d8.jpg

Webpack vs Vite:核心差异、选型建议

Vite 与 Webpack 深度对比:特性、短板与选型建议

1. 为什么需要前端构建工具?

现代前端开发中,我们常常使用 TypeScript、SCSS、JSX 等非原生语法,以及 npm 包、图片、字体等多种资源。浏览器无法直接运行这些内容,因此需要构建工具进行转译、打包、优化。Webpack 和 Vite 是目前最主流的两款构建工具,它们代表了两种不同的构建哲学。

简单说:构建工具帮助我们把“高级代码”变成浏览器能懂的代码,还能自动处理文件依赖、压缩体积、提供开发服务器(热更新)。没有它,我们就要手动做很多重复工作。

2. 核心差异速览

维度 Webpack Vite
构建方式 全量打包(bundle) 开发时按需编译 + 生产打包
启动速度 随项目增大而变慢 极快(几乎与项目规模无关)
热更新 需重新构建变化模块 基于 ESM 的即时 HMR
配置复杂度 较高,需要配置 loader/plugin 开箱即用,配置简洁
生产优化 成熟强大(代码分割、Tree Shaking) 基于 Rollup,基本够用
生态 海量 loader/plugin 兼容 Rollup 插件,逐渐丰富
旧浏览器支持 通过 polyfill 可兼容 IE11 需额外配置(如 @vitejs/plugin-legacy

热更新的定义:热更新就是修改代码后,不刷新页面直接更新模块,保持页面状态。Vite 的热更新比 Webpack 更快,因为它是基于浏览器的原生 ES Module 按需替换。

3. Webpack:功能强大的模块打包器

3.1 核心理念

Webpack 将所有资源(JS、CSS、图片等)视为模块,从入口开始递归构建依赖图,最终打包成一个或多个 bundle 文件。它强调配置化可扩展性

3.2 工作流程

  1. 读取 webpack.config.js 配置。
  2. 从入口(entry)开始,通过 loader 转换非 JS 文件。
  3. 使用 plugin 在构建生命周期中执行任务(如生成 HTML、压缩代码)。
  4. 输出打包后的文件到 dist 目录。

如下图:

3.3 关键配置示例

// webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  mode: 'development',        // 或 'production'
  entry: './src/index.js',    // 入口
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true               // 每次打包前清空 dist
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']   // 顺序:从右到左
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-env']
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({ template: './src/index.html' })
  ],
  devServer: {
    port: 8080,
    hot: true,
    open: true
  }
}

3.4 优势

  • 打包能力全面:支持几乎所有资源类型,通过 loader 体系无限扩展。
  • 生态丰富:有大量官方和社区 plugin,能满足各种复杂需求(如代码分割、资源内联、PWA 等)。
  • 生产优化成熟:Tree Shaking、代码分割、缓存控制等经过多年考验。
  • 高度可定制:几乎可以控制构建的每一个环节。

3.5 局限性

问题 说明
开发启动慢 每次启动都需要全量打包,项目越大越慢
热更新慢 修改代码后需要重新编译受影响的模块,大项目可能等待数秒
配置复杂 新手容易迷失在 loader 和 plugin 的组合中,配置错误难以排查
生产构建耗时长 大型项目打包可能耗时数分钟

4. Vite:面向现代浏览器的极速构建工具

4.1 核心理念

Vite 利用浏览器原生 ES Module 支持,在开发环境下不打包,直接按需编译请求的模块;生产环境则使用 Rollup 进行打包。它强调开发体验优先

4.2 工作流程

  1. 启动开发服务器,预构建依赖(使用 esbuild,极快)。
  2. 浏览器请求 main.js 时,Vite 拦截请求,实时编译 Vue/JSX/TS 等文件。
  3. 返回浏览器可执行的 ES Module 代码。
  4. 生产构建时调用 Rollup 打包,并进行优化。 如下图:

image.png

4.3 配置示例

// vite.config.js
import { defineConfig } from 'vite'
import legacy from '@vitejs/plugin-legacy'

export default defineConfig({
  plugins: [
    legacy({ targets: ['ie 11'] })   // 可选:兼容旧浏览器
  ],
  server: {
    port: 5173,
    open: true,
    proxy: { '/api': 'http://localhost:3000' }
  },
  build: {
    outDir: 'dist',
    sourcemap: true
  }
})

4.4 优势

  • 极速启动:无需打包,启动时间与项目规模无关,通常小于 1 秒。
  • 即时的热更新:基于 ESM 的 HMR 非常快,修改后浏览器几乎瞬间更新。
  • 开箱即用:支持 TypeScript、CSS、静态资源等,无需配置 loader。
  • 配置简洁:API 设计清晰,上手门槛低。
  • 现代工具链:使用 esbuild 预构建依赖,速度极快。

4.5 局限性

问题 说明
生产优化不如 Webpack 对于极大型项目,Vite 的打包结果可能比 Webpack 略大或优化不够细致
旧浏览器兼容麻烦 依赖 ES Module,要支持 IE11 必须引入 @vitejs/plugin-legacy,会增加构建复杂度
生态相对年轻 虽然兼容 Rollup 插件,但部分 Webpack 专属插件(如某些针对特殊资源的 loader)无法直接使用
开发环境与生产环境行为不一致 开发时使用 esbuild 转译,生产时使用 Rollup,可能导致细微差异

5. 如何选择?

5.1 适合 Webpack 的场景

  • 项目历史悠久,已经使用了大量 Webpack 专属插件(如某些特殊 loader)。
  • 需要极度精细的生产环境优化(如微前端架构、自定义代码分割策略)。
  • 团队对 Webpack 非常熟悉,迁移成本高。
  • 需要兼容非常古老的浏览器(如 IE11)且不希望额外配置。

5.2 适合 Vite 的场景

  • 新项目,希望快速启动和热更新,提升开发效率。
  • 使用 Vue 3 / React 18 + 现代浏览器为目标。
  • 项目以现代 JavaScript 为主,不依赖太多 Webpack 特有功能。
  • 希望配置简单,降低新手维护成本。

目前 Vite 已是 Vue 官方推荐工具,并广泛应用于 React、Svelte 等生态。对于绝大多数新项目,Vite 是更高效的选择。

6. 总结

维度 Webpack Vite
核心哲学 模块化打包,高度可控 开发体验优先,按需编译
启动速度 ⭐⭐ ⭐⭐⭐⭐⭐
热更新速度 ⭐⭐ ⭐⭐⭐⭐⭐
生产优化能力 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐
配置复杂度 ⭐⭐⭐⭐
生态丰富度 ⭐⭐⭐⭐⭐ ⭐⭐⭐
最佳适用 大型复杂项目、遗留系统 新项目、现代浏览器应用

如果你追求极致的开发体验和快速启动,选 Vite;如果你需要极度精细的生产优化和丰富的生态,或者维护老项目,继续用 Webpack。两者并非互斥,也可以根据项目模块逐步迁移。


7. 面试常见问题与回答思路

Q1:什么是构建工具?为什么需要它?

参考答案
“构建工具可以帮助我们把现代前端代码(比如 TypeScript、JSX、SCSS)转译成浏览器能识别的 JavaScript、CSS,同时还能合并文件、压缩代码、处理图片等。它可以自动化很多重复工作,提升开发效率,并且提供开发服务器支持热更新。”

Q2:Vite 和 Webpack 的核心区别是什么?

参考答案(抓住两点即可):
“Webpack 在开发模式下会全量打包整个项目,所以项目越大启动越慢;而 Vite 利用浏览器原生 ES Module,开发时不打包,只按需编译请求的文件,因此启动非常快,热更新也更快。另外,Webpack 配置复杂但生态成熟,Vite 开箱即用但生产优化略逊一筹。”

Q3:你用过 Vite 或 Webpack 吗?怎么搭建一个简单的 Vite 项目?

参考答案
“用过 Vite。搭建非常简单,只需要三行命令:

npm create vite@latest my-app
cd my-app
npm install
npm run dev

启动后就能看到页面。Vite 默认支持 CSS、TypeScript、静态资源等,不用额外配置。”

Q4:如果让你选一个构建工具,你会选哪个?为什么?

参考答案
“如果是新项目,我会选 Vite。因为它启动快、热更新快、配置简单,能显著提高开发效率,而且 Vue/React 官方都推荐。但如果项目需要兼容 IE11,或者已经用了很多 Webpack 特有插件,那我会选 Webpack。”

Q5:Vite 和 Webpack 在生产环境打包上有什么区别?

参考答案
“Webpack 的生产优化更成熟,比如代码分割的策略更精细,Tree Shaking 效果更好,适合大型复杂项目。Vite 生产环境使用 Rollup 打包,基本够用,但对于极大型项目可能打包结果略大或优化不够细致。”

Q6:你遇到过 Vite 或 Webpack 的问题吗?怎么解决的?

参考答案(如果没有实际遇到过,可以这样说):
“我用 Vite 时遇到过端口被占用的问题,通过配置 server.port 改成其他端口解决。另外,Vite 默认不支持 IE11,如果需要兼容,要安装 @vitejs/plugin-legacy 插件。”

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

前言

在前端性能优化中,减小 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:从打包流程到插件钩子执行时序的全链路解析

前言

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 的打包逻辑执行。
❌