Skip to content

Commit

Permalink
feat(angular): add generator to migrate old mfe config
Browse files Browse the repository at this point in the history
  • Loading branch information
Coly010 committed Mar 16, 2022
1 parent fd98044 commit 5d8fa9b
Show file tree
Hide file tree
Showing 23 changed files with 1,535 additions and 0 deletions.
36 changes: 36 additions & 0 deletions docs/generated/api-angular/generators/move-to-with-mf.md
@@ -0,0 +1,36 @@
---
title: '@nrwl/angular:move-to-with-mf generator'
description: 'Moves an old micro frontend configuration to use withModuleFederation helper.'
---

# @nrwl/angular:move-to-with-mf

Moves an old micro frontend configuration to use withModuleFederation helper.

## Usage

```bash
nx generate move-to-with-mf ...
```

By default, Nx will search for `move-to-with-mf` in the default collection provisioned in `workspace.json`.

You can specify the collection explicitly as follows:

```bash
nx g @nrwl/angular:move-to-with-mf ...
```

Show what will be generated without writing to disk:

```bash
nx g move-to-with-mf ... --dry-run
```

## Options

### project

Type: `string`

The name of the micro frontend project to migrate.
5 changes: 5 additions & 0 deletions docs/map.json
Expand Up @@ -689,6 +689,11 @@
"id": "move",
"file": "generated/api-angular/generators/move"
},
{
"name": "move-to-with-mf generator",
"id": "move-to-with-mf",
"file": "generated/api-angular/generators/move-to-with-mf"
},
{
"name": "ngrx generator",
"id": "ngrx",
Expand Down
10 changes: 10 additions & 0 deletions packages/angular/generators.json
Expand Up @@ -80,6 +80,11 @@
"aliases": ["mv"],
"description": "Moves an Angular application or library to another folder within the workspace and updates the project configuration."
},
"move-to-with-mf": {
"factory": "./src/generators/move-to-with-mf/move-to-with-mf.compat",
"schema": "./src/generators/move-to-with-mf/schema.json",
"description": "Moves an old micro frontend configuration to use withModuleFederation helper."
},
"mfe-host": {
"factory": "./src/generators/mfe-host/mfe-host.compat",
"schema": "./src/generators/mfe-host/schema.json",
Expand Down Expand Up @@ -225,6 +230,11 @@
"aliases": ["mv"],
"description": "Moves an Angular application or library to another folder within the workspace and updates the project configuration."
},
"move-to-with-mf": {
"factory": "./src/generators/move-to-with-mf/move-to-with-mf",
"schema": "./src/generators/move-to-with-mf/schema.json",
"description": "Moves an old micro frontend configuration to use withModuleFederation helper."
},
"mfe-host": {
"factory": "./src/generators/mfe-host/mfe-host",
"schema": "./src/generators/mfe-host/schema.json",
Expand Down
@@ -0,0 +1,29 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`moveToWithMF should migrate a standard previous generated host config correctly 1`] = `
"const { withModuleFederation } = require('@nrwl/angular/module-federation');
module.exports = withModuleFederation({
name: 'host1',
remotes: [['remote1', 'http://localhost:4201']],
});"
`;

exports[`moveToWithMF should migrate a standard previous generated remote config correctly 1`] = `
"const { withModuleFederation } = require('@nrwl/angular/module-federation');
module.exports = withModuleFederation({
name: 'remote1',
exposes: {
'./Module': 'apps/remote1/src/app/remote-entry/entry.module.ts',
},
});"
`;

exports[`moveToWithMF should migrate a standard previous generated remote config using object shared syntax correctly 1`] = `
"const { withModuleFederation } = require('@nrwl/angular/module-federation');
module.exports = withModuleFederation({
name: 'remote1',
exposes: {
'./Module': 'apps/remote1/src/app/remote-entry/entry.module.ts',
},
});"
`;
@@ -0,0 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`isHostRemoteConfig should return remote when correct remote config found 1`] = `
"{
'./Module': 'apps/remote1/src/app/remote-entry/entry.module.ts'
}"
`;
@@ -0,0 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`writeNewWebpackConfig should convert config that is both remote and host correctly 1`] = `
"const { withModuleFederation } = require('@nrwl/angular/module-federation');
module.exports = withModuleFederation({
name: 'both1',
remotes: [['remote1', 'http://localhost:4201']],
exposes: {
'./Module': 'apps/both/src/app/remote-entry/entry.module.ts'
},
});"
`;

exports[`writeNewWebpackConfig should convert config that is neither remote and host correctly 1`] = `
"const { withModuleFederation } = require('@nrwl/angular/module-federation');
module.exports = withModuleFederation({
name: 'neither',
});"
`;

exports[`writeNewWebpackConfig should convert host config correctly 1`] = `
"const { withModuleFederation } = require('@nrwl/angular/module-federation');
module.exports = withModuleFederation({
name: 'host1',
remotes: [['remote1', 'http://localhost:4201']],
});"
`;

exports[`writeNewWebpackConfig should convert remote config correctly 1`] = `
"const { withModuleFederation } = require('@nrwl/angular/module-federation');
module.exports = withModuleFederation({
name: 'remote1',
exposes: {
'./Module': 'apps/remote1/src/app/remote-entry/entry.module.ts'
},
});"
`;
@@ -0,0 +1,37 @@
import { tsquery } from '@phenomnomnominal/tsquery';
import { checkOutputNameMatchesProjectName } from './check-name-matches';
describe('checkOutputNameMatchesProjectName', () => {
it('should return true if the uniqueName matches the project name', () => {
// ARRANGE
const sourceText = `module.exports = {
output: {
uniqueName: 'proj'
}
}`;

const ast = tsquery.ast(sourceText);

// ACT
const result = checkOutputNameMatchesProjectName(ast, 'proj');

// ASSERT
expect(result).toBeTruthy();
});

it('should return false if the uniqueName does not match the project name', () => {
// ARRANGE
const sourceText = `module.exports = {
output: {
uniqueName: 'app1'
}
}`;

const ast = tsquery.ast(sourceText);

// ACT
const result = checkOutputNameMatchesProjectName(ast, 'proj');

// ASSERT
expect(result).toBeFalsy();
});
});
@@ -0,0 +1,29 @@
import type { SourceFile } from 'typescript';
import { tsquery } from '@phenomnomnominal/tsquery';

export function checkOutputNameMatchesProjectName(
ast: SourceFile,
projectName: string
) {
const OUTPUT_SELECTOR =
'PropertyAssignment:has(Identifier[name=output]) > ObjectLiteralExpression:has(PropertyAssignment:has(Identifier[name=uniqueName]))';
const UNIQUENAME_SELECTOR =
'ObjectLiteralExpression > PropertyAssignment:has(Identifier[name=uniqueName]) > StringLiteral';

const outputNodes = tsquery(ast, OUTPUT_SELECTOR, { visitAllChildren: true });
if (outputNodes.length === 0) {
// If the output isnt set in the config, then we can still set the project name correctly
return true;
}

const uniqueNameNodes = tsquery(outputNodes[0], UNIQUENAME_SELECTOR, {
visitAllChildren: true,
});
if (uniqueNameNodes.length === 0) {
// If the uniqeName isnt set in the config, then we can still set the project name correctly
return true;
}
const uniqueName = uniqueNameNodes[0].getText().replace(/'/g, '');

return uniqueName === projectName;
}
@@ -0,0 +1,182 @@
import { tsquery } from '@phenomnomnominal/tsquery';
import { checkSharedNpmPackagesMatchExpected } from './check-shared-npm-packages';

describe('checkSharedNpmPackagesMatchExpected', () => {
it('should return true if all shared packages match the config object we expect', () => {
// ARRANGE
const sourceText = `module.exports = {
plugins: [
new ModuleFederationPlugin({
shared: share({
'@angular/core': {
singleton: true,
strictVersion: true,
requiredVersion: 'auto',
includeSecondaries: true
},
'@angular/common': {
singleton: true,
strictVersion: true,
requiredVersion: 'auto',
includeSecondaries: true
},
'rxjs': {
singleton: true,
strictVersion: true,
requiredVersion: 'auto',
includeSecondaries: true
},
})
})
]
}`;

const ast = tsquery.ast(sourceText);

// ACT
const result = checkSharedNpmPackagesMatchExpected(ast);

// ASSERT
expect(result).toBeTruthy();
});

it('should return true if all shared packages match the config object we expect using object syntax', () => {
// ARRANGE
const sourceText = `module.exports = {
plugins: [
new ModuleFederationPlugin({
shared: {
'@angular/core': {
singleton: true,
strictVersion: true,
},
'@angular/common': {
singleton: true,
strictVersion: true,
},
'rxjs': {
singleton: true,
strictVersion: true,
},
}
})
]
}`;

const ast = tsquery.ast(sourceText);

// ACT
const result = checkSharedNpmPackagesMatchExpected(ast);

// ASSERT
expect(result).toBeTruthy();
});

it('should return false if any shared packages do not match the config object we expect using object syntax', () => {
// ARRANGE
const sourceText = `module.exports = {
plugins: [
new ModuleFederationPlugin({
shared: {
'@angular/core': {
singleton: true,
strictVersion: true,
},
'@angular/common': {
singleton: true,
strictVersion: false,
},
'rxjs': {
singleton: true,
strictVersion: true,
},
}
})
]
}`;

const ast = tsquery.ast(sourceText);

// ACT
const result = checkSharedNpmPackagesMatchExpected(ast);

// ASSERT
expect(result).toBeFalsy();
});

it('should return true if we arent sharing packages with the share helper', () => {
// ARRANGE
const sourceText = `module.exports = {
plugins: [
new ModuleFederationPlugin({
shared: share({})
})
]
}`;

const ast = tsquery.ast(sourceText);

// ACT
const result = checkSharedNpmPackagesMatchExpected(ast);

// ASSERT
expect(result).toBeTruthy();
});

it('should return true if we arent sharing packages with the standard shared syntax', () => {
// ARRANGE
const sourceText = `module.exports = {
plugins: [
new ModuleFederationPlugin({
shared: {}
})
]
}`;

const ast = tsquery.ast(sourceText);

// ACT
const result = checkSharedNpmPackagesMatchExpected(ast);

// ASSERT
expect(result).toBeTruthy();
});

it('should return false if any shared packages does not match the config object we expect', () => {
// ARRANGE
const sourceText = `module.exports = {
plugins: [
new ModuleFederationPlugin({
shared: share({
'@angular/core': {
singleton: true,
strictVersion: true,
requiredVersion: 'auto',
includeSecondaries: true
},
'@angular/common': {
singleton: true,
strictVersion: false,
requiredVersion: 'auto',
includeSecondaries: true
},
'rxjs': {
singleton: true,
strictVersion: true,
requiredVersion: 'auto',
includeSecondaries: true
},
})
})
]
}`;

const ast = tsquery.ast(sourceText);

// ACT
const result = checkSharedNpmPackagesMatchExpected(ast);

// ASSERT
expect(result).toBeFalsy();
});
});

0 comments on commit 5d8fa9b

Please sign in to comment.