feat(angular): add host option to MFE generator (#6368)
Add a host option to MFE generator to allow a remote to specify a host that it should be consumed by. Use this value to update the host application's webpack.config,js
This commit is contained in:
parent
904b3b6b7a
commit
776bd277b7
@ -50,6 +50,12 @@ Possible values: `protractor`, `cypress`, `none`
|
||||
|
||||
Test runner to use for end to end (e2e) tests.
|
||||
|
||||
### host
|
||||
|
||||
Type: `string`
|
||||
|
||||
The name of the host application that the remote application will be consumed by.
|
||||
|
||||
### inlineStyle
|
||||
|
||||
Alias(es): s
|
||||
|
||||
@ -40,6 +40,12 @@ Possible values: `host`, `remote`
|
||||
|
||||
Type of application to generate the Module Federation configuration for.
|
||||
|
||||
### host
|
||||
|
||||
Type: `string`
|
||||
|
||||
The name of the host application that the remote application will be consumed by.
|
||||
|
||||
### port
|
||||
|
||||
Type: `number`
|
||||
|
||||
@ -50,6 +50,12 @@ Possible values: `protractor`, `cypress`, `none`
|
||||
|
||||
Test runner to use for end to end (e2e) tests.
|
||||
|
||||
### host
|
||||
|
||||
Type: `string`
|
||||
|
||||
The name of the host application that the remote application will be consumed by.
|
||||
|
||||
### inlineStyle
|
||||
|
||||
Alias(es): s
|
||||
|
||||
@ -40,6 +40,12 @@ Possible values: `host`, `remote`
|
||||
|
||||
Type of application to generate the Module Federation configuration for.
|
||||
|
||||
### host
|
||||
|
||||
Type: `string`
|
||||
|
||||
The name of the host application that the remote application will be consumed by.
|
||||
|
||||
### port
|
||||
|
||||
Type: `number`
|
||||
|
||||
@ -50,6 +50,12 @@ Possible values: `protractor`, `cypress`, `none`
|
||||
|
||||
Test runner to use for end to end (e2e) tests.
|
||||
|
||||
### host
|
||||
|
||||
Type: `string`
|
||||
|
||||
The name of the host application that the remote application will be consumed by.
|
||||
|
||||
### inlineStyle
|
||||
|
||||
Alias(es): s
|
||||
|
||||
@ -40,6 +40,12 @@ Possible values: `host`, `remote`
|
||||
|
||||
Type of application to generate the Module Federation configuration for.
|
||||
|
||||
### host
|
||||
|
||||
Type: `string`
|
||||
|
||||
The name of the host application that the remote application will be consumed by.
|
||||
|
||||
### port
|
||||
|
||||
Type: `number`
|
||||
|
||||
@ -71,6 +71,7 @@
|
||||
"@nrwl/tao": "12.6.0-beta.2",
|
||||
"@nrwl/web": "12.6.0-beta.2",
|
||||
"@nrwl/workspace": "12.6.0-beta.2",
|
||||
"@phenomnomnominal/tsquery": "4.1.1",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
|
||||
"@popperjs/core": "^2.9.2",
|
||||
"@reduxjs/toolkit": "1.5.0",
|
||||
@ -280,4 +281,4 @@
|
||||
"ng-packagr/rxjs": "6.6.7",
|
||||
"**/xmlhttprequest-ssl": "~1.6.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -15,6 +15,7 @@
|
||||
"@angular-devkit",
|
||||
"@angular-eslint/",
|
||||
"@schematics",
|
||||
"@phenomnomnominal/tsquery",
|
||||
"ignore",
|
||||
"jasmine-marbles",
|
||||
"rxjs-for-await",
|
||||
|
||||
@ -40,6 +40,7 @@
|
||||
"@nrwl/linter": "*",
|
||||
"@nrwl/storybook": "*",
|
||||
"@schematics/angular": "^12.0.0",
|
||||
"@phenomnomnominal/tsquery": "4.1.1",
|
||||
"ignore": "^5.0.4",
|
||||
"jasmine-marbles": "~0.6.0",
|
||||
"rxjs-for-await": "0.0.2",
|
||||
|
||||
@ -1,5 +1,94 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`app --mfe should add a remote application and add it to a specified host applications webpack config that contains a remote application already 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: {
|
||||
remote1: 'remote1@http://localhost:4201/remoteEntry.js',
|
||||
remote2: 'remote2@http://localhost:4202/remoteEntry.js',
|
||||
|
||||
},
|
||||
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[`app --mfe 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`] = `
|
||||
"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: {
|
||||
remote1: 'remote1@http://localhost:4200/remoteEntry.js',
|
||||
|
||||
},
|
||||
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[`app --mfe should generate a Module Federation correctly for a each app 1`] = `
|
||||
"const ModuleFederationPlugin = require(\\"webpack/lib/container/ModuleFederationPlugin\\");
|
||||
const mf = require(\\"@angular-architects/module-federation/webpack\\");
|
||||
|
||||
@ -679,6 +679,58 @@ describe('app', () => {
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
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 generateApp(appTree, 'app1', {
|
||||
mfe: true,
|
||||
mfeType: 'host',
|
||||
});
|
||||
|
||||
// ACT
|
||||
await generateApp(appTree, 'remote1', {
|
||||
mfe: true,
|
||||
mfeType: 'remote',
|
||||
host: 'app1',
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
const hostWebpackConfig = appTree.read(
|
||||
'apps/app1/webpack.config.js',
|
||||
'utf-8'
|
||||
);
|
||||
expect(hostWebpackConfig).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 generateApp(appTree, 'app1', {
|
||||
mfe: true,
|
||||
mfeType: 'host',
|
||||
});
|
||||
|
||||
await generateApp(appTree, 'remote1', {
|
||||
mfe: true,
|
||||
mfeType: 'remote',
|
||||
host: 'app1',
|
||||
port: 4201,
|
||||
});
|
||||
|
||||
// ACT
|
||||
await generateApp(appTree, 'remote2', {
|
||||
mfe: true,
|
||||
mfeType: 'remote',
|
||||
host: 'app1',
|
||||
port: 4202,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
const hostWebpackConfig = appTree.read(
|
||||
'apps/app1/webpack.config.js',
|
||||
'utf-8'
|
||||
);
|
||||
expect(hostWebpackConfig).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ export async function addMfe(host: Tree, options: NormalizedSchema) {
|
||||
mfeType: options.mfeType,
|
||||
port: options.port,
|
||||
remotes: options.remotes,
|
||||
host: options.host,
|
||||
skipFormat: true,
|
||||
});
|
||||
}
|
||||
|
||||
@ -24,4 +24,5 @@ export interface Schema {
|
||||
mfeType?: 'host' | 'remote';
|
||||
remotes?: string[];
|
||||
port?: number;
|
||||
host?: string;
|
||||
}
|
||||
|
||||
@ -141,6 +141,10 @@
|
||||
"remotes": {
|
||||
"type": "array",
|
||||
"description": "A list of remote application names that the host application should consume."
|
||||
},
|
||||
"host": {
|
||||
"type": "string",
|
||||
"description": "The name of the host application that the remote application will be consumed by."
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
|
||||
@ -1,5 +1,94 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Init MFE should add a remote application and add it to a specified host applications webpack config that contains a remote application already 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: {
|
||||
remote1: 'remote1@http://localhost:4201/remoteEntry.js',
|
||||
remote2: 'remote2@http://localhost:4202/remoteEntry.js',
|
||||
|
||||
},
|
||||
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 add a remote application and add it to a specified host applications webpack config when no other remote has been added to it 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: {
|
||||
remote1: 'remote1@http://localhost:4200/remoteEntry.js',
|
||||
|
||||
},
|
||||
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 1`] = `
|
||||
"const ModuleFederationPlugin = require(\\"webpack/lib/container/ModuleFederationPlugin\\");
|
||||
const mf = require(\\"@angular-architects/module-federation/webpack\\");
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
import type { Tree } from '@nrwl/devkit';
|
||||
import type { Schema } from '../schema';
|
||||
|
||||
import { readProjectConfiguration } from '@nrwl/devkit';
|
||||
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||
import { ObjectLiteralExpression } from 'typescript';
|
||||
|
||||
export function addRemoteToHost(host: Tree, options: Schema) {
|
||||
if (options.mfeType === 'remote' && options.host) {
|
||||
const project = readProjectConfiguration(host, options.host);
|
||||
const hostWebpackPath =
|
||||
project.targets['build'].options.customWebpackConfig?.path;
|
||||
|
||||
if (!hostWebpackPath || !host.exists(hostWebpackPath)) {
|
||||
throw new Error(
|
||||
`The selected host application, ${options.host}, does not contain a webpack.config.js. Are you sure it has been set up as a host application?`
|
||||
);
|
||||
}
|
||||
|
||||
const hostWebpackConfig = host.read(hostWebpackPath, 'utf-8');
|
||||
const webpackAst = tsquery.ast(hostWebpackConfig);
|
||||
const mfRemotesNode = tsquery(
|
||||
webpackAst,
|
||||
'Identifier[name=remotes] ~ ObjectLiteralExpression',
|
||||
{ visitAllChildren: true }
|
||||
)[0] as ObjectLiteralExpression;
|
||||
|
||||
const endOfPropertiesPos = mfRemotesNode.properties.end;
|
||||
|
||||
const updatedConfig = `${hostWebpackConfig.slice(0, endOfPropertiesPos)}
|
||||
\t\t${options.appName}: '${options.appName}@http://localhost:${
|
||||
options.port ?? 4200
|
||||
}/remoteEntry.js',${hostWebpackConfig.slice(endOfPropertiesPos)}`;
|
||||
|
||||
host.write(hostWebpackPath, updatedConfig);
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
export * from './add-implicit-deps';
|
||||
export * from './add-remote-to-host';
|
||||
export * from './change-build-target';
|
||||
export * from './fix-bootstrap';
|
||||
export * from './generate-config';
|
||||
|
||||
@ -3,5 +3,6 @@ export interface Schema {
|
||||
mfeType: 'host' | 'remote';
|
||||
port?: number;
|
||||
remotes?: string[];
|
||||
host?: string;
|
||||
skipFormat?: boolean;
|
||||
}
|
||||
|
||||
@ -29,6 +29,10 @@
|
||||
"type": "array",
|
||||
"description": "A list of remote application names that the host application should consume."
|
||||
},
|
||||
"host": {
|
||||
"type": "string",
|
||||
"description": "The name of the host application that the remote application will be consumed by."
|
||||
},
|
||||
"skipFormat": {
|
||||
"type": "boolean",
|
||||
"description": "Skip formatting the workspace after the generator completes."
|
||||
|
||||
@ -164,4 +164,54 @@ describe('Init MFE', () => {
|
||||
|
||||
expect(nxJson.projects['app1'].implicitDependencies).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 setupMfe(host, {
|
||||
appName: 'app1',
|
||||
mfeType: 'host',
|
||||
});
|
||||
|
||||
// ACT
|
||||
await setupMfe(host, {
|
||||
appName: 'remote1',
|
||||
mfeType: 'remote',
|
||||
host: 'app1',
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
const hostWebpackConfig = host.read('apps/app1/webpack.config.js', 'utf-8');
|
||||
expect(hostWebpackConfig).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 applicationGenerator(host, {
|
||||
name: 'remote2',
|
||||
});
|
||||
|
||||
await setupMfe(host, {
|
||||
appName: 'app1',
|
||||
mfeType: 'host',
|
||||
});
|
||||
|
||||
await setupMfe(host, {
|
||||
appName: 'remote1',
|
||||
mfeType: 'remote',
|
||||
host: 'app1',
|
||||
port: 4201,
|
||||
});
|
||||
|
||||
// ACT
|
||||
await setupMfe(host, {
|
||||
appName: 'remote2',
|
||||
mfeType: 'remote',
|
||||
host: 'app1',
|
||||
port: 4202,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
const hostWebpackConfig = host.read('apps/app1/webpack.config.js', 'utf-8');
|
||||
expect(hostWebpackConfig).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
|
||||
import {
|
||||
addImplicitDeps,
|
||||
addRemoteToHost,
|
||||
changeBuildTarget,
|
||||
fixBootstrap,
|
||||
generateWebpackConfig,
|
||||
@ -20,6 +21,7 @@ export async function setupMfe(host: Tree, options: Schema) {
|
||||
const projectConfig = readProjectConfiguration(host, options.appName);
|
||||
|
||||
const remotesWithPorts = getRemotesWithPorts(host, options);
|
||||
addRemoteToHost(host, options);
|
||||
|
||||
generateWebpackConfig(host, options, projectConfig.root, remotesWithPorts);
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import { satisfies } from 'semver';
|
||||
// Ignore packages that are defined here per package
|
||||
const IGNORE_MATCHES = {
|
||||
'*': [],
|
||||
angular: ['webpack-merge'],
|
||||
angular: ['webpack-merge', '@phenomnomnominal/tsquery'],
|
||||
};
|
||||
|
||||
export default function getDiscrepancies(
|
||||
|
||||
@ -3765,6 +3765,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@opentelemetry/context-base/-/context-base-0.14.0.tgz#c67fc20a4d891447ca1a855d7d70fa79a3533001"
|
||||
integrity sha512-sDOAZcYwynHFTbLo6n8kIbLiVF3a3BLkrmehJUyEbT9F+Smbi47kLGS2gG2g0fjBLR/Lr1InPD7kXL7FaTqEkw==
|
||||
|
||||
"@phenomnomnominal/tsquery@4.1.1":
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@phenomnomnominal/tsquery/-/tsquery-4.1.1.tgz#42971b83590e9d853d024ddb04a18085a36518df"
|
||||
integrity sha512-jjMmK1tnZbm1Jq5a7fBliM4gQwjxMU7TFoRNwIyzwlO+eHPRCFv/Nv+H/Gi1jc3WR7QURG8D5d0Tn12YGrUqBQ==
|
||||
dependencies:
|
||||
esquery "^1.0.1"
|
||||
|
||||
"@pmmmwh/react-refresh-webpack-plugin@^0.4.3":
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.4.3.tgz#1eec460596d200c0236bf195b078a5d1df89b766"
|
||||
@ -11700,7 +11707,7 @@ esprima@^4.0.0, esprima@^4.0.1:
|
||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
|
||||
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
|
||||
|
||||
esquery@^1.2.0:
|
||||
esquery@^1.0.1, esquery@^1.2.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5"
|
||||
integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user