Thomas David Kehoe
Thomas David Kehoe

Reputation: 10930

How do I use `import` for npm modules in Firebase Cloud Functions?

My JavaScript Firebase Cloud Functions are running with npm modules imported with require. Now I want to use npm modules that are installed with import, not require. The Firebase Emulators throw this error:

 SyntaxError: Cannot use import statement outside a module

The Node.js documentation says:

import statements

An import statement can reference an ES module or a CommonJS module. import statements are permitted only in ES modules, but dynamic import() expressions are supported in CommonJS for loading ES modules.

When importing CommonJS modules, the module.exports object is provided as the default export. Named exports may be available, provided by static analysis as a convenience for better ecosystem compatibility.

require

The CommonJS module require always treats the files it references as CommonJS.

Using require to load an ES module is not supported because ES modules have asynchronous execution. Instead, use import() to load an ES module from a CommonJS module.

If I understand this correctly, Node modules can either be ES or CommonJS, that import handles both types, and require handles only CommonJS.

This documentation also suggests that my Cloud Function also needs to be an ES module to use import. Should the error message say:

SyntaxError: Cannot use import statement outside an ES module

That seems to be the problem: my Cloud Functions aren't in an ES module. How do I make an ES module for my Cloud Functions?

Reproduce error

Here's how to reproduce the error. Make a new directory and install Firebase, following the official documentation:

npm install -g firebase-tools
firebase init

Select Emulators.

Select Create a new project.

Select Functions Emulator and Firestore Emulator.

Emulators Setup

Accept the default ports. Download the emulators.

Functions Setup

Select TypeScript. Don't use ESLint. Install dependencies.

Emulate execution of your functions

firebase emulators:start

Here's the default index.ts.

import * as functions from "firebase-functions";

// // Start writing Firebase Functions
// // https://firebase.google.com/docs/functions/typescript
//
export const helloWorld = functions.https.onRequest((request, response) => {
  functions.logger.info("Hello logs!", {structuredData: true});
  response.send("Hello from Firebase!");
});

I also tried the module provided in the TypeScript documentation on modules:

import * as functions from "firebase-functions";

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

Fix path to index.ts

The first bug in functions/package.json is:

functions/lib/index.js does not exist, can't deploy Cloud Functions

Fix this by opening functions/package.json and changing

"main": "lib/index.js",

to

"main": "src/index.ts",

Module error

The next error is

Cannot use import statement outside a module

This is where I'm stuck. This seems to be saying that my Firebase Cloud Functions are not in an ES Module.

package.json

This question said to put "type": "module", in functions/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",
    "dependencies": {
        "firebase-admin": "^11.2.0",
        "firebase-functions": "^4.0.1",
        "got": "^12.5.2"
    },
    "devDependencies": {
        "typescript": "^4.7.4"
    },
    "private": true
}

That doesn't fix the error.

tsconfig.json

I opened tsconfig.json and changed "module": "CommonJS", to "module": "ESNext", and I changed "target": "es2017" to "target": "ESNext". This question explains what ESNext is.

The emulator continued to throw the error. Here's my tsconfig.json file:

{
  "compilerOptions": {
    "module": "ESNext",
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "outDir": "lib",
    "sourceMap": true,
    "strict": true,
    "target": "ESNext"
  },
  "compileOnSave": true,
  "include": [
    "src"
  ]
}

Recommended tsconfig.json

The TypeScript Handbook recommends this tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2015",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "$schema": "https://json.schemastore.org/tsconfig",
  "display": "Recommended"
}

That throws the same error.

Misconfiguration

There's clearly more than one misconfiguration in the default package.json and tsconfig.json. If someone can tell me how to configure these correctly I'll do a pull request to firebase-tools.

I'm using Node 18.1.0. Firebase recommends Node 16.

Firebase Cloud Functions documentation on handling dependencies says to use require with JavaScript cloud functions and import with TypeScript cloud functions.

Use the Node.js require() function to load any Node.js module you have installed. You can also use the require() function to import local files you deploy alongside your function.

If you are writing functions in TypeScript, use the import statement in the same way to load any Node.js module you have installed.

That doesn't make sense if the Node.js documentation is correct. require() can't load any Node module, it only handles CommonJS modules. The paragraph about TypeScript seems to say that you can't use import with JavaScript Cloud Functions.

ts-node

Would ts-node help?

Upvotes: 1

Views: 1162

Answers (0)

Related Questions