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-remote generator #9191

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-remote.md
@@ -0,0 +1,60 @@
---
title: '@nrwl/angular:mfe-remote generator'
description: 'Generate a Remote Angular Micro-Frontend Application.'
---

# @nrwl/angular:mfe-remote

Generate a Remote Angular Micro-Frontend Application.

## Usage

```bash
nx generate mfe-remote ...
```

```bash
nx g remote ... # same
```

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

You can specify the collection explicitly as follows:

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

Show what will be generated without writing to disk:

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

### Examples

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

```bash
nx g @nrwl/angular:mfe-remote appName --host=host --port=4201
```

## Options

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

Type: `string`

The name to give to the remote Angular app.

### host

Type: `string`

The name of the host app to attach this remote 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 @@ -643,6 +643,11 @@
"id": "mfe-host",
"file": "generated/api-angular/generators/mfe-host"
},
{
"name": "mfe-remote generator",
"id": "mfe-remote",
"file": "generated/api-angular/generators/mfe-remote"
},
{
"name": "move generator",
"id": "move",
Expand Down
12 changes: 12 additions & 0 deletions packages/angular/generators.json
Expand Up @@ -68,6 +68,12 @@
"aliases": ["secondary-entry-point", "entry-point"],
"description": "Creates a secondary entry point for an Angular publishable library."
},
"mfe-remote": {
"factory": "./src/generators/mfe-remote/mfe-remote.compat",
"schema": "./src/generators/mfe-remote/schema.json",
"aliases": ["remote"],
"description": "Generate a Remote Angular Micro-Frontend Application."
},
"move": {
"factory": "./src/generators/move/move#angularMoveSchematic",
"schema": "./src/generators/move/schema.json",
Expand Down Expand Up @@ -207,6 +213,12 @@
"aliases": ["secondary-entry-point", "entry-point"],
"description": "Creates a secondary entry point for an Angular publishable library."
},
"mfe-remote": {
"factory": "./src/generators/mfe-remote/mfe-remote",
"schema": "./src/generators/mfe-remote/schema.json",
"aliases": ["remote"],
"description": "Generate a Remote Angular Micro-Frontend Application."
},
"move": {
"factory": "./src/generators/move/move#angularMoveGenerator",
"schema": "./src/generators/move/schema.json",
Expand Down
1 change: 1 addition & 0 deletions packages/angular/generators.ts
Expand Up @@ -23,3 +23,4 @@ export * from './src/generators/add-linting/add-linting';
export * from './src/generators/component-cypress-spec/component-cypress-spec';
export * from './src/generators/component-story/component-story';
export * from './src/generators/web-worker/web-worker';
export * from './src/generators/mfe-remote/mfe-remote';
@@ -0,0 +1,192 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`MFE Remote App Generator should generate a remote mfe app with a host 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: \\"host\\",
publicPath: \\"auto\\",
},
optimization: {
runtimeChunk: false,
},
experiments: {
outputModule: true
},
resolve: {
alias: {
...sharedMappings.getAliases(),
},
},
plugins: [
new ModuleFederationPlugin({
remotes: {
\\"test\\": '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 Remote App Generator should generate a remote mfe app with a host 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({
name: \\"test\\",
filename: \\"remoteEntry.js\\",
exposes: {
'./Module': 'apps/test/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 Remote App Generator should generate a remote mfe app with no host 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({
name: \\"test\\",
filename: \\"remoteEntry.js\\",
exposes: {
'./Module': 'apps/test/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(),
],
};
"
`;
@@ -0,0 +1,4 @@
import { convertNxGenerator } from '@nrwl/devkit';
import mfeRemote from './mfe-remote';

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

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

// ACT
await mfeRemote(tree, {
name: 'test',
port: 4201,
});

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

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

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

// ACT
await mfeRemote(tree, {
name: 'test',
host: 'host',
port: 4201,
});

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

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

// ACT
try {
await mfeRemote(tree, {
name: 'test',
host: 'host',
port: 4201,
});
} catch (error) {
// ASSERT
expect(error.message).toEqual(
'The name of the application to be used as the host app does not exist. (host)'
);
}
});
});
24 changes: 24 additions & 0 deletions packages/angular/src/generators/mfe-remote/mfe-remote.ts
@@ -0,0 +1,24 @@
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 mfeRemote(tree: Tree, options: Schema) {
const projects = getProjects(tree);
if (options.host && !projects.has(options.host)) {
throw new Error(
`The name of the application to be used as the host app does not exist. (${options.host})`
);
}

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

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