feat(react): add crystal mf support to host and remote (#30424)
## Current Behavior The `@nx/react` `host` and `remote` generators currently use executors to support Module Federation ## Expected Behavior When `bundler=rspack` use Crystal Module Federation with no executors for Module Federation ## Related Issues #30391
This commit is contained in:
parent
176e8f985a
commit
9669dfdb62
@ -15,11 +15,11 @@ import { readPort, runCLI } from './utils';
|
||||
|
||||
describe('React Rspack Module Federation', () => {
|
||||
describe('Default Configuration', () => {
|
||||
beforeAll(() => {
|
||||
beforeEach(() => {
|
||||
newProject({ packages: ['@nx/react'] });
|
||||
});
|
||||
|
||||
afterAll(() => cleanupProject());
|
||||
afterEach(() => cleanupProject());
|
||||
|
||||
it.each`
|
||||
js
|
||||
@ -100,21 +100,11 @@ describe('React Rspack Module Federation', () => {
|
||||
|
||||
if (runE2ETests()) {
|
||||
const e2eResultsSwc = await runCommandUntil(
|
||||
`e2e ${shell}-e2e --no-watch --verbose`,
|
||||
`e2e ${shell}-e2e --verbose`,
|
||||
(output) => output.includes('All specs passed!')
|
||||
);
|
||||
|
||||
await killProcessAndPorts(e2eResultsSwc.pid, readPort(shell));
|
||||
|
||||
const e2eResultsTsNode = await runCommandUntil(
|
||||
`e2e ${shell}-e2e --no-watch --verbose`,
|
||||
(output) =>
|
||||
output.includes('Successfully ran target e2e for project'),
|
||||
{
|
||||
env: { NX_PREFER_TS_NODE: 'true' },
|
||||
}
|
||||
);
|
||||
await killProcessAndPorts(e2eResultsTsNode.pid, readPort(shell));
|
||||
}
|
||||
},
|
||||
500_000
|
||||
@ -173,291 +163,9 @@ describe('React Rspack Module Federation', () => {
|
||||
);
|
||||
|
||||
await killProcessAndPorts(e2eResultsSwc.pid, readPort(shell));
|
||||
|
||||
const e2eResultsTsNode = await runCommandUntil(
|
||||
`e2e ${shell}-e2e`,
|
||||
(output) =>
|
||||
output.includes('Successfully ran target e2e for project'),
|
||||
{
|
||||
env: { NX_PREFER_TS_NODE: 'true' },
|
||||
}
|
||||
);
|
||||
await killProcessAndPorts(e2eResultsTsNode.pid, readPort(shell));
|
||||
}
|
||||
}, 500_000);
|
||||
|
||||
it('should generate host and remote apps in webpack, convert to rspack and use playwright for e2es', async () => {
|
||||
const shell = uniq('shell');
|
||||
const remote1 = uniq('remote1');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/react:host ${shell} --remotes=${remote1} --bundler=webpack --e2eTestRunner=playwright --style=css --no-interactive --skipFormat`
|
||||
);
|
||||
|
||||
runCLI(
|
||||
`generate @nx/rspack:convert-webpack ${shell} --skipFormat --no-interactive`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/rspack:convert-webpack ${remote1} --skipFormat --no-interactive`
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`apps/${shell}-e2e/src/example.spec.ts`,
|
||||
stripIndents`
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('should display welcome message', async ({page}) => {
|
||||
await page.goto("/");
|
||||
expect(await page.locator('h1').innerText()).toContain('Welcome');
|
||||
});
|
||||
|
||||
test('should load remote 1', async ({page}) => {
|
||||
await page.goto("/${remote1}");
|
||||
expect(await page.locator('h1').innerText()).toContain('${remote1}');
|
||||
});
|
||||
`
|
||||
);
|
||||
|
||||
if (runE2ETests()) {
|
||||
const e2eResultsSwc = await runCommandUntil(
|
||||
`e2e ${shell}-e2e`,
|
||||
(output) => output.includes('Successfully ran target e2e for project')
|
||||
);
|
||||
|
||||
await killProcessAndPorts(e2eResultsSwc.pid, readPort(shell));
|
||||
|
||||
const e2eResultsTsNode = await runCommandUntil(
|
||||
`e2e ${shell}-e2e`,
|
||||
(output) =>
|
||||
output.includes('Successfully ran target e2e for project'),
|
||||
{
|
||||
env: { NX_PREFER_TS_NODE: 'true' },
|
||||
}
|
||||
);
|
||||
await killProcessAndPorts(e2eResultsTsNode.pid, readPort(shell));
|
||||
}
|
||||
}, 500_000);
|
||||
|
||||
it('should have interop between webpack host and rspack remote', async () => {
|
||||
const shell = uniq('shell');
|
||||
const remote1 = uniq('remote1');
|
||||
const remote2 = uniq('remote2');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/react:host apps/${shell} --name=${shell} --remotes=${remote1} --bundler=webpack --e2eTestRunner=cypress --style=css --no-interactive --skipFormat`
|
||||
);
|
||||
|
||||
runCLI(
|
||||
`generate @nx/react:remote apps/${remote2} --name=${remote2} --host=${shell} --bundler=rspack --style=css --no-interactive --skipFormat`
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`apps/${shell}-e2e/src/integration/app.spec.ts`,
|
||||
stripIndents`
|
||||
import { getGreeting } from '../support/app.po';
|
||||
|
||||
describe('shell app', () => {
|
||||
it('should display welcome message', () => {
|
||||
cy.visit('/')
|
||||
getGreeting().contains('Welcome ${shell}');
|
||||
});
|
||||
|
||||
it('should load remote 1', () => {
|
||||
cy.visit('/${remote1}')
|
||||
getGreeting().contains('Welcome ${remote1}');
|
||||
});
|
||||
|
||||
it('should load remote 2', () => {
|
||||
cy.visit('/${remote2}')
|
||||
getGreeting().contains('Welcome ${remote2}');
|
||||
});
|
||||
});
|
||||
`
|
||||
);
|
||||
|
||||
[shell, remote1, remote2].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));
|
||||
|
||||
if (runE2ETests()) {
|
||||
const e2eResultsSwc = await runCommandUntil(
|
||||
`e2e ${shell}-e2e --no-watch --verbose`,
|
||||
(output) => output.includes('All specs passed!')
|
||||
);
|
||||
|
||||
await killProcessAndPorts(e2eResultsSwc.pid, readPort(shell));
|
||||
|
||||
const e2eResultsTsNode = await runCommandUntil(
|
||||
`e2e ${shell}-e2e --no-watch --verbose`,
|
||||
(output) =>
|
||||
output.includes('Successfully ran target e2e for project'),
|
||||
{
|
||||
env: { NX_PREFER_TS_NODE: 'true' },
|
||||
}
|
||||
);
|
||||
await killProcessAndPorts(e2eResultsTsNode.pid, readPort(shell));
|
||||
}
|
||||
}, 500_000);
|
||||
|
||||
it('should have interop between rspack host and webpack remote', async () => {
|
||||
const shell = uniq('shell');
|
||||
const remote1 = uniq('remote1');
|
||||
const remote2 = uniq('remote2');
|
||||
runCLI(
|
||||
`generate @nx/react:host apps/${shell} --name=${shell} --remotes=${remote1} --bundler=rspack --e2eTestRunner=cypress --style=css --no-interactive --skipFormat`
|
||||
);
|
||||
|
||||
runCLI(
|
||||
`generate @nx/react:remote apps/${remote2} --name=${remote2} --host=${shell} --bundler=webpack --style=css --no-interactive --skipFormat`
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`apps/${shell}-e2e/src/integration/app.spec.ts`,
|
||||
stripIndents`
|
||||
import { getGreeting } from '../support/app.po';
|
||||
|
||||
describe('shell app', () => {
|
||||
it('should display welcome message', () => {
|
||||
cy.visit('/')
|
||||
getGreeting().contains('Welcome ${shell}');
|
||||
});
|
||||
|
||||
it('should load remote 1', () => {
|
||||
cy.visit('/${remote1}')
|
||||
getGreeting().contains('Welcome ${remote1}');
|
||||
});
|
||||
|
||||
it('should load remote 2', () => {
|
||||
cy.visit('/${remote2}')
|
||||
getGreeting().contains('Welcome ${remote2}');
|
||||
});
|
||||
|
||||
});
|
||||
`
|
||||
);
|
||||
|
||||
if (runE2ETests()) {
|
||||
const e2eResultsSwc = await runCommandUntil(
|
||||
`e2e ${shell}-e2e --no-watch --verbose`,
|
||||
(output) => output.includes('All specs passed!')
|
||||
);
|
||||
|
||||
await killProcessAndPorts(e2eResultsSwc.pid, readPort(shell));
|
||||
|
||||
const e2eResultsTsNode = await runCommandUntil(
|
||||
`e2e ${shell}-e2e --no-watch --verbose`,
|
||||
(output) =>
|
||||
output.includes('Successfully ran target e2e for project'),
|
||||
{
|
||||
env: { NX_PREFER_TS_NODE: 'true' },
|
||||
}
|
||||
);
|
||||
await killProcessAndPorts(e2eResultsTsNode.pid, readPort(shell));
|
||||
}
|
||||
}, 500_000);
|
||||
|
||||
describe('ssr', () => {
|
||||
it('should generate host and remote apps with ssr', async () => {
|
||||
const shell = uniq('shell');
|
||||
const remote1 = uniq('remote1');
|
||||
const remote2 = uniq('remote2');
|
||||
const remote3 = uniq('remote3');
|
||||
|
||||
await runCLIAsync(
|
||||
`generate @nx/react:host apps/${shell} --ssr --name=${shell} --remotes=${remote1},${remote2},${remote3} --bundler=rspack --style=css --no-interactive --skipFormat`
|
||||
);
|
||||
|
||||
expect(readPort(shell)).toEqual(4200);
|
||||
expect(readPort(remote1)).toEqual(4201);
|
||||
expect(readPort(remote2)).toEqual(4202);
|
||||
expect(readPort(remote3)).toEqual(4203);
|
||||
|
||||
[shell, remote1, remote2, remote3].forEach((app) => {
|
||||
checkFilesExist(
|
||||
`apps/${app}/module-federation.config.ts`,
|
||||
`apps/${app}/module-federation.server.config.ts`
|
||||
);
|
||||
['build', 'server'].forEach((target) => {
|
||||
['development', 'production'].forEach(async (configuration) => {
|
||||
const cliOutput = runCLI(`run ${app}:${target}:${configuration}`);
|
||||
expect(cliOutput).toContain('Successfully ran target');
|
||||
|
||||
await killPorts(readPort(app));
|
||||
});
|
||||
});
|
||||
});
|
||||
}, 500_000);
|
||||
|
||||
it('should serve remotes as static when running the host by default', async () => {
|
||||
const shell = uniq('shell');
|
||||
const remote1 = uniq('remote1');
|
||||
const remote2 = uniq('remote2');
|
||||
const remote3 = uniq('remote3');
|
||||
|
||||
await runCLIAsync(
|
||||
`generate @nx/react:host apps/${shell} --ssr --name=${shell} --remotes=${remote1},${remote2},${remote3} --bundler=rspack --style=css --e2eTestRunner=cypress --no-interactive --skipFormat`
|
||||
);
|
||||
|
||||
const serveResult = await runCommandUntil(`serve ${shell}`, (output) =>
|
||||
output.includes(`Nx SSR Static remotes proxies started successfully`)
|
||||
);
|
||||
|
||||
await killProcessAndPorts(serveResult.pid);
|
||||
}, 500_000);
|
||||
|
||||
it('should serve remotes as static and they should be able to be accessed from the host', async () => {
|
||||
const shell = uniq('shell');
|
||||
const remote1 = uniq('remote1');
|
||||
const remote2 = uniq('remote2');
|
||||
const remote3 = uniq('remote3');
|
||||
|
||||
await runCLIAsync(
|
||||
`generate @nx/react:host apps/${shell} --ssr --name=${shell} --remotes=${remote1},${remote2},${remote3} --bundler=rspack --style=css --e2eTestRunner=cypress --no-interactive --skipFormat`
|
||||
);
|
||||
|
||||
const capitalize = (s: string) =>
|
||||
s.charAt(0).toUpperCase() + s.slice(1);
|
||||
|
||||
updateFile(`apps/${shell}-e2e/src/e2e/app.cy.ts`, (content) => {
|
||||
return `
|
||||
describe('${shell}-e2e', () => {
|
||||
beforeEach(() => cy.visit('/'));
|
||||
|
||||
it('should display welcome message', () => {
|
||||
expect(cy.get('ul li').should('have.length', 4));
|
||||
expect(cy.get('ul li').eq(0).should('have.text', 'Home'));
|
||||
expect(cy.get('ul li').eq(1).should('have.text', '${capitalize(
|
||||
remote1
|
||||
)}'));
|
||||
expect(cy.get('ul li').eq(2).should('have.text', '${capitalize(
|
||||
remote2
|
||||
)}'));
|
||||
expect(cy.get('ul li').eq(3).should('have.text', '${capitalize(
|
||||
remote3
|
||||
)}'));
|
||||
});
|
||||
});
|
||||
`;
|
||||
});
|
||||
|
||||
if (runE2ETests()) {
|
||||
const hostE2eResults = await runCommandUntil(
|
||||
`e2e ${shell}-e2e --no-watch --verbose`,
|
||||
(output) => output.includes('All specs passed!')
|
||||
);
|
||||
await killProcessAndPorts(hostE2eResults.pid);
|
||||
}
|
||||
}, 600_000);
|
||||
});
|
||||
|
||||
// TODO(Coly010): investigate this failure
|
||||
xit('should support generating host and remote apps with the new name and root format', async () => {
|
||||
const shell = uniq('shell');
|
||||
|
||||
@ -94,13 +94,13 @@ describe('Dynamic Module Federation', () => {
|
||||
if (runE2ETests()) {
|
||||
// Serve Remote since it is dynamic and won't be started with the host
|
||||
const remoteProcess = await runCommandUntil(
|
||||
`serve-static ${remote} --no-watch --verbose`,
|
||||
`serve ${remote} --verbose`,
|
||||
() => {
|
||||
return true;
|
||||
}
|
||||
);
|
||||
const hostE2eResultsSwc = await runCommandUntil(
|
||||
`e2e ${shell}-e2e --no-watch --verbose`,
|
||||
`e2e ${shell}-e2e --verbose`,
|
||||
(output) => output.includes('All specs passed!')
|
||||
);
|
||||
|
||||
|
||||
@ -103,7 +103,7 @@ describe('Federate Module', () => {
|
||||
|
||||
if (runE2ETests()) {
|
||||
const hostE2eResults = await runCommandUntil(
|
||||
`e2e ${host}-e2e --no-watch --verbose`,
|
||||
`e2e ${host}-e2e --verbose`,
|
||||
(output) => output.includes('All specs passed!')
|
||||
);
|
||||
await killProcessAndPorts(
|
||||
@ -195,7 +195,7 @@ describe('Federate Module', () => {
|
||||
|
||||
if (runE2ETests()) {
|
||||
const hostE2eResults = await runCommandUntil(
|
||||
`e2e ${host}-e2e --no-watch --verbose`,
|
||||
`e2e ${host}-e2e --verbose`,
|
||||
(output) => output.includes('All specs passed!')
|
||||
);
|
||||
await killProcessAndPorts(
|
||||
|
||||
@ -15,13 +15,11 @@ describe('Independent Deployability', () => {
|
||||
let proj: string;
|
||||
|
||||
beforeAll(() => {
|
||||
process.env.NX_ADD_PLUGINS = 'false';
|
||||
proj = newProject();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
cleanupProject();
|
||||
delete process.env.NX_ADD_PLUGINS;
|
||||
});
|
||||
|
||||
it('should support promised based remotes', async () => {
|
||||
@ -47,8 +45,8 @@ describe('Independent Deployability', () => {
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`${remote}/webpack.config.prod.js`,
|
||||
`module.exports = require('./webpack.config');`
|
||||
`${remote}/rspack.config.prod.js`,
|
||||
`module.exports = require('./rspack.config');`
|
||||
);
|
||||
|
||||
// Update host to use promise based remote
|
||||
@ -86,27 +84,10 @@ describe('Independent Deployability', () => {
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`${host}/webpack.config.prod.js`,
|
||||
`module.exports = require('./webpack.config');`
|
||||
`${host}/rspack.config.prod.js`,
|
||||
`module.exports = require('./rspack.config');`
|
||||
);
|
||||
|
||||
// Update e2e project.json
|
||||
updateJson(`${host}-e2e/project.json`, (json) => {
|
||||
return {
|
||||
...json,
|
||||
targets: {
|
||||
...json.targets,
|
||||
e2e: {
|
||||
...json.targets.e2e,
|
||||
options: {
|
||||
...json.targets.e2e.options,
|
||||
devServerTarget: `${host}:serve-static:production`,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// update e2e
|
||||
updateFile(
|
||||
`${host}-e2e/src/e2e/app.cy.ts`,
|
||||
@ -142,18 +123,16 @@ describe('Independent Deployability', () => {
|
||||
expect(remoteOutput).toContain('Successfully ran target build');
|
||||
|
||||
if (runE2ETests()) {
|
||||
const remoteProcess = await runCommandUntil(
|
||||
`serve-static ${remote} --no-watch --verbose`,
|
||||
() => {
|
||||
return true;
|
||||
}
|
||||
);
|
||||
const hostE2eResults = await runCommandUntil(
|
||||
`e2e ${host}-e2e --no-watch --verbose`,
|
||||
`e2e ${host}-e2e --verbose`,
|
||||
(output) => output.includes('All specs passed!')
|
||||
);
|
||||
await killProcessAndPorts(hostE2eResults.pid, hostPort, hostPort + 1);
|
||||
await killProcessAndPorts(remoteProcess.pid, remotePort);
|
||||
await killProcessAndPorts(
|
||||
hostE2eResults.pid,
|
||||
hostPort,
|
||||
hostPort + 1,
|
||||
remotePort
|
||||
);
|
||||
}
|
||||
}, 500_000);
|
||||
|
||||
@ -279,7 +258,7 @@ describe('Independent Deployability', () => {
|
||||
if (runE2ETests()) {
|
||||
// test remote e2e
|
||||
const remoteE2eResults = await runCommandUntil(
|
||||
`e2e ${remote}-e2e --no-watch --verbose`,
|
||||
`e2e ${remote}-e2e --verbose`,
|
||||
(output) => output.includes('All specs passed!')
|
||||
);
|
||||
await killProcessAndPorts(remoteE2eResults.pid, remotePort);
|
||||
@ -294,7 +273,7 @@ describe('Independent Deployability', () => {
|
||||
);
|
||||
await killProcessAndPorts(remoteProcess.pid, remotePort);
|
||||
const shellE2eResults = await runCommandUntil(
|
||||
`e2e ${shell}-e2e --no-watch --verbose`,
|
||||
`e2e ${shell}-e2e --verbose`,
|
||||
(output) => output.includes('All specs passed!')
|
||||
);
|
||||
await killProcessAndPorts(
|
||||
@ -321,7 +300,7 @@ describe('Independent Deployability', () => {
|
||||
updateFile(
|
||||
`${shell}/module-federation.config.ts`,
|
||||
stripIndents`
|
||||
import { ModuleFederationConfig } from '@nx/webpack';
|
||||
import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: '${shell}',
|
||||
@ -334,14 +313,14 @@ describe('Independent Deployability', () => {
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`${shell}/webpack.config.prod.ts`,
|
||||
`export { default } from './webpack.config';`
|
||||
`${shell}/rspack.config.prod.ts`,
|
||||
`export { default } from './rspack.config';`
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`${remote}/module-federation.config.ts`,
|
||||
stripIndents`
|
||||
import { ModuleFederationConfig } from '@nx/webpack';
|
||||
import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: '${remote}',
|
||||
@ -356,8 +335,8 @@ describe('Independent Deployability', () => {
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`${remote}/webpack.config.prod.ts`,
|
||||
`export { default } from './webpack.config';`
|
||||
`${remote}/rspack.config.prod.ts`,
|
||||
`export { default } from './rspack.config';`
|
||||
);
|
||||
|
||||
// Update host e2e test to check that the remote works with library type var via navigation
|
||||
@ -394,8 +373,9 @@ describe('Independent Deployability', () => {
|
||||
|
||||
if (runE2ETests()) {
|
||||
const hostE2eResultsSwc = await runCommandUntil(
|
||||
`e2e ${shell}-e2e --no-watch --verbose`,
|
||||
(output) => output.includes('All specs passed!')
|
||||
`e2e ${shell}-e2e --verbose`,
|
||||
(output) =>
|
||||
output.includes('NX Successfully ran target e2e for project')
|
||||
);
|
||||
await killProcessAndPorts(
|
||||
hostE2eResultsSwc.pid,
|
||||
@ -405,32 +385,12 @@ describe('Independent Deployability', () => {
|
||||
);
|
||||
|
||||
const remoteE2eResultsSwc = await runCommandUntil(
|
||||
`e2e ${remote}-e2e --no-watch --verbose`,
|
||||
(output) => output.includes('All specs passed!')
|
||||
`e2e ${remote}-e2e --verbose`,
|
||||
(output) =>
|
||||
output.includes('NX Successfully ran target e2e for project')
|
||||
);
|
||||
|
||||
await killProcessAndPorts(remoteE2eResultsSwc.pid, remotePort);
|
||||
|
||||
const hostE2eResultsTsNode = await runCommandUntil(
|
||||
`e2e ${shell}-e2e --no-watch --verbose`,
|
||||
(output) => output.includes('All specs passed!'),
|
||||
{ env: { NX_PREFER_TS_NODE: 'true' } }
|
||||
);
|
||||
|
||||
await killProcessAndPorts(
|
||||
hostE2eResultsTsNode.pid,
|
||||
shellPort,
|
||||
shellPort + 1,
|
||||
remotePort
|
||||
);
|
||||
|
||||
const remoteE2eResultsTsNode = await runCommandUntil(
|
||||
`e2e ${remote}-e2e --no-watch --verbose`,
|
||||
(output) => output.includes('All specs passed!'),
|
||||
{ env: { NX_PREFER_TS_NODE: 'true' } }
|
||||
);
|
||||
|
||||
await killProcessAndPorts(remoteE2eResultsTsNode.pid, remotePort);
|
||||
}
|
||||
}, 500_000);
|
||||
});
|
||||
|
||||
183
e2e/react/src/module-federation/misc.rspack.test.ts
Normal file
183
e2e/react/src/module-federation/misc.rspack.test.ts
Normal file
@ -0,0 +1,183 @@
|
||||
import {
|
||||
cleanupProject,
|
||||
killProcessAndPorts,
|
||||
newProject,
|
||||
runCommandUntil,
|
||||
runE2ETests,
|
||||
uniq,
|
||||
updateFile,
|
||||
} from '@nx/e2e/utils';
|
||||
import { readPort, runCLI } from './utils';
|
||||
import { stripIndents } from 'nx/src/utils/strip-indents';
|
||||
|
||||
describe('React Rspack Module Federation Misc', () => {
|
||||
describe('Convert To Rspack', () => {
|
||||
beforeAll(() => {
|
||||
process.env.NX_ADD_PLUGINS = 'false';
|
||||
newProject({ packages: ['@nx/react', '@nx/rspack'] });
|
||||
});
|
||||
afterAll(() => {
|
||||
cleanupProject();
|
||||
delete process.env.NX_ADD_PLUGINS;
|
||||
});
|
||||
|
||||
it('should generate host and remote apps in webpack, convert to rspack and use playwright for e2es', async () => {
|
||||
const shell = uniq('shell');
|
||||
const remote1 = uniq('remote1');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/react:host ${shell} --remotes=${remote1} --bundler=webpack --e2eTestRunner=playwright --style=css --no-interactive --skipFormat`
|
||||
);
|
||||
|
||||
runCLI(
|
||||
`generate @nx/rspack:convert-webpack ${shell} --skipFormat --no-interactive`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nx/rspack:convert-webpack ${remote1} --skipFormat --no-interactive`
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`apps/${shell}-e2e/src/example.spec.ts`,
|
||||
stripIndents`
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('should display welcome message', async ({page}) => {
|
||||
await page.goto("/");
|
||||
expect(await page.locator('h1').innerText()).toContain('Welcome');
|
||||
});
|
||||
|
||||
test('should load remote 1', async ({page}) => {
|
||||
await page.goto("/${remote1}");
|
||||
expect(await page.locator('h1').innerText()).toContain('${remote1}');
|
||||
});
|
||||
`
|
||||
);
|
||||
|
||||
if (runE2ETests()) {
|
||||
const e2eResultsSwc = await runCommandUntil(
|
||||
`e2e ${shell}-e2e`,
|
||||
(output) => output.includes('Successfully ran target e2e for project')
|
||||
);
|
||||
|
||||
await killProcessAndPorts(e2eResultsSwc.pid, readPort(shell));
|
||||
}
|
||||
}, 500_000);
|
||||
});
|
||||
|
||||
describe('Interoperability', () => {
|
||||
beforeEach(() => {
|
||||
process.env.NX_ADD_PLUGINS = 'false';
|
||||
newProject({ packages: ['@nx/react'] });
|
||||
});
|
||||
afterEach(() => {
|
||||
cleanupProject();
|
||||
delete process.env.NX_ADD_PLUGINS;
|
||||
});
|
||||
|
||||
it('should have interop between webpack host and rspack remote', async () => {
|
||||
const shell = uniq('shell');
|
||||
const remote1 = uniq('remote1');
|
||||
const remote2 = uniq('remote2');
|
||||
|
||||
runCLI(
|
||||
`generate @nx/react:host apps/${shell} --name=${shell} --remotes=${remote1} --bundler=webpack --e2eTestRunner=cypress --style=css --no-interactive --skipFormat`
|
||||
);
|
||||
|
||||
runCLI(
|
||||
`generate @nx/react:remote apps/${remote2} --name=${remote2} --host=${shell} --bundler=rspack --style=css --no-interactive --skipFormat`
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`apps/${shell}-e2e/src/integration/app.spec.ts`,
|
||||
stripIndents`
|
||||
import { getGreeting } from '../support/app.po';
|
||||
|
||||
describe('shell app', () => {
|
||||
it('should display welcome message', () => {
|
||||
cy.visit('/')
|
||||
getGreeting().contains('Welcome ${shell}');
|
||||
});
|
||||
|
||||
it('should load remote 1', () => {
|
||||
cy.visit('/${remote1}')
|
||||
getGreeting().contains('Welcome ${remote1}');
|
||||
});
|
||||
|
||||
it('should load remote 2', () => {
|
||||
cy.visit('/${remote2}')
|
||||
getGreeting().contains('Welcome ${remote2}');
|
||||
});
|
||||
});
|
||||
`
|
||||
);
|
||||
|
||||
[shell, remote1, remote2].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));
|
||||
|
||||
if (runE2ETests()) {
|
||||
const e2eResultsSwc = await runCommandUntil(
|
||||
`e2e ${shell}-e2e --verbose`,
|
||||
(output) => output.includes('All specs passed!')
|
||||
);
|
||||
|
||||
await killProcessAndPorts(e2eResultsSwc.pid, readPort(shell));
|
||||
}
|
||||
}, 500_000);
|
||||
|
||||
it('should have interop between rspack host and webpack remote', async () => {
|
||||
const shell = uniq('shell');
|
||||
const remote1 = uniq('remote1');
|
||||
const remote2 = uniq('remote2');
|
||||
runCLI(
|
||||
`generate @nx/react:host apps/${shell} --name=${shell} --remotes=${remote1} --bundler=rspack --e2eTestRunner=cypress --style=css --no-interactive --skipFormat`
|
||||
);
|
||||
|
||||
runCLI(
|
||||
`generate @nx/react:remote apps/${remote2} --name=${remote2} --host=${shell} --bundler=webpack --style=css --no-interactive --skipFormat`
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`apps/${shell}-e2e/src/integration/app.cy.ts`,
|
||||
stripIndents`
|
||||
import { getGreeting } from '../support/app.po';
|
||||
|
||||
describe('shell app', () => {
|
||||
it('should display welcome message', () => {
|
||||
cy.visit('/')
|
||||
getGreeting().contains('Welcome ${shell}');
|
||||
});
|
||||
|
||||
it('should load remote 1', () => {
|
||||
cy.visit('/${remote1}')
|
||||
getGreeting().contains('Welcome ${remote1}');
|
||||
});
|
||||
|
||||
it('should load remote 2', () => {
|
||||
cy.visit('/${remote2}')
|
||||
getGreeting().contains('Welcome ${remote2}');
|
||||
});
|
||||
|
||||
});
|
||||
`
|
||||
);
|
||||
|
||||
if (runE2ETests()) {
|
||||
const e2eResultsSwc = await runCommandUntil(
|
||||
`e2e ${shell}-e2e --verbose`,
|
||||
(output) => output.includes('Successfully ran target e2e')
|
||||
);
|
||||
|
||||
await killProcessAndPorts(e2eResultsSwc.pid, readPort(shell));
|
||||
}
|
||||
}, 500_000);
|
||||
});
|
||||
});
|
||||
110
e2e/react/src/module-federation/ssr.rspack.test.ts
Normal file
110
e2e/react/src/module-federation/ssr.rspack.test.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import {
|
||||
checkFilesExist,
|
||||
cleanupProject,
|
||||
killPorts,
|
||||
killProcessAndPorts,
|
||||
newProject,
|
||||
runCLIAsync,
|
||||
runCommandUntil,
|
||||
runE2ETests,
|
||||
uniq,
|
||||
updateFile,
|
||||
} from '@nx/e2e/utils';
|
||||
import { readPort, runCLI } from './utils';
|
||||
|
||||
describe('React Rspack SSR Module Federation', () => {
|
||||
describe('ssr', () => {
|
||||
beforeEach(() => {
|
||||
newProject({ packages: ['@nx/react'] });
|
||||
});
|
||||
|
||||
afterEach(() => cleanupProject());
|
||||
|
||||
it('should generate host and remote apps with ssr', async () => {
|
||||
const shell = uniq('shell');
|
||||
const remote1 = uniq('remote1');
|
||||
const remote2 = uniq('remote2');
|
||||
const remote3 = uniq('remote3');
|
||||
|
||||
await runCLIAsync(
|
||||
`generate @nx/react:host apps/${shell} --ssr --name=${shell} --remotes=${remote1},${remote2},${remote3} --bundler=rspack --style=css --no-interactive --skipFormat`
|
||||
);
|
||||
|
||||
expect(readPort(shell)).toEqual(4000);
|
||||
expect(readPort(remote1)).toEqual(4201);
|
||||
expect(readPort(remote2)).toEqual(4202);
|
||||
expect(readPort(remote3)).toEqual(4203);
|
||||
|
||||
for (const app of [shell, remote1, remote2, remote3]) {
|
||||
checkFilesExist(
|
||||
`apps/${app}/module-federation.config.ts`,
|
||||
`apps/${app}/module-federation.server.config.ts`
|
||||
);
|
||||
const cliOutput = runCLI(`run ${app}:build`);
|
||||
expect(cliOutput).toContain('Successfully ran target');
|
||||
|
||||
await killPorts(readPort(app));
|
||||
}
|
||||
}, 500_000);
|
||||
|
||||
it('should serve remotes as static when running the host by default', async () => {
|
||||
const shell = uniq('shell');
|
||||
const remote1 = uniq('remote1');
|
||||
const remote2 = uniq('remote2');
|
||||
const remote3 = uniq('remote3');
|
||||
|
||||
await runCLIAsync(
|
||||
`generate @nx/react:host apps/${shell} --ssr --name=${shell} --remotes=${remote1},${remote2},${remote3} --bundler=rspack --style=css --e2eTestRunner=cypress --no-interactive --skipFormat`
|
||||
);
|
||||
|
||||
const serveResult = await runCommandUntil(`serve ${shell}`, (output) =>
|
||||
output.includes(`NX Static remotes proxies started successfully`)
|
||||
);
|
||||
|
||||
await killProcessAndPorts(serveResult.pid);
|
||||
}, 500_000);
|
||||
|
||||
it('should serve remotes as static and they should be able to be accessed from the host', async () => {
|
||||
const shell = uniq('shell');
|
||||
const remote1 = uniq('remote1');
|
||||
const remote2 = uniq('remote2');
|
||||
const remote3 = uniq('remote3');
|
||||
|
||||
await runCLIAsync(
|
||||
`generate @nx/react:host apps/${shell} --ssr --name=${shell} --remotes=${remote1},${remote2},${remote3} --bundler=rspack --style=css --e2eTestRunner=cypress --no-interactive --skipFormat`
|
||||
);
|
||||
|
||||
const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
|
||||
|
||||
updateFile(`apps/${shell}-e2e/src/e2e/app.cy.ts`, (content) => {
|
||||
return `
|
||||
describe('${shell}-e2e', () => {
|
||||
beforeEach(() => cy.visit('/'));
|
||||
|
||||
it('should display welcome message', () => {
|
||||
expect(cy.get('ul li').should('have.length', 4));
|
||||
expect(cy.get('ul li').eq(0).should('have.text', 'Home'));
|
||||
expect(cy.get('ul li').eq(1).should('have.text', '${capitalize(
|
||||
remote1
|
||||
)}'));
|
||||
expect(cy.get('ul li').eq(2).should('have.text', '${capitalize(
|
||||
remote2
|
||||
)}'));
|
||||
expect(cy.get('ul li').eq(3).should('have.text', '${capitalize(
|
||||
remote3
|
||||
)}'));
|
||||
});
|
||||
});
|
||||
`;
|
||||
});
|
||||
|
||||
if (runE2ETests()) {
|
||||
const hostE2eResults = await runCommandUntil(
|
||||
`e2e ${shell}-e2e --verbose`,
|
||||
(output) => output.includes('All specs passed!')
|
||||
);
|
||||
await killProcessAndPorts(hostE2eResults.pid);
|
||||
}
|
||||
}, 600_000);
|
||||
});
|
||||
});
|
||||
@ -24,12 +24,10 @@ import {
|
||||
startStaticRemotesFileServer,
|
||||
} from '../../utils';
|
||||
import { NxModuleFederationDevServerConfig } from '../../models';
|
||||
import { ChildProcess, fork } from 'node:child_process';
|
||||
|
||||
const PLUGIN_NAME = 'NxModuleFederationDevServerPlugin';
|
||||
|
||||
export class NxModuleFederationDevServerPlugin implements RspackPluginInstance {
|
||||
private devServerProcess: ChildProcess | undefined;
|
||||
private nxBin = require.resolve('nx/bin/nx');
|
||||
|
||||
constructor(
|
||||
@ -44,42 +42,52 @@ export class NxModuleFederationDevServerPlugin implements RspackPluginInstance {
|
||||
}
|
||||
|
||||
apply(compiler: Compiler) {
|
||||
compiler.hooks.beforeCompile.tapAsync(
|
||||
const isDevServer = process.env['WEBPACK_SERVE'];
|
||||
if (!isDevServer) {
|
||||
return;
|
||||
}
|
||||
compiler.hooks.watchRun.tapAsync(
|
||||
PLUGIN_NAME,
|
||||
async (params, callback) => {
|
||||
const staticRemotesConfig = await this.setup(compiler);
|
||||
async (compiler, callback) => {
|
||||
compiler.hooks.beforeCompile.tapAsync(
|
||||
PLUGIN_NAME,
|
||||
async (params, callback) => {
|
||||
const staticRemotesConfig = await this.setup();
|
||||
|
||||
logger.info(
|
||||
`NX Starting module federation dev-server for ${pc.bold(
|
||||
this._options.config.name
|
||||
)} with ${Object.keys(staticRemotesConfig).length} remotes`
|
||||
logger.info(
|
||||
`NX Starting module federation dev-server for ${pc.bold(
|
||||
this._options.config.name
|
||||
)} with ${Object.keys(staticRemotesConfig).length} remotes`
|
||||
);
|
||||
|
||||
const mappedLocationOfRemotes = await buildStaticRemotes(
|
||||
staticRemotesConfig,
|
||||
this._options.devServerConfig,
|
||||
this.nxBin
|
||||
);
|
||||
startStaticRemotesFileServer(
|
||||
staticRemotesConfig,
|
||||
workspaceRoot,
|
||||
this._options.devServerConfig.staticRemotesPort
|
||||
);
|
||||
startRemoteProxies(staticRemotesConfig, mappedLocationOfRemotes, {
|
||||
pathToCert: this._options.devServerConfig.sslCert,
|
||||
pathToKey: this._options.devServerConfig.sslCert,
|
||||
});
|
||||
|
||||
new DefinePlugin({
|
||||
'process.env.NX_MF_DEV_REMOTES': process.env.NX_MF_DEV_REMOTES,
|
||||
}).apply(compiler);
|
||||
|
||||
callback();
|
||||
}
|
||||
);
|
||||
|
||||
const mappedLocationOfRemotes = await buildStaticRemotes(
|
||||
staticRemotesConfig,
|
||||
this._options.devServerConfig,
|
||||
this.nxBin
|
||||
);
|
||||
startStaticRemotesFileServer(
|
||||
staticRemotesConfig,
|
||||
workspaceRoot,
|
||||
this._options.devServerConfig.staticRemotesPort
|
||||
);
|
||||
startRemoteProxies(staticRemotesConfig, mappedLocationOfRemotes, {
|
||||
pathToCert: this._options.devServerConfig.sslCert,
|
||||
pathToKey: this._options.devServerConfig.sslCert,
|
||||
});
|
||||
|
||||
new DefinePlugin({
|
||||
'process.env.NX_MF_DEV_REMOTES': process.env.NX_MF_DEV_REMOTES,
|
||||
}).apply(compiler);
|
||||
|
||||
callback();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private async setup(compiler: Compiler) {
|
||||
private async setup() {
|
||||
const projectGraph = readCachedProjectGraph();
|
||||
const { projects: workspaceProjects } =
|
||||
readProjectsConfigurationFromProjectGraph(projectGraph);
|
||||
|
||||
@ -4,14 +4,11 @@ import {
|
||||
NxModuleFederationConfigOverride,
|
||||
} from '../../../utils/models';
|
||||
import { getModuleFederationConfig } from '../../../with-module-federation/rspack/utils';
|
||||
import { NxModuleFederationDevServerConfig } from '../../models';
|
||||
import { NxModuleFederationDevServerPlugin } from './nx-module-federation-dev-server-plugin';
|
||||
|
||||
export class NxModuleFederationPlugin implements RspackPluginInstance {
|
||||
constructor(
|
||||
private _options: {
|
||||
config: ModuleFederationConfig;
|
||||
devServerConfig?: NxModuleFederationDevServerConfig;
|
||||
isServer?: boolean;
|
||||
},
|
||||
private configOverride?: NxModuleFederationConfigOverride
|
||||
@ -23,6 +20,7 @@ export class NxModuleFederationPlugin implements RspackPluginInstance {
|
||||
}
|
||||
|
||||
// This is required to ensure Module Federation will build the project correctly
|
||||
compiler.options.optimization ??= {};
|
||||
compiler.options.optimization.runtimeChunk = false;
|
||||
compiler.options.output.uniqueName = this._options.config.name;
|
||||
if (this._options.isServer) {
|
||||
|
||||
@ -46,6 +46,10 @@ export class NxModuleFederationSSRDevServerPlugin
|
||||
}
|
||||
|
||||
apply(compiler: Compiler) {
|
||||
const isDevServer = process.env['WEBPACK_SERVE'];
|
||||
if (!isDevServer) {
|
||||
return;
|
||||
}
|
||||
compiler.hooks.watchRun.tapAsync(
|
||||
PLUGIN_NAME,
|
||||
async (compiler, callback) => {
|
||||
@ -95,7 +99,7 @@ export class NxModuleFederationSSRDevServerPlugin
|
||||
}
|
||||
|
||||
private async startServer(compiler: Compiler) {
|
||||
compiler.hooks.afterEmit.tapAsync(PLUGIN_NAME, async (_, callback) => {
|
||||
compiler.hooks.done.tapAsync(PLUGIN_NAME, async (_, callback) => {
|
||||
const serverPath = join(
|
||||
compiler.options.output.path,
|
||||
(compiler.options.output.filename as string) ?? 'server.js'
|
||||
@ -105,14 +109,14 @@ export class NxModuleFederationSSRDevServerPlugin
|
||||
this.devServerProcess.on('exit', () => {
|
||||
res();
|
||||
});
|
||||
this.devServerProcess.kill();
|
||||
this.devServerProcess.kill('SIGKILL');
|
||||
this.devServerProcess = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
if (!existsSync(serverPath)) {
|
||||
for (let retries = 0; retries < 10; retries++) {
|
||||
await new Promise<void>((res) => setTimeout(res, 100));
|
||||
await new Promise<void>((res) => setTimeout(res, 200));
|
||||
if (existsSync(serverPath)) {
|
||||
break;
|
||||
}
|
||||
@ -124,10 +128,10 @@ export class NxModuleFederationSSRDevServerPlugin
|
||||
|
||||
this.devServerProcess = fork(serverPath);
|
||||
process.on('exit', () => {
|
||||
this.devServerProcess?.kill();
|
||||
this.devServerProcess?.kill('SIGKILL');
|
||||
});
|
||||
process.on('SIGINT', () => {
|
||||
this.devServerProcess?.kill();
|
||||
this.devServerProcess?.kill('SIGKILL');
|
||||
});
|
||||
callback();
|
||||
});
|
||||
|
||||
@ -27,7 +27,7 @@ export function parseRemotesConfig(
|
||||
workspaceRoot,
|
||||
}
|
||||
);
|
||||
if (outputPath.startsWith(projectRoot)) {
|
||||
if (!outputPath.startsWith(workspaceRoot)) {
|
||||
outputPath = joinPathFragments(workspaceRoot, outputPath);
|
||||
}
|
||||
const basePath = dirname(outputPath);
|
||||
|
||||
@ -59,7 +59,19 @@ function collectRemoteProjects(
|
||||
collected.add(remote);
|
||||
|
||||
const remoteProjectRoot = remoteProject.root;
|
||||
const remoteProjectTsConfig = remoteProject.targets['build'].options.tsConfig;
|
||||
let remoteProjectTsConfig =
|
||||
remoteProject.targets['build'].options.tsConfig ??
|
||||
[
|
||||
join(remoteProjectRoot, 'tsconfig.app.json'),
|
||||
join(remoteProjectRoot, 'tsconfig.json'),
|
||||
join(context.root, 'tsconfig.json'),
|
||||
join(context.root, 'tsconfig.base.json'),
|
||||
].find((p) => existsSync(p));
|
||||
if (!remoteProjectTsConfig) {
|
||||
throw new Error(
|
||||
`Could not find a tsconfig for remote project ${remote}. Please add a tsconfig.app.json or tsconfig.json to the project.`
|
||||
);
|
||||
}
|
||||
const remoteProjectConfig = getModuleFederationConfig(
|
||||
remoteProjectTsConfig,
|
||||
context.root,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import type { ExecutorContext } from '@nx/devkit';
|
||||
import { ExecutorContext, joinPathFragments } from '@nx/devkit';
|
||||
import { basename, dirname } from 'path';
|
||||
import { interpolate } from 'nx/src/tasks-runner/utils';
|
||||
|
||||
export type StaticRemoteConfig = {
|
||||
basePath: string;
|
||||
@ -22,8 +23,21 @@ export function parseStaticRemotesConfig(
|
||||
|
||||
const config: Record<string, StaticRemoteConfig> = {};
|
||||
for (const app of staticRemotes) {
|
||||
const outputPath =
|
||||
context.projectGraph.nodes[app].data.targets['build'].options.outputPath; // dist || dist/checkout
|
||||
const projectGraph = context.projectGraph;
|
||||
const projectRoot = projectGraph.nodes[app].data.root;
|
||||
let outputPath = interpolate(
|
||||
projectGraph.nodes[app].data.targets?.['build']?.options?.outputPath ??
|
||||
projectGraph.nodes[app].data.targets?.['build']?.outputs?.[0] ??
|
||||
`${context.root}/${projectGraph.nodes[app].data.root}/dist`,
|
||||
{
|
||||
projectName: projectGraph.nodes[app].data.name,
|
||||
projectRoot,
|
||||
workspaceRoot: context.root,
|
||||
}
|
||||
);
|
||||
if (outputPath.startsWith(projectRoot)) {
|
||||
outputPath = joinPathFragments(context.root, outputPath);
|
||||
}
|
||||
const basePath = ['', '/', '.'].some((p) => dirname(outputPath) === p)
|
||||
? outputPath
|
||||
: dirname(outputPath); // dist || dist/checkout -> dist
|
||||
@ -45,10 +59,22 @@ export function parseStaticSsrRemotesConfig(
|
||||
}
|
||||
const config: Record<string, StaticRemoteConfig> = {};
|
||||
for (const app of staticRemotes) {
|
||||
let outputPath = context.projectGraph.nodes[app].data.targets['build']
|
||||
.options.outputPath as string;
|
||||
outputPath = dirname(outputPath); // dist/browser => dist || dist/checkout/browser -> checkout
|
||||
|
||||
const projectGraph = context.projectGraph;
|
||||
const projectRoot = projectGraph.nodes[app].data.root;
|
||||
let outputPath = interpolate(
|
||||
projectGraph.nodes[app].data.targets?.['build']?.options?.outputPath ??
|
||||
projectGraph.nodes[app].data.targets?.['build']?.outputs?.[0] ??
|
||||
`${context.root}/${projectGraph.nodes[app].data.root}/dist`,
|
||||
{
|
||||
projectName: projectGraph.nodes[app].data.name,
|
||||
projectRoot,
|
||||
workspaceRoot: context.root,
|
||||
}
|
||||
);
|
||||
if (outputPath.startsWith(projectRoot)) {
|
||||
outputPath = joinPathFragments(context.root, outputPath);
|
||||
}
|
||||
outputPath = dirname(outputPath);
|
||||
const basePath = ['', '/', '.'].some((p) => dirname(outputPath) === p)
|
||||
? outputPath
|
||||
: dirname(outputPath); // dist || dist/checkout -> dist
|
||||
|
||||
@ -454,7 +454,6 @@ function normalizeOutput(
|
||||
relative(workspaceRoot, fullPath)
|
||||
);
|
||||
}
|
||||
|
||||
return joinPathFragments('{projectRoot}', pathRelativeToProjectRoot);
|
||||
}
|
||||
|
||||
|
||||
@ -59,6 +59,7 @@
|
||||
"@nx/jest",
|
||||
"@nx/rollup",
|
||||
"@nx/rsbuild",
|
||||
"@nx/rspack",
|
||||
"@nx/storybook",
|
||||
"@nx/vite",
|
||||
"@nx/webpack",
|
||||
|
||||
@ -58,6 +58,20 @@ export async function addE2e(
|
||||
options.addPlugin,
|
||||
options.devServerPort ?? 4200
|
||||
);
|
||||
} else if (options.bundler === 'rspack') {
|
||||
const { getRspackE2EWebServerInfo } = ensurePackage<
|
||||
typeof import('@nx/rspack')
|
||||
>('@nx/rspack', nxVersion);
|
||||
e2eWebServerInfo = await getRspackE2EWebServerInfo(
|
||||
tree,
|
||||
options.projectName,
|
||||
joinPathFragments(
|
||||
options.appProjectRoot,
|
||||
`rspack.config.${options.js ? 'js' : 'ts'}`
|
||||
),
|
||||
options.addPlugin,
|
||||
options.devServerPort ?? 4200
|
||||
);
|
||||
} else if (options.bundler === 'vite') {
|
||||
const { getViteE2EWebServerInfo, getReactRouterE2EWebServerInfo } =
|
||||
ensurePackage<typeof import('@nx/vite')>('@nx/vite', nxVersion);
|
||||
|
||||
@ -33,41 +33,13 @@ import {
|
||||
typesReactVersion,
|
||||
} from '../../../utils/versions';
|
||||
|
||||
export async function createApplicationFiles(
|
||||
export function getDefaultTemplateVariables(
|
||||
host: Tree,
|
||||
options: NormalizedSchema
|
||||
) {
|
||||
let styleSolutionSpecificAppFiles: string;
|
||||
if (options.styledModule && options.style !== 'styled-jsx') {
|
||||
styleSolutionSpecificAppFiles = '../files/style-styled-module';
|
||||
} else if (options.style === 'styled-jsx') {
|
||||
styleSolutionSpecificAppFiles = '../files/style-styled-jsx';
|
||||
} else if (options.style === 'tailwind') {
|
||||
styleSolutionSpecificAppFiles = '../files/style-tailwind';
|
||||
} else if (options.style === 'none') {
|
||||
styleSolutionSpecificAppFiles = '../files/style-none';
|
||||
} else if (options.globalCss) {
|
||||
styleSolutionSpecificAppFiles = '../files/style-global-css';
|
||||
} else {
|
||||
styleSolutionSpecificAppFiles = '../files/style-css-module';
|
||||
}
|
||||
const hasStyleFile = ['scss', 'css', 'less'].includes(options.style);
|
||||
|
||||
const onBoardingStatus = await createNxCloudOnboardingURLForWelcomeApp(
|
||||
host,
|
||||
options.nxCloudToken
|
||||
);
|
||||
|
||||
const connectCloudUrl =
|
||||
onBoardingStatus === 'unclaimed' &&
|
||||
(await getNxCloudAppOnBoardingUrl(options.nxCloudToken));
|
||||
|
||||
const relativePathToRootTsConfig = getRelativePathToRootTsConfig(
|
||||
host,
|
||||
options.appProjectRoot
|
||||
);
|
||||
const appTests = getAppTests(options);
|
||||
const templateVariables = {
|
||||
return {
|
||||
...options.names,
|
||||
...options,
|
||||
typesNodeVersion,
|
||||
@ -86,6 +58,79 @@ export async function createApplicationFiles(
|
||||
hasStyleFile,
|
||||
isUsingTsSolutionSetup: isUsingTsSolutionSetup(host),
|
||||
};
|
||||
}
|
||||
|
||||
export function createNxRspackPluginOptions(
|
||||
options: NormalizedSchema,
|
||||
rootOffset: string,
|
||||
tsx: boolean = true
|
||||
): WithNxOptions & WithReactOptions {
|
||||
return {
|
||||
target: 'web',
|
||||
outputPath: options.isUsingTsSolutionConfig
|
||||
? 'dist'
|
||||
: joinPathFragments(
|
||||
rootOffset,
|
||||
'dist',
|
||||
options.appProjectRoot != '.'
|
||||
? options.appProjectRoot
|
||||
: options.projectName
|
||||
),
|
||||
index: './src/index.html',
|
||||
baseHref: '/',
|
||||
main: maybeJs(
|
||||
{
|
||||
js: options.js,
|
||||
useJsx: true,
|
||||
},
|
||||
`./src/main.${tsx ? 'tsx' : 'ts'}`
|
||||
),
|
||||
tsConfig: './tsconfig.app.json',
|
||||
assets: ['./src/favicon.ico', './src/assets'],
|
||||
styles:
|
||||
options.styledModule || !options.hasStyles
|
||||
? []
|
||||
: [
|
||||
`./src/styles.${
|
||||
options.style !== 'tailwind' ? options.style : 'css'
|
||||
}`,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export async function createApplicationFiles(
|
||||
host: Tree,
|
||||
options: NormalizedSchema
|
||||
) {
|
||||
let styleSolutionSpecificAppFiles: string;
|
||||
if (options.styledModule && options.style !== 'styled-jsx') {
|
||||
styleSolutionSpecificAppFiles = '../files/style-styled-module';
|
||||
} else if (options.style === 'styled-jsx') {
|
||||
styleSolutionSpecificAppFiles = '../files/style-styled-jsx';
|
||||
} else if (options.style === 'tailwind') {
|
||||
styleSolutionSpecificAppFiles = '../files/style-tailwind';
|
||||
} else if (options.style === 'none') {
|
||||
styleSolutionSpecificAppFiles = '../files/style-none';
|
||||
} else if (options.globalCss) {
|
||||
styleSolutionSpecificAppFiles = '../files/style-global-css';
|
||||
} else {
|
||||
styleSolutionSpecificAppFiles = '../files/style-css-module';
|
||||
}
|
||||
|
||||
const onBoardingStatus = await createNxCloudOnboardingURLForWelcomeApp(
|
||||
host,
|
||||
options.nxCloudToken
|
||||
);
|
||||
|
||||
const connectCloudUrl =
|
||||
onBoardingStatus === 'unclaimed' &&
|
||||
(await getNxCloudAppOnBoardingUrl(options.nxCloudToken));
|
||||
|
||||
const relativePathToRootTsConfig = getRelativePathToRootTsConfig(
|
||||
host,
|
||||
options.appProjectRoot
|
||||
);
|
||||
const templateVariables = getDefaultTemplateVariables(host, options);
|
||||
|
||||
if (options.bundler === 'vite' && !options.useReactRouter) {
|
||||
generateFiles(
|
||||
@ -279,43 +324,6 @@ function createNxWebpackPluginOptions(
|
||||
};
|
||||
}
|
||||
|
||||
function createNxRspackPluginOptions(
|
||||
options: NormalizedSchema,
|
||||
rootOffset: string
|
||||
): WithNxOptions & WithReactOptions {
|
||||
return {
|
||||
target: 'web',
|
||||
outputPath: options.isUsingTsSolutionConfig
|
||||
? 'dist'
|
||||
: joinPathFragments(
|
||||
rootOffset,
|
||||
'dist',
|
||||
options.appProjectRoot != '.'
|
||||
? options.appProjectRoot
|
||||
: options.projectName
|
||||
),
|
||||
index: './src/index.html',
|
||||
baseHref: '/',
|
||||
main: maybeJs(
|
||||
{
|
||||
js: options.js,
|
||||
useJsx: true,
|
||||
},
|
||||
`./src/main.tsx`
|
||||
),
|
||||
tsConfig: './tsconfig.app.json',
|
||||
assets: ['./src/favicon.ico', './src/assets'],
|
||||
styles:
|
||||
options.styledModule || !options.hasStyles
|
||||
? []
|
||||
: [
|
||||
`./src/styles.${
|
||||
options.style !== 'tailwind' ? options.style : 'css'
|
||||
}`,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function generateReactRouterFiles(
|
||||
tree: Tree,
|
||||
options: NormalizedSchema,
|
||||
|
||||
@ -1,30 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`hostGenerator bundler=rspack should generate host files and configs for SSR 1`] = `
|
||||
"const { composePlugins, withNx, withReact } = require('@nx/rspack');
|
||||
const { withModuleFederationForSSR } = require('@nx/module-federation/rspack');
|
||||
|
||||
const baseConfig = require('./module-federation.config');
|
||||
|
||||
const defaultConfig = {
|
||||
...baseConfig,
|
||||
};
|
||||
|
||||
// Nx plugins for rspack to build config object from Nx options and context.
|
||||
/**
|
||||
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support for Module Federation
|
||||
* The DTS Plugin can be enabled by setting dts: true
|
||||
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
|
||||
*/
|
||||
module.exports = composePlugins(
|
||||
withNx(),
|
||||
withReact({ ssr: true }),
|
||||
withModuleFederationForSSR(defaultConfig, { dts: false })
|
||||
);
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`hostGenerator bundler=rspack should generate host files and configs for SSR 2`] = `
|
||||
"// @ts-check
|
||||
|
||||
/**
|
||||
@ -33,6 +9,12 @@ exports[`hostGenerator bundler=rspack should generate host files and configs for
|
||||
const moduleFederationConfig = {
|
||||
name: 'test',
|
||||
remotes: [],
|
||||
shared: (libraryName, libraryConfig) => {
|
||||
return {
|
||||
...libraryConfig,
|
||||
eager: true,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
@ -43,35 +25,17 @@ module.exports = moduleFederationConfig;
|
||||
`;
|
||||
|
||||
exports[`hostGenerator bundler=rspack should generate host files and configs for SSR when --typescriptConfiguration=true 1`] = `
|
||||
"import { composePlugins, withNx, withReact } from '@nx/rspack';
|
||||
import { withModuleFederationForSSR } from '@nx/module-federation/rspack';
|
||||
|
||||
import baseConfig from './module-federation.config';
|
||||
|
||||
const defaultConfig = {
|
||||
...baseConfig,
|
||||
};
|
||||
|
||||
// Nx plugins for rspack to build config object from Nx options and context.
|
||||
/**
|
||||
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support for Module Federation
|
||||
* The DTS Plugin can be enabled by setting dts: true
|
||||
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
|
||||
*/
|
||||
export default composePlugins(
|
||||
withNx(),
|
||||
withReact({ ssr: true }),
|
||||
withModuleFederationForSSR(defaultConfig, { dts: false })
|
||||
);
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`hostGenerator bundler=rspack should generate host files and configs for SSR when --typescriptConfiguration=true 2`] = `
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: 'test',
|
||||
remotes: [],
|
||||
shared: (libraryName, libraryConfig) => {
|
||||
return {
|
||||
...libraryConfig,
|
||||
eager: true,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
@ -82,26 +46,49 @@ export default config;
|
||||
`;
|
||||
|
||||
exports[`hostGenerator bundler=rspack should generate host files and configs when --typescriptConfiguration=false 1`] = `
|
||||
"const { composePlugins, withNx, withReact } = require('@nx/rspack');
|
||||
const { withModuleFederation } = require('@nx/module-federation/rspack');
|
||||
"const { NxAppRspackPlugin } = require('@nx/rspack/app-plugin');
|
||||
const { NxReactRspackPlugin } = require('@nx/rspack/react-plugin');
|
||||
const {
|
||||
NxModuleFederationPlugin,
|
||||
NxModuleFederationDevServerPlugin,
|
||||
} = require('@nx/module-federation/rspack');
|
||||
const { join } = require('path');
|
||||
|
||||
const baseConfig = require('./module-federation.config');
|
||||
const config = require('./module-federation.config');
|
||||
|
||||
const config = {
|
||||
...baseConfig,
|
||||
module.exports = {
|
||||
output: {
|
||||
path: join(__dirname, '../dist/test'),
|
||||
publicPath: 'auto',
|
||||
},
|
||||
devServer: {
|
||||
port: 4200,
|
||||
historyApiFallback: {
|
||||
index: '/index.html',
|
||||
disableDotRule: true,
|
||||
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new NxAppRspackPlugin({
|
||||
tsConfig: './tsconfig.app.json',
|
||||
main: './src/main.ts',
|
||||
index: './src/index.html',
|
||||
baseHref: '/',
|
||||
assets: ['./src/favicon.ico', './src/assets'],
|
||||
styles: ['./src/styles.css'],
|
||||
outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none',
|
||||
optimization: process.env['NODE_ENV'] === 'production',
|
||||
}),
|
||||
new NxReactRspackPlugin({
|
||||
// Uncomment this line if you don't want to use SVGR
|
||||
// See: https://react-svgr.com/
|
||||
// svgr: false
|
||||
}),
|
||||
new NxModuleFederationPlugin({ config }, { dts: false }),
|
||||
new NxModuleFederationDevServerPlugin({ config }),
|
||||
],
|
||||
};
|
||||
|
||||
// Nx plugins for rspack to build config object from Nx options and context.
|
||||
/**
|
||||
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support for Module Federation
|
||||
* The DTS Plugin can be enabled by setting dts: true
|
||||
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
|
||||
*/
|
||||
module.exports = composePlugins(
|
||||
withNx(),
|
||||
withReact(),
|
||||
withModuleFederation(config, { dts: false })
|
||||
);
|
||||
"
|
||||
`;
|
||||
|
||||
@ -129,23 +116,46 @@ module.exports = {
|
||||
`;
|
||||
|
||||
exports[`hostGenerator bundler=rspack should generate host files and configs when --typescriptConfiguration=true 1`] = `
|
||||
"import {composePlugins, withNx, withReact} from '@nx/rspack';
|
||||
import { withModuleFederation } from '@nx/module-federation/rspack';
|
||||
import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
"import { NxAppRspackPlugin } from '@nx/rspack/app-plugin';
|
||||
import { NxReactRspackPlugin } from '@nx/rspack/react-plugin';
|
||||
import { NxModuleFederationPlugin, NxModuleFederationDevServerPlugin } from '@nx/module-federation/rspack';
|
||||
import { join } from 'path';
|
||||
|
||||
import baseConfig from './module-federation.config';
|
||||
import config from './module-federation.config';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
...baseConfig,
|
||||
export default {
|
||||
output: {
|
||||
path: join(__dirname, '../dist/test'),
|
||||
publicPath: 'auto'
|
||||
},
|
||||
devServer: {
|
||||
port: 4200,
|
||||
historyApiFallback: {
|
||||
index: '/index.html',
|
||||
disableDotRule: true,
|
||||
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new NxAppRspackPlugin({
|
||||
tsConfig: './tsconfig.app.json',
|
||||
main: './src/main.ts',
|
||||
index: './src/index.html',
|
||||
baseHref: '/',
|
||||
assets: ["./src/favicon.ico","./src/assets"],
|
||||
styles: ["./src/styles.css"],
|
||||
outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none',
|
||||
optimization: process.env['NODE_ENV'] === 'production',
|
||||
}),
|
||||
new NxReactRspackPlugin({
|
||||
// Uncomment this line if you don't want to use SVGR
|
||||
// See: https://react-svgr.com/
|
||||
// svgr: false
|
||||
}),
|
||||
new NxModuleFederationPlugin({ config }, { dts: false }),
|
||||
new NxModuleFederationDevServerPlugin({ config }),
|
||||
],
|
||||
};
|
||||
|
||||
// Nx plugins for rspack to build config object from Nx options and context.
|
||||
/**
|
||||
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support for Module Federation
|
||||
* The DTS Plugin can be enabled by setting dts: true
|
||||
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
|
||||
*/
|
||||
export default composePlugins(withNx(), withReact(), withModuleFederation(config, { dts: false }));
|
||||
"
|
||||
`;
|
||||
|
||||
|
||||
@ -8,6 +8,12 @@ const config: ModuleFederationConfig = {
|
||||
"<%= r.fileName %>",
|
||||
<%_ }); } _%>
|
||||
],
|
||||
shared: (libraryName, libraryConfig) => {
|
||||
return {
|
||||
...libraryConfig,
|
||||
eager: true
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -0,0 +1,66 @@
|
||||
import { NxAppRspackPlugin } from '@nx/rspack/app-plugin';
|
||||
import { NxReactRspackPlugin } from '@nx/rspack/react-plugin';
|
||||
import { NxModuleFederationPlugin, NxModuleFederationSSRDevServerPlugin } from '@nx/module-federation/rspack';
|
||||
import { join } from 'path';
|
||||
|
||||
import browserMfConfig from './module-federation.config';
|
||||
import serverMfConfig from './module-federation.server.config';
|
||||
|
||||
const browserRspackConfig = {
|
||||
name: 'browser',
|
||||
output: {
|
||||
path: join(__dirname, '<%= rspackPluginOptions.outputPath %>', 'browser'),
|
||||
publicPath: 'auto'
|
||||
},
|
||||
devServer: {
|
||||
port: <%= devServerPort %>,
|
||||
historyApiFallback: {
|
||||
index: '/index.html',
|
||||
disableDotRule: true,
|
||||
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
|
||||
},
|
||||
devMiddleware: {
|
||||
writeToDisk: (file: string) => !file.includes('.hot-update.'),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new NxAppRspackPlugin({
|
||||
tsConfig: '<%= rspackPluginOptions.tsConfig %>',
|
||||
main: '<%= rspackPluginOptions.main %>',
|
||||
index: '<%= rspackPluginOptions.index %>',
|
||||
baseHref: '<%= rspackPluginOptions.baseHref %>',
|
||||
assets: <%- JSON.stringify(rspackPluginOptions.assets) %>,
|
||||
styles: <%- JSON.stringify(rspackPluginOptions.styles) %>,
|
||||
outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none',
|
||||
optimization: process.env['NODE_ENV'] === 'production',
|
||||
}),
|
||||
new NxReactRspackPlugin({
|
||||
// Uncomment this line if you don't want to use SVGR
|
||||
// See: https://react-svgr.com/
|
||||
// svgr: false
|
||||
}),
|
||||
new NxModuleFederationPlugin({ config: browserMfConfig }, { dts: false }),
|
||||
],
|
||||
};
|
||||
|
||||
const serverRspackConfig = {
|
||||
name: 'server',
|
||||
target: 'async-node',
|
||||
output: {
|
||||
path: join(__dirname, '<%= rspackPluginOptions.outputPath %>', 'server'),
|
||||
filename: 'server.js'
|
||||
},
|
||||
plugins: [
|
||||
new NxAppRspackPlugin({
|
||||
outputPath: join(__dirname, '<%= rspackPluginOptions.outputPath %>', 'server'),
|
||||
outputFileName: 'server.js',
|
||||
tsConfig: '<%= rspackPluginOptions.tsConfig %>',
|
||||
main: '<%= rspackPluginOptions.mainServer %>',
|
||||
baseHref: '<%= rspackPluginOptions.baseHref %>',
|
||||
}),
|
||||
new NxModuleFederationPlugin({ config: serverMfConfig, isServer: true }, { dts: false }),
|
||||
new NxModuleFederationSSRDevServerPlugin({ config: serverMfConfig }),
|
||||
],
|
||||
};
|
||||
|
||||
export default [browserRspackConfig, serverRspackConfig];
|
||||
@ -1,16 +0,0 @@
|
||||
import {composePlugins, withNx, withReact} from '@nx/rspack';
|
||||
import {withModuleFederationForSSR} from '@nx/module-federation/rspack';
|
||||
|
||||
import baseConfig from './module-federation.config';
|
||||
|
||||
const defaultConfig = {
|
||||
...baseConfig
|
||||
};
|
||||
|
||||
// Nx plugins for rspack to build config object from Nx options and context.
|
||||
/**
|
||||
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support for Module Federation
|
||||
* The DTS Plugin can be enabled by setting dts: true
|
||||
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
|
||||
*/
|
||||
export default composePlugins(withNx(), withReact({ssr: true}), withModuleFederationForSSR(defaultConfig, { dts: false }));
|
||||
@ -7,7 +7,7 @@ import { handleRequest } from './src/main.server';
|
||||
const port = process.env['PORT'] || <%= port %>;
|
||||
const app = express();
|
||||
|
||||
const browserDist = path.join(process.cwd(), '<%= browserBuildOutputPath %>');
|
||||
const browserDist = path.join(process.cwd(), '<%= rspackPluginOptions.outputPath %>', 'browser');
|
||||
const indexPath = path.join(browserDist, 'index.html');
|
||||
|
||||
app.use(cors());
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
import type { Request, Response } from 'express';
|
||||
import * as fs from 'fs';
|
||||
import * as ReactDOMServer from 'react-dom/server';
|
||||
import isbot from 'isbot'
|
||||
|
||||
import App from './app/app';
|
||||
|
||||
import { StaticRouter } from 'react-router-dom/server';
|
||||
|
||||
|
||||
let indexHtml: null | string = null;
|
||||
|
||||
export function handleRequest(indexPath: string) {
|
||||
return function render(req: Request, res: Response) {
|
||||
let didError = false;
|
||||
|
||||
if (!indexHtml) {
|
||||
indexHtml = fs.readFileSync(indexPath).toString();
|
||||
}
|
||||
|
||||
const [htmlStart, htmlEnd] = indexHtml.split(`<div id="root"></div>`);
|
||||
|
||||
// For bots (e.g. search engines), the content will not be streamed but render all at once.
|
||||
// For users, content should be streamed to the user as they are ready.
|
||||
const callbackName = isbot(req.headers['user-agent']) ? 'onAllReady' : 'onShellReady';
|
||||
|
||||
const stream = ReactDOMServer.renderToPipeableStream(
|
||||
<StaticRouter location={req.originalUrl}><App /></StaticRouter>,
|
||||
{
|
||||
[callbackName]() {
|
||||
res.statusCode = didError ? 500 : 200;
|
||||
res.setHeader('Content-type', 'text/html; charset=utf-8');
|
||||
res.write(`${htmlStart}<div id="root">`);
|
||||
stream.pipe(res);
|
||||
res.write(`</div>${htmlEnd}`);
|
||||
},
|
||||
onShellError(error) {
|
||||
console.error(error);
|
||||
res.statusCode = 500;
|
||||
res.send('<!doctype html><h1>Server Error</h1>');
|
||||
},
|
||||
onError(error) {
|
||||
didError = true;
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -13,6 +13,12 @@ const moduleFederationConfig = {
|
||||
}
|
||||
_%>
|
||||
],
|
||||
shared: (libraryName, libraryConfig) => {
|
||||
return {
|
||||
...libraryConfig,
|
||||
eager: true
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -0,0 +1,66 @@
|
||||
const { NxAppRspackPlugin } = require('@nx/rspack/app-plugin');
|
||||
const { NxReactRspackPlugin } = require('@nx/rspack/react-plugin');
|
||||
const { NxModuleFederationPlugin, NxModuleFederationSSRDevServerPlugin } = require('@nx/module-federation/rspack');
|
||||
const { join } = require('path');
|
||||
|
||||
const browserMfConfig = require('./module-federation.config');
|
||||
const serverMfConfig = require('./module-federation.server.config');
|
||||
|
||||
const browserRspackConfig = {
|
||||
name: 'browser',
|
||||
output: {
|
||||
path: join(__dirname, '<%= rspackPluginOptions.outputPath %>', 'browser'),
|
||||
publicPath: 'auto'
|
||||
},
|
||||
devServer: {
|
||||
port: <%= devServerPort %>,
|
||||
historyApiFallback: {
|
||||
index: '/index.html',
|
||||
disableDotRule: true,
|
||||
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
|
||||
},
|
||||
devMiddleware: {
|
||||
writeToDisk: (file: string) => !file.includes('.hot-update.'),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new NxAppRspackPlugin({
|
||||
tsConfig: '<%= rspackPluginOptions.tsConfig %>',
|
||||
main: '<%= rspackPluginOptions.main %>',
|
||||
index: '<%= rspackPluginOptions.index %>',
|
||||
baseHref: '<%= rspackPluginOptions.baseHref %>',
|
||||
assets: <%- JSON.stringify(rspackPluginOptions.assets) %>,
|
||||
styles: <%- JSON.stringify(rspackPluginOptions.styles) %>,
|
||||
outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none',
|
||||
optimization: process.env['NODE_ENV'] === 'production',
|
||||
}),
|
||||
new NxReactRspackPlugin({
|
||||
// Uncomment this line if you don't want to use SVGR
|
||||
// See: https://react-svgr.com/
|
||||
// svgr: false
|
||||
}),
|
||||
new NxModuleFederationPlugin({ config: browserMfConfig }, { dts: false }),
|
||||
],
|
||||
};
|
||||
|
||||
const serverRspackConfig = {
|
||||
name: 'server',
|
||||
target: 'async-node',
|
||||
output: {
|
||||
path: join(__dirname, '<%= rspackPluginOptions.outputPath %>', 'server'),
|
||||
filename: 'server.js'
|
||||
},
|
||||
plugins: [
|
||||
new NxAppRspackPlugin({
|
||||
outputPath: join(__dirname, '<%= rspackPluginOptions.outputPath %>', 'server'),
|
||||
outputFileName: 'server.js',
|
||||
tsConfig: '<%= rspackPluginOptions.tsConfig %>',
|
||||
main: '<%= rspackPluginOptions.mainServer %>',
|
||||
baseHref: '<%= rspackPluginOptions.baseHref %>',
|
||||
}),
|
||||
new NxModuleFederationPlugin({ config: serverMfConfig, isServer: true }, { dts: false }),
|
||||
new NxModuleFederationSSRDevServerPlugin({ config: serverMfConfig }),
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = [browserRspackConfig, serverRspackConfig];
|
||||
@ -1,16 +0,0 @@
|
||||
const {composePlugins, withNx, withReact} = require('@nx/rspack');
|
||||
const {withModuleFederationForSSR} = require('@nx/module-federation/rspack');
|
||||
|
||||
const baseConfig = require('./module-federation.config');
|
||||
|
||||
const defaultConfig = {
|
||||
...baseConfig
|
||||
};
|
||||
|
||||
// Nx plugins for rspack to build config object from Nx options and context.
|
||||
/**
|
||||
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support for Module Federation
|
||||
* The DTS Plugin can be enabled by setting dts: true
|
||||
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
|
||||
*/
|
||||
module.exports = composePlugins(withNx(), withReact({ssr: true}), withModuleFederationForSSR(defaultConfig, { dts: false }));
|
||||
@ -7,7 +7,7 @@ import { handleRequest } from './src/main.server';
|
||||
const port = process.env['PORT'] || <%= port %>;
|
||||
const app = express();
|
||||
|
||||
const browserDist = path.join(process.cwd(), '<%= browserBuildOutputPath %>');
|
||||
const browserDist = path.join(process.cwd(), '<%= rspackPluginOptions.outputPath %>', 'browser');
|
||||
const indexPath = path.join(browserDist, 'index.html');
|
||||
|
||||
app.use(cors());
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
import type { Request, Response } from 'express';
|
||||
import * as fs from 'fs';
|
||||
import * as ReactDOMServer from 'react-dom/server';
|
||||
import isbot from 'isbot'
|
||||
|
||||
import App from './app/app';
|
||||
|
||||
import { StaticRouter } from 'react-router-dom/server';
|
||||
|
||||
|
||||
let indexHtml: null | string = null;
|
||||
|
||||
export function handleRequest(indexPath: string) {
|
||||
return function render(req: Request, res: Response) {
|
||||
let didError = false;
|
||||
|
||||
if (!indexHtml) {
|
||||
indexHtml = fs.readFileSync(indexPath).toString();
|
||||
}
|
||||
|
||||
const [htmlStart, htmlEnd] = indexHtml.split(`<div id="root"></div>`);
|
||||
|
||||
// For bots (e.g. search engines), the content will not be streamed but render all at once.
|
||||
// For users, content should be streamed to the user as they are ready.
|
||||
const callbackName = isbot(req.headers['user-agent']) ? 'onAllReady' : 'onShellReady';
|
||||
|
||||
const stream = ReactDOMServer.renderToPipeableStream(
|
||||
<StaticRouter location={req.originalUrl}><App /></StaticRouter>,
|
||||
{
|
||||
[callbackName]() {
|
||||
res.statusCode = didError ? 500 : 200;
|
||||
res.setHeader('Content-type', 'text/html; charset=utf-8');
|
||||
res.write(`${htmlStart}<div id="root">`);
|
||||
stream.pipe(res);
|
||||
res.write(`</div>${htmlEnd}`);
|
||||
},
|
||||
onShellError(error) {
|
||||
console.error(error);
|
||||
res.statusCode = 500;
|
||||
res.send('<!doctype html><h1>Server Error</h1>');
|
||||
},
|
||||
onError(error) {
|
||||
didError = true;
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
import { composePlugins, withNx, withReact } from '@nx/rspack';
|
||||
import { withModuleFederation } from '@nx/module-federation/rspack';
|
||||
import { NxAppRspackPlugin } from '@nx/rspack/app-plugin';
|
||||
import { NxReactRspackPlugin } from '@nx/rspack/react-plugin';
|
||||
import { NxModuleFederationPlugin, NxModuleFederationDevServerPlugin } from '@nx/module-federation/rspack';
|
||||
import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
import { join } from 'path';
|
||||
|
||||
import baseConfig from './module-federation.config';
|
||||
|
||||
@ -30,10 +32,36 @@ const prodConfig: ModuleFederationConfig = {
|
||||
],
|
||||
};
|
||||
|
||||
// Nx plugins for rspack to build config object from Nx options and context.
|
||||
/**
|
||||
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support for Module Federation
|
||||
* The DTS Plugin can be enabled by setting dts: true
|
||||
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
|
||||
*/
|
||||
export default composePlugins(withNx(), withReact(), withModuleFederation(prodConfig, { dts: false }));
|
||||
export default {
|
||||
output: {
|
||||
path: join(__dirname, '<%= rspackPluginOptions.outputPath %>'),
|
||||
publicPath: 'auto'
|
||||
},
|
||||
devServer: {
|
||||
port: <%= devServerPort %>,
|
||||
historyApiFallback: {
|
||||
index: '/index.html',
|
||||
disableDotRule: true,
|
||||
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new NxAppRspackPlugin({
|
||||
tsConfig: '<%= rspackPluginOptions.tsConfig %>',
|
||||
main: '<%= rspackPluginOptions.main %>',
|
||||
index: '<%= rspackPluginOptions.index %>',
|
||||
baseHref: '<%= rspackPluginOptions.baseHref %>',
|
||||
assets: <%- JSON.stringify(rspackPluginOptions.assets) %>,
|
||||
styles: <%- JSON.stringify(rspackPluginOptions.styles) %>,
|
||||
outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none',
|
||||
optimization: process.env['NODE_ENV'] === 'production',
|
||||
}),
|
||||
new NxReactRspackPlugin({
|
||||
// Uncomment this line if you don't want to use SVGR
|
||||
// See: https://react-svgr.com/
|
||||
// svgr: false
|
||||
}),
|
||||
new NxModuleFederationPlugin({ config: prodConfig }, { dts: false }),
|
||||
new NxModuleFederationDevServerPlugin({ config: prodConfig }),
|
||||
],
|
||||
};
|
||||
|
||||
@ -1,17 +1,40 @@
|
||||
import {composePlugins, withNx, withReact} from '@nx/rspack';
|
||||
import { withModuleFederation } from '@nx/module-federation/rspack';
|
||||
import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
import { NxAppRspackPlugin } from '@nx/rspack/app-plugin';
|
||||
import { NxReactRspackPlugin } from '@nx/rspack/react-plugin';
|
||||
import { NxModuleFederationPlugin, NxModuleFederationDevServerPlugin } from '@nx/module-federation/rspack';
|
||||
import { join } from 'path';
|
||||
|
||||
import baseConfig from './module-federation.config';
|
||||
import config from './module-federation.config';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
...baseConfig,
|
||||
export default {
|
||||
output: {
|
||||
path: join(__dirname, '<%= rspackPluginOptions.outputPath %>'),
|
||||
publicPath: 'auto'
|
||||
},
|
||||
devServer: {
|
||||
port: <%= devServerPort %>,
|
||||
historyApiFallback: {
|
||||
index: '/index.html',
|
||||
disableDotRule: true,
|
||||
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new NxAppRspackPlugin({
|
||||
tsConfig: '<%= rspackPluginOptions.tsConfig %>',
|
||||
main: '<%= rspackPluginOptions.main %>',
|
||||
index: '<%= rspackPluginOptions.index %>',
|
||||
baseHref: '<%= rspackPluginOptions.baseHref %>',
|
||||
assets: <%- JSON.stringify(rspackPluginOptions.assets) %>,
|
||||
styles: <%- JSON.stringify(rspackPluginOptions.styles) %>,
|
||||
outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none',
|
||||
optimization: process.env['NODE_ENV'] === 'production',
|
||||
}),
|
||||
new NxReactRspackPlugin({
|
||||
// Uncomment this line if you don't want to use SVGR
|
||||
// See: https://react-svgr.com/
|
||||
// svgr: false
|
||||
}),
|
||||
new NxModuleFederationPlugin({ config }, { dts: false }),
|
||||
new NxModuleFederationDevServerPlugin({ config }),
|
||||
],
|
||||
};
|
||||
|
||||
// Nx plugins for rspack to build config object from Nx options and context.
|
||||
/**
|
||||
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support for Module Federation
|
||||
* The DTS Plugin can be enabled by setting dts: true
|
||||
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
|
||||
*/
|
||||
export default composePlugins(withNx(), withReact(), withModuleFederation(config, { dts: false }));
|
||||
|
||||
@ -1,16 +1,40 @@
|
||||
const { composePlugins, withNx, withReact } = require('@nx/rspack');
|
||||
const { withModuleFederation } = require('@nx/module-federation/rspack');
|
||||
const { NxAppRspackPlugin } = require('@nx/rspack/app-plugin');
|
||||
const { NxReactRspackPlugin } = require('@nx/rspack/react-plugin');
|
||||
const { NxModuleFederationPlugin, NxModuleFederationDevServerPlugin } = require('@nx/module-federation/rspack');
|
||||
const { join } = require('path');
|
||||
|
||||
const baseConfig = require('./module-federation.config');
|
||||
const config = require('./module-federation.config');
|
||||
|
||||
const config = {
|
||||
...baseConfig,
|
||||
module.exports = {
|
||||
output: {
|
||||
path: join(__dirname, '<%= rspackPluginOptions.outputPath %>'),
|
||||
publicPath: 'auto'
|
||||
},
|
||||
devServer: {
|
||||
port: <%= devServerPort %>,
|
||||
historyApiFallback: {
|
||||
index: '/index.html',
|
||||
disableDotRule: true,
|
||||
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new NxAppRspackPlugin({
|
||||
tsConfig: '<%= rspackPluginOptions.tsConfig %>',
|
||||
main: '<%= rspackPluginOptions.main %>',
|
||||
index: '<%= rspackPluginOptions.index %>',
|
||||
baseHref: '<%= rspackPluginOptions.baseHref %>',
|
||||
assets: <%- JSON.stringify(rspackPluginOptions.assets) %>,
|
||||
styles: <%- JSON.stringify(rspackPluginOptions.styles) %>,
|
||||
outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none',
|
||||
optimization: process.env['NODE_ENV'] === 'production',
|
||||
}),
|
||||
new NxReactRspackPlugin({
|
||||
// Uncomment this line if you don't want to use SVGR
|
||||
// See: https://react-svgr.com/
|
||||
// svgr: false
|
||||
}),
|
||||
new NxModuleFederationPlugin({ config }, { dts: false }),
|
||||
new NxModuleFederationDevServerPlugin({ config }),
|
||||
],
|
||||
};
|
||||
|
||||
// Nx plugins for rspack to build config object from Nx options and context.
|
||||
/**
|
||||
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support for Module Federation
|
||||
* The DTS Plugin can be enabled by setting dts: true
|
||||
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
|
||||
*/
|
||||
module.exports = composePlugins(withNx(), withReact(), withModuleFederation(config, { dts: false }));
|
||||
|
||||
@ -1,38 +1,66 @@
|
||||
const { composePlugins, withNx, withReact } = require('@nx/rspack');
|
||||
const { withModuleFederation } = require('@nx/module-federation/rspack');
|
||||
const { NxAppRspackPlugin } = require('@nx/rspack/app-plugin');
|
||||
const { NxReactRspackPlugin } = require('@nx/rspack/react-plugin');
|
||||
const { NxModuleFederationPlugin, NxModuleFederationDevServerPlugin } = require('@nx/module-federation/rspack');
|
||||
const { join } = require('path');
|
||||
|
||||
const baseConfig = require('./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 %>/'],
|
||||
<%_ }); _%>
|
||||
],
|
||||
...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 rspack to build config object from Nx options and context.
|
||||
/**
|
||||
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support for Module Federation
|
||||
* The DTS Plugin can be enabled by setting dts: true
|
||||
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
|
||||
*/
|
||||
module.exports = composePlugins(withNx(), withReact(), withModuleFederation(prodConfig, { dts: false }));
|
||||
module.exports = {
|
||||
output: {
|
||||
path: join(__dirname, '<%= rspackPluginOptions.outputPath %>'),
|
||||
publicPath: 'auto'
|
||||
},
|
||||
devServer: {
|
||||
port: <%= devServerPort %>,
|
||||
historyApiFallback: {
|
||||
index: '/index.html',
|
||||
disableDotRule: true,
|
||||
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new NxAppRspackPlugin({
|
||||
tsConfig: '<%= rspackPluginOptions.tsConfig %>',
|
||||
main: '<%= rspackPluginOptions.main %>',
|
||||
index: '<%= rspackPluginOptions.index %>',
|
||||
baseHref: '<%= rspackPluginOptions.baseHref %>',
|
||||
assets: <%- JSON.stringify(rspackPluginOptions.assets) %>,
|
||||
styles: <%- JSON.stringify(rspackPluginOptions.styles) %>,
|
||||
outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none',
|
||||
optimization: process.env['NODE_ENV'] === 'production',
|
||||
}),
|
||||
new NxReactRspackPlugin({
|
||||
// Uncomment this line if you don't want to use SVGR
|
||||
// See: https://react-svgr.com/
|
||||
// svgr: false
|
||||
}),
|
||||
new NxModuleFederationPlugin({ config: prodConfig }, { dts: false }),
|
||||
new NxModuleFederationDevServerPlugin({ config: prodConfig }),
|
||||
],
|
||||
};
|
||||
|
||||
@ -226,7 +226,6 @@ describe('hostGenerator', () => {
|
||||
|
||||
expect(tree.exists('test/tsconfig.json')).toBeTruthy();
|
||||
expect(tree.exists('test/rspack.config.prod.js')).toBeTruthy();
|
||||
expect(tree.exists('test/rspack.server.config.js')).toBeTruthy();
|
||||
expect(tree.exists('test/rspack.config.js')).toBeTruthy();
|
||||
expect(tree.exists('test/module-federation.config.js')).toBeTruthy();
|
||||
expect(
|
||||
@ -250,9 +249,6 @@ describe('hostGenerator', () => {
|
||||
include: ['src/remotes.d.ts', 'src/main.server.tsx', 'server.ts'],
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read('test/rspack.server.config.js', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
expect(
|
||||
tree.read('test/module-federation.server.config.js', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
@ -272,7 +268,6 @@ describe('hostGenerator', () => {
|
||||
|
||||
expect(tree.exists('test/tsconfig.json')).toBeTruthy();
|
||||
expect(tree.exists('test/rspack.config.prod.ts')).toBeTruthy();
|
||||
expect(tree.exists('test/rspack.server.config.ts')).toBeTruthy();
|
||||
expect(tree.exists('test/rspack.config.ts')).toBeTruthy();
|
||||
expect(tree.exists('test/module-federation.config.ts')).toBeTruthy();
|
||||
expect(
|
||||
@ -296,9 +291,6 @@ describe('hostGenerator', () => {
|
||||
include: ['src/remotes.d.ts', 'src/main.server.tsx', 'server.ts'],
|
||||
});
|
||||
|
||||
expect(
|
||||
tree.read('test/rspack.server.config.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
expect(
|
||||
tree.read('test/module-federation.server.config.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
|
||||
@ -29,15 +29,18 @@ import {
|
||||
} from '../../utils/versions';
|
||||
import { ensureRootProjectName } from '@nx/devkit/src/generators/project-name-and-root-utils';
|
||||
import { updateModuleFederationTsconfig } from './lib/update-module-federation-tsconfig';
|
||||
import { normalizeHostName } from './lib/normalize-host-name';
|
||||
|
||||
export async function hostGenerator(
|
||||
host: Tree,
|
||||
schema: Schema
|
||||
): Promise<GeneratorCallback> {
|
||||
const tasks: GeneratorCallback[] = [];
|
||||
const name = await normalizeHostName(host, schema.directory, schema.name);
|
||||
const options: NormalizedSchema = {
|
||||
...(await normalizeOptions<Schema>(host, {
|
||||
...schema,
|
||||
name,
|
||||
useProjectJson: true,
|
||||
})),
|
||||
js: schema.js ?? false,
|
||||
@ -45,8 +48,8 @@ export async function hostGenerator(
|
||||
? false
|
||||
: schema.typescriptConfiguration ?? true,
|
||||
dynamic: schema.dynamic ?? false,
|
||||
// TODO(colum): remove when MF works with Crystal
|
||||
addPlugin: false,
|
||||
// TODO(colum): remove when Webpack MF works with Crystal
|
||||
addPlugin: !schema.bundler || schema.bundler === 'rspack' ? true : false,
|
||||
bundler: schema.bundler ?? 'rspack',
|
||||
};
|
||||
|
||||
@ -106,17 +109,19 @@ export async function hostGenerator(
|
||||
}
|
||||
|
||||
addModuleFederationFiles(host, options, remotesWithPorts);
|
||||
updateModuleFederationProject(host, options);
|
||||
updateModuleFederationProject(host, options, true);
|
||||
updateModuleFederationE2eProject(host, options);
|
||||
updateModuleFederationTsconfig(host, options);
|
||||
|
||||
if (options.ssr) {
|
||||
const setupSsrTask = await setupSsrGenerator(host, {
|
||||
project: options.projectName,
|
||||
serverPort: options.devServerPort,
|
||||
skipFormat: true,
|
||||
});
|
||||
tasks.push(setupSsrTask);
|
||||
if (options.bundler !== 'rspack') {
|
||||
const setupSsrTask = await setupSsrGenerator(host, {
|
||||
project: options.projectName,
|
||||
serverPort: options.devServerPort,
|
||||
skipFormat: true,
|
||||
});
|
||||
tasks.push(setupSsrTask);
|
||||
}
|
||||
|
||||
const setupSsrForHostTask = await setupSsrForHost(
|
||||
host,
|
||||
@ -127,14 +132,7 @@ export async function hostGenerator(
|
||||
tasks.push(setupSsrForHostTask);
|
||||
|
||||
const projectConfig = readProjectConfiguration(host, options.projectName);
|
||||
if (options.bundler === 'rspack') {
|
||||
projectConfig.targets.server.executor = '@nx/rspack:rspack';
|
||||
projectConfig.targets.server.options.rspackConfig = joinPathFragments(
|
||||
projectConfig.root,
|
||||
`rspack.server.config.${options.typescriptConfiguration ? 'ts' : 'js'}`
|
||||
);
|
||||
delete projectConfig.targets.server.options.webpackConfig;
|
||||
} else {
|
||||
if (options.bundler !== 'rspack') {
|
||||
projectConfig.targets.server.options.webpackConfig = joinPathFragments(
|
||||
projectConfig.root,
|
||||
`webpack.server.config.${options.typescriptConfiguration ? 'ts' : 'js'}`
|
||||
|
||||
@ -4,27 +4,52 @@ import {
|
||||
joinPathFragments,
|
||||
names,
|
||||
readProjectConfiguration,
|
||||
offsetFromRoot,
|
||||
} from '@nx/devkit';
|
||||
import { maybeJs } from '../../../utils/maybe-js';
|
||||
import { NormalizedSchema } from '../schema';
|
||||
import {
|
||||
createNxRspackPluginOptions,
|
||||
getDefaultTemplateVariables,
|
||||
} from '../../application/lib/create-application-files';
|
||||
|
||||
export function addModuleFederationFiles(
|
||||
host: Tree,
|
||||
options: NormalizedSchema,
|
||||
defaultRemoteManifest: { name: string; port: number }[]
|
||||
) {
|
||||
const templateVariables = {
|
||||
...names(options.projectName),
|
||||
...options,
|
||||
static: !options?.dynamic,
|
||||
tmpl: '',
|
||||
remotes: defaultRemoteManifest.map(({ name, port }) => {
|
||||
return {
|
||||
...names(name),
|
||||
port,
|
||||
};
|
||||
}),
|
||||
};
|
||||
const templateVariables =
|
||||
options.bundler === 'rspack'
|
||||
? {
|
||||
...getDefaultTemplateVariables(host, options as any),
|
||||
rspackPluginOptions: {
|
||||
...createNxRspackPluginOptions(
|
||||
options as any,
|
||||
offsetFromRoot(options.appProjectRoot),
|
||||
false
|
||||
),
|
||||
mainServer: `./server.ts`,
|
||||
},
|
||||
static: !options?.dynamic,
|
||||
remotes: defaultRemoteManifest.map(({ name, port }) => {
|
||||
return {
|
||||
...names(name),
|
||||
port,
|
||||
};
|
||||
}),
|
||||
}
|
||||
: {
|
||||
...names(options.projectName),
|
||||
...options,
|
||||
static: !options?.dynamic,
|
||||
tmpl: '',
|
||||
remotes: defaultRemoteManifest.map(({ name, port }) => {
|
||||
return {
|
||||
...names(name),
|
||||
port,
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
const projectConfig = readProjectConfiguration(host, options.projectName);
|
||||
const pathToMFManifest = joinPathFragments(
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
import { Tree } from '@nx/devkit';
|
||||
import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils';
|
||||
|
||||
export async function normalizeHostName(
|
||||
tree: Tree,
|
||||
directory: string,
|
||||
name?: string
|
||||
): Promise<string> {
|
||||
const { projectName } = await determineProjectNameAndRootOptions(tree, {
|
||||
name,
|
||||
directory,
|
||||
projectType: 'application',
|
||||
});
|
||||
return projectName;
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import type { GeneratorCallback, Tree } from '@nx/devkit';
|
||||
import { GeneratorCallback, offsetFromRoot, Tree } from '@nx/devkit';
|
||||
import {
|
||||
addDependenciesToPackageJson,
|
||||
generateFiles,
|
||||
@ -8,23 +8,32 @@ import {
|
||||
runTasksInSerial,
|
||||
updateProjectConfiguration,
|
||||
} from '@nx/devkit';
|
||||
|
||||
import type { Schema } from '../schema';
|
||||
import { moduleFederationNodeVersion } from '../../../utils/versions';
|
||||
import type { NormalizedSchema } from '../schema';
|
||||
import {
|
||||
corsVersion,
|
||||
expressVersion,
|
||||
isbotVersion,
|
||||
moduleFederationNodeVersion,
|
||||
typesExpressVersion,
|
||||
} from '../../../utils/versions';
|
||||
import {
|
||||
createNxRspackPluginOptions,
|
||||
getDefaultTemplateVariables,
|
||||
} from '../../application/lib/create-application-files';
|
||||
|
||||
export async function setupSsrForHost(
|
||||
tree: Tree,
|
||||
options: Schema,
|
||||
options: NormalizedSchema,
|
||||
appName: string,
|
||||
defaultRemoteManifest: { name: string; port: number }[]
|
||||
) {
|
||||
const tasks: GeneratorCallback[] = [];
|
||||
let project = readProjectConfiguration(tree, appName);
|
||||
project.targets.serve.executor =
|
||||
options.bundler === 'rspack'
|
||||
? '@nx/rspack:module-federation-ssr-dev-server'
|
||||
: '@nx/react:module-federation-ssr-dev-server';
|
||||
updateProjectConfiguration(tree, appName, project);
|
||||
if (options.bundler !== 'rspack') {
|
||||
project.targets.serve.executor =
|
||||
'@nx/react:module-federation-ssr-dev-server';
|
||||
updateProjectConfiguration(tree, appName, project);
|
||||
}
|
||||
|
||||
const pathToModuleFederationSsrFiles = options.typescriptConfiguration
|
||||
? `${
|
||||
@ -34,30 +43,58 @@ export async function setupSsrForHost(
|
||||
options.bundler === 'rspack' ? 'rspack-' : 'webpack-'
|
||||
}module-federation-ssr`;
|
||||
|
||||
const templateVariables =
|
||||
options.bundler === 'rspack'
|
||||
? {
|
||||
...getDefaultTemplateVariables(tree, options as any),
|
||||
rspackPluginOptions: {
|
||||
...createNxRspackPluginOptions(
|
||||
options as any,
|
||||
offsetFromRoot(options.appProjectRoot),
|
||||
false
|
||||
),
|
||||
mainServer: `./server.ts`,
|
||||
},
|
||||
port: Number(options?.devServerPort) || 4200,
|
||||
appName,
|
||||
static: !options?.dynamic,
|
||||
remotes: defaultRemoteManifest.map(({ name, port }) => {
|
||||
return {
|
||||
...names(name),
|
||||
port,
|
||||
};
|
||||
}),
|
||||
}
|
||||
: {
|
||||
...options,
|
||||
static: !options?.dynamic,
|
||||
port: Number(options?.devServerPort) || 4200,
|
||||
appName,
|
||||
tmpl: '',
|
||||
browserBuildOutputPath: project.targets.build?.options?.outputPath,
|
||||
remotes: defaultRemoteManifest.map(({ name, port }) => {
|
||||
return {
|
||||
...names(name),
|
||||
port,
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
generateFiles(
|
||||
tree,
|
||||
joinPathFragments(__dirname, `../files/${pathToModuleFederationSsrFiles}`),
|
||||
project.root,
|
||||
{
|
||||
...options,
|
||||
static: !options?.dynamic,
|
||||
port: Number(options?.devServerPort) || 4200,
|
||||
remotes: defaultRemoteManifest.map(({ name, port }) => {
|
||||
return {
|
||||
...names(name),
|
||||
port,
|
||||
};
|
||||
}),
|
||||
appName,
|
||||
tmpl: '',
|
||||
browserBuildOutputPath: project.targets.build.options.outputPath,
|
||||
}
|
||||
templateVariables
|
||||
);
|
||||
|
||||
const installTask = addDependenciesToPackageJson(
|
||||
tree,
|
||||
{
|
||||
'@module-federation/node': moduleFederationNodeVersion,
|
||||
cors: corsVersion,
|
||||
isbot: isbotVersion,
|
||||
express: expressVersion,
|
||||
'@types/express': typesExpressVersion,
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
@ -1,22 +1,49 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`remote generator bundler=rspack should create the remote with the correct config files 1`] = `
|
||||
"const { composePlugins, withNx, withReact } = require('@nx/rspack');
|
||||
const { withModuleFederation } = require('@nx/module-federation/rspack');
|
||||
"const { NxAppRspackPlugin } = require('@nx/rspack/app-plugin');
|
||||
const { NxReactRspackPlugin } = require('@nx/rspack/react-plugin');
|
||||
const { NxModuleFederationPlugin, NxModuleFederationDevServerPlugin } = require('@nx/module-federation/rspack');
|
||||
const { join } = require('path');
|
||||
|
||||
const baseConfig = require('./module-federation.config');
|
||||
const config = require('./module-federation.config');
|
||||
|
||||
const config = {
|
||||
...baseConfig,
|
||||
module.exports = {
|
||||
output: {
|
||||
path: join(__dirname, '../dist/test'),
|
||||
publicPath: 'auto'
|
||||
},
|
||||
devServer: {
|
||||
port: 4201,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
historyApiFallback: {
|
||||
index: '/index.html',
|
||||
disableDotRule: true,
|
||||
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new NxAppRspackPlugin({
|
||||
tsConfig: './tsconfig.app.json',
|
||||
main: './src/main.ts',
|
||||
index: './src/index.html',
|
||||
baseHref: '/',
|
||||
assets: ["./src/favicon.ico","./src/assets"],
|
||||
styles: ["./src/styles.css"],
|
||||
outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none',
|
||||
optimization: process.env['NODE_ENV'] === 'production',
|
||||
}),
|
||||
new NxReactRspackPlugin({
|
||||
// Uncomment this line if you don't want to use SVGR
|
||||
// See: https://react-svgr.com/
|
||||
// svgr: false
|
||||
}),
|
||||
new NxModuleFederationPlugin({ config }, { dts: false }),
|
||||
new NxModuleFederationDevServerPlugin({ config }),
|
||||
],
|
||||
};
|
||||
|
||||
// Nx plugins for rspack to build config object from Nx options and context.
|
||||
/**
|
||||
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support Module Federation
|
||||
* The DTS Plugin can be enabled by setting dts: true
|
||||
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
|
||||
*/
|
||||
module.exports = composePlugins(withNx(), withReact(), withModuleFederation(config, { dts: false }));
|
||||
"
|
||||
`;
|
||||
|
||||
@ -36,22 +63,49 @@ module.exports = {
|
||||
`;
|
||||
|
||||
exports[`remote generator bundler=rspack should create the remote with the correct config files when --js=true 1`] = `
|
||||
"const { composePlugins, withNx, withReact } = require('@nx/rspack');
|
||||
const { withModuleFederation } = require('@nx/module-federation/rspack');
|
||||
"const { NxAppRspackPlugin } = require('@nx/rspack/app-plugin');
|
||||
const { NxReactRspackPlugin } = require('@nx/rspack/react-plugin');
|
||||
const { NxModuleFederationPlugin, NxModuleFederationDevServerPlugin } = require('@nx/module-federation/rspack');
|
||||
const { join } = require('path');
|
||||
|
||||
const baseConfig = require('./module-federation.config');
|
||||
const config = require('./module-federation.config');
|
||||
|
||||
const config = {
|
||||
...baseConfig,
|
||||
module.exports = {
|
||||
output: {
|
||||
path: join(__dirname, '../dist/test'),
|
||||
publicPath: 'auto'
|
||||
},
|
||||
devServer: {
|
||||
port: 4201,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
historyApiFallback: {
|
||||
index: '/index.html',
|
||||
disableDotRule: true,
|
||||
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new NxAppRspackPlugin({
|
||||
tsConfig: './tsconfig.app.json',
|
||||
main: './src/main.jsx',
|
||||
index: './src/index.html',
|
||||
baseHref: '/',
|
||||
assets: ["./src/favicon.ico","./src/assets"],
|
||||
styles: ["./src/styles.css"],
|
||||
outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none',
|
||||
optimization: process.env['NODE_ENV'] === 'production',
|
||||
}),
|
||||
new NxReactRspackPlugin({
|
||||
// Uncomment this line if you don't want to use SVGR
|
||||
// See: https://react-svgr.com/
|
||||
// svgr: false
|
||||
}),
|
||||
new NxModuleFederationPlugin({ config }, { dts: false }),
|
||||
new NxModuleFederationDevServerPlugin({ config }),
|
||||
],
|
||||
};
|
||||
|
||||
// Nx plugins for rspack to build config object from Nx options and context.
|
||||
/**
|
||||
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support Module Federation
|
||||
* The DTS Plugin can be enabled by setting dts: true
|
||||
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
|
||||
*/
|
||||
module.exports = composePlugins(withNx(), withReact(), withModuleFederation(config, { dts: false }));
|
||||
"
|
||||
`;
|
||||
|
||||
@ -71,26 +125,52 @@ module.exports = {
|
||||
`;
|
||||
|
||||
exports[`remote generator bundler=rspack should create the remote with the correct config files when --typescriptConfiguration=true 1`] = `
|
||||
"import { composePlugins, withNx, withReact } from '@nx/rspack';
|
||||
import { withModuleFederation } from '@nx/module-federation/rspack';
|
||||
"import { NxAppRspackPlugin } from '@nx/rspack/app-plugin';
|
||||
import { NxReactRspackPlugin } from '@nx/rspack/react-plugin';
|
||||
import {
|
||||
NxModuleFederationPlugin,
|
||||
NxModuleFederationDevServerPlugin,
|
||||
} from '@nx/module-federation/rspack';
|
||||
import { join } from 'path';
|
||||
|
||||
import baseConfig from './module-federation.config';
|
||||
import config from './module-federation.config';
|
||||
|
||||
const config = {
|
||||
...baseConfig,
|
||||
export default {
|
||||
output: {
|
||||
path: join(__dirname, '../dist/test'),
|
||||
publicPath: 'auto',
|
||||
},
|
||||
devServer: {
|
||||
port: 4201,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
historyApiFallback: {
|
||||
index: '/index.html',
|
||||
disableDotRule: true,
|
||||
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new NxAppRspackPlugin({
|
||||
tsConfig: './tsconfig.app.json',
|
||||
main: './src/main.ts',
|
||||
index: './src/index.html',
|
||||
baseHref: '/',
|
||||
assets: ['./src/favicon.ico', './src/assets'],
|
||||
styles: ['./src/styles.css'],
|
||||
outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none',
|
||||
optimization: process.env['NODE_ENV'] === 'production',
|
||||
}),
|
||||
new NxReactRspackPlugin({
|
||||
// Uncomment this line if you don't want to use SVGR
|
||||
// See: https://react-svgr.com/
|
||||
// svgr: false
|
||||
}),
|
||||
new NxModuleFederationPlugin({ config }, { dts: false }),
|
||||
new NxModuleFederationDevServerPlugin({ config }),
|
||||
],
|
||||
};
|
||||
|
||||
// Nx plugins for rspack to build config object from Nx options and context.
|
||||
/**
|
||||
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support Module Federation
|
||||
* The DTS Plugin can be enabled by setting dts: true
|
||||
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
|
||||
*/
|
||||
export default composePlugins(
|
||||
withNx(),
|
||||
withReact(),
|
||||
withModuleFederation(config, { dts: false })
|
||||
);
|
||||
"
|
||||
`;
|
||||
|
||||
@ -117,26 +197,6 @@ export default config;
|
||||
`;
|
||||
|
||||
exports[`remote generator bundler=rspack should generate correct remote with config files when using --ssr 1`] = `
|
||||
"const {composePlugins, withNx, withReact} = require('@nx/rspack');
|
||||
const {withModuleFederationForSSR} = require('@nx/module-federation/rspack');
|
||||
|
||||
const baseConfig = require("./module-federation.server.config");
|
||||
|
||||
const defaultConfig = {
|
||||
...baseConfig,
|
||||
};
|
||||
|
||||
// Nx plugins for rspack to build config object from Nx options and context.
|
||||
/**
|
||||
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support Module Federation
|
||||
* The DTS Plugin can be enabled by setting dts: true
|
||||
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
|
||||
*/
|
||||
module.exports = composePlugins(withNx(), withReact({ssr: true}), withModuleFederationForSSR(defaultConfig, { dts: false }));
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`remote generator bundler=rspack should generate correct remote with config files when using --ssr 2`] = `
|
||||
"/**
|
||||
* Nx requires a default export of the config to allow correct resolution of the module federation graph.
|
||||
**/
|
||||
@ -145,35 +205,17 @@ module.exports = {
|
||||
exposes: {
|
||||
'./Module': './src/remote-entry.ts',
|
||||
},
|
||||
shared: (libraryName, libraryConfig) => {
|
||||
return {
|
||||
...libraryConfig,
|
||||
eager: true
|
||||
}
|
||||
},
|
||||
};
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`remote generator bundler=rspack should generate correct remote with config files when using --ssr and --typescriptConfiguration=true 1`] = `
|
||||
"import { composePlugins, withNx, withReact } from '@nx/rspack';
|
||||
import { withModuleFederationForSSR } from '@nx/module-federation/rspack';
|
||||
|
||||
import baseConfig from './module-federation.server.config';
|
||||
|
||||
const defaultConfig = {
|
||||
...baseConfig,
|
||||
};
|
||||
|
||||
// Nx plugins for rspack to build config object from Nx options and context.
|
||||
/**
|
||||
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support Module Federation
|
||||
* The DTS Plugin can be enabled by setting dts: true
|
||||
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
|
||||
*/
|
||||
export default composePlugins(
|
||||
withNx(),
|
||||
withReact({ ssr: true }),
|
||||
withModuleFederationForSSR(defaultConfig, { dts: false })
|
||||
);
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`remote generator bundler=rspack should generate correct remote with config files when using --ssr and --typescriptConfiguration=true 2`] = `
|
||||
"import { ModuleFederationConfig } from '@nx/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
@ -181,6 +223,12 @@ const config: ModuleFederationConfig = {
|
||||
exposes: {
|
||||
'./Module': './src/remote-entry.ts',
|
||||
},
|
||||
shared: (libraryName, libraryConfig) => {
|
||||
return {
|
||||
...libraryConfig,
|
||||
eager: true,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -5,6 +5,12 @@ const config: ModuleFederationConfig = {
|
||||
exposes: {
|
||||
'./Module': './src/remote-entry.<%= js ? 'js' : 'ts' %>',
|
||||
},
|
||||
shared: (libraryName, libraryConfig) => {
|
||||
return {
|
||||
...libraryConfig,
|
||||
eager: true
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -0,0 +1,69 @@
|
||||
import { NxAppRspackPlugin } from '@nx/rspack/app-plugin';
|
||||
import { NxReactRspackPlugin } from '@nx/rspack/react-plugin';
|
||||
import { NxModuleFederationPlugin, NxModuleFederationSSRDevServerPlugin } from '@nx/module-federation/rspack';
|
||||
import { join } from 'path';
|
||||
|
||||
import browserMfConfig from './module-federation.config';
|
||||
import serverMfConfig from './module-federation.server.config';
|
||||
|
||||
const browserRspackConfig = {
|
||||
name: 'browser',
|
||||
output: {
|
||||
path: join(__dirname, '<%= rspackPluginOptions.outputPath %>', 'browser'),
|
||||
publicPath: 'auto'
|
||||
},
|
||||
devServer: {
|
||||
port: <%= devServerPort %>,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
historyApiFallback: {
|
||||
index: '/index.html',
|
||||
disableDotRule: true,
|
||||
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
|
||||
},
|
||||
devMiddleware: {
|
||||
writeToDisk: (file: string) => !file.includes('.hot-update.'),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new NxAppRspackPlugin({
|
||||
tsConfig: '<%= rspackPluginOptions.tsConfig %>',
|
||||
main: '<%= rspackPluginOptions.main %>',
|
||||
index: '<%= rspackPluginOptions.index %>',
|
||||
baseHref: '<%= rspackPluginOptions.baseHref %>',
|
||||
assets: <%- JSON.stringify(rspackPluginOptions.assets) %>,
|
||||
styles: <%- JSON.stringify(rspackPluginOptions.styles) %>,
|
||||
outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none',
|
||||
optimization: process.env['NODE_ENV'] === 'production',
|
||||
}),
|
||||
new NxReactRspackPlugin({
|
||||
// Uncomment this line if you don't want to use SVGR
|
||||
// See: https://react-svgr.com/
|
||||
// svgr: false
|
||||
}),
|
||||
new NxModuleFederationPlugin({ config: browserMfConfig }, { dts: false }),
|
||||
],
|
||||
};
|
||||
|
||||
const serverRspackConfig = {
|
||||
name: 'server',
|
||||
target: 'async-node',
|
||||
output: {
|
||||
path: join(__dirname, '<%= rspackPluginOptions.outputPath %>', 'server'),
|
||||
filename: 'server.js'
|
||||
},
|
||||
plugins: [
|
||||
new NxAppRspackPlugin({
|
||||
outputPath: join(__dirname, '<%= rspackPluginOptions.outputPath %>', 'server'),
|
||||
outputFileName: 'server.js',
|
||||
tsConfig: '<%= rspackPluginOptions.tsConfig %>',
|
||||
main: '<%= rspackPluginOptions.mainServer %>',
|
||||
baseHref: '<%= rspackPluginOptions.baseHref %>',
|
||||
}),
|
||||
new NxModuleFederationPlugin({ config: serverMfConfig, isServer: true }, { dts: false }),
|
||||
new NxModuleFederationSSRDevServerPlugin({ config: serverMfConfig }),
|
||||
],
|
||||
};
|
||||
|
||||
export default [browserRspackConfig, serverRspackConfig];
|
||||
@ -1,16 +0,0 @@
|
||||
import {composePlugins, withNx, withReact} from '@nx/rspack';
|
||||
import {withModuleFederationForSSR} from '@nx/module-federation/rspack';
|
||||
|
||||
import baseConfig from "./module-federation.server.config";
|
||||
|
||||
const defaultConfig = {
|
||||
...baseConfig,
|
||||
};
|
||||
|
||||
// Nx plugins for rspack to build config object from Nx options and context.
|
||||
/**
|
||||
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support Module Federation
|
||||
* The DTS Plugin can be enabled by setting dts: true
|
||||
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
|
||||
*/
|
||||
export default composePlugins(withNx(), withReact({ssr: true}), withModuleFederationForSSR(defaultConfig, { dts: false }));
|
||||
@ -7,8 +7,8 @@ import { handleRequest } from './src/main.server';
|
||||
const port = process.env['PORT'] || <%= port %>;
|
||||
const app = express();
|
||||
|
||||
const browserDist = path.join(process.cwd(), '<%= browserBuildOutputPath %>');
|
||||
const serverDist = path.join(process.cwd(), '<%= serverBuildOutputPath %>');
|
||||
const browserDist = path.join(process.cwd(), '<%= rspackPluginOptions.outputPath %>', 'browser');
|
||||
const serverDist = path.join(process.cwd(), '<%= rspackPluginOptions.outputPath %>', 'server');
|
||||
const indexPath = path.join(browserDist, 'index.html');
|
||||
|
||||
app.use(cors());
|
||||
|
||||
@ -0,0 +1,45 @@
|
||||
import type { Request, Response } from 'express';
|
||||
import * as fs from 'fs';
|
||||
import * as ReactDOMServer from 'react-dom/server';
|
||||
import isbot from 'isbot';
|
||||
|
||||
import App from './app/app';
|
||||
|
||||
let indexHtml: null | string = null;
|
||||
|
||||
export function handleRequest(indexPath: string) {
|
||||
return function render(req: Request, res: Response) {
|
||||
let didError = false;
|
||||
|
||||
if (!indexHtml) {
|
||||
indexHtml = fs.readFileSync(indexPath).toString();
|
||||
}
|
||||
|
||||
const [htmlStart, htmlEnd] = indexHtml.split(`<div id="root"></div>`);
|
||||
|
||||
// For bots (e.g. search engines), the content will not be streamed but render all at once.
|
||||
// For users, content should be streamed to the user as they are ready.
|
||||
const callbackName = isbot(req.headers['user-agent'])
|
||||
? 'onAllReady'
|
||||
: 'onShellReady';
|
||||
|
||||
const stream = ReactDOMServer.renderToPipeableStream(<App />, {
|
||||
[callbackName]() {
|
||||
res.statusCode = didError ? 500 : 200;
|
||||
res.setHeader('Content-type', 'text/html; charset=utf-8');
|
||||
res.write(`${htmlStart}<div id="root">`);
|
||||
stream.pipe(res);
|
||||
res.write(`</div>${htmlEnd}`);
|
||||
},
|
||||
onShellError(error) {
|
||||
console.error(error);
|
||||
res.statusCode = 500;
|
||||
res.send('<!doctype html><h1>Server Error</h1>');
|
||||
},
|
||||
onError(error) {
|
||||
didError = true;
|
||||
console.error(error);
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
@ -6,4 +6,10 @@ module.exports = {
|
||||
exposes: {
|
||||
'./Module': './src/remote-entry.<%= js ? 'js' : 'ts' %>',
|
||||
},
|
||||
shared: (libraryName, libraryConfig) => {
|
||||
return {
|
||||
...libraryConfig,
|
||||
eager: true
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@ -0,0 +1,69 @@
|
||||
const { NxAppRspackPlugin } = require('@nx/rspack/app-plugin');
|
||||
const { NxReactRspackPlugin } = require('@nx/rspack/react-plugin');
|
||||
const { NxModuleFederationPlugin, NxModuleFederationSSRDevServerPlugin } = require('@nx/module-federation/rspack');
|
||||
const { join } = require('path');
|
||||
|
||||
const browserMfConfig = require('./module-federation.config');
|
||||
const serverMfConfig = require('./module-federation.server.config');
|
||||
|
||||
const browserRspackConfig = {
|
||||
name: 'browser',
|
||||
output: {
|
||||
path: join(__dirname, '<%= rspackPluginOptions.outputPath %>', 'browser'),
|
||||
publicPath: 'auto'
|
||||
},
|
||||
devServer: {
|
||||
port: <%= devServerPort %>,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
historyApiFallback: {
|
||||
index: '/index.html',
|
||||
disableDotRule: true,
|
||||
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
|
||||
},
|
||||
devMiddleware: {
|
||||
writeToDisk: (file: string) => !file.includes('.hot-update.'),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new NxAppRspackPlugin({
|
||||
tsConfig: '<%= rspackPluginOptions.tsConfig %>',
|
||||
main: '<%= rspackPluginOptions.main %>',
|
||||
index: '<%= rspackPluginOptions.index %>',
|
||||
baseHref: '<%= rspackPluginOptions.baseHref %>',
|
||||
assets: <%- JSON.stringify(rspackPluginOptions.assets) %>,
|
||||
styles: <%- JSON.stringify(rspackPluginOptions.styles) %>,
|
||||
outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none',
|
||||
optimization: process.env['NODE_ENV'] === 'production',
|
||||
}),
|
||||
new NxReactRspackPlugin({
|
||||
// Uncomment this line if you don't want to use SVGR
|
||||
// See: https://react-svgr.com/
|
||||
// svgr: false
|
||||
}),
|
||||
new NxModuleFederationPlugin({ config: browserMfConfig }, { dts: false }),
|
||||
],
|
||||
};
|
||||
|
||||
const serverRspackConfig = {
|
||||
name: 'server',
|
||||
target: 'async-node',
|
||||
output: {
|
||||
path: join(__dirname, '<%= rspackPluginOptions.outputPath %>', 'server'),
|
||||
filename: 'server.js'
|
||||
},
|
||||
plugins: [
|
||||
new NxAppRspackPlugin({
|
||||
outputPath: join(__dirname, '<%= rspackPluginOptions.outputPath %>', 'server'),
|
||||
outputFileName: 'server.js',
|
||||
tsConfig: '<%= rspackPluginOptions.tsConfig %>',
|
||||
main: '<%= rspackPluginOptions.mainServer %>',
|
||||
baseHref: '<%= rspackPluginOptions.baseHref %>',
|
||||
}),
|
||||
new NxModuleFederationPlugin({ config: serverMfConfig, isServer: true }, { dts: false }),
|
||||
new NxModuleFederationSSRDevServerPlugin({ config: serverMfConfig }),
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = [browserRspackConfig, serverRspackConfig];
|
||||
@ -1,16 +0,0 @@
|
||||
const {composePlugins, withNx, withReact} = require('@nx/rspack');
|
||||
const {withModuleFederationForSSR} = require('@nx/module-federation/rspack');
|
||||
|
||||
const baseConfig = require("./module-federation.server.config");
|
||||
|
||||
const defaultConfig = {
|
||||
...baseConfig,
|
||||
};
|
||||
|
||||
// Nx plugins for rspack to build config object from Nx options and context.
|
||||
/**
|
||||
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support Module Federation
|
||||
* The DTS Plugin can be enabled by setting dts: true
|
||||
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
|
||||
*/
|
||||
module.exports = composePlugins(withNx(), withReact({ssr: true}), withModuleFederationForSSR(defaultConfig, { dts: false }));
|
||||
@ -7,8 +7,8 @@ import { handleRequest } from './src/main.server';
|
||||
const port = process.env['PORT'] || <%= port %>;
|
||||
const app = express();
|
||||
|
||||
const browserDist = path.join(process.cwd(), '<%= browserBuildOutputPath %>');
|
||||
const serverDist = path.join(process.cwd(), '<%= serverBuildOutputPath %>');
|
||||
const browserDist = path.join(process.cwd(), '<%= rspackPluginOptions.outputPath %>', 'browser');
|
||||
const serverDist = path.join(process.cwd(), '<%= rspackPluginOptions.outputPath %>', 'server');
|
||||
const indexPath = path.join(browserDist, 'index.html');
|
||||
|
||||
app.use(cors());
|
||||
|
||||
@ -0,0 +1,45 @@
|
||||
import type { Request, Response } from 'express';
|
||||
import * as fs from 'fs';
|
||||
import * as ReactDOMServer from 'react-dom/server';
|
||||
import isbot from 'isbot';
|
||||
|
||||
import App from './app/app';
|
||||
|
||||
let indexHtml: null | string = null;
|
||||
|
||||
export function handleRequest(indexPath: string) {
|
||||
return function render(req: Request, res: Response) {
|
||||
let didError = false;
|
||||
|
||||
if (!indexHtml) {
|
||||
indexHtml = fs.readFileSync(indexPath).toString();
|
||||
}
|
||||
|
||||
const [htmlStart, htmlEnd] = indexHtml.split(`<div id="root"></div>`);
|
||||
|
||||
// For bots (e.g. search engines), the content will not be streamed but render all at once.
|
||||
// For users, content should be streamed to the user as they are ready.
|
||||
const callbackName = isbot(req.headers['user-agent'])
|
||||
? 'onAllReady'
|
||||
: 'onShellReady';
|
||||
|
||||
const stream = ReactDOMServer.renderToPipeableStream(<App />, {
|
||||
[callbackName]() {
|
||||
res.statusCode = didError ? 500 : 200;
|
||||
res.setHeader('Content-type', 'text/html; charset=utf-8');
|
||||
res.write(`${htmlStart}<div id="root">`);
|
||||
stream.pipe(res);
|
||||
res.write(`</div>${htmlEnd}`);
|
||||
},
|
||||
onShellError(error) {
|
||||
console.error(error);
|
||||
res.statusCode = 500;
|
||||
res.send('<!doctype html><h1>Server Error</h1>');
|
||||
},
|
||||
onError(error) {
|
||||
didError = true;
|
||||
console.error(error);
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
@ -1,16 +1,43 @@
|
||||
import {composePlugins, withNx, withReact} from '@nx/rspack';
|
||||
import {withModuleFederation} from '@nx/module-federation/rspack';
|
||||
import { NxAppRspackPlugin } from '@nx/rspack/app-plugin';
|
||||
import { NxReactRspackPlugin } from '@nx/rspack/react-plugin';
|
||||
import { NxModuleFederationPlugin, NxModuleFederationDevServerPlugin } from '@nx/module-federation/rspack';
|
||||
import { join } from 'path';
|
||||
|
||||
import baseConfig from './module-federation.config';
|
||||
import config from './module-federation.config';
|
||||
|
||||
const config = {
|
||||
...baseConfig,
|
||||
export default {
|
||||
output: {
|
||||
path: join(__dirname, '<%= rspackPluginOptions.outputPath %>'),
|
||||
publicPath: 'auto'
|
||||
},
|
||||
devServer: {
|
||||
port: <%= devServerPort %>,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
historyApiFallback: {
|
||||
index: '/index.html',
|
||||
disableDotRule: true,
|
||||
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new NxAppRspackPlugin({
|
||||
tsConfig: '<%= rspackPluginOptions.tsConfig %>',
|
||||
main: '<%= rspackPluginOptions.main %>',
|
||||
index: '<%= rspackPluginOptions.index %>',
|
||||
baseHref: '<%= rspackPluginOptions.baseHref %>',
|
||||
assets: <%- JSON.stringify(rspackPluginOptions.assets) %>,
|
||||
styles: <%- JSON.stringify(rspackPluginOptions.styles) %>,
|
||||
outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none',
|
||||
optimization: process.env['NODE_ENV'] === 'production',
|
||||
}),
|
||||
new NxReactRspackPlugin({
|
||||
// Uncomment this line if you don't want to use SVGR
|
||||
// See: https://react-svgr.com/
|
||||
// svgr: false
|
||||
}),
|
||||
new NxModuleFederationPlugin({ config }, { dts: false }),
|
||||
new NxModuleFederationDevServerPlugin({ config }),
|
||||
],
|
||||
};
|
||||
|
||||
// Nx plugins for rspack to build config object from Nx options and context.
|
||||
/**
|
||||
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support Module Federation
|
||||
* The DTS Plugin can be enabled by setting dts: true
|
||||
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
|
||||
*/
|
||||
export default composePlugins(withNx(), withReact(), withModuleFederation(config, { dts: false }));
|
||||
|
||||
@ -1,16 +1,43 @@
|
||||
const { composePlugins, withNx, withReact } = require('@nx/rspack');
|
||||
const { withModuleFederation } = require('@nx/module-federation/rspack');
|
||||
const { NxAppRspackPlugin } = require('@nx/rspack/app-plugin');
|
||||
const { NxReactRspackPlugin } = require('@nx/rspack/react-plugin');
|
||||
const { NxModuleFederationPlugin, NxModuleFederationDevServerPlugin } = require('@nx/module-federation/rspack');
|
||||
const { join } = require('path');
|
||||
|
||||
const baseConfig = require('./module-federation.config');
|
||||
const config = require('./module-federation.config');
|
||||
|
||||
const config = {
|
||||
...baseConfig,
|
||||
module.exports = {
|
||||
output: {
|
||||
path: join(__dirname, '<%= rspackPluginOptions.outputPath %>'),
|
||||
publicPath: 'auto'
|
||||
},
|
||||
devServer: {
|
||||
port: <%= devServerPort %>,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
},
|
||||
historyApiFallback: {
|
||||
index: '/index.html',
|
||||
disableDotRule: true,
|
||||
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new NxAppRspackPlugin({
|
||||
tsConfig: '<%= rspackPluginOptions.tsConfig %>',
|
||||
main: '<%= rspackPluginOptions.main %>',
|
||||
index: '<%= rspackPluginOptions.index %>',
|
||||
baseHref: '<%= rspackPluginOptions.baseHref %>',
|
||||
assets: <%- JSON.stringify(rspackPluginOptions.assets) %>,
|
||||
styles: <%- JSON.stringify(rspackPluginOptions.styles) %>,
|
||||
outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none',
|
||||
optimization: process.env['NODE_ENV'] === 'production',
|
||||
}),
|
||||
new NxReactRspackPlugin({
|
||||
// Uncomment this line if you don't want to use SVGR
|
||||
// See: https://react-svgr.com/
|
||||
// svgr: false
|
||||
}),
|
||||
new NxModuleFederationPlugin({ config }, { dts: false }),
|
||||
new NxModuleFederationDevServerPlugin({ config }),
|
||||
],
|
||||
};
|
||||
|
||||
// Nx plugins for rspack to build config object from Nx options and context.
|
||||
/**
|
||||
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support Module Federation
|
||||
* The DTS Plugin can be enabled by setting dts: true
|
||||
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
|
||||
*/
|
||||
module.exports = composePlugins(withNx(), withReact(), withModuleFederation(config, { dts: false }));
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { GeneratorCallback, Tree } from '@nx/devkit';
|
||||
import { GeneratorCallback, names, offsetFromRoot, Tree } from '@nx/devkit';
|
||||
import {
|
||||
addDependenciesToPackageJson,
|
||||
generateFiles,
|
||||
@ -10,7 +10,17 @@ import {
|
||||
|
||||
import { NormalizedSchema } from '../../application/schema';
|
||||
import type { Schema } from '../schema';
|
||||
import { moduleFederationNodeVersion } from '../../../utils/versions';
|
||||
import {
|
||||
corsVersion,
|
||||
expressVersion,
|
||||
isbotVersion,
|
||||
moduleFederationNodeVersion,
|
||||
typesExpressVersion,
|
||||
} from '../../../utils/versions';
|
||||
import {
|
||||
createNxRspackPluginOptions,
|
||||
getDefaultTemplateVariables,
|
||||
} from '../../application/lib/create-application-files';
|
||||
|
||||
export async function setupSsrForRemote(
|
||||
tree: Tree,
|
||||
@ -28,22 +38,49 @@ export async function setupSsrForRemote(
|
||||
options.bundler === 'rspack' ? 'rspack-' : 'webpack-'
|
||||
}module-federation-ssr`;
|
||||
|
||||
const templateVariables =
|
||||
options.bundler === 'rspack'
|
||||
? {
|
||||
...getDefaultTemplateVariables(tree, options),
|
||||
rspackPluginOptions: {
|
||||
...createNxRspackPluginOptions(
|
||||
options,
|
||||
offsetFromRoot(options.appProjectRoot),
|
||||
false
|
||||
),
|
||||
mainServer: `./server.ts`,
|
||||
},
|
||||
port: Number(options?.devServerPort) || 4200,
|
||||
appName,
|
||||
}
|
||||
: {
|
||||
...options,
|
||||
port: Number(options?.devServerPort) || 4200,
|
||||
appName,
|
||||
tmpl: '',
|
||||
browserBuildOutputPath: project.targets.build?.options?.outputPath,
|
||||
serverBuildOutputPath: project.targets.server?.options?.outputPath,
|
||||
};
|
||||
|
||||
generateFiles(
|
||||
tree,
|
||||
joinPathFragments(__dirname, `../files/${pathToModuleFederationSsrFiles}`),
|
||||
project.root,
|
||||
{
|
||||
...options,
|
||||
port: Number(options?.devServerPort) || 4200,
|
||||
appName,
|
||||
tmpl: '',
|
||||
browserBuildOutputPath: project.targets.build.options.outputPath,
|
||||
serverBuildOutputPath: project.targets.server.options.outputPath,
|
||||
}
|
||||
templateVariables
|
||||
);
|
||||
|
||||
// For hosts to use when running remotes in static mode.
|
||||
const originalOutputPath = project.targets.build?.options?.outputPath;
|
||||
const originalOutputPath =
|
||||
project.targets.build?.options?.outputPath ??
|
||||
options.isUsingTsSolutionConfig
|
||||
? 'dist'
|
||||
: joinPathFragments(
|
||||
offsetFromRoot(options.appProjectRoot),
|
||||
'dist',
|
||||
options.appProjectRoot != '.'
|
||||
? options.appProjectRoot
|
||||
: options.projectName
|
||||
);
|
||||
const serverOptions = project.targets.server?.options;
|
||||
const serverOutputPath =
|
||||
serverOptions?.outputPath ??
|
||||
@ -66,6 +103,10 @@ export async function setupSsrForRemote(
|
||||
tree,
|
||||
{
|
||||
'@module-federation/node': moduleFederationNodeVersion,
|
||||
cors: corsVersion,
|
||||
isbot: isbotVersion,
|
||||
express: expressVersion,
|
||||
'@types/express': typesExpressVersion,
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
@ -242,7 +242,9 @@ describe('remote generator', () => {
|
||||
});
|
||||
|
||||
const mainFile = tree.read('test/server.ts', 'utf-8');
|
||||
expect(mainFile).toContain(`join(process.cwd(), 'dist/test/browser')`);
|
||||
expect(mainFile).toContain(
|
||||
`join(process.cwd(), '../dist/test', 'browser')`
|
||||
);
|
||||
expect(mainFile).toContain('nx.server.ready');
|
||||
});
|
||||
|
||||
@ -262,14 +264,10 @@ describe('remote generator', () => {
|
||||
bundler: 'rspack',
|
||||
});
|
||||
|
||||
expect(tree.exists('test/rspack.server.config.js')).toBeTruthy();
|
||||
expect(
|
||||
tree.exists('test/module-federation.server.config.js')
|
||||
).toBeTruthy();
|
||||
|
||||
expect(
|
||||
tree.read('test/rspack.server.config.js', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
expect(
|
||||
tree.read('test/module-federation.server.config.js', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
@ -291,14 +289,10 @@ describe('remote generator', () => {
|
||||
bundler: 'rspack',
|
||||
});
|
||||
|
||||
expect(tree.exists('test/rspack.server.config.ts')).toBeTruthy();
|
||||
expect(
|
||||
tree.exists('test/module-federation.server.config.ts')
|
||||
).toBeTruthy();
|
||||
|
||||
expect(
|
||||
tree.read('test/rspack.server.config.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
expect(
|
||||
tree.read('test/module-federation.server.config.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
|
||||
@ -6,6 +6,7 @@ import {
|
||||
GeneratorCallback,
|
||||
joinPathFragments,
|
||||
names,
|
||||
offsetFromRoot,
|
||||
readProjectConfiguration,
|
||||
runTasksInSerial,
|
||||
stripIndents,
|
||||
@ -31,16 +32,33 @@ import {
|
||||
nxVersion,
|
||||
} from '../../utils/versions';
|
||||
import { ensureRootProjectName } from '@nx/devkit/src/generators/project-name-and-root-utils';
|
||||
import {
|
||||
createNxRspackPluginOptions,
|
||||
getDefaultTemplateVariables,
|
||||
} from '../application/lib/create-application-files';
|
||||
|
||||
export function addModuleFederationFiles(
|
||||
host: Tree,
|
||||
options: NormalizedSchema<Schema>
|
||||
) {
|
||||
const templateVariables = {
|
||||
...names(options.projectName),
|
||||
...options,
|
||||
tmpl: '',
|
||||
};
|
||||
const templateVariables =
|
||||
options.bundler === 'rspack'
|
||||
? {
|
||||
...getDefaultTemplateVariables(host, options),
|
||||
rspackPluginOptions: {
|
||||
...createNxRspackPluginOptions(
|
||||
options,
|
||||
offsetFromRoot(options.appProjectRoot),
|
||||
false
|
||||
),
|
||||
mainServer: `./server.ts`,
|
||||
},
|
||||
}
|
||||
: {
|
||||
...names(options.projectName),
|
||||
...options,
|
||||
tmpl: '',
|
||||
};
|
||||
|
||||
generateFiles(
|
||||
host,
|
||||
@ -106,8 +124,8 @@ export async function remoteGenerator(host: Tree, schema: Schema) {
|
||||
? false
|
||||
: schema.typescriptConfiguration ?? true,
|
||||
dynamic: schema.dynamic ?? false,
|
||||
// TODO(colum): remove when MF works with Crystal
|
||||
addPlugin: false,
|
||||
// TODO(colum): remove when Webpack MF works with Crystal
|
||||
addPlugin: !schema.bundler || schema.bundler === 'rspack' ? true : false,
|
||||
bundler: schema.bundler ?? 'rspack',
|
||||
};
|
||||
|
||||
@ -170,13 +188,15 @@ export async function remoteGenerator(host: Tree, schema: Schema) {
|
||||
setupTspathForRemote(host, options);
|
||||
|
||||
if (options.ssr) {
|
||||
const setupSsrTask = await setupSsrGenerator(host, {
|
||||
project: options.projectName,
|
||||
serverPort: options.devServerPort,
|
||||
skipFormat: true,
|
||||
bundler: options.bundler,
|
||||
});
|
||||
tasks.push(setupSsrTask);
|
||||
if (options.bundler !== 'rspack') {
|
||||
const setupSsrTask = await setupSsrGenerator(host, {
|
||||
project: options.projectName,
|
||||
serverPort: options.devServerPort,
|
||||
skipFormat: true,
|
||||
bundler: options.bundler,
|
||||
});
|
||||
tasks.push(setupSsrTask);
|
||||
}
|
||||
|
||||
const setupSsrForRemoteTask = await setupSsrForRemote(
|
||||
host,
|
||||
@ -186,20 +206,13 @@ export async function remoteGenerator(host: Tree, schema: Schema) {
|
||||
tasks.push(setupSsrForRemoteTask);
|
||||
|
||||
const projectConfig = readProjectConfiguration(host, options.projectName);
|
||||
if (options.bundler === 'rspack') {
|
||||
projectConfig.targets.server.executor = '@nx/rspack:rspack';
|
||||
projectConfig.targets.server.options.rspackConfig = joinPathFragments(
|
||||
projectConfig.root,
|
||||
`rspack.server.config.${options.typescriptConfiguration ? 'ts' : 'js'}`
|
||||
);
|
||||
delete projectConfig.targets.server.options.webpackConfig;
|
||||
} else {
|
||||
if (options.bundler !== 'rspack') {
|
||||
projectConfig.targets.server.options.webpackConfig = joinPathFragments(
|
||||
projectConfig.root,
|
||||
`webpack.server.config.${options.typescriptConfiguration ? 'ts' : 'js'}`
|
||||
);
|
||||
updateProjectConfiguration(host, options.projectName, projectConfig);
|
||||
}
|
||||
updateProjectConfiguration(host, options.projectName, projectConfig);
|
||||
}
|
||||
if (!options.setParserOptionsProject) {
|
||||
host.delete(
|
||||
|
||||
@ -20,33 +20,13 @@ export function updateModuleFederationProject(
|
||||
typescriptConfiguration?: boolean;
|
||||
dynamic?: boolean;
|
||||
bundler?: 'rspack' | 'webpack';
|
||||
}
|
||||
ssr?: boolean;
|
||||
},
|
||||
isHost = false
|
||||
) {
|
||||
const projectConfig = readProjectConfiguration(host, options.projectName);
|
||||
|
||||
if (options.bundler === 'rspack') {
|
||||
projectConfig.targets.build.executor = '@nx/rspack:rspack';
|
||||
projectConfig.targets.build.options = {
|
||||
...(projectConfig.targets.build.options ?? {}),
|
||||
main: maybeJs(
|
||||
{ js: options.js, useJsx: true },
|
||||
`${options.appProjectRoot}/src/main.ts`
|
||||
),
|
||||
rspackConfig: `${options.appProjectRoot}/rspack.config.${
|
||||
options.typescriptConfiguration && !options.js ? 'ts' : 'js'
|
||||
}`,
|
||||
target: 'web',
|
||||
};
|
||||
|
||||
projectConfig.targets.build.configurations ??= {};
|
||||
|
||||
projectConfig.targets.build.configurations.production = {
|
||||
...(projectConfig.targets.build.configurations?.production ?? {}),
|
||||
rspackConfig: `${options.appProjectRoot}/rspack.config.prod.${
|
||||
options.typescriptConfiguration && !options.js ? 'ts' : 'js'
|
||||
}`,
|
||||
};
|
||||
} else {
|
||||
if (options.bundler !== 'rspack') {
|
||||
projectConfig.targets.build.options = {
|
||||
...(projectConfig.targets.build.options ?? {}),
|
||||
main: maybeJs(options, `${options.appProjectRoot}/src/main.ts`),
|
||||
@ -67,20 +47,7 @@ export function updateModuleFederationProject(
|
||||
|
||||
// If host should be configured to use dynamic federation
|
||||
if (options.dynamic) {
|
||||
if (options.bundler === 'rspack') {
|
||||
const pathToProdRspackConfig = joinPathFragments(
|
||||
projectConfig.root,
|
||||
`rspack.prod.config.${
|
||||
options.typescriptConfiguration && !options.js ? 'ts' : 'js'
|
||||
}`
|
||||
);
|
||||
if (host.exists(pathToProdRspackConfig)) {
|
||||
host.delete(pathToProdRspackConfig);
|
||||
}
|
||||
|
||||
delete projectConfig.targets.build.configurations.production
|
||||
?.rspackConfig;
|
||||
} else {
|
||||
if (options.bundler !== 'rspack') {
|
||||
const pathToProdWebpackConfig = joinPathFragments(
|
||||
projectConfig.root,
|
||||
`webpack.prod.config.${
|
||||
@ -96,38 +63,41 @@ export function updateModuleFederationProject(
|
||||
}
|
||||
}
|
||||
|
||||
if (options.bundler === 'rspack') {
|
||||
projectConfig.targets.serve.executor =
|
||||
'@nx/rspack:module-federation-dev-server';
|
||||
} else {
|
||||
if (options.bundler !== 'rspack') {
|
||||
projectConfig.targets.serve.executor =
|
||||
'@nx/react:module-federation-dev-server';
|
||||
}
|
||||
projectConfig.targets.serve.options.port = options.devServerPort;
|
||||
projectConfig.targets.serve ??= {};
|
||||
projectConfig.targets.serve.options ??= {};
|
||||
projectConfig.targets.serve.options.port =
|
||||
options.bundler === 'rspack' && options.ssr && isHost
|
||||
? 4000
|
||||
: options.devServerPort;
|
||||
|
||||
// `serve-static` for remotes that don't need to be in development mode
|
||||
const serveStaticExecutor =
|
||||
options.bundler === 'rspack'
|
||||
? '@nx/rspack:module-federation-static-server'
|
||||
: '@nx/react:module-federation-static-server';
|
||||
projectConfig.targets['serve-static'] = {
|
||||
executor: serveStaticExecutor,
|
||||
defaultConfiguration: 'production',
|
||||
options: {
|
||||
serveTarget: `${options.projectName}:serve`,
|
||||
},
|
||||
configurations: {
|
||||
development: {
|
||||
serveTarget: `${options.projectName}:serve:development`,
|
||||
if (options.bundler !== 'rspack') {
|
||||
const serveStaticExecutor = '@nx/react:module-federation-static-server';
|
||||
projectConfig.targets['serve-static'] = {
|
||||
executor: serveStaticExecutor,
|
||||
defaultConfiguration: 'production',
|
||||
options: {
|
||||
serveTarget: `${options.projectName}:serve`,
|
||||
},
|
||||
production: {
|
||||
serveTarget: `${options.projectName}:serve:production`,
|
||||
configurations: {
|
||||
development: {
|
||||
serveTarget: `${options.projectName}:serve:development`,
|
||||
},
|
||||
production: {
|
||||
serveTarget: `${options.projectName}:serve:production`,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// Typechecks must be performed first before build and serve to generate remote d.ts files.
|
||||
if (isUsingTsSolutionSetup(host)) {
|
||||
projectConfig.targets.build ??= {};
|
||||
projectConfig.targets.serve ??= {};
|
||||
projectConfig.targets.build.dependsOn = ['^build', 'typecheck'];
|
||||
projectConfig.targets.serve.dependsOn = ['typecheck'];
|
||||
}
|
||||
|
||||
@ -45,6 +45,7 @@
|
||||
"@module-federation/node",
|
||||
// @nx/workspace is only required in < 15.8
|
||||
"@nx/workspace",
|
||||
"@nx/react",
|
||||
// Imported types only
|
||||
"@module-federation/sdk",
|
||||
"@module-federation/enhanced",
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import { readProjectConfiguration } from '@nx/devkit';
|
||||
// nx-ignore-next-line
|
||||
|
||||
@ -2,7 +2,9 @@ import {
|
||||
addDependenciesToPackageJson,
|
||||
formatFiles,
|
||||
getProjects,
|
||||
readNxJson,
|
||||
type Tree,
|
||||
updateNxJson,
|
||||
updateProjectConfiguration,
|
||||
} from '@nx/devkit';
|
||||
import { Schema } from './schema';
|
||||
@ -78,6 +80,37 @@ export default async function (tree: Tree, options: Schema) {
|
||||
}
|
||||
|
||||
updateProjectConfiguration(tree, options.project, project);
|
||||
const nxJson = readNxJson(tree);
|
||||
if (nxJson.plugins !== undefined && nxJson.plugins.length > 0) {
|
||||
const nonRspackPlugins = nxJson.plugins.filter(
|
||||
(plugin) =>
|
||||
(typeof plugin !== 'string' && plugin.plugin !== '@nx/rspack/plugin') ||
|
||||
(typeof plugin === 'string' && plugin !== '@nx/rspack/plugin')
|
||||
);
|
||||
let rspackPlugins = nxJson.plugins.filter(
|
||||
(plugin) =>
|
||||
(typeof plugin !== 'string' && plugin.plugin === '@nx/rspack/plugin') ||
|
||||
(typeof plugin === 'string' && plugin === '@nx/rspack/plugin')
|
||||
);
|
||||
|
||||
if (rspackPlugins.length === 0) {
|
||||
rspackPlugins = rspackPlugins.map((plugin) => {
|
||||
if (typeof plugin === 'string') {
|
||||
return {
|
||||
plugin: plugin,
|
||||
exclude: [`${project.root}/*`],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...plugin,
|
||||
exclude: [...(plugin.exclude ?? []), `${project.root}/*`],
|
||||
};
|
||||
});
|
||||
nxJson.plugins = [...nonRspackPlugins, ...rspackPlugins];
|
||||
updateNxJson(tree, nxJson);
|
||||
}
|
||||
}
|
||||
const installTask = addDependenciesToPackageJson(
|
||||
tree,
|
||||
{},
|
||||
|
||||
@ -4,4 +4,5 @@ export * from './utils/config';
|
||||
export * from './utils/with-nx';
|
||||
export * from './utils/with-react';
|
||||
export * from './utils/with-web';
|
||||
export * from './utils/e2e-web-server-info-utils';
|
||||
export * from './plugins/use-legacy-nx-plugin/use-legacy-nx-plugin';
|
||||
|
||||
39
packages/rspack/src/utils/e2e-web-server-info-utils.ts
Normal file
39
packages/rspack/src/utils/e2e-web-server-info-utils.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { type Tree, readNxJson } from '@nx/devkit';
|
||||
import { getE2EWebServerInfo } from '@nx/devkit/src/generators/e2e-web-server-info-utils';
|
||||
|
||||
export async function getRspackE2EWebServerInfo(
|
||||
tree: Tree,
|
||||
projectName: string,
|
||||
configFilePath: string,
|
||||
isPluginBeingAdded: boolean,
|
||||
e2ePortOverride?: number
|
||||
) {
|
||||
const nxJson = readNxJson(tree);
|
||||
let e2ePort = e2ePortOverride ?? 4200;
|
||||
|
||||
if (
|
||||
nxJson.targetDefaults?.['serve'] &&
|
||||
nxJson.targetDefaults?.['serve'].options?.port
|
||||
) {
|
||||
e2ePort = nxJson.targetDefaults?.['serve'].options?.port;
|
||||
}
|
||||
|
||||
return getE2EWebServerInfo(
|
||||
tree,
|
||||
projectName,
|
||||
{
|
||||
plugin: '@nx/rspack/plugin',
|
||||
serveTargetName: 'serveTargetName',
|
||||
serveStaticTargetName: 'previewTargetName',
|
||||
configFilePath,
|
||||
},
|
||||
{
|
||||
defaultServeTargetName: 'serve',
|
||||
defaultServeStaticTargetName: 'preview',
|
||||
defaultE2EWebServerAddress: `http://localhost:${e2ePort}`,
|
||||
defaultE2ECiBaseUrl: 'http://localhost:4200',
|
||||
defaultE2EPort: e2ePort,
|
||||
},
|
||||
isPluginBeingAdded
|
||||
);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user