0

My Firebase Cloud Functions, which use ES modules (import and export, not require and exports), are running in JavaScript but not in TypeScript. I'm getting this error:

SyntaxError: Cannot use import statement outside a module

My index.ts file isn't recognized as an ES module and my identical index.js is recognized as an ES module. I tried renaming it index.mts, that didn't help. This suggests that tsconfig.json isn't configured correctly.

{
  "compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "node",
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "outDir": "src",
    "sourceMap": true,
    "strict": true,
    "target": "ESNext"
  },
  "compileOnSave": true,
  "include": [
    "src/index.ts"
  ],
  "exclude": ["wwwroot"],
}

Here's my package.json:

{
    "name": "functions",
    "type": "module",
    "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"
    },
    "engines": {
        "node": "16"
    },
    "main": "src/index.ts", // change to "src/index.js" for JavaScript
    "dependencies": {
        "firebase-admin": "^11.2.0",
        "firebase-functions": "^4.0.1"
    },
    "devDependencies": {
        "typescript": "^4.8.4"
    },
    "private": true
}

And my index.ts Firebase Cloud Functions.

import * as functions from "firebase-functions";

export default function helloWorld() {
    console.log("Hello, world!");
};

export const makeUppercase = functions.firestore.document('messages/{docId}').onCreate((snap: any, context: any) => {
    const original = snap.data().original;
    functions.logger.log('Uppercasing', context.params.docId, original);
    const uppercase = original.toUpperCase();
    return snap.ref.set({ uppercase }, { merge: true });
});
Thomas David Kehoe
  • 10,040
  • 14
  • 61
  • 100
  • Does this answer your question? [Typescript: Cannot use import statement outside a module](https://stackoverflow.com/questions/58273824/typescript-cannot-use-import-statement-outside-a-module) – Marc Anthony B Oct 25 '22 at 06:09
  • No, I'd already put `"type": "module"` in `package.json`. Changing the filename to `index.mts` didn't help. – Thomas David Kehoe Oct 25 '22 at 17:23

2 Answers2

0

I was missing the step npm run serve. To use TypeScript with Firebase Functions you run firebase init functions and select TypeScript. This creates a directory structure

myproject
├── functions
│   ├── firebase-debug.log
│   ├── firestore-debug.log
│   ├── lib
│   │   ├── index.js
│   │   └── index.js.map
│   ├── node_modules
│   ├── package-lock.json
│   ├── package.json
│   ├── src
│   │   └── index.ts
│   ├── tsconfig.json
│   └── ui-debug.log

The directory lib is missing and the file lib/index.js. Write a function or comment out the default function and then run npm run serve in the functions directory. This should make the lib directory and the lib/index.js file, with your function transpiled into JavaScript. Start the emulator and your function should run.

Notice that the transpiled lib/index.js uses require and exports when your TypeScript src/index.ts uses import and export. This is what throws the error

SyntaxError: Cannot use import statement outside a module

It's saying that needs require, not import. Whether lib/index.js is a ES module or a CommonJS module is beyond me but it doesn't matter, you never have to touch lib/index.js.

You'll have to run npm run serve every time you update your functions in src/index.ts.

Thomas David Kehoe
  • 10,040
  • 14
  • 61
  • 100
0

This should solve if for you:

{
  "compilerOptions": {
    "module": "esnext",
    "moduleResolution": "nodenext",
    "esModuleInterop": true,
    "allowJs": true,
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "outDir": "lib",
    "sourceMap": true,
    "strict": true,
    "target": "es2017",
    "typeRoots": ["./node_modules/@types", "src/typings"]
  },
  "compileOnSave": true,
  "include": [
    "src"
  ]
}

specifically: "moduleResolution": "nodenext", and "esModuleInterop": true,

I have also added a typeRoots folder that's optional and you can place it inside your ./functions/src folder.

You also only need to npm run build and not serve from within your functions folder.

Davie
  • 162
  • 9