Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Problem deploying Firebase function with TypeScript using ES Module system

I am having an issue deploying a Firebase function compiled from Typescript, using the ES Module system.

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
}

This builds just fine but of course the TypeScript compiler doesn't include the .js extensions on the import statement in the output file ./lib/index.js.

Then when I try to deploy the functions I get the following error:

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

I need to be able to import an npm library that uses ES modules compiled from TypeScript without the .js extensions included (it doesn't have commonjs version). Since I'm using the node 16 runtime for the functions I understand I should be able to run ES2020 targeted, ES modules, but is there a way to set the --experimental-specifier-resolution=node flag in the Firebase functions runtime?

How can I get the function to deploy/run in node 16 Firebase cloud runtime, without deploying as a commonjs module?

2 Solutions that won't work and why:

1. I can't just modify the import statements

In the example above I could change

import myGreeting from "./my-greeting";

to

import myGreeting from "./my-greeting.js";

That would be a really impractical workflow, but beyond that the problem is that I'm using a ES Module library that has these unmodified import statements, built from TypeScript, so I definately don't want to go in there and change all of the import statements to include the .js extension.

2. I can't use "module": "CommonJS" in tsconfig.json

I can get the example above to deploy just fine if I change the tsconfig.json compilerOptions to "module": "CommonJS" and take the "type": "module" out of package.json, but that's not an option because I need to work with a library that is only available as an ES module, it doesn't have a CommonJS version published.

like image 810
Adam D Avatar asked Aug 12 '26 08:08

Adam D


2 Answers

I have the same problem for a long time. But finally got a solution.

Build with Esbuild

(Follow instructions from Matt Pocock https://www.totaltypescript.com/build-a-node-app-with-typescript-and-esbuild)

  1. Adding Our Config Files

1.1 "type": "module" in package.json Next, add "type": "module" to the package.json file.

{
  // ...other properties
  "type": "module"
  // ...other properties
}

1.2 Dependencies Next, let's install our dependencies:

pnpm add -D typescript esbuild @types/node npm-run-all

1.3 TypeScript Config Add a tsconfig.json file at the root of the project with the following configuration:

{
  "compilerOptions": {
    /* Base Options: */
    "esModuleInterop": true,
    "skipLibCheck": true,
    "target": "es2022",
    "verbatimModuleSyntax": true,
    "allowJs": true,
    "resolveJsonModule": true,
    "moduleDetection": "force",
    /* Strictness */
    "strict": true,
    "noUncheckedIndexedAccess": true,
    /* If NOT transpiling with TypeScript: */
    "moduleResolution": "Bundler",
    "module": "ESNext",
    "noEmit": true,
    /* If your code doesn't run in the DOM: */
    "lib": ["es2022"]
  }
}
  1. Adding Our Scripts

2.1 build script Add a build script to package.json:

{
  // ...other properties
  "scripts": {
   "build": "esbuild src/index.ts --bundle --platform=node --outfile=lib/index.js --format=esm --external:./node_modules/*",
  }
  // ...other properties
}

2.2 dev script

{
  // ...other properties
  "scripts": {
    "dev:tsc": "tsc --watch --preserveWatchOutput",
    "dev:node": "firebase emulators:start --only functions",
    "dev:esbuild": "pnpm run build --watch",
    "dev": "run-p dev:*",
  }
  // ...other properties
}

Run pnpm dev

And finally you got your Firebase functions emulator running with Typescript, ESM and even wathc mode (watch is guide slow for me, takes 5-8s for functions to handle new code, but still it does it automatically🎉)

like image 148
kehitys Avatar answered Aug 14 '26 09:08

kehitys


You need to modify the import in your source (index.ts) from

import myGreeting from "./my-greeting";

to

import myGreeting from "./my-greeting.js";

TSC will not modify the import statements as far as I know.

like image 42
prodriguez02 Avatar answered Aug 14 '26 08:08

prodriguez02



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!