工程化、测试与性能 · KNOWLEDGE CHAPTER
K23

构建与包交付

构建是把源码图变成可交付资源图的一条流水线。每一站只提供自己的保证:类型检查回答约束是否成立,转译回答语法能否执行,打包决定依赖与分块,发布则保证某一版本引用的资源仍然存在。某一站成功,不能代替整条链路的验收。

工程化、测试与性能2 道章节练习10 道主练关联

核心模型

构建是把源码图变成可交付资源图的一条流水线。每一站只提供自己的保证:类型检查回答约束是否成立,转译回答语法能否执行,打包决定依赖与分块,发布则保证某一版本引用的资源仍然存在。某一站成功,不能代替整条链路的验收。

原理与机制

从文本到可执行图

源文件先被解析为 AST,转换器通过遍历改写类型语法、JSX 或目标环境不支持的语法,再生成 JavaScript 与 source map。Babel 的 TypeScript 转换不做完整类型检查,Vite 也把全项目检查留给 tsc --noEmit;Vue 模板需相应的 vue-tsc。语法转换与运行时能力又是两层:箭头函数能降级,Promise、Map 或浏览器 API 是否存在要靠目标环境、明确配置的 polyfill 或降级策略保证。CI 应等待每项检查并传播非零退出码,外部 JSON 仍须运行时校验。

开发服务器与插件的切入点

模块图的边表示导入关系。典型 Vite 开发服务按浏览器 ESM 请求转换应用模块,依赖另作预处理;生产构建才完整组织发布资源。插件的 resolveId、load、transform 分别认领模块、供应内容、转换内容,因此虚拟模块应在前两个阶段提供。开发期没有 moduleParsed,也不执行除 closeBundle 外的产物生成 hook;只在 generateBundle 里造模块会出现 build 成功、serve 失败。apply 和 enforce 只能限制场景与次序,不能补出不存在的生命周期。

热更新是有边界的状态迁移

文件变化使模块图中的相关节点失效,更新沿导入关系传播,直到可接受更新的边界;客户端加载新模块并由框架或 accept 回调完成替换。边界不能安全处理导出变化时,应继续失效或整页刷新。旧模块创建的监听器、计时器和连接不会因源码替换自然消失,dispose 必须清理。HMR 保留多少状态取决于契约,它既不是浏览器缓存,也不能证明生产加载快。

删除代码与交付接口

静态 ESM 便于分析,但未使用导出可删,不代表模块无需执行。CSS 导入、自动注册和全局补丁都有副作用,sideEffects 应准确保留它们。分块则改变执行时机:按路由和业务边界拆分,并行发现代码与数据,避免把每个组件拆成串行瀑布。组件库真正交付的是 tarball:检查 exports、类型入口、样式资源、peerDependencies 和构建 external;peer 声明本身不会阻止重复打入 React。以最小消费者矩阵验证,不让仓库 alias 掩盖缺文件。

发布资源也有生命周期

VITE_ 变量通常在构建时进入客户端产物,是公开配置而非保密机制;字符串需解析,服务器改环境不会改旧 JS。同一产物跨环境可先读取版本化公开配置。入口 HTML 与哈希分块要一致发布,旧资源保留兼容窗口。懒路由失败先看 URL、状态码与版本,再提示保存草稿后刷新,并限制刷新次数。新增 exports 也可能封闭历史子路径,应纳入版本迁移,而不只检查主入口能否运行。

最小示例

javascript
javascript
// Vite 插件:开发请求和生产构建共用虚拟模块
const publicId = 'virtual:app-info';
const internalId = '\0' + publicId;
export default {
  name: 'app-info',
  resolveId(id) { if (id === publicId) return internalId; },
  load(id) {
    if (id === internalId)
      return 'export const buildLabel = "teaching"';
  }
};

消费端导入 virtual:app-info 时,两种模式都能经过 resolveId→load 得到源码。把 load 改为只在 generateBundle 生成文件,开发请求就失去内容供应。此例只演示公开常量;实际配置应校验并绝不嵌入服务端密钥。

核验:Node v24.19.0:模块语法检查及虚拟ID解析、源码供应、无关ID断言通过;未运行Vite开发/HMR/生产构建集成。

边界与取舍

边界

  • 构建通过但类型检查失败完全可能;旧浏览器能解析产物,也仍可能缺 Promise。
  • 自动注册包误标 sideEffects:false 可能连样式一起删掉;ESM 格式不是体积承诺。
  • 首次导航变慢可能来自过度拆包;旧标签页报 chunk 404 也不能靠无限重试修好。

取舍

  • 细粒度分块减少初始下载,却增加发现、请求与调度成本;按用户路径衡量总字节和可操作时间。
  • 保留旧资源与兼容入口增加存储维护成本,却为长会话和回滚争取恢复窗口。

口述示范

我会先画源码到产物的阶段和模块图,分别验证类型、转译、运行时与包接口。再解释更新传播和副作用清理,最后用实际消费者及旧页面跨发布测试证明交付成立。

章节练习

练习 1

一个组件库单元测试通过,发布后样式消失且 React 重复,怎样建立最小证据链?

展开检查点
  • 使用发布 tarball 创建消费者,而非源码别名。
  • 检查 exports、types、CSS 副作用和框架 external;同时断言样式、运行时和包体。
练习 2

插件只在 build 正常,线上部署后旧页面懒路由又失败,两类故障怎样分别定位?

展开检查点
  • 开发阶段检查 resolveId/load/transform 与 hook 生命周期。
  • 线上保留旧页再发布,核对旧分块、草稿恢复和刷新上限;分别验证 tsc 关卡。

参考来源与核验边界

2026-10-04 当前 Vite 文档使用 Rolldown/Oxc;旧版“开发 esbuild、生产 Rollup”不能作为所有版本的定义。实现需对照项目锁定版本,示例未覆盖 HMR 虚拟模块主动失效。

内容版本:2026-10-04.2 · 题目和来源 ID 保持原样,本站不将面经标签解释为企业官方出题或高频保证。