diff --git a/packages/react/module-federation.ts b/packages/react/module-federation.ts index 69cd3e5fd1..da79035f5e 100644 --- a/packages/react/module-federation.ts +++ b/packages/react/module-federation.ts @@ -1,8 +1,12 @@ import { withModuleFederation } from './src/module-federation/with-module-federation'; +import { withModuleFederationForSSR } from './src/module-federation/with-module-federation-ssr'; export { withModuleFederation }; +export { withModuleFederationForSSR }; // Support for older generated code: `const withModuleFederation = require('@nrwl/react/module-federation')` module.exports = withModuleFederation; + // Allow newer generated code to work: `const { withModuleFederation } = require(...)`; module.exports.withModuleFederation = withModuleFederation; +module.exports.withModuleFederationForSSR = withModuleFederationForSSR; diff --git a/packages/react/src/module-federation/utils.ts b/packages/react/src/module-federation/utils.ts new file mode 100644 index 0000000000..b1eb5cef18 --- /dev/null +++ b/packages/react/src/module-federation/utils.ts @@ -0,0 +1,65 @@ +import { + applyAdditionalShared, + applySharedFunction, + createProjectGraphAsync, + getDependentPackagesForProject, + mapRemotes, + mapRemotesForSSR, + ModuleFederationConfig, + ProjectConfiguration, + ProjectGraph, + readCachedProjectGraph, + sharePackages, + shareWorkspaceLibraries, +} from '@nrwl/devkit'; + +export async function getModuleFederationConfig( + mfConfig: ModuleFederationConfig, + determineRemoteUrl: (remote: string) => string, + options: { isServer: boolean } = { isServer: false } +) { + let projectGraph: ProjectGraph; + try { + projectGraph = readCachedProjectGraph(); + } catch (e) { + projectGraph = await createProjectGraphAsync(); + } + + const project = projectGraph.nodes[mfConfig.name]?.data; + + if (!project) { + throw Error( + `Cannot find project "${mfConfig.name}". Check that the name is correct in module-federation.config.js` + ); + } + + const dependencies = getDependentPackagesForProject( + projectGraph, + mfConfig.name + ); + const sharedLibraries = shareWorkspaceLibraries( + dependencies.workspaceLibraries + ); + + const npmPackages = sharePackages(dependencies.npmPackages); + + const sharedDependencies = { + ...sharedLibraries.getLibraries(), + ...npmPackages, + }; + + applySharedFunction(sharedDependencies, mfConfig.shared); + applyAdditionalShared( + sharedDependencies, + mfConfig.additionalShared, + projectGraph + ); + + const mapRemotesFunction = options.isServer ? mapRemotesForSSR : mapRemotes; + const mappedRemotes = + !mfConfig.remotes || mfConfig.remotes.length === 0 + ? {} + : mapRemotesFunction(mfConfig.remotes, 'js', determineRemoteUrl); + + return { sharedLibraries, sharedDependencies, mappedRemotes }; +} diff --git a/packages/react/src/module-federation/with-module-federation-ssr.ts b/packages/react/src/module-federation/with-module-federation-ssr.ts new file mode 100644 index 0000000000..22b2502efe --- /dev/null +++ b/packages/react/src/module-federation/with-module-federation-ssr.ts @@ -0,0 +1,64 @@ +import { ModuleFederationConfig } from '@nrwl/devkit'; +import { readCachedProjectConfiguration } from 'nx/src/project-graph/project-graph'; +import { getModuleFederationConfig } from './utils'; + +function determineRemoteUrl(remote: string) { + const remoteConfiguration = readCachedProjectConfiguration(remote); + const serveTarget = remoteConfiguration?.targets?.serve; + + if (!serveTarget) { + throw new Error( + `Cannot automatically determine URL of remote (${remote}). Looked for property "host" in the project's "serve" target.\n + You can also use the tuple syntax in your webpack config to configure your remotes. e.g. \`remotes: [['remote1', '//localhost:4201']]\`` + ); + } + + const host = serveTarget.options?.host ?? '//localhost'; + const port = serveTarget.options?.port ?? 4201; + return `${ + host.endsWith('/') ? host.slice(0, -1) : host + }:${port}/server/remoteEntry.js`; +} + +export async function withModuleFederationForSSR( + options: ModuleFederationConfig +) { + const reactWebpackConfig = require('../../plugins/webpack'); + + const { sharedLibraries, sharedDependencies, mappedRemotes } = + await getModuleFederationConfig(options, determineRemoteUrl, { + isServer: true, + }); + + return (config) => { + config = reactWebpackConfig(config); + + config.target = false; + config.output.uniqueName = options.name; + config.optimization = { + runtimeChunk: false, + }; + + config.plugins.push( + new (require('@module-federation/node').UniversalFederationPlugin)( + { + name: options.name, + filename: 'remoteEntry.js', + exposes: options.exposes, + remotes: mappedRemotes, + shared: { + ...sharedDependencies, + }, + library: { + type: 'commonjs-module', + }, + isServer: true, + }, + {} + ), + sharedLibraries.getReplacementPlugin() + ); + + return config; + }; +} diff --git a/packages/react/src/module-federation/with-module-federation.ts b/packages/react/src/module-federation/with-module-federation.ts index 7e003e6a82..49119495c3 100644 --- a/packages/react/src/module-federation/with-module-federation.ts +++ b/packages/react/src/module-federation/with-module-federation.ts @@ -1,17 +1,6 @@ -import { - applyAdditionalShared, - applySharedFunction, - createProjectGraphAsync, - getDependentPackagesForProject, - mapRemotes, - ModuleFederationConfig, - ProjectConfiguration, - ProjectGraph, - readCachedProjectGraph, - sharePackages, - shareWorkspaceLibraries, -} from '@nrwl/devkit'; +import { ModuleFederationConfig } from '@nrwl/devkit'; import { readCachedProjectConfiguration } from 'nx/src/project-graph/project-graph'; +import { getModuleFederationConfig } from './utils'; import ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); function determineRemoteUrl(remote: string) { @@ -34,42 +23,9 @@ function determineRemoteUrl(remote: string) { export async function withModuleFederation(options: ModuleFederationConfig) { const reactWebpackConfig = require('../../plugins/webpack'); - let projectGraph: ProjectGraph; - try { - projectGraph = readCachedProjectGraph(); - } catch (e) { - projectGraph = await createProjectGraphAsync(); - } - const project = projectGraph.nodes[options.name]?.data; - - if (!project) { - throw Error( - `Cannot find project "${options.name}". Check that the name is correct in module-federation.config.js` - ); - } - - const dependencies = getDependentPackagesForProject( - projectGraph, - options.name - ); - const sharedLibraries = shareWorkspaceLibraries( - dependencies.workspaceLibraries - ); - - const npmPackages = sharePackages(dependencies.npmPackages); - - const sharedDependencies = { - ...sharedLibraries.getLibraries(), - ...npmPackages, - }; - - applySharedFunction(sharedDependencies, options.shared); - applyAdditionalShared( - sharedDependencies, - options.additionalShared, - projectGraph - ); + const { sharedDependencies, sharedLibraries, mappedRemotes } = + await getModuleFederationConfig(options, determineRemoteUrl); return (config) => { config = reactWebpackConfig(config); @@ -85,11 +41,6 @@ export async function withModuleFederation(options: ModuleFederationConfig) { outputModule: true, }; - const mappedRemotes = - !options.remotes || options.remotes.length === 0 - ? {} - : mapRemotes(options.remotes, 'js', determineRemoteUrl); - config.plugins.push( new ModuleFederationPlugin({ name: options.name, diff --git a/scripts/depcheck/missing.ts b/scripts/depcheck/missing.ts index 9cd08f4eab..df2269801a 100644 --- a/scripts/depcheck/missing.ts +++ b/scripts/depcheck/missing.ts @@ -87,6 +87,7 @@ const IGNORE_MATCHES_IN_PACKAGE = { 'stylus-loader', 'swc-loader', 'tsconfig-paths-webpack-plugin', + '@module-federation/node', ], rollup: ['@swc/core'], storybook: [