Skip to content

Commit

Permalink
Add support ES modules for Firebase Functions (firebase#3485)
Browse files Browse the repository at this point in the history
\To import modules packaged as ES module, we have to use import instead of require. Both the emulator and function deploy code hardcodes require to load user's function code and ends up throwing ERR_REQUIRE_ESM when given ES modules.

We will now try using import when require fails with an ERR_REQUIRE_ESM error. This is a bit lazy - we could've detected whether given module is ES module vs CommonJS and dispatch require vs import appropriately - but I think it simplifies the script while still being correct.
  • Loading branch information
taeold authored and devpeerapong committed Dec 14, 2021
1 parent 4ec1e69 commit d507b1c
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 4 deletions.
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),
};

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

0 comments on commit d507b1c

Please sign in to comment.