firebase / firebase/firebase-tools
Functions code is loaded multiple times by emulator without warning for some ESM related error
- 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
Assessment
This issue has not been assessed yet.