模块系统与包管理:CommonJS 与 ESM、循环依赖、npm/yarn/pnpm
📌 面试问答卡片
面试问题:CommonJS 和 ESM 有哪些核心区别?
考察点:模块语法、加载时机、静态/动态分析、顶层 this、import 的 hoist 行为。
回答框架:从语法、加载机制、编译时特性、引用方式四个维度对比,先说静态 vs 动态,再举例说明 require 是运行时同步,import 是编译时异步。
示例回答:(85字)CommonJS 使用 require 同步加载,运行时得到值的拷贝;ESM 使用 import/export,编译时静态分析,支持 tree shaking,且为引用绑定。ESM 在循环依赖中表现更优,但 Node.js 中需开启 "type": "module" 或使用 .mjs 后缀。
可能追问:“ESM 的 import 为什么不能在条件语句中使用?”“在同一个项目中如何混用两种模块系统?”
面试问题:Node.js 如何处理循环依赖?请举例说明。
考察点:模块缓存机制、require 的返回值、ESM 的暂时性死区、实际工程中的规避策略。
回答框架:先解释循环依赖的触发场景;再分 CommonJS(返回未完成 exports)和 ESM(引用绑定+暂时性死区)分别说明;最后给出重构建议(如提取公共模块、延迟引用)。
示例回答:(96字)CommonJS 遇到循环依赖时,会返回当前模块已执行的 exports 对象(可能不完整),因此依赖顺序很重要。ESM 通过实时绑定和 TDZ(暂时性死区)防止未初始化访问。工程上推荐将公共依赖提取为独立模块,或在函数内部延迟 require 避免加载时机问题。
可能追问:“ESM 的循环依赖一定能正确工作吗?”“你项目中有遇到过循环依赖导致的 Bug 吗?怎么排查的?”
require.cache 查看模块缓存,或用工具 madge 检测循环依赖。面试问题:npm、yarn、pnpm 的主要区别是什么?你如何选择?
考察点:依赖管理策略(扁平化/嵌套/hard link)、安装速度、磁盘空间、lock 文件、workspace 支持。
回答框架:先对比三者在node_modules 结构、安装速度、安全性上的差异;然后给出选型建议:小项目用 npm 默认,团队协作推荐 yarn,大型 monorepo 优先 pnpm。
示例回答:(92字)npm 使用扁平化 node_modules 但可能发生幽灵依赖;yarn 通过 yarn.lock 锁定版本,安装速度优于 npm v1,但 node_modules 结构与 npm 类似;pnpm 使用硬链接和符号链接创建严格隔离的依赖树,节省磁盘且杜绝幽灵依赖。我当前团队在 monorepo 中全面使用 pnpm + workspace。
可能追问:“pnpm 的 node_modules 结构是怎样的?为什么说它更安全?”“yarn 和 npm 的 lock 文件有什么本质区别?”
🏗️ 项目案例模板:Node.js 中间层聚合服务
| 维度 | 内容 |
|---|---|
| 项目名称 | 数据中台 API 聚合网关 |
| 业务背景 | 前端需要从 6 个后端微服务聚合用户、订单、商品、库存等数据,原始方案前端并发 6 次请求,导致白屏超过 4s。需要 Node.js 中间层做 BFF 聚合,使用 ESM 模块化开发,依赖 pnpm monorepo 管理。 |
| 规模指标 | 涉及 6 个下游服务、日均 200 万请求、响应时间从 4s 降至 800ms、Node.js 层 2 个节点部署。 |
| 技术栈 | Node.js 18 + ESM + Express + pnpm workspace + TypeScript + ESBuild 打包 |
| 核心难点 |
|
| 面试高频追问 |
|
| 可迁移回答话术 | “我负责的中间层项目从 CommonJS 迁移到 ESM,主要解决了两个问题:一是使用 createRequire 加载遗留 CJS 模块;二是通过 pnpm 的 .npmrc 配置 node-linker=hoisted 兼容旧包。对于循环依赖,我们用 madge 扫描出 3 处环形,通过提取公共接口层消除,同时利用 ESM 的 TDZ 机制早期发现未初始化问题。” |
📊 知识结构速览
| 维度 | CommonJS | ESM |
|---|---|---|
| 语法 | require() / module.exports |
import / export |
| 加载时机 | 运行时同步 | 编译时静态 + 运行时异步(Node 中同步) |
| 引用方式 | 值拷贝(基本类型) | 实时绑定(引用) |
| 循环依赖 | 返回未完成 exports(有隐患) | TDZ 保护 + 引用绑定(更安全) |
| Node 启用方式 | 默认 .js / .cjs | "type": "module" 或 .mjs |
--install-strategy 控制结构;yarn v2+ 采用 PnP 模式跳过 node_modules;pnpm 使用 content-addressable 存储,重复包只存一份。推荐 pnpm 用于新项目,尤其是在 monorepo 场景下。