前端工程化面试题:构建工具、编译、规范、CI/CD 与 Monorepo
工程化面试考察的是你能不能把项目从“能跑”带到“可维护、可构建、可发布、可协作”。
构建工具
Vite
知识点讲解
Vite 开发环境基于原生 ESM,按需转换模块,所以冷启动快。生产构建默认使用 Rollup,负责打包、代码分割和资源优化。
面试常问
问:Vite 为什么比 Webpack 开发启动快?
答:Webpack 开发时通常要先构建依赖图并打包;Vite 利用浏览器原生 ESM,源码按需编译,依赖预构建后缓存,启动成本更低。
Webpack、Rollup 与 Rspack
知识点讲解
Webpack 生态成熟,适合复杂业务和历史项目。Rollup 更擅长库打包,输出更干净。Rspack 用 Rust 实现,目标是兼容 Webpack 生态并提升性能。
面试常问
问:Loader 和 Plugin 区别?
答:Loader 处理单个模块的转换,比如 TS、CSS、图片;Plugin 参与构建生命周期,可以做资源优化、HTML 注入、环境变量、分析报告等更广的事情。
编译与包管理
Babel、SWC 与 ESBuild
知识点讲解
Babel 生态成熟、插件丰富,适合语法转换和定制。SWC 和 ESBuild 更快,常用于转译和压缩,但某些 Babel 插件能力不完全等价。
面试常问
问:Babel 做 polyfill 吗?
答:Babel 本身主要做语法转换。新的 API 需要 polyfill,比如 Promise、Array.from。通常配合 core-js 和 @babel/preset-env 的 useBuiltIns。
npm、pnpm 与 yarn
知识点讲解
包管理器负责依赖安装、锁定版本、脚本执行。pnpm 通过内容寻址存储和硬链接节省磁盘,并用严格 node_modules 结构减少幽灵依赖。
面试常问
问:什么是幽灵依赖?
答:项目代码使用了没有直接声明的依赖,只是因为它被其他依赖提升到了 node_modules 根目录。换环境或升级依赖后可能突然失效。
质量与发布
ESLint、Prettier 与 Husky
知识点讲解
ESLint 关注代码质量和潜在错误,Prettier 关注格式统一。Husky 常配合 lint-staged 在提交前检查变更文件,防止低级问题进入仓库。
面试常问
问:ESLint 和 Prettier 冲突怎么办?
答:职责分离。用 Prettier 处理格式,用 ESLint 处理代码规则,通过相关配置关闭 ESLint 中与格式冲突的规则。
CI/CD
知识点讲解
CI 负责自动检查、测试、构建;CD 负责自动部署。常见流程包括安装依赖、缓存、lint、test、build、产物上传、部署、回滚。
面试常问
问:前端上线流程怎么设计?
答:提交代码后触发 CI,执行 lint/test/build,产物上传到对象存储或服务器,CDN 刷新缓存,灰度发布,监控错误和性能指标,异常时回滚。
Monorepo
基本概念
知识点讲解
Monorepo 是把多个项目或包放在同一个仓库里统一管理。优点是依赖统一、代码复用方便、跨包修改原子提交;挑战是构建缓存、权限、版本发布和仓库规模。
面试常问
问:Monorepo 适合什么场景?
答:多个包之间频繁协作、组件库和业务项目共存、多应用共享工具链时适合。小项目或团队协作边界很清晰时不一定需要。
TurboRepo 与 Nx
知识点讲解
TurboRepo 强调任务编排和缓存,配置较轻。Nx 能力更完整,包含项目图、生成器、插件和更强约束,适合大型组织。
面试常问
问:构建缓存有什么价值?
答:如果输入文件、依赖和命令没变,就可以复用上次构建结果,减少 CI 时间和本地等待时间。
底层追问与代码示例
Tree Shaking 为什么依赖 ESM
知识点讲解
Tree Shaking 的基础是静态分析。ESM 的 import/export 是静态结构,构建工具可以在编译阶段知道哪些导出被使用。CommonJS 的 require 可以出现在条件语句和动态路径里,静态分析更困难。
1 | // ESM,容易静态分析 |
1 | // CommonJS,路径和导出都可能动态变化 |
面试常问
问:为什么有些代码明明没用却没被摇掉?
答:可能是模块有副作用、包没有正确声明 sideEffects、使用了动态导入方式、代码被 Babel 转成 CommonJS,或者导出对象被整体引用。
1 | { |
手写一个简化版 Loader
知识点讲解
Webpack Loader 本质是把一种资源转换成 JS 模块或另一种资源。它接收源码,返回转换后的源码。
1 | // markdown-loader.js |
真实 loader 要考虑 source map、异步处理、缓存、错误上报和 loader 顺序。多个 loader 执行顺序通常是从右到左、从下到上。
Plugin 的生命周期
知识点讲解
Plugin 通过 Tapable 订阅编译生命周期。它不只处理单个文件,而是能介入整个构建过程。
1 | class BuildTimePlugin { |
面试常问
问:Loader 和 Plugin 怎么选?
答:处理某类模块内容用 Loader;需要影响构建流程、生成额外资源、分析依赖图、修改输出产物,用 Plugin。
Vite 依赖预构建
知识点讲解
Vite 会用 esbuild 对依赖做预构建,主要解决两个问题:把 CommonJS/UMD 转成 ESM;把依赖内部大量小模块合并,减少浏览器请求数量。
1 | // vite.config.js |
专业回答要补充:源码通常按需转换,依赖通常预构建并缓存,生产构建仍然交给 Rollup。