From 87a5896221a726578c3433071755fba3465824f4 Mon Sep 17 00:00:00 2001 From: Zack Jackson Date: Tue, 18 Jul 2023 17:17:32 -0700 Subject: [PATCH] fix: Fix call undefined delegate (#1149) * fix: thrown error during chunk correlation of empty graph connection * fix: call of undefined errors in delegate modules * test: fix tests of delegate plugin * chore: prettier * refactor: improve package fragmentation re-export underlaying utilities to allow for less fragmentation of package versions for the sake of access to these tools * fix: prevent module graph connection conflict (#1151) * fix: prevent module graph connection conflict I remove eager modules from remote, but in doing so also removes dependencies of delegate moduels, this preserves dependency tree of delegate module and removes all other eager references from eager:true * test: fix mock of delegate module eager flag * test: fix mock of delegate module eager flag * test: fix mock of delegate module eager flag * feat: support next/image * style:lint fix * style:lint fix * style:lint fix --- .eslintrc.json | 2 +- apps/3000-home/next.config.js | 3 +- apps/3000-home/pages/dynamic-remote.js | 2 +- apps/3000-home/pages/index.tsx | 2 - apps/3000-home/remote-delegate.js | 4 +- apps/3001-shop/next.config.js | 2 +- apps/3001-shop/pages/_document.js | 1 + apps/3001-shop/remote-delegate.js | 19 +- apps/3002-checkout/next.config.js | 3 +- apps/3002-checkout/pages/_document.js | 1 + apps/3002-checkout/remote-delegate.js | 2 +- package-lock.json | 189 ++++++++++++++++ package.json | 1 + packages/nextjs-mf/README.md | 6 +- packages/nextjs-mf/importDelegatedModule.ts | 3 + packages/nextjs-mf/node.ts | 1 + packages/nextjs-mf/src/default-delegate.ts | 7 +- packages/nextjs-mf/src/internal.ts | 6 + .../src/loaders/patchDefaultSharedLoader.ts | 1 + .../apply-client-plugins.ts | 1 - .../RemoveEagerModulesFromRuntimePlugin.ts | 7 +- packages/nextjs-mf/utilities.ts | 1 + .../node/src/plugins/NodeFederationPlugin.ts | 2 +- packages/utilities/project.json | 25 +-- .../src/plugins/DelegateModulesPlugin.test.ts | 32 ++- .../src/plugins/DelegateModulesPlugin.ts | 60 ++++-- packages/utilities/src/utils/common.ts | 154 +------------ .../src/utils/correctImportPath.spec.ts | 21 +- .../utilities/src/utils/correctImportPath.ts | 1 - .../src/utils/importDelegatedModule.ts | 4 +- packages/utilities/src/utils/importRemote.ts | 4 +- packages/utilities/src/utils/pure.ts | 204 ++++++++++++++++++ 32 files changed, 532 insertions(+), 239 deletions(-) create mode 100644 packages/nextjs-mf/importDelegatedModule.ts create mode 100644 packages/nextjs-mf/node.ts create mode 100644 packages/nextjs-mf/utilities.ts create mode 100644 packages/utilities/src/utils/pure.ts diff --git a/.eslintrc.json b/.eslintrc.json index 873e3d83015..4e068735b92 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -7,7 +7,7 @@ "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], "rules": { "@nrwl/nx/enforce-module-boundaries": [ - "error", + "warn", { "enforceBuildableLibDependency": true, "allow": [], diff --git a/apps/3000-home/next.config.js b/apps/3000-home/next.config.js index 8ee86bd40f0..e7f80e8e812 100644 --- a/apps/3000-home/next.config.js +++ b/apps/3000-home/next.config.js @@ -3,7 +3,7 @@ const { registerPluginTSTranspiler } = require('nx/src/utils/nx-plugin.js'); registerPluginTSTranspiler(); const { withNx } = require('@nrwl/next/plugins/with-nx'); const NextFederationPlugin = require('@module-federation/nextjs-mf'); -const { createDelegatedModule } = require('@module-federation/utilities'); +const { createDelegatedModule } = require('@module-federation/nextjs-mf/utilities'); /** * @type {import('@nrwl/next/plugins/with-nx').WithNxOptions} @@ -53,6 +53,7 @@ const nextConfig = { antd: {}, }, extraOptions: { + debug:false, exposePages: true, enableImageLoaderFix: true, enableUrlLoaderFix: true, diff --git a/apps/3000-home/pages/dynamic-remote.js b/apps/3000-home/pages/dynamic-remote.js index f13784149a9..a47824411ec 100644 --- a/apps/3000-home/pages/dynamic-remote.js +++ b/apps/3000-home/pages/dynamic-remote.js @@ -1,6 +1,6 @@ import React from 'react'; //eslint-disable-next-line -import { injectScript } from '@module-federation/utilities'; +import { injectScript } from '@module-federation/nextjs-mf/utilities'; // example of dynamic remote import on server and client const isServer = typeof window === 'undefined'; //could also use diff --git a/apps/3000-home/pages/index.tsx b/apps/3000-home/pages/index.tsx index de8dc3815e7..0cabaae1556 100644 --- a/apps/3000-home/pages/index.tsx +++ b/apps/3000-home/pages/index.tsx @@ -2,8 +2,6 @@ import React, { Suspense, lazy } from 'react'; import Head from 'next/head'; import CheckoutTitle from 'checkout/CheckoutTitle'; import ButtonOldAnt from 'checkout/ButtonOldAnt'; -console.log(ButtonOldAnt); -// console.log('cc', cc); // const CheckoutTitle = lazy(() => import('checkout/CheckoutTitle')); // const ButtonOldAnt = lazy(() => import('checkout/ButtonOldAnt')); const WebpackSvgRemote = lazy(() => diff --git a/apps/3000-home/remote-delegate.js b/apps/3000-home/remote-delegate.js index bf9a185c28a..a73fb8a8dbb 100644 --- a/apps/3000-home/remote-delegate.js +++ b/apps/3000-home/remote-delegate.js @@ -1,3 +1,4 @@ +import importDelegatedModule from "@module-federation/nextjs-mf/importDelegatedModule"; /* eslint-disable no-undef */ // eslint-disable-next-line no-async-promise-executor module.exports = new Promise(async (resolve, reject) => { @@ -5,9 +6,6 @@ module.exports = new Promise(async (resolve, reject) => { const currentRequest = new URLSearchParams(__resourceQuery).get('remote'); const [global, url] = currentRequest.split('@'); - const { importDelegatedModule } = await import( - '@module-federation/utilities/src/utils/importDelegatedModule' - ); importDelegatedModule({ global, diff --git a/apps/3001-shop/next.config.js b/apps/3001-shop/next.config.js index 148a8678a2a..56bb82640fb 100644 --- a/apps/3001-shop/next.config.js +++ b/apps/3001-shop/next.config.js @@ -4,7 +4,7 @@ registerPluginTSTranspiler(); const { withNx } = require('@nx/next/plugins/with-nx'); const NextFederationPlugin = require('@module-federation/nextjs-mf'); -const { createDelegatedModule } = require('@module-federation/utilities'); +const { createDelegatedModule } = require('@module-federation/nextjs-mf/utilities'); /** * @type {import('@nx/next/plugins/with-nx').WithNxOptions} diff --git a/apps/3001-shop/pages/_document.js b/apps/3001-shop/pages/_document.js index b807088d48a..4af3d6f93fb 100644 --- a/apps/3001-shop/pages/_document.js +++ b/apps/3001-shop/pages/_document.js @@ -1,3 +1,4 @@ +//eslint-disable @nrwl/nx/enforce-module-boundaries import React from 'react'; import Document, { Html, Head, Main, NextScript } from 'next/document'; import { diff --git a/apps/3001-shop/remote-delegate.js b/apps/3001-shop/remote-delegate.js index 789450d7d47..e6404065f67 100644 --- a/apps/3001-shop/remote-delegate.js +++ b/apps/3001-shop/remote-delegate.js @@ -1,29 +1,22 @@ /* eslint-disable no-undef */ - -// Delegates are currently not used in this example, but are left here for testing. // eslint-disable-next-line no-async-promise-executor module.exports = new Promise(async (resolve, reject) => { - const { importDelegatedModule } = await import( - '@module-federation/utilities' - ); - - //eslint-disable-next-line - // console.log('Delegate being called for', __resourceQuery, __webpack_runtime_id__); //eslint-disable-next-line const currentRequest = new URLSearchParams(__resourceQuery).get('remote'); const [global, url] = currentRequest.split('@'); + const {importDelegatedModule} = await import("@module-federation/nextjs-mf/importDelegatedModule") importDelegatedModule({ global, url: url + '?' + Date.now(), }) .then(async (remote) => { - // console.log( - // __resourceQuery, - // 'resolved remote from', - // __webpack_runtime_id__ - // ); + console.log( + __resourceQuery, + 'resolved remote from', + __webpack_runtime_id__ + ); resolve(remote); }) diff --git a/apps/3002-checkout/next.config.js b/apps/3002-checkout/next.config.js index 2af673ea21a..6c0f2448ba4 100644 --- a/apps/3002-checkout/next.config.js +++ b/apps/3002-checkout/next.config.js @@ -4,7 +4,8 @@ registerPluginTSTranspiler(); const { withNx } = require('@nx/next/plugins/with-nx'); const NextFederationPlugin = require('@module-federation/nextjs-mf'); -const { createDelegatedModule } = require('@module-federation/utilities'); +const { createDelegatedModule } = require('@module-federation/nextjs-mf/utilities'); + /** * @type {import('@nx/next/plugins/with-nx').WithNxOptions} diff --git a/apps/3002-checkout/pages/_document.js b/apps/3002-checkout/pages/_document.js index b807088d48a..b6392fbe91a 100644 --- a/apps/3002-checkout/pages/_document.js +++ b/apps/3002-checkout/pages/_document.js @@ -1,5 +1,6 @@ import React from 'react'; import Document, { Html, Head, Main, NextScript } from 'next/document'; +//eslint-disable-next-line @nrwl/nx/enforce-module-boundaries import { revalidate, FlushedChunks, diff --git a/apps/3002-checkout/remote-delegate.js b/apps/3002-checkout/remote-delegate.js index 0fad24d5356..dd032e1724f 100644 --- a/apps/3002-checkout/remote-delegate.js +++ b/apps/3002-checkout/remote-delegate.js @@ -8,7 +8,7 @@ module.exports = new Promise(async (resolve, reject) => { //eslint-disable-next-line const currentRequest = new URLSearchParams(__resourceQuery).get('remote'); const { importDelegatedModule } = await import( - '@module-federation/utilities' + '@module-federation/nextjs-mf/importDelegatedModule' ); const [global, url] = currentRequest.split('@'); diff --git a/package-lock.json b/package-lock.json index c68a384ab39..60e28ed7435 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4434,6 +4434,126 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@next/swc-darwin-x64": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.2.tgz", + "integrity": "sha512-iZuYr7ZvGLPjPmfhhMl0ISm+z8EiyLBC1bLyFwGBxkWmPXqdJ60mzuTaDSr5WezDwv0fz32HB7JHmRC6JVHSZg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.2.tgz", + "integrity": "sha512-2xVabFtIge6BJTcJrW8YuUnYTuQjh4jEuRuS2mscyNVOj6zUZkom3CQg+egKOoS+zh2rrro66ffSKIS+ztFJTg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.2.tgz", + "integrity": "sha512-wKRCQ27xCUJx5d6IivfjYGq8oVngqIhlhSAJntgXLt7Uo9sRT/3EppMHqUZRfyuNBTbykEre1s5166z+pvRB5A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.2.tgz", + "integrity": "sha512-NpCa+UVhhuNeaFVUP1Bftm0uqtvLWq2JTm7+Ta48+2Uqj2mNXrDIvyn1DY/ZEfmW/1yvGBRaUAv9zkMkMRixQA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.2.tgz", + "integrity": "sha512-ZWVC72x0lW4aj44e3khvBrj2oSYj1bD0jESmyah3zG/3DplEy/FOtYkMzbMjHTdDSheso7zH8GIlW6CDQnKhmQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.2.tgz", + "integrity": "sha512-pLT+OWYpzJig5K4VKhLttlIfBcVZfr2+Xbjra0Tjs83NQSkFS+y7xx+YhCwvpEmXYLIvaggj2ONPyjbiigOvHQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.2.tgz", + "integrity": "sha512-dhpiksQCyGca4WY0fJyzK3FxMDFoqMb0Cn+uDB+9GYjpU2K5//UGPQlCwiK4JHxuhg8oLMag5Nf3/IPSJNG8jw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.2.tgz", + "integrity": "sha512-O7bort1Vld00cu8g0jHZq3cbSTUNMohOEvYqsqE10+yfohhdPHzvzO+ziJRz4Dyyr/fYKREwS7gR4JC0soSOMw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@node-rs/jieba": { "version": "1.7.0", "dev": true, @@ -37365,6 +37485,21 @@ "type": "github", "url": "https://github.com/sponsors/wooorm" } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.2.tgz", + "integrity": "sha512-6BBlqGu3ewgJflv9iLCwO1v1hqlecaIH2AotpKfVUEzUxuuDNJQZ2a4KLb4MBl8T9/vca1YuWhSqtbF6ZuUJJw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } } }, "dependencies": { @@ -40072,6 +40207,54 @@ } } }, + "@next/swc-darwin-x64": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.2.tgz", + "integrity": "sha512-iZuYr7ZvGLPjPmfhhMl0ISm+z8EiyLBC1bLyFwGBxkWmPXqdJ60mzuTaDSr5WezDwv0fz32HB7JHmRC6JVHSZg==", + "optional": true + }, + "@next/swc-linux-arm64-gnu": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.2.tgz", + "integrity": "sha512-2xVabFtIge6BJTcJrW8YuUnYTuQjh4jEuRuS2mscyNVOj6zUZkom3CQg+egKOoS+zh2rrro66ffSKIS+ztFJTg==", + "optional": true + }, + "@next/swc-linux-arm64-musl": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.2.tgz", + "integrity": "sha512-wKRCQ27xCUJx5d6IivfjYGq8oVngqIhlhSAJntgXLt7Uo9sRT/3EppMHqUZRfyuNBTbykEre1s5166z+pvRB5A==", + "optional": true + }, + "@next/swc-linux-x64-gnu": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.2.tgz", + "integrity": "sha512-NpCa+UVhhuNeaFVUP1Bftm0uqtvLWq2JTm7+Ta48+2Uqj2mNXrDIvyn1DY/ZEfmW/1yvGBRaUAv9zkMkMRixQA==", + "optional": true + }, + "@next/swc-linux-x64-musl": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.2.tgz", + "integrity": "sha512-ZWVC72x0lW4aj44e3khvBrj2oSYj1bD0jESmyah3zG/3DplEy/FOtYkMzbMjHTdDSheso7zH8GIlW6CDQnKhmQ==", + "optional": true + }, + "@next/swc-win32-arm64-msvc": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.2.tgz", + "integrity": "sha512-pLT+OWYpzJig5K4VKhLttlIfBcVZfr2+Xbjra0Tjs83NQSkFS+y7xx+YhCwvpEmXYLIvaggj2ONPyjbiigOvHQ==", + "optional": true + }, + "@next/swc-win32-ia32-msvc": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.2.tgz", + "integrity": "sha512-dhpiksQCyGca4WY0fJyzK3FxMDFoqMb0Cn+uDB+9GYjpU2K5//UGPQlCwiK4JHxuhg8oLMag5Nf3/IPSJNG8jw==", + "optional": true + }, + "@next/swc-win32-x64-msvc": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.2.tgz", + "integrity": "sha512-O7bort1Vld00cu8g0jHZq3cbSTUNMohOEvYqsqE10+yfohhdPHzvzO+ziJRz4Dyyr/fYKREwS7gR4JC0soSOMw==", + "optional": true + }, "@node-rs/jieba": { "version": "1.7.0", "dev": true, @@ -61485,6 +61668,12 @@ "zwitch": { "version": "2.0.4", "dev": true + }, + "@next/swc-darwin-arm64": { + "version": "13.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.2.tgz", + "integrity": "sha512-6BBlqGu3ewgJflv9iLCwO1v1hqlecaIH2AotpKfVUEzUxuuDNJQZ2a4KLb4MBl8T9/vca1YuWhSqtbF6ZuUJJw==", + "optional": true } } } diff --git a/package.json b/package.json index 818453ae714..e5a3e243e1d 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "prod": "nx build:libs && nx run-many --target=build --verbose --output-style=stream", "commit": "cz", "git:pruneTags": "git tag -l | xargs git tag -d && git fetch --all --prune --tags", + "release:normal": "nx run-many --target=version --projects=utils,node,nextjs-mf --maxParallel=1 --allowEmptyRelease=true", "prerelease:all": "nx run-many --target=version --projects=utils,node,nextjs-mf --releaseAs=prerelease --preid=rc --maxParallel=1 --allowEmptyRelease=true", "release:node": "nx run node:version", "release:next": "nx run nextjs-mf:version", diff --git a/packages/nextjs-mf/README.md b/packages/nextjs-mf/README.md index 0f696ed06e4..473bcd8f73c 100644 --- a/packages/nextjs-mf/README.md +++ b/packages/nextjs-mf/README.md @@ -253,7 +253,7 @@ If an error surfaces while loading the script, a unique error object is generate ```js //next.config.js -const { createDelegatedModule } = require('@module-federation/utilities'); +const { createDelegatedModule } = require('@module-federation/nextjs-mf/utilities'); const remotes = { checkout: createDelegatedModule(require.resolve('./remote-delegate.js'), { remote: `checkout@http://localhost:3002/_next/static/${isServer ? 'ssr' : 'chunks'}/remoteEntry.js`, @@ -266,7 +266,7 @@ const remotes = { // ALL imports MUST BE dynamic imports in here like import() module.exports = new Promise(async (resolve, reject) => { const { importDelegatedModule } = await import( - '@module-federation/utilities/src/utils/importDelegatedModule' + '@module-federation/nextjs-mf/importDelegatedModule' ); // eslint-disable-next-line no-undef const currentRequest = new URLSearchParams(__resourceQuery).get('remote'); @@ -308,7 +308,7 @@ For more information on `__resourceQuery` visit: https://webpack.js.org/api/modu ```js // next.config.js -// its advised you use createDelegatedModule from @module-federation/utilities instead of manually creating the delegate module +// its advised you use {createDelegatedModule} from @module-federation/utilities (re-exported as @module-federation/nextjs-mf/utilities) instead of manually creating the delegate module const remotes = { // pass pointer to remote-delegate, pass delegate remote name as query param, // at runtime webpack will pass this as __resourceQuery diff --git a/packages/nextjs-mf/importDelegatedModule.ts b/packages/nextjs-mf/importDelegatedModule.ts new file mode 100644 index 00000000000..0dc9ae47cc4 --- /dev/null +++ b/packages/nextjs-mf/importDelegatedModule.ts @@ -0,0 +1,3 @@ +import {importDelegatedModule as importDelegate} from "@module-federation/utilities/src/utils/importDelegatedModule"; +export const importDelegatedModule = importDelegate; +export default importDelegate; diff --git a/packages/nextjs-mf/node.ts b/packages/nextjs-mf/node.ts new file mode 100644 index 00000000000..0769b5e9c33 --- /dev/null +++ b/packages/nextjs-mf/node.ts @@ -0,0 +1 @@ +export * from "@module-federation/node" diff --git a/packages/nextjs-mf/src/default-delegate.ts b/packages/nextjs-mf/src/default-delegate.ts index d1a5d9d5f47..2f3b7da7bf4 100644 --- a/packages/nextjs-mf/src/default-delegate.ts +++ b/packages/nextjs-mf/src/default-delegate.ts @@ -1,10 +1,7 @@ +import { importDelegatedModule } from '@module-federation/utilities/src/utils/importDelegatedModule' + // eslint-disable-next-line no-async-promise-executor module.exports = new Promise(async (resolve, reject) => { - // @ts-ignore - const { importDelegatedModule } = await import( - // @ts-ignore - '@module-federation/utilities/src/utils/importDelegatedModule' - ); // eslint-disable-next-line no-undef const currentRequest = new URLSearchParams(__resourceQuery).get('remote'); // @ts-ignore diff --git a/packages/nextjs-mf/src/internal.ts b/packages/nextjs-mf/src/internal.ts index 225cd878622..723ea1c4dde 100644 --- a/packages/nextjs-mf/src/internal.ts +++ b/packages/nextjs-mf/src/internal.ts @@ -42,6 +42,12 @@ export const DEFAULT_SHARE_SCOPE: SharedObject = { import: false, eager: false, }, + 'next/image': { + requiredVersion: false, + singleton: true, + import: undefined, + eager: false, + }, 'next/script': { requiredVersion: false, singleton: true, diff --git a/packages/nextjs-mf/src/loaders/patchDefaultSharedLoader.ts b/packages/nextjs-mf/src/loaders/patchDefaultSharedLoader.ts index 4621bb4a797..2f7a0e7be5a 100644 --- a/packages/nextjs-mf/src/loaders/patchDefaultSharedLoader.ts +++ b/packages/nextjs-mf/src/loaders/patchDefaultSharedLoader.ts @@ -21,6 +21,7 @@ throw new Error('should not exec'); import('next/router'); import('next/head'); import('next/script'); + import('next/image'); import('next/dynamic'); import('styled-jsx'); import('styled-jsx/style'); diff --git a/packages/nextjs-mf/src/plugins/NextFederationPlugin/apply-client-plugins.ts b/packages/nextjs-mf/src/plugins/NextFederationPlugin/apply-client-plugins.ts index 5a20083c20a..1245880a72e 100644 --- a/packages/nextjs-mf/src/plugins/NextFederationPlugin/apply-client-plugins.ts +++ b/packages/nextjs-mf/src/plugins/NextFederationPlugin/apply-client-plugins.ts @@ -4,7 +4,6 @@ import { NextFederationPluginExtraOptions, } from '@module-federation/utilities'; import DelegateModulesPlugin from '@module-federation/utilities/src/plugins/DelegateModulesPlugin'; -import { DEFAULT_SHARE_SCOPE_BROWSER } from '../../internal'; import { ChunkCorrelationPlugin } from '@module-federation/node'; import InvertedContainerPlugin from '../container/InvertedContainerPlugin'; import JsonpChunkLoading from '../JsonpChunkLoading'; diff --git a/packages/nextjs-mf/src/plugins/container/RemoveEagerModulesFromRuntimePlugin.ts b/packages/nextjs-mf/src/plugins/container/RemoveEagerModulesFromRuntimePlugin.ts index 5ca1b9001e4..f4595aad8e9 100644 --- a/packages/nextjs-mf/src/plugins/container/RemoveEagerModulesFromRuntimePlugin.ts +++ b/packages/nextjs-mf/src/plugins/container/RemoveEagerModulesFromRuntimePlugin.ts @@ -22,7 +22,7 @@ class RemoveEagerModulesFromRuntimePlugin { ); return; } - //return + //return compiler.hooks.thisCompilation.tap( 'RemoveEagerModulesFromRuntimePlugin', (compilation: Compilation) => { @@ -62,8 +62,11 @@ class RemoveEagerModulesFromRuntimePlugin { if ( module.constructor.name === 'ExternalModule' || module.type === 'provide-module' || - module.type === 'consume-shared-module' + module.type === 'consume-shared-module' || + //@ts-ignore + module.buildMeta?.eager // added flag from other plugin, non standard module api ) { + // could also use outgoing module connections to find modules that are not connected to the runtime return; } diff --git a/packages/nextjs-mf/utilities.ts b/packages/nextjs-mf/utilities.ts new file mode 100644 index 00000000000..eeca1a40cae --- /dev/null +++ b/packages/nextjs-mf/utilities.ts @@ -0,0 +1 @@ +export * from '@module-federation/utilities' diff --git a/packages/node/src/plugins/NodeFederationPlugin.ts b/packages/node/src/plugins/NodeFederationPlugin.ts index 51098184e31..07c75f717d9 100644 --- a/packages/node/src/plugins/NodeFederationPlugin.ts +++ b/packages/node/src/plugins/NodeFederationPlugin.ts @@ -2,7 +2,7 @@ import type { Compiler, container } from 'webpack'; import type { ModuleFederationPluginOptions } from '../types'; -import { extractUrlAndGlobal } from '@module-federation/utilities'; +import { extractUrlAndGlobal } from '@module-federation/utilities/src/utils/pure'; interface NodeFederationOptions extends ModuleFederationPluginOptions { experiments?: Record; diff --git a/packages/utilities/project.json b/packages/utilities/project.json index b48309887d7..be70ea9d258 100644 --- a/packages/utilities/project.json +++ b/packages/utilities/project.json @@ -6,18 +6,14 @@ "targets": { "build": { "executor": "@nx/js:tsc", - "outputs": [ - "{options.outputPath}" - ], + "outputs": ["{options.outputPath}"], "options": { "outputPath": "dist/packages/utilities", "main": "packages/utilities/src/index.ts", "tsConfig": "packages/utilities/tsconfig.lib.json", "buildableProjectDepsInPackageJsonType": "dependencies", "updateBuildableProjectDepsInPackageJson": true, - "assets": [ - "packages/utilities/*.md" - ] + "assets": ["packages/utilities/*.md"] } }, "publish": { @@ -33,20 +29,14 @@ }, "lint": { "executor": "@nx/linter:eslint", - "outputs": [ - "{options.outputFile}" - ], + "outputs": ["{options.outputFile}"], "options": { - "lintFilePatterns": [ - "packages/utilities/**/*.ts" - ] + "lintFilePatterns": ["packages/utilities/**/*.ts"] } }, "test": { "executor": "@nx/jest:jest", - "outputs": [ - "{workspaceRoot}/coverage/packages/utilities" - ], + "outputs": ["{workspaceRoot}/coverage/packages/utilities"], "options": { "jestConfig": "packages/utilities/jest.config.ts", "passWithNoTests": true @@ -55,10 +45,7 @@ "version": { "executor": "@jscutlery/semver:version", "options": { - "postTargets": [ - "utils:npm", - "utils:github" - ], + "postTargets": ["utils:npm", "utils:github"], "trackDeps": true, "push": true } diff --git a/packages/utilities/src/plugins/DelegateModulesPlugin.test.ts b/packages/utilities/src/plugins/DelegateModulesPlugin.test.ts index ecd36a4d68e..975257d4b12 100644 --- a/packages/utilities/src/plugins/DelegateModulesPlugin.test.ts +++ b/packages/utilities/src/plugins/DelegateModulesPlugin.test.ts @@ -2,12 +2,23 @@ import DelegateModulesPlugin from './DelegateModulesPlugin'; import { Compilation } from 'webpack'; import { RawSource } from 'webpack-sources'; +function createMockModuleDependency(resource: string): any { + return { + resource, + identifier: () => resource, + source: () => new RawSource(''), + dependencies: [], + }; +} +const dependency = createMockModuleDependency('dependency'); // Mock a minimal Webpack Module function createMockModule(resource: string): any { return { resource, identifier: () => resource, source: () => new RawSource(''), + dependencies: [dependency], + buildMeta: {}, }; } @@ -25,6 +36,8 @@ function createMockCompiler(): any { }; } +const chunkMap = {}; + // Mock a minimal Webpack Compilation function createMockCompilation(): Compilation { return { @@ -37,10 +50,23 @@ function createMockCompilation(): Compilation { }, }, chunkGraph: { - isModuleInChunk: jest.fn(), - connectChunkAndModule: jest.fn(), + //@ts-ignore + isModuleInChunk: jest.fn((module, chunk) => { + //@ts-ignore + return !!chunkMap?.[chunk.name]?.[module.identifier()]; + }), + // @ts-ignore + connectChunkAndModule: jest.fn((chunk, module) => { + //@ts-ignore + chunkMap[chunk.name] = {}; + //@ts-ignore + chunkMap[chunk.name][module.identifier()] = module; + }), disconnectChunkAndModule: jest.fn(), }, + moduleGraph: { + getModule: jest.fn(() => dependency), + }, } as unknown as Compilation; } @@ -102,7 +128,7 @@ describe('DelegateModulesPlugin', () => { // Check if connectChunkAndModule was called expect(compilation.chunkGraph.connectChunkAndModule).toHaveBeenCalledTimes( - 4 + 8 ); // Check if disconnectChunkAndModule was called diff --git a/packages/utilities/src/plugins/DelegateModulesPlugin.ts b/packages/utilities/src/plugins/DelegateModulesPlugin.ts index fb48e41fefb..48c5ae5d06b 100644 --- a/packages/utilities/src/plugins/DelegateModulesPlugin.ts +++ b/packages/utilities/src/plugins/DelegateModulesPlugin.ts @@ -1,10 +1,4 @@ -import type { - Compiler, - Compilation, - Chunk, - Module, - NormalModule, -} from 'webpack'; +import type { Compiler, Compilation, Chunk, Module } from 'webpack'; /** * A webpack plugin that moves specified modules from chunks to runtime chunk. @@ -28,20 +22,46 @@ class DelegateModulesPlugin { return undefined; } - addDelegatesToChunks(compilation: Compilation, chunks: Chunk[]): void { + private addDelegatesToChunks( + compilation: Compilation, + chunks: Chunk[] + ): void { for (const chunk of chunks) { - this._delegateModules.forEach((module) => { - if (!compilation.chunkGraph.isModuleInChunk(module, chunk)) { - this.options.debug && - console.log( - 'adding ', - module.identifier(), - ' to chunk', - chunk.name - ); - compilation.chunkGraph.connectChunkAndModule(chunk, module); - } - }); + for (const module of Array.from(this._delegateModules)) { + this.addModuleAndDependenciesToChunk(module, chunk, compilation); + } + } + } + + private addModuleAndDependenciesToChunk( + module: Module, + chunk: Chunk, + compilation: Compilation + ): void { + if (!compilation.chunkGraph.isModuleInChunk(module, chunk)) { + if (this.options.debug) { + console.log('adding ', module.identifier(), ' to chunk', chunk.name); + } + if (module.buildMeta) { + //@ts-ignore + module.buildMeta.eager = true; // non-standard way to keep track of initial + } + // modules needed by delegate module, so during eager share removal they are perserved + compilation.chunkGraph.connectChunkAndModule(chunk, module); + } + + for (const dependency of module.dependencies) { + const dependencyModule = compilation.moduleGraph.getModule(dependency); + if ( + dependencyModule && + !compilation.chunkGraph.isModuleInChunk(dependencyModule, chunk) + ) { + this.addModuleAndDependenciesToChunk( + dependencyModule, + chunk, + compilation + ); + } } } diff --git a/packages/utilities/src/utils/common.ts b/packages/utilities/src/utils/common.ts index 3b15f16bfb6..b0542961f5c 100644 --- a/packages/utilities/src/utils/common.ts +++ b/packages/utilities/src/utils/common.ts @@ -8,24 +8,13 @@ import type { RuntimeRemote, WebpackRemoteContainer, } from '../types'; -import { getRuntimeRemotes } from './getRuntimeRemotes'; -import { RemoteVars } from '../types'; -let remotesFromProcess = {} as RemoteVars; -try { - // @ts-ignore - remotesFromProcess = process.env['REMOTES'] || {}; -} catch (e) { - // not in webpack bundle -} -export const remoteVars = remotesFromProcess as RemoteVars; -// split the @ syntax into url and global -export const extractUrlAndGlobal = (urlAndGlobal: string): [string, string] => { - const index = urlAndGlobal.indexOf('@'); - if (index <= 0 || index === urlAndGlobal.length - 1) { - throw new Error(`Invalid request "${urlAndGlobal}"`); - } - return [urlAndGlobal.substring(index + 1), urlAndGlobal.substring(0, index)]; -}; + +import * as utils from './pure'; + +export const remoteVars = utils.remoteVars; +export const extractUrlAndGlobal = utils.extractUrlAndGlobal; +export const loadScript = utils.loadScript; +export const getRuntimeRemotes = utils.getRuntimeRemotes; export const createDelegatedModule = ( delegate: string, @@ -44,135 +33,6 @@ export const createDelegatedModule = ( return `internal ${delegate}?${queries.join('&')}`; }; -export const loadScript = (keyOrRuntimeRemoteItem: string | RuntimeRemote) => { - const runtimeRemotes = getRuntimeRemotes(); - - // 1) Load remote container if needed - let asyncContainer: RuntimeRemote['asyncContainer']; - const reference = - typeof keyOrRuntimeRemoteItem === 'string' - ? runtimeRemotes[keyOrRuntimeRemoteItem] - : keyOrRuntimeRemoteItem; - - if (reference.asyncContainer) { - asyncContainer = - typeof reference.asyncContainer.then === 'function' - ? reference.asyncContainer - : // @ts-ignore - reference.asyncContainer(); - } else { - // This casting is just to satisfy typescript, - // In reality remoteGlobal will always be a string; - const remoteGlobal = reference.global as unknown as string; - - // Check if theres an override for container key if not use remote global - const containerKey = reference.uniqueKey - ? (reference.uniqueKey as unknown as string) - : remoteGlobal; - - const __webpack_error__ = new Error() as Error & { - type: string; - request: string | null; - }; - - // @ts-ignore - if (!globalThis.__remote_scope__) { - // create a global scope for container, similar to how remotes are set on window in the browser - // @ts-ignore - globalThis.__remote_scope__ = { - // @ts-ignore - _config: {}, - }; - } - // @ts-ignore - const globalScope = - // @ts-ignore - typeof window !== 'undefined' ? window : globalThis.__remote_scope__; - - if (typeof window === 'undefined') { - globalScope['_config'][containerKey] = reference.url; - } else { - // to match promise template system, can be removed once promise template is gone - if (!globalScope['remoteLoading']) { - globalScope['remoteLoading'] = {}; - } - if (globalScope['remoteLoading'][containerKey]) { - return globalScope['remoteLoading'][containerKey]; - } - } - // @ts-ignore - asyncContainer = new Promise(function (resolve, reject) { - function resolveRemoteGlobal() { - const asyncContainer = globalScope[ - remoteGlobal - ] as unknown as AsyncContainer; - return resolve(asyncContainer); - } - - if (typeof globalScope[remoteGlobal] !== 'undefined') { - return resolveRemoteGlobal(); - } - - (__webpack_require__ as any).l( - reference.url, - function (event: Event) { - if (typeof globalScope[remoteGlobal] !== 'undefined') { - return resolveRemoteGlobal(); - } - - const errorType = - event && (event.type === 'load' ? 'missing' : event.type); - const realSrc = - event && event.target && (event.target as HTMLScriptElement).src; - - __webpack_error__.message = - 'Loading script failed.\n(' + - errorType + - ': ' + - realSrc + - ' or global var ' + - remoteGlobal + - ')'; - - __webpack_error__.name = 'ScriptExternalLoadError'; - __webpack_error__.type = errorType; - __webpack_error__.request = realSrc; - - reject(__webpack_error__); - }, - containerKey - ); - }).catch(function (err) { - console.error('container is offline, returning fake remote'); - console.error(err); - - return { - fake: true, - // @ts-ignore - get: (arg) => { - console.warn('faking', arg, 'module on, its offline'); - - return Promise.resolve(() => { - return { - __esModule: true, - default: () => { - return null; - }, - }; - }); - }, - //eslint-disable-next-line - init: () => {}, - }; - }); - if (typeof window !== 'undefined') { - globalScope['remoteLoading'][containerKey] = asyncContainer; - } - } - - return asyncContainer; -}; - const createContainerSharingScope = ( asyncContainer: AsyncContainer | undefined ) => { diff --git a/packages/utilities/src/utils/correctImportPath.spec.ts b/packages/utilities/src/utils/correctImportPath.spec.ts index 346fe538352..0d708f36f1b 100644 --- a/packages/utilities/src/utils/correctImportPath.spec.ts +++ b/packages/utilities/src/utils/correctImportPath.spec.ts @@ -5,15 +5,18 @@ describe(`${correctImportPath.name}()`, () => { jest.clearAllMocks(); }); - it.each(['linux', undefined])('should return correct path on non-windows systems: %s', platform => { - Object.defineProperty(process, 'platform', { - value: platform - }) + it.each(['linux', undefined])( + 'should return correct path on non-windows systems: %s', + (platform) => { + Object.defineProperty(process, 'platform', { + value: platform, + }); - const actual = correctImportPath('/context/path', '/path/to/file.js'); + const actual = correctImportPath('/context/path', '/path/to/file.js'); - expect(actual).toEqual('/path/to/file.js'); - }); + expect(actual).toEqual('/path/to/file.js'); + } + ); it.each([ ['.\\file.js', './file.js'], @@ -30,8 +33,8 @@ describe(`${correctImportPath.name}()`, () => { 'should return correct path on windows systems - %s', (entryFile: string, output: string) => { Object.defineProperty(process, 'platform', { - value: 'win32' - }) + value: 'win32', + }); const actual = correctImportPath('C:\\path\\to\\dir', entryFile); diff --git a/packages/utilities/src/utils/correctImportPath.ts b/packages/utilities/src/utils/correctImportPath.ts index 9514eb9804f..ac58f77f88c 100644 --- a/packages/utilities/src/utils/correctImportPath.ts +++ b/packages/utilities/src/utils/correctImportPath.ts @@ -1,5 +1,4 @@ export const correctImportPath = (context: string, entryFile: string) => { - if (typeof process !== 'undefined') { if (process?.platform !== 'win32') { return entryFile; diff --git a/packages/utilities/src/utils/importDelegatedModule.ts b/packages/utilities/src/utils/importDelegatedModule.ts index fe095161ed6..e081dd7569d 100644 --- a/packages/utilities/src/utils/importDelegatedModule.ts +++ b/packages/utilities/src/utils/importDelegatedModule.ts @@ -1,5 +1,5 @@ import { RuntimeRemote, WebpackRemoteContainer } from '../types'; -import { loadScript } from './common'; +import { loadScript } from './pure'; export const importDelegatedModule = async ( keyOrRuntimeRemoteItem: string | RuntimeRemote @@ -13,7 +13,7 @@ export const importDelegatedModule = async ( // most of this is only needed because of legacy promise based implementation // can remove proxies once we remove promise based implementations if (typeof window === 'undefined') { - if (!Object.hasOwnProperty.call(keyOrRuntimeRemoteItem, 'global')) { + if (!Object.hasOwnProperty.call(keyOrRuntimeRemoteItem, 'globalThis')) { return asyncContainer; } diff --git a/packages/utilities/src/utils/importRemote.ts b/packages/utilities/src/utils/importRemote.ts index c0ca4b19f2f..198549233d6 100644 --- a/packages/utilities/src/utils/importRemote.ts +++ b/packages/utilities/src/utils/importRemote.ts @@ -104,14 +104,14 @@ export const importRemote = async ({ const [, moduleFactory] = await Promise.all([ initContainer(window[remoteScope]), (window[remoteScope] as unknown as WebpackRemoteContainer).get( - (module === '.' || module.startsWith('./')) ? module : `./${module}` + module === '.' || module.startsWith('./') ? module : `./${module}` ), ]); return moduleFactory(); } else { const moduleFactory = await ( window[remoteScope] as unknown as WebpackRemoteContainer - ).get((module === '.' || module.startsWith('./')) ? module : `./${module}`); + ).get(module === '.' || module.startsWith('./') ? module : `./${module}`); return moduleFactory(); } }; diff --git a/packages/utilities/src/utils/pure.ts b/packages/utilities/src/utils/pure.ts new file mode 100644 index 00000000000..b2e5cccfc49 --- /dev/null +++ b/packages/utilities/src/utils/pure.ts @@ -0,0 +1,204 @@ +import { + AsyncContainer, + RemoteVars, + RuntimeRemote, + RuntimeRemotesMap, +} from '../types/index'; + +let pure = {} as RemoteVars; +try { + // @ts-ignore + pure = process.env['REMOTES'] || {}; +} catch (e) { + // not in webpack bundle +} +export const remoteVars = pure as RemoteVars; + +export const extractUrlAndGlobal = (urlAndGlobal: string): [string, string] => { + const index = urlAndGlobal.indexOf('@'); + if (index <= 0 || index === urlAndGlobal.length - 1) { + throw new Error(`Invalid request "${urlAndGlobal}"`); + } + return [urlAndGlobal.substring(index + 1), urlAndGlobal.substring(0, index)]; +}; + +export const loadScript = (keyOrRuntimeRemoteItem: string | RuntimeRemote) => { + const runtimeRemotes = getRuntimeRemotes(); + + // 1) Load remote container if needed + let asyncContainer: RuntimeRemote['asyncContainer']; + const reference = + typeof keyOrRuntimeRemoteItem === 'string' + ? runtimeRemotes[keyOrRuntimeRemoteItem] + : keyOrRuntimeRemoteItem; + + if (reference.asyncContainer) { + asyncContainer = + typeof reference.asyncContainer.then === 'function' + ? reference.asyncContainer + : // @ts-ignore + reference.asyncContainer(); + } else { + // This casting is just to satisfy typescript, + // In reality remoteGlobal will always be a string; + const remoteGlobal = reference.global as unknown as string; + + // Check if theres an override for container key if not use remote global + const containerKey = reference.uniqueKey + ? (reference.uniqueKey as unknown as string) + : remoteGlobal; + + const __webpack_error__ = new Error() as Error & { + type: string; + request: string | null; + }; + + // @ts-ignore + if (!globalThis.__remote_scope__) { + // create a global scope for container, similar to how remotes are set on window in the browser + // @ts-ignore + globalThis.__remote_scope__ = { + // @ts-ignore + _config: {}, + }; + } + // @ts-ignore + const globalScope = + // @ts-ignore + typeof window !== 'undefined' ? window : globalThis.__remote_scope__; + + if (typeof window === 'undefined') { + globalScope['_config'][containerKey] = reference.url; + } else { + // to match promise template system, can be removed once promise template is gone + if (!globalScope['remoteLoading']) { + globalScope['remoteLoading'] = {}; + } + if (globalScope['remoteLoading'][containerKey]) { + return globalScope['remoteLoading'][containerKey]; + } + } + // @ts-ignore + asyncContainer = new Promise(function (resolve, reject) { + function resolveRemoteGlobal() { + const asyncContainer = globalScope[ + remoteGlobal + ] as unknown as AsyncContainer; + return resolve(asyncContainer); + } + + if (typeof globalScope[remoteGlobal] !== 'undefined') { + return resolveRemoteGlobal(); + } + + (__webpack_require__ as any).l( + reference.url, + function (event: Event) { + if (typeof globalScope[remoteGlobal] !== 'undefined') { + return resolveRemoteGlobal(); + } + + const errorType = + event && (event.type === 'load' ? 'missing' : event.type); + const realSrc = + event && event.target && (event.target as HTMLScriptElement).src; + + __webpack_error__.message = + 'Loading script failed.\n(' + + errorType + + ': ' + + realSrc + + ' or global var ' + + remoteGlobal + + ')'; + + __webpack_error__.name = 'ScriptExternalLoadError'; + __webpack_error__.type = errorType; + __webpack_error__.request = realSrc; + + reject(__webpack_error__); + }, + containerKey + ); + }).catch(function (err) { + console.error('container is offline, returning fake remote'); + console.error(err); + + return { + fake: true, + // @ts-ignore + get: (arg) => { + console.warn('faking', arg, 'module on, its offline'); + + return Promise.resolve(() => { + return { + __esModule: true, + default: () => { + return null; + }, + }; + }); + }, + //eslint-disable-next-line + init: () => {}, + }; + }); + if (typeof window !== 'undefined') { + globalScope['remoteLoading'][containerKey] = asyncContainer; + } + } + + return asyncContainer; +}; + +export const getRuntimeRemotes = () => { + try { + const runtimeRemotes = Object.entries(remoteVars).reduce(function ( + acc, + item + ) { + const [key, value] = item; + // if its an object with a thenable (eagerly executing function) + if (typeof value === 'object' && typeof value.then === 'function') { + acc[key] = { asyncContainer: value }; + } + // if its a function that must be called (lazily executing function) + else if (typeof value === 'function') { + // @ts-ignore + acc[key] = { asyncContainer: value }; + } + // if its a delegate module, skip it + else if (typeof value === 'string' && value.startsWith('internal ')) { + const [request, query] = value.replace('internal ', '').split('?'); + if (query) { + const remoteSyntax = new URLSearchParams(query).get('remote'); + if (remoteSyntax) { + const [url, global] = extractUrlAndGlobal(remoteSyntax); + acc[key] = { global, url }; + } + } + } + // if its just a string (global@url) + else if (typeof value === 'string') { + const [url, global] = extractUrlAndGlobal(value); + acc[key] = { global, url }; + } + // we dont know or currently support this type + else { + //@ts-ignore + console.warn('remotes process', process.env.REMOTES); + throw new Error( + `[mf] Invalid value received for runtime_remote "${key}"` + ); + } + return acc; + }, + {} as RuntimeRemotesMap); + + return runtimeRemotes; + } catch (err) { + console.warn('Unable to retrieve runtime remotes: ', err); + } + + return {} as RuntimeRemotesMap; +};