feat(angular): init mfe generator (#6209)
Add generator to scaffold Module Federation configuration for a given Angular application
This commit is contained in:
parent
80d3999d42
commit
dd51c18d12
59
docs/angular/api-angular/generators/setup-mfe.md
Normal file
59
docs/angular/api-angular/generators/setup-mfe.md
Normal file
@ -0,0 +1,59 @@
|
||||
# setup-mfe
|
||||
|
||||
Generate a Module Federation configuration for a given Angular application.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
nx generate setup-mfe ...
|
||||
```
|
||||
|
||||
By default, Nx will search for `setup-mfe` in the default collection provisioned in `angular.json`.
|
||||
|
||||
You can specify the collection explicitly as follows:
|
||||
|
||||
```bash
|
||||
nx g @nrwl/angular:setup-mfe ...
|
||||
```
|
||||
|
||||
Show what will be generated without writing to disk:
|
||||
|
||||
```bash
|
||||
nx g setup-mfe ... --dry-run
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### appName
|
||||
|
||||
Type: `string`
|
||||
|
||||
The name of the application to generate the Module Federation configuration for.
|
||||
|
||||
### mfeType
|
||||
|
||||
Default: `shell`
|
||||
|
||||
Type: `string`
|
||||
|
||||
Possible values: `shell`, `remote`
|
||||
|
||||
Type of application to generate the Module Federation configuration for.
|
||||
|
||||
### port
|
||||
|
||||
Type: `number`
|
||||
|
||||
The port at which the remote application should be served.
|
||||
|
||||
### remotes
|
||||
|
||||
Type: `array`
|
||||
|
||||
A list of remote application names that the shell application should consume.
|
||||
|
||||
### skipFormat
|
||||
|
||||
Type: `boolean`
|
||||
|
||||
Skip formatting the workspace after the generator completes.
|
||||
@ -1031,6 +1031,8 @@ Reads a project configuration.
|
||||
The project configuration is stored in workspace.json and nx.json. The utility will read
|
||||
both files.
|
||||
|
||||
**`throws`** If supplied projectName cannot be found
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
|
||||
@ -422,6 +422,11 @@
|
||||
"id": "ngrx",
|
||||
"file": "angular/api-angular/generators/ngrx"
|
||||
},
|
||||
{
|
||||
"name": "setup-mfe generator",
|
||||
"id": "setup-mfe",
|
||||
"file": "angular/api-angular/generators/setup-mfe"
|
||||
},
|
||||
{
|
||||
"name": "stories generator",
|
||||
"id": "stories",
|
||||
@ -1558,6 +1563,11 @@
|
||||
"id": "ngrx",
|
||||
"file": "react/api-angular/generators/ngrx"
|
||||
},
|
||||
{
|
||||
"name": "setup-mfe generator",
|
||||
"id": "setup-mfe",
|
||||
"file": "react/api-angular/generators/setup-mfe"
|
||||
},
|
||||
{
|
||||
"name": "stories generator",
|
||||
"id": "stories",
|
||||
@ -2658,6 +2668,11 @@
|
||||
"id": "ngrx",
|
||||
"file": "node/api-angular/generators/ngrx"
|
||||
},
|
||||
{
|
||||
"name": "setup-mfe generator",
|
||||
"id": "setup-mfe",
|
||||
"file": "node/api-angular/generators/setup-mfe"
|
||||
},
|
||||
{
|
||||
"name": "stories generator",
|
||||
"id": "stories",
|
||||
|
||||
59
docs/node/api-angular/generators/setup-mfe.md
Normal file
59
docs/node/api-angular/generators/setup-mfe.md
Normal file
@ -0,0 +1,59 @@
|
||||
# setup-mfe
|
||||
|
||||
Generate a Module Federation configuration for a given Angular application.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
nx generate setup-mfe ...
|
||||
```
|
||||
|
||||
By default, Nx will search for `setup-mfe` in the default collection provisioned in `workspace.json`.
|
||||
|
||||
You can specify the collection explicitly as follows:
|
||||
|
||||
```bash
|
||||
nx g @nrwl/angular:setup-mfe ...
|
||||
```
|
||||
|
||||
Show what will be generated without writing to disk:
|
||||
|
||||
```bash
|
||||
nx g setup-mfe ... --dry-run
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### appName
|
||||
|
||||
Type: `string`
|
||||
|
||||
The name of the application to generate the Module Federation configuration for.
|
||||
|
||||
### mfeType
|
||||
|
||||
Default: `shell`
|
||||
|
||||
Type: `string`
|
||||
|
||||
Possible values: `shell`, `remote`
|
||||
|
||||
Type of application to generate the Module Federation configuration for.
|
||||
|
||||
### port
|
||||
|
||||
Type: `number`
|
||||
|
||||
The port at which the remote application should be served.
|
||||
|
||||
### remotes
|
||||
|
||||
Type: `array`
|
||||
|
||||
A list of remote application names that the shell application should consume.
|
||||
|
||||
### skipFormat
|
||||
|
||||
Type: `boolean`
|
||||
|
||||
Skip formatting the workspace after the generator completes.
|
||||
@ -1031,6 +1031,8 @@ Reads a project configuration.
|
||||
The project configuration is stored in workspace.json and nx.json. The utility will read
|
||||
both files.
|
||||
|
||||
**`throws`** If supplied projectName cannot be found
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
|
||||
59
docs/react/api-angular/generators/setup-mfe.md
Normal file
59
docs/react/api-angular/generators/setup-mfe.md
Normal file
@ -0,0 +1,59 @@
|
||||
# setup-mfe
|
||||
|
||||
Generate a Module Federation configuration for a given Angular application.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
nx generate setup-mfe ...
|
||||
```
|
||||
|
||||
By default, Nx will search for `setup-mfe` in the default collection provisioned in `workspace.json`.
|
||||
|
||||
You can specify the collection explicitly as follows:
|
||||
|
||||
```bash
|
||||
nx g @nrwl/angular:setup-mfe ...
|
||||
```
|
||||
|
||||
Show what will be generated without writing to disk:
|
||||
|
||||
```bash
|
||||
nx g setup-mfe ... --dry-run
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### appName
|
||||
|
||||
Type: `string`
|
||||
|
||||
The name of the application to generate the Module Federation configuration for.
|
||||
|
||||
### mfeType
|
||||
|
||||
Default: `shell`
|
||||
|
||||
Type: `string`
|
||||
|
||||
Possible values: `shell`, `remote`
|
||||
|
||||
Type of application to generate the Module Federation configuration for.
|
||||
|
||||
### port
|
||||
|
||||
Type: `number`
|
||||
|
||||
The port at which the remote application should be served.
|
||||
|
||||
### remotes
|
||||
|
||||
Type: `array`
|
||||
|
||||
A list of remote application names that the shell application should consume.
|
||||
|
||||
### skipFormat
|
||||
|
||||
Type: `boolean`
|
||||
|
||||
Skip formatting the workspace after the generator completes.
|
||||
@ -1031,6 +1031,8 @@ Reads a project configuration.
|
||||
The project configuration is stored in workspace.json and nx.json. The utility will read
|
||||
both files.
|
||||
|
||||
**`throws`** If supplied projectName cannot be found
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
|
||||
@ -122,6 +122,7 @@ myorg/
|
||||
- [library](/{{framework}}/angular/library) - Creates an Angular library.
|
||||
- [move](/{{framework}}/angular/move) - Moves an Angular application or library to another folder within the workspace and updates the project configuration.
|
||||
- [ngrx](/{{framework}}/angular/ngrx) - Adds NgRx support to an application or library.
|
||||
- [setup-mfe](/{{framework}}/angular/setup-mfe) - Generate a Module Federation configuration for a given Angular application.
|
||||
- [stories](/{{framework}}/angular/stories) - Creates stories/specs for all components declared in a project.
|
||||
- [storybook-configuration](/{{framework}}/angular/storybook-configuration) - Adds Storybook configuration to a project.
|
||||
- [storybook-migrate-defaults-5-to-6](/{{framework}}/angular/storybook-migrate-defaults-5-to-6) - Generates default Storybook configuration files using Storybook version >=6.x specs, for projects that already have Storybook instances and configurations of versions <6.x.
|
||||
|
||||
@ -84,7 +84,6 @@
|
||||
"@storybook/angular": "~6.3.0",
|
||||
"@storybook/core": "~6.3.0",
|
||||
"@storybook/react": "~6.3.0",
|
||||
"@svgr/webpack": "^5.4.0",
|
||||
"@svgr/webpack": "5.5.0",
|
||||
"@tailwindcss/typography": "^0.4.0",
|
||||
"@testing-library/react": "11.2.5",
|
||||
@ -279,4 +278,4 @@
|
||||
"ng-packagr/rxjs": "6.6.7",
|
||||
"**/xmlhttprequest-ssl": "~1.6.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -96,6 +96,13 @@
|
||||
"schema": "./src/generators/upgrade-module/schema.json",
|
||||
"description": "Sets up an Upgrade Module."
|
||||
},
|
||||
|
||||
"setup-mfe": {
|
||||
"factory": "./src/generators/setup-mfe/compat",
|
||||
"schema": "./src/generators/setup-mfe/schema.json",
|
||||
"description": "Generate a Module Federation configuration for a given Angular application."
|
||||
},
|
||||
|
||||
"web-worker": {
|
||||
"factory": "./src/generators/web-worker/compat",
|
||||
"schema": "./src/generators/web-worker/schema.json",
|
||||
@ -115,6 +122,11 @@
|
||||
"aliases": ["app"],
|
||||
"description": "Creates an Angular application."
|
||||
},
|
||||
"setup-mfe": {
|
||||
"factory": "./src/generators/setup-mfe/setup-mfe",
|
||||
"schema": "./src/generators/setup-mfe/schema.json",
|
||||
"description": "Generate a Module Federation configuration for a given Angular application."
|
||||
},
|
||||
"component-cypress-spec": {
|
||||
"factory": "./src/generators/component-cypress-spec/component-cypress-spec",
|
||||
"schema": "./src/generators/component-cypress-spec/schema.json",
|
||||
|
||||
@ -11,3 +11,4 @@ export * from './src/generators/storybook-configuration/storybook-configuration'
|
||||
export * from './src/generators/storybook-migrate-defaults-5-to-6/storybook-migrate-defaults-5-to-6';
|
||||
export * from './src/generators/storybook-migrate-stories-to-6-2/migrate-stories-to-6-2';
|
||||
export * from './src/generators/upgrade-module/upgrade-module';
|
||||
export * from './src/generators/setup-mfe/setup-mfe';
|
||||
|
||||
@ -0,0 +1,89 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Init MFE should create webpack configs correctly 1`] = `
|
||||
"const ModuleFederationPlugin = require(\\"webpack/lib/container/ModuleFederationPlugin\\");
|
||||
const mf = require(\\"@angular-architects/module-federation/webpack\\");
|
||||
const path = require(\\"path\\");
|
||||
|
||||
const sharedMappings = new mf.SharedMappings();
|
||||
sharedMappings.register(path.join(__dirname, \\"../../tsconfig.base.json\\"), [
|
||||
/* mapped paths to share */
|
||||
]);
|
||||
|
||||
module.exports = {
|
||||
output: {
|
||||
uniqueName: \\"app1\\",
|
||||
publicPath: \\"auto\\",
|
||||
},
|
||||
optimization: {
|
||||
runtimeChunk: false,
|
||||
minimize: false,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
...sharedMappings.getAliases(),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new ModuleFederationPlugin({
|
||||
remotes: {
|
||||
|
||||
},
|
||||
shared: {
|
||||
\\"@angular/core\\": { singleton: true, strictVersion: true },
|
||||
\\"@angular/common\\": { singleton: true, strictVersion: true },
|
||||
\\"@angular/common/http\\": { singleton: true, strictVersion: true },
|
||||
\\"@angular/router\\": { singleton: true, strictVersion: true },
|
||||
...sharedMappings.getDescriptors(),
|
||||
},
|
||||
}),
|
||||
sharedMappings.getPlugin(),
|
||||
],
|
||||
};
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`Init MFE should create webpack configs correctly 2`] = `
|
||||
"const ModuleFederationPlugin = require(\\"webpack/lib/container/ModuleFederationPlugin\\");
|
||||
const mf = require(\\"@angular-architects/module-federation/webpack\\");
|
||||
const path = require(\\"path\\");
|
||||
|
||||
const sharedMappings = new mf.SharedMappings();
|
||||
sharedMappings.register(path.join(__dirname, \\"../../tsconfig.base.json\\"), [
|
||||
/* mapped paths to share */
|
||||
]);
|
||||
|
||||
module.exports = {
|
||||
output: {
|
||||
uniqueName: \\"remote1\\",
|
||||
publicPath: \\"auto\\",
|
||||
},
|
||||
optimization: {
|
||||
runtimeChunk: false,
|
||||
minimize: false,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
...sharedMappings.getAliases(),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new ModuleFederationPlugin({
|
||||
name: \\"remote1\\",
|
||||
filename: \\"remoteEntry.js\\",
|
||||
exposes: {
|
||||
'./Component': 'apps/remote1/src/app/app.component.ts',
|
||||
},
|
||||
shared: {
|
||||
\\"@angular/core\\": { singleton: true, strictVersion: true },
|
||||
\\"@angular/common\\": { singleton: true, strictVersion: true },
|
||||
\\"@angular/common/http\\": { singleton: true, strictVersion: true },
|
||||
\\"@angular/router\\": { singleton: true, strictVersion: true },
|
||||
...sharedMappings.getDescriptors(),
|
||||
},
|
||||
}),
|
||||
sharedMappings.getPlugin(),
|
||||
],
|
||||
};
|
||||
"
|
||||
`;
|
||||
@ -0,0 +1,41 @@
|
||||
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
|
||||
const mf = require("@angular-architects/module-federation/webpack");
|
||||
const path = require("path");
|
||||
|
||||
const sharedMappings = new mf.SharedMappings();
|
||||
sharedMappings.register(path.join(__dirname, "../../tsconfig.base.json"), [
|
||||
/* mapped paths to share */
|
||||
]);
|
||||
|
||||
module.exports = {
|
||||
output: {
|
||||
uniqueName: "<%= name %>",
|
||||
publicPath: "auto",
|
||||
},
|
||||
optimization: {
|
||||
runtimeChunk: false,
|
||||
minimize: false,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
...sharedMappings.getAliases(),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new ModuleFederationPlugin({<% if(type === 'remote') { %>
|
||||
name: "<%= name %>",
|
||||
filename: "remoteEntry.js",
|
||||
exposes: {
|
||||
'./Component': '<%= sourceRoot %>/src/app/app.component.ts',
|
||||
},<% } %><% if(type === 'shell') { %>
|
||||
remotes: {
|
||||
<% remotes.forEach(function(remote) { %>"<%= remote.remoteName %>": "<%= remote.remoteName %>@http://localhost:<%= remote.port %>/remoteEntry.js",<% }); %>
|
||||
},<% } %>
|
||||
shared: {<% sharedLibraries.forEach(function (lib) { %>
|
||||
"<%= lib %>": { singleton: true, strictVersion: true },<% }); %>
|
||||
...sharedMappings.getDescriptors(),
|
||||
},
|
||||
}),
|
||||
sharedMappings.getPlugin(),
|
||||
],
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
module.exports = require('./webpack.config');
|
||||
@ -0,0 +1,23 @@
|
||||
import type { Tree } from '@nrwl/devkit';
|
||||
import type { Schema } from '../schema';
|
||||
|
||||
import {
|
||||
readProjectConfiguration,
|
||||
updateProjectConfiguration,
|
||||
} from '@nrwl/devkit';
|
||||
|
||||
export function addImplicitDeps(host: Tree, options: Schema) {
|
||||
if (
|
||||
options.mfeType === 'shell' &&
|
||||
Array.isArray(options.remotes) &&
|
||||
options.remotes.length > 0
|
||||
) {
|
||||
const appConfig = readProjectConfiguration(host, options.appName);
|
||||
appConfig.implicitDependencies = Array.isArray(
|
||||
appConfig.implicitDependencies
|
||||
)
|
||||
? [...appConfig.implicitDependencies, ...options.remotes]
|
||||
: [...options.remotes];
|
||||
updateProjectConfiguration(host, options.appName, appConfig);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
import type { Tree } from '@nrwl/devkit';
|
||||
import type { Schema } from '../schema';
|
||||
|
||||
import {
|
||||
readProjectConfiguration,
|
||||
updateProjectConfiguration,
|
||||
} from '@nrwl/devkit';
|
||||
|
||||
export function changeBuildTarget(host: Tree, options: Schema) {
|
||||
const appConfig = readProjectConfiguration(host, options.appName);
|
||||
|
||||
appConfig.targets.build.executor = '@nrwl/angular:webpack-browser';
|
||||
appConfig.targets.build.options = {
|
||||
...appConfig.targets.build.options,
|
||||
customWebpackConfig: {
|
||||
path: `${appConfig.root}/webpack.config.js`,
|
||||
},
|
||||
};
|
||||
|
||||
appConfig.targets.build.configurations.production = {
|
||||
...appConfig.targets.build.configurations.production,
|
||||
customWebpackConfig: {
|
||||
path: `${appConfig.root}/webpack.prod.config.js`,
|
||||
},
|
||||
};
|
||||
|
||||
updateProjectConfiguration(host, options.appName, appConfig);
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
import type { Tree } from '@nrwl/devkit';
|
||||
import type { Schema } from '../schema';
|
||||
|
||||
import { joinPathFragments } from '@nrwl/devkit';
|
||||
|
||||
export function fixBootstrap(host: Tree, appRoot: string) {
|
||||
const mainFilePath = joinPathFragments(appRoot, 'src/main.ts');
|
||||
const bootstrapCode = host.read(mainFilePath, 'utf-8');
|
||||
host.write(joinPathFragments(appRoot, 'src/bootstrap.ts'), bootstrapCode);
|
||||
|
||||
host.write(
|
||||
mainFilePath,
|
||||
`import('./bootstrap').catch(err => console.error(err));`
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
import type { Tree } from '@nrwl/devkit';
|
||||
import type { Schema } from '../schema';
|
||||
import { generateFiles, joinPathFragments, logger } from '@nrwl/devkit';
|
||||
|
||||
const SHARED_SINGLETON_LIBRARIES = [
|
||||
'@angular/core',
|
||||
'@angular/common',
|
||||
'@angular/common/http',
|
||||
'@angular/router',
|
||||
];
|
||||
|
||||
export function generateWebpackConfig(
|
||||
host: Tree,
|
||||
options: Schema,
|
||||
appRoot: string,
|
||||
remotesWithPorts: { remoteName: string; port: number }[]
|
||||
) {
|
||||
if (
|
||||
host.exists(`${appRoot}/webpack.config.js`) ||
|
||||
host.exists(`${appRoot}/webpack.prod.config.js`)
|
||||
) {
|
||||
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
|
||||
If this was not the outcome you expected, you can discard the changes we have made, create a backup of your current webpack config, and run the command again.`
|
||||
);
|
||||
}
|
||||
generateFiles(host, joinPathFragments(__dirname, '../files'), appRoot, {
|
||||
tmpl: '',
|
||||
type: options.mfeType,
|
||||
name: options.appName,
|
||||
remotes: remotesWithPorts ?? [],
|
||||
sourceRoot: appRoot,
|
||||
sharedLibraries: SHARED_SINGLETON_LIBRARIES,
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
import type { Tree } from '@nrwl/devkit';
|
||||
import type { Schema } from '../schema';
|
||||
|
||||
import { readProjectConfiguration } from '@nrwl/devkit';
|
||||
|
||||
export function getRemotesWithPorts(host: Tree, options: Schema) {
|
||||
// If type is shell and remotes supplied, check remotes exist
|
||||
const remotesWithPort: { remoteName: string; port: number }[] = [];
|
||||
if (
|
||||
options.mfeType === 'shell' &&
|
||||
Array.isArray(options.remotes) &&
|
||||
options.remotes.length > 0
|
||||
) {
|
||||
for (const remote of options.remotes) {
|
||||
const remoteConfig = readProjectConfiguration(host, remote);
|
||||
remotesWithPort.push({
|
||||
remoteName: remote,
|
||||
port: remoteConfig.targets['mfe-serve']?.options.port ?? 4200,
|
||||
});
|
||||
}
|
||||
}
|
||||
return remotesWithPort;
|
||||
}
|
||||
6
packages/angular/src/generators/setup-mfe/lib/index.ts
Normal file
6
packages/angular/src/generators/setup-mfe/lib/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export * from './add-implicit-deps';
|
||||
export * from './change-build-target';
|
||||
export * from './fix-bootstrap';
|
||||
export * from './generate-config';
|
||||
export * from './get-remotes-with-ports';
|
||||
export * from './setup-serve-target';
|
||||
@ -0,0 +1,25 @@
|
||||
import type { Tree } from '@nrwl/devkit';
|
||||
import type { Schema } from '../schema';
|
||||
|
||||
import {
|
||||
readProjectConfiguration,
|
||||
updateProjectConfiguration,
|
||||
} from '@nrwl/devkit';
|
||||
|
||||
export function setupServeTarget(host: Tree, options: Schema) {
|
||||
if (options.mfeType === 'remote') {
|
||||
const appConfig = readProjectConfiguration(host, options.appName);
|
||||
|
||||
const port = options.port ?? 4200;
|
||||
|
||||
appConfig.targets['mfe-serve'] = {
|
||||
executor: '@nrwl/workspace:run-commands',
|
||||
options: {
|
||||
command: `nx serve ${options.appName}`,
|
||||
port,
|
||||
},
|
||||
};
|
||||
|
||||
updateProjectConfiguration(host, options.appName, appConfig);
|
||||
}
|
||||
}
|
||||
7
packages/angular/src/generators/setup-mfe/schema.d.ts
vendored
Normal file
7
packages/angular/src/generators/setup-mfe/schema.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
export interface Schema {
|
||||
appName: string;
|
||||
mfeType: 'shell' | 'remote';
|
||||
port?: number;
|
||||
remotes?: string[];
|
||||
skipFormat?: boolean;
|
||||
}
|
||||
39
packages/angular/src/generators/setup-mfe/schema.json
Normal file
39
packages/angular/src/generators/setup-mfe/schema.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"$id": "GeneratorAngularMFESetup",
|
||||
"cli": "nx",
|
||||
"title": "Generate Module Federation Setup for Angular App",
|
||||
"description": "Create Module Federation configuration files for given Angular Application.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"appName": {
|
||||
"type": "string",
|
||||
"description": "The name of the application to generate the Module Federation configuration for.",
|
||||
"$default": {
|
||||
"$source": "argv",
|
||||
"index": 0
|
||||
},
|
||||
"x-prompt": "What app would you like to generate a Module Federation configuration for?"
|
||||
},
|
||||
"mfeType": {
|
||||
"type": "string",
|
||||
"enum": ["shell", "remote"],
|
||||
"description": "Type of application to generate the Module Federation configuration for.",
|
||||
"default": "shell"
|
||||
},
|
||||
"port": {
|
||||
"type": "number",
|
||||
"description": "The port at which the remote application should be served."
|
||||
},
|
||||
"remotes": {
|
||||
"type": "array",
|
||||
"description": "A list of remote application names that the shell application should consume."
|
||||
},
|
||||
"skipFormat": {
|
||||
"type": "boolean",
|
||||
"description": "Skip formatting the workspace after the generator completes."
|
||||
}
|
||||
},
|
||||
"required": ["appName", "mfeType"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
import { convertNxGenerator } from '@nrwl/devkit';
|
||||
import { setupMfe } from './setup-mfe';
|
||||
|
||||
export default convertNxGenerator(setupMfe);
|
||||
167
packages/angular/src/generators/setup-mfe/setup-mfe.spec.ts
Normal file
167
packages/angular/src/generators/setup-mfe/setup-mfe.spec.ts
Normal file
@ -0,0 +1,167 @@
|
||||
import type { NxJsonConfiguration, Tree } from '@nrwl/devkit';
|
||||
import { readJson, readProjectConfiguration } from '@nrwl/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
|
||||
import { setupMfe } from './setup-mfe';
|
||||
import applicationGenerator from '../application/application';
|
||||
describe('Init MFE', () => {
|
||||
let host: Tree;
|
||||
|
||||
beforeEach(async () => {
|
||||
host = createTreeWithEmptyWorkspace();
|
||||
await applicationGenerator(host, {
|
||||
name: 'app1',
|
||||
});
|
||||
await applicationGenerator(host, {
|
||||
name: 'remote1',
|
||||
});
|
||||
});
|
||||
|
||||
test.each([
|
||||
['app1', 'shell'],
|
||||
['remote1', 'remote'],
|
||||
])(
|
||||
'should create webpack configs correctly',
|
||||
async (app, type: 'shell' | 'remote') => {
|
||||
// ACT
|
||||
await setupMfe(host, {
|
||||
appName: app,
|
||||
mfeType: type,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
expect(host.exists(`apps/${app}/webpack.config.js`)).toBeTruthy();
|
||||
expect(host.exists(`apps/${app}/webpack.prod.config.js`)).toBeTruthy();
|
||||
|
||||
const webpackContetnts = host.read(
|
||||
`apps/${app}/webpack.config.js`,
|
||||
'utf-8'
|
||||
);
|
||||
expect(webpackContetnts).toMatchSnapshot();
|
||||
}
|
||||
);
|
||||
|
||||
test.each([
|
||||
['app1', 'shell'],
|
||||
['remote1', 'remote'],
|
||||
])(
|
||||
'create bootstrap file with the contents of main.ts',
|
||||
async (app, type: 'shell' | 'remote') => {
|
||||
// ARRANGE
|
||||
const mainContents = host.read(`apps/${app}/src/main.ts`, 'utf-8');
|
||||
|
||||
// ACT
|
||||
await setupMfe(host, {
|
||||
appName: app,
|
||||
mfeType: type,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
const bootstrapContents = host.read(
|
||||
`apps/${app}/src/bootstrap.ts`,
|
||||
'utf-8'
|
||||
);
|
||||
const updatedMainContents = host.read(`apps/${app}/src/main.ts`, 'utf-8');
|
||||
|
||||
expect(bootstrapContents).toEqual(mainContents);
|
||||
expect(updatedMainContents).not.toEqual(mainContents);
|
||||
}
|
||||
);
|
||||
|
||||
test.each([
|
||||
['app1', 'shell'],
|
||||
['remote1', 'remote'],
|
||||
])(
|
||||
'should alter main.ts to import the bootstrap file dynamically',
|
||||
async (app, type: 'shell' | 'remote') => {
|
||||
// ARRANGE
|
||||
const mainContents = host.read(`apps/${app}/src/main.ts`, 'utf-8');
|
||||
|
||||
// ACT
|
||||
await setupMfe(host, {
|
||||
appName: app,
|
||||
mfeType: type,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
const updatedMainContents = host.read(`apps/${app}/src/main.ts`, 'utf-8');
|
||||
|
||||
expect(updatedMainContents).toEqual(
|
||||
`import('./bootstrap').catch(err => console.error(err));`
|
||||
);
|
||||
expect(updatedMainContents).not.toEqual(mainContents);
|
||||
}
|
||||
);
|
||||
|
||||
test.each([
|
||||
['app1', 'shell'],
|
||||
['remote1', 'remote'],
|
||||
])(
|
||||
'should change the build target and set correct path to webpack config',
|
||||
async (app, type: 'shell' | 'remote') => {
|
||||
// ACT
|
||||
await setupMfe(host, {
|
||||
appName: app,
|
||||
mfeType: type,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
const { build } = readProjectConfiguration(host, app).targets;
|
||||
|
||||
expect(build.executor).toEqual('@nrwl/angular:webpack-browser');
|
||||
expect(build.options.customWebpackConfig.path).toEqual(
|
||||
`apps/${app}/webpack.config.js`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
test.each([
|
||||
['app1', 'shell'],
|
||||
['remote1', 'remote'],
|
||||
])(
|
||||
'should install @angular-architects/module-federation in the monorepo',
|
||||
async (app, type: 'shell' | 'remote') => {
|
||||
// ACT
|
||||
await setupMfe(host, {
|
||||
appName: app,
|
||||
mfeType: type,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
const { dependencies } = readJson(host, 'package.json');
|
||||
|
||||
expect(
|
||||
dependencies['@angular-architects/module-federation']
|
||||
).toBeTruthy();
|
||||
}
|
||||
);
|
||||
|
||||
it('should add the remote config to the shell when --remotes flag supplied', async () => {
|
||||
// ACT
|
||||
await setupMfe(host, {
|
||||
appName: 'app1',
|
||||
mfeType: 'shell',
|
||||
remotes: ['remote1'],
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
const webpackContents = host.read(`apps/app1/webpack.config.js`, 'utf-8');
|
||||
|
||||
expect(webpackContents).toContain(
|
||||
'"remote1": "remote1@http://localhost:4200/remoteEntry.js"'
|
||||
);
|
||||
});
|
||||
it('should update the implicit dependencies of the shell when --remotes flag supplied', async () => {
|
||||
// ACT
|
||||
await setupMfe(host, {
|
||||
appName: 'app1',
|
||||
mfeType: 'shell',
|
||||
remotes: ['remote1'],
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
const nxJson: NxJsonConfiguration = readJson(host, 'nx.json');
|
||||
|
||||
expect(nxJson.projects['app1'].implicitDependencies).toContain('remote1');
|
||||
});
|
||||
});
|
||||
47
packages/angular/src/generators/setup-mfe/setup-mfe.ts
Normal file
47
packages/angular/src/generators/setup-mfe/setup-mfe.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import type { Tree } from '@nrwl/devkit';
|
||||
import type { Schema } from './schema';
|
||||
|
||||
import {
|
||||
readProjectConfiguration,
|
||||
addDependenciesToPackageJson,
|
||||
formatFiles,
|
||||
} from '@nrwl/devkit';
|
||||
|
||||
import {
|
||||
addImplicitDeps,
|
||||
changeBuildTarget,
|
||||
fixBootstrap,
|
||||
generateWebpackConfig,
|
||||
getRemotesWithPorts,
|
||||
setupServeTarget,
|
||||
} from './lib';
|
||||
|
||||
export async function setupMfe(host: Tree, options: Schema) {
|
||||
const projectConfig = readProjectConfiguration(host, options.appName);
|
||||
|
||||
const remotesWithPorts = getRemotesWithPorts(host, options);
|
||||
|
||||
generateWebpackConfig(host, options, projectConfig.root, remotesWithPorts);
|
||||
|
||||
addImplicitDeps(host, options);
|
||||
changeBuildTarget(host, options);
|
||||
setupServeTarget(host, options);
|
||||
|
||||
fixBootstrap(host, projectConfig.root);
|
||||
|
||||
// add package to install
|
||||
const installPackages = addDependenciesToPackageJson(
|
||||
host,
|
||||
{ '@angular-architects/module-federation': '^12.2.0' },
|
||||
{}
|
||||
);
|
||||
|
||||
// format files
|
||||
if (!options.skipFormat) {
|
||||
await formatFiles(host);
|
||||
}
|
||||
|
||||
return installPackages;
|
||||
}
|
||||
|
||||
export default setupMfe;
|
||||
@ -151,6 +151,7 @@ export function updateWorkspaceConfiguration(
|
||||
*
|
||||
* @param host - the file system tree
|
||||
* @param projectName - unique name. Often directories are part of the name (e.g., mydir-mylib)
|
||||
* @throws If supplied projectName cannot be found
|
||||
*/
|
||||
export function readProjectConfiguration(
|
||||
host: Tree,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user