如何在TypeScript AWS栈单仓项目中正确构建、配置并打包共享代码?
我来帮你梳理一下这个问题——你遇到的核心是npm workspaces单仓里的共享lib包在AWS Lambda打包和本地调试时的模块解析/编译问题,咱们一步步拆解解决:
一、先搞定共享库(lib)的基础配置
首先得让lib能正确编译成可执行的JS,同时被其他包识别:
- 修改
packages/lib/package.json:{ "name": "@my-monorepo/lib", "version": "1.0.0", "main": "./build/index.js", "types": "./build/index.d.ts", "type": "commonjs", "scripts": { "build": "tsc" } }- 把
main指向编译后的JS文件,而非源TS文件 - 添加
types字段让TS能找到类型声明 - 增加
build脚本专门编译lib代码
- 把
- 修改
packages/lib/tsconfig.json,指定编译输出目录:{ "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./build", "rootDir": "./" }, "include": ["**/*"] } - 在根
package.json里加个快捷编译命令:
运行{ "scripts": { "build:lib": "npm run build --workspace=@my-monorepo/lib" } }npm run build:lib就能把lib编译到build目录了。
二、修复CDK Lambda打包的“找不到模块”问题
你之前犯了个小错误:把@my-monorepo/lib加到了nodeModules数组里——这个配置是告诉CDK“这些依赖从npm拉取,不要打包进代码”,但你的lib是本地包,npm仓库根本没有它,所以Lambda运行时自然找不到。
咱们要让esbuild(CDK NodejsFunction默认用的打包工具)把lib的代码直接内联到Lambda的打包文件里:
export class LambdaStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); const prototypeLambda = new NodejsFunction(this, "PrototypeLambda", { functionName: "prototypeLambda", handler: "routesHandler", runtime: Runtime.NODEJS_18_X, timeout: Duration.minutes(3), entry: "../lambda/test.ts", // 只保留真正的第三方依赖ulid,移除本地的@my-monorepo/lib bundling: { externalModules: ["ulid"], // 可选:如果esbuild解析@my-monorepo/lib路径有问题,手动指定别名 esbuildArgs: { "--resolve-alias": "@my-monorepo/lib=../lib" } }, tsconfig: "../lambda/tsconfig.json" }); } }
这样配置后,esbuild会把lib的代码直接打包进Lambda的index.js,部署后就不会出现找不到模块的错误了。
三、解决本地Serverless调试的SyntaxError
本地调试时出现Cannot use import statement outside a module,是因为你直接引用了lib的TS源文件(之前lib的main是index.ts),Node.js没法直接执行TS代码。推荐用下面这个方案:
用Serverless esbuild插件自动打包TS
配置Serverless Framework的esbuild插件,让它打包lambda时自动处理TS引用,包括lib的TS文件:
在lambda目录的serverless.yml里添加:
plugins: - serverless-esbuild custom: esbuild: bundle: true minify: false sourcemap: true platform: node target: node18 external: - ulid # 第三方依赖可以用本地node_modules,不用打包 resolveExtensions: [.ts, .js]
这样esbuild会把lambda代码和lib的TS代码一起编译打包成JS,本地调试时就不会有语法错误了。
如果你不想用插件,也可以先运行npm run build:lib编译lib,再启动Serverless调试——此时lambda会引用编译后的JS文件,Node.js能正常执行。
四、优化TS配置确保跨包类型正常
修改根tsconfig.base.json,添加paths配置让TS能正确解析@my-monorepo/lib的路径:
{ "compilerOptions": { "target": "ES2022", "module": "CommonJS", "declaration": true, "sourceMap": true, "strict": true, "moduleResolution": "node", "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "composite": true, "baseUrl": ".", "paths": { "@my-monorepo/lib": ["packages/lib/index.ts"] } }, "exclude": ["node_modules"], "include": ["packages/**/*"] }
lambda和web的tsconfig都继承这个base,这样TS编译和类型提示都会正常工作,前端Vite的引用也不会受影响。
关于直接用相对路径的疑问
你提到直接用import { random } from "../lib"能工作,这确实是最简单的方式,但用包名@my-monorepo/lib引用的好处是:
- 依赖关系更清晰,符合npm包规范
- 以后lib目录结构变化时,不用修改所有引用的相对路径
- 方便后续扩展(比如把lib发布到私有npm仓库)
至于bundle大小,只要你在lib里只放类型、验证schema和工具函数,完全不用担心——esbuild会自动tree-shake掉未使用的代码,体积不会有明显增长。
最后总结操作流程
- 配置lib的编译脚本和输出路径,确保生成可执行的JS和类型文件
- 调整CDK Lambda配置,让esbuild把lib代码内联打包,而非当作外部依赖
- 配置Serverless esbuild插件,解决本地调试的TS执行问题
- 优化TS paths配置,确保跨包类型解析正确
这样就能彻底解决你遇到的本地调试语法错误、部署后找不到模块的问题,同时保持单仓的规范结构~
备注:内容来源于stack exchange,提问作者Tsar Bomba

