Skip to content

Commit 7f99ac5

Browse files
authored
Add support ES modules for Firebase Functions (#3485)
\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.
1 parent 8064795 commit 7f99ac5

File tree

3 files changed

+41
-4
lines changed

3 files changed

+41
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Support loading Firebase Functions packaged as an ES module. (#3485)

src/deploy/functions/runtimes/node/triggerParser.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,28 @@ var EXIT = function () {
77
process.exit(0);
88
};
99

10-
(function () {
10+
/**
11+
* Dynamically load import function to prevent TypeScript from
12+
* transpiling into a require.
13+
*
14+
* See https://github.com/microsoft/TypeScript/issues/43329.
15+
*/
16+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
17+
const dynamicImport = new Function("modulePath", "return import(modulePath)");
18+
19+
async function loadModule(packageDir) {
20+
try {
21+
return require(packageDir);
22+
} catch (e) {
23+
if (e.code === "ERR_REQUIRE_ESM") {
24+
const mod = await dynamicImport(require.resolve(packageDir));
25+
return mod;
26+
}
27+
throw e;
28+
}
29+
}
30+
31+
(async function () {
1132
if (!process.send) {
1233
console.warn("Could not parse function triggers (process.send === undefined).");
1334
process.exit(1);
@@ -23,7 +44,7 @@ var EXIT = function () {
2344
var mod;
2445
var triggers = [];
2546
try {
26-
mod = require(packageDir);
47+
mod = await loadModule(packageDir);
2748
} catch (e) {
2849
if (e.code === "MODULE_NOT_FOUND") {
2950
process.send(

src/emulator/functionsEmulatorRuntime.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ import * as _ from "lodash";
2727
let triggers: EmulatedTriggerMap | undefined;
2828
let developerPkgJSON: PackageJSON | undefined;
2929

30+
/**
31+
* Dynamically load import function to prevent TypeScript from
32+
* transpiling into a require.
33+
*
34+
* See https://github.com/microsoft/TypeScript/issues/43329.
35+
*/
36+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
37+
const dynamicImport = new Function("modulePath", "return import(modulePath)");
38+
3039
function isFeatureEnabled(
3140
frb: FunctionsRuntimeBundle,
3241
feature: keyof FunctionsRuntimeFeatures
@@ -567,6 +576,7 @@ async function initializeFirebaseAdminStubs(frb: FunctionsRuntimeBundle): Promis
567576
// Stub the admin module in the require cache
568577
require.cache[adminResolution.resolution] = {
569578
exports: proxiedAdminModule,
579+
path: path.dirname(adminResolution.resolution),
570580
};
571581

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

781792
logDebug("firebase-functions has been stubbed.", {
@@ -1065,8 +1076,12 @@ async function initializeRuntime(
10651076
try {
10661077
triggerModule = require(frb.cwd);
10671078
} catch (err) {
1068-
await moduleResolutionDetective(frb, err);
1069-
return;
1079+
if (err.code !== "ERR_REQUIRE_ESM") {
1080+
await moduleResolutionDetective(frb, err);
1081+
return;
1082+
}
1083+
// tslint:disable:no-unsafe-assignment
1084+
triggerModule = await dynamicImport(require.resolve(frb.cwd));
10701085
}
10711086
}
10721087
if (extensionTriggers) {

0 commit comments

Comments
 (0)