架构概览
系统定位
ncc 本质上是一个 Webpack 编排器(orchestrator),为 Node.js 模块打包提供零配置体验。它不是自己实现一个 bundler,而是在 Webpack 之上构建一个定制化的配置层和后处理管道。
核心架构图
数据流
编译流程
- 入口解析:CLI 或 API 接收输入文件路径,解析为绝对路径
- 配置生成:根据选项生成完整的 Webpack 配置对象
- Webpack 编译:调用
webpack()并输出到内存文件系统(memory-fs) - 后处理:对编译结果进行 Terser 压缩、Source Map 处理、V8 缓存生成等
- 输出:返回
{ code, map, assets, symlinks, stats }对象
模块解析策略
ncc 使用定制化的模块解析策略:
- 对 CJS 和 ESM 依赖分别配置
conditionNames - 使用
TsconfigPathsPlugin支持 TypeScriptpaths别名 - 自定义 resolve 插件将未找到的模块转为运行时
require(而非构建时报错) - TypeScript
.js导入自动解析到.ts/.tsx文件
ESM 与 CJS 检测
ncc 根据以下规则自动判断输出格式:
.mjs输入 → ESM 输出.cjs输入 → CJS 输出.js输入 +"type": "module"包边界 → ESM 输出- 其他 → CJS 输出
关键设计决策
1. 内存文件系统
使用 memory-fs 作为 Webpack 的输出文件系统,避免中间磁盘 I/O。所有编译产物先写入内存,后处理完成后再由 CLI 层写入磁盘。
2. 未找到模块的宽容处理
当模块解析失败时,ncc 不会在构建时报错,而是将其转为运行时 require() 调用。这通过 src/@@notfound.js + notfound-loader.js + 自定义 resolve 插件实现。这使得动态依赖不会阻断构建。
3. 自定义 Terser 集成
ncc 不使用 Webpack 内建的压缩,而是在后处理阶段手动调用 Terser。原因是某些包(如 auth0)在 Webpack 的 Terser 集成中会返回 undefined。
4. __webpack_require__ 重命名
为支持嵌套 ncc 构建(一个 ncc 产物作为另一个的输入),ncc 将 __webpack_require__ 重命名为 __nccwpck_require__,支持最多 9 层嵌套。
5. V8 编译缓存
可选的 V8 缓存模式会预编译代码并生成 .cache 文件,后续启动时直接加载编译后的字节码,显著提升冷启动速度。
6. 自举编译
ncc 使用自身来编译自身。scripts/build.js 调用 ncc API 编译 src/ 中的每个入口点,产物写入 dist/。这既是一个验证手段(ncc 能处理自己的代码),也是项目分发策略(只发布编译后的单文件)。
与 Webpack 的关系
ncc 对 Webpack 的使用方式与典型的 Web 应用构建有本质区别: