diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..0e71b404de7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Support loading Firebase Functions packaged as an ES module. (#3485) diff --git a/src/deploy/functions/runtimes/node/triggerParser.js b/src/deploy/functions/runtimes/node/triggerParser.js index 0a488027ff5..ff1bdd26baf 100644 --- a/src/deploy/functions/runtimes/node/triggerParser.js +++ b/src/deploy/functions/runtimes/node/triggerParser.js @@ -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); @@ -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( diff --git a/src/emulator/functionsEmulatorRuntime.ts b/src/emulator/functionsEmulatorRuntime.ts index ac4e98842de..9b99530e9a9 100644 --- a/src/emulator/functionsEmulatorRuntime.ts +++ b/src/emulator/functionsEmulatorRuntime.ts @@ -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 @@ -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.", { @@ -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.", { @@ -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) {