From 11fcb8f2d41beff1c58f8b144d2fd4ccce7f1ac8 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Mon, 18 Sep 2023 17:11:38 +0100 Subject: [PATCH] feat(react): switch default to typescript configuration for module federation (#19031) --- .../packages/react/generators/host.json | 5 + .../packages/react/generators/remote.json | 5 + .../src/react-module-federation.test.ts | 14 +- .../host/__snapshots__/host.spec.ts.snap | 128 ++++++++++++++++ ...module-federation.server.config.ts__tmpl__ | 10 ++ .../server.ts__tmpl__ | 28 ++++ .../tsconfig.server.json__tmpl__ | 15 ++ .../webpack.server.config.ts__tmpl__ | 12 ++ .../module-federation.config.ts__tmpl__ | 10 ++ .../module-federation-ts/src/main.ts__tmpl__ | 1 + .../src/remotes.d.ts__tmpl__ | 4 + .../webpack.config.prod.ts__tmpl__ | 32 ++++ .../webpack.config.ts__tmpl__ | 12 ++ .../react/src/generators/host/host.spec.ts | 133 +++++++++++++++-- packages/react/src/generators/host/host.ts | 14 +- .../host/lib/add-module-federation-files.ts | 24 ++- .../generators/host/lib/setup-ssr-for-host.ts | 6 +- .../react/src/generators/host/schema.d.ts | 1 + .../react/src/generators/host/schema.json | 5 + .../remote/__snapshots__/remote.spec.ts.snap | 139 ++++++++++++++++++ ...module-federation.server.config.ts__tmpl__ | 10 ++ .../server.ts__tmpl__ | 45 ++++++ .../webpack.server.config.ts__tmpl__ | 12 ++ .../module-federation.config.ts__tmpl__ | 10 ++ .../module-federation-ts/src/main.ts__tmpl__ | 1 + .../src/remote-entry.ts__tmpl__ | 1 + .../webpack.config.prod.ts__tmpl__ | 1 + .../webpack.config.ts__tmpl__ | 12 ++ .../remote/lib/setup-ssr-for-remote.ts | 6 +- .../remote/lib/update-host-with-remote.ts | 10 +- .../src/generators/remote/remote.spec.ts | 104 +++++++++++++ .../react/src/generators/remote/remote.ts | 36 ++++- .../react/src/generators/remote/schema.d.ts | 6 + .../react/src/generators/remote/schema.json | 5 + .../rules/update-module-federation-project.ts | 9 +- 35 files changed, 823 insertions(+), 43 deletions(-) create mode 100644 packages/react/src/generators/host/__snapshots__/host.spec.ts.snap create mode 100644 packages/react/src/generators/host/files/module-federation-ssr-ts/module-federation.server.config.ts__tmpl__ create mode 100644 packages/react/src/generators/host/files/module-federation-ssr-ts/server.ts__tmpl__ create mode 100644 packages/react/src/generators/host/files/module-federation-ssr-ts/tsconfig.server.json__tmpl__ create mode 100644 packages/react/src/generators/host/files/module-federation-ssr-ts/webpack.server.config.ts__tmpl__ create mode 100644 packages/react/src/generators/host/files/module-federation-ts/module-federation.config.ts__tmpl__ create mode 100644 packages/react/src/generators/host/files/module-federation-ts/src/main.ts__tmpl__ create mode 100644 packages/react/src/generators/host/files/module-federation-ts/src/remotes.d.ts__tmpl__ create mode 100644 packages/react/src/generators/host/files/module-federation-ts/webpack.config.prod.ts__tmpl__ create mode 100644 packages/react/src/generators/host/files/module-federation-ts/webpack.config.ts__tmpl__ create mode 100644 packages/react/src/generators/remote/__snapshots__/remote.spec.ts.snap create mode 100644 packages/react/src/generators/remote/files/module-federation-ssr-ts/module-federation.server.config.ts__tmpl__ create mode 100644 packages/react/src/generators/remote/files/module-federation-ssr-ts/server.ts__tmpl__ create mode 100644 packages/react/src/generators/remote/files/module-federation-ssr-ts/webpack.server.config.ts__tmpl__ create mode 100644 packages/react/src/generators/remote/files/module-federation-ts/module-federation.config.ts__tmpl__ create mode 100644 packages/react/src/generators/remote/files/module-federation-ts/src/main.ts__tmpl__ create mode 100644 packages/react/src/generators/remote/files/module-federation-ts/src/remote-entry.ts__tmpl__ create mode 100644 packages/react/src/generators/remote/files/module-federation-ts/webpack.config.prod.ts__tmpl__ create mode 100644 packages/react/src/generators/remote/files/module-federation-ts/webpack.config.ts__tmpl__ diff --git a/docs/generated/packages/react/generators/host.json b/docs/generated/packages/react/generators/host.json index 729b9a0d5ac15..38150a0a55eea 100644 --- a/docs/generated/packages/react/generators/host.json +++ b/docs/generated/packages/react/generators/host.json @@ -160,6 +160,11 @@ "description": "Generate a React app with a minimal setup. No nx starter template.", "type": "boolean", "default": false + }, + "typescriptConfiguration": { + "type": "boolean", + "description": "Whether the module federation configuration and webpack configuration files should use TS.", + "default": true } }, "required": ["name"], diff --git a/docs/generated/packages/react/generators/remote.json b/docs/generated/packages/react/generators/remote.json index 06a32594def34..af929e51d825c 100644 --- a/docs/generated/packages/react/generators/remote.json +++ b/docs/generated/packages/react/generators/remote.json @@ -158,6 +158,11 @@ "description": "Whether to configure SSR for the host application", "type": "boolean", "default": false + }, + "typescriptConfiguration": { + "type": "boolean", + "description": "Whether the module federation configuration and webpack configuration files should use TS.", + "default": true } }, "required": ["name"], diff --git a/e2e/react-core/src/react-module-federation.test.ts b/e2e/react-core/src/react-module-federation.test.ts index 600dd5473edd7..c25fe34d12a57 100644 --- a/e2e/react-core/src/react-module-federation.test.ts +++ b/e2e/react-core/src/react-module-federation.test.ts @@ -39,10 +39,10 @@ describe('React Module Federation', () => { `generate @nx/react:remote ${remote3} --style=css --host=${shell} --no-interactive` ); - checkFilesExist(`apps/${shell}/module-federation.config.js`); - checkFilesExist(`apps/${remote1}/module-federation.config.js`); - checkFilesExist(`apps/${remote2}/module-federation.config.js`); - checkFilesExist(`apps/${remote3}/module-federation.config.js`); + checkFilesExist(`apps/${shell}/module-federation.config.ts`); + checkFilesExist(`apps/${remote1}/module-federation.config.ts`); + checkFilesExist(`apps/${remote2}/module-federation.config.ts`); + checkFilesExist(`apps/${remote3}/module-federation.config.ts`); await expect(runCLIAsync(`test ${shell}`)).resolves.toMatchObject({ combinedOutput: expect.stringContaining('Test Suites: 1 passed, 1 total'), @@ -54,7 +54,7 @@ describe('React Module Federation', () => { expect(readPort(remote3)).toEqual(4203); updateFile( - `apps/${shell}/webpack.config.js`, + `apps/${shell}/webpack.config.ts`, stripIndents` import { composePlugins, withNx, ModuleFederationConfig } from '@nx/webpack'; import { withReact } from '@nx/react'; @@ -135,8 +135,8 @@ describe('React Module Federation', () => { // check files are generated without the layout directory ("apps/") and // using the project name as the directory when no directory is provided - checkFilesExist(`${shell}/module-federation.config.js`); - checkFilesExist(`${remote}/module-federation.config.js`); + checkFilesExist(`${shell}/module-federation.config.ts`); + checkFilesExist(`${remote}/module-federation.config.ts`); // check default generated host is built successfully const buildOutput = runCLI(`run ${shell}:build:development`); diff --git a/packages/react/src/generators/host/__snapshots__/host.spec.ts.snap b/packages/react/src/generators/host/__snapshots__/host.spec.ts.snap new file mode 100644 index 0000000000000..3f527ffc55daa --- /dev/null +++ b/packages/react/src/generators/host/__snapshots__/host.spec.ts.snap @@ -0,0 +1,128 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`hostGenerator should generate host files and configs 1`] = ` +"const { composePlugins, withNx } = require('@nx/webpack'); +const { withReact } = require('@nx/react'); +const { withModuleFederation } = require('@nx/react/module-federation'); + +const baseConfig = require('./module-federation.config'); + +const config = { + ...baseConfig, +}; + +// Nx plugins for webpack to build config object from Nx options and context. +module.exports = composePlugins( + withNx(), + withReact(), + withModuleFederation(config) +); +" +`; + +exports[`hostGenerator should generate host files and configs 2`] = ` +"module.exports = { + name: 'test', + remotes: [], +}; +" +`; + +exports[`hostGenerator should generate host files and configs for SSR 1`] = ` +"const { composePlugins, withNx } = require('@nx/webpack'); +const { withReact } = require('@nx/react'); +const { withModuleFederationForSSR } = require('@nx/react/module-federation'); + +const baseConfig = require('./module-federation.config'); + +const defaultConfig = { + ...baseConfig, +}; + +// Nx plugins for webpack to build config object from Nx options and context. +module.exports = composePlugins( + withNx(), + withReact({ ssr: true }), + withModuleFederationForSSR(defaultConfig) +); +" +`; + +exports[`hostGenerator should generate host files and configs for SSR 2`] = ` +"// @ts-check + +/** + * @type {import('@nx/webpack').ModuleFederationConfig} + **/ +const moduleFederationConfig = { + name: 'test', + remotes: [], +}; + +module.exports = moduleFederationConfig; +" +`; + +exports[`hostGenerator should generate host files and configs for SSR when --typescriptConfiguration=true 1`] = ` +"import { composePlugins, withNx } from '@nx/webpack'; +import { withReact } from '@nx/react'; +import { withModuleFederationForSSR } from '@nx/react/module-federation'; + +import baseConfig from './module-federation.config'; + +const defaultConfig = { + ...baseConfig, +}; + +// Nx plugins for webpack to build config object from Nx options and context. +export default composePlugins( + withNx(), + withReact({ ssr: true }), + withModuleFederationForSSR(defaultConfig) +); +" +`; + +exports[`hostGenerator should generate host files and configs for SSR when --typescriptConfiguration=true 2`] = ` +"import { ModuleFederationConfig } from '@nx/webpack'; + +const config: ModuleFederationConfig = { + name: 'test', + remotes: [], +}; + +export default config; +" +`; + +exports[`hostGenerator should generate host files and configs when --typescriptConfiguration=true 1`] = ` +"import { composePlugins, withNx } from '@nx/webpack'; +import { withReact } from '@nx/react'; +import { withModuleFederation } from '@nx/react/module-federation'; + +import baseConfig from './module-federation.config'; + +const config = { + ...baseConfig, +}; + +// Nx plugins for webpack to build config object from Nx options and context. +export default composePlugins( + withNx(), + withReact(), + withModuleFederation(config) +); +" +`; + +exports[`hostGenerator should generate host files and configs when --typescriptConfiguration=true 2`] = ` +"import { ModuleFederationConfig } from '@nx/webpack'; + +const config: ModuleFederationConfig = { + name: 'test', + remotes: [], +}; + +export default config; +" +`; diff --git a/packages/react/src/generators/host/files/module-federation-ssr-ts/module-federation.server.config.ts__tmpl__ b/packages/react/src/generators/host/files/module-federation-ssr-ts/module-federation.server.config.ts__tmpl__ new file mode 100644 index 0000000000000..24f5f21f76d5e --- /dev/null +++ b/packages/react/src/generators/host/files/module-federation-ssr-ts/module-federation.server.config.ts__tmpl__ @@ -0,0 +1,10 @@ +import { ModuleFederationConfig } from '@nx/webpack'; + +const config: ModuleFederationConfig = { + name: '<%= projectName %>', + remotes: [ + <% remotes.forEach(function(r) {%> "<%= r.fileName %>", <% }); %> + ], +}; + +export default config; diff --git a/packages/react/src/generators/host/files/module-federation-ssr-ts/server.ts__tmpl__ b/packages/react/src/generators/host/files/module-federation-ssr-ts/server.ts__tmpl__ new file mode 100644 index 0000000000000..22781ec5b8bbd --- /dev/null +++ b/packages/react/src/generators/host/files/module-federation-ssr-ts/server.ts__tmpl__ @@ -0,0 +1,28 @@ +import * as path from 'path'; +import express from 'express'; +import cors from 'cors'; + +import { handleRequest } from './src/main.server'; + +const port = process.env['PORT'] || 4200; +const app = express(); + +const browserDist = path.join(process.cwd(), '<%= browserBuildOutputPath %>'); +const indexPath = path.join(browserDist, 'index.html'); + +app.use(cors()); + +app.get( + '*.*', + express.static(browserDist, { + maxAge: '1y', + }) +); + +app.use('*', handleRequest(indexPath)); + +const server = app.listen(port, () => { + console.log(`Express server listening on http://localhost:${port}`); +}); + +server.on('error', console.error); diff --git a/packages/react/src/generators/host/files/module-federation-ssr-ts/tsconfig.server.json__tmpl__ b/packages/react/src/generators/host/files/module-federation-ssr-ts/tsconfig.server.json__tmpl__ new file mode 100644 index 0000000000000..1f18bfb6839a8 --- /dev/null +++ b/packages/react/src/generators/host/files/module-federation-ssr-ts/tsconfig.server.json__tmpl__ @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.app.json", + "compilerOptions": { + "outDir": "../../out-tsc/server", + "target": "es2019", + "types": [ + "node" + ] + }, + "include": [ + "src/remotes.d.ts", + "src/main.server.tsx", + "server.ts" + ] +} diff --git a/packages/react/src/generators/host/files/module-federation-ssr-ts/webpack.server.config.ts__tmpl__ b/packages/react/src/generators/host/files/module-federation-ssr-ts/webpack.server.config.ts__tmpl__ new file mode 100644 index 0000000000000..8cfb8790cef8b --- /dev/null +++ b/packages/react/src/generators/host/files/module-federation-ssr-ts/webpack.server.config.ts__tmpl__ @@ -0,0 +1,12 @@ +import {composePlugins, withNx} from '@nx/webpack'; +import {withReact} from '@nx/react'; +import {withModuleFederationForSSR} from '@nx/react/module-federation'; + +import baseConfig from './module-federation.config'; + +const defaultConfig = { + ...baseConfig +}; + +// Nx plugins for webpack to build config object from Nx options and context. +export default composePlugins(withNx(), withReact({ssr: true}), withModuleFederationForSSR(defaultConfig)); diff --git a/packages/react/src/generators/host/files/module-federation-ts/module-federation.config.ts__tmpl__ b/packages/react/src/generators/host/files/module-federation-ts/module-federation.config.ts__tmpl__ new file mode 100644 index 0000000000000..98819353a1f2d --- /dev/null +++ b/packages/react/src/generators/host/files/module-federation-ts/module-federation.config.ts__tmpl__ @@ -0,0 +1,10 @@ +import { ModuleFederationConfig } from '@nx/webpack'; + +const config: ModuleFederationConfig = { + name: '<%= projectName %>', + remotes: [ + <% remotes.forEach(function(r) {%> "<%= r.fileName %>", <% }); %> +], +}; + +export default config; diff --git a/packages/react/src/generators/host/files/module-federation-ts/src/main.ts__tmpl__ b/packages/react/src/generators/host/files/module-federation-ts/src/main.ts__tmpl__ new file mode 100644 index 0000000000000..137c64f9f4475 --- /dev/null +++ b/packages/react/src/generators/host/files/module-federation-ts/src/main.ts__tmpl__ @@ -0,0 +1 @@ +import('./bootstrap'); \ No newline at end of file diff --git a/packages/react/src/generators/host/files/module-federation-ts/src/remotes.d.ts__tmpl__ b/packages/react/src/generators/host/files/module-federation-ts/src/remotes.d.ts__tmpl__ new file mode 100644 index 0000000000000..1a6f4583c7541 --- /dev/null +++ b/packages/react/src/generators/host/files/module-federation-ts/src/remotes.d.ts__tmpl__ @@ -0,0 +1,4 @@ + // Declare your remote Modules here + // Example declare module 'about/Module'; + + <% remotes.forEach(function(r) { %>declare module '<%= r.fileName %>/Module';<% }); %> \ No newline at end of file diff --git a/packages/react/src/generators/host/files/module-federation-ts/webpack.config.prod.ts__tmpl__ b/packages/react/src/generators/host/files/module-federation-ts/webpack.config.prod.ts__tmpl__ new file mode 100644 index 0000000000000..9098350c5dccf --- /dev/null +++ b/packages/react/src/generators/host/files/module-federation-ts/webpack.config.prod.ts__tmpl__ @@ -0,0 +1,32 @@ +import { composePlugins, withNx } from '@nx/webpack'; +import { withReact } from '@nx/react'; +import { withModuleFederation } from '@nx/react/module-federation'; + +import baseConfig from './module-federation.config'; + +const prodConfig = { + ...baseConfig, + /* + * Remote overrides for production. + * Each entry is a pair of a unique name and the URL where it is deployed. + * + * e.g. + * remotes: [ + * ['app1', 'http://app1.example.com'], + * ['app2', 'http://app2.example.com'], + * ] + * + * You can also use a full path to the remoteEntry.js file if desired. + * + * remotes: [ + * ['app1', 'http://example.com/path/to/app1/remoteEntry.js'], + * ['app2', 'http://example.com/path/to/app2/remoteEntry.js'], + * ] + */ + remotes: [ + <% remotes.forEach(function(r) {%>['<%= r.fileName %>', 'http://localhost:<%= r.port %>/'],<% }); %> +], +}; + +// Nx plugins for webpack to build config object from Nx options and context. +export default composePlugins(withNx(), withReact(), withModuleFederation(prodConfig)); diff --git a/packages/react/src/generators/host/files/module-federation-ts/webpack.config.ts__tmpl__ b/packages/react/src/generators/host/files/module-federation-ts/webpack.config.ts__tmpl__ new file mode 100644 index 0000000000000..0398a5def8ec0 --- /dev/null +++ b/packages/react/src/generators/host/files/module-federation-ts/webpack.config.ts__tmpl__ @@ -0,0 +1,12 @@ +import {composePlugins, withNx} from '@nx/webpack'; +import {withReact} from '@nx/react'; +import {withModuleFederation} from '@nx/react/module-federation'; + +import baseConfig from './module-federation.config'; + +const config = { + ...baseConfig, +}; + +// Nx plugins for webpack to build config object from Nx options and context. +export default composePlugins(withNx(), withReact(), withModuleFederation(config)); diff --git a/packages/react/src/generators/host/host.spec.ts b/packages/react/src/generators/host/host.spec.ts index bbdb0bfc8631e..bc78b795e9cf0 100644 --- a/packages/react/src/generators/host/host.spec.ts +++ b/packages/react/src/generators/host/host.spec.ts @@ -19,14 +19,44 @@ describe('hostGenerator', () => { unitTestRunner: 'none', e2eTestRunner: 'none', projectNameAndRootFormat: 'as-provided', + typescriptConfiguration: false, }); - expect(tree.exists('test/tsconfig.json')); - expect(tree.exists('test/webpack.config.prod.js')); - expect(tree.exists('test/webpack.config.js')); - expect(tree.exists('test/src/bootstrap.tsx')); - expect(tree.exists('test/src/main.ts')); - expect(tree.exists('test/src/remotes.d.ts')); + expect(tree.exists('test/tsconfig.json')).toBeTruthy(); + expect(tree.exists('test/webpack.config.prod.js')).toBeTruthy(); + expect(tree.exists('test/webpack.config.js')).toBeTruthy(); + expect(tree.exists('test/module-federation.config.js')).toBeTruthy(); + expect(tree.exists('test/src/bootstrap.tsx')).toBeTruthy(); + expect(tree.exists('test/src/main.ts')).toBeTruthy(); + expect(tree.exists('test/src/remotes.d.ts')).toBeTruthy(); + expect(tree.read('test/webpack.config.js', 'utf-8')).toMatchSnapshot(); + expect( + tree.read('test/module-federation.config.js', 'utf-8') + ).toMatchSnapshot(); + }); + + it('should generate host files and configs when --typescriptConfiguration=true', async () => { + await hostGenerator(tree, { + name: 'test', + style: 'css', + linter: Linter.None, + unitTestRunner: 'none', + e2eTestRunner: 'none', + projectNameAndRootFormat: 'as-provided', + typescriptConfiguration: true, + }); + + expect(tree.exists('test/tsconfig.json')).toBeTruthy(); + expect(tree.exists('test/webpack.config.prod.ts')).toBeTruthy(); + expect(tree.exists('test/webpack.config.ts')).toBeTruthy(); + expect(tree.exists('test/module-federation.config.ts')).toBeTruthy(); + expect(tree.exists('test/src/bootstrap.tsx')).toBeTruthy(); + expect(tree.exists('test/src/main.ts')).toBeTruthy(); + expect(tree.exists('test/src/remotes.d.ts')).toBeTruthy(); + expect(tree.read('test/webpack.config.ts', 'utf-8')).toMatchSnapshot(); + expect( + tree.read('test/module-federation.config.ts', 'utf-8') + ).toMatchSnapshot(); }); it('should install @nx/web for the file-server executor', async () => { @@ -53,16 +83,19 @@ describe('hostGenerator', () => { unitTestRunner: 'none', e2eTestRunner: 'none', projectNameAndRootFormat: 'as-provided', + typescriptConfiguration: false, }); - expect(tree.exists('test/tsconfig.json')); - expect(tree.exists('test/webpack.config.prod.js')); - expect(tree.exists('test/webpack.config.server.js')); - expect(tree.exists('test/webpack.config.js')); - expect(tree.exists('test/src/main.server.tsx')); - expect(tree.exists('test/src/bootstrap.tsx')); - expect(tree.exists('test/src/main.ts')); - expect(tree.exists('test/src/remotes.d.ts')); + expect(tree.exists('test/tsconfig.json')).toBeTruthy(); + expect(tree.exists('test/webpack.config.prod.js')).toBeTruthy(); + expect(tree.exists('test/webpack.server.config.js')).toBeTruthy(); + expect(tree.exists('test/webpack.config.js')).toBeTruthy(); + expect(tree.exists('test/module-federation.config.js')).toBeTruthy(); + expect(tree.exists('test/module-federation.server.config.js')).toBeTruthy(); + expect(tree.exists('test/src/main.server.tsx')).toBeTruthy(); + expect(tree.exists('test/src/bootstrap.tsx')).toBeTruthy(); + expect(tree.exists('test/src/main.ts')).toBeTruthy(); + expect(tree.exists('test/src/remotes.d.ts')).toBeTruthy(); expect(readJson(tree, 'test/tsconfig.server.json')).toEqual({ compilerOptions: { @@ -73,6 +106,54 @@ describe('hostGenerator', () => { extends: './tsconfig.app.json', include: ['src/remotes.d.ts', 'src/main.server.tsx', 'server.ts'], }); + + expect( + tree.read('test/webpack.server.config.js', 'utf-8') + ).toMatchSnapshot(); + expect( + tree.read('test/module-federation.server.config.js', 'utf-8') + ).toMatchSnapshot(); + }); + + it('should generate host files and configs for SSR when --typescriptConfiguration=true', async () => { + await hostGenerator(tree, { + name: 'test', + ssr: true, + style: 'css', + linter: Linter.None, + unitTestRunner: 'none', + e2eTestRunner: 'none', + projectNameAndRootFormat: 'as-provided', + typescriptConfiguration: true, + }); + + expect(tree.exists('test/tsconfig.json')).toBeTruthy(); + expect(tree.exists('test/webpack.config.prod.ts')).toBeTruthy(); + expect(tree.exists('test/webpack.server.config.ts')).toBeTruthy(); + expect(tree.exists('test/webpack.config.ts')).toBeTruthy(); + expect(tree.exists('test/module-federation.config.ts')).toBeTruthy(); + expect(tree.exists('test/module-federation.server.config.ts')).toBeTruthy(); + expect(tree.exists('test/src/main.server.tsx')).toBeTruthy(); + expect(tree.exists('test/src/bootstrap.tsx')).toBeTruthy(); + expect(tree.exists('test/src/main.ts')).toBeTruthy(); + expect(tree.exists('test/src/remotes.d.ts')).toBeTruthy(); + + expect(readJson(tree, 'test/tsconfig.server.json')).toEqual({ + compilerOptions: { + outDir: '../../out-tsc/server', + target: 'es2019', + types: ['node'], + }, + extends: './tsconfig.app.json', + include: ['src/remotes.d.ts', 'src/main.server.tsx', 'server.ts'], + }); + + expect( + tree.read('test/webpack.server.config.ts', 'utf-8') + ).toMatchSnapshot(); + expect( + tree.read('test/module-federation.server.config.ts', 'utf-8') + ).toMatchSnapshot(); }); it('should generate a host and remotes in a directory correctly when using --projectNameAndRootFormat=as-provided', async () => { @@ -87,6 +168,7 @@ describe('hostGenerator', () => { linter: Linter.None, style: 'css', unitTestRunner: 'none', + typescriptConfiguration: false, }); expect(tree.exists('foo/remote1/project.json')).toBeTruthy(); @@ -96,4 +178,27 @@ describe('hostGenerator', () => { tree.read('foo/host-app/module-federation.config.js', 'utf-8') ).toContain(`'remote1', 'remote2', 'remote3'`); }); + + it('should generate a host and remotes in a directory correctly when using --projectNameAndRootFormat=as-provided and --typescriptConfiguration=true', async () => { + const tree = createTreeWithEmptyWorkspace(); + + await hostGenerator(tree, { + name: 'hostApp', + directory: 'foo/hostApp', + remotes: ['remote1', 'remote2', 'remote3'], + projectNameAndRootFormat: 'as-provided', + e2eTestRunner: 'none', + linter: Linter.None, + style: 'css', + unitTestRunner: 'none', + typescriptConfiguration: true, + }); + + expect(tree.exists('foo/remote1/project.json')).toBeTruthy(); + expect(tree.exists('foo/remote2/project.json')).toBeTruthy(); + expect(tree.exists('foo/remote3/project.json')).toBeTruthy(); + expect( + tree.read('foo/host-app/module-federation.config.ts', 'utf-8') + ).toContain(`'remote1', 'remote2', 'remote3'`); + }); }); diff --git a/packages/react/src/generators/host/host.ts b/packages/react/src/generators/host/host.ts index 10f02db8035dd..49b76e772396c 100644 --- a/packages/react/src/generators/host/host.ts +++ b/packages/react/src/generators/host/host.ts @@ -19,7 +19,7 @@ import { } from './lib/normalize-remote'; import { setupSsrForHost } from './lib/setup-ssr-for-host'; import { updateModuleFederationE2eProject } from './lib/update-module-federation-e2e-project'; -import { Schema } from './schema'; +import { NormalizedSchema, Schema } from './schema'; export async function hostGenerator(host: Tree, schema: Schema) { return hostGeneratorInternal(host, { @@ -30,11 +30,10 @@ export async function hostGenerator(host: Tree, schema: Schema) { export async function hostGeneratorInternal(host: Tree, schema: Schema) { const tasks: GeneratorCallback[] = []; - const options = await normalizeOptions( - host, - schema, - '@nx/react:host' - ); + const options: NormalizedSchema = { + ...(await normalizeOptions(host, schema, '@nx/react:host')), + typescriptConfiguration: schema.typescriptConfiguration ?? true, + }; const initTask = await applicationGenerator(host, { ...options, @@ -65,6 +64,7 @@ export async function hostGeneratorInternal(host: Tree, schema: Schema) { ssr: options.ssr, skipFormat: true, projectNameAndRootFormat: options.projectNameAndRootFormat, + typescriptConfiguration: options.typescriptConfiguration, }); remotePort++; } @@ -93,7 +93,7 @@ export async function hostGeneratorInternal(host: Tree, schema: Schema) { const projectConfig = readProjectConfiguration(host, options.projectName); projectConfig.targets.server.options.webpackConfig = joinPathFragments( projectConfig.root, - 'webpack.server.config.js' + `webpack.server.config.${options.typescriptConfiguration ? 'ts' : 'js'}` ); updateProjectConfiguration(host, options.projectName, projectConfig); } diff --git a/packages/react/src/generators/host/lib/add-module-federation-files.ts b/packages/react/src/generators/host/lib/add-module-federation-files.ts index 0e047ca17a1b7..2c11386b4bebb 100644 --- a/packages/react/src/generators/host/lib/add-module-federation-files.ts +++ b/packages/react/src/generators/host/lib/add-module-federation-files.ts @@ -1,5 +1,5 @@ import { NormalizedSchema } from '../schema'; -import { generateFiles, names } from '@nx/devkit'; +import { generateFiles, joinPathFragments, names } from '@nx/devkit'; import { join } from 'path'; export function addModuleFederationFiles( @@ -34,11 +34,31 @@ export function addModuleFederationFiles( templateVariables ); + const pathToModuleFederationFiles = options.typescriptConfiguration + ? 'module-federation-ts' + : 'module-federation'; // New entry file is created here. generateFiles( host, - join(__dirname, `../files/module-federation`), + join(__dirname, `../files/${pathToModuleFederationFiles}`), options.appProjectRoot, templateVariables ); + + if (options.typescriptConfiguration) { + const pathToWebpackConfig = joinPathFragments( + options.appProjectRoot, + 'webpack.config.js' + ); + const pathToWebpackProdConfig = joinPathFragments( + options.appProjectRoot, + 'webpack.config.prod.js' + ); + if (host.exists(pathToWebpackConfig)) { + host.delete(pathToWebpackConfig); + } + if (host.exists(pathToWebpackProdConfig)) { + host.delete(pathToWebpackProdConfig); + } + } } diff --git a/packages/react/src/generators/host/lib/setup-ssr-for-host.ts b/packages/react/src/generators/host/lib/setup-ssr-for-host.ts index 4c532558764b5..0d6d2fb737874 100644 --- a/packages/react/src/generators/host/lib/setup-ssr-for-host.ts +++ b/packages/react/src/generators/host/lib/setup-ssr-for-host.ts @@ -23,9 +23,13 @@ export async function setupSsrForHost( project.targets.serve.executor = '@nx/react:module-federation-ssr-dev-server'; updateProjectConfiguration(tree, appName, project); + const pathToModuleFederationSsrFiles = options.typescriptConfiguration + ? 'module-federation-ssr-ts' + : 'module-federation-ssr'; + generateFiles( tree, - joinPathFragments(__dirname, '../files/module-federation-ssr'), + joinPathFragments(__dirname, `../files/${pathToModuleFederationSsrFiles}`), project.root, { ...options, diff --git a/packages/react/src/generators/host/schema.d.ts b/packages/react/src/generators/host/schema.d.ts index 78724ffc6d2b5..51dc14d63b688 100644 --- a/packages/react/src/generators/host/schema.d.ts +++ b/packages/react/src/generators/host/schema.d.ts @@ -24,6 +24,7 @@ export interface Schema { tags?: string; unitTestRunner: 'jest' | 'vitest' | 'none'; minimal?: boolean; + typescriptConfiguration?: boolean; } export interface NormalizedSchema extends Schema { diff --git a/packages/react/src/generators/host/schema.json b/packages/react/src/generators/host/schema.json index 5d7e59f479831..f4aa838a2b4c6 100644 --- a/packages/react/src/generators/host/schema.json +++ b/packages/react/src/generators/host/schema.json @@ -166,6 +166,11 @@ "description": "Generate a React app with a minimal setup. No nx starter template.", "type": "boolean", "default": false + }, + "typescriptConfiguration": { + "type": "boolean", + "description": "Whether the module federation configuration and webpack configuration files should use TS.", + "default": true } }, "required": ["name"], diff --git a/packages/react/src/generators/remote/__snapshots__/remote.spec.ts.snap b/packages/react/src/generators/remote/__snapshots__/remote.spec.ts.snap new file mode 100644 index 0000000000000..52f74730045da --- /dev/null +++ b/packages/react/src/generators/remote/__snapshots__/remote.spec.ts.snap @@ -0,0 +1,139 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`remote generator should create the remote with the correct config files 1`] = ` +"const { composePlugins, withNx } = require('@nx/webpack'); +const { withReact } = require('@nx/react'); +const { withModuleFederation } = require('@nx/react/module-federation'); + +const baseConfig = require('./module-federation.config'); + +const config = { + ...baseConfig, +}; + +// Nx plugins for webpack to build config object from Nx options and context. +module.exports = composePlugins( + withNx(), + withReact(), + withModuleFederation(config) +); +" +`; + +exports[`remote generator should create the remote with the correct config files 2`] = ` +"module.exports = require('./webpack.config'); +" +`; + +exports[`remote generator should create the remote with the correct config files 3`] = ` +"module.exports = { + name: 'test', + exposes: { + './Module': './src/remote-entry.ts', + }, +}; +" +`; + +exports[`remote generator should create the remote with the correct config files when --typescriptConfiguration=true 1`] = ` +"import { composePlugins, withNx } from '@nx/webpack'; +import { withReact } from '@nx/react'; +import { withModuleFederation } from '@nx/react/module-federation'; + +import baseConfig from './module-federation.config'; + +const config = { + ...baseConfig, +}; + +// Nx plugins for webpack to build config object from Nx options and context. +export default composePlugins( + withNx(), + withReact(), + withModuleFederation(config) +); +" +`; + +exports[`remote generator should create the remote with the correct config files when --typescriptConfiguration=true 2`] = ` +"export default require('./webpack.config'); +" +`; + +exports[`remote generator should create the remote with the correct config files when --typescriptConfiguration=true 3`] = ` +"import { ModuleFederationConfig } from '@nx/webpack'; + +const config: ModuleFederationConfig = { + name: 'test', + exposes: { + './Module': './src/remote-entry.ts', + }, +}; + +export default config; +" +`; + +exports[`remote generator should generate correct remote with config files when using --ssr 1`] = ` +"const { composePlugins, withNx } = require('@nx/webpack'); +const { withReact } = require('@nx/react'); +const { withModuleFederationForSSR } = require('@nx/react/module-federation'); + +const baseConfig = require('./module-federation.server.config'); + +const defaultConfig = { + ...baseConfig, +}; + +// Nx plugins for webpack to build config object from Nx options and context. +module.exports = composePlugins( + withNx(), + withReact({ ssr: true }), + withModuleFederationForSSR(defaultConfig) +); +" +`; + +exports[`remote generator should generate correct remote with config files when using --ssr 2`] = ` +"module.exports = { + name: 'test', + exposes: { + './Module': 'test/src/remote-entry.ts', + }, +}; +" +`; + +exports[`remote generator should generate correct remote with config files when using --ssr and --typescriptConfiguration=true 1`] = ` +"import { composePlugins, withNx } from '@nx/webpack'; +import { withReact } from '@nx/react'; +import { withModuleFederationForSSR } from '@nx/react/module-federation'; + +import baseConfig from './module-federation.server.config'; + +const defaultConfig = { + ...baseConfig, +}; + +// Nx plugins for webpack to build config object from Nx options and context. +export default composePlugins( + withNx(), + withReact({ ssr: true }), + withModuleFederationForSSR(defaultConfig) +); +" +`; + +exports[`remote generator should generate correct remote with config files when using --ssr and --typescriptConfiguration=true 2`] = ` +"import { ModuleFederationConfig } from '@nx/webpack'; + +const config: ModuleFederationConfig = { + name: 'test', + exposes: { + './Module': 'test/src/remote-entry.ts', + }, +}; + +export default config; +" +`; diff --git a/packages/react/src/generators/remote/files/module-federation-ssr-ts/module-federation.server.config.ts__tmpl__ b/packages/react/src/generators/remote/files/module-federation-ssr-ts/module-federation.server.config.ts__tmpl__ new file mode 100644 index 0000000000000..4481a845c9f80 --- /dev/null +++ b/packages/react/src/generators/remote/files/module-federation-ssr-ts/module-federation.server.config.ts__tmpl__ @@ -0,0 +1,10 @@ +import {ModuleFederationConfig} from '@nx/webpack'; + +const config: ModuleFederationConfig = { + name: '<%= projectName %>', + exposes: { + './Module': '<%= appProjectRoot %>/src/remote-entry.ts', + }, +}; + +export default config; diff --git a/packages/react/src/generators/remote/files/module-federation-ssr-ts/server.ts__tmpl__ b/packages/react/src/generators/remote/files/module-federation-ssr-ts/server.ts__tmpl__ new file mode 100644 index 0000000000000..8b377d7360932 --- /dev/null +++ b/packages/react/src/generators/remote/files/module-federation-ssr-ts/server.ts__tmpl__ @@ -0,0 +1,45 @@ +import * as path from 'path'; +import express from 'express'; +import cors from 'cors'; + +import { handleRequest } from './src/main.server'; + +const port = process.env['PORT'] || 4200; +const app = express(); + +const browserDist = path.join(process.cwd(), '<%= browserBuildOutputPath %>'); +const serverDist = path.join(process.cwd(), '<%= serverBuildOutputPath %>'); +const indexPath = path.join(browserDist, 'index.html'); + +app.use(cors()); + +// Client-side static bundles +app.get( + '*.*', + express.static(browserDist, { + maxAge: '1y', + }) +); + +// Static bundles for server-side module federation +app.use('/server', + express.static(serverDist, { + maxAge: '1y' + }) +); + +app.use('*', handleRequest(indexPath)); + +const server = app.listen(port, () => { + console.log(`Express server listening on http://localhost:${port}`); + + /** + * DO NOT REMOVE IF USING @nx/react:module-federation-dev-ssr executor + * to serve your Host application with this Remote application. + * This message allows Nx to determine when the Remote is ready to be + * consumed by the Host. + */ + process.send?.('nx.server.ready'); +}); + +server.on('error', console.error); diff --git a/packages/react/src/generators/remote/files/module-federation-ssr-ts/webpack.server.config.ts__tmpl__ b/packages/react/src/generators/remote/files/module-federation-ssr-ts/webpack.server.config.ts__tmpl__ new file mode 100644 index 0000000000000..e370fcbd0a94d --- /dev/null +++ b/packages/react/src/generators/remote/files/module-federation-ssr-ts/webpack.server.config.ts__tmpl__ @@ -0,0 +1,12 @@ +import {composePlugins, withNx} from '@nx/webpack'; +import {withReact} from '@nx/react'; +import {withModuleFederationForSSR} from '@nx/react/module-federation'; + +import baseConfig from "./module-federation.server.config"; + +const defaultConfig = { + ...baseConfig, +}; + +// Nx plugins for webpack to build config object from Nx options and context. +export default composePlugins(withNx(), withReact({ssr: true}), withModuleFederationForSSR(defaultConfig)); diff --git a/packages/react/src/generators/remote/files/module-federation-ts/module-federation.config.ts__tmpl__ b/packages/react/src/generators/remote/files/module-federation-ts/module-federation.config.ts__tmpl__ new file mode 100644 index 0000000000000..00e5754cce526 --- /dev/null +++ b/packages/react/src/generators/remote/files/module-federation-ts/module-federation.config.ts__tmpl__ @@ -0,0 +1,10 @@ +import {ModuleFederationConfig} from '@nx/webpack'; + +const config: ModuleFederationConfig = { + name: '<%= projectName %>', + exposes: { + './Module': './src/remote-entry.ts', + }, +}; + +export default config; diff --git a/packages/react/src/generators/remote/files/module-federation-ts/src/main.ts__tmpl__ b/packages/react/src/generators/remote/files/module-federation-ts/src/main.ts__tmpl__ new file mode 100644 index 0000000000000..b93c7a0268a59 --- /dev/null +++ b/packages/react/src/generators/remote/files/module-federation-ts/src/main.ts__tmpl__ @@ -0,0 +1 @@ +import('./bootstrap'); diff --git a/packages/react/src/generators/remote/files/module-federation-ts/src/remote-entry.ts__tmpl__ b/packages/react/src/generators/remote/files/module-federation-ts/src/remote-entry.ts__tmpl__ new file mode 100644 index 0000000000000..8c1fd1008a0bb --- /dev/null +++ b/packages/react/src/generators/remote/files/module-federation-ts/src/remote-entry.ts__tmpl__ @@ -0,0 +1 @@ +export { default } from './app/app'; diff --git a/packages/react/src/generators/remote/files/module-federation-ts/webpack.config.prod.ts__tmpl__ b/packages/react/src/generators/remote/files/module-federation-ts/webpack.config.prod.ts__tmpl__ new file mode 100644 index 0000000000000..7e1a4811b97b1 --- /dev/null +++ b/packages/react/src/generators/remote/files/module-federation-ts/webpack.config.prod.ts__tmpl__ @@ -0,0 +1 @@ +export default require('./webpack.config'); diff --git a/packages/react/src/generators/remote/files/module-federation-ts/webpack.config.ts__tmpl__ b/packages/react/src/generators/remote/files/module-federation-ts/webpack.config.ts__tmpl__ new file mode 100644 index 0000000000000..0398a5def8ec0 --- /dev/null +++ b/packages/react/src/generators/remote/files/module-federation-ts/webpack.config.ts__tmpl__ @@ -0,0 +1,12 @@ +import {composePlugins, withNx} from '@nx/webpack'; +import {withReact} from '@nx/react'; +import {withModuleFederation} from '@nx/react/module-federation'; + +import baseConfig from './module-federation.config'; + +const config = { + ...baseConfig, +}; + +// Nx plugins for webpack to build config object from Nx options and context. +export default composePlugins(withNx(), withReact(), withModuleFederation(config)); diff --git a/packages/react/src/generators/remote/lib/setup-ssr-for-remote.ts b/packages/react/src/generators/remote/lib/setup-ssr-for-remote.ts index 0cb1ce3760ffc..7b3fa4ff07aee 100644 --- a/packages/react/src/generators/remote/lib/setup-ssr-for-remote.ts +++ b/packages/react/src/generators/remote/lib/setup-ssr-for-remote.ts @@ -20,9 +20,13 @@ export async function setupSsrForRemote( const tasks: GeneratorCallback[] = []; const project = readProjectConfiguration(tree, appName); + const pathToModuleFederationSsrFiles = options.typescriptConfiguration + ? 'module-federation-ssr-ts' + : 'module-federation-ssr'; + generateFiles( tree, - joinPathFragments(__dirname, '../files/module-federation-ssr'), + joinPathFragments(__dirname, `../files/${pathToModuleFederationSsrFiles}`), project.root, { ...options, diff --git a/packages/react/src/generators/remote/lib/update-host-with-remote.ts b/packages/react/src/generators/remote/lib/update-host-with-remote.ts index 955cd7f97d0d8..44868ce1dae25 100644 --- a/packages/react/src/generators/remote/lib/update-host-with-remote.ts +++ b/packages/react/src/generators/remote/lib/update-host-with-remote.ts @@ -25,10 +25,18 @@ export function updateHostWithRemote( } const hostConfig = readProjectConfiguration(host, hostName); - const moduleFederationConfigPath = joinPathFragments( + let moduleFederationConfigPath = joinPathFragments( hostConfig.root, 'module-federation.config.js' ); + + if (!host.exists(moduleFederationConfigPath)) { + moduleFederationConfigPath = joinPathFragments( + hostConfig.root, + 'module-federation.config.ts' + ); + } + const remoteDefsPath = joinPathFragments( hostConfig.sourceRoot, 'remotes.d.ts' diff --git a/packages/react/src/generators/remote/remote.spec.ts b/packages/react/src/generators/remote/remote.spec.ts index 156db510b617e..5b1783d357a59 100644 --- a/packages/react/src/generators/remote/remote.spec.ts +++ b/packages/react/src/generators/remote/remote.spec.ts @@ -4,6 +4,56 @@ import { Linter } from '@nx/linter'; import remote from './remote'; describe('remote generator', () => { + it('should create the remote with the correct config files', async () => { + const tree = createTreeWithEmptyWorkspace(); + await remote(tree, { + name: 'test', + devServerPort: 4201, + e2eTestRunner: 'cypress', + linter: Linter.EsLint, + skipFormat: false, + style: 'css', + unitTestRunner: 'jest', + projectNameAndRootFormat: 'as-provided', + typescriptConfiguration: false, + }); + + expect(tree.exists('test/webpack.config.js')).toBeTruthy(); + expect(tree.exists('test/webpack.config.prod.js')).toBeTruthy(); + expect(tree.exists('test/module-federation.config.js')).toBeTruthy(); + + expect(tree.read('test/webpack.config.js', 'utf-8')).toMatchSnapshot(); + expect(tree.read('test/webpack.config.prod.js', 'utf-8')).toMatchSnapshot(); + expect( + tree.read('test/module-federation.config.js', 'utf-8') + ).toMatchSnapshot(); + }); + + it('should create the remote with the correct config files when --typescriptConfiguration=true', async () => { + const tree = createTreeWithEmptyWorkspace(); + await remote(tree, { + name: 'test', + devServerPort: 4201, + e2eTestRunner: 'cypress', + linter: Linter.EsLint, + skipFormat: false, + style: 'css', + unitTestRunner: 'jest', + projectNameAndRootFormat: 'as-provided', + typescriptConfiguration: true, + }); + + expect(tree.exists('test/webpack.config.ts')).toBeTruthy(); + expect(tree.exists('test/webpack.config.prod.ts')).toBeTruthy(); + expect(tree.exists('test/module-federation.config.ts')).toBeTruthy(); + + expect(tree.read('test/webpack.config.ts', 'utf-8')).toMatchSnapshot(); + expect(tree.read('test/webpack.config.prod.ts', 'utf-8')).toMatchSnapshot(); + expect( + tree.read('test/module-federation.config.ts', 'utf-8') + ).toMatchSnapshot(); + }); + it('should install @nx/web for the file-server executor', async () => { const tree = createTreeWithEmptyWorkspace(); await remote(tree, { @@ -57,4 +107,58 @@ describe('remote generator', () => { expect(mainFile).toContain(`join(process.cwd(), 'dist/test/browser')`); expect(mainFile).toContain('nx.server.ready'); }); + + it('should generate correct remote with config files when using --ssr', async () => { + const tree = createTreeWithEmptyWorkspace(); + + await remote(tree, { + name: 'test', + devServerPort: 4201, + e2eTestRunner: 'cypress', + linter: Linter.EsLint, + skipFormat: false, + style: 'css', + unitTestRunner: 'jest', + ssr: true, + projectNameAndRootFormat: 'as-provided', + typescriptConfiguration: false, + }); + + expect(tree.exists('test/webpack.server.config.js')).toBeTruthy(); + expect(tree.exists('test/module-federation.server.config.js')).toBeTruthy(); + + expect( + tree.read('test/webpack.server.config.js', 'utf-8') + ).toMatchSnapshot(); + expect( + tree.read('test/module-federation.server.config.js', 'utf-8') + ).toMatchSnapshot(); + }); + + it('should generate correct remote with config files when using --ssr and --typescriptConfiguration=true', async () => { + const tree = createTreeWithEmptyWorkspace(); + + await remote(tree, { + name: 'test', + devServerPort: 4201, + e2eTestRunner: 'cypress', + linter: Linter.EsLint, + skipFormat: false, + style: 'css', + unitTestRunner: 'jest', + ssr: true, + projectNameAndRootFormat: 'as-provided', + typescriptConfiguration: true, + }); + + expect(tree.exists('test/webpack.server.config.ts')).toBeTruthy(); + expect(tree.exists('test/module-federation.server.config.ts')).toBeTruthy(); + + expect( + tree.read('test/webpack.server.config.ts', 'utf-8') + ).toMatchSnapshot(); + expect( + tree.read('test/module-federation.server.config.ts', 'utf-8') + ).toMatchSnapshot(); + }); }); diff --git a/packages/react/src/generators/remote/remote.ts b/packages/react/src/generators/remote/remote.ts index 65df8088590ac..a51570464afef 100644 --- a/packages/react/src/generators/remote/remote.ts +++ b/packages/react/src/generators/remote/remote.ts @@ -22,7 +22,7 @@ import { setupSsrForRemote } from './lib/setup-ssr-for-remote'; export function addModuleFederationFiles( host: Tree, - options: NormalizedSchema + options: NormalizedSchema ) { const templateVariables = { ...names(options.name), @@ -30,12 +30,33 @@ export function addModuleFederationFiles( tmpl: '', }; + const pathToModuleFederationFiles = options.typescriptConfiguration + ? 'module-federation-ts' + : 'module-federation'; + generateFiles( host, - join(__dirname, `./files/module-federation`), + join(__dirname, `./files/${pathToModuleFederationFiles}`), options.appProjectRoot, templateVariables ); + + if (options.typescriptConfiguration) { + const pathToWebpackConfig = joinPathFragments( + options.appProjectRoot, + 'webpack.config.js' + ); + const pathToWebpackProdConfig = joinPathFragments( + options.appProjectRoot, + 'webpack.config.prod.js' + ); + if (host.exists(pathToWebpackConfig)) { + host.delete(pathToWebpackConfig); + } + if (host.exists(pathToWebpackProdConfig)) { + host.delete(pathToWebpackProdConfig); + } + } } export async function remoteGenerator(host: Tree, schema: Schema) { @@ -47,11 +68,10 @@ export async function remoteGenerator(host: Tree, schema: Schema) { export async function remoteGeneratorInternal(host: Tree, schema: Schema) { const tasks: GeneratorCallback[] = []; - const options = await normalizeOptions( - host, - schema, - '@nx/react:remote' - ); + const options: NormalizedSchema = { + ...(await normalizeOptions(host, schema, '@nx/react:remote')), + typescriptConfiguration: schema.typescriptConfiguration ?? true, + }; const initAppTask = await applicationGenerator(host, { ...options, // Only webpack works with module federation for now. @@ -93,7 +113,7 @@ export async function remoteGeneratorInternal(host: Tree, schema: Schema) { const projectConfig = readProjectConfiguration(host, options.projectName); projectConfig.targets.server.options.webpackConfig = joinPathFragments( projectConfig.root, - 'webpack.server.config.js' + `webpack.server.config.${options.typescriptConfiguration ? 'ts' : 'js'}` ); updateProjectConfiguration(host, options.projectName, projectConfig); } diff --git a/packages/react/src/generators/remote/schema.d.ts b/packages/react/src/generators/remote/schema.d.ts index e964deef65b58..cbcb6a96f17c1 100644 --- a/packages/react/src/generators/remote/schema.d.ts +++ b/packages/react/src/generators/remote/schema.d.ts @@ -1,6 +1,7 @@ import type { ProjectNameAndRootFormat } from '@nx/devkit/src/generators/project-name-and-root-utils'; import type { Linter } from '@nx/linter'; import type { SupportedStyles } from '../../../typings'; +import type { NormalizedSchema as ApplicationNormalizedSchema } from '../application/schema'; export interface Schema { classComponent?: boolean; @@ -24,4 +25,9 @@ export interface Schema { style: SupportedStyles; tags?: string; unitTestRunner: 'jest' | 'vitest' | 'none'; + typescriptConfiguration?: boolean; +} + +export interface NormalizedSchema extends ApplicationNormalizedSchema { + typescriptConfiguration: boolean; } diff --git a/packages/react/src/generators/remote/schema.json b/packages/react/src/generators/remote/schema.json index 2d669ce11103a..b17641d08b353 100644 --- a/packages/react/src/generators/remote/schema.json +++ b/packages/react/src/generators/remote/schema.json @@ -164,6 +164,11 @@ "description": "Whether to configure SSR for the host application", "type": "boolean", "default": false + }, + "typescriptConfiguration": { + "type": "boolean", + "description": "Whether the module federation configuration and webpack configuration files should use TS.", + "default": true } }, "required": ["name"], diff --git a/packages/react/src/rules/update-module-federation-project.ts b/packages/react/src/rules/update-module-federation-project.ts index c0a83b781e635..7534b19a07a13 100644 --- a/packages/react/src/rules/update-module-federation-project.ts +++ b/packages/react/src/rules/update-module-federation-project.ts @@ -13,6 +13,7 @@ export function updateModuleFederationProject( projectName: string; appProjectRoot: string; devServerPort?: number; + typescriptConfiguration?: boolean; } ): GeneratorCallback { const projectConfig = readProjectConfiguration(host, options.projectName); @@ -20,12 +21,16 @@ export function updateModuleFederationProject( projectConfig.targets.build.options = { ...projectConfig.targets.build.options, main: `${options.appProjectRoot}/src/main.ts`, - webpackConfig: `${options.appProjectRoot}/webpack.config.js`, + webpackConfig: `${options.appProjectRoot}/webpack.config.${ + options.typescriptConfiguration ? 'ts' : 'js' + }`, }; projectConfig.targets.build.configurations.production = { ...projectConfig.targets.build.configurations.production, - webpackConfig: `${options.appProjectRoot}/webpack.config.prod.js`, + webpackConfig: `${options.appProjectRoot}/webpack.config.prod.${ + options.typescriptConfiguration ? 'ts' : 'js' + }`, }; projectConfig.targets.serve.executor =