feat(angular): switch default to typescript configuration for module federation (#18998)
This commit is contained in:
parent
5366d49936
commit
9be869ff7b
@ -170,6 +170,11 @@
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": false,
|
"default": false,
|
||||||
"x-priority": "important"
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"typescriptConfiguration": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether the module federation configuration and webpack configuration files should use TS.",
|
||||||
|
"default": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
|
|||||||
@ -163,6 +163,11 @@
|
|||||||
"description": "Whether to configure SSR for the remote application to be consumed by a host application using SSR.",
|
"description": "Whether to configure SSR for the remote application to be consumed by a host application using SSR.",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": false
|
"default": false
|
||||||
|
},
|
||||||
|
"typescriptConfiguration": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether the module federation configuration and webpack configuration files should use TS.",
|
||||||
|
"default": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
|
|||||||
@ -73,6 +73,11 @@
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Whether the application is a standalone application. _Note: This is only supported in Angular versions >= 14.1.0_",
|
"description": "Whether the application is a standalone application. _Note: This is only supported in Angular versions >= 14.1.0_",
|
||||||
"default": false
|
"default": false
|
||||||
|
},
|
||||||
|
"typescriptConfiguration": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether the module federation configuration and webpack configuration files should use TS.",
|
||||||
|
"default": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["appName", "mfType"],
|
"required": ["appName", "mfType"],
|
||||||
|
|||||||
@ -70,7 +70,7 @@ describe('Angular Module Federation', () => {
|
|||||||
}Module } from '@${proj}/${sharedLib}';
|
}Module } from '@${proj}/${sharedLib}';
|
||||||
import { ${
|
import { ${
|
||||||
names(secondaryEntry).className
|
names(secondaryEntry).className
|
||||||
}Module } from '@${proj}/${secondaryEntry}';
|
}Module } from '@${proj}/${sharedLib}/${secondaryEntry}';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { NxWelcomeComponent } from './nx-welcome.component';
|
import { NxWelcomeComponent } from './nx-welcome.component';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
@ -79,7 +79,7 @@ describe('Angular Module Federation', () => {
|
|||||||
declarations: [AppComponent, NxWelcomeComponent],
|
declarations: [AppComponent, NxWelcomeComponent],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
SharedModule,
|
${names(sharedLib).className}Module,
|
||||||
RouterModule.forRoot(
|
RouterModule.forRoot(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@ -107,14 +107,15 @@ describe('Angular Module Federation', () => {
|
|||||||
import { ${names(sharedLib).className}Module } from '@${proj}/${sharedLib}';
|
import { ${names(sharedLib).className}Module } from '@${proj}/${sharedLib}';
|
||||||
import { ${
|
import { ${
|
||||||
names(secondaryEntry).className
|
names(secondaryEntry).className
|
||||||
}Module } from '@${proj}/${secondaryEntry}';
|
}Module } from '@${proj}/${sharedLib}/${secondaryEntry}';
|
||||||
import { RemoteEntryComponent } from './entry.component';
|
import { RemoteEntryComponent } from './entry.component';
|
||||||
|
import { NxWelcomeComponent } from './nx-welcome.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [RemoteEntryComponent],
|
declarations: [RemoteEntryComponent, NxWelcomeComponent],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
${names(sharedLib).className}Module,
|
||||||
RouterModule.forChild([
|
RouterModule.forChild([
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
@ -128,15 +129,23 @@ describe('Angular Module Federation', () => {
|
|||||||
`
|
`
|
||||||
);
|
);
|
||||||
|
|
||||||
const process = await runCommandUntil(
|
const processSwc = await runCommandUntil(
|
||||||
`serve ${hostApp} --port=${hostPort} --dev-remotes=${remoteApp1}`,
|
`serve ${hostApp} --port=${hostPort} --dev-remotes=${remoteApp1}`,
|
||||||
(output) =>
|
(output) =>
|
||||||
output.includes(`listening on localhost:${remotePort}`) &&
|
!output.includes(`Remote '${remoteApp1}' failed to serve correctly`) &&
|
||||||
output.includes(`listening on localhost:${hostPort}`)
|
output.includes(`listening on localhost:${hostPort}`)
|
||||||
);
|
);
|
||||||
|
await killProcessAndPorts(processSwc.pid, hostPort, remotePort);
|
||||||
|
|
||||||
// port and process cleanup
|
const processTsNode = await runCommandUntil(
|
||||||
await killProcessAndPorts(process.pid, hostPort, remotePort);
|
`serve ${hostApp} --port=${hostPort} --dev-remotes=${remoteApp1}`,
|
||||||
|
(output) =>
|
||||||
|
!output.includes(`Remote '${remoteApp1}' failed to serve correctly`) &&
|
||||||
|
output.includes(`listening on localhost:${hostPort}`),
|
||||||
|
{ env: { NX_PREFER_TS_NODE: 'true' } }
|
||||||
|
);
|
||||||
|
|
||||||
|
await killProcessAndPorts(processTsNode.pid, hostPort, remotePort);
|
||||||
}, 20_000_000);
|
}, 20_000_000);
|
||||||
|
|
||||||
it('should convert apps to MF successfully', async () => {
|
it('should convert apps to MF successfully', async () => {
|
||||||
@ -161,15 +170,24 @@ describe('Angular Module Federation', () => {
|
|||||||
`generate @nx/angular:setup-mf ${app2} --mfType=remote --host=${app1} --port=${app2Port} --no-interactive`
|
`generate @nx/angular:setup-mf ${app2} --mfType=remote --host=${app1} --port=${app2Port} --no-interactive`
|
||||||
);
|
);
|
||||||
|
|
||||||
const process = await runCommandUntil(
|
const processSwc = await runCommandUntil(
|
||||||
`serve ${app1} --dev-remotes=${app2}`,
|
`serve ${app1} --dev-remotes=${app2}`,
|
||||||
(output) =>
|
(output) =>
|
||||||
output.includes(`listening on localhost:${app1Port}`) &&
|
!output.includes(`Remote '${app2}' failed to serve correctly`) &&
|
||||||
output.includes(`listening on localhost:${app2Port}`)
|
output.includes(`listening on localhost:${app1Port}`)
|
||||||
);
|
);
|
||||||
|
|
||||||
// port and process cleanup
|
await killProcessAndPorts(processSwc.pid, app1Port, app2Port);
|
||||||
await killProcessAndPorts(process.pid, app1Port, app2Port);
|
|
||||||
|
const processTsNode = await runCommandUntil(
|
||||||
|
`serve ${app1} --dev-remotes=${app2}`,
|
||||||
|
(output) =>
|
||||||
|
!output.includes(`Remote '${app2}' failed to serve correctly`) &&
|
||||||
|
output.includes(`listening on localhost:${app1Port}`),
|
||||||
|
{ env: { NX_PREFER_TS_NODE: 'true' } }
|
||||||
|
);
|
||||||
|
|
||||||
|
await killProcessAndPorts(processTsNode.pid, app1Port, app2Port);
|
||||||
}, 20_000_000);
|
}, 20_000_000);
|
||||||
|
|
||||||
it('should scaffold MF + SSR setup successfully', async () => {
|
it('should scaffold MF + SSR setup successfully', async () => {
|
||||||
@ -189,7 +207,7 @@ describe('Angular Module Federation', () => {
|
|||||||
const remote2Port = readJson(join(remote2, 'project.json')).targets.serve
|
const remote2Port = readJson(join(remote2, 'project.json')).targets.serve
|
||||||
.options.port;
|
.options.port;
|
||||||
|
|
||||||
const process = await runCommandUntil(
|
const processSwc = await runCommandUntil(
|
||||||
`serve-ssr ${host} --port=${hostPort}`,
|
`serve-ssr ${host} --port=${hostPort}`,
|
||||||
(output) =>
|
(output) =>
|
||||||
output.includes(
|
output.includes(
|
||||||
@ -203,8 +221,34 @@ describe('Angular Module Federation', () => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// port and process cleanup
|
await killProcessAndPorts(
|
||||||
await killProcessAndPorts(process.pid, hostPort, remote1Port, remote2Port);
|
processSwc.pid,
|
||||||
|
hostPort,
|
||||||
|
remote1Port,
|
||||||
|
remote2Port
|
||||||
|
);
|
||||||
|
|
||||||
|
const processTsNode = await runCommandUntil(
|
||||||
|
`serve-ssr ${host} --port=${hostPort}`,
|
||||||
|
(output) =>
|
||||||
|
output.includes(
|
||||||
|
`Node Express server listening on http://localhost:${remote1Port}`
|
||||||
|
) &&
|
||||||
|
output.includes(
|
||||||
|
`Node Express server listening on http://localhost:${remote2Port}`
|
||||||
|
) &&
|
||||||
|
output.includes(
|
||||||
|
`Angular Universal Live Development Server is listening`
|
||||||
|
),
|
||||||
|
{ env: { NX_PREFER_TS_NODE: 'true' } }
|
||||||
|
);
|
||||||
|
|
||||||
|
await killProcessAndPorts(
|
||||||
|
processTsNode.pid,
|
||||||
|
hostPort,
|
||||||
|
remote1Port,
|
||||||
|
remote2Port
|
||||||
|
);
|
||||||
}, 20_000_000);
|
}, 20_000_000);
|
||||||
|
|
||||||
it('should should support generating host and remote apps with --project-name-and-root-format=derived', async () => {
|
it('should should support generating host and remote apps with --project-name-and-root-format=derived', async () => {
|
||||||
@ -229,17 +273,33 @@ describe('Angular Module Federation', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// check default generated host is built successfully
|
// check default generated host is built successfully
|
||||||
const buildOutput = runCLI(`build ${hostApp}`);
|
const buildOutputSwc = runCLI(`build ${hostApp}`);
|
||||||
expect(buildOutput).toContain('Successfully ran target build');
|
expect(buildOutputSwc).toContain('Successfully ran target build');
|
||||||
|
|
||||||
const process = await runCommandUntil(
|
const buildOutputTsNode = runCLI(`build ${hostApp}`, {
|
||||||
|
env: { NX_PREFER_TS_NODE: 'true' },
|
||||||
|
});
|
||||||
|
expect(buildOutputTsNode).toContain('Successfully ran target build');
|
||||||
|
|
||||||
|
const processSwc = await runCommandUntil(
|
||||||
`serve ${hostApp} --port=${hostPort} --dev-remotes=${remoteApp}`,
|
`serve ${hostApp} --port=${hostPort} --dev-remotes=${remoteApp}`,
|
||||||
(output) =>
|
(output) =>
|
||||||
output.includes(`listening on localhost:${remotePort}`) &&
|
!output.includes(`Remote '${remoteApp}' failed to serve correctly`) &&
|
||||||
output.includes(`listening on localhost:${hostPort}`)
|
output.includes(`listening on localhost:${hostPort}`)
|
||||||
);
|
);
|
||||||
|
|
||||||
// port and process cleanup
|
await killProcessAndPorts(processSwc.pid, hostPort, remotePort);
|
||||||
await killProcessAndPorts(process.pid, hostPort, remotePort);
|
|
||||||
|
const processTsNode = await runCommandUntil(
|
||||||
|
`serve ${hostApp} --port=${hostPort} --dev-remotes=${remoteApp}`,
|
||||||
|
(output) =>
|
||||||
|
!output.includes(`Remote '${remoteApp}' failed to serve correctly`) &&
|
||||||
|
output.includes(`listening on localhost:${hostPort}`),
|
||||||
|
{
|
||||||
|
env: { NX_PREFER_TS_NODE: 'true' },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await killProcessAndPorts(processTsNode.pid, hostPort, remotePort);
|
||||||
}, 20_000_000);
|
}, 20_000_000);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -229,7 +229,10 @@ export function runCommandAsync(
|
|||||||
|
|
||||||
export function runCommandUntil(
|
export function runCommandUntil(
|
||||||
command: string,
|
command: string,
|
||||||
criteria: (output: string) => boolean
|
criteria: (output: string) => boolean,
|
||||||
|
opts: RunCmdOpts = {
|
||||||
|
env: undefined,
|
||||||
|
}
|
||||||
): Promise<ChildProcess> {
|
): Promise<ChildProcess> {
|
||||||
const pm = getPackageManagerCommand();
|
const pm = getPackageManagerCommand();
|
||||||
const p = exec(`${pm.runNx} ${command}`, {
|
const p = exec(`${pm.runNx} ${command}`, {
|
||||||
@ -238,6 +241,7 @@ export function runCommandUntil(
|
|||||||
env: {
|
env: {
|
||||||
CI: 'true',
|
CI: 'true',
|
||||||
...getStrippedEnvironmentVariables(),
|
...getStrippedEnvironmentVariables(),
|
||||||
|
...opts.env,
|
||||||
FORCE_COLOR: 'false',
|
FORCE_COLOR: 'false',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -385,6 +385,401 @@ exports[`Host App Generator --ssr should generate the correct files for standalo
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`Host App Generator --ssr should generate the correct files for standalone when --typescript=true 1`] = `
|
||||||
|
"import { bootstrapApplication } from '@angular/platform-browser';
|
||||||
|
import { appConfig } from './app/app.config';
|
||||||
|
import { AppComponent } from './app/app.component';
|
||||||
|
|
||||||
|
bootstrapApplication(AppComponent, appConfig).catch((err) =>
|
||||||
|
console.error(err)
|
||||||
|
);
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Host App Generator --ssr should generate the correct files for standalone when --typescript=true 2`] = `
|
||||||
|
"import { bootstrapApplication } from '@angular/platform-browser';
|
||||||
|
import { AppComponent } from './app/app.component';
|
||||||
|
import { config } from './app/app.config.server';
|
||||||
|
|
||||||
|
const bootstrap = () => bootstrapApplication(AppComponent, config);
|
||||||
|
|
||||||
|
export default bootstrap;
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Host App Generator --ssr should generate the correct files for standalone when --typescript=true 3`] = `
|
||||||
|
"import 'zone.js/dist/zone-node';
|
||||||
|
|
||||||
|
import { APP_BASE_HREF } from '@angular/common';
|
||||||
|
import { ngExpressEngine } from '@nguniversal/express-engine';
|
||||||
|
import * as express from 'express';
|
||||||
|
import * as cors from 'cors';
|
||||||
|
import { existsSync } from 'fs';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
|
import bootstrap from './bootstrap.server';
|
||||||
|
|
||||||
|
// The Express app is exported so that it can be used by serverless Functions.
|
||||||
|
export function app(): express.Express {
|
||||||
|
const server = express();
|
||||||
|
const browserBundles = join(process.cwd(), 'dist/test/browser');
|
||||||
|
|
||||||
|
server.use(cors());
|
||||||
|
const indexHtml = existsSync(join(browserBundles, 'index.original.html'))
|
||||||
|
? 'index.original.html'
|
||||||
|
: 'index';
|
||||||
|
|
||||||
|
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/main/modules/express-engine)
|
||||||
|
server.engine(
|
||||||
|
'html',
|
||||||
|
ngExpressEngine({
|
||||||
|
bootstrap,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
server.set('view engine', 'html');
|
||||||
|
server.set('views', browserBundles);
|
||||||
|
|
||||||
|
// Serve static files from /browser
|
||||||
|
server.get(
|
||||||
|
'*.*',
|
||||||
|
express.static(browserBundles, {
|
||||||
|
maxAge: '1y',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// All regular routes use the Universal engine
|
||||||
|
server.get('*', (req, res) => {
|
||||||
|
// keep it async to avoid blocking the server thread
|
||||||
|
|
||||||
|
res.render(indexHtml, {
|
||||||
|
providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }],
|
||||||
|
req,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
function run(): void {
|
||||||
|
const port = process.env['PORT'] || 4000;
|
||||||
|
|
||||||
|
// Start up the Node server
|
||||||
|
const server = app();
|
||||||
|
server.listen(port, () => {
|
||||||
|
console.log(\`Node Express server listening on http://localhost:\${port}\`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
||||||
|
|
||||||
|
export default bootstrap;
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Host App Generator --ssr should generate the correct files for standalone when --typescript=true 4`] = `
|
||||||
|
"import('./src/main.server');
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Host App Generator --ssr should generate the correct files for standalone when --typescript=true 5`] = `
|
||||||
|
"import { ModuleFederationConfig } from '@nx/webpack';
|
||||||
|
|
||||||
|
const config: ModuleFederationConfig = {
|
||||||
|
name: 'test',
|
||||||
|
remotes: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Host App Generator --ssr should generate the correct files for standalone when --typescript=true 6`] = `
|
||||||
|
"import { withModuleFederationForSSR } from '@nx/angular/module-federation';
|
||||||
|
import config from './module-federation.config';
|
||||||
|
|
||||||
|
export default withModuleFederationForSSR(config);
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Host App Generator --ssr should generate the correct files for standalone when --typescript=true 7`] = `
|
||||||
|
"import { NxWelcomeComponent } from './nx-welcome.component';
|
||||||
|
import { Route } from '@angular/router';
|
||||||
|
|
||||||
|
export const appRoutes: Route[] = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: NxWelcomeComponent,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Host App Generator --ssr should generate the correct files for standalone when --typescript=true 8`] = `
|
||||||
|
"import { ApplicationConfig } from '@angular/core';
|
||||||
|
import {
|
||||||
|
provideRouter,
|
||||||
|
withEnabledBlockingInitialNavigation,
|
||||||
|
} from '@angular/router';
|
||||||
|
import { appRoutes } from './app.routes';
|
||||||
|
|
||||||
|
export const appConfig: ApplicationConfig = {
|
||||||
|
providers: [provideRouter(appRoutes, withEnabledBlockingInitialNavigation())],
|
||||||
|
};
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Host App Generator --ssr should generate the correct files for standalone when --typescript=true 9`] = `
|
||||||
|
"import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
|
||||||
|
import { provideServerRendering } from '@angular/platform-server';
|
||||||
|
import { appConfig } from './app.config';
|
||||||
|
|
||||||
|
const serverConfig: ApplicationConfig = {
|
||||||
|
providers: [provideServerRendering()],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const config = mergeApplicationConfig(appConfig, serverConfig);
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Host App Generator --ssr should generate the correct files for standalone when --typescript=true 10`] = `
|
||||||
|
{
|
||||||
|
"configurations": {
|
||||||
|
"development": {
|
||||||
|
"buildOptimizer": false,
|
||||||
|
"extractLicenses": false,
|
||||||
|
"optimization": false,
|
||||||
|
"sourceMap": true,
|
||||||
|
"vendorChunk": true,
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
"outputHashing": "media",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "production",
|
||||||
|
"dependsOn": [
|
||||||
|
"build",
|
||||||
|
],
|
||||||
|
"executor": "@nx/angular:webpack-server",
|
||||||
|
"options": {
|
||||||
|
"customWebpackConfig": {
|
||||||
|
"path": "test/webpack.server.config.ts",
|
||||||
|
},
|
||||||
|
"main": "test/server.ts",
|
||||||
|
"outputPath": "dist/test/server",
|
||||||
|
"tsConfig": "test/tsconfig.server.json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Host App Generator --ssr should generate the correct files for standalone when --typescript=true 11`] = `
|
||||||
|
{
|
||||||
|
"configurations": {
|
||||||
|
"development": {
|
||||||
|
"browserTarget": "test:build:development",
|
||||||
|
"serverTarget": "test:server:development",
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
"browserTarget": "test:build:production",
|
||||||
|
"serverTarget": "test:server:production",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "development",
|
||||||
|
"executor": "@nx/angular:module-federation-dev-ssr",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Host App Generator --ssr should generate the correct files when --typescript=true 1`] = `
|
||||||
|
"import { NgModule } from '@angular/core';
|
||||||
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
import { appRoutes } from './app.routes';
|
||||||
|
import { NxWelcomeComponent } from './nx-welcome.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [AppComponent, NxWelcomeComponent],
|
||||||
|
imports: [
|
||||||
|
BrowserModule,
|
||||||
|
RouterModule.forRoot(appRoutes, { initialNavigation: 'enabledBlocking' }),
|
||||||
|
],
|
||||||
|
providers: [],
|
||||||
|
bootstrap: [AppComponent],
|
||||||
|
})
|
||||||
|
export class AppModule {}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Host App Generator --ssr should generate the correct files when --typescript=true 2`] = `
|
||||||
|
"import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||||
|
import { AppModule } from './app/app.module';
|
||||||
|
|
||||||
|
platformBrowserDynamic()
|
||||||
|
.bootstrapModule(AppModule)
|
||||||
|
.catch((err) => console.error(err));
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Host App Generator --ssr should generate the correct files when --typescript=true 3`] = `
|
||||||
|
"export { AppServerModule } from './app/app.server.module';
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Host App Generator --ssr should generate the correct files when --typescript=true 4`] = `
|
||||||
|
"import 'zone.js/dist/zone-node';
|
||||||
|
|
||||||
|
import { APP_BASE_HREF } from '@angular/common';
|
||||||
|
import { ngExpressEngine } from '@nguniversal/express-engine';
|
||||||
|
import * as express from 'express';
|
||||||
|
import * as cors from 'cors';
|
||||||
|
import { existsSync } from 'fs';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
|
import { AppServerModule } from './bootstrap.server';
|
||||||
|
|
||||||
|
// The Express app is exported so that it can be used by serverless Functions.
|
||||||
|
export function app(): express.Express {
|
||||||
|
const server = express();
|
||||||
|
const browserBundles = join(process.cwd(), 'dist/test/browser');
|
||||||
|
|
||||||
|
server.use(cors());
|
||||||
|
const indexHtml = existsSync(join(browserBundles, 'index.original.html'))
|
||||||
|
? 'index.original.html'
|
||||||
|
: 'index';
|
||||||
|
|
||||||
|
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/main/modules/express-engine)
|
||||||
|
server.engine(
|
||||||
|
'html',
|
||||||
|
ngExpressEngine({
|
||||||
|
bootstrap: AppServerModule,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
server.set('view engine', 'html');
|
||||||
|
server.set('views', browserBundles);
|
||||||
|
|
||||||
|
// Serve static files from /browser
|
||||||
|
server.get(
|
||||||
|
'*.*',
|
||||||
|
express.static(browserBundles, {
|
||||||
|
maxAge: '1y',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// All regular routes use the Universal engine
|
||||||
|
server.get('*', (req, res) => {
|
||||||
|
// keep it async to avoid blocking the server thread
|
||||||
|
|
||||||
|
res.render(indexHtml, {
|
||||||
|
providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }],
|
||||||
|
req,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
function run(): void {
|
||||||
|
const port = process.env['PORT'] || 4000;
|
||||||
|
|
||||||
|
// Start up the Node server
|
||||||
|
const server = app();
|
||||||
|
server.listen(port, () => {
|
||||||
|
console.log(\`Node Express server listening on http://localhost:\${port}\`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
||||||
|
|
||||||
|
export * from './bootstrap.server';
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Host App Generator --ssr should generate the correct files when --typescript=true 5`] = `
|
||||||
|
"import('./src/main.server');
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Host App Generator --ssr should generate the correct files when --typescript=true 6`] = `
|
||||||
|
"import { ModuleFederationConfig } from '@nx/webpack';
|
||||||
|
|
||||||
|
const config: ModuleFederationConfig = {
|
||||||
|
name: 'test',
|
||||||
|
remotes: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Host App Generator --ssr should generate the correct files when --typescript=true 7`] = `
|
||||||
|
"import { withModuleFederationForSSR } from '@nx/angular/module-federation';
|
||||||
|
import config from './module-federation.config';
|
||||||
|
|
||||||
|
export default withModuleFederationForSSR(config);
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Host App Generator --ssr should generate the correct files when --typescript=true 8`] = `
|
||||||
|
"import { NxWelcomeComponent } from './nx-welcome.component';
|
||||||
|
import { Route } from '@angular/router';
|
||||||
|
|
||||||
|
export const appRoutes: Route[] = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: NxWelcomeComponent,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Host App Generator --ssr should generate the correct files when --typescript=true 9`] = `
|
||||||
|
{
|
||||||
|
"configurations": {
|
||||||
|
"development": {
|
||||||
|
"buildOptimizer": false,
|
||||||
|
"extractLicenses": false,
|
||||||
|
"optimization": false,
|
||||||
|
"sourceMap": true,
|
||||||
|
"vendorChunk": true,
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
"outputHashing": "media",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "production",
|
||||||
|
"dependsOn": [
|
||||||
|
"build",
|
||||||
|
],
|
||||||
|
"executor": "@nx/angular:webpack-server",
|
||||||
|
"options": {
|
||||||
|
"customWebpackConfig": {
|
||||||
|
"path": "test/webpack.server.config.ts",
|
||||||
|
},
|
||||||
|
"main": "test/server.ts",
|
||||||
|
"outputPath": "dist/test/server",
|
||||||
|
"tsConfig": "test/tsconfig.server.json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Host App Generator --ssr should generate the correct files when --typescript=true 10`] = `
|
||||||
|
{
|
||||||
|
"configurations": {
|
||||||
|
"development": {
|
||||||
|
"browserTarget": "test:build:development",
|
||||||
|
"serverTarget": "test:server:development",
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
"browserTarget": "test:build:production",
|
||||||
|
"serverTarget": "test:server:production",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "development",
|
||||||
|
"executor": "@nx/angular:module-federation-dev-ssr",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`Host App Generator should generate a host app with a remote 1`] = `
|
exports[`Host App Generator should generate a host app with a remote 1`] = `
|
||||||
"const { withModuleFederation } = require('@nx/angular/module-federation');
|
"const { withModuleFederation } = require('@nx/angular/module-federation');
|
||||||
const config = require('./module-federation.config');
|
const config = require('./module-federation.config');
|
||||||
@ -399,6 +794,22 @@ module.exports = withModuleFederation(config);
|
|||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`Host App Generator should generate a host app with a remote when --typesscript=true 1`] = `
|
||||||
|
"import { withModuleFederation } from '@nx/angular/module-federation';
|
||||||
|
import config from './module-federation.config';
|
||||||
|
|
||||||
|
export default withModuleFederation(config);
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Host App Generator should generate a host app with a remote when --typesscript=true 2`] = `
|
||||||
|
"import { withModuleFederation } from '@nx/angular/module-federation';
|
||||||
|
import config from './module-federation.config';
|
||||||
|
|
||||||
|
export default withModuleFederation(config);
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`Host App Generator should generate a host app with no remotes 1`] = `
|
exports[`Host App Generator should generate a host app with no remotes 1`] = `
|
||||||
"const { withModuleFederation } = require('@nx/angular/module-federation');
|
"const { withModuleFederation } = require('@nx/angular/module-federation');
|
||||||
const config = require('./module-federation.config');
|
const config = require('./module-federation.config');
|
||||||
@ -406,6 +817,14 @@ module.exports = withModuleFederation(config);
|
|||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`Host App Generator should generate a host app with no remotes when --typescript=true 1`] = `
|
||||||
|
"import { withModuleFederation } from '@nx/angular/module-federation';
|
||||||
|
import config from './module-federation.config';
|
||||||
|
|
||||||
|
export default withModuleFederation(config);
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`Host App Generator should generate a host with remotes using standalone components 1`] = `
|
exports[`Host App Generator should generate a host with remotes using standalone components 1`] = `
|
||||||
"import { bootstrapApplication } from '@angular/platform-browser';
|
"import { bootstrapApplication } from '@angular/platform-browser';
|
||||||
import { appConfig } from './app/app.config';
|
import { appConfig } from './app/app.config';
|
||||||
|
|||||||
@ -0,0 +1,4 @@
|
|||||||
|
import {withModuleFederationForSSR} from '@nx/angular/module-federation';
|
||||||
|
import config from './module-federation.config';
|
||||||
|
|
||||||
|
export default withModuleFederationForSSR(config)
|
||||||
@ -18,11 +18,25 @@ describe('Host App Generator', () => {
|
|||||||
// ACT
|
// ACT
|
||||||
await generateTestHostApplication(tree, {
|
await generateTestHostApplication(tree, {
|
||||||
name: 'test',
|
name: 'test',
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ASSERT
|
// ASSERT
|
||||||
expect(tree.read('test/webpack.config.js', 'utf-8')).toMatchSnapshot();
|
expect(tree.read('test/webpack.config.js', 'utf-8')).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
it('should generate a host app with no remotes when --typescript=true', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await generateTestHostApplication(tree, {
|
||||||
|
name: 'test',
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('test/webpack.config.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
it('should generate a host app with a remote', async () => {
|
it('should generate a host app with a remote', async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
@ -30,18 +44,40 @@ describe('Host App Generator', () => {
|
|||||||
|
|
||||||
await generateTestRemoteApplication(tree, {
|
await generateTestRemoteApplication(tree, {
|
||||||
name: 'remote',
|
name: 'remote',
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ACT
|
// ACT
|
||||||
await generateTestHostApplication(tree, {
|
await generateTestHostApplication(tree, {
|
||||||
name: 'test',
|
name: 'test',
|
||||||
remotes: ['remote'],
|
remotes: ['remote'],
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ASSERT
|
// ASSERT
|
||||||
expect(tree.read('remote/webpack.config.js', 'utf-8')).toMatchSnapshot();
|
expect(tree.read('remote/webpack.config.js', 'utf-8')).toMatchSnapshot();
|
||||||
expect(tree.read('test/webpack.config.js', 'utf-8')).toMatchSnapshot();
|
expect(tree.read('test/webpack.config.js', 'utf-8')).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
it('should generate a host app with a remote when --typesscript=true', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
|
||||||
|
await generateTestRemoteApplication(tree, {
|
||||||
|
name: 'remote',
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await generateTestHostApplication(tree, {
|
||||||
|
name: 'test',
|
||||||
|
remotes: ['remote'],
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('remote/webpack.config.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read('test/webpack.config.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
it('should generate a host and any remotes that dont exist with correct routing setup', async () => {
|
it('should generate a host and any remotes that dont exist with correct routing setup', async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
@ -52,6 +88,7 @@ describe('Host App Generator', () => {
|
|||||||
await generateTestHostApplication(tree, {
|
await generateTestHostApplication(tree, {
|
||||||
name: 'hostApp',
|
name: 'hostApp',
|
||||||
remotes: ['remote1', 'remote2'],
|
remotes: ['remote1', 'remote2'],
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ASSERT
|
// ASSERT
|
||||||
@ -72,17 +109,49 @@ describe('Host App Generator', () => {
|
|||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should generate a host and any remotes that dont exist with correct routing setup when --typescript=true', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
|
||||||
|
await generateTestHostApplication(tree, {
|
||||||
|
name: 'hostApp',
|
||||||
|
remotes: ['remote1', 'remote2'],
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.exists('remote1/project.json')).toBeTruthy();
|
||||||
|
expect(tree.exists('remote2/project.json')).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
tree.read('host-app/module-federation.config.ts', 'utf-8')
|
||||||
|
).toContain(`'remote1', 'remote2'`);
|
||||||
|
expect(tree.read('host-app/src/app/app.component.html', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"<ul class="remote-menu">
|
||||||
|
<li><a routerLink="/">Home</a></li>
|
||||||
|
<li><a routerLink="remote1">Remote1</a></li>
|
||||||
|
<li><a routerLink="remote2">Remote2</a></li>
|
||||||
|
</ul>
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
it('should generate a host, integrate existing remotes and generate any remotes that dont exist', async () => {
|
it('should generate a host, integrate existing remotes and generate any remotes that dont exist', async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
await generateTestRemoteApplication(tree, {
|
await generateTestRemoteApplication(tree, {
|
||||||
name: 'remote1',
|
name: 'remote1',
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ACT
|
// ACT
|
||||||
await generateTestHostApplication(tree, {
|
await generateTestHostApplication(tree, {
|
||||||
name: 'hostApp',
|
name: 'hostApp',
|
||||||
remotes: ['remote1', 'remote2', 'remote3'],
|
remotes: ['remote1', 'remote2', 'remote3'],
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ASSERT
|
// ASSERT
|
||||||
@ -94,11 +163,36 @@ describe('Host App Generator', () => {
|
|||||||
).toContain(`'remote1', 'remote2', 'remote3'`);
|
).toContain(`'remote1', 'remote2', 'remote3'`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should generate a host, integrate existing remotes and generate any remotes that dont exist when --typescript=true', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
await generateTestRemoteApplication(tree, {
|
||||||
|
name: 'remote1',
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await generateTestHostApplication(tree, {
|
||||||
|
name: 'hostApp',
|
||||||
|
remotes: ['remote1', 'remote2', 'remote3'],
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.exists('remote1/project.json')).toBeTruthy();
|
||||||
|
expect(tree.exists('remote2/project.json')).toBeTruthy();
|
||||||
|
expect(tree.exists('remote3/project.json')).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
tree.read('host-app/module-federation.config.ts', 'utf-8')
|
||||||
|
).toContain(`'remote1', 'remote2', 'remote3'`);
|
||||||
|
});
|
||||||
|
|
||||||
it('should generate a host, integrate existing remotes and generate any remotes that dont exist, in a directory', async () => {
|
it('should generate a host, integrate existing remotes and generate any remotes that dont exist, in a directory', async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
await generateTestRemoteApplication(tree, {
|
await generateTestRemoteApplication(tree, {
|
||||||
name: 'remote1',
|
name: 'remote1',
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ACT
|
// ACT
|
||||||
@ -106,6 +200,7 @@ describe('Host App Generator', () => {
|
|||||||
name: 'hostApp',
|
name: 'hostApp',
|
||||||
directory: 'foo/hostApp',
|
directory: 'foo/hostApp',
|
||||||
remotes: ['remote1', 'remote2', 'remote3'],
|
remotes: ['remote1', 'remote2', 'remote3'],
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ASSERT
|
// ASSERT
|
||||||
@ -117,6 +212,31 @@ describe('Host App Generator', () => {
|
|||||||
).toContain(`'remote1', 'remote2', 'remote3'`);
|
).toContain(`'remote1', 'remote2', 'remote3'`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should generate a host, integrate existing remotes and generate any remotes that dont exist, in a directory when --typescript=true', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
await generateTestRemoteApplication(tree, {
|
||||||
|
name: 'remote1',
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await generateTestHostApplication(tree, {
|
||||||
|
name: 'hostApp',
|
||||||
|
directory: 'foo/hostApp',
|
||||||
|
remotes: ['remote1', 'remote2', 'remote3'],
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.exists('remote1/project.json')).toBeTruthy();
|
||||||
|
expect(tree.exists('foo/remote2/project.json')).toBeTruthy();
|
||||||
|
expect(tree.exists('foo/remote3/project.json')).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
tree.read('foo/host-app/module-federation.config.ts', 'utf-8')
|
||||||
|
).toContain(`'remote1', 'remote2', 'remote3'`);
|
||||||
|
});
|
||||||
|
|
||||||
it('should generate a host with remotes using standalone components', async () => {
|
it('should generate a host with remotes using standalone components', async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
@ -197,6 +317,7 @@ describe('Host App Generator', () => {
|
|||||||
await generateTestHostApplication(tree, {
|
await generateTestHostApplication(tree, {
|
||||||
name: 'test',
|
name: 'test',
|
||||||
ssr: true,
|
ssr: true,
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ASSERT
|
// ASSERT
|
||||||
@ -223,6 +344,41 @@ describe('Host App Generator', () => {
|
|||||||
expect(project.targets['serve-ssr']).toMatchSnapshot();
|
expect(project.targets['serve-ssr']).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should generate the correct files when --typescript=true', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await generateTestHostApplication(tree, {
|
||||||
|
name: 'test',
|
||||||
|
ssr: true,
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
const project = readProjectConfiguration(tree, 'test');
|
||||||
|
expect(
|
||||||
|
tree.read(`test/src/app/app.module.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(tree.read(`test/src/bootstrap.ts`, 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`test/src/bootstrap.server.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(tree.read(`test/src/main.server.ts`, 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read(`test/server.ts`, 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`test/module-federation.config.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`test/webpack.server.config.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`test/src/app/app.routes.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(project.targets.server).toMatchSnapshot();
|
||||||
|
expect(project.targets['serve-ssr']).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
it('should generate the correct files for standalone', async () => {
|
it('should generate the correct files for standalone', async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
@ -232,6 +388,7 @@ describe('Host App Generator', () => {
|
|||||||
name: 'test',
|
name: 'test',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
ssr: true,
|
ssr: true,
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ASSERT
|
// ASSERT
|
||||||
@ -261,6 +418,46 @@ describe('Host App Generator', () => {
|
|||||||
expect(project.targets.server).toMatchSnapshot();
|
expect(project.targets.server).toMatchSnapshot();
|
||||||
expect(project.targets['serve-ssr']).toMatchSnapshot();
|
expect(project.targets['serve-ssr']).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should generate the correct files for standalone when --typescript=true', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await generateTestHostApplication(tree, {
|
||||||
|
name: 'test',
|
||||||
|
standalone: true,
|
||||||
|
ssr: true,
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
const project = readProjectConfiguration(tree, 'test');
|
||||||
|
expect(tree.exists(`test/src/app/app.module.ts`)).toBeFalsy();
|
||||||
|
expect(tree.read(`test/src/bootstrap.ts`, 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`test/src/bootstrap.server.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(tree.read(`test/src/main.server.ts`, 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read(`test/server.ts`, 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`test/module-federation.config.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`test/webpack.server.config.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`test/src/app/app.routes.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`test/src/app/app.config.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`test/src/app/app.config.server.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(project.targets.server).toMatchSnapshot();
|
||||||
|
expect(project.targets['serve-ssr']).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should error correctly when Angular version does not support standalone', async () => {
|
it('should error correctly when Angular version does not support standalone', async () => {
|
||||||
@ -291,6 +488,7 @@ describe('Host App Generator', () => {
|
|||||||
await generateTestRemoteApplication(tree, {
|
await generateTestRemoteApplication(tree, {
|
||||||
name: 'remote1',
|
name: 'remote1',
|
||||||
projectNameAndRootFormat: 'derived',
|
projectNameAndRootFormat: 'derived',
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ACT
|
// ACT
|
||||||
@ -298,6 +496,7 @@ describe('Host App Generator', () => {
|
|||||||
name: 'hostApp',
|
name: 'hostApp',
|
||||||
remotes: ['remote1', 'remote2', 'remote3'],
|
remotes: ['remote1', 'remote2', 'remote3'],
|
||||||
projectNameAndRootFormat: 'derived',
|
projectNameAndRootFormat: 'derived',
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ASSERT
|
// ASSERT
|
||||||
@ -315,6 +514,7 @@ describe('Host App Generator', () => {
|
|||||||
await generateTestRemoteApplication(tree, {
|
await generateTestRemoteApplication(tree, {
|
||||||
name: 'remote1',
|
name: 'remote1',
|
||||||
projectNameAndRootFormat: 'derived',
|
projectNameAndRootFormat: 'derived',
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ACT
|
// ACT
|
||||||
@ -323,6 +523,7 @@ describe('Host App Generator', () => {
|
|||||||
directory: 'foo',
|
directory: 'foo',
|
||||||
remotes: ['remote1', 'remote2', 'remote3'],
|
remotes: ['remote1', 'remote2', 'remote3'],
|
||||||
projectNameAndRootFormat: 'derived',
|
projectNameAndRootFormat: 'derived',
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ASSERT
|
// ASSERT
|
||||||
@ -333,5 +534,31 @@ describe('Host App Generator', () => {
|
|||||||
tree.read('apps/foo/host-app/module-federation.config.js', 'utf-8')
|
tree.read('apps/foo/host-app/module-federation.config.js', 'utf-8')
|
||||||
).toContain(`'remote1', 'foo-remote2', 'foo-remote3'`);
|
).toContain(`'remote1', 'foo-remote2', 'foo-remote3'`);
|
||||||
});
|
});
|
||||||
|
it('should generate a host, integrate existing remotes and generate any remotes that dont exist, in a directory when --typescript=true', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
await generateTestRemoteApplication(tree, {
|
||||||
|
name: 'remote1',
|
||||||
|
projectNameAndRootFormat: 'derived',
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await generateTestHostApplication(tree, {
|
||||||
|
name: 'hostApp',
|
||||||
|
directory: 'foo',
|
||||||
|
remotes: ['remote1', 'remote2', 'remote3'],
|
||||||
|
projectNameAndRootFormat: 'derived',
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.exists('apps/remote1/project.json')).toBeTruthy();
|
||||||
|
expect(tree.exists('apps/foo/remote2/project.json')).toBeTruthy();
|
||||||
|
expect(tree.exists('apps/foo/remote3/project.json')).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
tree.read('apps/foo/host-app/module-federation.config.ts', 'utf-8')
|
||||||
|
).toContain(`'remote1', 'foo-remote2', 'foo-remote3'`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -23,14 +23,16 @@ export async function host(tree: Tree, options: Schema) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function hostInternal(tree: Tree, options: Schema) {
|
export async function hostInternal(tree: Tree, schema: Schema) {
|
||||||
const installedAngularVersionInfo = getInstalledAngularVersionInfo(tree);
|
const installedAngularVersionInfo = getInstalledAngularVersionInfo(tree);
|
||||||
|
|
||||||
if (lt(installedAngularVersionInfo.version, '14.1.0') && options.standalone) {
|
if (lt(installedAngularVersionInfo.version, '14.1.0') && schema.standalone) {
|
||||||
throw new Error(stripIndents`The "standalone" option is only supported in Angular >= 14.1.0. You are currently using ${installedAngularVersionInfo.version}.
|
throw new Error(stripIndents`The "standalone" option is only supported in Angular >= 14.1.0. You are currently using ${installedAngularVersionInfo.version}.
|
||||||
You can resolve this error by removing the "standalone" option or by migrating to Angular 14.1.0.`);
|
You can resolve this error by removing the "standalone" option or by migrating to Angular 14.1.0.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { typescriptConfiguration = true, ...options }: Schema = schema;
|
||||||
|
|
||||||
const projects = getProjects(tree);
|
const projects = getProjects(tree);
|
||||||
|
|
||||||
const remotesToGenerate: string[] = [];
|
const remotesToGenerate: string[] = [];
|
||||||
@ -78,11 +80,17 @@ export async function hostInternal(tree: Tree, options: Schema) {
|
|||||||
skipE2E,
|
skipE2E,
|
||||||
e2eProjectName: skipE2E ? undefined : `${hostProjectName}-e2e`,
|
e2eProjectName: skipE2E ? undefined : `${hostProjectName}-e2e`,
|
||||||
prefix: options.prefix,
|
prefix: options.prefix,
|
||||||
|
typescriptConfiguration,
|
||||||
});
|
});
|
||||||
|
|
||||||
let installTasks = [appInstallTask];
|
let installTasks = [appInstallTask];
|
||||||
if (options.ssr) {
|
if (options.ssr) {
|
||||||
let ssrInstallTask = await addSsr(tree, options, hostProjectName);
|
let ssrInstallTask = await addSsr(
|
||||||
|
tree,
|
||||||
|
options,
|
||||||
|
hostProjectName,
|
||||||
|
typescriptConfiguration
|
||||||
|
);
|
||||||
installTasks.push(ssrInstallTask);
|
installTasks.push(ssrInstallTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,6 +115,7 @@ export async function hostInternal(tree: Tree, options: Schema) {
|
|||||||
host: hostProjectName,
|
host: hostProjectName,
|
||||||
skipFormat: true,
|
skipFormat: true,
|
||||||
standalone: options.standalone,
|
standalone: options.standalone,
|
||||||
|
typescriptConfiguration,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,12 @@ import {
|
|||||||
} from '../../../utils/versions';
|
} from '../../../utils/versions';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
|
||||||
export async function addSsr(tree: Tree, options: Schema, appName: string) {
|
export async function addSsr(
|
||||||
|
tree: Tree,
|
||||||
|
options: Schema,
|
||||||
|
appName: string,
|
||||||
|
typescriptConfiguration: boolean
|
||||||
|
) {
|
||||||
let project = readProjectConfiguration(tree, appName);
|
let project = readProjectConfiguration(tree, appName);
|
||||||
|
|
||||||
await setupSsr(tree, {
|
await setupSsr(tree, {
|
||||||
@ -40,19 +45,33 @@ export async function addSsr(tree: Tree, options: Schema, appName: string) {
|
|||||||
'browser'
|
'browser'
|
||||||
);
|
);
|
||||||
|
|
||||||
generateFiles(tree, join(__dirname, '../files'), project.root, {
|
generateFiles(tree, join(__dirname, '../files/common'), project.root, {
|
||||||
appName,
|
appName,
|
||||||
browserBundleOutput,
|
browserBundleOutput,
|
||||||
standalone: options.standalone,
|
standalone: options.standalone,
|
||||||
tmpl: '',
|
tmpl: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const pathToTemplateFiles = typescriptConfiguration ? 'ts' : 'js';
|
||||||
|
|
||||||
|
generateFiles(
|
||||||
|
tree,
|
||||||
|
join(__dirname, '../files', pathToTemplateFiles),
|
||||||
|
project.root,
|
||||||
|
{
|
||||||
|
tmpl: '',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// update project.json
|
// update project.json
|
||||||
project = readProjectConfiguration(tree, appName);
|
project = readProjectConfiguration(tree, appName);
|
||||||
|
|
||||||
project.targets.server.executor = '@nx/angular:webpack-server';
|
project.targets.server.executor = '@nx/angular:webpack-server';
|
||||||
project.targets.server.options.customWebpackConfig = {
|
project.targets.server.options.customWebpackConfig = {
|
||||||
path: joinPathFragments(project.root, 'webpack.server.config.js'),
|
path: joinPathFragments(
|
||||||
|
project.root,
|
||||||
|
`webpack.server.config.${pathToTemplateFiles}`
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
project.targets['serve-ssr'].executor =
|
project.targets['serve-ssr'].executor =
|
||||||
|
|||||||
@ -29,4 +29,5 @@ export interface Schema {
|
|||||||
skipFormat?: boolean;
|
skipFormat?: boolean;
|
||||||
standalone?: boolean;
|
standalone?: boolean;
|
||||||
ssr?: boolean;
|
ssr?: boolean;
|
||||||
|
typescriptConfiguration?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -173,6 +173,11 @@
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": false,
|
"default": false,
|
||||||
"x-priority": "important"
|
"x-priority": "important"
|
||||||
|
},
|
||||||
|
"typescriptConfiguration": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether the module federation configuration and webpack configuration files should use TS.",
|
||||||
|
"default": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
|
|||||||
@ -230,6 +230,241 @@ exports[`MF Remote App Generator --ssr should generate the correct files 13`] =
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`MF Remote App Generator --ssr should generate the correct files when --typescriptConfiguration=true 1`] = `
|
||||||
|
"import { NgModule } from '@angular/core';
|
||||||
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [AppComponent],
|
||||||
|
imports: [
|
||||||
|
BrowserModule,
|
||||||
|
RouterModule.forRoot(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
loadChildren: () =>
|
||||||
|
import('./remote-entry/entry.module').then(
|
||||||
|
(m) => m.RemoteEntryModule
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{ initialNavigation: 'enabledBlocking' }
|
||||||
|
),
|
||||||
|
],
|
||||||
|
providers: [],
|
||||||
|
bootstrap: [AppComponent],
|
||||||
|
})
|
||||||
|
export class AppModule {}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`MF Remote App Generator --ssr should generate the correct files when --typescriptConfiguration=true 2`] = `
|
||||||
|
"import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||||
|
import { AppModule } from './app/app.module';
|
||||||
|
|
||||||
|
platformBrowserDynamic()
|
||||||
|
.bootstrapModule(AppModule)
|
||||||
|
.catch((err) => console.error(err));
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`MF Remote App Generator --ssr should generate the correct files when --typescriptConfiguration=true 3`] = `
|
||||||
|
"export { AppServerModule } from './app/app.server.module';
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`MF Remote App Generator --ssr should generate the correct files when --typescriptConfiguration=true 4`] = `
|
||||||
|
"import 'zone.js/dist/zone-node';
|
||||||
|
|
||||||
|
import { APP_BASE_HREF } from '@angular/common';
|
||||||
|
import { ngExpressEngine } from '@nguniversal/express-engine';
|
||||||
|
import * as express from 'express';
|
||||||
|
import * as cors from 'cors';
|
||||||
|
import { existsSync } from 'fs';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
|
import { AppServerModule } from './bootstrap.server';
|
||||||
|
|
||||||
|
// The Express app is exported so that it can be used by serverless Functions.
|
||||||
|
export function app(): express.Express {
|
||||||
|
const server = express();
|
||||||
|
const browserBundles = join(process.cwd(), 'dist/test/browser');
|
||||||
|
const serverBundles = join(process.cwd(), 'dist/test/server');
|
||||||
|
|
||||||
|
server.use(cors());
|
||||||
|
const indexHtml = existsSync(join(browserBundles, 'index.original.html'))
|
||||||
|
? 'index.original.html'
|
||||||
|
: 'index';
|
||||||
|
|
||||||
|
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/main/modules/express-engine)
|
||||||
|
server.engine(
|
||||||
|
'html',
|
||||||
|
ngExpressEngine({
|
||||||
|
bootstrap: AppServerModule,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
server.set('view engine', 'html');
|
||||||
|
server.set('views', browserBundles);
|
||||||
|
|
||||||
|
// Example Express Rest API endpoints
|
||||||
|
// server.get('/api/**', (req, res) => { });
|
||||||
|
// Serve static files from /browser
|
||||||
|
// serve static files
|
||||||
|
server.use('/', express.static(browserBundles, { maxAge: '1y' }));
|
||||||
|
server.use('/server', express.static(serverBundles, { maxAge: '1y' }));
|
||||||
|
|
||||||
|
// All regular routes use the Universal engine
|
||||||
|
server.get('*', (req, res) => {
|
||||||
|
res.render(indexHtml, {
|
||||||
|
req,
|
||||||
|
providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
function run(): void {
|
||||||
|
const port = process.env['PORT'] || 4000;
|
||||||
|
|
||||||
|
// Start up the Node server
|
||||||
|
const server = app();
|
||||||
|
server.listen(port, () => {
|
||||||
|
console.log(\`Node Express server listening on http://localhost:\${port}\`);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DO NOT REMOVE IF USING @nx/angular:module-federation-dev-ssr executor
|
||||||
|
* to serve your Host application with this Remote application.
|
||||||
|
* This message allows Nx to determine when the Remote is ready to be
|
||||||
|
* consumed by the Host.
|
||||||
|
*/
|
||||||
|
process.send && process.send('nx.server.ready');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
||||||
|
|
||||||
|
export * from './bootstrap.server';
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`MF Remote App Generator --ssr should generate the correct files when --typescriptConfiguration=true 5`] = `
|
||||||
|
"import('./src/main.server');
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`MF Remote App Generator --ssr should generate the correct files when --typescriptConfiguration=true 6`] = `
|
||||||
|
"import { ModuleFederationConfig } from '@nx/webpack';
|
||||||
|
|
||||||
|
const config: ModuleFederationConfig = {
|
||||||
|
name: 'test',
|
||||||
|
exposes: {
|
||||||
|
'./Module': 'test/src/app/remote-entry/entry.module.ts',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`MF Remote App Generator --ssr should generate the correct files when --typescriptConfiguration=true 7`] = `
|
||||||
|
"import { withModuleFederationForSSR } from '@nx/angular/module-federation';
|
||||||
|
import config from './module-federation.config';
|
||||||
|
|
||||||
|
export default withModuleFederationForSSR(config);
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`MF Remote App Generator --ssr should generate the correct files when --typescriptConfiguration=true 8`] = `
|
||||||
|
"import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'proj-test-entry',
|
||||||
|
template: \`<proj-nx-welcome></proj-nx-welcome>\`,
|
||||||
|
})
|
||||||
|
export class RemoteEntryComponent {}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`MF Remote App Generator --ssr should generate the correct files when --typescriptConfiguration=true 9`] = `
|
||||||
|
"import { Route } from '@angular/router';
|
||||||
|
|
||||||
|
export const appRoutes: Route[] = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
loadChildren: () =>
|
||||||
|
import('./remote-entry/entry.module').then((m) => m.RemoteEntryModule),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`MF Remote App Generator --ssr should generate the correct files when --typescriptConfiguration=true 10`] = `
|
||||||
|
"import { Route } from '@angular/router';
|
||||||
|
import { RemoteEntryComponent } from './entry.component';
|
||||||
|
|
||||||
|
export const remoteRoutes: Route[] = [
|
||||||
|
{ path: '', component: RemoteEntryComponent },
|
||||||
|
];
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`MF Remote App Generator --ssr should generate the correct files when --typescriptConfiguration=true 11`] = `
|
||||||
|
{
|
||||||
|
"configurations": {
|
||||||
|
"development": {
|
||||||
|
"buildOptimizer": false,
|
||||||
|
"extractLicenses": false,
|
||||||
|
"optimization": false,
|
||||||
|
"sourceMap": true,
|
||||||
|
"vendorChunk": true,
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
"outputHashing": "media",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "production",
|
||||||
|
"dependsOn": [
|
||||||
|
"build",
|
||||||
|
],
|
||||||
|
"executor": "@nx/angular:webpack-server",
|
||||||
|
"options": {
|
||||||
|
"customWebpackConfig": {
|
||||||
|
"path": "test/webpack.server.config.ts",
|
||||||
|
},
|
||||||
|
"main": "test/server.ts",
|
||||||
|
"outputPath": "dist/test/server",
|
||||||
|
"tsConfig": "test/tsconfig.server.json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`MF Remote App Generator --ssr should generate the correct files when --typescriptConfiguration=true 12`] = `
|
||||||
|
"import { Route } from '@angular/router';
|
||||||
|
import { RemoteEntryComponent } from './entry.component';
|
||||||
|
|
||||||
|
export const remoteRoutes: Route[] = [
|
||||||
|
{ path: '', component: RemoteEntryComponent },
|
||||||
|
];
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`MF Remote App Generator --ssr should generate the correct files when --typescriptConfiguration=true 13`] = `
|
||||||
|
{
|
||||||
|
"dependsOn": [
|
||||||
|
"build",
|
||||||
|
"server",
|
||||||
|
],
|
||||||
|
"executor": "nx:run-commands",
|
||||||
|
"options": {
|
||||||
|
"command": "PORT=4201 node dist/test/server/main.js",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`MF Remote App Generator should generate a remote mf app with a host 1`] = `
|
exports[`MF Remote App Generator should generate a remote mf app with a host 1`] = `
|
||||||
"const { withModuleFederation } = require('@nx/angular/module-federation');
|
"const { withModuleFederation } = require('@nx/angular/module-federation');
|
||||||
const config = require('./module-federation.config');
|
const config = require('./module-federation.config');
|
||||||
@ -244,6 +479,22 @@ module.exports = withModuleFederation(config);
|
|||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`MF Remote App Generator should generate a remote mf app with a host when --typescriptConfiguration=true 1`] = `
|
||||||
|
"import { withModuleFederation } from '@nx/angular/module-federation';
|
||||||
|
import config from './module-federation.config';
|
||||||
|
|
||||||
|
export default withModuleFederation(config);
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`MF Remote App Generator should generate a remote mf app with a host when --typescriptConfiguration=true 2`] = `
|
||||||
|
"import { withModuleFederation } from '@nx/angular/module-federation';
|
||||||
|
import config from './module-federation.config';
|
||||||
|
|
||||||
|
export default withModuleFederation(config);
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`MF Remote App Generator should generate a remote mf app with no host 1`] = `
|
exports[`MF Remote App Generator should generate a remote mf app with no host 1`] = `
|
||||||
"const { withModuleFederation } = require('@nx/angular/module-federation');
|
"const { withModuleFederation } = require('@nx/angular/module-federation');
|
||||||
const config = require('./module-federation.config');
|
const config = require('./module-federation.config');
|
||||||
@ -251,6 +502,14 @@ module.exports = withModuleFederation(config);
|
|||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`MF Remote App Generator should generate a remote mf app with no host when --typescriptConfiguration=true 1`] = `
|
||||||
|
"import { withModuleFederation } from '@nx/angular/module-federation';
|
||||||
|
import config from './module-federation.config';
|
||||||
|
|
||||||
|
export default withModuleFederation(config);
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`MF Remote App Generator should generate the a remote setup for standalone components 1`] = `
|
exports[`MF Remote App Generator should generate the a remote setup for standalone components 1`] = `
|
||||||
"import { bootstrapApplication } from '@angular/platform-browser';
|
"import { bootstrapApplication } from '@angular/platform-browser';
|
||||||
import { appConfig } from './app/app.config';
|
import { appConfig } from './app/app.config';
|
||||||
@ -309,3 +568,66 @@ export const remoteRoutes: Route[] = [
|
|||||||
];
|
];
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`MF Remote App Generator should generate the a remote setup for standalone components when --typescriptConfiguration=true 1`] = `
|
||||||
|
"import { bootstrapApplication } from '@angular/platform-browser';
|
||||||
|
import { appConfig } from './app/app.config';
|
||||||
|
import { RemoteEntryComponent } from './app/remote-entry/entry.component';
|
||||||
|
|
||||||
|
bootstrapApplication(RemoteEntryComponent, appConfig).catch((err) =>
|
||||||
|
console.error(err)
|
||||||
|
);
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`MF Remote App Generator should generate the a remote setup for standalone components when --typescriptConfiguration=true 2`] = `
|
||||||
|
"import { ModuleFederationConfig } from '@nx/webpack';
|
||||||
|
|
||||||
|
const config: ModuleFederationConfig = {
|
||||||
|
name: 'test',
|
||||||
|
exposes: {
|
||||||
|
'./Routes': 'test/src/app/remote-entry/entry.routes.ts',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`MF Remote App Generator should generate the a remote setup for standalone components when --typescriptConfiguration=true 3`] = `
|
||||||
|
"import { Component } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NxWelcomeComponent } from './nx-welcome.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, NxWelcomeComponent],
|
||||||
|
selector: 'proj-test-entry',
|
||||||
|
template: \`<proj-nx-welcome></proj-nx-welcome>\`,
|
||||||
|
})
|
||||||
|
export class RemoteEntryComponent {}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`MF Remote App Generator should generate the a remote setup for standalone components when --typescriptConfiguration=true 4`] = `
|
||||||
|
"import { Route } from '@angular/router';
|
||||||
|
|
||||||
|
export const appRoutes: Route[] = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
loadChildren: () =>
|
||||||
|
import('./remote-entry/entry.routes').then((m) => m.remoteRoutes),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`MF Remote App Generator should generate the a remote setup for standalone components when --typescriptConfiguration=true 5`] = `
|
||||||
|
"import { Route } from '@angular/router';
|
||||||
|
import { RemoteEntryComponent } from './entry.component';
|
||||||
|
|
||||||
|
export const remoteRoutes: Route[] = [
|
||||||
|
{ path: '', component: RemoteEntryComponent },
|
||||||
|
];
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|||||||
@ -0,0 +1,4 @@
|
|||||||
|
import {withModuleFederationForSSR} from '@nx/angular/module-federation';
|
||||||
|
import config from './module-federation.config';
|
||||||
|
|
||||||
|
export default withModuleFederationForSSR(config)
|
||||||
@ -22,7 +22,13 @@ export async function addSsr(
|
|||||||
appName,
|
appName,
|
||||||
port,
|
port,
|
||||||
standalone,
|
standalone,
|
||||||
}: { appName: string; port: number; standalone: boolean }
|
typescriptConfiguration,
|
||||||
|
}: {
|
||||||
|
appName: string;
|
||||||
|
port: number;
|
||||||
|
standalone: boolean;
|
||||||
|
typescriptConfiguration: boolean;
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
let project = readProjectConfiguration(tree, appName);
|
let project = readProjectConfiguration(tree, appName);
|
||||||
|
|
||||||
@ -52,7 +58,7 @@ export async function addSsr(
|
|||||||
|
|
||||||
generateFiles(
|
generateFiles(
|
||||||
tree,
|
tree,
|
||||||
joinPathFragments(__dirname, '../files/base'),
|
joinPathFragments(__dirname, `../files/common`),
|
||||||
project.root,
|
project.root,
|
||||||
{
|
{
|
||||||
appName,
|
appName,
|
||||||
@ -63,6 +69,17 @@ export async function addSsr(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const pathToTemplateFiles = typescriptConfiguration ? 'base-ts' : 'base';
|
||||||
|
|
||||||
|
generateFiles(
|
||||||
|
tree,
|
||||||
|
joinPathFragments(__dirname, `../files/${pathToTemplateFiles}`),
|
||||||
|
project.root,
|
||||||
|
{
|
||||||
|
tmpl: '',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (standalone) {
|
if (standalone) {
|
||||||
generateFiles(
|
generateFiles(
|
||||||
tree,
|
tree,
|
||||||
@ -81,7 +98,10 @@ export async function addSsr(
|
|||||||
|
|
||||||
project.targets.server.executor = '@nx/angular:webpack-server';
|
project.targets.server.executor = '@nx/angular:webpack-server';
|
||||||
project.targets.server.options.customWebpackConfig = {
|
project.targets.server.options.customWebpackConfig = {
|
||||||
path: joinPathFragments(project.root, 'webpack.server.config.js'),
|
path: joinPathFragments(
|
||||||
|
project.root,
|
||||||
|
`webpack.server.config.${typescriptConfiguration ? 'ts' : 'js'}`
|
||||||
|
),
|
||||||
};
|
};
|
||||||
project.targets['serve-ssr'].options = {
|
project.targets['serve-ssr'].options = {
|
||||||
...(project.targets['serve-ssr'].options ?? {}),
|
...(project.targets['serve-ssr'].options ?? {}),
|
||||||
|
|||||||
@ -23,6 +23,7 @@ describe('MF Remote App Generator', () => {
|
|||||||
await generateTestRemoteApplication(tree, {
|
await generateTestRemoteApplication(tree, {
|
||||||
name: 'test',
|
name: 'test',
|
||||||
port: 4201,
|
port: 4201,
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ASSERT
|
// ASSERT
|
||||||
@ -33,18 +34,35 @@ describe('MF Remote App Generator', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should generate a remote mf app with no host when --typescriptConfiguration=true', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await generateTestRemoteApplication(tree, {
|
||||||
|
name: 'test',
|
||||||
|
port: 4201,
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('test/webpack.config.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
it('should generate a remote mf app with a host', async () => {
|
it('should generate a remote mf app with a host', async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
|
||||||
await generateTestHostApplication(tree, {
|
await generateTestHostApplication(tree, {
|
||||||
name: 'host',
|
name: 'host',
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ACT
|
// ACT
|
||||||
await generateTestRemoteApplication(tree, {
|
await generateTestRemoteApplication(tree, {
|
||||||
name: 'test',
|
name: 'test',
|
||||||
host: 'host',
|
host: 'host',
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ASSERT
|
// ASSERT
|
||||||
@ -52,6 +70,27 @@ describe('MF Remote App Generator', () => {
|
|||||||
expect(tree.read('test/webpack.config.js', 'utf-8')).toMatchSnapshot();
|
expect(tree.read('test/webpack.config.js', 'utf-8')).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should generate a remote mf app with a host when --typescriptConfiguration=true', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
|
||||||
|
await generateTestHostApplication(tree, {
|
||||||
|
name: 'host',
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await generateTestRemoteApplication(tree, {
|
||||||
|
name: 'test',
|
||||||
|
host: 'host',
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('host/webpack.config.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read('test/webpack.config.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
it('should error when a remote app is attempted to be generated with an incorrect host', async () => {
|
it('should error when a remote app is attempted to be generated with an incorrect host', async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
@ -125,6 +164,7 @@ describe('MF Remote App Generator', () => {
|
|||||||
await generateTestRemoteApplication(tree, {
|
await generateTestRemoteApplication(tree, {
|
||||||
name: 'test',
|
name: 'test',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ASSERT
|
// ASSERT
|
||||||
@ -150,6 +190,36 @@ describe('MF Remote App Generator', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should generate the a remote setup for standalone components when --typescriptConfiguration=true', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await generateTestRemoteApplication(tree, {
|
||||||
|
name: 'test',
|
||||||
|
standalone: true,
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.exists(`test/src/app/app.module.ts`)).toBeFalsy();
|
||||||
|
expect(tree.exists(`test/src/app/app.component.ts`)).toBeFalsy();
|
||||||
|
expect(
|
||||||
|
tree.exists(`test/src/app/remote-entry/entry.module.ts`)
|
||||||
|
).toBeFalsy();
|
||||||
|
expect(tree.read(`test/src/bootstrap.ts`, 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`test/module-federation.config.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`test/src/app/remote-entry/entry.component.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(tree.read(`test/src/app/app.routes.ts`, 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`test/src/app/remote-entry/entry.routes.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
it('should not generate an e2e project when e2eTestRunner is none', async () => {
|
it('should not generate an e2e project when e2eTestRunner is none', async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
@ -217,6 +287,7 @@ describe('MF Remote App Generator', () => {
|
|||||||
await generateTestRemoteApplication(tree, {
|
await generateTestRemoteApplication(tree, {
|
||||||
name: 'test',
|
name: 'test',
|
||||||
ssr: true,
|
ssr: true,
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ASSERT
|
// ASSERT
|
||||||
@ -254,6 +325,53 @@ describe('MF Remote App Generator', () => {
|
|||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
expect(project.targets['static-server']).toMatchSnapshot();
|
expect(project.targets['static-server']).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should generate the correct files when --typescriptConfiguration=true', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await generateTestRemoteApplication(tree, {
|
||||||
|
name: 'test',
|
||||||
|
ssr: true,
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
const project = readProjectConfiguration(tree, 'test');
|
||||||
|
expect(
|
||||||
|
tree.exists(`test/src/app/remote-entry/entry.module.ts`)
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(
|
||||||
|
tree.read(`test/src/app/app.module.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(tree.read(`test/src/bootstrap.ts`, 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`test/src/bootstrap.server.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(tree.read(`test/src/main.server.ts`, 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read(`test/server.ts`, 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`test/module-federation.config.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`test/webpack.server.config.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`test/src/app/remote-entry/entry.component.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`test/src/app/app.routes.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`test/src/app/remote-entry/entry.routes.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(project.targets.server).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read(`test/src/app/remote-entry/entry.routes.ts`, 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(project.targets['static-server']).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should error correctly when Angular version does not support standalone', async () => {
|
it('should error correctly when Angular version does not support standalone', async () => {
|
||||||
@ -285,6 +403,7 @@ describe('MF Remote App Generator', () => {
|
|||||||
name: 'test',
|
name: 'test',
|
||||||
port: 4201,
|
port: 4201,
|
||||||
projectNameAndRootFormat: 'derived',
|
projectNameAndRootFormat: 'derived',
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(tree.exists('apps/test/webpack.config.js')).toBe(true);
|
expect(tree.exists('apps/test/webpack.config.js')).toBe(true);
|
||||||
@ -299,6 +418,7 @@ describe('MF Remote App Generator', () => {
|
|||||||
port: 4201,
|
port: 4201,
|
||||||
directory: 'shared',
|
directory: 'shared',
|
||||||
projectNameAndRootFormat: 'derived',
|
projectNameAndRootFormat: 'derived',
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(tree.exists('apps/shared/test/webpack.config.js')).toBe(true);
|
expect(tree.exists('apps/shared/test/webpack.config.js')).toBe(true);
|
||||||
|
|||||||
@ -21,14 +21,16 @@ export async function remote(tree: Tree, options: Schema) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remoteInternal(tree: Tree, options: Schema) {
|
export async function remoteInternal(tree: Tree, schema: Schema) {
|
||||||
const installedAngularVersionInfo = getInstalledAngularVersionInfo(tree);
|
const installedAngularVersionInfo = getInstalledAngularVersionInfo(tree);
|
||||||
|
|
||||||
if (lt(installedAngularVersionInfo.version, '14.1.0') && options.standalone) {
|
if (lt(installedAngularVersionInfo.version, '14.1.0') && schema.standalone) {
|
||||||
throw new Error(stripIndents`The "standalone" option is only supported in Angular >= 14.1.0. You are currently using ${installedAngularVersionInfo.version}.
|
throw new Error(stripIndents`The "standalone" option is only supported in Angular >= 14.1.0. You are currently using ${installedAngularVersionInfo.version}.
|
||||||
You can resolve this error by removing the "standalone" option or by migrating to Angular 14.1.0.`);
|
You can resolve this error by removing the "standalone" option or by migrating to Angular 14.1.0.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { typescriptConfiguration = true, ...options }: Schema = schema;
|
||||||
|
|
||||||
const projects = getProjects(tree);
|
const projects = getProjects(tree);
|
||||||
if (options.host && !projects.has(options.host)) {
|
if (options.host && !projects.has(options.host)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -71,6 +73,7 @@ export async function remoteInternal(tree: Tree, options: Schema) {
|
|||||||
e2eProjectName: skipE2E ? undefined : `${remoteProjectName}-e2e`,
|
e2eProjectName: skipE2E ? undefined : `${remoteProjectName}-e2e`,
|
||||||
standalone: options.standalone,
|
standalone: options.standalone,
|
||||||
prefix: options.prefix,
|
prefix: options.prefix,
|
||||||
|
typescriptConfiguration,
|
||||||
});
|
});
|
||||||
|
|
||||||
let installTasks = [appInstallTask];
|
let installTasks = [appInstallTask];
|
||||||
@ -78,6 +81,7 @@ export async function remoteInternal(tree: Tree, options: Schema) {
|
|||||||
let ssrInstallTask = await addSsr(tree, {
|
let ssrInstallTask = await addSsr(tree, {
|
||||||
appName: remoteProjectName,
|
appName: remoteProjectName,
|
||||||
port,
|
port,
|
||||||
|
typescriptConfiguration,
|
||||||
standalone: options.standalone,
|
standalone: options.standalone,
|
||||||
});
|
});
|
||||||
installTasks.push(ssrInstallTask);
|
installTasks.push(ssrInstallTask);
|
||||||
|
|||||||
@ -28,4 +28,5 @@ export interface Schema {
|
|||||||
skipFormat?: boolean;
|
skipFormat?: boolean;
|
||||||
standalone?: boolean;
|
standalone?: boolean;
|
||||||
ssr?: boolean;
|
ssr?: boolean;
|
||||||
|
typescriptConfiguration?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -166,6 +166,11 @@
|
|||||||
"description": "Whether to configure SSR for the remote application to be consumed by a host application using SSR.",
|
"description": "Whether to configure SSR for the remote application to be consumed by a host application using SSR.",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": false
|
"default": false
|
||||||
|
},
|
||||||
|
"typescriptConfiguration": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether the module federation configuration and webpack configuration files should use TS.",
|
||||||
|
"default": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
|
|||||||
@ -10,6 +10,16 @@ fetch('/assets/module-federation.manifest.json')
|
|||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`Init MF --federationType=dynamic should create a host with the correct configurations when --typescriptConfiguration=true 1`] = `
|
||||||
|
"import { setRemoteDefinitions } from '@nx/angular/mf';
|
||||||
|
|
||||||
|
fetch('/assets/module-federation.manifest.json')
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((definitions) => setRemoteDefinitions(definitions))
|
||||||
|
.then(() => import('./bootstrap').catch((err) => console.error(err)));
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`Init MF should add a remote application and add it to a specified host applications router config 1`] = `
|
exports[`Init MF should add a remote application and add it to a specified host applications router config 1`] = `
|
||||||
"import { NxWelcomeComponent } from './nx-welcome.component';
|
"import { NxWelcomeComponent } from './nx-welcome.component';
|
||||||
import { Route } from '@angular/router';
|
import { Route } from '@angular/router';
|
||||||
@ -41,6 +51,18 @@ exports[`Init MF should add a remote application and add it to a specified host
|
|||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`Init MF should add a remote application and add it to a specified host applications webpack config that contains a remote application already when --typescriptConfiguration=true 1`] = `
|
||||||
|
"import { ModuleFederationConfig } from '@nx/webpack';
|
||||||
|
|
||||||
|
const config: ModuleFederationConfig = {
|
||||||
|
name: 'app1',
|
||||||
|
remotes: ['remote1', 'remote2'],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`Init MF should add a remote application and add it to a specified host applications webpack config when no other remote has been added to it 1`] = `
|
exports[`Init MF should add a remote application and add it to a specified host applications webpack config when no other remote has been added to it 1`] = `
|
||||||
"module.exports = {
|
"module.exports = {
|
||||||
name: 'app1',
|
name: 'app1',
|
||||||
@ -49,6 +71,18 @@ exports[`Init MF should add a remote application and add it to a specified host
|
|||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`Init MF should add a remote application and add it to a specified host applications webpack config when no other remote has been added to it when --typescriptConfiguration=true 1`] = `
|
||||||
|
"import { ModuleFederationConfig } from '@nx/webpack';
|
||||||
|
|
||||||
|
const config: ModuleFederationConfig = {
|
||||||
|
name: 'app1',
|
||||||
|
remotes: ['remote1'],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`Init MF should add a remote to dynamic host correctly 1`] = `
|
exports[`Init MF should add a remote to dynamic host correctly 1`] = `
|
||||||
"import { NxWelcomeComponent } from './nx-welcome.component';
|
"import { NxWelcomeComponent } from './nx-welcome.component';
|
||||||
import { Route } from '@angular/router';
|
import { Route } from '@angular/router';
|
||||||
@ -68,6 +102,25 @@ export const appRoutes: Route[] = [
|
|||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`Init MF should add a remote to dynamic host correctly when --typescriptConfiguration=true 1`] = `
|
||||||
|
"import { NxWelcomeComponent } from './nx-welcome.component';
|
||||||
|
import { Route } from '@angular/router';
|
||||||
|
import { loadRemoteModule } from '@nx/angular/mf';
|
||||||
|
|
||||||
|
export const appRoutes: Route[] = [
|
||||||
|
{
|
||||||
|
path: 'remote1',
|
||||||
|
loadChildren: () =>
|
||||||
|
loadRemoteModule('remote1', './Module').then((m) => m.RemoteEntryModule),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: NxWelcomeComponent,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`Init MF should create webpack and mf configs correctly 1`] = `
|
exports[`Init MF should create webpack and mf configs correctly 1`] = `
|
||||||
"const { withModuleFederation } = require('@nx/angular/module-federation');
|
"const { withModuleFederation } = require('@nx/angular/module-federation');
|
||||||
const config = require('./module-federation.config');
|
const config = require('./module-federation.config');
|
||||||
@ -100,6 +153,48 @@ exports[`Init MF should create webpack and mf configs correctly 4`] = `
|
|||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`Init MF should create webpack and mf configs correctly when --typescriptConfiguration=true 1`] = `
|
||||||
|
"import { withModuleFederation } from '@nx/angular/module-federation';
|
||||||
|
import config from './module-federation.config';
|
||||||
|
|
||||||
|
export default withModuleFederation(config);
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Init MF should create webpack and mf configs correctly when --typescriptConfiguration=true 2`] = `
|
||||||
|
"import { ModuleFederationConfig } from '@nx/webpack';
|
||||||
|
|
||||||
|
const config: ModuleFederationConfig = {
|
||||||
|
name: 'app1',
|
||||||
|
remotes: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Init MF should create webpack and mf configs correctly when --typescriptConfiguration=true 3`] = `
|
||||||
|
"import { withModuleFederation } from '@nx/angular/module-federation';
|
||||||
|
import config from './module-federation.config';
|
||||||
|
|
||||||
|
export default withModuleFederation(config);
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Init MF should create webpack and mf configs correctly when --typescriptConfiguration=true 4`] = `
|
||||||
|
"import { ModuleFederationConfig } from '@nx/webpack';
|
||||||
|
|
||||||
|
const config: ModuleFederationConfig = {
|
||||||
|
name: 'remote1',
|
||||||
|
exposes: {
|
||||||
|
'./Module': 'remote1/src/app/remote-entry/entry.module.ts',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`Init MF should generate the remote entry component correctly when prefix is not provided 1`] = `
|
exports[`Init MF should generate the remote entry component correctly when prefix is not provided 1`] = `
|
||||||
"import { Component } from '@angular/core';
|
"import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,12 @@
|
|||||||
|
import { ModuleFederationConfig } from '@nx/webpack';
|
||||||
|
|
||||||
|
const config: ModuleFederationConfig = {
|
||||||
|
name: '<%= name %>',<% if(type === 'host') { %>
|
||||||
|
remotes: [<% remotes.forEach(function(remote) { %>'<%= remote.remoteName %>',<% }); %>]<% } %><% if(type === 'remote') { %>
|
||||||
|
exposes: {<% if(standalone) { %>
|
||||||
|
'./Routes': '<%= projectRoot %>/src/app/remote-entry/entry.routes.ts',<% } else { %>
|
||||||
|
'./Module': '<%= projectRoot %>/src/app/remote-entry/entry.module.ts',<% } %>
|
||||||
|
},<% } %>
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
import {withModuleFederation} from '@nx/angular/module-federation';
|
||||||
|
import config from './module-federation.config';
|
||||||
|
|
||||||
|
export default withModuleFederation(config);
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
import {withModuleFederation} from '@nx/angular/module-federation';
|
||||||
|
import config from './module-federation.config';
|
||||||
|
|
||||||
|
export default withModuleFederation({
|
||||||
|
...config,
|
||||||
|
/*
|
||||||
|
* Remote overrides for production.
|
||||||
|
* Each entry is a pair of a unique name and the URL where it is deployed.
|
||||||
|
*
|
||||||
|
* e.g.
|
||||||
|
* remotes: [
|
||||||
|
* ['app1', 'https://app1.example.com'],
|
||||||
|
* ['app2', 'https://app2.example.com'],
|
||||||
|
* ]
|
||||||
|
*/
|
||||||
|
});
|
||||||
@ -35,8 +35,17 @@ export function addRemoteToHost(tree: Tree, options: Schema) {
|
|||||||
pathToMFManifest
|
pathToMFManifest
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isHostUsingTypescriptConfig = tree.exists(
|
||||||
|
joinPathFragments(hostProject.root, 'module-federation.config.ts')
|
||||||
|
);
|
||||||
|
|
||||||
if (hostFederationType === 'static') {
|
if (hostFederationType === 'static') {
|
||||||
addRemoteToStaticHost(tree, options, hostProject);
|
addRemoteToStaticHost(
|
||||||
|
tree,
|
||||||
|
options,
|
||||||
|
hostProject,
|
||||||
|
isHostUsingTypescriptConfig
|
||||||
|
);
|
||||||
} else if (hostFederationType === 'dynamic') {
|
} else if (hostFederationType === 'dynamic') {
|
||||||
addRemoteToDynamicHost(tree, options, pathToMFManifest);
|
addRemoteToDynamicHost(tree, options, pathToMFManifest);
|
||||||
}
|
}
|
||||||
@ -69,16 +78,19 @@ function determineHostFederationType(
|
|||||||
function addRemoteToStaticHost(
|
function addRemoteToStaticHost(
|
||||||
tree: Tree,
|
tree: Tree,
|
||||||
options: Schema,
|
options: Schema,
|
||||||
hostProject: ProjectConfiguration
|
hostProject: ProjectConfiguration,
|
||||||
|
isHostUsingTypescrpt: boolean
|
||||||
) {
|
) {
|
||||||
const hostMFConfigPath = joinPathFragments(
|
const hostMFConfigPath = joinPathFragments(
|
||||||
hostProject.root,
|
hostProject.root,
|
||||||
'module-federation.config.js'
|
isHostUsingTypescrpt
|
||||||
|
? 'module-federation.config.ts'
|
||||||
|
: 'module-federation.config.js'
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!hostMFConfigPath || !tree.exists(hostMFConfigPath)) {
|
if (!hostMFConfigPath || !tree.exists(hostMFConfigPath)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`The selected host application, ${options.host}, does not contain a module-federation.config.js or module-federation.manifest.json file. Are you sure it has been set up as a host application?`
|
`The selected host application, ${options.host}, does not contain a module-federation.config.{ts,js} or module-federation.manifest.json file. Are you sure it has been set up as a host application?`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,18 +9,20 @@ import {
|
|||||||
export function changeBuildTarget(host: Tree, options: Schema) {
|
export function changeBuildTarget(host: Tree, options: Schema) {
|
||||||
const appConfig = readProjectConfiguration(host, options.appName);
|
const appConfig = readProjectConfiguration(host, options.appName);
|
||||||
|
|
||||||
|
const configExtName = options.typescriptConfiguration ? 'ts' : 'js';
|
||||||
|
|
||||||
appConfig.targets.build.executor = '@nx/angular:webpack-browser';
|
appConfig.targets.build.executor = '@nx/angular:webpack-browser';
|
||||||
appConfig.targets.build.options = {
|
appConfig.targets.build.options = {
|
||||||
...appConfig.targets.build.options,
|
...appConfig.targets.build.options,
|
||||||
customWebpackConfig: {
|
customWebpackConfig: {
|
||||||
path: `${appConfig.root}/webpack.config.js`,
|
path: `${appConfig.root}/webpack.config.${configExtName}`,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
appConfig.targets.build.configurations.production = {
|
appConfig.targets.build.configurations.production = {
|
||||||
...appConfig.targets.build.configurations.production,
|
...appConfig.targets.build.configurations.production,
|
||||||
customWebpackConfig: {
|
customWebpackConfig: {
|
||||||
path: `${appConfig.root}/webpack.prod.config.js`,
|
path: `${appConfig.root}/webpack.prod.config.${configExtName}`,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,10 @@ export function generateWebpackConfig(
|
|||||||
if (
|
if (
|
||||||
tree.exists(`${appRoot}/module-federation.config.js`) ||
|
tree.exists(`${appRoot}/module-federation.config.js`) ||
|
||||||
tree.exists(`${appRoot}/webpack.config.js`) ||
|
tree.exists(`${appRoot}/webpack.config.js`) ||
|
||||||
tree.exists(`${appRoot}/webpack.prod.config.js`)
|
tree.exists(`${appRoot}/webpack.prod.config.js`) ||
|
||||||
|
tree.exists(`${appRoot}/module-federation.config.ts`) ||
|
||||||
|
tree.exists(`${appRoot}/webpack.config.ts`) ||
|
||||||
|
tree.exists(`${appRoot}/webpack.prod.config.ts`)
|
||||||
) {
|
) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
`NOTE: We encountered an existing webpack config for the app ${options.appName}. We have overwritten this file with the Module Federation Config.\n
|
`NOTE: We encountered an existing webpack config for the app ${options.appName}. We have overwritten this file with the Module Federation Config.\n
|
||||||
@ -19,9 +22,13 @@ export function generateWebpackConfig(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pathToWebpackTemplateFiles = options.typescriptConfiguration
|
||||||
|
? 'ts-webpack'
|
||||||
|
: 'webpack';
|
||||||
|
|
||||||
generateFiles(
|
generateFiles(
|
||||||
tree,
|
tree,
|
||||||
joinPathFragments(__dirname, '../files/webpack'),
|
joinPathFragments(__dirname, `../files/${pathToWebpackTemplateFiles}`),
|
||||||
appRoot,
|
appRoot,
|
||||||
{
|
{
|
||||||
tmpl: '',
|
tmpl: '',
|
||||||
|
|||||||
@ -8,6 +8,7 @@ export function normalizeOptions(
|
|||||||
): NormalizedOptions {
|
): NormalizedOptions {
|
||||||
return {
|
return {
|
||||||
...options,
|
...options,
|
||||||
|
typescriptConfiguration: options.typescriptConfiguration ?? true,
|
||||||
federationType: options.federationType ?? 'static',
|
federationType: options.federationType ?? 'static',
|
||||||
prefix: options.prefix ?? getProjectPrefix(tree, options.appName),
|
prefix: options.prefix ?? getProjectPrefix(tree, options.appName),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -23,7 +23,7 @@ export function setupHostIfDynamic(tree: Tree, options: Schema) {
|
|||||||
|
|
||||||
const pathToProdWebpackConfig = joinPathFragments(
|
const pathToProdWebpackConfig = joinPathFragments(
|
||||||
project.root,
|
project.root,
|
||||||
'webpack.prod.config.js'
|
`webpack.prod.config.${options.typescriptConfiguration ? 'ts' : 'js'}`
|
||||||
);
|
);
|
||||||
if (tree.exists(pathToProdWebpackConfig)) {
|
if (tree.exists(pathToProdWebpackConfig)) {
|
||||||
tree.delete(pathToProdWebpackConfig);
|
tree.delete(pathToProdWebpackConfig);
|
||||||
|
|||||||
@ -14,6 +14,7 @@ export interface Schema {
|
|||||||
prefix?: string;
|
prefix?: string;
|
||||||
standalone?: boolean;
|
standalone?: boolean;
|
||||||
skipE2E?: boolean;
|
skipE2E?: boolean;
|
||||||
|
typescriptConfiguration?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NormalizedOptions extends Schema {
|
export interface NormalizedOptions extends Schema {
|
||||||
|
|||||||
@ -73,6 +73,11 @@
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Whether the application is a standalone application. _Note: This is only supported in Angular versions >= 14.1.0_",
|
"description": "Whether the application is a standalone application. _Note: This is only supported in Angular versions >= 14.1.0_",
|
||||||
"default": false
|
"default": false
|
||||||
|
},
|
||||||
|
"typescriptConfiguration": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether the module federation configuration and webpack configuration files should use TS.",
|
||||||
|
"default": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["appName", "mfType"],
|
"required": ["appName", "mfType"],
|
||||||
|
|||||||
@ -33,6 +33,7 @@ describe('Init MF', () => {
|
|||||||
await setupMf(tree, {
|
await setupMf(tree, {
|
||||||
appName: app,
|
appName: app,
|
||||||
mfType: type,
|
mfType: type,
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ASSERT
|
// ASSERT
|
||||||
@ -51,6 +52,35 @@ describe('Init MF', () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
['app1', 'host'],
|
||||||
|
['remote1', 'remote'],
|
||||||
|
])(
|
||||||
|
'should create webpack and mf configs correctly when --typescriptConfiguration=true',
|
||||||
|
async (app, type: 'host' | 'remote') => {
|
||||||
|
// ACT
|
||||||
|
await setupMf(tree, {
|
||||||
|
appName: app,
|
||||||
|
mfType: type,
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.exists(`${app}/module-federation.config.ts`)).toBeTruthy();
|
||||||
|
expect(tree.exists(`${app}/webpack.config.ts`)).toBeTruthy();
|
||||||
|
expect(tree.exists(`${app}/webpack.prod.config.ts`)).toBeTruthy();
|
||||||
|
|
||||||
|
const webpackContents = tree.read(`${app}/webpack.config.ts`, 'utf-8');
|
||||||
|
expect(webpackContents).toMatchSnapshot();
|
||||||
|
|
||||||
|
const mfConfigContents = tree.read(
|
||||||
|
`${app}/module-federation.config.ts`,
|
||||||
|
'utf-8'
|
||||||
|
);
|
||||||
|
expect(mfConfigContents).toMatchSnapshot();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
test.each([
|
test.each([
|
||||||
['app1', 'host'],
|
['app1', 'host'],
|
||||||
['remote1', 'remote'],
|
['remote1', 'remote'],
|
||||||
@ -110,6 +140,7 @@ describe('Init MF', () => {
|
|||||||
await setupMf(tree, {
|
await setupMf(tree, {
|
||||||
appName: app,
|
appName: app,
|
||||||
mfType: type,
|
mfType: type,
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ASSERT
|
// ASSERT
|
||||||
@ -127,6 +158,34 @@ describe('Init MF', () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
['app1', 'host'],
|
||||||
|
['remote1', 'remote'],
|
||||||
|
])(
|
||||||
|
'should change the build and serve target and set correct path to webpack config when --typescriptConfiguration=true',
|
||||||
|
async (app, type: 'host' | 'remote') => {
|
||||||
|
// ACT
|
||||||
|
await setupMf(tree, {
|
||||||
|
appName: app,
|
||||||
|
mfType: type,
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
const { build, serve } = readProjectConfiguration(tree, app).targets;
|
||||||
|
|
||||||
|
expect(serve.executor).toEqual(
|
||||||
|
type === 'host'
|
||||||
|
? '@nx/angular:module-federation-dev-server'
|
||||||
|
: '@nx/angular:webpack-dev-server'
|
||||||
|
);
|
||||||
|
expect(build.executor).toEqual('@nx/angular:webpack-browser');
|
||||||
|
expect(build.options.customWebpackConfig.path).toEqual(
|
||||||
|
`${app}/webpack.config.ts`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
it('should not generate a webpack prod file for dynamic host', async () => {
|
it('should not generate a webpack prod file for dynamic host', async () => {
|
||||||
// ACT
|
// ACT
|
||||||
await setupMf(tree, {
|
await setupMf(tree, {
|
||||||
@ -137,7 +196,7 @@ describe('Init MF', () => {
|
|||||||
|
|
||||||
// ASSERT
|
// ASSERT
|
||||||
const { build } = readProjectConfiguration(tree, 'app1').targets;
|
const { build } = readProjectConfiguration(tree, 'app1').targets;
|
||||||
expect(tree.exists('app1/webpack.prod.config.js')).toBeFalsy();
|
expect(tree.exists('app1/webpack.prod.config.ts')).toBeFalsy();
|
||||||
expect(build.configurations.production.customWebpackConfig).toBeUndefined();
|
expect(build.configurations.production.customWebpackConfig).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -174,6 +233,7 @@ describe('Init MF', () => {
|
|||||||
appName: 'app1',
|
appName: 'app1',
|
||||||
mfType: 'host',
|
mfType: 'host',
|
||||||
remotes: ['remote1'],
|
remotes: ['remote1'],
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ASSERT
|
// ASSERT
|
||||||
@ -185,11 +245,30 @@ describe('Init MF', () => {
|
|||||||
expect(mfConfigContents).toContain(`'remote1'`);
|
expect(mfConfigContents).toContain(`'remote1'`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should add the remote config to the host when --remotes flag supplied when --typescriptConfiguration=true', async () => {
|
||||||
|
// ACT
|
||||||
|
await setupMf(tree, {
|
||||||
|
appName: 'app1',
|
||||||
|
mfType: 'host',
|
||||||
|
remotes: ['remote1'],
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
const mfConfigContents = tree.read(
|
||||||
|
`app1/module-federation.config.ts`,
|
||||||
|
'utf-8'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mfConfigContents).toContain(`'remote1'`);
|
||||||
|
});
|
||||||
|
|
||||||
it('should add a remote application and add it to a specified host applications webpack config when no other remote has been added to it', async () => {
|
it('should add a remote application and add it to a specified host applications webpack config when no other remote has been added to it', async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
await setupMf(tree, {
|
await setupMf(tree, {
|
||||||
appName: 'app1',
|
appName: 'app1',
|
||||||
mfType: 'host',
|
mfType: 'host',
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ACT
|
// ACT
|
||||||
@ -197,6 +276,7 @@ describe('Init MF', () => {
|
|||||||
appName: 'remote1',
|
appName: 'remote1',
|
||||||
mfType: 'remote',
|
mfType: 'remote',
|
||||||
host: 'app1',
|
host: 'app1',
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ASSERT
|
// ASSERT
|
||||||
@ -204,6 +284,27 @@ describe('Init MF', () => {
|
|||||||
expect(hostMfConfig).toMatchSnapshot();
|
expect(hostMfConfig).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should add a remote application and add it to a specified host applications webpack config when no other remote has been added to it when --typescriptConfiguration=true', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
await setupMf(tree, {
|
||||||
|
appName: 'app1',
|
||||||
|
mfType: 'host',
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await setupMf(tree, {
|
||||||
|
appName: 'remote1',
|
||||||
|
mfType: 'remote',
|
||||||
|
host: 'app1',
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
const hostMfConfig = tree.read('app1/module-federation.config.ts', 'utf-8');
|
||||||
|
expect(hostMfConfig).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
it('should add a remote application and add it to a specified host applications webpack config that contains a remote application already', async () => {
|
it('should add a remote application and add it to a specified host applications webpack config that contains a remote application already', async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
await generateTestApplication(tree, {
|
await generateTestApplication(tree, {
|
||||||
@ -213,6 +314,7 @@ describe('Init MF', () => {
|
|||||||
await setupMf(tree, {
|
await setupMf(tree, {
|
||||||
appName: 'app1',
|
appName: 'app1',
|
||||||
mfType: 'host',
|
mfType: 'host',
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
await setupMf(tree, {
|
await setupMf(tree, {
|
||||||
@ -220,6 +322,7 @@ describe('Init MF', () => {
|
|||||||
mfType: 'remote',
|
mfType: 'remote',
|
||||||
host: 'app1',
|
host: 'app1',
|
||||||
port: 4201,
|
port: 4201,
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ACT
|
// ACT
|
||||||
@ -228,6 +331,7 @@ describe('Init MF', () => {
|
|||||||
mfType: 'remote',
|
mfType: 'remote',
|
||||||
host: 'app1',
|
host: 'app1',
|
||||||
port: 4202,
|
port: 4202,
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ASSERT
|
// ASSERT
|
||||||
@ -235,6 +339,40 @@ describe('Init MF', () => {
|
|||||||
expect(hostMfConfig).toMatchSnapshot();
|
expect(hostMfConfig).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should add a remote application and add it to a specified host applications webpack config that contains a remote application already when --typescriptConfiguration=true', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
await generateTestApplication(tree, {
|
||||||
|
name: 'remote2',
|
||||||
|
});
|
||||||
|
|
||||||
|
await setupMf(tree, {
|
||||||
|
appName: 'app1',
|
||||||
|
mfType: 'host',
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await setupMf(tree, {
|
||||||
|
appName: 'remote1',
|
||||||
|
mfType: 'remote',
|
||||||
|
host: 'app1',
|
||||||
|
port: 4201,
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await setupMf(tree, {
|
||||||
|
appName: 'remote2',
|
||||||
|
mfType: 'remote',
|
||||||
|
host: 'app1',
|
||||||
|
port: 4202,
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
const hostMfConfig = tree.read('app1/module-federation.config.ts', 'utf-8');
|
||||||
|
expect(hostMfConfig).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
it('should add a remote application and add it to a specified host applications router config', async () => {
|
it('should add a remote application and add it to a specified host applications router config', async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
await generateTestApplication(tree, {
|
await generateTestApplication(tree, {
|
||||||
@ -303,6 +441,7 @@ describe('Init MF', () => {
|
|||||||
mfType: 'host',
|
mfType: 'host',
|
||||||
routing: true,
|
routing: true,
|
||||||
federationType: 'dynamic',
|
federationType: 'dynamic',
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ASSERT
|
// ASSERT
|
||||||
@ -314,6 +453,26 @@ describe('Init MF', () => {
|
|||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
expect(tree.read('app1/src/main.ts', 'utf-8')).toMatchSnapshot();
|
expect(tree.read('app1/src/main.ts', 'utf-8')).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should create a host with the correct configurations when --typescriptConfiguration=true', async () => {
|
||||||
|
// ARRANGE & ACT
|
||||||
|
await setupMf(tree, {
|
||||||
|
appName: 'app1',
|
||||||
|
mfType: 'host',
|
||||||
|
routing: true,
|
||||||
|
federationType: 'dynamic',
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('app1/module-federation.config.ts', 'utf-8')).toContain(
|
||||||
|
'remotes: []'
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
tree.exists('app1/src/assets/module-federation.manifest.json')
|
||||||
|
).toBeTruthy();
|
||||||
|
expect(tree.read('app1/src/main.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate bootstrap with environments for ng14', async () => {
|
it('should generate bootstrap with environments for ng14', async () => {
|
||||||
@ -365,6 +524,7 @@ describe('Init MF', () => {
|
|||||||
mfType: 'host',
|
mfType: 'host',
|
||||||
routing: true,
|
routing: true,
|
||||||
federationType: 'dynamic',
|
federationType: 'dynamic',
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ACT
|
// ACT
|
||||||
@ -374,6 +534,7 @@ describe('Init MF', () => {
|
|||||||
port: 4201,
|
port: 4201,
|
||||||
host: 'app1',
|
host: 'app1',
|
||||||
routing: true,
|
routing: true,
|
||||||
|
typescriptConfiguration: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ASSERT
|
// ASSERT
|
||||||
@ -388,6 +549,38 @@ describe('Init MF', () => {
|
|||||||
expect(tree.read('app1/src/app/app.routes.ts', 'utf-8')).toMatchSnapshot();
|
expect(tree.read('app1/src/app/app.routes.ts', 'utf-8')).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should add a remote to dynamic host correctly when --typescriptConfiguration=true', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
await setupMf(tree, {
|
||||||
|
appName: 'app1',
|
||||||
|
mfType: 'host',
|
||||||
|
routing: true,
|
||||||
|
federationType: 'dynamic',
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await setupMf(tree, {
|
||||||
|
appName: 'remote1',
|
||||||
|
mfType: 'remote',
|
||||||
|
port: 4201,
|
||||||
|
host: 'app1',
|
||||||
|
routing: true,
|
||||||
|
typescriptConfiguration: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('app1/module-federation.config.ts', 'utf-8')).toContain(
|
||||||
|
'remotes: []'
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
readJson(tree, 'app1/src/assets/module-federation.manifest.json')
|
||||||
|
).toEqual({
|
||||||
|
remote1: 'http://localhost:4201',
|
||||||
|
});
|
||||||
|
expect(tree.read('app1/src/app/app.routes.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
it('should throw an error when installed version of angular < 14.1.0 and --standalone is used', async () => {
|
it('should throw an error when installed version of angular < 14.1.0 and --standalone is used', async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
updateJson(tree, 'package.json', (json) => ({
|
updateJson(tree, 'package.json', (json) => ({
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user