Next.js 源码构建全解:pnpm build 背后的 SWC 编译、Webpack 打包与 tsc 类型生成流水线

Next.js 源码构建全解:pnpm build 背后的 SWC 编译、Webpack 打包与 tsc 类型生成流水线 Next.js 源码构建全解pnpm build 背后的 SWC 编译、Webpack 打包与 tsc 类型生成流水线【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js本文以 Next.js 官方贡献文档 Building 指南 为核心讲清楚在源码仓库中执行pnpm build时实际发生了什么taskr 如何并行调度任务、SWC 如何编译 TypeScript 源码、Webpack 如何对产物做运行时打包、tsc如何独立生成类型声明以及如何本地编译 Rust 原生代码与 WASM 版本。读完后你可以完整复现 Next.js 从packages/next/src源码到packages/next/dist发行物的全部构建链路并具备独立调试构建各阶段的能力。构建入口从 pnpm build 到 taskr在仓库根目录执行以下命令即可构建 Next.js 的全部类型定义与包pnpm build从源码结构看这条命令背后有两层调度根目录 package.json 中build脚本定义为turbo run build --remote-cache-timeout 60 --summarize true即通过 Turbo 在 monorepo 层面并行触发各包的 build 任务核心的next包中package.json 的build脚本则是taskr release。Next.js 使用 taskr 文件中每个任务名对应一个导出函数的名字。例如taskr release会执行taskfile.js中的release()函数。主任务文件通过taskr.requires字段引入四个辅助任务文件taskfile-webpack.js、taskfile-ncc.js、taskfile-swc.js 和 taskfile-watch.js分别封装 Webpack 打包、NCC 预编译、SWC 编译和文件监听能力。对照taskfile.js源码完整的构建流水线是// packages/next/taskfile.js export async function release(task) { await task.clear(dist).start(build) } export async function build(task, opts) { await task.serial([precompile, compile, generate_types], opts) }即release先清空dist/再串行执行三个阶段预编译precompile→ 编译打包compile→ 生成类型generate_types。compile阶段内部又拆为next_compileSWC 编译源码与next_bundleWebpack 打包随后补齐react-refresh、next/font、capsize 字体度量等产物export async function compile(task, opts) { await task.serial([next_compile, next_bundle], opts) await task.serial([ncc_react_refresh_utils, ncc_next_font, capsize_metrics]) }值得注意的一个工程细节是precompile阶段的copy_ncced任务export async function copy_ncced(task) { // we dont ncc every time we build since these wont change // that often and can be committed to the repo saving build time await task.source(src/compiled/**/*).target(dist/compiled) }源码注释说明NCCvercel/ncc预编译产物变化不频繁因此直接提交进仓库构建时只做拷贝从而显著缩短构建时间。而ncc主任务重新编译node-html-parser、jest-worker、browserslist等数十个依赖则通过pnpm ncc-compiled即taskr ncc单独按需执行。阶段一使用 SWC 编译 TypeScript 源码按官方文档的说明默认情况下会安装并使用next-swc二进制的最新 canary 版本来编译项目的 TypeScript 源码。这些源码的构建目标是packages/next/dist/...目录输出包含编译后的 JavaScript 文件与 source map。对应到任务文件next_compile会并行调度大量形如下面的子任务每个子任务用 glob 圈定一个源码目录经.swc()步骤编译后落到dist的对应位置export async function server(task, opts) { await task .source(src/server/**/!(*.test).(js|ts|tsx)) .swc(server, { dev: opts.dev }) .target(dist/server) }可以观察到几个规律源文件按目录分片src/server、src/client、src/shared、src/cli、src/lib、src/trace、src/telemetry等每片独立成任务便于 taskr 并行执行多数模块会同时产出 CommonJS 版本dist/...和 ESM 版本dist/esm/...即esm: true选项例如server与server_esm两个任务成对出现glob 中的!(*.test)排除了测试文件与最终发行物只包含运行时代码的定位一致。开发场景下还可以直接跑pnpm devnext包中定义为cross-env NEXT_SERVER_NO_MANGLE1 taskr。此时taskfile.js的默认任务会先清dist、启动一次build然后注册一整套task.watch监听器实现src/到dist/的增量重编译例如await task.watch(src/server, [server, server_esm, server_wasm], opts) await task.watch(src/client, [client, client_esm], opts)阶段二使用 Webpack 打包产物编译阶段结束后项目基于编译产物dist目录再用 Webpack 做运行时打包。配置位于 next-runtime.webpack-config.js。taskfile.js中为不同产物形态定义了成组的 Webpack 任务全部以dist为 source并调用同一个配置工厂export async function next_bundle_app_prod(task, opts) { await task.source(dist).webpack({ watch: opts.dev, config: require(./next-runtime.webpack-config)({ dev: false, bundleType: app, }), name: next-bundle-app-prod, }) }从源码结构看该配置工厂支持dev、turbo、experimental、bundleType等选项任务名也按这些维度组合如next_bundle_app_prod、next_bundle_app_dev_turbo、next_bundle_app_prod_experimental等。可以推断这些选项分别对应 App 运行时在开发/生产、Turbopack 启用与否、实验通道开启与否时的不同打包形态。阶段三生成类型定义类型定义由 TypeScript 编译器tsc生成也可以单独构建pnpm types两个层级的定义如下根目录 package.json 中types脚本为lerna run types --stream在 monorepo 各包间流式执行next包的types脚本见 package.json为types: tsc --project tsconfig.build.json --declaration --emitDeclarationOnly --stripInternal --declarationDir dist即只输出声明文件--emitDeclarationOnly、剥离internal标记的 API--stripInternal、声明目录指向dist。类型检查项目录由 tsconfig.build.json 控制它继承基础 tsconfig.json 并做两点调整设置rootDir: src让声明文件的目录结构与src对齐在基础 exclude 列表之上额外排除./**/*.test.ts、./**/*.test.tsx、./**/*.stories.tsx和./**/storybook/**/*即不生成测试与 Storybook 相关的类型定义。基础tsconfig.json则沿用了仓库根部的 tsconfig-tsec.json并开启了strict、stripInternal、verbatimModuleSyntax等编译选项target为ES2018、moduleResolution为bundler。tsconfig.build.json中还有注释提醒修改exclude时须同步更新两个文件。在taskfile.js中generate_types通过execa子进程调用pnpm run types完成该阶段export async function generate_types(task, opts) { const watchmode opts.dev const typesPromise execa(pnpm, [run, types, ...], { stdio: inherit }) if (!watchmode) { await typesPromise } }值得注意的是 watch 模式下该进程永不退出taskr 因此不能等待 Promise 完成而是依赖文件监听手动重启任务——这是把类型生成嵌入增量构建循环时处理的一个典型异步问题。本地编译 Turbopack、WASM 等 Rust 代码如果你正在直接修改 Rust 代码或想验证尚未发布为 canary 的最新 Rust 实现需要安装 Rust 工具链后执行pnpm swc-build-native根目录 package.json 中该脚本定义为tsx scripts/build-native.ts即由仓库内的构建脚本驱动原生二进制的本地编译。若要本地验证 WASM 构建步骤为安装 wasm-pack执行构建命令wasm_target替换为具体的 wasm 目标平台pnpm --filternext/swc build-wasm --target wasm_target运行node ./scripts/setup-wasm.mjs将构建产物拷贝进node_modules以NODE_OPTIONS--no-addons启动 next强制其使用 wasm 二进制而非原生 add-on。对应的 Rust 源码位于仓库的crates/与turbopack/目录下如 crates/next-swc、turbopack/crates构建产物与 Node 侧的桥接可通过 scripts/install-native.mjs 等脚本查看。清理构建产物任何需要清理项目的场景执行pnpm clean根目录 package.json 中该脚本的实现为clean: lerna clean -y lerna run clean lerna exec node ../../scripts/rm.mjs dist即先用 lerna 清理工作区的node_modules、再触发各包自己的 clean 脚本最后用仓库自有的 scripts/rm.mjs 删除各包的dist目录。小结pnpm build在根目录经 Turbo 分发落到next包即taskr release清空dist后串行执行precompile → compile → generate_typesSWC 编译把packages/next/src下的 TypeScript 源码排除测试文件按目录分片编译为 CJS/ESM 双版本 JS 与 source mapNCC 预编译依赖则直接复用已提交的src/compiledWebpack 基于 next-runtime.webpack-config.js 对dist产物按 dev/prod、turbo、experimental 等维度打出运行时 bundlepnpm types使用 tsconfig.build.json 单独生成声明文件Rust 与 WASM 侧通过pnpm swc-build-native、pnpm --filternext/swc build-wasm和scripts/setup-wasm.mjs完成本地验证pnpm clean负责彻底清理。以上全部依据 contributing/core/building.md 原始指南并结合 taskfile.js、package.json、packages/next/package.json、tsconfig.build.json 等仓库内文件交叉验证读者可直接沿这些路径深入阅读实现细节。【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考