Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(angular): add mfe-host generator #9218

Merged
merged 1 commit into from Mar 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
60 changes: 60 additions & 0 deletions docs/generated/api-angular/generators/mfe-host.md
@@ -0,0 +1,60 @@
---
title: '@nrwl/angular:mfe-host generator'
description: 'Generate a Host Angular Micro Frontend Application.'
---

# @nrwl/angular:mfe-host

Generate a Host Angular Micro Frontend Application.

## Usage

```bash
nx generate mfe-host ...
```

```bash
nx g host ... # same
```

By default, Nx will search for `mfe-host` in the default collection provisioned in `workspace.json`.

You can specify the collection explicitly as follows:

```bash
nx g @nrwl/angular:mfe-host ...
```

Show what will be generated without writing to disk:

```bash
nx g mfe-host ... --dry-run
```

### Examples

Create an Angular app with configuration in place for MFE. If remotes is provided, attach the remote app to this app's configuration.:

```bash
nx g @nrwl/angular:mfe-host appName --remotes=remote1
```

## Options

### name (_**required**_)

Type: `string`

The name to give to the host Angular app.

### host

Type: `string`

The name of the host app to attach this host app to.

### port

Type: `string`

The port on which this app should be served.
5 changes: 5 additions & 0 deletions docs/map.json
Expand Up @@ -638,6 +638,11 @@
"id": "library-secondary-entry-point",
"file": "generated/api-angular/generators/library-secondary-entry-point"
},
{
"name": "mfe-host generator",
"id": "mfe-host",
"file": "generated/api-angular/generators/mfe-host"
},
{
"name": "move generator",
"id": "move",
Expand Down
12 changes: 12 additions & 0 deletions packages/angular/generators.json
Expand Up @@ -74,6 +74,12 @@
"aliases": ["mv"],
"description": "Moves an Angular application or library to another folder within the workspace and updates the project configuration."
},
"mfe-host": {
"factory": "./src/generators/mfe-host/mfe-host.compat",
"schema": "./src/generators/mfe-host/schema.json",
"aliases": ["host"],
"description": "Generate a Host Angular Micro Frontend Application."
},
"ngrx": {
"factory": "./src/generators/ngrx/compat",
"schema": "./src/generators/ngrx/schema.json",
Expand Down Expand Up @@ -207,6 +213,12 @@
"aliases": ["mv"],
"description": "Moves an Angular application or library to another folder within the workspace and updates the project configuration."
},
"mfe-host": {
"factory": "./src/generators/mfe-host/mfe-host",
"schema": "./src/generators/mfe-host/schema.json",
"aliases": ["host"],
"description": "Generate a Host Angular Micro Frontend Application."
},
"ngrx": {
"factory": "./src/generators/ngrx/ngrx",
"schema": "./src/generators/ngrx/schema.json",
Expand Down
1 change: 1 addition & 0 deletions packages/angular/generators.ts
Expand Up @@ -15,6 +15,7 @@ export * from './src/generators/storybook-migrate-defaults-5-to-6/storybook-migr
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';
export * from './src/generators/mfe-host/mfe-host';
export * from './src/generators/scam/scam';
export * from './src/generators/scam-directive/scam-directive';
export * from './src/generators/scam-pipe/scam-pipe';
Expand Down
@@ -0,0 +1,189 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`MFE Host App Generator should generate a host mfe app with a remote 1`] = `
"const ModuleFederationPlugin = require(\\"webpack/lib/container/ModuleFederationPlugin\\");
const mf = require(\\"@angular-architects/module-federation/webpack\\");
const path = require(\\"path\\");
const share = mf.share;

/**
* We use the NX_TSCONFIG_PATH environment variable when using the @nrwl/angular:webpack-browser
* builder as it will generate a temporary tsconfig file which contains any required remappings of
* shared libraries.
* A remapping will occur when a library is buildable, as webpack needs to know the location of the
* built files for the buildable library.
* This NX_TSCONFIG_PATH environment variable is set by the @nrwl/angular:webpack-browser and it contains
* the location of the generated temporary tsconfig file.
*/
const tsConfigPath = process.env.NX_TSCONFIG_PATH ?? path.join(__dirname, '../../tsconfig.base.json');

const workspaceRootPath = path.join(__dirname, '../../');
const sharedMappings = new mf.SharedMappings();
sharedMappings.register(tsConfigPath, [
/* mapped paths to share */
], workspaceRootPath);

module.exports = {
output: {
uniqueName: \\"remote\\",
publicPath: \\"auto\\",
},
optimization: {
runtimeChunk: false,
},
experiments: {
outputModule: true
},
resolve: {
alias: {
...sharedMappings.getAliases(),
},
},
plugins: [
new ModuleFederationPlugin({
name: \\"remote\\",
filename: \\"remoteEntry.js\\",
exposes: {
'./Module': 'apps/remote/src/app/remote-entry/entry.module.ts',
},
shared: share({
\\"@angular/core\\": { singleton: true, strictVersion: true, requiredVersion: 'auto', includeSecondaries: true },
\\"@angular/common\\": { singleton: true, strictVersion: true, requiredVersion: 'auto', includeSecondaries: true },
\\"@angular/common/http\\": { singleton: true, strictVersion: true, requiredVersion: 'auto', includeSecondaries: true },
\\"@angular/router\\": { singleton: true, strictVersion: true, requiredVersion: 'auto', includeSecondaries: true },
\\"rxjs\\": { singleton: true, strictVersion: true, requiredVersion: 'auto', includeSecondaries: true },
...sharedMappings.getDescriptors(),
}),
library: {
type: 'module'
},
}),
sharedMappings.getPlugin(),
],
};
"
`;

exports[`MFE Host App Generator should generate a host mfe app with a remote 2`] = `
"const ModuleFederationPlugin = require(\\"webpack/lib/container/ModuleFederationPlugin\\");
const mf = require(\\"@angular-architects/module-federation/webpack\\");
const path = require(\\"path\\");
const share = mf.share;

/**
* We use the NX_TSCONFIG_PATH environment variable when using the @nrwl/angular:webpack-browser
* builder as it will generate a temporary tsconfig file which contains any required remappings of
* shared libraries.
* A remapping will occur when a library is buildable, as webpack needs to know the location of the
* built files for the buildable library.
* This NX_TSCONFIG_PATH environment variable is set by the @nrwl/angular:webpack-browser and it contains
* the location of the generated temporary tsconfig file.
*/
const tsConfigPath = process.env.NX_TSCONFIG_PATH ?? path.join(__dirname, '../../tsconfig.base.json');

const workspaceRootPath = path.join(__dirname, '../../');
const sharedMappings = new mf.SharedMappings();
sharedMappings.register(tsConfigPath, [
/* mapped paths to share */
], workspaceRootPath);

module.exports = {
output: {
uniqueName: \\"test\\",
publicPath: \\"auto\\",
},
optimization: {
runtimeChunk: false,
},
experiments: {
outputModule: true
},
resolve: {
alias: {
...sharedMappings.getAliases(),
},
},
plugins: [
new ModuleFederationPlugin({
remotes: {
\\"remote\\": \\"http://localhost:4201/remoteEntry.js\\",
},
shared: share({
\\"@angular/core\\": { singleton: true, strictVersion: true, requiredVersion: 'auto', includeSecondaries: true },
\\"@angular/common\\": { singleton: true, strictVersion: true, requiredVersion: 'auto', includeSecondaries: true },
\\"@angular/common/http\\": { singleton: true, strictVersion: true, requiredVersion: 'auto', includeSecondaries: true },
\\"@angular/router\\": { singleton: true, strictVersion: true, requiredVersion: 'auto', includeSecondaries: true },
\\"rxjs\\": { singleton: true, strictVersion: true, requiredVersion: 'auto', includeSecondaries: true },
...sharedMappings.getDescriptors(),
}),
library: {
type: 'module'
},
}),
sharedMappings.getPlugin(),
],
};
"
`;

exports[`MFE Host App Generator should generate a host mfe app with no remotes 1`] = `
"const ModuleFederationPlugin = require(\\"webpack/lib/container/ModuleFederationPlugin\\");
const mf = require(\\"@angular-architects/module-federation/webpack\\");
const path = require(\\"path\\");
const share = mf.share;

/**
* We use the NX_TSCONFIG_PATH environment variable when using the @nrwl/angular:webpack-browser
* builder as it will generate a temporary tsconfig file which contains any required remappings of
* shared libraries.
* A remapping will occur when a library is buildable, as webpack needs to know the location of the
* built files for the buildable library.
* This NX_TSCONFIG_PATH environment variable is set by the @nrwl/angular:webpack-browser and it contains
* the location of the generated temporary tsconfig file.
*/
const tsConfigPath = process.env.NX_TSCONFIG_PATH ?? path.join(__dirname, '../../tsconfig.base.json');

const workspaceRootPath = path.join(__dirname, '../../');
const sharedMappings = new mf.SharedMappings();
sharedMappings.register(tsConfigPath, [
/* mapped paths to share */
], workspaceRootPath);

module.exports = {
output: {
uniqueName: \\"test\\",
publicPath: \\"auto\\",
},
optimization: {
runtimeChunk: false,
},
experiments: {
outputModule: true
},
resolve: {
alias: {
...sharedMappings.getAliases(),
},
},
plugins: [
new ModuleFederationPlugin({
remotes: {

},
shared: share({
\\"@angular/core\\": { singleton: true, strictVersion: true, requiredVersion: 'auto', includeSecondaries: true },
\\"@angular/common\\": { singleton: true, strictVersion: true, requiredVersion: 'auto', includeSecondaries: true },
\\"@angular/common/http\\": { singleton: true, strictVersion: true, requiredVersion: 'auto', includeSecondaries: true },
\\"@angular/router\\": { singleton: true, strictVersion: true, requiredVersion: 'auto', includeSecondaries: true },
\\"rxjs\\": { singleton: true, strictVersion: true, requiredVersion: 'auto', includeSecondaries: true },
...sharedMappings.getDescriptors(),
}),
library: {
type: 'module'
},
}),
sharedMappings.getPlugin(),
],
};
"
`;
4 changes: 4 additions & 0 deletions packages/angular/src/generators/mfe-host/mfe-host.compat.ts
@@ -0,0 +1,4 @@
import { convertNxGenerator } from '@nrwl/devkit';
import mfeHost from './mfe-host';

export default convertNxGenerator(mfeHost);
61 changes: 61 additions & 0 deletions packages/angular/src/generators/mfe-host/mfe-host.spec.ts
@@ -0,0 +1,61 @@
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import mfeHost from './mfe-host';
import applicationGenerator from '../application/application';

describe('MFE Host App Generator', () => {
it('should generate a host mfe app with no remotes', async () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace(2);

// ACT
await mfeHost(tree, {
name: 'test',
});

// ASSERT
expect(tree.read('apps/test/webpack.config.js', 'utf-8')).toMatchSnapshot();
});

it('should generate a host mfe app with a remote', async () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace(2);

await applicationGenerator(tree, {
name: 'remote',
mfe: true,
mfeType: 'remote',
routing: true,
port: 4201,
});

// ACT
await mfeHost(tree, {
name: 'test',
remotes: ['remote'],
});

// ASSERT
expect(
tree.read('apps/remote/webpack.config.js', 'utf-8')
).toMatchSnapshot();
expect(tree.read('apps/test/webpack.config.js', 'utf-8')).toMatchSnapshot();
});

it('should error when a host app is attempted to be generated with an incorrect remote', async () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace(2);

// ACT
try {
await mfeHost(tree, {
name: 'test',
remotes: ['remote'],
});
} catch (error) {
// ASSERT
expect(error.message).toEqual(
'Could not find specified remote application (remote)'
);
}
});
});
30 changes: 30 additions & 0 deletions packages/angular/src/generators/mfe-host/mfe-host.ts
@@ -0,0 +1,30 @@
import type { Tree } from '@nrwl/devkit';
import type { Schema } from './schema';

import { getProjects } from '@nrwl/devkit';
import applicationGenerator from '../application/application';

export default async function mfeHost(tree: Tree, options: Schema) {
const projects = getProjects(tree);

if (options.remotes && options.remotes.length > 0) {
options.remotes.forEach((remote) => {
if (!projects.has(remote)) {
throw new Error(
`Could not find specified remote application (${remote})`
);
}
});
}

const installTask = await applicationGenerator(tree, {
name: options.name,
mfe: true,
mfeType: 'host',
routing: true,
remotes: options.remotes ?? [],
port: 4200,
});

return installTask;
}
4 changes: 4 additions & 0 deletions packages/angular/src/generators/mfe-host/schema.d.ts
@@ -0,0 +1,4 @@
export interface Schema {
name: string;
remotes?: string[];
}