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

Add support ES modules for Firebase Functions #3485

Merged
merged 12 commits into from
Jun 28, 2021
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Support loading Firebase Functions packaged as an ES module. (#3485)
25 changes: 23 additions & 2 deletions src/deploy/functions/runtimes/node/triggerParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,28 @@ var EXIT = function () {
process.exit(0);
};

(function () {
/**
* Dynamically load import function to prevent TypeScript from
* transpiling into a require.
*
* See https://github.com/microsoft/TypeScript/issues/43329.
*/
// eslint-disable-next-line @typescript-eslint/no-implied-eval
const dynamicImport = new Function("modulePath", "return import(modulePath)");

async function loadModule(packageDir) {
try {
return require(packageDir);
} catch (e) {
if (e.code === "ERR_REQUIRE_ESM") {
const mod = await dynamicImport(require.resolve(packageDir));
return mod;
}
throw e;
}
}

(async function () {
if (!process.send) {
console.warn("Could not parse function triggers (process.send === undefined).");
process.exit(1);
Expand All @@ -23,7 +44,7 @@ var EXIT = function () {
var mod;
var triggers = [];
try {
mod = require(packageDir);
mod = await loadModule(packageDir);
} catch (e) {
if (e.code === "MODULE_NOT_FOUND") {
process.send(
Expand Down
19 changes: 17 additions & 2 deletions src/emulator/functionsEmulatorRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ import * as _ from "lodash";
let triggers: EmulatedTriggerMap | undefined;
let developerPkgJSON: PackageJSON | undefined;

/**
* Dynamically load import function to prevent TypeScript from
* transpiling into a require.
*
* See https://github.com/microsoft/TypeScript/issues/43329.
*/
// eslint-disable-next-line @typescript-eslint/no-implied-eval
const dynamicImport = new Function("modulePath", "return import(modulePath)");

function isFeatureEnabled(
frb: FunctionsRuntimeBundle,
feature: keyof FunctionsRuntimeFeatures
Expand Down Expand Up @@ -567,6 +576,7 @@ async function initializeFirebaseAdminStubs(frb: FunctionsRuntimeBundle): Promis
// Stub the admin module in the require cache
require.cache[adminResolution.resolution] = {
exports: proxiedAdminModule,
path: path.dirname(adminResolution.resolution),
Copy link
Contributor Author

@taeold taeold Jun 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needed to be done - otherwise, the dynamic import called failed with errors like this:

TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received undefined
    at new NodeError (node:internal/errors:363:5)
    at validateString (node:internal/validators:119:11)
    at Object.resolve (node:path:1098:7)
    at Function.Module._nodeModulePaths (node:internal/modules/cjs/loader:625:17)
    at cjsPreparseModuleExports (node:internal/modules/esm/translators:258:30)
    at Loader.commonjsStrategy (node:internal/modules/esm/translators:188:35)

@samtstern Think this is safe modification, but I'm 100% unfamiliar with this code path. Let me know if this isn't going to work (I tested things out, and emulator continues to work normally).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems safe to me.

};

logDebug("firebase-admin has been stubbed.", {
Expand Down Expand Up @@ -776,6 +786,7 @@ async function initializeFunctionsConfigHelper(frb: FunctionsRuntimeBundle): Pro
// Stub the functions module in the require cache
require.cache[functionsResolution.resolution] = {
exports: proxiedFunctionsModule,
path: path.dirname(functionsResolution.resolution),
};

logDebug("firebase-functions has been stubbed.", {
Expand Down Expand Up @@ -1065,8 +1076,12 @@ async function initializeRuntime(
try {
triggerModule = require(frb.cwd);
} catch (err) {
await moduleResolutionDetective(frb, err);
return;
if (err.code !== "ERR_REQUIRE_ESM") {
await moduleResolutionDetective(frb, err);
return;
}
// tslint:disable:no-unsafe-assignment
triggerModule = await dynamicImport(require.resolve(frb.cwd));
}
}
if (extensionTriggers) {
Expand Down