Redwood 构建指南:深入理解yarn rw build的 API 与 Web 双端生产构建流程
【免费下载链接】redwoodRedwoodGraphQL项目地址: https://gitcode.com/gh_mirrors/re/redwood
本文基于 Redwood 官方文档 Builds 展开,系统讲解 Redwood 框架生产构建的全过程:API 侧如何被转译进./api/dist、Web 侧如何被 Vite 打包进./web/dist,并顺带还原zip-it-and-ship-it在 Netlify 上的 Lambda 打包实践。读完本文,你将掌握yarn rw build各选项的准确含义、底层构建任务的实际执行顺序,以及如何在本地复现 Netlify 的构建与函数打包流程。
构建概览:一条命令,两个产物目录
Redwood 是一个前后端一体的全栈框架,一个项目中同时包含api(服务端)与web(浏览器端)两个 side。生产构建命令将它们分别处理,最终产出两套相互独立的构建产物:
| Side | 构建工具 | 产物目录 | 产物用途 |
|---|---|---|---|
api | Babel(由 esbuild 驱动) | ./api/dist | 服务端代码、GraphQL 服务、Serverless 函数 |
web | Vite | ./web/dist | 静态资源、SPA / SSR 页面 |
这个结论同时被 Builds 文档与 cli-commands.md 的 build 小节 明确记载:"We use Babel to transpile the api side into./api/distand Vite to package the web side into./web/dist."
在命令行中,build子命令支持限定构建范围,位置参数side..表示可以传入一个数组(yargs 的 variadic positional arguments):
yarn rw build # 默认构建 api 与 web 两侧 yarn rw build api # 只构建 API 侧 yarn rw build web # 只构建 Web 侧 yarn rw build api web # 显式指定构建两侧对应的命令定义位于 packages/cli/src/commands/build.js:command = 'build [side..]',description = 'Build for production'。命令还通过.middleware()检查 Node 版本,版本不满足要求时会直接报错退出,这是rw build在生产机与 CI 上稳定运行的第一道保障。
rw build的完整参数表
与 Builds 配套的 CLI 参考文档 cli-commands.md 记录了基础参数,而命令的实际实现 build.js 中还包含文档表格之外的重要选项。合并两者,得到完整的参数说明:
| 参数 / 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
side | array | ['api', 'web'] | 要构建的 side,可选值为api、web,可同时传多个 |
--verbose, -v | boolean | false | 输出更多构建信息,同时让 Listr 任务渲染器进入 verbose 模式 |
--prerender | boolean | true | Web 构建完成后是否执行预渲染;对未标记 prerender 路由的项目会给出提示 |
--prisma, --db | boolean | true | 是否生成 Prisma Client;在 api 侧存在schema.prisma且构建 api 或存在预渲染路由时生效 |
其中--prisma与--prerender在 buildHandler.js 的 handler 签名中有默认值定义。注意一个实用的细节:--prerender的默认值是true,如果只想"纯打包"不做预渲染,需要显式传--no-prerender;同理--prisma对应--no-prisma。
API 侧构建:Babel 转译与./api/dist
构建流程:先清空,再转译
API 侧的构建入口是 packages/internal/src/build/api.ts。整个流程分为两步:
cleanApiBuild():直接调用fs.remove(rwjsPaths.api.dist)清空旧的./api/dist目录(见 api.ts 第 38-41 行),保证产物目录内不会残留上次构建的过期文件;buildApi()/transpileApi():通过 esbuild 的build()API 转译全部 API 源码文件(见 api.ts 第 19-26 行)。
值得注意的是,文档中"api side is transpiled by Babel"的描述在实现上更精确地说应该是:esbuild 负责文件遍历与打包编排,Babel 负责真正的代码转换。esbuild 通过自定义插件runRwBabelTransformsPlugin(见 api.ts 第 43-72 行),对每个.js/.ts/.tsx/.jsx文件调用@redwoodjs/babel-config提供的transformWithBabel,并把转换后的代码以loader: 'js'交还给 esbuild 输出。
esbuild 关键构建选项
getEsbuildOptions() 揭示了产物形态的关键约束:
platform: 'node'、target: 'node20':产物面向 Node.js 20 运行环境;bundle: false:API 侧不做依赖打包,node_modules 中的依赖在部署时按需安装或由平台处理;format:根据项目配置动态选择esm或cjs(projectSideIsEsm('api')),即项目可以按 ESM 或 CJS 风格产出;sourcemap: true:同时生成.js.map外部 sourcemap 并写入sourceMappingURL注释,便于线上排查错误;outdir: rwjsPaths.api.dist:产物统一输出到./api/dist。
GraphQL Schema 校验前置步骤
在真正开始转译 API 代码之前,构建任务还会先执行loadAndValidateSdls对 GraphQL SDL 文件做一次校验(见 buildHandler.js 第 71-74 行)。如果 schema 定义存在错误,构建会在产出任何 API 代码前就失败,从源头拦截有问题的 GraphQL 层。
Web 侧构建:Vite 打包与./web/dist
构建入口与两种模式
Web 侧构建由独立的可执行文件 packages/vite/bins/rw-vite-build.mjs 驱动,buildHandler通过execa以子进程方式调用它,并把process.cwd切换到 web 目录。这样做的原因在 buildHandler.js 的注释 中写得很清楚:postcss / tailwind 等工具依赖正确的 cwd 才能解析配置,而把 cwd 变化隔离在子进程中,可以避免影响构建进程内其他并行的任务。
根据是否启用 Streaming SSR,构建走两条不同路径:
- 普通 SPA 模式(
experimental.streamingSsr.enabled为 false):调用buildWeb(),即 packages/vite/src/build/build.ts 中的 buildWeb,本质是调用 Vite 的build(),并显式传入configFile: rwjsPaths.web.viteConfig(即web/vite.config.{js,ts})与envFile: false; - Streaming SSR 模式:调用
buildFeServer(),产物面向服务端渲染场景。
rw-vite-build.mjs启动时会做三重防御性检查:必须传入--webDir、目录必须真实存在、目录内必须有package.json,否则直接报错退出(见 rw-vite-build.mjs 第 17-32 行)。
产物目录与 200.html 约定
普通 SPA 模式下,构建产物输出到./web/dist。构建完成后还有一步容易被忽略的收尾:复制index.html为200.html(见 buildHandler.js 第 119-129 行)。这是为 Serverless / 静态托管平台的 SPA fallback 准备的——当用户直接访问/some/route这类深层路径时,平台会回退到200.html再由前端路由接管。Streaming SSR 模式不使用index.html,因此会跳过这一步骤。
Web 构建前还会清理旧的./web/dist与web预构建目录(cleanWebBuild,见 build.ts 第 12-16 行),确保产物干净一致。
构建前的准备任务:Prisma Client 与代码生成
rw build不是简单地"翻译源码",在正式构建之前,buildHandler.js 会按序执行一系列准备任务:
- 生成 Prisma Client(条件:
--prisma开启、存在api/db/schema.prisma、且本次构建涉及 api 侧或有预渲染路由):通过generatePrismaCommand构造prisma generate命令并在 api 目录执行; - 为 GraphQL Fragments / Trusted Documents 生成类型(条件:
redwood.toml中graphql.fragments或graphql.trustedDocuments开启):调用generate()运行 codegen,生成 possible types 与 trusted document store 的哈希,供运行时使用。
这一顺序在构建测试 packages/cli/src/commands/tests/build.test.js 中有明确断言,完整任务序列为:
Generating Prisma Client... Verifying graphql schema... Building API... Building Web...预渲染(Prerender):Web 构建后的可选步骤
当--prerender开启且构建范围包含 web 侧时,构建主流程结束后会触发triggerPrerender()(见 buildHandler.js 第 134-154 行):
- 它先用
detectPrerenderRoutes()扫描Routes文件中标记了prerender的路由; - 若没有任何路由标记
prerender,则打印提示 "You have not marked any routes to 'prerender' in your Routes",并不会报错; - 若存在预渲染路由,则以独立子进程运行
yarn rw prerender。
之所以用独立子进程,注释里给出了重要原因:由于 Node 的模块缓存(require module caching),必须在独立进程里运行才能正确加载刚生成的 Prisma Client。
对应地,测试 build.test.js 第 77-92 行 验证了"只构建 web + 开启 prerender + 无预渲染路由"场景下,控制台会依次打印Starting prerendering...与未标记 prerender 的警告。
在 Netlify 上:本地复现 Lambda 打包流程
Builds 文档专门给出了在本地模拟 Netlify 构建步骤的命令,这是理解 Serverless 部署形态的关键:
yarn rw build api cd api yarn zip-it-and-ship-it dist/functions/ zipballs/原理
yarn rw build api只构建 API 侧,产物位于./api/dist,其中每个 GraphQL 服务、每个自定义函数都会被转译为一个独立文件放在./api/dist/functions下;zip-it-and-ship-it会逐个解析dist/functions/下的 Lambda 函数文件,为每个 Lambda 函数生成一个 zip 包,zip 包内包含该函数运行所需的全部依赖,输出到zipballs/目录。
这正是 Serverless 部署的核心诉求:平台只安装每个函数自己需要的依赖,而不是把整个项目的node_modules都塞进去,从而显著减小函数体积、加快冷启动。
前置安装
@netlify/zip-it-and-ship-it需要作为 api side 的开发依赖安装:
yarn workspace api add -D @netlify/zip-it-and-ship-it关于 zip 的产物最终如何被 AWS Lambda / Netlify Functions 消费,可进一步参阅 deploy/serverless 文档(即原文档中指向的 "AWS Serverless Deploy" 说明)。
从源码看函数的产出方式
API 侧转译后的目录结构由findApiFiles()与 esbuild 的 entryPoints 决定(见 api.ts 第 74-96 行):所有 API 源文件都被作为独立 entry point 转译到api/dist,函数目录对应api/src/functions,因此每个函数在dist/functions中都有独立产物,这正是 zip-it-and-ship-it 能按函数粒度打包的前提。
与部署环节的衔接
rw build的产物是部署的输入,Redwood 针对不同平台的部署命令本质上都是"先构建、再按平台规则上传/打包":
- Serverless 平台(Netlify / Vercel / AWS Lambda):如上文所述,API 侧按函数粒度 zip,Web 侧上传
./web/dist静态资源,并依赖200.html实现 SPA fallback; - Baremetal / Docker 自托管:需要完整的服务端产物,部署模板中同样会先执行 build 步骤。
Redwood 还提供了yarn rw deploy <provider>命令族(见 packages/cli/src/commands/deploy),这些命令在内部都会以rw build的产物为基础完成上传,而 deploy/helpers.js 中也能看到对 build 命令的引用,印证了"构建是部署前置依赖"这一关系。
小结:一次构建的全景时间线
综合文档与源码,一次完整的yarn rw build(双端、默认参数、存在 schema.prisma)实际执行的是:
- 生成 Prisma Client(如需要);
- 为 Fragments / Trusted Documents 生成类型(如配置开启);
- 校验 GraphQL schema(api 侧);
- 清空并转译 API 代码到
./api/dist(Babel + esbuild,Node 20 目标); - 通过
rw-vite-build.mjs子进程构建 Web 到./web/dist,并复制出200.html(非 SSR 模式); - 若有预渲染路由,子进程执行
yarn rw prerender。
如需在本地观察每一步的具体行为,可加--verbose运行;想跳过 Prisma Client 生成则用--no-prisma,想跳过预渲染则用--no-prerender。相关源码与测试可继续深入阅读 buildHandler.js、build/api.ts、rw-vite-build.mjs 以及 build.test.js。
【免费下载链接】redwoodRedwoodGraphQL项目地址: https://gitcode.com/gh_mirrors/re/redwood
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考