From 10dcd948acf93de9d321879839a9dc11c9ba0b14 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Fri, 11 Jun 2021 12:00:41 -0700 Subject: [PATCH 01/11] Support deploying functions in ES modules. --- .../functions/runtimes/node/triggerParser.js | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/deploy/functions/runtimes/node/triggerParser.js b/src/deploy/functions/runtimes/node/triggerParser.js index 0a488027ff5..86d6858dbce 100644 --- a/src/deploy/functions/runtimes/node/triggerParser.js +++ b/src/deploy/functions/runtimes/node/triggerParser.js @@ -7,7 +7,25 @@ var EXIT = function () { process.exit(0); }; -(function () { +// Dynamic import function required to load user code packaged as an +// ES module is only available on Node.js v13.2.0 and up. +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#browser_compatibility +// eslint-disable-next-line @typescript-eslint/no-implied-eval +var dynamicImport = new Function("modulePath", "return import(modulePath)"); + +function loadModule(packageDir) { + try { + return Promise.resolve(require(packageDir)); + } catch (e) { + if (e.code === "ERR_REQUIRE_ESM") { + var pkg = require.resolve(packageDir); + return dynamicImport(pkg); + } + throw e; + } +} + +(async function () { if (!process.send) { console.warn("Could not parse function triggers (process.send === undefined)."); process.exit(1); @@ -23,7 +41,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( From 24bb020e0be01287352a4735039ce23b83472d73 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Fri, 11 Jun 2021 12:43:48 -0700 Subject: [PATCH 02/11] Add better comments. --- src/deploy/functions/runtimes/node/triggerParser.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/deploy/functions/runtimes/node/triggerParser.js b/src/deploy/functions/runtimes/node/triggerParser.js index 86d6858dbce..031080e5429 100644 --- a/src/deploy/functions/runtimes/node/triggerParser.js +++ b/src/deploy/functions/runtimes/node/triggerParser.js @@ -7,9 +7,12 @@ var EXIT = function () { process.exit(0); }; -// Dynamic import function required to load user code packaged as an -// ES module is only available on Node.js v13.2.0 and up. -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#browser_compatibility +/** + * 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 var dynamicImport = new Function("modulePath", "return import(modulePath)"); From 9664b026a1f1823700467d02eeb18bfe4032b18e Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Sun, 27 Jun 2021 21:00:01 +0900 Subject: [PATCH 03/11] Use const. --- src/deploy/functions/runtimes/node/triggerParser.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/deploy/functions/runtimes/node/triggerParser.js b/src/deploy/functions/runtimes/node/triggerParser.js index 031080e5429..ff1bdd26baf 100644 --- a/src/deploy/functions/runtimes/node/triggerParser.js +++ b/src/deploy/functions/runtimes/node/triggerParser.js @@ -14,15 +14,15 @@ var EXIT = function () { * See https://github.com/microsoft/TypeScript/issues/43329. */ // eslint-disable-next-line @typescript-eslint/no-implied-eval -var dynamicImport = new Function("modulePath", "return import(modulePath)"); +const dynamicImport = new Function("modulePath", "return import(modulePath)"); -function loadModule(packageDir) { +async function loadModule(packageDir) { try { - return Promise.resolve(require(packageDir)); + return require(packageDir); } catch (e) { if (e.code === "ERR_REQUIRE_ESM") { - var pkg = require.resolve(packageDir); - return dynamicImport(pkg); + const mod = await dynamicImport(require.resolve(packageDir)); + return mod; } throw e; } From 750b58f7ee0974783c34a04d3a9c20120af3c434 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Sun, 27 Jun 2021 21:01:18 +0900 Subject: [PATCH 04/11] Allow emulator to load ESM (doesn't work). --- src/emulator/functionsEmulatorRuntime.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/emulator/functionsEmulatorRuntime.ts b/src/emulator/functionsEmulatorRuntime.ts index ac4e98842de..97ef6d63915 100644 --- a/src/emulator/functionsEmulatorRuntime.ts +++ b/src/emulator/functionsEmulatorRuntime.ts @@ -27,6 +27,18 @@ 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", + "console.log(modulePath); return import(modulePath)" +); + function isFeatureEnabled( frb: FunctionsRuntimeBundle, feature: keyof FunctionsRuntimeFeatures @@ -1065,8 +1077,13 @@ async function initializeRuntime( try { triggerModule = require(frb.cwd); } catch (err) { - await moduleResolutionDetective(frb, err); - return; + if (err.code === "ERR_REQUIRE_ESM") { + // tslint:disable:no-unsafe-assignment + triggerModule = await dynamicImport(require.resolve(frb.cwd)); + } else { + await moduleResolutionDetective(frb, err); + return; + } } } if (extensionTriggers) { From c7dbc9cdfebf05fb9c56e424956f1ecfff5644ca Mon Sep 17 00:00:00 2001 From: "Daniel Y. Lee" Date: Sun, 27 Jun 2021 23:32:22 +0900 Subject: [PATCH 05/11] Fix module cache corruption. --- src/emulator/functionsEmulatorRuntime.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/emulator/functionsEmulatorRuntime.ts b/src/emulator/functionsEmulatorRuntime.ts index 97ef6d63915..86c831c05ed 100644 --- a/src/emulator/functionsEmulatorRuntime.ts +++ b/src/emulator/functionsEmulatorRuntime.ts @@ -579,6 +579,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.", { @@ -788,6 +789,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.", { From 4475010ec787573a0abb56daab20325c2792186a Mon Sep 17 00:00:00 2001 From: "Daniel Y. Lee" Date: Sun, 27 Jun 2021 23:35:54 +0900 Subject: [PATCH 06/11] Remove debug console log. --- src/emulator/functionsEmulatorRuntime.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/emulator/functionsEmulatorRuntime.ts b/src/emulator/functionsEmulatorRuntime.ts index 86c831c05ed..9a3943534b3 100644 --- a/src/emulator/functionsEmulatorRuntime.ts +++ b/src/emulator/functionsEmulatorRuntime.ts @@ -34,10 +34,7 @@ let developerPkgJSON: PackageJSON | undefined; * See https://github.com/microsoft/TypeScript/issues/43329. */ // eslint-disable-next-line @typescript-eslint/no-implied-eval -const dynamicImport = new Function( - "modulePath", - "console.log(modulePath); return import(modulePath)" -); +const dynamicImport = new Function("modulePath", "return import(modulePath)"); function isFeatureEnabled( frb: FunctionsRuntimeBundle, From 2ef11bc95017d733276395440d3ba4a9b2712246 Mon Sep 17 00:00:00 2001 From: "Daniel Y. Lee" Date: Sun, 27 Jun 2021 23:37:40 +0900 Subject: [PATCH 07/11] Remove unnecessary block. --- src/emulator/functionsEmulatorRuntime.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/emulator/functionsEmulatorRuntime.ts b/src/emulator/functionsEmulatorRuntime.ts index 9a3943534b3..972b3442c0d 100644 --- a/src/emulator/functionsEmulatorRuntime.ts +++ b/src/emulator/functionsEmulatorRuntime.ts @@ -1076,13 +1076,12 @@ async function initializeRuntime( try { triggerModule = require(frb.cwd); } catch (err) { - if (err.code === "ERR_REQUIRE_ESM") { - // tslint:disable:no-unsafe-assignment - triggerModule = await dynamicImport(require.resolve(frb.cwd)); - } else { + 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) { From eb63f64c0bcc90011eb16166adaab7162c32794d Mon Sep 17 00:00:00 2001 From: "Daniel Y. Lee" Date: Sun, 27 Jun 2021 23:46:49 +0900 Subject: [PATCH 08/11] Add changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..ff46b5e7ac4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Support loading Firebase Functions packaged as as ES module. (#3485) \ No newline at end of file From 2bbeec1b9c51f673cffedb26317a6fda22402986 Mon Sep 17 00:00:00 2001 From: "Daniel Y. Lee" Date: Sun, 27 Jun 2021 23:48:00 +0900 Subject: [PATCH 09/11] Fix linter errors. --- src/emulator/functionsEmulatorRuntime.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/emulator/functionsEmulatorRuntime.ts b/src/emulator/functionsEmulatorRuntime.ts index 972b3442c0d..9b99530e9a9 100644 --- a/src/emulator/functionsEmulatorRuntime.ts +++ b/src/emulator/functionsEmulatorRuntime.ts @@ -576,7 +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) + path: path.dirname(adminResolution.resolution), }; logDebug("firebase-admin has been stubbed.", { @@ -786,7 +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) + path: path.dirname(functionsResolution.resolution), }; logDebug("firebase-functions has been stubbed.", { From fb448048fe03ff3695cc48d487073777b17b4f37 Mon Sep 17 00:00:00 2001 From: "Daniel Y. Lee" Date: Sun, 27 Jun 2021 23:55:52 +0900 Subject: [PATCH 10/11] Fix lint issue on CHANGELOG. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff46b5e7ac4..8dabe13e429 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1 @@ -- Support loading Firebase Functions packaged as as ES module. (#3485) \ No newline at end of file +- Support loading Firebase Functions packaged as as ES module. (#3485) From 22421775a05ce50aad14fb6eb7570fd5e79293d3 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Mon, 28 Jun 2021 20:08:44 +0900 Subject: [PATCH 11/11] Fix typo. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dabe13e429..0e71b404de7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1 @@ -- Support loading Firebase Functions packaged as as ES module. (#3485) +- Support loading Firebase Functions packaged as an ES module. (#3485)