Skip to content

Commit

Permalink
feat(react): Add federate-module generator
Browse files Browse the repository at this point in the history
  • Loading branch information
ndcunningham committed Sep 21, 2023
1 parent d1310e3 commit f9c3f63
Show file tree
Hide file tree
Showing 12 changed files with 445 additions and 2 deletions.
8 changes: 8 additions & 0 deletions docs/generated/manifests/menus.json
Original file line number Diff line number Diff line change
Expand Up @@ -8034,6 +8034,14 @@
"children": [],
"isExternal": false,
"disableCollapsible": false
},
{
"id": "federate-module",
"path": "/packages/react/generators/federate-module",
"name": "federate-module",
"children": [],
"isExternal": false,
"disableCollapsible": false
}
],
"isExternal": false,
Expand Down
9 changes: 9 additions & 0 deletions docs/generated/manifests/packages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2182,6 +2182,15 @@
"originalFilePath": "/packages/react/src/generators/setup-ssr/schema.json",
"path": "/packages/react/generators/setup-ssr",
"type": "generator"
},
"/packages/react/generators/federate-module": {
"description": "Federate a module.",
"file": "generated/packages/react/generators/federate-module.json",
"hidden": false,
"name": "federate-module",
"originalFilePath": "/packages/react/src/generators/federate-module/schema.json",
"path": "/packages/react/generators/federate-module",
"type": "generator"
}
},
"path": "/packages/react"
Expand Down
9 changes: 9 additions & 0 deletions docs/generated/packages-metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -2158,6 +2158,15 @@
"originalFilePath": "/packages/react/src/generators/setup-ssr/schema.json",
"path": "react/generators/setup-ssr",
"type": "generator"
},
{
"description": "Federate a module.",
"file": "generated/packages/react/generators/federate-module.json",
"hidden": false,
"name": "federate-module",
"originalFilePath": "/packages/react/src/generators/federate-module/schema.json",
"path": "react/generators/federate-module",
"type": "generator"
}
],
"githubRoot": "https://github.com/nrwl/nx/blob/master",
Expand Down
118 changes: 118 additions & 0 deletions docs/generated/packages/react/generators/federate-module.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
{
"name": "federate-module",
"factory": "./src/generators/federate-module/federate-module#federateModuleGenerator",
"schema": {
"$schema": "http://json-schema.org/draft-04/schema",
"cli": "nx",
"$id": "NxReactFederateModule",
"title": "Federate Module",
"description": "Creat a federated module, which can be loaded by a remote host",
"examples": [
{
"command": "nx g federate-module --path=libs/ui/src/component/my-cmp.ts --remote=my-remote-app",
"description": "Create a federated module called my-remote-app, that exposes my-cmp from libs/ui/src/component/my-cmp.ts"
}
],
"type": "object",
"properties": {
"name": {
"description": "The name of the module.",
"type": "string",
"$default": { "$source": "argv", "index": 0 },
"x-prompt": "What name would you like to use for the module?",
"pattern": "^[a-zA-Z][^:]*$"
},
"path": {
"type": "string",
"description": "The path to the module to be federated",
"x-prompt": "What is the path to the module to be federated?"
},
"remote": {
"type": "string",
"description": "The name of the remote",
"x-prompt": "What is/should the remote be named?"
},
"projectNameAndRootFormat": {
"description": "Whether to generate the project name and root directory as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
"type": "string",
"enum": ["as-provided", "derived"]
},
"style": {
"description": "The file extension to be used for style files.",
"type": "string",
"default": "css",
"alias": "s",
"x-prompt": {
"message": "Which stylesheet format would you like to use?",
"type": "list",
"items": [
{ "value": "css", "label": "CSS" },
{
"value": "scss",
"label": "SASS(.scss) [ http://sass-lang.com ]"
},
{
"value": "less",
"label": "LESS [ http://lesscss.org ]"
},
{
"value": "styled-components",
"label": "styled-components [ https://styled-components.com ]"
},
{
"value": "@emotion/styled",
"label": "emotion [ https://emotion.sh ]"
},
{
"value": "styled-jsx",
"label": "styled-jsx [ https://www.npmjs.com/package/styled-jsx ]"
},
{
"value": "styl",
"label": "DEPRECATD: Stylus(.styl) [ http://stylus-lang.com ]"
},
{ "value": "none", "label": "None" }
]
}
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",
"enum": ["eslint"],
"default": "eslint"
},
"skipFormat": {
"description": "Skip formatting files.",
"type": "boolean",
"default": false,
"x-priority": "internal"
},
"unitTestRunner": {
"type": "string",
"enum": ["jest", "none"],
"description": "Test runner to use for unit tests.",
"default": "jest"
},
"e2eTestRunner": {
"type": "string",
"enum": ["cypress", "none"],
"description": "Test runner to use for end to end (e2e) tests.",
"default": "cypress"
},
"host": {
"type": "string",
"description": "The host / shell application for this remote.",
"x-priority": "important"
}
},
"required": ["name"],
"additionalProperties": false,
"presets": []
},
"description": "Federate a module.",
"hidden": false,
"implementation": "/packages/react/src/generators/federate-module/federate-module#federateModuleGenerator.ts",
"aliases": [],
"path": "/packages/react/src/generators/federate-module/schema.json",
"type": "generator"
}
1 change: 1 addition & 0 deletions docs/shared/reference/sitemap.md
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,7 @@
- [component-test](/packages/react/generators/component-test)
- [setup-tailwind](/packages/react/generators/setup-tailwind)
- [setup-ssr](/packages/react/generators/setup-ssr)
- [federate-module](/packages/react/generators/federate-module)
- [react-native](/packages/react-native)
- [documents](/packages/react-native/documents)
- [Overview](/packages/react-native/documents/overview)
Expand Down
14 changes: 14 additions & 0 deletions packages/react/generators.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,13 @@
"schema": "./src/generators/setup-ssr/schema.json",
"description": "Set up SSR configuration for a project.",
"hidden": false
},

"federate-module": {
"factory": "./src/generators/federate-module/federate-module#federateModuleSchematic",
"schema": "./src/generators/federate-module/schema.json",
"description": "Federate a module.",
"hidden": false
}
},
"generators": {
Expand Down Expand Up @@ -218,6 +225,13 @@
"schema": "./src/generators/setup-ssr/schema.json",
"description": "Set up SSR configuration for a project.",
"hidden": false
},

"federate-module": {
"factory": "./src/generators/federate-module/federate-module#federateModuleGenerator",
"schema": "./src/generators/federate-module/schema.json",
"description": "Federate a module.",
"hidden": false
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// TODO
describe('federate-module', () => {
it('should work', () => {
expect(true).toEqual(true);
});
});
27 changes: 27 additions & 0 deletions packages/react/src/generators/federate-module/federate-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Tree } from '@nx/devkit';
import { Schema } from './schema';

import { remoteGeneratorInternal } from '../remote/remote';
import { addPathToExposes, checkRemoteExists, getRemote } from './lib/utils';

export async function federateModuleGenerator(tree: Tree, schema: Schema) {
// Check remote exists
let remote = await checkRemoteExists(tree, schema.remote);
if (!remote) {
// create remote
await remoteGeneratorInternal(tree, {
name: schema.remote,
e2eTestRunner: schema.e2eTestRunner,
skipFormat: schema.skipFormat,
linter: schema.linter,
style: schema.style,
unitTestRunner: schema.unitTestRunner,
host: schema.host,
projectNameAndRootFormat: schema.projectNameAndRootFormat ?? 'derived',
});
}

remote = await getRemote(schema.remote);
// add path to exposes property
addPathToExposes(tree, remote.root, schema.name, schema.path);
}
124 changes: 124 additions & 0 deletions packages/react/src/generators/federate-module/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import {
ChangeType,
ProjectGraph,
StringChange,
Tree,
applyChangesToString,
createProjectGraphAsync,
joinPathFragments,
readCachedProjectGraph,
} from '@nx/devkit';
import { join, isAbsolute } from 'path';
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
import * as ts from 'typescript';
import { findNodes } from '@nx/js';

let tsModule: typeof import('typescript');

if (!tsModule) {
tsModule = ensureTypescript();
}

/**
* Adds a Module Federation path to the exposes property of the module federation config
* The assumption here is made the we will only update a TypeScript Module Federation file namely 'module-federation.config.ts'
* @param tree Tree for the workspace
* @param projectPath Project path relative to the workspace
* @param moduleName The name of the module to expose
* @param modulePath The path to the module to expose
*/
export function addPathToExposes(tree, projectPath, moduleName, modulePath) {
if (isAbsolute(modulePath)) {
throw new Error(
`Module path: ${modulePath} must be relative to the project ${projectPath}`
);
}
const moduleFederationConfigPath = joinPathFragments(
projectPath,
'module-federation.config.ts'
);
const configCode = tree.read(moduleFederationConfigPath).toString();
const source = tsModule.createSourceFile(
moduleFederationConfigPath,
configCode,
tsModule.ScriptTarget.Latest,
true
);

tree.write(
moduleFederationConfigPath,
applyChangesToString(
configCode,
addExposesToConfig(source, moduleName, modulePath)
)
);
}

/**
*
* @param tree The workspace tree
* @param remoteName The name of the remote to check
* @returns Remote ProjectConfig if it exists, false otherwise
*/
export async function checkRemoteExists(tree: Tree, remoteName: string) {
const remote = await getRemote(remoteName);
const hasModuleFederationConfig = tree.exists(
join(remote.root, 'module-federation.config.ts')
);
return remote && hasModuleFederationConfig ? remote : false;
}

export async function getRemote(remoteName: string) {
let projectGraph: ProjectGraph;
try {
projectGraph = readCachedProjectGraph();
} catch (e) {
projectGraph = await createProjectGraphAsync();
}

const remote = projectGraph.nodes[remoteName]?.data;
return remote;
}

export function addExposesToConfig(
source: ts.SourceFile,
moduleName: string,
modulePath: string
) {
if (tsModule) {
tsModule = ensureTypescript();
}

const assignments = findNodes(
source,
tsModule.SyntaxKind.PropertyAssignment
) as ts.PropertyAssignment[];

const exposesAssignment = assignments.find(
(s) => s.name.getText() === 'exposes'
);

if (exposesAssignment) {
const arrayExpression =
exposesAssignment.initializer as ts.ArrayLiteralExpression;

if (!arrayExpression.elements) return [];

const lastElement =
arrayExpression.elements[arrayExpression.elements.length - 1];
return [
lastElement
? {
type: ChangeType.Insert,
index: lastElement.end,
text: `,`,
}
: null,
{
type: ChangeType.Insert,
index: exposesAssignment.end - 1,
text: `'${moduleName}': '${modulePath}',\n`,
},
].filter(Boolean) as StringChange[];
}
}
12 changes: 12 additions & 0 deletions packages/react/src/generators/federate-module/schema.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export interface Schema {
name: string;
path: string;
remote: string;
projectNameAndRootFormat?: ProjectNameAndRootFormat;
e2eTestRunner?: 'cypress' | 'none';
host?: string;
linter?: Linter;
skipFormat?: boolean;
style?: SupportedStyles;
unitTestRunner?: 'jest' | 'vitest' | 'none';
}
Loading

0 comments on commit f9c3f63

Please sign in to comment.