diff --git a/docs/generated/packages/react.json b/docs/generated/packages/react.json index bf5a7215e9..113a4504b7 100644 --- a/docs/generated/packages/react.json +++ b/docs/generated/packages/react.json @@ -1579,6 +1579,54 @@ "aliases": [], "hidden": false, "path": "/packages/react/src/executors/module-federation-dev-server/schema.json" + }, + { + "name": "module-federation-ssr-dev-server", + "implementation": "/packages/react/src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl.ts", + "schema": { + "version": 2, + "outputCapture": "direct-nodejs", + "title": "Module Federation SSR Dev Server", + "description": "Serve a SSR host application along with its known remotes.", + "cli": "nx", + "type": "object", + "properties": { + "browserTarget": { + "type": "string", + "description": "Target which builds the browser application." + }, + "serverTarget": { + "type": "string", + "description": "Target which builds the server application." + }, + "port": { + "type": "number", + "description": "The port to be set on `process.env.PORT` for use in the server.", + "default": 4200 + }, + "devRemotes": { + "type": "array", + "items": { "type": "string" }, + "description": "List of remote applications to run in development mode (i.e. using serve target)." + }, + "skipRemotes": { + "type": "array", + "items": { "type": "string" }, + "description": "List of remote applications to not automatically serve, either statically or in development mode. This can be useful for multi-repository module federation setups where the host application uses a remote application from an external repository." + }, + "host": { + "type": "string", + "description": "Host to listen on.", + "default": "localhost" + } + }, + "required": ["browserTarget", "serverTarget"], + "presets": [] + }, + "description": "Serve a host application along with it's known remotes.", + "aliases": [], + "hidden": false, + "path": "/packages/react/src/executors/module-federation-ssr-dev-server/schema.json" } ] } diff --git a/docs/packages.json b/docs/packages.json index d18c0cb3e3..4b70f786be 100644 --- a/docs/packages.json +++ b/docs/packages.json @@ -289,7 +289,10 @@ "description": "The React plugin for Nx contains executors and generators for managing React applications and libraries within an Nx workspace. It provides:\n\n\n- Integration with libraries such as Jest, Cypress, and Storybook.\n\n- Generators for applications, libraries, components, hooks, and more.\n\n- Library build support for publishing packages to npm or other registries.\n\n- Utilities for automatic workspace refactoring.", "path": "generated/packages/react.json", "schemas": { - "executors": ["module-federation-dev-server"], + "executors": [ + "module-federation-dev-server", + "module-federation-ssr-dev-server" + ], "generators": [ "init", "application", diff --git a/packages/react/executors.json b/packages/react/executors.json index 54466bb777..91a086fe2c 100644 --- a/packages/react/executors.json +++ b/packages/react/executors.json @@ -4,6 +4,11 @@ "implementation": "./src/executors/module-federation-dev-server/compat", "schema": "./src/executors/module-federation-dev-server/schema.json", "description": "Serve a host or remote application." + }, + "module-federation-ssr-dev-server": { + "implementation": "./src/executors/module-federation-ssr-dev-server/compat", + "schema": "./src/executors/module-federation-ssr-dev-server/schema.json", + "description": "Serve a host application along with it's known remotes." } }, "executors": { @@ -11,6 +16,11 @@ "implementation": "./src/executors/module-federation-dev-server/module-federation-dev-server.impl", "schema": "./src/executors/module-federation-dev-server/schema.json", "description": "Serve a host or remote application." + }, + "module-federation-ssr-dev-server": { + "implementation": "./src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl", + "schema": "./src/executors/module-federation-ssr-dev-server/schema.json", + "description": "Serve a host application along with it's known remotes." } } } diff --git a/packages/react/src/executors/module-federation-ssr-dev-server/compat.ts b/packages/react/src/executors/module-federation-ssr-dev-server/compat.ts new file mode 100644 index 0000000000..1659f720fa --- /dev/null +++ b/packages/react/src/executors/module-federation-ssr-dev-server/compat.ts @@ -0,0 +1,5 @@ +import { convertNxExecutor } from '@nrwl/devkit'; + +import ssrDevServer from './module-federation-ssr-dev-server.impl'; + +export default convertNxExecutor(ssrDevServer); diff --git a/packages/react/src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl.ts b/packages/react/src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl.ts new file mode 100644 index 0000000000..87cc9e21e6 --- /dev/null +++ b/packages/react/src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl.ts @@ -0,0 +1,81 @@ +import { ExecutorContext, logger, runExecutor } from '@nrwl/devkit'; +import ssrDevServerExecutor from '@nrwl/webpack/src/executors/ssr-dev-server/ssr-dev-server.impl'; +import { WebSsrDevServerOptions } from '@nrwl/webpack/src/executors/ssr-dev-server/schema'; +import { join } from 'path'; +import * as chalk from 'chalk'; +import { + combineAsyncIterableIterators, + tapAsyncIterable, +} from '@nrwl/devkit/src/utils/async-iterable'; + +type ModuleFederationDevServerOptions = WebSsrDevServerOptions & { + devRemotes?: string | string[]; + skipRemotes?: string[]; + host: string; +}; + +export default async function* moduleFederationSsrDevServer( + options: ModuleFederationDevServerOptions, + context: ExecutorContext +) { + let iter = ssrDevServerExecutor(options, context); + const p = context.workspace.projects[context.projectName]; + + const moduleFederationConfigPath = join( + context.root, + p.root, + 'module-federation.config.js' + ); + + let moduleFederationConfig: any; + try { + moduleFederationConfig = require(moduleFederationConfigPath); + } catch { + // TODO(jack): Add a link to guide + throw new Error( + `Could not load ${moduleFederationConfigPath}. Was this project generated with "@nrwl/react:host"?` + ); + } + + const remotesToSkip = new Set(options.skipRemotes ?? []); + const knownRemotes = (moduleFederationConfig.remotes ?? []).filter( + (r) => !remotesToSkip.has(r) + ); + + const devServeApps = !options.devRemotes + ? [] + : Array.isArray(options.devRemotes) + ? options.devRemotes + : [options.devRemotes]; + + for (const app of knownRemotes) { + const [appName] = Array.isArray(app) ? app : [app]; + const isDev = devServeApps.includes(appName); + iter = combineAsyncIterableIterators( + iter, + await runExecutor( + { + project: appName, + target: isDev ? 'serve' : 'serve-server', + configuration: context.configurationName, + }, + { + watch: isDev, + }, + context + ) + ); + } + + let numAwaiting = knownRemotes.length + 1; // remotes + host + return yield* tapAsyncIterable(iter, (x) => { + numAwaiting--; + if (numAwaiting === 0) { + logger.info( + `[ ${chalk.green('ready')} ] http://${options.host ?? 'localhost'}:${ + options.port ?? 4200 + }` + ); + } + }); +} diff --git a/packages/react/src/executors/module-federation-ssr-dev-server/schema.json b/packages/react/src/executors/module-federation-ssr-dev-server/schema.json new file mode 100644 index 0000000000..afba8e464e --- /dev/null +++ b/packages/react/src/executors/module-federation-ssr-dev-server/schema.json @@ -0,0 +1,43 @@ +{ + "version": 2, + "outputCapture": "direct-nodejs", + "title": "Module Federation SSR Dev Server", + "description": "Serve a SSR host application along with its known remotes.", + "cli": "nx", + "type": "object", + "properties": { + "browserTarget": { + "type": "string", + "description": "Target which builds the browser application." + }, + "serverTarget": { + "type": "string", + "description": "Target which builds the server application." + }, + "port": { + "type": "number", + "description": "The port to be set on `process.env.PORT` for use in the server.", + "default": 4200 + }, + "devRemotes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of remote applications to run in development mode (i.e. using serve target)." + }, + "skipRemotes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of remote applications to not automatically serve, either statically or in development mode. This can be useful for multi-repository module federation setups where the host application uses a remote application from an external repository." + }, + "host": { + "type": "string", + "description": "Host to listen on.", + "default": "localhost" + } + }, + "required": ["browserTarget", "serverTarget"] +}