Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
hitsthings opened this issue Jan 12, 2022 · 2 comments

Comments

@hitsthings
Copy link

hitsthings commented Jan 12, 2022

[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.
@google-oss-bot
Copy link
Contributor

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.

@davidbielik
Copy link
Contributor

I'm seeing the same issue:

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants