使用ES Module部署TypeScript编写的Firebase函数遇阻求助
我在部署TypeScript编写、采用ES Module系统的Firebase函数时遇到了模块找不到的问题。相关代码文件如下:
index.ts
import * as functions from "firebase-functions"; import myGreeting from "./my-greeting"; export const helloWorld = functions.https.onRequest((request, response) => { response.send(myGreeting); });
my-greeting.ts
export default "HELLO WORLD";
tsconfig.json
{ "compilerOptions": { "module": "ES2020", "noImplicitReturns": true, "noUnusedLocals": true, "moduleResolution": "node", "outDir": "lib", "sourceMap": true, "strict": true, "target": "ES2020" }, "compileOnSave": true, "include": [ "src" ] }
package.json
{ "name": "functions", "scripts": { "build": "tsc", "build:watch": "tsc --watch", "serve": "npm run build && firebase emulators:start --only functions", "shell": "npm run build && firebase functions:shell", "start": "npm run shell", "deploy": "firebase deploy --only functions", "logs": "firebase functions:log" }, "type": "module", "engines": { "node": "16" }, "main": "lib/index.js", "dependencies": { "firebase-admin": "^10.2.0", "firebase-functions": "^3.21.0" }, "devDependencies": { "typescript": "^4.6.4" }, "private": true }
代码可以正常编译,但TypeScript编译器不会在输出的./lib/index.js导入语句中添加.js扩展名,部署时出现以下错误:
Error: Failed to load function definition from source: Failed to generate manifest from function source: Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/my-project/functions/lib/my-greeting' imported from /my-project/functions-test/functions/lib/index.js
我需要导入一个仅提供ES Module版本(无CommonJS版本)的npm库,该库的导入语句也不含.js后缀。Node.js 16理论上支持ES2020规范的ES Module,但Firebase函数运行时无法设置--experimental-specifier-resolution=node标志。
我需要在不转为CommonJS模块的前提下,让函数在Node.js 16的Firebase云运行时成功部署并运行。以下两种方案不可行:
- 无法直接修改导入语句:手动给所有导入语句添加
.js后缀过于繁琐,且依赖的第三方ES Module库本身就不含这类后缀,无法逐一修改。 - 无法切换为CommonJS模块:若将
tsconfig.json的module改为CommonJS并移除package.json中的type: module,示例代码可正常部署,但我依赖的库仅提供ES Module版本,无法兼容CommonJS。
可行解决方案
方案1:使用tsc-alias自动补全.js后缀
tsc-alias可以在TypeScript编译完成后,自动将相对导入路径补全为带.js后缀的格式,符合Node.js ES模块的要求:
- 安装开发依赖:
npm install --save-dev tsc-alias - 修改
package.json中的build脚本:"scripts": { "build": "tsc && tsc-alias", // 其他脚本保持不变 } - 正常执行
npm run build后,编译生成的lib目录下的文件会自动补全导入语句的.js后缀,部署即可正常运行。
方案2:使用ESBuild替代TypeScript编译器
ESBuild是一个快速的JavaScript/TypeScript打包工具,默认会处理ES模块的导入后缀问题,同时支持直接输出符合Node.js要求的ES模块:
- 安装开发依赖:
npm install --save-dev esbuild - 修改
package.json中的build脚本(如需保留类型检查,可新增类型检查脚本):"scripts": { "build": "esbuild src/index.ts --bundle --outfile=lib/index.js --platform=node --target=node16 --format=esm", "type-check": "tsc --noEmit", "serve": "npm run type-check && npm run build && firebase emulators:start --only functions", // 其他脚本按需调整 } - ESBuild会自动处理所有模块导入的后缀,同时可以将代码打包为单个文件,避免模块查找问题。
方案3:调整TypeScript配置为NodeNext模式
TypeScript的NodeNext模块模式会严格遵循Node.js的模块解析规则,编译ES模块时会自动添加.js后缀:
- 修改
tsconfig.json的compilerOptions:{ "compilerOptions": { "module": "NodeNext", "moduleResolution": "NodeNext", "target": "ES2020", // 保留其他原有配置(noImplicitReturns、noUnusedLocals等) }, // 其他配置保持不变 } - 确保
package.json中的type仍为module,然后正常执行npm run build即可。此方案需要TypeScript 4.7及以上版本支持。
注:Firebase Cloud Functions运行时不支持自定义Node.js启动参数,因此无法通过添加
--experimental-specifier-resolution=node来绕过后缀检查,必须通过编译层面解决这个问题。
内容的提问来源于stack exchange,提问作者Adam D

