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",
|
||||
"default": false,
|
||||
"x-priority": "important"
|
||||
},
|
||||
"typescriptConfiguration": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the module federation configuration and webpack configuration files should use TS.",
|
||||
"default": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
||||
@ -163,6 +163,11 @@
|
||||
"description": "Whether to configure SSR for the remote application to be consumed by a host application using SSR.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"typescriptConfiguration": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the module federation configuration and webpack configuration files should use TS.",
|
||||
"default": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
||||
@ -73,6 +73,11 @@
|
||||
"type": "boolean",
|
||||
"description": "Whether the application is a standalone application. _Note: This is only supported in Angular versions >= 14.1.0_",
|
||||
"default": false
|
||||
},
|
||||
"typescriptConfiguration": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the module federation configuration and webpack configuration files should use TS.",
|
||||
"default": true
|
||||
}
|
||||
},
|
||||
"required": ["appName", "mfType"],
|
||||
|
||||
@ -70,7 +70,7 @@ describe('Angular Module Federation', () => {
|
||||
}Module } from '@${proj}/${sharedLib}';
|
||||
import { ${
|
||||
names(secondaryEntry).className
|
||||
}Module } from '@${proj}/${secondaryEntry}';
|
||||
}Module } from '@${proj}/${sharedLib}/${secondaryEntry}';
|
||||
import { AppComponent } from './app.component';
|
||||
import { NxWelcomeComponent } from './nx-welcome.component';
|
||||
import { RouterModule } from '@angular/router';
|
||||
@ -79,7 +79,7 @@ describe('Angular Module Federation', () => {
|
||||
declarations: [AppComponent, NxWelcomeComponent],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
SharedModule,
|
||||
${names(sharedLib).className}Module,
|
||||
RouterModule.forRoot(
|
||||
[
|
||||
{
|
||||
@ -107,14 +107,15 @@ describe('Angular Module Federation', () => {
|
||||
import { ${names(sharedLib).className}Module } from '@${proj}/${sharedLib}';
|
||||
import { ${
|
||||
names(secondaryEntry).className
|
||||
}Module } from '@${proj}/${secondaryEntry}';
|
||||
}Module } from '@${proj}/${sharedLib}/${secondaryEntry}';
|
||||
import { RemoteEntryComponent } from './entry.component';
|
||||
import { NxWelcomeComponent } from './nx-welcome.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [RemoteEntryComponent],
|
||||
declarations: [RemoteEntryComponent, NxWelcomeComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
${names(sharedLib).className}Module,
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: '',
|
||||
@ -128,15 +129,23 @@ describe('Angular Module Federation', () => {
|
||||
`
|
||||
);
|
||||
|
||||
const process = await runCommandUntil(
|
||||
const processSwc = await runCommandUntil(
|
||||
`serve ${hostApp} --port=${hostPort} --dev-remotes=${remoteApp1}`,
|
||||
(output) =>
|
||||
output.includes(`listening on localhost:${remotePort}`) &&
|
||||
!output.includes(`Remote '${remoteApp1}' failed to serve correctly`) &&
|
||||
output.includes(`listening on localhost:${hostPort}`)
|
||||
);
|
||||
await killProcessAndPorts(processSwc.pid, hostPort, remotePort);
|
||||
|
||||
// port and process cleanup
|
||||
await killProcessAndPorts(process.pid, hostPort, remotePort);
|
||||
const processTsNode = await runCommandUntil(
|
||||
`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);
|
||||
|
||||
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`
|
||||
);
|
||||
|
||||
const process = await runCommandUntil(
|
||||
const processSwc = await runCommandUntil(
|
||||
`serve ${app1} --dev-remotes=${app2}`,
|
||||
(output) =>
|
||||
output.includes(`listening on localhost:${app1Port}`) &&
|
||||
output.includes(`listening on localhost:${app2Port}`)
|
||||
!output.includes(`Remote '${app2}' failed to serve correctly`) &&
|
||||
output.includes(`listening on localhost:${app1Port}`)
|
||||
);
|
||||
|
||||
// port and process cleanup
|
||||
await killProcessAndPorts(process.pid, app1Port, app2Port);
|
||||
await killProcessAndPorts(processSwc.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);
|
||||
|
||||
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
|
||||
.options.port;
|
||||
|
||||
const process = await runCommandUntil(
|
||||
const processSwc = await runCommandUntil(
|
||||
`serve-ssr ${host} --port=${hostPort}`,
|
||||
(output) =>
|
||||
output.includes(
|
||||
@ -203,8 +221,34 @@ describe('Angular Module Federation', () => {
|
||||
)
|
||||
);
|
||||
|
||||
// port and process cleanup
|
||||
await killProcessAndPorts(process.pid, hostPort, remote1Port, remote2Port);
|
||||
await killProcessAndPorts(
|
||||
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);
|
||||
|
||||
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
|
||||
const buildOutput = runCLI(`build ${hostApp}`);
|
||||
expect(buildOutput).toContain('Successfully ran target build');
|
||||
const buildOutputSwc = runCLI(`build ${hostApp}`);
|
||||
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}`,
|
||||
(output) =>
|
||||
output.includes(`listening on localhost:${remotePort}`) &&
|
||||
!output.includes(`Remote '${remoteApp}' failed to serve correctly`) &&
|
||||
output.includes(`listening on localhost:${hostPort}`)
|
||||
);
|
||||
|
||||
// port and process cleanup
|
||||
await killProcessAndPorts(process.pid, hostPort, remotePort);
|
||||
await killProcessAndPorts(processSwc.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);
|
||||
});
|
||||
|
||||
@ -229,7 +229,10 @@ export function runCommandAsync(
|
||||
|
||||
export function runCommandUntil(
|
||||
command: string,
|
||||
criteria: (output: string) => boolean
|
||||
criteria: (output: string) => boolean,
|
||||
opts: RunCmdOpts = {
|
||||
env: undefined,
|
||||
}
|
||||
): Promise<ChildProcess> {
|
||||
const pm = getPackageManagerCommand();
|
||||
const p = exec(`${pm.runNx} ${command}`, {
|
||||
@ -238,6 +241,7 @@ export function runCommandUntil(
|
||||
env: {
|
||||
CI: 'true',
|
||||
...getStrippedEnvironmentVariables(),
|
||||
...opts.env,
|
||||
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`] = `
|
||||
"const { withModuleFederation } = require('@nx/angular/module-federation');
|
||||
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`] = `
|
||||
"const { withModuleFederation } = require('@nx/angular/module-federation');
|
||||
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`] = `
|
||||
"import { bootstrapApplication } from '@angular/platform-browser';
|
||||
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
|
||||
await generateTestHostApplication(tree, {
|
||||
name: 'test',
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
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 () => {
|
||||
// ARRANGE
|
||||
@ -30,18 +44,40 @@ describe('Host App Generator', () => {
|
||||
|
||||
await generateTestRemoteApplication(tree, {
|
||||
name: 'remote',
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// ACT
|
||||
await generateTestHostApplication(tree, {
|
||||
name: 'test',
|
||||
remotes: ['remote'],
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read('remote/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 () => {
|
||||
// ARRANGE
|
||||
@ -52,6 +88,7 @@ describe('Host App Generator', () => {
|
||||
await generateTestHostApplication(tree, {
|
||||
name: 'hostApp',
|
||||
remotes: ['remote1', 'remote2'],
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// 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 () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||
await generateTestRemoteApplication(tree, {
|
||||
name: 'remote1',
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// ACT
|
||||
await generateTestHostApplication(tree, {
|
||||
name: 'hostApp',
|
||||
remotes: ['remote1', 'remote2', 'remote3'],
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
@ -94,11 +163,36 @@ describe('Host App Generator', () => {
|
||||
).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 () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||
await generateTestRemoteApplication(tree, {
|
||||
name: 'remote1',
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// ACT
|
||||
@ -106,6 +200,7 @@ describe('Host App Generator', () => {
|
||||
name: 'hostApp',
|
||||
directory: 'foo/hostApp',
|
||||
remotes: ['remote1', 'remote2', 'remote3'],
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
@ -117,6 +212,31 @@ describe('Host App Generator', () => {
|
||||
).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 () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||
@ -197,6 +317,7 @@ describe('Host App Generator', () => {
|
||||
await generateTestHostApplication(tree, {
|
||||
name: 'test',
|
||||
ssr: true,
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
@ -223,6 +344,41 @@ describe('Host App Generator', () => {
|
||||
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 () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||
@ -232,6 +388,7 @@ describe('Host App Generator', () => {
|
||||
name: 'test',
|
||||
standalone: true,
|
||||
ssr: true,
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
@ -261,6 +418,46 @@ describe('Host App Generator', () => {
|
||||
expect(project.targets.server).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 () => {
|
||||
@ -291,6 +488,7 @@ describe('Host App Generator', () => {
|
||||
await generateTestRemoteApplication(tree, {
|
||||
name: 'remote1',
|
||||
projectNameAndRootFormat: 'derived',
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// ACT
|
||||
@ -298,6 +496,7 @@ describe('Host App Generator', () => {
|
||||
name: 'hostApp',
|
||||
remotes: ['remote1', 'remote2', 'remote3'],
|
||||
projectNameAndRootFormat: 'derived',
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
@ -315,6 +514,7 @@ describe('Host App Generator', () => {
|
||||
await generateTestRemoteApplication(tree, {
|
||||
name: 'remote1',
|
||||
projectNameAndRootFormat: 'derived',
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// ACT
|
||||
@ -323,6 +523,7 @@ describe('Host App Generator', () => {
|
||||
directory: 'foo',
|
||||
remotes: ['remote1', 'remote2', 'remote3'],
|
||||
projectNameAndRootFormat: 'derived',
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
@ -333,5 +534,31 @@ describe('Host App Generator', () => {
|
||||
tree.read('apps/foo/host-app/module-federation.config.js', 'utf-8')
|
||||
).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);
|
||||
|
||||
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}.
|
||||
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 remotesToGenerate: string[] = [];
|
||||
@ -78,11 +80,17 @@ export async function hostInternal(tree: Tree, options: Schema) {
|
||||
skipE2E,
|
||||
e2eProjectName: skipE2E ? undefined : `${hostProjectName}-e2e`,
|
||||
prefix: options.prefix,
|
||||
typescriptConfiguration,
|
||||
});
|
||||
|
||||
let installTasks = [appInstallTask];
|
||||
if (options.ssr) {
|
||||
let ssrInstallTask = await addSsr(tree, options, hostProjectName);
|
||||
let ssrInstallTask = await addSsr(
|
||||
tree,
|
||||
options,
|
||||
hostProjectName,
|
||||
typescriptConfiguration
|
||||
);
|
||||
installTasks.push(ssrInstallTask);
|
||||
}
|
||||
|
||||
@ -107,6 +115,7 @@ export async function hostInternal(tree: Tree, options: Schema) {
|
||||
host: hostProjectName,
|
||||
skipFormat: true,
|
||||
standalone: options.standalone,
|
||||
typescriptConfiguration,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -18,7 +18,12 @@ import {
|
||||
} from '../../../utils/versions';
|
||||
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);
|
||||
|
||||
await setupSsr(tree, {
|
||||
@ -40,19 +45,33 @@ export async function addSsr(tree: Tree, options: Schema, appName: string) {
|
||||
'browser'
|
||||
);
|
||||
|
||||
generateFiles(tree, join(__dirname, '../files'), project.root, {
|
||||
generateFiles(tree, join(__dirname, '../files/common'), project.root, {
|
||||
appName,
|
||||
browserBundleOutput,
|
||||
standalone: options.standalone,
|
||||
tmpl: '',
|
||||
});
|
||||
|
||||
const pathToTemplateFiles = typescriptConfiguration ? 'ts' : 'js';
|
||||
|
||||
generateFiles(
|
||||
tree,
|
||||
join(__dirname, '../files', pathToTemplateFiles),
|
||||
project.root,
|
||||
{
|
||||
tmpl: '',
|
||||
}
|
||||
);
|
||||
|
||||
// update project.json
|
||||
project = readProjectConfiguration(tree, appName);
|
||||
|
||||
project.targets.server.executor = '@nx/angular:webpack-server';
|
||||
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 =
|
||||
|
||||
@ -29,4 +29,5 @@ export interface Schema {
|
||||
skipFormat?: boolean;
|
||||
standalone?: boolean;
|
||||
ssr?: boolean;
|
||||
typescriptConfiguration?: boolean;
|
||||
}
|
||||
|
||||
@ -173,6 +173,11 @@
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"x-priority": "important"
|
||||
},
|
||||
"typescriptConfiguration": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the module federation configuration and webpack configuration files should use TS.",
|
||||
"default": true
|
||||
}
|
||||
},
|
||||
"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`] = `
|
||||
"const { withModuleFederation } = require('@nx/angular/module-federation');
|
||||
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`] = `
|
||||
"const { withModuleFederation } = require('@nx/angular/module-federation');
|
||||
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`] = `
|
||||
"import { bootstrapApplication } from '@angular/platform-browser';
|
||||
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,
|
||||
port,
|
||||
standalone,
|
||||
}: { appName: string; port: number; standalone: boolean }
|
||||
typescriptConfiguration,
|
||||
}: {
|
||||
appName: string;
|
||||
port: number;
|
||||
standalone: boolean;
|
||||
typescriptConfiguration: boolean;
|
||||
}
|
||||
) {
|
||||
let project = readProjectConfiguration(tree, appName);
|
||||
|
||||
@ -52,7 +58,7 @@ export async function addSsr(
|
||||
|
||||
generateFiles(
|
||||
tree,
|
||||
joinPathFragments(__dirname, '../files/base'),
|
||||
joinPathFragments(__dirname, `../files/common`),
|
||||
project.root,
|
||||
{
|
||||
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) {
|
||||
generateFiles(
|
||||
tree,
|
||||
@ -81,7 +98,10 @@ export async function addSsr(
|
||||
|
||||
project.targets.server.executor = '@nx/angular:webpack-server';
|
||||
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 ?? {}),
|
||||
|
||||
@ -23,6 +23,7 @@ describe('MF Remote App Generator', () => {
|
||||
await generateTestRemoteApplication(tree, {
|
||||
name: 'test',
|
||||
port: 4201,
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// 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 () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||
|
||||
await generateTestHostApplication(tree, {
|
||||
name: 'host',
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// ACT
|
||||
await generateTestRemoteApplication(tree, {
|
||||
name: 'test',
|
||||
host: 'host',
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
@ -52,6 +70,27 @@ describe('MF Remote App Generator', () => {
|
||||
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 () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||
@ -125,6 +164,7 @@ describe('MF Remote App Generator', () => {
|
||||
await generateTestRemoteApplication(tree, {
|
||||
name: 'test',
|
||||
standalone: true,
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// 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 () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||
@ -217,6 +287,7 @@ describe('MF Remote App Generator', () => {
|
||||
await generateTestRemoteApplication(tree, {
|
||||
name: 'test',
|
||||
ssr: true,
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
@ -254,6 +325,53 @@ describe('MF Remote App Generator', () => {
|
||||
).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 () => {
|
||||
@ -285,6 +403,7 @@ describe('MF Remote App Generator', () => {
|
||||
name: 'test',
|
||||
port: 4201,
|
||||
projectNameAndRootFormat: 'derived',
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
expect(tree.exists('apps/test/webpack.config.js')).toBe(true);
|
||||
@ -299,6 +418,7 @@ describe('MF Remote App Generator', () => {
|
||||
port: 4201,
|
||||
directory: 'shared',
|
||||
projectNameAndRootFormat: 'derived',
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
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}.
|
||||
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);
|
||||
if (options.host && !projects.has(options.host)) {
|
||||
throw new Error(
|
||||
@ -71,6 +73,7 @@ export async function remoteInternal(tree: Tree, options: Schema) {
|
||||
e2eProjectName: skipE2E ? undefined : `${remoteProjectName}-e2e`,
|
||||
standalone: options.standalone,
|
||||
prefix: options.prefix,
|
||||
typescriptConfiguration,
|
||||
});
|
||||
|
||||
let installTasks = [appInstallTask];
|
||||
@ -78,6 +81,7 @@ export async function remoteInternal(tree: Tree, options: Schema) {
|
||||
let ssrInstallTask = await addSsr(tree, {
|
||||
appName: remoteProjectName,
|
||||
port,
|
||||
typescriptConfiguration,
|
||||
standalone: options.standalone,
|
||||
});
|
||||
installTasks.push(ssrInstallTask);
|
||||
|
||||
@ -28,4 +28,5 @@ export interface Schema {
|
||||
skipFormat?: boolean;
|
||||
standalone?: 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.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"typescriptConfiguration": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the module federation configuration and webpack configuration files should use TS.",
|
||||
"default": true
|
||||
}
|
||||
},
|
||||
"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`] = `
|
||||
"import { NxWelcomeComponent } from './nx-welcome.component';
|
||||
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`] = `
|
||||
"module.exports = {
|
||||
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`] = `
|
||||
"import { NxWelcomeComponent } from './nx-welcome.component';
|
||||
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`] = `
|
||||
"const { withModuleFederation } = require('@nx/angular/module-federation');
|
||||
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`] = `
|
||||
"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
|
||||
);
|
||||
|
||||
const isHostUsingTypescriptConfig = tree.exists(
|
||||
joinPathFragments(hostProject.root, 'module-federation.config.ts')
|
||||
);
|
||||
|
||||
if (hostFederationType === 'static') {
|
||||
addRemoteToStaticHost(tree, options, hostProject);
|
||||
addRemoteToStaticHost(
|
||||
tree,
|
||||
options,
|
||||
hostProject,
|
||||
isHostUsingTypescriptConfig
|
||||
);
|
||||
} else if (hostFederationType === 'dynamic') {
|
||||
addRemoteToDynamicHost(tree, options, pathToMFManifest);
|
||||
}
|
||||
@ -69,16 +78,19 @@ function determineHostFederationType(
|
||||
function addRemoteToStaticHost(
|
||||
tree: Tree,
|
||||
options: Schema,
|
||||
hostProject: ProjectConfiguration
|
||||
hostProject: ProjectConfiguration,
|
||||
isHostUsingTypescrpt: boolean
|
||||
) {
|
||||
const hostMFConfigPath = joinPathFragments(
|
||||
hostProject.root,
|
||||
'module-federation.config.js'
|
||||
isHostUsingTypescrpt
|
||||
? 'module-federation.config.ts'
|
||||
: 'module-federation.config.js'
|
||||
);
|
||||
|
||||
if (!hostMFConfigPath || !tree.exists(hostMFConfigPath)) {
|
||||
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) {
|
||||
const appConfig = readProjectConfiguration(host, options.appName);
|
||||
|
||||
const configExtName = options.typescriptConfiguration ? 'ts' : 'js';
|
||||
|
||||
appConfig.targets.build.executor = '@nx/angular:webpack-browser';
|
||||
appConfig.targets.build.options = {
|
||||
...appConfig.targets.build.options,
|
||||
customWebpackConfig: {
|
||||
path: `${appConfig.root}/webpack.config.js`,
|
||||
path: `${appConfig.root}/webpack.config.${configExtName}`,
|
||||
},
|
||||
};
|
||||
|
||||
appConfig.targets.build.configurations.production = {
|
||||
...appConfig.targets.build.configurations.production,
|
||||
customWebpackConfig: {
|
||||
path: `${appConfig.root}/webpack.prod.config.js`,
|
||||
path: `${appConfig.root}/webpack.prod.config.${configExtName}`,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -11,7 +11,10 @@ export function generateWebpackConfig(
|
||||
if (
|
||||
tree.exists(`${appRoot}/module-federation.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(
|
||||
`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(
|
||||
tree,
|
||||
joinPathFragments(__dirname, '../files/webpack'),
|
||||
joinPathFragments(__dirname, `../files/${pathToWebpackTemplateFiles}`),
|
||||
appRoot,
|
||||
{
|
||||
tmpl: '',
|
||||
|
||||
@ -8,6 +8,7 @@ export function normalizeOptions(
|
||||
): NormalizedOptions {
|
||||
return {
|
||||
...options,
|
||||
typescriptConfiguration: options.typescriptConfiguration ?? true,
|
||||
federationType: options.federationType ?? 'static',
|
||||
prefix: options.prefix ?? getProjectPrefix(tree, options.appName),
|
||||
};
|
||||
|
||||
@ -23,7 +23,7 @@ export function setupHostIfDynamic(tree: Tree, options: Schema) {
|
||||
|
||||
const pathToProdWebpackConfig = joinPathFragments(
|
||||
project.root,
|
||||
'webpack.prod.config.js'
|
||||
`webpack.prod.config.${options.typescriptConfiguration ? 'ts' : 'js'}`
|
||||
);
|
||||
if (tree.exists(pathToProdWebpackConfig)) {
|
||||
tree.delete(pathToProdWebpackConfig);
|
||||
|
||||
@ -14,6 +14,7 @@ export interface Schema {
|
||||
prefix?: string;
|
||||
standalone?: boolean;
|
||||
skipE2E?: boolean;
|
||||
typescriptConfiguration?: boolean;
|
||||
}
|
||||
|
||||
export interface NormalizedOptions extends Schema {
|
||||
|
||||
@ -73,6 +73,11 @@
|
||||
"type": "boolean",
|
||||
"description": "Whether the application is a standalone application. _Note: This is only supported in Angular versions >= 14.1.0_",
|
||||
"default": false
|
||||
},
|
||||
"typescriptConfiguration": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the module federation configuration and webpack configuration files should use TS.",
|
||||
"default": true
|
||||
}
|
||||
},
|
||||
"required": ["appName", "mfType"],
|
||||
|
||||
@ -33,6 +33,7 @@ describe('Init MF', () => {
|
||||
await setupMf(tree, {
|
||||
appName: app,
|
||||
mfType: type,
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// 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([
|
||||
['app1', 'host'],
|
||||
['remote1', 'remote'],
|
||||
@ -110,6 +140,7 @@ describe('Init MF', () => {
|
||||
await setupMf(tree, {
|
||||
appName: app,
|
||||
mfType: type,
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// 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 () => {
|
||||
// ACT
|
||||
await setupMf(tree, {
|
||||
@ -137,7 +196,7 @@ describe('Init MF', () => {
|
||||
|
||||
// ASSERT
|
||||
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();
|
||||
});
|
||||
|
||||
@ -174,6 +233,7 @@ describe('Init MF', () => {
|
||||
appName: 'app1',
|
||||
mfType: 'host',
|
||||
remotes: ['remote1'],
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
@ -185,11 +245,30 @@ describe('Init MF', () => {
|
||||
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 () => {
|
||||
// ARRANGE
|
||||
await setupMf(tree, {
|
||||
appName: 'app1',
|
||||
mfType: 'host',
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// ACT
|
||||
@ -197,6 +276,7 @@ describe('Init MF', () => {
|
||||
appName: 'remote1',
|
||||
mfType: 'remote',
|
||||
host: 'app1',
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
@ -204,6 +284,27 @@ describe('Init MF', () => {
|
||||
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 () => {
|
||||
// ARRANGE
|
||||
await generateTestApplication(tree, {
|
||||
@ -213,6 +314,7 @@ describe('Init MF', () => {
|
||||
await setupMf(tree, {
|
||||
appName: 'app1',
|
||||
mfType: 'host',
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
await setupMf(tree, {
|
||||
@ -220,6 +322,7 @@ describe('Init MF', () => {
|
||||
mfType: 'remote',
|
||||
host: 'app1',
|
||||
port: 4201,
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// ACT
|
||||
@ -228,6 +331,7 @@ describe('Init MF', () => {
|
||||
mfType: 'remote',
|
||||
host: 'app1',
|
||||
port: 4202,
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
@ -235,6 +339,40 @@ describe('Init MF', () => {
|
||||
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 () => {
|
||||
// ARRANGE
|
||||
await generateTestApplication(tree, {
|
||||
@ -303,6 +441,7 @@ describe('Init MF', () => {
|
||||
mfType: 'host',
|
||||
routing: true,
|
||||
federationType: 'dynamic',
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
@ -314,6 +453,26 @@ describe('Init MF', () => {
|
||||
).toBeTruthy();
|
||||
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 () => {
|
||||
@ -365,6 +524,7 @@ describe('Init MF', () => {
|
||||
mfType: 'host',
|
||||
routing: true,
|
||||
federationType: 'dynamic',
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// ACT
|
||||
@ -374,6 +534,7 @@ describe('Init MF', () => {
|
||||
port: 4201,
|
||||
host: 'app1',
|
||||
routing: true,
|
||||
typescriptConfiguration: false,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
@ -388,6 +549,38 @@ describe('Init MF', () => {
|
||||
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 () => {
|
||||
// ARRANGE
|
||||
updateJson(tree, 'package.json', (json) => ({
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user