firebase / firebase/firebase-tools

Functions code is loaded multiple times by emulator without warning for some ESM related error

Open
#4,009 2 comments 2 reactions 1 assignee Claimed by @Berlioz View on GitHub
emulator-suite emulators: functions type: bug
Dominant language
TypeScript
Stars
4.5k
Forks
1.3k
Avg merge
1d 12h
Merged PRs (30d)
84

Description

### [REQUIRED] Environment info

**firebase-tools:** 9.23.3

**Platform:** macOS

### [REQUIRED] Test case

```js
const admin = require("firebase-admin")

admin.initializeApp() // called twice

exports.user = require("node-fetch") // causes ESM error
```

### [REQUIRED] Steps to reproduce

`firebase emulators:start` with index.js that looks like (generated from TS, similar to above):

```js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.user = void 0;
const admin = require("firebase-admin");

console.log('before');
console.trace("hmm");
if (admin.apps.length) {
console.log(admin.apps.filter(Boolean).map(a => a.name + a.options.projectId));
}

admin.initializeApp();

console.log("after");

exports.user = require("./user");
//# sourceMappingURL=index.js.map
```

Note that I require my code after calling initializeApp. Before I added the console noise it was just (TS):

```js
import * as admin from 'firebase-admin'

admin.initializeApp()

export * as user from './user'
```

### [REQUIRED] Expected behavior

Code is loaded once and prints:

```
i functions: Watching "/Users/hitsthings/Code/in8/in8app-server/functions" for Cloud Functions...
> before
> Trace: hmm
> at Object. (/Users/hitsthings/Code/in8/in8app-server/functions/lib/index.js:6:9)
> at Module._compile (internal/modules/cjs/loader.js:1085:14)
> at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
> at Module.load (internal/modules/cjs/loader.js:950:32)
> at Function.Module._load (internal/modules/cjs/loader.js:790:12)
> at Module.require (internal/modules/cjs/loader.js:974:19)
> at require (internal/modules/cjs/helpers.js:93:18)
> at initializeRuntime (/Users/hitsthings/Code/in8/in8app-server/node_modules/firebase-tools/lib/emulator/functionsEmulatorRuntime.js:640:29)
> at processTicksAndRejections (internal/process/task_queues.js:95:5)
> at async handleMessage (/Users/hitsthings/Code/in8/in8app-server/node_modules/firebase-tools/lib/emulator/functionsEmulatorRuntime.js:684:20)
> after
> (SOME ERROR FROM ./user)???
```

### [REQUIRED] Actual behavior

Code is loaded twice and prints:

```
i functions: Watching "/Users/hitsthings/Code/in8/in8app-server/functions" for Cloud Functions...
> before
> Trace: hmm
> at Object. (/Users/hitsthings/Code/in8/in8app-server/functions/lib/index.js:6:9)
> at Module._compile (internal/modules/cjs/loader.js:1085:14)
> at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
> at Module.load (internal/modules/cjs/loader.js:950:32)
> at Function.Module._load (internal/modules/cjs/loader.js:790:12)
> at Module.require (internal/modules/cjs/loader.js:974:19)
> at require (internal/modules/cjs/helpers.js:93:18)
> at initializeRuntime (/Users/hitsthings/Code/in8/in8app-server/node_modules/firebase-tools/lib/emulator/functionsEmulatorRuntime.js:640:29)
> at processTicksAndRejections (internal/process/task_queues.js:95:5)
> at async handleMessage (/Users/hitsthings/Code/in8/in8app-server/node_modules/firebase-tools/lib/emulator/functionsEmulatorRuntime.js:684:20)
> after
> before
> [ '[DEFAULT]in8app' ]
> Trace: hmm
> at Object. (/Users/hitsthings/Code/in8/in8app-server/functions/lib/index.js:6:9)
> at Module._compile (internal/modules/cjs/loader.js:1085:14)
> at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
> at Module.load (internal/modules/cjs/loader.js:950:32)
> at Function.Module._load (internal/modules/cjs/loader.js:790:12)
> at ModuleWrap. (internal/modules/esm/translators.js:199:29)
> at ModuleJob.run (internal/modules/esm/module_job.js:183:25)
> at async Loader.import (internal/modules/esm/loader.js:178:24)
> at async initializeRuntime (/Users/hitsthings/Code/in8/in8app-server/node_modules/firebase-tools/lib/emulator/functionsEmulatorRuntime.js:649:29)
> at async handleMessage (/Users/hitsthings/Code/in8/in8app-server/node_modules/firebase-tools/lib/emulator/functionsEmulatorRuntime.js:684:20)
⚠ functions: The default Firebase app already exists. This means you called initializeApp() more than once without providing an app name as the second argument. In most cases you only need to call initializeApp() once. But if you do want to initialize multiple apps, pass a second argument to initializeApp() to give each app a unique name.
⚠ Your function was killed because it raised an unhandled error.

```

Looking at the relevant lines of `functionsEmulatorRuntime.js` in that trace brings up:

```js

try {
640: triggerModule = require(frb.cwd);
}
catch (err) {
if (err.code !== "ERR_REQUIRE_ESM") {
await moduleResolutionDetective(frb, err);
return;
}
const modulePath = require.resolve(frb.cwd);
const moduleURL = url_1.pathToFileURL(modulePath).href;
649: triggerModule = await dynamicImport(moduleURL);
}
```

My code is being `require()`'d, then `dynamicImport()`'d. Both times, `initializeApp()` is run successfully (which is bad).

If I conditionally call `initializeApp()` only if there are no apps yet, I do get a (somewhat) useful message about `node-fetch`. But it was too late in my case:

```
⚠ functions: Must use import to load ES Module: /Users/hitsthings/Code/in8/in8app-server/functions/node_modules/node-fetch/src/index.js
require() of ES modules is not supported.
require() of /Users/hitsthings/Code/in8/in8app-server/functions/node_modules/node-fetch/src/index.js from /Users/hitsthings/Code/in8/in8app-server/functions/lib/user/in8-subscription.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules.
Instead rename index.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from /Users/hitsthings/Code/in8/in8app-server/functions/node_modules/node-fetch/package.json.
```

### Suggested fixes

1. Add logging of the initial ESM error before retrying and potentially creating new errors.
2. Switch the automatic ESM compile to a configuration option OR properly clean up the environment before running the code a second time.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.