From aa9c9a6623795977d0ef965c93bd2690e2e7fbf9 Mon Sep 17 00:00:00 2001 From: Remco Date: Tue, 7 Nov 2023 13:00:31 +0100 Subject: [PATCH 1/7] fix(module-federation): fix module federation for React js --- .../host/__snapshots__/host.spec.ts.snap | 77 ++++++++++--------- .../src/app/__fileName__.tsx__tmpl__ | 0 .../src/main.ts__tmpl__ | 0 .../common/src/app/__fileName__.js__tmpl__ | 33 ++++++++ .../src/main.js__tmpl__} | 0 .../react/src/generators/host/host.spec.ts | 61 ++++++++++++--- .../host/lib/add-module-federation-files.ts | 15 +++- .../src/main.ts__tmpl__ | 0 .../src/remote-entry.ts__tmpl__ | 0 .../src/main.js__tmpl__} | 0 .../src/remote-entry.js__tmpl__} | 0 .../remote/lib/setup-tspath-for-remote.ts | 2 +- .../remote/lib/update-host-with-remote.ts | 11 ++- .../react/src/generators/remote/remote.ts | 28 +++---- .../rules/update-module-federation-project.ts | 3 +- 15 files changed, 157 insertions(+), 73 deletions(-) rename packages/react/src/generators/host/files/{common => common-ts}/src/app/__fileName__.tsx__tmpl__ (100%) rename packages/react/src/generators/host/files/{module-federation-ts => common-ts}/src/main.ts__tmpl__ (100%) create mode 100644 packages/react/src/generators/host/files/common/src/app/__fileName__.js__tmpl__ rename packages/react/src/generators/host/files/{module-federation/src/main.ts__tmpl__ => common/src/main.js__tmpl__} (100%) rename packages/react/src/generators/remote/files/{module-federation-ts => common-ts}/src/main.ts__tmpl__ (100%) rename packages/react/src/generators/remote/files/{module-federation-ts => common-ts}/src/remote-entry.ts__tmpl__ (100%) rename packages/react/src/generators/remote/files/{module-federation/src/main.ts__tmpl__ => common/src/main.js__tmpl__} (100%) rename packages/react/src/generators/remote/files/{module-federation/src/remote-entry.ts__tmpl__ => common/src/remote-entry.js__tmpl__} (100%) diff --git a/packages/react/src/generators/host/__snapshots__/host.spec.ts.snap b/packages/react/src/generators/host/__snapshots__/host.spec.ts.snap index f3ed178601a6f..c77b7a573f969 100644 --- a/packages/react/src/generators/host/__snapshots__/host.spec.ts.snap +++ b/packages/react/src/generators/host/__snapshots__/host.spec.ts.snap @@ -1,42 +1,5 @@ // 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', - /** - * To use a remote that does not exist in your current Nx Workspace - * You can use the tuple-syntax to define your remote - * - * remotes: [['my-external-remote', 'https://nx-angular-remote.netlify.app']] - * - * You _may_ need to add a \`remotes.d.ts\` file to your \`src/\` folder declaring the external remote for tsc, with the - * following content: - * - * declare module 'my-external-remote'; - * - */ - remotes: [ - - ], -};" -`; - exports[`hostGenerator should generate host files and configs for SSR 1`] = ` "const { composePlugins, withNx } = require('@nx/webpack'); const { withReact } = require('@nx/react'); @@ -104,6 +67,46 @@ export default config; " `; +exports[`hostGenerator should generate host files and configs when --typescriptConfiguration=false 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 when --typescriptConfiguration=false 2`] = ` +"module.exports = { + name: 'test', + /** + * To use a remote that does not exist in your current Nx Workspace + * You can use the tuple-syntax to define your remote + * + * remotes: [['my-external-remote', 'https://nx-angular-remote.netlify.app']] + * + * You _may_ need to add a \`remotes.d.ts\` file to your \`src/\` folder declaring the external remote for tsc, with the + * following content: + * + * declare module 'my-external-remote'; + * + */ + remotes: [], +}; +" +`; + exports[`hostGenerator should generate host files and configs when --typescriptConfiguration=true 1`] = ` "import {composePlugins, withNx, ModuleFederationConfig} from '@nx/webpack'; import {withReact} from '@nx/react'; diff --git a/packages/react/src/generators/host/files/common/src/app/__fileName__.tsx__tmpl__ b/packages/react/src/generators/host/files/common-ts/src/app/__fileName__.tsx__tmpl__ similarity index 100% rename from packages/react/src/generators/host/files/common/src/app/__fileName__.tsx__tmpl__ rename to packages/react/src/generators/host/files/common-ts/src/app/__fileName__.tsx__tmpl__ diff --git a/packages/react/src/generators/host/files/module-federation-ts/src/main.ts__tmpl__ b/packages/react/src/generators/host/files/common-ts/src/main.ts__tmpl__ similarity index 100% rename from packages/react/src/generators/host/files/module-federation-ts/src/main.ts__tmpl__ rename to packages/react/src/generators/host/files/common-ts/src/main.ts__tmpl__ diff --git a/packages/react/src/generators/host/files/common/src/app/__fileName__.js__tmpl__ b/packages/react/src/generators/host/files/common/src/app/__fileName__.js__tmpl__ new file mode 100644 index 0000000000000..00edc49cc2d87 --- /dev/null +++ b/packages/react/src/generators/host/files/common/src/app/__fileName__.js__tmpl__ @@ -0,0 +1,33 @@ +import * as React from 'react'; +<% if (!minimal) { %> +import NxWelcome from "./nx-welcome"; +<% } %> +import { Link, Route, Routes } from 'react-router-dom'; + +<% if (remotes.length > 0) { %> +<% remotes.forEach(function(r) { %> + const <%= r.className %> = React.lazy(() => import('<%= r.fileName %>/Module')); + <% }); %> +<% } %> +export function App() { + return ( + + + + <% if (!minimal) { %> + } /> + <% } %> + <% remotes.forEach(function(r) { %> + />} /> + <% }); %> + + + ); +} + +export default App; diff --git a/packages/react/src/generators/host/files/module-federation/src/main.ts__tmpl__ b/packages/react/src/generators/host/files/common/src/main.js__tmpl__ similarity index 100% rename from packages/react/src/generators/host/files/module-federation/src/main.ts__tmpl__ rename to packages/react/src/generators/host/files/common/src/main.js__tmpl__ diff --git a/packages/react/src/generators/host/host.spec.ts b/packages/react/src/generators/host/host.spec.ts index 7f360b965886a..c45c65cecf729 100644 --- a/packages/react/src/generators/host/host.spec.ts +++ b/packages/react/src/generators/host/host.spec.ts @@ -104,7 +104,7 @@ describe('hostGenerator', () => { tree = createTreeWithEmptyWorkspace(); }); - it('should generate host files and configs', async () => { + it('should generate host files and configs when --js=true', async () => { await hostGenerator(tree, { name: 'test', style: 'css', @@ -114,18 +114,32 @@ describe('hostGenerator', () => { projectNameAndRootFormat: 'as-provided', typescriptConfiguration: false, skipFormat: true, + js: true, }); 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.js')).toBeTruthy(); + expect(tree.exists('test/src/main.js')).toBeTruthy(); + expect(tree.exists('test/src/app/app.js')).toBeTruthy(); + }); + + it('should generate host files and configs when --js=false', async () => { + await hostGenerator(tree, { + name: 'test', + style: 'css', + linter: Linter.None, + unitTestRunner: 'none', + e2eTestRunner: 'none', + projectNameAndRootFormat: 'as-provided', + typescriptConfiguration: false, + }); + + expect(tree.exists('test/tsconfig.json')).toBeTruthy(); + expect(tree.exists('test/src/bootstrap.tsx')).toBeTruthy(); expect(tree.exists('test/src/main.ts')).toBeTruthy(); - expect(tree.read('test/webpack.config.js', 'utf-8')).toMatchSnapshot(); - expect( - tree.read('test/module-federation.config.js', 'utf-8') - ).toMatchSnapshot(); + expect(tree.exists('test/src/app/app.tsx')).toBeTruthy(); }); it('should generate host files and configs when --typescriptConfiguration=true', async () => { @@ -141,17 +155,42 @@ describe('hostGenerator', () => { }); 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.read('test/webpack.config.ts', 'utf-8')).toMatchSnapshot(); + + expect(tree.exists('test/module-federation.config.ts')).toBeTruthy(); expect( tree.read('test/module-federation.config.ts', 'utf-8') ).toMatchSnapshot(); }); + it('should generate host files and configs when --typescriptConfiguration=false', async () => { + await hostGenerator(tree, { + name: 'test', + style: 'css', + linter: Linter.None, + unitTestRunner: 'none', + e2eTestRunner: 'none', + projectNameAndRootFormat: 'as-provided', + typescriptConfiguration: false, + }); + + 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.read('test/webpack.config.js', 'utf-8')).toMatchSnapshot(); + + expect(tree.exists('test/module-federation.config.js')).toBeTruthy(); + expect( + tree.read('test/module-federation.config.js', 'utf-8') + ).toMatchSnapshot(); + }); + it('should install @nx/web for the file-server executor', async () => { const tree = createTreeWithEmptyWorkspace(); await hostGenerator(tree, { 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 d020ade2c0912..72636636e4934 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 @@ -35,13 +35,22 @@ export function addModuleFederationFiles( // Renaming original entry file so we can use `import(./bootstrap)` in // new entry file. host.rename( - joinPathFragments(options.appProjectRoot, 'src/main.tsx'), - joinPathFragments(options.appProjectRoot, 'src/bootstrap.tsx') + joinPathFragments( + options.appProjectRoot, + `src/main.${options.js ? 'js' : 'tsx'}` + ), + joinPathFragments( + options.appProjectRoot, + `src/bootstrap.${options.js ? 'js' : 'tsx'}` + ) ); generateFiles( host, - joinPathFragments(__dirname, `../files/common`), + joinPathFragments( + __dirname, + `../files/${options.js ? 'common' : 'common-ts'}` + ), options.appProjectRoot, templateVariables ); diff --git a/packages/react/src/generators/remote/files/module-federation-ts/src/main.ts__tmpl__ b/packages/react/src/generators/remote/files/common-ts/src/main.ts__tmpl__ similarity index 100% rename from packages/react/src/generators/remote/files/module-federation-ts/src/main.ts__tmpl__ rename to packages/react/src/generators/remote/files/common-ts/src/main.ts__tmpl__ diff --git a/packages/react/src/generators/remote/files/module-federation-ts/src/remote-entry.ts__tmpl__ b/packages/react/src/generators/remote/files/common-ts/src/remote-entry.ts__tmpl__ similarity index 100% rename from packages/react/src/generators/remote/files/module-federation-ts/src/remote-entry.ts__tmpl__ rename to packages/react/src/generators/remote/files/common-ts/src/remote-entry.ts__tmpl__ diff --git a/packages/react/src/generators/remote/files/module-federation/src/main.ts__tmpl__ b/packages/react/src/generators/remote/files/common/src/main.js__tmpl__ similarity index 100% rename from packages/react/src/generators/remote/files/module-federation/src/main.ts__tmpl__ rename to packages/react/src/generators/remote/files/common/src/main.js__tmpl__ diff --git a/packages/react/src/generators/remote/files/module-federation/src/remote-entry.ts__tmpl__ b/packages/react/src/generators/remote/files/common/src/remote-entry.js__tmpl__ similarity index 100% rename from packages/react/src/generators/remote/files/module-federation/src/remote-entry.ts__tmpl__ rename to packages/react/src/generators/remote/files/common/src/remote-entry.js__tmpl__ diff --git a/packages/react/src/generators/remote/lib/setup-tspath-for-remote.ts b/packages/react/src/generators/remote/lib/setup-tspath-for-remote.ts index 1f17568a55ba2..fee2c812032f9 100644 --- a/packages/react/src/generators/remote/lib/setup-tspath-for-remote.ts +++ b/packages/react/src/generators/remote/lib/setup-tspath-for-remote.ts @@ -6,7 +6,7 @@ import { addTsConfigPath } from '@nx/js'; export function setupTspathForRemote(tree: Tree, options: Schema) { const project = readProjectConfiguration(tree, options.name); - const exportPath = `./src/remote-entry.ts`; + const exportPath = `./src/remote-entry.${options.js ? 'js' : 'ts'}`; const exportName = 'Module'; 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 44868ce1dae25..1fd65945f5b1a 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 @@ -105,7 +105,16 @@ export function updateHostWithRemote( } function findAppComponentPath(host: Tree, sourceRoot: string) { - const locations = ['app/app.tsx', 'app/App.tsx', 'app.tsx', 'App.tsx']; + const locations = [ + 'app/app.jsx', + 'app/App.jsx', + 'app.jsx', + 'App.jsx', + 'app/app.tsx', + 'app/App.tsx', + 'app.tsx', + 'App.tsx', + ]; for (const loc of locations) { if (host.exists(joinPathFragments(sourceRoot, loc))) { return joinPathFragments(sourceRoot, loc); diff --git a/packages/react/src/generators/remote/remote.ts b/packages/react/src/generators/remote/remote.ts index 5754f1a1142d1..45ecfc416d78e 100644 --- a/packages/react/src/generators/remote/remote.ts +++ b/packages/react/src/generators/remote/remote.ts @@ -33,6 +33,13 @@ export function addModuleFederationFiles( tmpl: '', }; + generateFiles( + host, + join(__dirname, `../files/${options.js ? 'common' : 'common-ts'}`), + options.appProjectRoot, + templateVariables + ); + const pathToModuleFederationFiles = options.typescriptConfiguration ? 'module-federation-ts' : 'module-federation'; @@ -43,23 +50,6 @@ export function addModuleFederationFiles( 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) { @@ -94,8 +84,8 @@ export async function remoteGeneratorInternal(host: Tree, schema: Schema) { // Renaming original entry file so we can use `import(./bootstrap)` in // new entry file. host.rename( - join(options.appProjectRoot, 'src/main.tsx'), - join(options.appProjectRoot, 'src/bootstrap.tsx') + join(options.appProjectRoot, `src/main.${options.js ? 'js' : 'tsx'}`), + join(options.appProjectRoot, `src/bootstrap.${options.js ? 'js' : 'tsx'}`) ); addModuleFederationFiles(host, options); diff --git a/packages/react/src/rules/update-module-federation-project.ts b/packages/react/src/rules/update-module-federation-project.ts index 02c4c787dec50..2f95c03af8e07 100644 --- a/packages/react/src/rules/update-module-federation-project.ts +++ b/packages/react/src/rules/update-module-federation-project.ts @@ -16,13 +16,14 @@ export function updateModuleFederationProject( devServerPort?: number; typescriptConfiguration?: boolean; dynamic?: boolean; + js?: boolean; } ): GeneratorCallback { const projectConfig = readProjectConfiguration(host, options.projectName); projectConfig.targets.build.options = { ...projectConfig.targets.build.options, - main: `${options.appProjectRoot}/src/main.ts`, + main: `${options.appProjectRoot}/src/main.${options.js ? 'js' : 'ts'}`, webpackConfig: `${options.appProjectRoot}/webpack.config.${ options.typescriptConfiguration ? 'ts' : 'js' }`, From 8de86858d128252c4d9f525870b9c2bac7c51133 Mon Sep 17 00:00:00 2001 From: Remco Date: Tue, 7 Nov 2023 16:23:11 +0100 Subject: [PATCH 2/7] fix(module-federation): fix test for remote --- packages/react/src/generators/remote/remote.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/generators/remote/remote.ts b/packages/react/src/generators/remote/remote.ts index 45ecfc416d78e..2164ee91a4d99 100644 --- a/packages/react/src/generators/remote/remote.ts +++ b/packages/react/src/generators/remote/remote.ts @@ -35,7 +35,7 @@ export function addModuleFederationFiles( generateFiles( host, - join(__dirname, `../files/${options.js ? 'common' : 'common-ts'}`), + join(__dirname, `./files/${options.js ? 'common' : 'common-ts'}`), options.appProjectRoot, templateVariables ); From 4ee29d5930a46792bc40e396660ab94ccd847e4a Mon Sep 17 00:00:00 2001 From: Remco Date: Tue, 7 Nov 2023 17:41:33 +0100 Subject: [PATCH 3/7] fix(module-federation): fix host app component path in remote --- .../src/generators/remote/lib/update-host-with-remote.ts | 9 +++++---- packages/react/src/generators/remote/remote.spec.ts | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) 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 1fd65945f5b1a..2bfeb2be28009 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,6 +25,7 @@ export function updateHostWithRemote( } const hostConfig = readProjectConfiguration(host, hostName); + let moduleFederationConfigPath = joinPathFragments( hostConfig.root, 'module-federation.config.js' @@ -106,10 +107,10 @@ export function updateHostWithRemote( function findAppComponentPath(host: Tree, sourceRoot: string) { const locations = [ - 'app/app.jsx', - 'app/App.jsx', - 'app.jsx', - 'App.jsx', + 'app/app.js', + 'app/App.js', + 'app.js', + 'App.js', 'app/app.tsx', 'app/App.tsx', 'app.tsx', diff --git a/packages/react/src/generators/remote/remote.spec.ts b/packages/react/src/generators/remote/remote.spec.ts index daa6b83d3d539..b02e9bbbb864e 100644 --- a/packages/react/src/generators/remote/remote.spec.ts +++ b/packages/react/src/generators/remote/remote.spec.ts @@ -3,7 +3,7 @@ import { ProjectGraph, readJson, readNxJson } from '@nx/devkit'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; import { Linter } from '@nx/eslint'; import remote from './remote'; -import { getRootTsConfigPath, getRootTsConfigPathInTree } from '@nx/js'; +import { getRootTsConfigPathInTree } from '@nx/js'; jest.mock('@nx/devkit', () => { const original = jest.requireActual('@nx/devkit'); From b537686b0fc2e664bf1f2d2df50abe9fb637cbac Mon Sep 17 00:00:00 2001 From: Remco Date: Wed, 8 Nov 2023 21:47:13 +0100 Subject: [PATCH 4/7] fix(module-federation): re-add removal of js files --- packages/react/src/generators/remote/remote.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/react/src/generators/remote/remote.ts b/packages/react/src/generators/remote/remote.ts index 2164ee91a4d99..ce2aba070f5f4 100644 --- a/packages/react/src/generators/remote/remote.ts +++ b/packages/react/src/generators/remote/remote.ts @@ -50,6 +50,23 @@ export function addModuleFederationFiles( 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) { From 54a0b6240f4bb5486e5d1051fbad52435625a01b Mon Sep 17 00:00:00 2001 From: Remco Date: Wed, 3 Jan 2024 13:53:00 +0100 Subject: [PATCH 5/7] fix(module-federation): fix wrong exposes for js --- .../remote/__snapshots__/remote.spec.ts.snap | 29 +++++++++++++++++ .../module-federation.config.js__tmpl__ | 2 +- .../src/generators/remote/remote.spec.ts | 31 +++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/packages/react/src/generators/remote/__snapshots__/remote.spec.ts.snap b/packages/react/src/generators/remote/__snapshots__/remote.spec.ts.snap index 866590a2f648b..86f3cb22418fb 100644 --- a/packages/react/src/generators/remote/__snapshots__/remote.spec.ts.snap +++ b/packages/react/src/generators/remote/__snapshots__/remote.spec.ts.snap @@ -29,6 +29,35 @@ exports[`remote generator should create the remote with the correct config files " `; +exports[`remote generator should create the remote with the correct config files when --js=true 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 when --js=true 2`] = `"module.exports = require('./webpack.config');"`; + +exports[`remote generator should create the remote with the correct config files when --js=true 3`] = ` +"module.exports = { + name: 'test', + + exposes: { + './Module': './src/remote-entry.js', + }, +}; +" +`; + 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'; diff --git a/packages/react/src/generators/remote/files/module-federation/module-federation.config.js__tmpl__ b/packages/react/src/generators/remote/files/module-federation/module-federation.config.js__tmpl__ index c03dfd1688bdd..042baaf403ada 100644 --- a/packages/react/src/generators/remote/files/module-federation/module-federation.config.js__tmpl__ +++ b/packages/react/src/generators/remote/files/module-federation/module-federation.config.js__tmpl__ @@ -4,6 +4,6 @@ module.exports = { library: { type: 'var', name: '<%= projectName %>'}, <% } %> exposes: { - './Module': './src/remote-entry.ts', + './Module': './src/remote-entry.<%= locals.js ? 'js' : 'ts' %>', }, }; diff --git a/packages/react/src/generators/remote/remote.spec.ts b/packages/react/src/generators/remote/remote.spec.ts index b02e9bbbb864e..51da2f98cbf63 100644 --- a/packages/react/src/generators/remote/remote.spec.ts +++ b/packages/react/src/generators/remote/remote.spec.ts @@ -128,6 +128,37 @@ describe('remote generator', () => { ]); }); + it('should create the remote with the correct config files when --js=true', async () => { + const tree = createTreeWithEmptyWorkspace(); + await remote(tree, { + name: 'test', + devServerPort: 4201, + e2eTestRunner: 'cypress', + linter: Linter.EsLint, + skipFormat: true, + style: 'css', + unitTestRunner: 'jest', + projectNameAndRootFormat: 'as-provided', + typescriptConfiguration: false, + js: true, + }); + + 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(); + + const tsconfigJson = readJson(tree, getRootTsConfigPathInTree(tree)); + expect(tsconfigJson.compilerOptions.paths['test/Module']).toEqual([ + 'test/src/remote-entry.js', + ]); + }); + it('should create the remote with the correct config files when --typescriptConfiguration=true', async () => { const tree = createTreeWithEmptyWorkspace(); await remote(tree, { From 46a40b3b0732bbb27488b9201c97ca916e6e68af Mon Sep 17 00:00:00 2001 From: Jack Hsu Date: Mon, 29 Jan 2024 12:43:18 -0500 Subject: [PATCH 6/7] chore(react): clean-up and add e2e test for --js option for module federation --- .../src/react-module-federation.test.ts | 141 ++++++++++-------- packages/cypress/plugins/cypress-preset.ts | 14 +- packages/react/src/generators/host/host.ts | 1 + .../host/lib/add-module-federation-files.ts | 7 +- .../react/src/generators/remote/remote.ts | 24 +-- .../rules/update-module-federation-project.ts | 8 +- 6 files changed, 102 insertions(+), 93 deletions(-) diff --git a/e2e/react-module-federation/src/react-module-federation.test.ts b/e2e/react-module-federation/src/react-module-federation.test.ts index 2f48529138259..3696c072bb24f 100644 --- a/e2e/react-module-federation/src/react-module-federation.test.ts +++ b/e2e/react-module-federation/src/react-module-federation.test.ts @@ -20,51 +20,61 @@ import { join } from 'path'; import { createTreeWithEmptyWorkspace } from 'nx/src/devkit-testing-exports'; describe('React Module Federation', () => { - let proj: string; - let tree: Tree; - beforeAll(() => { - tree = createTreeWithEmptyWorkspace(); - proj = newProject({ packages: ['@nx/react'] }); + newProject({ packages: ['@nx/react'] }); }); afterAll(() => cleanupProject()); describe('Default Configuration', () => { - it('should generate host and remote apps', async () => { - const shell = uniq('shell'); - const remote1 = uniq('remote1'); - const remote2 = uniq('remote2'); - const remote3 = uniq('remote3'); - - // Since we are using a single-file server for the remotes - const defaultRemotePort = 4201; - - runCLI( - `generate @nx/react:host ${shell} --remotes=${remote1},${remote2},${remote3} --style=css --no-interactive --skipFormat` - ); + it.each` + js + ${false} + ${true} + `( + 'should generate host and remote apps', + async ({ js }) => { + const shell = uniq('shell'); + const remote1 = uniq('remote1'); + const remote2 = uniq('remote2'); + const remote3 = uniq('remote3'); + + // Since we are using a single-file server for the remotes + const defaultRemotePort = 4201; + + runCLI( + `generate @nx/react:host ${shell} --remotes=${remote1},${remote2},${remote3} --style=css --no-interactive --skipFormat --js=${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`); + checkFilesExist( + `apps/${shell}/module-federation.config.${js ? 'js' : 'ts'}` + ); + checkFilesExist( + `apps/${remote1}/module-federation.config.${js ? 'js' : 'ts'}` + ); + checkFilesExist( + `apps/${remote2}/module-federation.config.${js ? 'js' : 'ts'}` + ); + checkFilesExist( + `apps/${remote3}/module-federation.config.${js ? 'js' : 'ts'}` + ); - await expect(runCLIAsync(`test ${shell}`)).resolves.toMatchObject({ - combinedOutput: expect.stringContaining( - 'Test Suites: 1 passed, 1 total' - ), - }); + await expect(runCLIAsync(`test ${shell}`)).resolves.toMatchObject({ + combinedOutput: expect.stringContaining( + 'Test Suites: 1 passed, 1 total' + ), + }); - updateFile( - `apps/${shell}/webpack.config.ts`, - stripIndents` - import { composePlugins, withNx, ModuleFederationConfig } from '@nx/webpack'; - import { withReact } from '@nx/react'; - import { withModuleFederation } from '@nx/react/module-federation'; + updateFile( + `apps/${shell}/webpack.config.${js ? 'js' : 'ts'}`, + stripIndents` + const { composePlugins, withNx } = require('@nx/webpack'); + const { withReact } = require('@nx/react'); + const { withModuleFederation } = require('@nx/react/module-federation'); - import baseConfig from './module-federation.config'; + const baseConfig = require('./module-federation.config'); - const config: ModuleFederationConfig = { + const config = { ...baseConfig, remotes: [ '${remote1}', @@ -76,11 +86,11 @@ describe('React Module Federation', () => { // Nx plugins for webpack to build config object from Nx options and context. module.exports = composePlugins(withNx(), withReact(), withModuleFederation(config)); ` - ); + ); - updateFile( - `apps/${shell}-e2e/src/integration/app.spec.ts`, - stripIndents` + updateFile( + `apps/${shell}-e2e/src/integration/app.spec.${js ? 'js' : 'ts'}`, + stripIndents` import { getGreeting } from '../support/app.po'; describe('shell app', () => { @@ -105,34 +115,36 @@ describe('React Module Federation', () => { }); }); ` - ); - - if (runE2ETests()) { - const e2eResultsSwc = await runCommandUntil( - `e2e ${shell}-e2e --no-watch --verbose`, - (output) => output.includes('All specs passed!') ); - await killProcessAndPorts( - e2eResultsSwc.pid, - readPort(shell), - defaultRemotePort - ); + if (runE2ETests()) { + const e2eResultsSwc = await runCommandUntil( + `e2e ${shell}-e2e --no-watch --verbose`, + (output) => output.includes('All specs passed!') + ); - const e2eResultsTsNode = await runCommandUntil( - `e2e ${shell}-e2e --no-watch --verbose`, - (output) => output.includes('All specs passed!'), - { - env: { NX_PREFER_TS_NODE: 'true' }, - } - ); - await killProcessAndPorts( - e2eResultsTsNode.pid, - readPort(shell), - defaultRemotePort - ); - } - }, 500_000); + await killProcessAndPorts( + e2eResultsSwc.pid, + readPort(shell), + defaultRemotePort + ); + + const e2eResultsTsNode = await runCommandUntil( + `e2e ${shell}-e2e --no-watch --verbose`, + (output) => output.includes('All specs passed!'), + { + env: { NX_PREFER_TS_NODE: 'true' }, + } + ); + await killProcessAndPorts( + e2eResultsTsNode.pid, + readPort(shell), + defaultRemotePort + ); + } + }, + 500_000 + ); it('should generate host and remote apps with ssr', async () => { const shell = uniq('shell'); @@ -865,8 +877,7 @@ describe('React Module Federation', () => { describe('Dynamic Module Federation', () => { beforeAll(() => { - tree = createTreeWithEmptyWorkspace(); - proj = newProject({ packages: ['@nx/react'] }); + newProject({ packages: ['@nx/react'] }); }); afterAll(() => cleanupProject()); diff --git a/packages/cypress/plugins/cypress-preset.ts b/packages/cypress/plugins/cypress-preset.ts index 824b63083e685..84ca2b1ddc8bd 100644 --- a/packages/cypress/plugins/cypress-preset.ts +++ b/packages/cypress/plugins/cypress-preset.ts @@ -1,6 +1,6 @@ import { workspaceRoot } from '@nx/devkit'; import { dirname, join, relative } from 'path'; -import { lstatSync } from 'fs'; +import { existsSync, lstatSync } from 'fs'; import vitePreprocessor from '../src/plugins/preprocessor-vite'; import { NX_PLUGIN_OPTIONS } from '../src/utils/constants'; @@ -102,12 +102,22 @@ export function nxE2EPreset( ) { const basePath = options?.cypressDir || 'src'; + const dir = dirname(pathToConfig); + let supportFile: undefined | string = undefined; + for (const f of ['e2e.ts', 'e2e.js']) { + const candidate = join(dir, basePath, 'support', f); + if (existsSync(candidate)) { + supportFile = candidate; + break; + } + } + const baseConfig: any /*Cypress.EndToEndConfigOptions & { [NX_PLUGIN_OPTIONS]: unknown; }*/ = { ...nxBaseCypressPreset(pathToConfig), fileServerFolder: '.', - supportFile: `${basePath}/support/e2e.{js,ts}`, + supportFile, specPattern: `${basePath}/**/*.cy.{js,jsx,ts,tsx}`, fixturesFolder: `${basePath}/fixtures`, diff --git a/packages/react/src/generators/host/host.ts b/packages/react/src/generators/host/host.ts index 1be2d52ccafc5..7b26f7ac27c84 100644 --- a/packages/react/src/generators/host/host.ts +++ b/packages/react/src/generators/host/host.ts @@ -75,6 +75,7 @@ export async function hostGeneratorInternal( skipFormat: true, projectNameAndRootFormat: options.projectNameAndRootFormat, typescriptConfiguration: options.typescriptConfiguration, + js: options.js, dynamic: options.dynamic, host: options.name, }); 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 72636636e4934..489de5313e6c5 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 @@ -55,9 +55,10 @@ export function addModuleFederationFiles( templateVariables ); - const pathToModuleFederationFiles = options.typescriptConfiguration - ? 'module-federation-ts' - : 'module-federation'; + const pathToModuleFederationFiles = + options.typescriptConfiguration && !options.js + ? 'module-federation-ts' + : 'module-federation'; // New entry file is created here. generateFiles( host, diff --git a/packages/react/src/generators/remote/remote.ts b/packages/react/src/generators/remote/remote.ts index ce2aba070f5f4..c650101653633 100644 --- a/packages/react/src/generators/remote/remote.ts +++ b/packages/react/src/generators/remote/remote.ts @@ -40,9 +40,10 @@ export function addModuleFederationFiles( templateVariables ); - const pathToModuleFederationFiles = options.typescriptConfiguration - ? 'module-federation-ts' - : 'module-federation'; + const pathToModuleFederationFiles = + options.typescriptConfiguration && !options.js + ? 'module-federation-ts' + : 'module-federation'; generateFiles( host, @@ -50,23 +51,6 @@ export function addModuleFederationFiles( 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) { diff --git a/packages/react/src/rules/update-module-federation-project.ts b/packages/react/src/rules/update-module-federation-project.ts index 2f95c03af8e07..7257dd0801c5b 100644 --- a/packages/react/src/rules/update-module-federation-project.ts +++ b/packages/react/src/rules/update-module-federation-project.ts @@ -25,14 +25,14 @@ export function updateModuleFederationProject( ...projectConfig.targets.build.options, main: `${options.appProjectRoot}/src/main.${options.js ? 'js' : 'ts'}`, webpackConfig: `${options.appProjectRoot}/webpack.config.${ - options.typescriptConfiguration ? 'ts' : 'js' + options.typescriptConfiguration && !options.js ? 'ts' : 'js' }`, }; projectConfig.targets.build.configurations.production = { ...projectConfig.targets.build.configurations.production, webpackConfig: `${options.appProjectRoot}/webpack.config.prod.${ - options.typescriptConfiguration ? 'ts' : 'js' + options.typescriptConfiguration && !options.js ? 'ts' : 'js' }`, }; @@ -40,7 +40,9 @@ export function updateModuleFederationProject( if (options.dynamic) { const pathToProdWebpackConfig = joinPathFragments( projectConfig.root, - `webpack.prod.config.${options.typescriptConfiguration ? 'ts' : 'js'}` + `webpack.prod.config.${ + options.typescriptConfiguration && !options.js ? 'ts' : 'js' + }` ); if (host.exists(pathToProdWebpackConfig)) { host.delete(pathToProdWebpackConfig); From 2d24ef8a63418550477a19c5fa05fcebfefe90a1 Mon Sep 17 00:00:00 2001 From: Emily Xiong Date: Wed, 7 Feb 2024 14:28:52 -0500 Subject: [PATCH 7/7] fix(react): fix js for module federation --- .../react/generators/federate-module.json | 3 +- .../packages/react/generators/host.json | 5 +- .../packages/react/generators/remote.json | 5 +- .../src/react-module-federation.test.ts | 50 +++++++++++++++++-- .../src/generators/application/application.ts | 3 +- .../generators/application/lib/add-project.ts | 7 +-- .../generators/application/lib/add-routing.ts | 13 ++--- .../lib/create-application-files.ts | 8 +-- .../application/lib/update-jest-config.ts | 8 +-- .../federate-module/federate-module.spec.ts | 8 +-- .../generators/federate-module/schema.json | 3 +- packages/react/src/generators/host/host.ts | 5 +- .../host/lib/add-module-federation-files.ts | 17 +++---- .../react/src/generators/host/schema.json | 5 +- .../library/lib/add-rollup-build-target.ts | 4 +- .../library/lib/update-app-routes.ts | 2 +- .../react/src/generators/library/library.ts | 13 +++-- ...module-federation.server.config.ts__tmpl__ | 2 +- ...module-federation.server.config.js__tmpl__ | 2 +- .../module-federation.config.ts__tmpl__ | 2 +- .../module-federation.config.js__tmpl__ | 2 +- .../remote/lib/setup-tspath-for-remote.ts | 5 +- .../remote/lib/update-host-with-remote.ts | 8 +-- .../react/src/generators/remote/remote.ts | 35 ++++++++++--- .../react/src/generators/remote/schema.json | 5 +- .../rules/update-module-federation-project.ts | 5 +- .../library/lib => utils}/maybe-js.ts | 4 +- 27 files changed, 145 insertions(+), 84 deletions(-) rename packages/react/src/{generators/library/lib => utils}/maybe-js.ts (54%) diff --git a/docs/generated/packages/react/generators/federate-module.json b/docs/generated/packages/react/generators/federate-module.json index 0f156209fedc0..b490fdb5911bb 100644 --- a/docs/generated/packages/react/generators/federate-module.json +++ b/docs/generated/packages/react/generators/federate-module.json @@ -69,7 +69,8 @@ "e2eTestRunner": { "type": "string", "enum": ["cypress", "playwright", "none"], - "description": "Test runner to use for end to end (e2e) tests.", + "description": "Test runner to use for end to end (E2E) tests.", + "x-prompt": "Which E2E test runner would you like to use?", "default": "cypress" }, "host": { diff --git a/docs/generated/packages/react/generators/host.json b/docs/generated/packages/react/generators/host.json index f6541bee84e48..8d72ba1722b0f 100644 --- a/docs/generated/packages/react/generators/host.json +++ b/docs/generated/packages/react/generators/host.json @@ -94,7 +94,8 @@ "e2eTestRunner": { "type": "string", "enum": ["cypress", "playwright", "none"], - "description": "Test runner to use for end to end (e2e) tests.", + "description": "Test runner to use for end to end (E2E) tests.", + "x-prompt": "Which E2E test runner would you like to use?", "default": "cypress" }, "tags": { @@ -164,7 +165,7 @@ }, "typescriptConfiguration": { "type": "boolean", - "description": "Whether the module federation configuration and webpack configuration files should use TS.", + "description": "Whether the module federation configuration and webpack configuration files should use TS. When --js is used, this flag is ignored.", "default": true } }, diff --git a/docs/generated/packages/react/generators/remote.json b/docs/generated/packages/react/generators/remote.json index f9b6de2b9fc0b..b6d4cded8e663 100644 --- a/docs/generated/packages/react/generators/remote.json +++ b/docs/generated/packages/react/generators/remote.json @@ -100,7 +100,8 @@ "e2eTestRunner": { "type": "string", "enum": ["cypress", "playwright", "none"], - "description": "Test runner to use for end to end (e2e) tests.", + "description": "Test runner to use for end to end (E2E) tests.", + "x-prompt": "Which E2E test runner would you like to use?", "default": "cypress" }, "tags": { @@ -163,7 +164,7 @@ }, "typescriptConfiguration": { "type": "boolean", - "description": "Whether the module federation configuration and webpack configuration files should use TS.", + "description": "Whether the module federation configuration and webpack configuration files should use TS. When --js is used, this flag is ignored.", "default": true } }, diff --git a/e2e/react-module-federation/src/react-module-federation.test.ts b/e2e/react-module-federation/src/react-module-federation.test.ts index 3696c072bb24f..f358430c496d1 100644 --- a/e2e/react-module-federation/src/react-module-federation.test.ts +++ b/e2e/react-module-federation/src/react-module-federation.test.ts @@ -65,9 +65,10 @@ describe('React Module Federation', () => { ), }); - updateFile( - `apps/${shell}/webpack.config.${js ? 'js' : 'ts'}`, - stripIndents` + if (js) { + updateFile( + `apps/${shell}/webpack.config.js`, + stripIndents` const { composePlugins, withNx } = require('@nx/webpack'); const { withReact } = require('@nx/react'); const { withModuleFederation } = require('@nx/react/module-federation'); @@ -86,7 +87,31 @@ describe('React Module Federation', () => { // Nx plugins for webpack to build config object from Nx options and context. module.exports = composePlugins(withNx(), withReact(), withModuleFederation(config)); ` - ); + ); + } else { + updateFile( + `apps/${shell}/webpack.config.ts`, + stripIndents` + 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, + remotes: [ + '${remote1}', + ['${remote2}', 'http://localhost:${defaultRemotePort}/${remote2}/remoteEntry.js'], + ['${remote3}', 'http://localhost:${defaultRemotePort}/${remote3}/remoteEntry.js'], + ], + }; + + // Nx plugins for webpack to build config object from Nx options and context. + export default composePlugins(withNx(), withReact(), withModuleFederation(config)); + ` + ); + } updateFile( `apps/${shell}-e2e/src/integration/app.spec.${js ? 'js' : 'ts'}`, @@ -117,6 +142,23 @@ describe('React Module Federation', () => { ` ); + [shell, remote1, remote2, remote3].forEach((app) => { + ['development', 'production'].forEach(async (configuration) => { + const cliOutput = runCLI(`run ${app}:build:${configuration}`); + expect(cliOutput).toContain('Successfully ran target'); + }); + }); + + const serveResult = await runCommandUntil(`serve ${shell}`, (output) => + output.includes(`http://localhost:${readPort(shell)}`) + ); + + await killProcessAndPorts( + serveResult.pid, + readPort(shell), + defaultRemotePort + ); + if (runE2ETests()) { const e2eResultsSwc = await runCommandUntil( `e2e ${shell}-e2e --no-watch --verbose`, diff --git a/packages/react/src/generators/application/application.ts b/packages/react/src/generators/application/application.ts index 094d0b4a7a5f9..4b8892870ef3e 100644 --- a/packages/react/src/generators/application/application.ts +++ b/packages/react/src/generators/application/application.ts @@ -3,7 +3,7 @@ import { NormalizedSchema, Schema } from './schema'; import { createApplicationFiles } from './lib/create-application-files'; import { updateSpecConfig } from './lib/update-jest-config'; import { normalizeOptions } from './lib/normalize-options'; -import { addProject, maybeJs } from './lib/add-project'; +import { addProject } from './lib/add-project'; import { addJest } from './lib/add-jest'; import { addRouting } from './lib/add-routing'; import { setDefaults } from './lib/set-defaults'; @@ -27,6 +27,7 @@ import { nxRspackVersion, nxVersion, } from '../../utils/versions'; +import { maybeJs } from '../../utils/maybe-js'; import { installCommonDependencies } from './lib/install-common-dependencies'; import { extractTsConfigBase } from '../../utils/create-ts-config'; import { addSwcDependencies } from '@nx/js/src/utils/swc/add-swc-dependencies'; diff --git a/packages/react/src/generators/application/lib/add-project.ts b/packages/react/src/generators/application/lib/add-project.ts index 29920520ea1ed..bda4f2b39da9d 100644 --- a/packages/react/src/generators/application/lib/add-project.ts +++ b/packages/react/src/generators/application/lib/add-project.ts @@ -7,6 +7,7 @@ import { Tree, } from '@nx/devkit'; import { hasWebpackPlugin } from '../../../utils/has-webpack-plugin'; +import { maybeJs } from '../../../utils/maybe-js'; export function addProject(host: Tree, options: NormalizedSchema) { const project: ProjectConfiguration = { @@ -31,12 +32,6 @@ export function addProject(host: Tree, options: NormalizedSchema) { }); } -export function maybeJs(options: NormalizedSchema, path: string): string { - return options.js && (path.endsWith('.ts') || path.endsWith('.tsx')) - ? path.replace(/\.tsx?$/, '.js') - : path; -} - function createBuildTarget(options: NormalizedSchema): TargetConfiguration { return { executor: '@nx/webpack:webpack', diff --git a/packages/react/src/generators/application/lib/add-routing.ts b/packages/react/src/generators/application/lib/add-routing.ts index bbb45ad8dc699..95eaebe96f1a9 100644 --- a/packages/react/src/generators/application/lib/add-routing.ts +++ b/packages/react/src/generators/application/lib/add-routing.ts @@ -1,6 +1,3 @@ -import { addInitialRoutes } from '../../../utils/ast-utils'; -import { NormalizedSchema } from '../schema'; -import { reactRouterDomVersion } from '../../../utils/versions'; import { joinPathFragments, Tree, @@ -8,6 +5,10 @@ import { addDependenciesToPackageJson, } from '@nx/devkit'; import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript'; +import { addInitialRoutes } from '../../../utils/ast-utils'; +import { reactRouterDomVersion } from '../../../utils/versions'; +import { maybeJs } from '../../../utils/maybe-js'; +import { NormalizedSchema } from '../schema'; let tsModule: typeof import('typescript'); @@ -47,9 +48,3 @@ export function addRouting(host: Tree, options: NormalizedSchema) { return () => {}; } - -function maybeJs(options: NormalizedSchema, path: string): string { - return options.js && (path.endsWith('.ts') || path.endsWith('.tsx')) - ? path.replace(/\.tsx?$/, '.js') - : path; -} diff --git a/packages/react/src/generators/application/lib/create-application-files.ts b/packages/react/src/generators/application/lib/create-application-files.ts index 6c1c17c4968f4..91ddf811212f9 100644 --- a/packages/react/src/generators/application/lib/create-application-files.ts +++ b/packages/react/src/generators/application/lib/create-application-files.ts @@ -7,16 +7,16 @@ import { Tree, writeJson, } from '@nx/devkit'; +import { WithNxOptions } from '@nx/webpack'; import { getRelativePathToRootTsConfig } from '@nx/js'; import { join } from 'path'; import { createTsConfig } from '../../../utils/create-ts-config'; import { getInSourceVitestTestsTemplate } from '../../../utils/get-in-source-vitest-tests-template'; -import { NormalizedSchema } from '../schema'; -import { getAppTests } from './get-app-tests'; -import { maybeJs } from './add-project'; +import { maybeJs } from '../../../utils/maybe-js'; import { WithReactOptions } from '../../../../plugins/with-react'; -import { WithNxOptions } from '@nx/webpack'; import { hasWebpackPlugin } from '../../../utils/has-webpack-plugin'; +import { NormalizedSchema } from '../schema'; +import { getAppTests } from './get-app-tests'; export function createApplicationFiles(host: Tree, options: NormalizedSchema) { let styleSolutionSpecificAppFiles: string; diff --git a/packages/react/src/generators/application/lib/update-jest-config.ts b/packages/react/src/generators/application/lib/update-jest-config.ts index 3af0f138a5ff8..62e279dc87a4a 100644 --- a/packages/react/src/generators/application/lib/update-jest-config.ts +++ b/packages/react/src/generators/application/lib/update-jest-config.ts @@ -1,3 +1,4 @@ +import { maybeJs } from '../../../utils/maybe-js'; import { updateJestConfigContent } from '../../../utils/jest-utils'; import { NormalizedSchema } from '../schema'; import { Tree, updateJson } from '@nx/devkit'; @@ -26,9 +27,10 @@ export function updateSpecConfig(host: Tree, options: NormalizedSchema) { return; } - const configPath = `${options.appProjectRoot}/jest.config.${ - options.js ? 'js' : 'ts' - }`; + const configPath = maybeJs( + options, + `${options.appProjectRoot}/jest.config.ts` + ); const originalContent = host.read(configPath, 'utf-8'); const content = updateJestConfigContent(originalContent); host.write(configPath, content); diff --git a/packages/react/src/generators/federate-module/federate-module.spec.ts b/packages/react/src/generators/federate-module/federate-module.spec.ts index 7c61a1729ea2d..878719272998e 100644 --- a/packages/react/src/generators/federate-module/federate-module.spec.ts +++ b/packages/react/src/generators/federate-module/federate-module.spec.ts @@ -44,10 +44,10 @@ describe('federate-module', () => { it('should contain an entry for the new path for module federation', async () => { await federateModuleGenerator(tree, schema); - expect(tree.exists('my-remote/module-federation.config.js')).toBe(true); + expect(tree.exists('my-remote/module-federation.config.ts')).toBe(true); const content = tree.read( - 'my-remote/module-federation.config.js', + 'my-remote/module-federation.config.ts', 'utf-8' ); expect(content).toContain( @@ -89,7 +89,7 @@ describe('federate-module', () => { it('should append the new path to the module federation config', async () => { let content = tree.read( - `${remoteSchema.name}/module-federation.config.js`, + `${remoteSchema.name}/module-federation.config.ts`, 'utf-8' ); @@ -103,7 +103,7 @@ describe('federate-module', () => { }); content = tree.read( - `${remoteSchema.name}/module-federation.config.js`, + `${remoteSchema.name}/module-federation.config.ts`, 'utf-8' ); expect(content).toContain( diff --git a/packages/react/src/generators/federate-module/schema.json b/packages/react/src/generators/federate-module/schema.json index 74025490ee77b..85c4868390c3b 100644 --- a/packages/react/src/generators/federate-module/schema.json +++ b/packages/react/src/generators/federate-module/schema.json @@ -69,7 +69,8 @@ "e2eTestRunner": { "type": "string", "enum": ["cypress", "playwright", "none"], - "description": "Test runner to use for end to end (e2e) tests.", + "description": "Test runner to use for end to end (E2E) tests.", + "x-prompt": "Which E2E test runner would you like to use?", "default": "cypress" }, "host": { diff --git a/packages/react/src/generators/host/host.ts b/packages/react/src/generators/host/host.ts index 7b26f7ac27c84..a21e9c558a24b 100644 --- a/packages/react/src/generators/host/host.ts +++ b/packages/react/src/generators/host/host.ts @@ -39,7 +39,10 @@ export async function hostGeneratorInternal( const tasks: GeneratorCallback[] = []; const options: NormalizedSchema = { ...(await normalizeOptions(host, schema, '@nx/react:host')), - typescriptConfiguration: schema.typescriptConfiguration ?? true, + js: schema.js ?? false, + typescriptConfiguration: schema.js + ? false + : schema.typescriptConfiguration ?? true, dynamic: schema.dynamic ?? false, // TODO(colum): remove when MF works with Crystal addPlugin: false, 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 489de5313e6c5..9aaf8e35e7d4f 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,4 +1,3 @@ -import { NormalizedSchema } from '../schema'; import { Tree, generateFiles, @@ -6,6 +5,8 @@ import { names, readProjectConfiguration, } from '@nx/devkit'; +import { maybeJs } from '../../../utils/maybe-js'; +import { NormalizedSchema } from '../schema'; export function addModuleFederationFiles( host: Tree, @@ -35,13 +36,10 @@ export function addModuleFederationFiles( // Renaming original entry file so we can use `import(./bootstrap)` in // new entry file. host.rename( + joinPathFragments(options.appProjectRoot, maybeJs(options, 'src/main.tsx')), joinPathFragments( options.appProjectRoot, - `src/main.${options.js ? 'js' : 'tsx'}` - ), - joinPathFragments( - options.appProjectRoot, - `src/bootstrap.${options.js ? 'js' : 'tsx'}` + maybeJs(options, 'src/bootstrap.tsx') ) ); @@ -55,10 +53,9 @@ export function addModuleFederationFiles( templateVariables ); - const pathToModuleFederationFiles = - options.typescriptConfiguration && !options.js - ? 'module-federation-ts' - : 'module-federation'; + const pathToModuleFederationFiles = options.typescriptConfiguration + ? 'module-federation-ts' + : 'module-federation'; // New entry file is created here. generateFiles( host, diff --git a/packages/react/src/generators/host/schema.json b/packages/react/src/generators/host/schema.json index 7fa5d6d169378..4f5e84a1db9d8 100644 --- a/packages/react/src/generators/host/schema.json +++ b/packages/react/src/generators/host/schema.json @@ -100,7 +100,8 @@ "e2eTestRunner": { "type": "string", "enum": ["cypress", "playwright", "none"], - "description": "Test runner to use for end to end (e2e) tests.", + "description": "Test runner to use for end to end (E2E) tests.", + "x-prompt": "Which E2E test runner would you like to use?", "default": "cypress" }, "tags": { @@ -170,7 +171,7 @@ }, "typescriptConfiguration": { "type": "boolean", - "description": "Whether the module federation configuration and webpack configuration files should use TS.", + "description": "Whether the module federation configuration and webpack configuration files should use TS. When --js is used, this flag is ignored.", "default": true } }, diff --git a/packages/react/src/generators/library/lib/add-rollup-build-target.ts b/packages/react/src/generators/library/lib/add-rollup-build-target.ts index 0bd00ed358cfd..dcb1d7a5ce471 100644 --- a/packages/react/src/generators/library/lib/add-rollup-build-target.ts +++ b/packages/react/src/generators/library/lib/add-rollup-build-target.ts @@ -9,13 +9,13 @@ import { updateProjectConfiguration, } from '@nx/devkit'; -import { maybeJs } from './maybe-js'; -import { NormalizedSchema } from '../schema'; +import { maybeJs } from '../../../utils/maybe-js'; import { nxVersion, rollupPluginUrlVersion, svgrRollupVersion, } from '../../../utils/versions'; +import { NormalizedSchema } from '../schema'; export async function addRollupBuildTarget( host: Tree, diff --git a/packages/react/src/generators/library/lib/update-app-routes.ts b/packages/react/src/generators/library/lib/update-app-routes.ts index de334545a570a..7ed70e127eb7c 100644 --- a/packages/react/src/generators/library/lib/update-app-routes.ts +++ b/packages/react/src/generators/library/lib/update-app-routes.ts @@ -14,7 +14,7 @@ import { findComponentImportPath, } from '../../../utils/ast-utils'; import { addInitialRoutes } from '../../../utils/ast-utils'; -import { maybeJs } from './maybe-js'; +import { maybeJs } from '../../../utils/maybe-js'; import { reactRouterDomVersion } from '../../../utils/versions'; import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript'; diff --git a/packages/react/src/generators/library/library.ts b/packages/react/src/generators/library/library.ts index 45d02de5d8bd5..8b50dc1f8b824 100644 --- a/packages/react/src/generators/library/library.ts +++ b/packages/react/src/generators/library/library.ts @@ -1,3 +1,4 @@ +import { relative } from 'path'; import { addProjectConfiguration, ensurePackage, @@ -9,10 +10,11 @@ import { updateJson, } from '@nx/devkit'; import { getRelativeCwd } from '@nx/devkit/src/generators/artifact-name-and-directory-utils'; - +import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; import { addTsConfigPath, initGenerator as jsInitGenerator } from '@nx/js'; import { nxVersion } from '../../utils/versions'; +import { maybeJs } from '../../utils/maybe-js'; import componentGenerator from '../component/component'; import initGenerator from '../init/init'; import { Schema } from './schema'; @@ -25,8 +27,6 @@ import { createFiles } from './lib/create-files'; import { extractTsConfigBase } from '../../utils/create-ts-config'; import { installCommonDependencies } from './lib/install-common-dependencies'; import { setDefaults } from './lib/set-defaults'; -import { relative } from 'path'; -import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; export async function libraryGenerator(host: Tree, schema: Schema) { return await libraryGeneratorInternal(host, { @@ -227,10 +227,9 @@ export async function libraryGeneratorInternal(host: Tree, schema: Schema) { if (!options.skipTsConfig) { addTsConfigPath(host, options.importPath, [ - joinPathFragments( - options.projectRoot, - './src', - 'index.' + (options.js ? 'js' : 'ts') + maybeJs( + options, + joinPathFragments(options.projectRoot, './src/index.ts') ), ]); } 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__ index 4481a845c9f80..c2359ff000924 100644 --- 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__ @@ -3,7 +3,7 @@ import {ModuleFederationConfig} from '@nx/webpack'; const config: ModuleFederationConfig = { name: '<%= projectName %>', exposes: { - './Module': '<%= appProjectRoot %>/src/remote-entry.ts', + './Module': '<%= appProjectRoot %>/src/remote-entry.<%= js ? 'js' : 'ts' %>', }, }; diff --git a/packages/react/src/generators/remote/files/module-federation-ssr/module-federation.server.config.js__tmpl__ b/packages/react/src/generators/remote/files/module-federation-ssr/module-federation.server.config.js__tmpl__ index 07de46836b47d..2859c54edcc18 100644 --- a/packages/react/src/generators/remote/files/module-federation-ssr/module-federation.server.config.js__tmpl__ +++ b/packages/react/src/generators/remote/files/module-federation-ssr/module-federation.server.config.js__tmpl__ @@ -1,6 +1,6 @@ module.exports = { name: '<%= projectName %>', exposes: { - './Module': '<%= appProjectRoot %>/src/remote-entry.ts', + './Module': '<%= appProjectRoot %>/src/remote-entry.<%= js ? 'js' : 'ts' %>', }, }; 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__ index d3f4d6bea1c21..01c1e928b5167 100644 --- 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__ @@ -6,7 +6,7 @@ const config: ModuleFederationConfig = { library: { type: 'var', name: '<%= projectName %>'}, <% } %> exposes: { - './Module': './src/remote-entry.ts', + './Module': './src/remote-entry.<%= js ? 'js' : 'ts' %>', }, }; diff --git a/packages/react/src/generators/remote/files/module-federation/module-federation.config.js__tmpl__ b/packages/react/src/generators/remote/files/module-federation/module-federation.config.js__tmpl__ index 042baaf403ada..da1a17ef1c16d 100644 --- a/packages/react/src/generators/remote/files/module-federation/module-federation.config.js__tmpl__ +++ b/packages/react/src/generators/remote/files/module-federation/module-federation.config.js__tmpl__ @@ -4,6 +4,6 @@ module.exports = { library: { type: 'var', name: '<%= projectName %>'}, <% } %> exposes: { - './Module': './src/remote-entry.<%= locals.js ? 'js' : 'ts' %>', + './Module': './src/remote-entry.<%= js ? 'js' : 'ts' %>', }, }; diff --git a/packages/react/src/generators/remote/lib/setup-tspath-for-remote.ts b/packages/react/src/generators/remote/lib/setup-tspath-for-remote.ts index fee2c812032f9..8c9ef51dbb50c 100644 --- a/packages/react/src/generators/remote/lib/setup-tspath-for-remote.ts +++ b/packages/react/src/generators/remote/lib/setup-tspath-for-remote.ts @@ -1,12 +1,13 @@ import type { Tree } from '@nx/devkit'; -import type { Schema } from '../schema'; import { joinPathFragments, readProjectConfiguration } from '@nx/devkit'; import { addTsConfigPath } from '@nx/js'; +import { maybeJs } from '../../../utils/maybe-js'; +import type { Schema } from '../schema'; export function setupTspathForRemote(tree: Tree, options: Schema) { const project = readProjectConfiguration(tree, options.name); - const exportPath = `./src/remote-entry.${options.js ? 'js' : 'ts'}`; + const exportPath = maybeJs(options, './src/remote-entry.ts'); const exportName = 'Module'; 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 2bfeb2be28009..c526b42119e48 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 @@ -107,14 +107,14 @@ export function updateHostWithRemote( function findAppComponentPath(host: Tree, sourceRoot: string) { const locations = [ - 'app/app.js', - 'app/App.js', - 'app.js', - 'App.js', 'app/app.tsx', 'app/App.tsx', + 'app/app.js', + 'app/App.js', 'app.tsx', 'App.tsx', + 'app.js', + 'App.js', ]; for (const loc of locations) { if (host.exists(joinPathFragments(sourceRoot, loc))) { diff --git a/packages/react/src/generators/remote/remote.ts b/packages/react/src/generators/remote/remote.ts index c650101653633..5ab224398ba2b 100644 --- a/packages/react/src/generators/remote/remote.ts +++ b/packages/react/src/generators/remote/remote.ts @@ -22,6 +22,7 @@ import { setupSsrForRemote } from './lib/setup-ssr-for-remote'; import { setupTspathForRemote } from './lib/setup-tspath-for-remote'; import { addRemoteToDynamicHost } from './lib/add-remote-to-dynamic-host'; import { addMfEnvToTargetDefaultInputs } from '../../utils/add-mf-env-to-inputs'; +import { maybeJs } from '../../utils/maybe-js'; export function addModuleFederationFiles( host: Tree, @@ -40,10 +41,9 @@ export function addModuleFederationFiles( templateVariables ); - const pathToModuleFederationFiles = - options.typescriptConfiguration && !options.js - ? 'module-federation-ts' - : 'module-federation'; + const pathToModuleFederationFiles = options.typescriptConfiguration + ? 'module-federation-ts' + : 'module-federation'; generateFiles( host, @@ -51,6 +51,23 @@ export function addModuleFederationFiles( 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) { @@ -64,7 +81,11 @@ export async function remoteGeneratorInternal(host: Tree, schema: Schema) { const tasks: GeneratorCallback[] = []; const options: NormalizedSchema = { ...(await normalizeOptions(host, schema, '@nx/react:remote')), - typescriptConfiguration: schema.typescriptConfiguration ?? false, + // when js is set to true, we want to use the js configuration + js: schema.js ?? false, + typescriptConfiguration: schema.js + ? false + : schema.typescriptConfiguration ?? true, dynamic: schema.dynamic ?? false, // TODO(colum): remove when MF works with Crystal addPlugin: false, @@ -85,8 +106,8 @@ export async function remoteGeneratorInternal(host: Tree, schema: Schema) { // Renaming original entry file so we can use `import(./bootstrap)` in // new entry file. host.rename( - join(options.appProjectRoot, `src/main.${options.js ? 'js' : 'tsx'}`), - join(options.appProjectRoot, `src/bootstrap.${options.js ? 'js' : 'tsx'}`) + join(options.appProjectRoot, maybeJs(options, 'src/main.tsx')), + join(options.appProjectRoot, maybeJs(options, 'src/bootstrap.tsx')) ); addModuleFederationFiles(host, options); diff --git a/packages/react/src/generators/remote/schema.json b/packages/react/src/generators/remote/schema.json index 41f1498e2b465..5e56b6bb2feb4 100644 --- a/packages/react/src/generators/remote/schema.json +++ b/packages/react/src/generators/remote/schema.json @@ -106,7 +106,8 @@ "e2eTestRunner": { "type": "string", "enum": ["cypress", "playwright", "none"], - "description": "Test runner to use for end to end (e2e) tests.", + "description": "Test runner to use for end to end (E2E) tests.", + "x-prompt": "Which E2E test runner would you like to use?", "default": "cypress" }, "tags": { @@ -169,7 +170,7 @@ }, "typescriptConfiguration": { "type": "boolean", - "description": "Whether the module federation configuration and webpack configuration files should use TS.", + "description": "Whether the module federation configuration and webpack configuration files should use TS. When --js is used, this flag is ignored.", "default": true } }, diff --git a/packages/react/src/rules/update-module-federation-project.ts b/packages/react/src/rules/update-module-federation-project.ts index 7257dd0801c5b..a77662e324b2a 100644 --- a/packages/react/src/rules/update-module-federation-project.ts +++ b/packages/react/src/rules/update-module-federation-project.ts @@ -7,23 +7,24 @@ import { updateProjectConfiguration, } from '@nx/devkit'; import { nxVersion } from '../utils/versions'; +import { maybeJs } from '../utils/maybe-js'; export function updateModuleFederationProject( host: Tree, options: { + js?: boolean; projectName: string; appProjectRoot: string; devServerPort?: number; typescriptConfiguration?: boolean; dynamic?: boolean; - js?: boolean; } ): GeneratorCallback { const projectConfig = readProjectConfiguration(host, options.projectName); projectConfig.targets.build.options = { ...projectConfig.targets.build.options, - main: `${options.appProjectRoot}/src/main.${options.js ? 'js' : 'ts'}`, + main: maybeJs(options, `${options.appProjectRoot}/src/main.ts`), webpackConfig: `${options.appProjectRoot}/webpack.config.${ options.typescriptConfiguration && !options.js ? 'ts' : 'js' }`, diff --git a/packages/react/src/generators/library/lib/maybe-js.ts b/packages/react/src/utils/maybe-js.ts similarity index 54% rename from packages/react/src/generators/library/lib/maybe-js.ts rename to packages/react/src/utils/maybe-js.ts index 274417777b012..17daba9548c89 100644 --- a/packages/react/src/generators/library/lib/maybe-js.ts +++ b/packages/react/src/utils/maybe-js.ts @@ -1,6 +1,4 @@ -import { NormalizedSchema } from '../schema'; - -export function maybeJs(options: NormalizedSchema, path: string): string { +export function maybeJs(options: { js?: boolean }, path: string): string { return options.js && (path.endsWith('.ts') || path.endsWith('.tsx')) ? path.replace(/\.tsx?$/, '.js') : path;