CI/CD 与工具链开发

共 28 题
📑 题目列表 28 题
#
★★★

1. Vite 插件钩子(config/configResolved/transformIndexHtml/transform/handleHotUpdate/closeBundle)的执行时机与 enforce(pre/post)排序

Vite 插件钩子(config/configResolved/transformIndexHtml/transform/handleHotUpdate/closeBundle)的执行时机是什么?enforce(pre/post)如何排序?

  • 各钩子的生命周期阶段(配置、转换、HTML、HMR、构建结束)
  • enforce: 'pre'/'post' 与默认顺序的排序机制
  • 钩子执行顺序对插件行为的影响(转换链、优先级)

Vite 插件钩子分布在构建与开发的全生命周期:config(读取配置前,可修改配置对象)、configResolved(配置解析完成后,只读快照)、configureServer(dev server 创建时)、transformIndexHtml(HTML 转换,可注入脚本/标签)、resolveId/load/transform(模块解析与转换链——dev 与 build 共用)、handleHotUpdate(HMR 更新时,可自定义热更内容)、closeBundle(build 结束,清理/产物后处理)。执行时机决定插件"在哪一步干预",如 transformIndexHtml 注入 polyfill 标签、handleHotUpdate 自定义热更边界、closeBundle 做产物收尾。

enforce 排序:插件在数组中默认按注册顺序执行(resolveId/load/transform 等链式钩子),enforce: 'pre' 把插件排到"默认之前"(核心转换之前)、'post' 排到默认之后(核心转换之后)——典型用法:pre 用于别名/特殊语法转换(先于通用转换)、post 用于后处理(压缩、产物修正);排序影响链式结果(transform 依次叠加),错误顺序会导致"转换后语法不再匹配"或"后处理被后续插件覆盖"。工程注意点:HMR 相关钩子只在 dev 生效(build 无 HMR)、closeBundle 在 build 生效(dev 中对应 server close)、钩子的执行次数(build 多入口时 transform 可能多次)与"幂等性"(插件处理需可重复)、以及 enforce 对"非链式钩子"(config、closeBundle)无排序意义(仍按注册顺序)。

本题考察 Vite 插件生命周期的全局视图:钩子按阶段分布、enforce 控制链式顺序。回答应逐钩子说明时机、enforce 语义与排序陷阱。

#
★★★

2. Rollup/Vite 虚拟模块的实现(resolveId 返回 \0 前缀约定、load 提供内容)与 this.resolve 的用途

Rollup/Vite 的虚拟模块如何实现?\0 前缀约定与 this.resolve 的作用是什么?

  • 虚拟模块的两步实现(resolveId 拦截 + load 提供内容)
  • \0 前缀的"防泄漏"语义(不进入产物、不被打包)
  • this.resolve 的用途(插件内解析路径、触发依赖解析)

虚拟模块是"不存在于文件系统、由插件在内存中提供"的模块:第一步 resolveId——插件在解析阶段拦截特定 id(如 virtual:foo),返回"唯一的虚拟 id"(带 \0 前缀,如 \0virtual:foo),告诉打包器"这个 id 由我处理";第二步 load——打包器对该 id 调用 load 钩子,插件返回模块内容字符串(JS/JSON/任意可转换内容),后续再经 transform 处理;\0 前缀的语义:打包器约定以 \0 开头的 id 是"内部虚拟 id"——不会出现在产物文件的 import 语句中(避免"产物引用不存在文件")、不会被其他插件误解析(约定隔离)、不参与文件系统操作(watch、sourcemap 处理)——是虚拟模块的"防泄漏"协议。this.resolve 是插件上下文 API:在插件内部按打包器的解析规则解析一个路径(相对/别名/裸模块)到最终 id(可带 options 指定 skippe 等),用途:在 load/transform 中解析"依赖的依赖"(如 CSS 文件引用的图片)、构建"组合模块"(把一个 id 解析后读取其转换结果)、以及触发"依赖追踪"(让打包器把解析结果纳入依赖图,实现"导入清单"类虚拟模块)。

工程应用:虚拟模块用于"按需生成内容"(如 Vite 的 import.meta.glob 展开、按请求注入 env 模块、CSS 变量生成)、"隐藏中间层"(把构建期数据伪装成模块);注意:\0 id 不能被用户代码直接引用(会报错或被打包器忽略)、load 返回内容需是"有效模块"(可转换、可被解析)、this.resolve 需处理解析失败(返回 null 时的回退逻辑)。

本题考察插件核心机制的完整链路:解析拦截 → 内容提供 → 防泄漏协议 → 内部解析。回答应说明两步实现、\0 语义与 this.resolve 的典型用途。

#
★★★

3. Webpack loader 的 pitch 机制与 remainingRequest 执行顺序,pitch 提前返回如何跳过后续 loader

Webpack loader 的 pitch 机制是什么?remainingRequest 与执行顺序如何工作?pitch 提前返回如何跳过后续 loader?

  • loader 的正常执行顺序(从右到左)与 pitch 阶段(从左到右)
  • pitch 返回值的"短路"语义(跳过剩余 loader 与模块请求)
  • remainingRequest 与 loaderContext 的关键字段

Webpack loader 的完整执行分两个阶段:先按"从左到右"依次调用每个 loader 的 pitch 函数(同步可选),再按"从右到左"执行 loader 主体(normal 函数,把上一个 loader 的结果传入下一个);pitch 用于"在读取模块内容前"干预(如提前判断、拦截请求);关键点——pitch 如果返回值(非 undefined),则"短路":跳过剩余 loader 的 pitch 与所有尚未执行的 normal loader,webpack 直接把 pitch 返回值作为模块内容交给"已执行的 loader 链",且这个"跳过"使被处理的请求(原始模块)不再被读取,模块内容就是返回值。

remainingRequest 是 loaderContext 提供的请求字符串:表示"当前 loader 之后(右侧)还未执行的 loader + 资源路径",用于"把剩余请求重新转交"——典型场景是"内联 loader 链"(如 file-loader/url-loader 内部用 remainingRequest 把处理后的资源再交给后续 loader)与"pitch 中决定如何继续";loaderContext 还有 currentRequest(全部请求)、previousRequest(已执行的左侧 loader)、callback 机制(异步 loader 的 callback(null, result, sourceMap))等。工程理解:loader 顺序"先写先执行 pitch、后执行 normal",配置数组的顺序直接决定转换链方向(如 [css-loader, postcss-loader] 先 postcss 后 css 处理),pitch 短路是"提前结束管线"的机制(如"模块已内联,无需再处理"),也是实现"loader 间协作"(如 style-loader 用 pitch 把 CSS 转成 JS 注入)的基础。

本题考察 loader 管线的双阶段模型:pitch 从右到左/正向左到右与短路语义。回答应说明执行顺序、pitch 返回值效应与 remainingRequest 的协作用途。

#
★★★

4. Babel 插件的 visitor 与 AST 变换基本流程(path/node/scope 的操作与遍历)

Babel 插件的 visitor 与 AST 变换基本流程是什么?path、node、scope 如何操作?

  • 插件结构(visitor 挂载、进入/退出)与 AST 遍历模型
  • path(节点路径)的增删改、替换与作用域(scope)操作
  • 变换流程(parse → traverse → transform → generate)的插件视角

Babel 插件导出一个函数(接收 babel 类型工具,返回 { visitor }):visitor 按节点类型声明处理(如 Identifier(path)、CallExpression(path)),每个 visitor 支持 enter/exit 两阶段(enter 先序遍历、exit 后序遍历);遍历模型基于 path——path 是"节点在树中的位置"(含父链、scope、进行中状态),API:path.node(当前节点)、path.parentPath(父路径)、path.traverse(子遍历)、path.replaceWith/replaceWithMultiple(替换节点)、path.insertBefore/insertAfter(插入)、path.remove(删除)、path.isXxx(类型断言)、path.find/stop 等;node 是纯数据(AST 节点对象),通过 t(@babel/types)构造与断言(t.identifier、t.callExpression);scope 是作用域对象:path.scope 提供绑定(bindings:声明与引用的映射)、rename(重命名)、generateUid(生成唯一名)、getBinding/scope.getProgramParent 等,变换中处理"变量名冲突"必须通过 scope 而非裸字符串替换。

基本流程(插件视角):源文件 parse 成 AST → 插件 visitor 在遍历中按需读取/变换(查询节点 → 检查条件(类型、属性、scope 状态)→ 构造新节点(t.*)→ replaceWith/insert 并注册 scope 变更 → 必要时 path.skip/stop 控制遍历)→ 最终 generate 为代码(含 sourcemap);工程要点:变换必须保持"AST 合法"(新节点用 t 构造而非字符串)、名称引入需查重(scope.hasBinding 或 generateUid)、退出阶段适合"后置修正"(如依赖子节点处理结果)、插件幂等性(同 AST 跑两次结果一致)与"不破坏类型注释/装饰器"等边界。

本题考察 Babel 插件开发的模型:visitor 遍历、path 操作、scope 管理三位一体。回答应说明遍历两阶段、path/node API 层次与名称冲突的 scope 处理。

#
★★★

5. 蓝绿部署与金丝雀发布(Argo Rollouts、Spinnaker)

蓝绿部署与金丝雀发布的机制是什么?Argo Rollouts、Spinnaker 如何支持前端部署?

  • 蓝绿(全量切换)与金丝雀(渐进放量)的流量模型
  • 回滚机制(版本保留、流量回切)与验证策略
  • 前端静态产物的蓝绿/金丝雀实践(CDN、路由、灰度)

蓝绿部署:维护"蓝"(当前)与"绿"(新版本)两套完整环境,验证通过后把流量整体切换到绿(入口/负载均衡切换),失败立即切回蓝——优点是回滚快(纯流量切换,秒级)、新旧完全隔离;缺点是资源双倍(两套环境常驻)与"全量切换"(无渐进)。金丝雀部署:新版本先承接小比例流量(如 5%)逐步放大(10%→50%→100%),每个阶段观察指标(错误率、延迟、业务指标),异常即回切——优点是风险渐进、可基于真实流量验证;缺点是灰度时间窗内需"双版本并存"与流量分配设施。Argo Rollouts(Kubernetes):以 CRD 管理 Rollout(蓝绿/金丝雀策略声明、自动分阶段、分析器(Analysis)基于 Prometheus 等指标自动判定放量或回滚);Spinnaker:Netflix 开源的多云发布平台(pipeline 编排蓝绿/金丝雀、集成指标验证(Kayenta)与回滚)。

前端实践:静态资源版本化(contenthash 目录/文件名)后,"蓝绿"对应"两套版本产物共存 + HTML/路由指向切换"(CDN 版本目录切换、DNS/网关切流),"金丝雀"对应"按用户/地域/百分比灰度分发"(CDN 权重、边缘函数按 header/cookie 分流、特性开关配合);验证:金丝雀阶段监控前端指标(web vitals、错误率、转化)与后端联动;回滚:切回旧版本产物 + CDN 刷新 + 缓存治理(旧版本保留期)。前端特有的注意点:静态资源缓存(切流后旧缓存残留——版本化文件名 + 入口短缓存)、SSR/网关层流量一致性(同一用户会话命中同一版本)、SPA 路由与版本兼容。

本题考察发布策略的模型与工具:蓝绿"全量快切"、金丝雀"渐进放量"。回答应说明机制、工具支持与前端静态产物的落地(版本化、切流、验证、回滚)。

#
★★★

6. CI 缓存(actions/cache、Turbo 远程缓存、pnpm store)在构建加速的应用

CI 缓存(actions/cache、Turbo 远程缓存、pnpm store)如何应用到构建加速?缓存层如何设计?

  • 三类缓存的层次(依赖安装、任务产物、包管理器 store)
  • actions/cache 的 key/restore 策略与失效
  • 缓存命中率与正确性的平衡(环境变化、注入污染)

CI 缓存分三层:一是"依赖安装缓存"——包管理器 store(pnpm store、npm cache)缓存依赖包内容,安装时命中(锁文件未变)直接本地链接/复制,避免重复下载;用 actions/cache 以"lockfile hash"为 key 存取(restore-keys 支持降级命中);二是"构建任务缓存"——任务级缓存(Turborepo/Nx 本地 + 远程)按输入哈希复用构建/测试结果,跨 PR 共享;三是"产物/中间缓存"——其他可复用中间物(Playwright 浏览器、esbuild 缓存、编译缓存)按版本 key 缓存。actions/cache 的机制:key(精确命中)/restore-keys(前缀降级)+ 保存/恢复动作(@actions/cache),命中后跳过安装;Turbo 远程缓存(Vercel/自建)在"同一输入"下让不同 CI job 共享任务输出。

缓存设计要点:一是"key 语义"——依赖缓存 key 必须含 lockfile hash(依赖变化即失效)+ 平台(OS)+ 包管理器版本;任务缓存 key 由 Turbo 自动计算(输入哈希);二是"命中率与失效"——key 过宽(忽略 lockfile)导致"错误命中"(旧依赖)或污染,过窄(含时间戳)导致永不命中;restore-keys 提供"降级命中 + 部分复用";三是"正确性"——缓存环境差异(Windows/linux 不互通)、cache 污染(被篡改的缓存)风险(CI 中可对 store 做校验)、"缓存写入口"(仅主干可信 job 写、PR 只读防投毒);四是"容量与清理"——CI 缓存配额与淘汰(actions/cache 按 LRU)、大产物不入缓存;五是"分层验证"——缓存命中后构建产物仍需"正确性冒烟"(命中不代表可发布),发布链路用"无缓存全量"兜底。

本题考察 CI 缓存的三层架构:依赖、任务、中间物各司其职。回答应说明 actions/cache 的 key 策略、Turbo 远程缓存的共享模型与正确性/安全治理。

#
★★★

7. GitHub Actions 工作流与矩阵构建

GitHub Actions 工作流如何组织?矩阵构建(matrix)如何配置与治理?

  • 工作流结构(trigger、jobs、steps、conditions)与事件模型
  • 矩阵构建(strategy.matrix)的维度与 include/exclude
  • 成本治理(并发、超时、条件跳过)与复用(composite/reusable workflow)

GitHub Actions 工作流(.github/workflows/*.yml):triggers(on:push/PR/定时/手动 workflow_dispatch)、jobs(并行执行单元,含 needs 依赖、runs-on 环境、strategy、steps)、steps(命令/action 序列,含 if 条件、with 参数、env)、以及 secrets/env 注入与 artifact 传递(上传/下载);矩阵构建(strategy.matrix)让同一 job 以"维度组合"并行展开(如 os × node-version × 浏览器),每个组合一个实例(matrix.os、matrix.node-version 上下文变量),用 include(追加组合)与 exclude(排除组合)精确控制组合集;典型用法:多 Node 版本兼容矩阵、多平台(ubuntu/windows/macos)构建、多浏览器 E2E。

治理要点:一是"矩阵成本"——组合数爆炸(3×4×2=24 job)直接翻倍 CI 消耗:PR 用"最小矩阵"(单版本)、主干/发布用"全矩阵"分层;二是"条件与跳过"——paths 过滤(变更才触发)、if 条件(跳过特定组合,如 windows 跳过某测试)、concurrency(同一 PR 的新 run 取消旧的);三是"超时与失败策略"——timeout-minutes、continue-on-error(非关键组合失败不阻塞)、fail-fast 控制;四是"复用"——composite actions(自封装步骤组)与 reusable workflows(跨仓库复用工作流,caller/callee 参数化)减少复制;五是"安全"——secrets 最小权限(环境级 secrets、OIDC)、第三方 action 的版本锁定与审查(供应链)、权限声明(permissions 白名单)。

本题考察 CI 平台的工程化使用:工作流模型、矩阵维度与成本治理。回答应说明结构、矩阵机制与分层矩阵、复用、安全治理。

#
★★★

8. Dependabot/Renovate 在依赖自动 PR 与版本策略的工程价值

Dependabot 与 Renovate 在依赖自动 PR 与版本策略上有什么工程价值?如何治理?

  • 自动升级 PR 的机制(漏洞修复优先、版本更新调度)
  • 版本策略(分组、范围、自动合并条件)与升级节奏
  • 自动升级的安全治理(验证门禁、人工 review 边界、失败回退)

Dependabot(GitHub 内置)自动为依赖生成升级 PR:security updates(漏洞修复,高优先级,自动触发)与 version updates(定期调度:daily/weekly/monthly,按目录扫描 package.json 等),PR 带 changelog/漏洞说明,通过配置(dependabot.yml:package-ecosystem、schedule、ignore、open-pull-requests-limit)治理;Renovate 更可编程:schedule(时间窗)、group(把多依赖升级合并一个 PR,如"所有 patch")、rangeStrategy(bump 策略:replace/pin/update-lockfile)、auto-merge 条件、dependencyDashboard(人工接管面板)、多平台(GitHub/GitLab/自托管)与自定义规则(regex manager 管理非标准依赖)。

工程价值:一是"漏洞响应自动化"——安全更新不等人(依赖漏洞发布即 PR),配合审计门禁形成"检测 → PR → 合并"闭环;二是"升级节奏可控"——按 schedule/group 治理升级噪声(每天大量 PR vs 每周聚合 PR),避免"升级轰炸"淹没 review;三是"版本策略落地"——rangeStrategy 与 pin 策略统一(库 pin 精确版本、应用 ^range + lockfile 更新),major 升级单独 PR(需人工);治理要点:一是"验证门禁"——每个升级 PR 必须过 CI(构建 + 测试),关键依赖(框架、打包器、TS)major 升级人工 review(影响面大),可配"升级失败自动标记/回退";二是"噪声治理"——group 与 ignore 规则(忽略不升级的包)、依赖预算(限制同时打开的升级 PR);三是"供应链安全"——升级 PR 的包来源校验(信誉、provenance)、与 lockfile 变更审查联动,防止"升级掩盖投毒";四是"与发布流程"——组件库的依赖升级与 changesets 协作(升级可能触发包版本变更)。

本题考察依赖自动化的工程化:安全优先、节奏可控、验证门禁。回答应对比 Dependabot/Renovate 的机制、版本策略落地与治理要点。

#
★★★

9. GitHub Actions 的矩阵策略(matrix.os/node-version)

GitHub Actions 的矩阵策略(matrix.os/node-version)如何配置?有什么工程实践?

  • matrix 的定义(os、node-version、组合展开)与上下文
  • include/exclude 的精细控制与动态矩阵
  • 矩阵的验证价值(多平台、多版本兼容)与成本控制

矩阵配置:strategy.matrix 声明维度(os: [ubuntu-latest, windows-latest]、node-version: [18, 20, 22]),GitHub 展开为"笛卡尔组合"的并行 job(每个组合一个实例,matrix.os/matrix.node-version 在步骤中引用);include 追加额外组合(含非标准字段,如 browser)、exclude 排除特定组合(如 windows × node 18 的已知不兼容);job 内用 setup-node 的 node-version: ${{ matrix.node-version }} 切换版本;组合还可从 JSON 输出动态生成(fromJSON,配合前置 job 计算需要验证的组合——按变更决定矩阵项)。

工程实践:一是"分层矩阵"——PR 用精简矩阵(如 ubuntu × 当前 Node 版本),主干/发布用全矩阵(多 OS × 多版本),避免每个 PR 全矩阵的昂贵验证;二是"矩阵语义"——OS 矩阵验证跨平台(路径、换行、原生模块差异)、Node 版本矩阵验证运行时兼容(API 变化、引擎要求)、浏览器矩阵验证 E2E 兼容(chromium/firefox/webkit);三是"失败策略"——fail-fast: false(组合独立成败,看全量结果)、continue-on-error(标记非关键组合(如实验版本)不阻塞);四是"成本治理"——组合数 = 笛卡尔积,需用 exclude/include 控制实际执行集、并发限制(max-parallel)、超时;五是"矩阵与缓存"——矩阵组合的缓存 key 需含组合上下文(os + node-version),否则跨组合错误复用;六是"可观测"——矩阵 job 命名(name 模板含组合值)与汇总(merge 结果 job)提升报告可读性。

本题考察矩阵构建的配置与治理:组合展开模型、include/exclude 控制与分层策略。回答应说明语法、验证价值(多平台兼容)与成本控制。

#
★★★

10. 前端特性开关(Feature Flag)体系设计(客户端 SDK、开关评估、清理机制)与灰度发布的协作

前端特性开关(Feature Flag)体系如何设计?客户端 SDK、开关评估、清理机制如何工作?与灰度发布如何协作?

  • 特性开关的类型(发布开关、实验开关、权限开关)与粒度
  • 客户端 SDK 的评估模型(远程开关拉取、本地缓存、默认值)
  • 清理机制(到期、代码死分支)与灰度发布协作

特性开关体系分层:一是"类型与粒度"——release flag(控制功能上线,灰度用)、experiment flag(A/B 实验,分配实验组)、permission flag(按用户权限)与 kill switch(紧急熔断);粒度按"功能/组件/API"声明,命名规范(scope-name 模式);二是"开关存储与评估"——远端开关配置(Feature Management 平台/自建服务)下发(SSR 注入或客户端拉取),客户端 SDK 本地缓存开关值与评估规则(按用户/属性/百分比),评估在"运行时"进行(代码分支 if (flag) ),必须提供"默认值"(服务不可达时回退),并支持"启动前阻塞拉取"(避免闪烁)与"运行时更新"(无刷新切换,配合轮询/推送);三是"安全"——客户端开关可被绕过(源码可见),敏感功能需"服务端强制校验"(客户端开关只控 UI,服务端开关控权限)。

清理机制:开关是"技术债"——功能稳定后需清理(删除分支 + 移除开关引用 + 删除远端配置),治理:开关注册表(记录 owner、到期时间、关联工单)、过期提醒(配置平台扫描到期 flag)、清理门禁(发布前检查"新增开关的清理计划")、死代码检测(flag 恒真时 lint 提示);与灰度发布协作:特性开关是"应用层灰度"(按用户/环境控制功能可见),部署灰度是"基础设施层"(流量百分比切到新版本)——两者配合:新功能"代码先上线(开关关)→ 开关灰度开放(按比例/白名单)→ 全量开放 → 清理开关",实现"部署与发布解耦"(代码发布不意味着功能上线),降低发布风险并支持快速回退(关开关即回滚功能)。

本题考察特性开关的体系化设计:类型、SDK 评估、清理与灰度协作。回答应覆盖开关分类、客户端评估模型(默认值、防闪烁)、清理治理与"部署发布解耦"的协作模式。

#
★★★

11. Docker 多阶段构建在前端产物(Node 编译 + Nginx 服务)

Docker 多阶段构建如何用于前端产物?Node 编译阶段与 Nginx 服务阶段如何设计?

  • 多阶段构建(builder 编译 + runner 运行)的镜像瘦身原理
  • 前端镜像的构建层缓存(依赖层、源码层分离)
  • Nginx 配置(SPA 路由、缓存头、gzip)与产物部署

Docker 多阶段构建让"编译环境与运行环境分离":第一阶段(builder)基于 node 镜像执行构建(npm ci/install → build → 产出 dist),第二阶段(runner)基于 nginx 镜像只拷贝 dist 与 nginx 配置——最终镜像不包含 node、源码与依赖(体积从 GB 级降到几十 MB),同时减少攻击面;关键设计:一是"层缓存"——依赖安装层(COPY package.json + lockfile → npm ci)与源码层(COPY .)分开(依赖层复用需 lockfile 不变),构建产物层(COPY --from=builder /app/dist)与配置层分离,CI 缓存与镜像层缓存叠加加速;二是"构建参数"——构建期环境(API 地址、版本号)通过 ARG/ENV 注入(构建期配置 vs 运行时配置的取舍:静态化进产物 vs 运行时注入模板);三是"安全"——node 基础镜像用官方 slim/alpine、依赖锁文件安装(防投毒)、容器以非 root 运行(nginx 用户)、产物只读挂载。

Nginx 服务设计:SPA 路由回退(try_files $uri /index.html,history 路由刷新 404 问题)、静态资源缓存头(contenthash 资源 immutable 长缓存、HTML no-cache)、gzip/brotli(静态压缩或动态模块)、安全头(CSP、X-Frame-Options)、以及健康检查(/healthz)与日志;部署配合:镜像 tag(commit hash 可追溯)、多版本保留(蓝绿/回滚)、以及"SSR 变体"(若 SSR 服务用 node 镜像跑 server 产物,阶段设计相应调整:builder 编译 + runner 运行 server)。

本题考察前端容器化的标准形态:多阶段瘦身、层缓存、Nginx 与安全。回答应说明两阶段职责、缓存分层与 Nginx 的关键配置点。

#
★★★

12. 构建产物 checksum 与部署可追溯

构建产物的 checksum 如何用于部署可追溯?机制与工程实践是什么?

  • 产物 checksum 的生成(内容哈希、清单文件)与指纹
  • 部署可追溯(版本 ↔ 产物 ↔ 源码 ↔ 环境)的关联链
  • 实践(记录、验证、回滚依据、审计)

构建产物 checksum(内容哈希)是"产物指纹":构建后对每个产物文件(或整体)计算 hash(sha256 等),生成清单(manifest:文件名 ↔ hash ↔ 构建元信息),产物文件名本身可含 contenthash(静态资源)或清单记录(整体包);其价值在于"内容即身份"——同一 checksum 必是同一产物,部署/缓存/校验都以它为凭据。部署可追溯的关联链:commit(源码版本)→ 构建(CI job、环境、依赖 lockfile 版本)→ 产物(checksum)→ 镜像/部署(tag 含 commit 或 checksum)→ 运行时(版本探针/产物内嵌版本号),任一环节可反查:线上版本出问题 → 查部署记录 → 定位产物 checksum → 溯源 commit 与构建环境。

工程实践:一是"记录"——CI 生成并归档 checksum 清单(构建产物 + 元数据,存 CI 工件/制品库)、镜像 tag 用 commit/checksum(可读且可溯)、部署记录(时间、环境、checksum、操作人)入库;二是"验证"——部署前校验产物完整性(下载校验 checksum,防传输损坏/篡改)、运行时版本探针(/version 接口或页面标记暴露当前 checksum,用于线上核对);三是"回滚依据"——保留多版本产物(历史 checksum 可重建/可下载),回滚 = 部署"目标 checksum"的版本,验证"回滚后的 checksum 与记录一致";四是"审计"——发布审计(谁在何时部署了什么 checksum)、合规追踪(产物与源码对应关系)、以及"供应链延伸"(产物 checksum 与依赖 lockfile/SBOM 关联,完整追溯依赖来源)。

本题考察发布可信度工程:checksum 建立"产物身份",追溯链连接源码到运行时。回答应说明指纹机制、追溯链设计与记录/验证/回滚实践。

#
★★★

13. Vercel、Netlify、Cloudflare Pages 的部署集成

Vercel、Netlify、Cloudflare Pages 的部署集成如何选型?它们的能力与边界是什么?

  • 三家平台的定位(托管部署、边缘、Serverless、静态/SSR)
  • 部署集成(Git 连接、构建配置、预览部署、回滚)
  • 按项目类型(纯静态、SSR、边缘)的选型与迁移

三家都是"现代前端部署平台":Vercel——与 Next.js 深度绑定(其 SSR/ISR/边缘能力官方支持),部署模型:Git 连接自动构建(框架预设自动识别),提供预览部署(每个 PR 生成预览 URL)、生产部署、即时回滚(版本历史一键回退)、边缘网络(全球 CDN + 边缘函数)、环境变量/保护(密码、SSO)与监控分析(Speed Insights、Web Analytics),优势是 SSR 生态(Next/Nuxt 等框架适配最佳)与开发者体验;Netlify——老牌 JAMstack 平台:静态托管 + 表单/身份/函数(Netlify Functions 基于 AWS Lambda)、分支部署(branch deploy)、split testing(A/B 流量)、回滚与 CDN,配置 via netlify.toml,生态成熟但边缘能力弱于 Vercel/CF;Cloudflare Pages——基于 Cloudflare 全球网络:静态站点 + Functions(Workers 兼容)、预览部署、回滚(版本快照)、免费额度高,与 Cloudflare 全家桶(DNS、CDN、WAF、R2)集成,边缘性能好且"接近零延迟",但 SSR 支持需 Workers 生态适配(或 Pages 的 SSR 特性,有限)。

选型:纯静态/文档站——三家皆可(CF 免费额度与速度、Netlify 功能全);Next.js 等框架 SSR/ISR——Vercel 最顺(官方优化);需要边缘函数与全球 CDN 性能——Cloudflare;已有 AWS 或需自托管——可考虑"Git 集成 + 静态托管"组合或自建(GitHub Pages/自建 CDN)。工程实践:无论哪家,部署集成要点一致——Git 触发(main 生产 + PR 预览)、构建命令与产物目录声明(平台构建)、环境变量管理(分环境)、回滚流程(版本历史验证)、预览部署的权限保护(敏感项目)、以及"多平台并行评估"(同项目三家对比构建/部署/性能再做决定,避免迁移成本)。

本题考察托管平台的选型框架:框架生态、边缘能力、部署体验是三维度。回答应对比三家定位与边界,给出按项目类型的选型逻辑与集成要点。

#
★★

14. Container Registry 与 Artifact 供应链

Container Registry 与 Artifact 的供应链如何治理?工程实践有哪些?

  • Registry/制品库的定位(镜像与包/产物存储)与访问控制
  • 供应链环节(构建、签名、扫描、拉取校验)的治理
  • 制品准入与可追溯(SBOM、签名、策略)

Container Registry(Docker Hub、GHCR、ECR、自建 Harbor 等)存储镜像,Artifact 仓库(npm 私服、Nexus、Artifactory)存储包与构建产物——两者都是"供应链的存储与分发环节":治理目标是"从构建到消费全程可信"。环节治理:一是"构建来源"——镜像/包必须由可信 CI 构建(构建器身份可验证,如 Sigstore 的构建身份绑定)、禁止手工构建上传;二是"签名"——镜像(cosign)与包(npm provenance)签名,注册表记录签名,拉取/安装时校验(供应链防投毒的关键);三是"扫描"——镜像(Trivy 集成 registry 扫描)与制品(audit/OSV)入库即扫描,漏洞策略(高危阻塞拉取/部署);四是"访问控制"——registry 权限模型(组织/仓库级)、拉取认证(私有镜像)、写权限最小化(只有 CI 可推送);五是"可追溯"——镜像 tag 用 commit/checksum 不可变(避免 latest 漂移)、镜像 digest 记录部署、制品与 SBOM 关联(镜像内嵌 SBOM、registry 展示)。

工程实践:一是"准入策略"——部署门禁:未签名/高危漏洞/无 SBOM 的镜像拒绝部署(registry 策略引擎或 CI 校验);二是"私服策略"——npm 私服做"依赖镜像与白名单"(公有包缓存 + 内部包发布),锁外部源(registry.npmjs.org 代理 + 审计);三是"生命周期"——制品保留策略(版本保留数、漏洞后撤回(yank)流程)、过期清理;四是"审计"——推送/拉取日志、签名验证失败告警、供应链事件响应(镜像漏洞爆出后按 digest 反查部署面);五是"供应链文件"——镜像/包关联 SBOM 与 provenance,形成"制品 → 来源 → 依赖"完整链条。

本题考察制品供应链的治理闭环:存储分发、签名扫描、准入策略与可追溯。回答应覆盖环节治理(构建、签名、扫描、校验)与工程实践(准入、私服、生命周期)。

#
★★

15. CI 构建缓存(GitHub Actions actions/cache、CircleCI cache)

GitHub Actions 的 actions/cache 与 CircleCI 的 cache 如何用于 CI 构建缓存?策略有何异同?

  • actions/cache 的 key/restore-keys 恢复模型与保存时机
  • CircleCI 的 save_cache/restore_cache 与 workspace/依赖缓存
  • 缓存命中率优化(key 设计、部分命中)与失效治理

GitHub Actions 的 actions/cache:job 中调用 action 恢复(key/restore-keys 查找)与保存(post 步骤自动保存,key 写入新缓存条目),key 设计决定命中:精确 key(如 lockfile hash)命中则跳过安装、restore-keys 前缀实现"部分命中"(如 npm- 匹配最近一次同前缀缓存),保存只在"key 未命中"时写入(避免覆盖);缓存范围:actions/cache 缓存任意路径(依赖目录、构建产物、浏览器),GitHub 托管缓存(同仓库、按分支/环境隔离,配额 LRU 淘汰);CircleCI:restore_cache(keys 列表 + 首个命中)与 save_cache(按 key 保存,可带路径),另有 workspace(job 间传递文件,构建产物衔接)与"依赖缓存 + 源缓存"的经典组合;CircleCI 的 cache 保存也只在未命中时执行(避免覆盖已有缓存)。

异同:恢复模型一致(key 精确 + 前缀降级);差异在"作用域与配额"(GitHub 按仓库+分支隔离、CircleCI 按项目,均 LRU)、保存时机(GitHub 的 post 自动保存 vs CircleCI 的显式 save_cache 步骤)、以及生态集成(GitHub 与 Actions 原生;CircleCI 与 orb)。优化实践:一是"key 分层"——"精确 key(lockfile hash)→ 降级 key(包管理器版本前缀)→ 兜底",实现"完全命中/部分命中/重建"三级;二是"内容选择"——只缓存"稳定且大"的目录(node_modules/store、playwright 浏览器、构建缓存),时间戳/临时目录不入缓存;三是"命中验证"——命中率监控(对比缓存命中/未命中的构建时长)、缓存健康(损坏缓存用 clear 清除);四是"安全"——缓存内容不可信(可能被注入),恢复后校验(如依赖完整性)、PR 分支缓存的可信度(写入口限制)。

本题考察 CI 缓存机制的平台对比:恢复模型、保存语义与 key 策略。回答应说明两者的异同、三级 key 设计与命中治理。

#
★★

16. GitHub Actions 的 concurrency/timeout/matrix 在 CI 成本治理的应用

GitHub Actions 的 concurrency、timeout、matrix 如何用于 CI 成本治理?

  • concurrency(并发取消策略:group、cancel-in-progress)与冗余构建控制
  • timeout-minutes(超时兜底)与 job 耗时治理
  • matrix 的规模控制与分层(成本与覆盖的平衡)

CI 成本治理的核心是"花掉的构建时间最小化":concurrency——声明并发组(group:如分支名、PR 号)并设置 cancel-in-progress: true:同一组内新 run 启动时取消旧的进行中 run,避免"同一 PR 多次推送产生多轮重复构建"(每轮都花钱)——对高频推送的开发流节省显著;也可用 concurrency 限制"同时运行的 run 数"(资源配额)。timeout——每 job 设 timeout-minutes(默认 6h 过长),构建挂起/死锁时兜底终止(不再计费/占用),超时即失败(排查"为什么超时");对矩阵组合统一超时;timeout 还能暴露"慢 job"(耗时治理对象:拆分、缓存、并行)。matrix——成本与覆盖的平衡:组合数是成本乘数(每个组合一个 job),治理:PR 用精简矩阵(单 OS 单版本)、主干/发布全矩阵;组合内用 include 精确追加(非笛卡尔全量)、排除已知不兼容组合;必要时"动态矩阵"(按变更计算组合集)。

组合治理实践:一是"分层流水线"——快速层(lint/单测,小矩阵)先跑,全量层(E2E、多平台)只在通过后/发布前跑,避免"PR 一进来就全矩阵重活";二是"计费优化"——选择便宜的 runner(linux 优先、大内存按需)、自托管 runner 成本评估、矩阵的 max-parallel 控制峰值;三是"监控"——CI 耗时报告(每 job 时长、矩阵总时长)、成本看板(月度 CI 分钟数)、超时/取消率监控;四是"门禁分层"——PR 门禁(快而准:变更相关)与发布门禁(全而严:完整矩阵)分离,用 affected/过滤让 PR 只跑必要 job。

本题考察 CI 成本的三板斧:concurrency 消除冗余、timeout 兜底挂起、matrix 控制规模。回答应说明机制与分层流水线、监控治理。

#
★★

17. SRI(Subresource Integrity)的原理(hash 校验)与在 CDN 第三方资源上的应用与失效场景

SRI(Subresource Integrity)的原理是什么?在 CDN 第三方资源上如何应用?失效场景有哪些?

  • SRI 的 hash 校验机制(integrity 属性、CORS 前提、算法)
  • 第三方 CDN 资源(库、字体、统计脚本)的 SRI 应用
  • 失效场景(资源更新未同步、CORS 缺失、动态生成)与处理

SRI(Subresource Integrity)通过

本题考察 SRI 的机制与应用边界:hash 校验、CORS 前提、第三方应用与失效治理。回答应说明原理、应用场景与"更新未同步""CORS 缺失"等失效处理。

#
★★

18. 前端静态产物的版本化部署与快速回滚机制(多版本保留、HTML 指向切换、CDN 刷新)

前端静态产物的版本化部署与快速回滚机制如何设计?多版本保留、HTML 指向切换、CDN 刷新如何协作?

  • 版本化产物(目录/文件名含版本)与多版本共存
  • HTML 入口指向切换(回滚的本质是"切入口")
  • CDN 缓存刷新与新旧版本缓存的一致性治理

前端静态产物的版本化部署核心:"产物不可变 + 入口可变"——每个版本构建产物放入"版本化目录"(如 /releases/v20260804-abc123/ 或文件名带 contenthash),历史版本完整保留(多版本共存,支撑回滚与灰度);HTML(index.html)是"入口",其引用指向"当前版本目录"——发布 = 更新 HTML 指向新版本目录,回滚 = 把 HTML 指向切回旧版本目录(秒级,无需重新构建),这是"快速回滚"的本质:静态资源不可变(version 目录不变)、入口可变(指向切换);配合"部署记录"(版本 ↔ 目录 ↔ commit ↔ checksum)让回滚决策有据。

CDN 刷新协作:新版本目录是新 URL(CDN 无缓存,直接回源新内容);回滚切回旧目录时,旧目录的 URL 在 CDN 可能仍有旧缓存(与"上一版内容"相同则无害),但"HTML 入口"本身需要缓存治理——HTML 必须短缓存(no-cache 或极短 max-age,配合 ETag 协商),保证"切入口"后用户尽快拿到新 HTML;若 HTML 也被 CDN 长缓存,回滚/发布会延迟生效,需"主动刷新"(CDN purge HTML 的 URL);资源缓存头:版本化资源 immutable 长缓存(URL 即版本)、HTML 协商缓存(no-cache);灰度场景:边缘函数/网关按权重或用户特征把"入口请求"分流到不同版本目录(金丝雀),回滚即调权。工程注意点:多版本保留的存储成本(保留策略:最近 N 个 + 长留 LTS 版本)、版本目录与 CSP/SRI 的一致性、SSR 场景(SSR 服务引用版本产物的路径一致)、以及"刷新时序"(先发布资源后切入口,避免入口指向未就绪目录)。

本题考察静态部署的版本化模型:"资源不可变、入口可变"与 CDN 缓存头治理。回答应说明多版本目录、入口切换回滚、缓存头与刷新协作。

#
★★

19. AI 辅助代码审查(API 幻觉、安全漏洞、性能)的工程治理

AI 辅助代码审查的工程治理如何设计?API 幻觉、安全漏洞、性能问题的边界怎么把握?

  • AI 审查的能力边界(风格、模式识别 vs 语义、上下文)
  • AI 审查的典型误报(API 幻觉:虚构 API/参数、过时 API 认知)
  • 治理框架(人机分工、规则约束、结果验证)

AI 辅助代码审查(如 Code Review 助手)的价值:快速识别风格问题、重复代码、常见反模式、测试缺失、文档缺失等"模式类"问题,并对变更做摘要(影响面、风险点),提升 review 效率;能力边界:AI 缺乏"完整项目上下文与真实运行验证"——对"业务语义正确性""性能真实影响(需基准)""安全漏洞的真实可利用性"判断不可靠,且存在"API 幻觉"(编造不存在的 API/参数/配置、对库的过时认知(如把已废弃 API 当新推荐)、编造 benchmark 结论),因此 AI 审查的产出是"提示"而非"结论"。

治理框架:一是"人机分工"——AI 做"第一遍粗筛"(格式、模式、清单核对),人工聚焦"语义与设计"(架构、业务正确性、安全权衡),AI 建议需人工验证后才采纳;二是"约束"——给 AI 明确规则(审查清单、禁止臆断 API 的指令(只基于代码库内定义/文档)、输出格式(问题 + 证据 + 严重级)),用"仓库上下文"(接入文档/类型定义)降低幻觉;三是"验证闭环"——AI 标记的安全/性能问题必须经"证据确认"(SAST 工具复验、性能基准、手动复现)才进入修复队列,未验证不直接修复(防"修错");四是"指标治理"——统计 AI 建议的采纳率与误报率(反馈机制让提示更准)、区分"确定性规则"(可自动修)与"建议性提示";五是"安全红线"——安全类结论(CVE、注入、敏感信息)走人工安全评审,AI 不决策;整体原则:"AI 提效不替代判断,证据驱动不猜测修复"。

本题考察 AI 审查的工程化:能力边界(模式 vs 语义)、幻觉风险与人机分工。回答应说明边界、幻觉治理与"证据验证"闭环。

#
★★

20. vitest --shard 在大测试集并行切片的工程价值

vitest --shard 如何在大测试集上做并行切片?工程价值与配置注意点是什么?

  • --shard 的分片模型(按测试文件分片、多机并行)
  • 与 CI 矩阵/分布式执行的配合(分片数、索引)
  • 分片的负载均衡与稳定性(慢文件、片间方差)

vitest --shard 把测试集按文件分片:--shard=2/4 表示"第 2 片 / 共 4 片",vitest 把测试文件划分到各片(默认按文件路径哈希/顺序分布),各片独立运行(互不依赖、各自报告);配合 CI 并行:矩阵或分布式 runner(GitHub Actions matrix × N、或自建并行任务)让每台机器跑一片,总耗时从"单机全量"降为"最大片耗时",测试规模大(千级用例、分钟级全量)时收益显著;vitest 还支持 --shard 的"分片间不重复"(每个文件只在一片),保证覆盖率汇总(合并 coverage 报告)。

工程价值:一是"墙钟时间线性下降"——N 台机器并行 ≈ 全量耗时/N(受最大片限制),CI 测试阶段从小时级降到分钟级;二是"资源弹性"——按 CI 预算选择分片数(分片数 = 并行度);三是"失败定位"——每片独立报告,失败片单独重跑(--shard 重跑失败片,而非全量)。注意点:一是"片间负载均衡"——文件数均分不意味时间均分(慢测试文件集中在一片会拖长总耗时):用"按文件耗时加权"或"随机种子分片(--shard-seed)"重试分散,或用 vitest 的慢文件提示优化;二是"共享资源隔离"——分片并行时测试共享的资源(数据库、端口、文件系统)需隔离(每片独立环境),否则并行引入不稳定;三是"顺序依赖"——测试间存在顺序/全局状态依赖时,分片会打破原有顺序暴露问题(应修复为隔离的测试而非合并分片);四是"与缓存/覆盖率协作"——分片运行需合并覆盖率(coverage 配置 all + 汇总)、分片结果聚合到统一报告。

本题考察大测试集的并行化:分片模型、CI 配合与负载均衡。回答应说明 --shard 机制、价值(墙钟下降)与片间方差、资源隔离等注意点。

#
★★

21. Rspack 自定义 loader 的 JS 实现与 Rust(builtin-loader)实现的边界与迁移成本

Rspack 自定义 loader 的 JS 实现与 Rust(builtin-loader)实现有什么边界?迁移成本如何评估?

  • JS loader(兼容 webpack API)与 builtin loader(Rust 实现)的差异
  • 迁移的触发条件(性能瓶颈)与适配面(API、异步、上下文)
  • 迁移成本的评估(等价验证、双实现共存、渐进迁移)

Rspack 支持两类自定义 loader:JS loader——与 webpack loader API 兼容(this.getOptions、callback、pitch 等),通过 JS 运行时执行,使用与 webpack 几乎一致(迁移成本低、生态兼容),但性能受 JS 桥接开销限制;builtin loader(内置/原生 loader)——用 Rust 实现(或在 JS 中声明但核心在 Rust 管线执行,如内置的 swc-loader、lightningcss-loader),直接嵌入 Rspack 的 Rust 编译管线,性能最优(无 JS 桥接、可并行、少 GC 停顿),但开发门槛高(需 Rust/napi-rs)且 API 语义(上下文、异步时机)与 JS loader 有差异。边界:JS loader 适合"功能型/生态复用型"(多数场景足够快),builtin loader 适合"性能瓶颈型"(大规模转换、高频执行的核心 loader)。

迁移成本评估:一是"触发条件"——先用 profiling 确认 loader 是构建瓶颈(占比高)再迁移,非瓶颈迁移无收益;二是"适配面"——JS loader 的 this 上下文(getOptions、getLogger、async 回调、pitch 语义)在 Rust 实现中的等价面逐一核对(哪些 API 支持、哪些行为差异,如 sourceMap 传递、异步处理);三是"等价验证"——双实现共存期:同一输入分别跑 JS/Rust 版本对比输出(产物 diff + 用例测试),保证语义等价(loader 输出、错误行为、sourcemap);四是"渐进迁移"——先"JS 兜底 + Rust 主路径"(按文件/规则分流)、验证稳定后全量切换;五是"维护成本"——Rust loader 需编译链(napi-rs 构建、平台二进制)与团队 Rust 能力,长期维护成本高于 JS,迁移决策需计入"人力成本",避免"为性能牺牲可维护性"。

本题考察 loader 性能化的工程决策:两类实现的机制差异、迁移触发条件与等价验证。回答应说明边界(性能 vs 门槛)、评估流程(profiling 触发、等价验证、渐进切换)。

#
★★

22. raw loader(接收 Buffer)与内联资源导入(inline resource import)的应用场景

raw loader(接收 Buffer)与内联资源导入(inline resource import)的应用场景是什么?

  • raw loader 的语义(以字符串/Buffer 输出文件内容)
  • 内联资源导入(webpack 的 inline syntax、?raw 查询)的应用
  • 资源导入方式的选型(文本、二进制、构建期数据)

raw loader 把文件内容以原始字符串输出(raw-loader 或 webpack 5 的内置 asset/source 类型):导入一个 .txt/.svg/任何文本文件时得到其内容字符串,适合"把静态文本嵌入代码"(模板字符串、SQL、SVG 源码、配置文件片段),避免运行时 fetch(离线可用、无请求开销);Buffer 语义:raw loader 也可接收 Buffer(二进制资源原样保留,如自定义格式的二进制数据、图标字节流),webpack 5 的 asset modules 按需选择(asset/source 输出字符串、asset/inline 输出 base64 data URL、asset 自动选择);"内联资源导入"(inline resource import)指"在导入语句中直接指定加载方式"——webpack 的 inline syntax(!!、!、-! 前缀控制 loader 链,如 require('!!raw-loader!./data.txt'))与 Vite 的查询参数(import text from './data.txt?raw'、?url、?inline),让"每个导入点"选择处理方式而不必全局配置 loader。

应用场景:一是"构建期数据"——把"构建时才知道、运行时固定"的数据(版本号、配置快照、静态文档)以文本形式嵌入产物;二是"避免运行时 IO"——小文本资源(图标、模板)内联进 bundle(减少请求),大资源用 url/文件(避免 bundle 膨胀);三是"工具链协作"——把 raw 内容交给后续 loader(如把 SVG 字符串传入组件处理);选型:文本/配置类用 ?raw/asset-source(字符串语义)、二进制/小图用 asset/inline(data URL)、大资源走文件 + 版本化 URL;注意:raw 导入绕过打包器的资源处理(不经 CSS/url 优化)、内容变更影响产物 hash(缓存)、敏感内容内联的风险(源码可见)。内联语法的治理:优先用"统一配置 + 查询参数"(显式、可搜索),避免散布 inline 前缀魔法。

本题考察资源导入的原始模式:raw 语义、内联导入语法与应用选型。回答应说明两种机制、典型场景(嵌入文本/数据、免 IO)与治理注意点。

#
★★

23. Rollup 插件与 Vite 插件的兼容边界(Rollup 兼容钩子与 Vite 独有钩子的差异)

Rollup 插件与 Vite 插件的兼容边界是什么?Rollup 兼容钩子与 Vite 独有钩子有何差异?

  • 共享的 Rollup 钩子(resolveId/load/transform/renderChunk 等)与兼容前提
  • Vite 独有钩子(config/configResolved/transformIndexHtml/configureServer/handleHotUpdate)与平台能力
  • 插件兼容的实践(use 条件、enforce、build/dev 差异)

Vite 的插件体系建立在 Rollup 插件 API 之上:resolveId、load、transform、buildStart、buildEnd、renderChunk、generateBundle 等"Rollup 兼容钩子"在 Vite 中行为与 Rollup 一致(build 阶段完全复用 Rollup 管线),因此"纯 Rollup 插件"通常可直接用于 Vite(需满足前提:不依赖 Rollup 私有 API、不假设文件系统行为(虚拟模块需处理 dev 差异)、输出钩子(renderChunk/generateBundle)只在 build 生效);差异在"环境"——Vite 的 dev 模式用"原生 ESM + 按需转换",没有 Rollup 的完整打包流程(无 renderChunk、无 bundle 级操作),因此"依赖打包期产物"的插件在 dev 不生效(dev/build 行为分裂的常见来源)。

Vite 独有钩子提供"Vite 平台能力":config(修改 Vite 配置)、configResolved(读最终配置)、configureServer(定制 dev server:中间件、拦截请求)、transformIndexHtml(转换 HTML)、handleHotUpdate(定制 HMR)、load/transform 的 Vite 变体(dev 中同步 API 与 id 解析差异);这些钩子只存在于 Vite,Rollup 插件无法使用。兼容实践:一是"判断环境"——用 configResolved 的 command(serve/build)与 apply('serve'|'build'|函数)控制插件生效范围;二是"enforce 排序"——pre/post 调整在转换链的位置(Rollup 没有 enforce 概念);三是"虚拟模块的 dev/build 一致"——虚拟模块在 dev(serve 时按需 load)与 build(打包)都要可用,需自测两模式;四是"API 差异"——this.resolve/this.load 的选项、模块 id 的规范化(\0 前缀处理)在 Vite 中的细节差异(如 load 返回类型、异步时机),跨平台插件需在 Vite 中回归测试。

本题考察插件双平台兼容的边界:共享钩子(build 侧一致)、Vite 独有钩子(平台能力)、dev/build 行为分裂。回答应说明钩子分层、独有能力与兼容实践。

#
★★

24. Webpack compiler/compilation 钩子的开发方式与 Tapable 同步/异步钩子的实现原理

Webpack compiler/compilation 钩子如何开发?Tapable 的同步/异步钩子实现原理是什么?

  • compiler/compilation 生命周期钩子(beforeRun/emit/afterEmit 等)与插件注册
  • Tapable 的钩子类型(Sync/AsyncSeries/AsyncParallel/Waterfall/Bail)与调用语义
  • 钩子机制的实现原理(tap 注册、调用链、熔断与串并行)

Webpack 插件通过"钩子(hooks)"介入编译生命周期:compiler 对象持有全局钩子(environment、afterEnvironment、beforeRun、run、compile、make、emit、afterEmit、done、watchRun 等),compilation 对象持有编译期内钩子(buildModule、succeedModule、optimizeChunks、seal、beforeChunkAssets 等);插件在 apply(compiler) 中注册(compiler.hooks.emit.tap('name', fn) / tapAsync / tapPromise),对应钩子在生命周期到达时被触发——"emit"在产物写入前(可修改产物)、"done"在构建完成;compilation 是"本次编译"的上下文(模块图、chunk、assets),钩子接收相应参数(compilation、chunk、module 等),插件通过钩子"在正确时机做正确修改"。

Tapable 的实现原理:钩子由"类型"决定调用语义——SyncHook(同步顺序调用,无返回值传递)、SyncBailHook(任一返回值非 undefined 即短路停止)、SyncWaterfallHook(前一返回值作为后一入参,瀑布传递)、AsyncSeriesHook(异步串行:前一个完成(callback/promise)才调下一个)、AsyncParallelHook(异步并行,全部完成才结束)、AsyncSeriesWaterfall 等;实现上:tap 把"监听函数注册进钩子",call/callAsync/promise 触发时按类型生成/执行调用链(webpack 用"编译函数"(new Function)把监听器拼接为高效调用代码,而非逐个循环调用——性能关键),异步钩子依赖回调或 Promise 串行/汇合;理解钩子语义是"工具链插件开发"的基础:选错类型(如需要短路却用 SyncHook)导致行为错误;工程注意:钩子参数与文档核对(版本演进中钩子可能改名/加参)、注册时机(某些钩子只在特定流程触发)、异步钩子的 callback 必须调用(不调则构建挂起)与错误传递。

本题考察 webpack 插件体系的内核:生命周期钩子分布与 Tapable 类型语义。回答应说明 compiler/compilation 钩子用法、Sync/Async/Waterfall/Bail 语义与编译函数实现原理。

#
★★

25. esbuild 插件(onResolve/onLoad/setup)的开发流程及其在 Vite 依赖预构建中的作用与局限

esbuild 插件(onResolve/onLoad/setup)如何开发?在 Vite 依赖预构建中有什么作用与局限?

  • 插件结构(setup、onResolve、onLoad)与回调语义(filter、namespace)
  • 虚拟模块与解析定制的插件实现
  • 在 Vite 预构建中的应用(esbuildOptions.plugins)与局限(API 有限)

esbuild 插件是"解析与加载阶段的定制钩子":插件导出 setup(build) 函数,在 setup 中注册 onResolve(解析拦截:匹配 filter(正则)的导入路径 → 返回 path/namespace/external 等解析结果或 null 放行)与 onLoad(加载拦截:对指定 namespace 的模块返回 contents/loader/resolveDir 等);namespace 是"模块来源标记"(file: 文件、虚拟 namespace 由插件持有),支持虚拟模块(onLoad 返回内存内容);还有 onStart/onEnd(生命周期)与 build 上下文(resolve 方法在插件内解析路径)。开发流程:声明 filter(注意性能:filter 是字符串匹配,太宽会拦截所有导入影响性能,宜精确)、用 namespace 隔离插件模块、onLoad 中按 loader 返回内容(js/ts/json/text 等)。

在 Vite 依赖预构建中的作用:Vite 用 esbuild 打包依赖时可通过 optimizeDeps.esbuildOptions.plugins 注入插件——用途:处理特殊格式的依赖(wasm、特殊扩展名)、修改依赖的解析(external 化某些包、重定向版本)、注入 define(替换依赖内全局)、定制 CJS 转换行为;典型场景是"让 esbuild 理解依赖中的非标准语法/替换特定实现"。局限:一是"API 有限"——esbuild 插件体系比 webpack/Rollup 简单(无完整的产物级钩子、无 renderChunk 类后处理、模块图信息少(无 getModuleInfo 类 API)),复杂转换(依赖其他模块信息)难实现;二是"预构建边界"——esbuildOptions 只影响预构建(dev 依赖打包),生产构建(Rollup/Rolldown)不执行这些插件(需在两者配置中重复/同步,易漂移);三是"性能"——插件回调是 JS 侧开销(预构建的 esbuild 原生快会被插件拖慢)、filter 过宽拖累所有解析;四是"语义差异"——依赖内部 require 的动态行为、条件导出细节在 esbuild 转换下的近似处理,插件难以完全纠正(需 exclude + 原生加载兜底)。

本题考察 esbuild 插件机制与其在 Vite 预构建中的角色:onResolve/onLoad 是核心、预构建注入是应用、API 有限与 dev/build 漂移是局限。回答应说明开发流程与作用局限。

#

26. Playwright 在 CI 中 E2E 测试与浏览器安装链路(playwright install --with-deps)

Playwright 在 CI 中如何做 E2E 测试?浏览器安装链路(playwright install --with-deps)如何设计?

  • Playwright 的浏览器二进制安装与版本匹配(browsers.json)
  • CI 中的安装链路(--with-deps 系统依赖、缓存、浏览器缓存)
  • E2E 在 CI 的分层(冒烟/全量、分片、重试)与稳定化

Playwright 的浏览器是独立二进制(chromium/firefox/webkit 按版本下载),版本由 playwright 包版本决定(browsers.json 锁定):本机/CI 需执行 playwright install 安装对应版本,安装内容含浏览器二进制 + 系统依赖(--with-deps 自动安装 Linux 系统库:libnss、libatk 等,基于 apt);CI 链路设计:一是"缓存"——浏览器二进制大(数百 MB),用 CI 缓存(actions/cache)按"playwright 版本 + OS"缓存 ~/.cache/ms-playwright,避免每次安装;二是"镜像/预装"——用官方镜像(mcr.microsoft.com/playwright)或自建预装镜像,跳过运行时装依赖(安装链路在镜像构建期完成);三是"版本一致性"——package.json 的 playwright 版本与 CI 镜像/缓存版本匹配(升级 playwright 需同步缓存 key),不匹配会触发重新安装或运行失败;四是"分片并行"——按测试文件分片(--shard)多机并行,缩短 E2E 墙钟时间。

CI 中 E2E 的工程实践:一是"分层"——PR 跑冒烟(核心路径、快速)、发布前/夜间跑全量(回归矩阵);二是"稳定化"——重试策略(--retries 处理 flaky)、webServer 配置(CI 中自动起服务并等待就绪)、trace/截图(失败时自动捕获,附到报告定位);三是"报告与制品"——HTML 报告、trace、video 上传为 CI 制品;四是"资源"——并发数(workers)与 CI runner 资源匹配、浏览器下载与 sandbox 问题(容器需 --no-sandbox 或正确配置)、网络(测试环境的 mock/隔离,避免 CI 依赖外网不稳定);五是"与部署集成"——E2E 可打"部署后验证"(对 preview 环境跑冒烟),作为发布门禁一环。

本题考察 E2E 的 CI 落地链路:浏览器安装(install/--with-deps/缓存/镜像)、分层与分片、稳定化手段。回答应说明安装链路设计与 CI 治理实践。

#

27. PostCSS 插件(AST walker/Declaration 处理)的开发与 Tailwind v4 @plugin 的协作

PostCSS 插件如何开发(AST walker/Declaration 处理)?与 Tailwind v4 的 @plugin 如何协作?

  • PostCSS 插件结构(postcss.plugin/函数式、Root/Declaration 遍历)
  • Declaration 处理(属性/值修改、条件判断)与 API(walkDecls、prop 等)
  • Tailwind v4 的 @plugin 指令加载 PostCSS 插件的机制

PostCSS 插件是"AST 转换器":插件导出函数(接收 postcss 与可选 options),返回 { postcssPlugin, Once/Root 遍历逻辑 }(或旧式 postcss.plugin('name', opts => { ... })),核心模型——AST 节点(Root → Rule → Declaration/Rule 嵌套),遍历 API:root.walkDecls(prop, fn)(按属性名遍历声明)、root.walkRules、root.walkAtRules,节点操作:decl.prop/decl.value(读写)、decl.clone/removeBefore/insertAfter、rule.remove/append;典型插件(如 autoprefixer、px2rem):遍历 Declarations → 按条件(prop/value 模式)计算新值 → 修改/插入新声明,并可用 root.walkAtRules 处理 @media 等。

Tailwind v4 的 @plugin 协作:Tailwind v4 改为"CSS 优先配置",用 @plugin 指令在 CSS 中加载 PostCSS 插件(@plugin "./my-plugin.js";),使插件参与 Tailwind 的 CSS 处理链(生成过程中运行,可操作生成结果或注入自定义逻辑),例如:插件遍历 Tailwind 输出的 Declarations 做后处理(兼容性修正、自定义值变换)、或配合 @custom-variant 扩展语义;与"构建工具集成"的差异:Tailwind v4 也可作为 PostCSS 插件本身使用(@tailwindcss/postcss),@plugin 是"Tailwind 内部加载用户插件"的机制(独立于构建器的 PostCSS 配置);开发注意:插件保持"确定性 + 幂等"(重复运行结果一致)、声明式处理(不依赖全局状态)、性能(walk 全树开销、尽量定向遍历(walkDecls 指定属性))、以及 Tailwind v4 的 CSS 处理顺序(插件在哪个阶段运行——@plugin 加载的插件参与 Tailwind 管线,与 PostCSS 配置中的插件链不同)。

本题考察 PostCSS 插件开发与 Tailwind v4 的插件协作:AST 遍历/Declaration 处理是基础、@plugin 是 v4 的加载机制。回答应说明插件结构、遍历 API 与协作方式、注意事项。

#

28. 工具链插件的调试手段(钩子日志、中间产物 dump)与单测策略

工具链插件(打包器/转译器插件)的调试手段与单测策略是什么?

  • 钩子日志与调试输出(注入日志、DEBUG 环境、时序观察)
  • 中间产物 dump(AST、转换结果、模块图快照)的对比调试
  • 插件的单测策略(fixture 驱动、快照、错误路径)

工具链插件调试的核心是"看中间产物":一是"钩子日志"——在插件各钩子中注入日志(钩子名、参数摘要、耗时),用 DEBUG 环境变量(如 DEBUG=my-plugin*)开关输出,观察"钩子何时触发、参数是什么、顺序如何"(定位"为什么没触发/触发多次/顺序错");二是"中间产物 dump"——关键环节输出中间态:AST(AST explorer 或序列化输出)、转换前后代码(插件内 writeFile 临时文件或 debug 模式)、模块图/产物快照(对比期望与实际的差异);三是"对比法"——同一输入分别"带插件/不带插件""旧版本/新版本"跑,diff 输出定位插件影响面(行为回归)。

单测策略:一是"fixture 驱动"——为每种场景准备最小 fixture(源码 + 配置 + 期望产物),用真实工具链(webpack/Rspack/Vite 的 Node API)跑插件后断言产物(文件存在、内容匹配、hash 稳定);二是"快照测试"——转换结果做快照(jest snapshot:AST 序列化、产物代码、错误消息),变更即审查;三是"错误路径"——插件报错场景(非法输入、解析失败、异步回调不调用)断言"正确报错不挂死"(工具链行为:错误信息、退出码);四是"边界用例"——空文件、特殊编码、Windows 路径、循环依赖等(平台差异用例跑多 OS 矩阵);五是"测试框架"——Jest/Vitest 直调插件 API(纯函数化设计:输入输出分离便于单测),配合工具链的"内存编译"(如 webpack 的 memory-fs)快速迭代;六是"集成冒烟"——发布前用真实项目(或样例仓库)构建验证插件,防"单测过、真项目挂"。

本题考察工具链插件开发的工程质量:调试(钩子日志、中间 dump)与测试(fixture、快照、错误路径)。回答应说明调试手段的定位与单测策略的层次。