firebase-tools icon indicating copy to clipboard operation
firebase-tools copied to clipboard

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

Open hitsthings opened this issue 3 years ago • 2 comments

[REQUIRED] Environment info

firebase-tools: 9.23.3

Platform: macOS

[REQUIRED] Test case

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):

"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):

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.<anonymous> (/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.<anonymous> (/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.<anonymous> (/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.<anonymous> (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:


        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.

hitsthings avatar Jan 12 '22 04:01 hitsthings

This issue does not have all the information required by the template. Looks like you forgot to fill out some sections. Please update the issue with more information.

google-oss-bot avatar Jan 12 '22 04:01 google-oss-bot

I'm seeing the same issue:

"firebase-admin": "^11.6.0",
"firebase-functions": "^4.2.0",
"firebase-functions-test": "^3.0.0",

davidbielik avatar Apr 08 '23 18:04 davidbielik