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(react): update MFE config and routes when adding remote to host #9687

Merged
merged 1 commit into from Apr 5, 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
6 changes: 5 additions & 1 deletion e2e/react/src/react.mfe.test.ts
Expand Up @@ -22,12 +22,16 @@ describe('React MFE', () => {
const remote3 = uniq('remote3');

runCLI(
`generate @nrwl/react:mfe-host ${shell} --style=css --remotes=${remote1},${remote2},${remote3} --no-interactive`
`generate @nrwl/react:mfe-host ${shell} --style=css --remotes=${remote1},${remote2} --no-interactive`
);
runCLI(
`generate @nrwl/react:mfe-remote ${remote3} --style=css --host=${shell} --no-interactive`
);

checkFilesExist(`apps/${shell}/mfe.config.js`);
checkFilesExist(`apps/${remote1}/mfe.config.js`);
checkFilesExist(`apps/${remote2}/mfe.config.js`);
checkFilesExist(`apps/${remote3}/mfe.config.js`);

await expect(runCLIAsync(`test ${shell}`)).resolves.toMatchObject({
combinedOutput: expect.stringContaining('Test Suites: 1 passed, 1 total'),
Expand Down
@@ -0,0 +1,41 @@
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import { addProjectConfiguration, Tree } from '@nrwl/devkit';

import { findFreePort } from './find-free-port';

describe('findFreePort', () => {
it('should return the largest port + 1', () => {
const tree = createTreeWithEmptyWorkspace();
addProject(tree, 'app1', 4200);
addProject(tree, 'app2', 4201);
addProject(tree, 'no-serve');

const port = findFreePort(tree);

expect(port).toEqual(4202);
});

it('should default to port 4200', () => {
const tree = createTreeWithEmptyWorkspace();
addProject(tree, 'no-serve');

const port = findFreePort(tree);

expect(port).toEqual(4200);
});
});

function addProject(tree: Tree, name: string, port?: number) {
addProjectConfiguration(tree, name, {
name: 'app1',
root: '/app1',
targets: port
? {
serve: {
executor: '',
options: { port },
},
}
: {},
});
}
14 changes: 14 additions & 0 deletions packages/react/src/generators/application/lib/find-free-port.ts
@@ -0,0 +1,14 @@
import { Tree } from 'nx/src/config/tree';
import { getProjects } from '@nrwl/devkit';

export function findFreePort(host: Tree) {
const projects = getProjects(host);
let port = -Infinity;
for (const [, p] of projects.entries()) {
const curr = p.targets?.serve?.options?.port;
if (typeof curr === 'number') {
port = Math.max(port, curr);
}
}
return port > 0 ? port + 1 : 4200;
}
@@ -1,6 +1,7 @@
import { NormalizedSchema, Schema } from '../schema';
import { assertValidStyle } from '../../../utils/assertion';
import { names, Tree, normalizePath, getWorkspaceLayout } from '@nrwl/devkit';
import { getWorkspaceLayout, names, normalizePath, Tree } from '@nrwl/devkit';
import { findFreePort } from './find-free-port';

export function normalizeOptions(
host: Tree,
Expand Down Expand Up @@ -34,6 +35,7 @@ export function normalizeOptions(
options.unitTestRunner = options.unitTestRunner ?? 'jest';
options.e2eTestRunner = options.e2eTestRunner ?? 'cypress';
options.compiler = options.compiler ?? 'babel';
options.devServerPort ??= findFreePort(host);

return {
...options,
Expand All @@ -45,6 +47,5 @@ export function normalizeOptions(
fileName,
styledModule,
hasStyles: options.style !== 'none',
devServerPort: options.devServerPort ?? 4200,
};
}
@@ -1 +1,12 @@
module.exports = require('./webpack.config');
const withModuleFederation = require('@nrwl/react/module-federation');
const mfeConfig = require('./mfe.config');

module.exports = withModuleFederation({
...mfeConfig,
// Override remote location for production build.
// Each entry is a pair of an unique name and the URL where it is deployed.
// remotes: [
// ['app1', 'http://app1.example.com/'],
// ['app2', 'http://app2.example.com/'],
// ],
});
@@ -1,8 +1,97 @@
import { Tree } from '@nrwl/devkit';
import { Schema } from '../schema';
import {
applyChangesToString,
joinPathFragments,
logger,
names,
readProjectConfiguration,
Tree,
} from '@nrwl/devkit';
import {
addRemoteDefinition,
addRemoteRoute,
addRemoteToMfeConfig,
} from '../../../mfe/mfe-ast-utils';
import * as ts from 'typescript';

export function updateHostWithRemote(host: Tree, options: Schema) {
// find the host project path
// Update remotes inside ${host_path}/src/remotes.d.ts
// Update remotes inside ${host_path}/mfe.config.js
export function updateHostWithRemote(
host: Tree,
hostName: string,
remoteName: string
) {
const hostConfig = readProjectConfiguration(host, hostName);
const mfeConfigPath = joinPathFragments(hostConfig.root, 'mfe.config.js');
const remoteDefsPath = joinPathFragments(
hostConfig.sourceRoot,
'remotes.d.ts'
);
const appComponentPath = findAppComponentPath(host, hostConfig.sourceRoot);

if (host.exists(mfeConfigPath)) {
// find the host project path
// Update remotes inside ${host_path}/src/remotes.d.ts
let sourceCode = host.read(mfeConfigPath).toString();
const source = ts.createSourceFile(
mfeConfigPath,
sourceCode,
ts.ScriptTarget.Latest,
true
);
host.write(
mfeConfigPath,
applyChangesToString(sourceCode, addRemoteToMfeConfig(source, remoteName))
);
} else {
// TODO(jack): Point to the nx.dev guide when ready.
logger.warn(
`Could not find MFE configuration at ${mfeConfigPath}. Did you generate this project with "@nrwl/react:mfe-host"?`
);
}

if (host.exists(remoteDefsPath)) {
let sourceCode = host.read(remoteDefsPath).toString();
const source = ts.createSourceFile(
mfeConfigPath,
sourceCode,
ts.ScriptTarget.Latest,
true
);
host.write(
remoteDefsPath,
applyChangesToString(sourceCode, addRemoteDefinition(source, remoteName))
);
} else {
logger.warn(
`Could not find remote definitions at ${remoteDefsPath}. Did you generate this project with "@nrwl/react:mfe-host"?`
);
}

if (host.exists(appComponentPath)) {
let sourceCode = host.read(appComponentPath).toString();
const source = ts.createSourceFile(
mfeConfigPath,
sourceCode,
ts.ScriptTarget.Latest,
true
);
host.write(
appComponentPath,
applyChangesToString(
sourceCode,
addRemoteRoute(source, names(remoteName))
)
);
} else {
logger.warn(
`Could not find app component at ${appComponentPath}. Did you generate this project with "@nrwl/react:mfe-host"?`
);
}
}

function findAppComponentPath(host: Tree, sourceRoot: string) {
const locations = ['app/app.tsx', 'app/App.tsx', 'app.tsx', 'App.tsx'];
for (const loc of locations) {
if (host.exists(joinPathFragments(sourceRoot, loc))) {
return joinPathFragments(sourceRoot, loc);
}
}
}
2 changes: 2 additions & 0 deletions packages/react/src/generators/mfe-host/mfe-host.ts
Expand Up @@ -42,3 +42,5 @@ export async function mfeHostGenerator(host: Tree, schema: Schema) {

return initTask;
}

export default mfeHostGenerator;
12 changes: 6 additions & 6 deletions packages/react/src/generators/mfe-remote/mfe-remote.ts
Expand Up @@ -28,6 +28,10 @@ export async function mfeRemoteGenerator(host: Tree, schema: Schema) {
const options = normalizeOptions(host, schema);
const initApp = await applicationGenerator(host, options);

if (schema.host) {
updateHostWithRemote(host, schema.host, options.name);
}

// Module federation requires bootstrap code to be dynamically imported.
// Renaming original entry file so we can use `import(./bootstrap)` in
// new entry file.
Expand All @@ -38,16 +42,12 @@ export async function mfeRemoteGenerator(host: Tree, schema: Schema) {

addMfeFiles(host, options);
updateMfeProject(host, options);
if (schema.host) {
updateHostWithRemote(host, options);
} else {
// Log that no host has been passed in so we will use the default project as the host (Only if through CLI)
// Since Remotes can be generated from the Host Generator we should probably have some identifier to use
}

if (!options.skipFormat) {
await formatFiles(host);
}

return runTasksInSerial(initApp);
}

export default mfeRemoteGenerator;
4 changes: 0 additions & 4 deletions packages/react/src/generators/mfe-remote/schema.d.ts
Expand Up @@ -9,10 +9,6 @@ export interface Schema {
directory?: string;
tags?: string;
unitTestRunner: 'jest' | 'none';
/**
* @deprecated
*/
babelJest?: boolean;
e2eTestRunner: 'cypress' | 'none';
linter: Linter;
pascalCaseFiles?: boolean;
Expand Down