Skip to content

Commit

Permalink
feat(react): add SSR suppoprt to host generator for module federation (
Browse files Browse the repository at this point in the history
  • Loading branch information
jaysoo authored Nov 29, 2022
1 parent 19acffa commit 6c59cbb
Show file tree
Hide file tree
Showing 26 changed files with 499 additions and 62 deletions.
17 changes: 17 additions & 0 deletions docs/generated/packages/react.json
Original file line number Diff line number Diff line change
Expand Up @@ -1099,6 +1099,11 @@
"devServerPort": {
"type": "number",
"description": "The port for the dev server of the remote app."
},
"ssr": {
"description": "Whether to configure SSR for the host application",
"type": "boolean",
"default": false
}
},
"required": ["name"],
Expand Down Expand Up @@ -1260,6 +1265,11 @@
"devServerPort": {
"type": "number",
"description": "The port for the dev server of the remote app."
},
"ssr": {
"description": "Whether to configure SSR for the host application",
"type": "boolean",
"default": false
}
},
"required": ["name"],
Expand Down Expand Up @@ -1447,6 +1457,13 @@
"skipFormat": {
"type": "boolean",
"description": "Skip formatting the workspace after the generator completes."
},
"extraInclude": {
"type": "array",
"items": { "type": "string" },
"hidden": true,
"description": "Extra include entries in tsconfig.",
"default": []
}
},
"required": ["project"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ export function normalizeProjectName(options: Schema) {
return normalizeDirectory(options).replace(new RegExp('/', 'g'), '-');
}

export function normalizeOptions(
export function normalizeOptions<T extends Schema = Schema>(
host: Tree,
options: Schema
): NormalizedSchema {
): NormalizedSchema<T> {
const appDirectory = normalizeDirectory(options);
const appProjectName = normalizeProjectName(options);
const e2eProjectName = options.rootProject
Expand Down
4 changes: 2 additions & 2 deletions packages/react/src/generators/application/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { SupportedStyles } from '../../../typings/style';
export interface Schema {
name: string;
style: SupportedStyles;
skipFormat: boolean;
skipFormat?: boolean;
directory?: string;
tags?: string;
unitTestRunner?: 'jest' | 'vitest' | 'none';
Expand Down Expand Up @@ -34,7 +34,7 @@ export interface Schema {
minimal?: boolean;
}

export interface NormalizedSchema extends Schema {
export interface NormalizedSchema<T extends Schema = Schema> extends T {
projectName: string;
appProjectRoot: string;
e2eProjectName: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// @ts-check

/**
* @type {import('@nrwl/devkit').ModuleFederationConfig}
**/
const moduleFederationConfig = {
name: '<%= projectName %>',
remotes: [
<% remotes.forEach(function(r) {%> "<%= r.fileName %>", <% }); %>
],
};

module.exports = moduleFederationConfig;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const { withModuleFederationForSSR } = require('@nrwl/react/module-federation');
const baseConfig = require("./module-federation.server.config");

const defaultConfig = {
...baseConfig
};

module.exports = withModuleFederationForSSR(defaultConfig);
60 changes: 60 additions & 0 deletions packages/react/src/generators/host/host.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { Tree } from '@nrwl/devkit';
import { readJson } from '@nrwl/devkit';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import hostGenerator from './host';
import { Linter } from '@nrwl/linter';

describe('hostGenerator', () => {
let tree: Tree;

beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
});

it('should generate host files and configs', async () => {
await hostGenerator(tree, {
name: 'test',
style: 'css',
linter: Linter.None,
unitTestRunner: 'none',
e2eTestRunner: 'none',
});

expect(tree.exists('apps/test/tsconfig.json'));
expect(tree.exists('apps/test/webpack.config.prod.js'));
expect(tree.exists('apps/test/webpack.config.js'));
expect(tree.exists('apps/test/src/bootstrap.tsx'));
expect(tree.exists('apps/test/src/main.ts'));
expect(tree.exists('apps/test/src/remotes.d.ts'));
});

it('should generate host files and configs for SSR', async () => {
await hostGenerator(tree, {
name: 'test',
ssr: true,
style: 'css',
linter: Linter.None,
unitTestRunner: 'none',
e2eTestRunner: 'none',
});

expect(tree.exists('apps/test/tsconfig.json'));
expect(tree.exists('apps/test/webpack.config.prod.js'));
expect(tree.exists('apps/test/webpack.config.server.js'));
expect(tree.exists('apps/test/webpack.config.js'));
expect(tree.exists('apps/test/src/main.server.tsx'));
expect(tree.exists('apps/test/src/bootstrap.tsx'));
expect(tree.exists('apps/test/src/main.ts'));
expect(tree.exists('apps/test/src/remotes.d.ts'));

expect(readJson(tree, 'apps/test/tsconfig.server.json')).toEqual({
compilerOptions: {
outDir: '../../out-tsc/server',
target: 'es2019',
types: ['node'],
},
extends: './tsconfig.app.json',
include: ['src/main.server.tsx', 'server.ts'],
});
});
});
42 changes: 39 additions & 3 deletions packages/react/src/generators/host/host.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
import { formatFiles, Tree } from '@nrwl/devkit';
import {
formatFiles,
GeneratorCallback,
joinPathFragments,
readProjectConfiguration,
Tree,
updateProjectConfiguration,
} from '@nrwl/devkit';

import applicationGenerator from '../application/application';
import { normalizeOptions } from '../application/lib/normalize-options';
import { updateModuleFederationProject } from '../../rules/update-module-federation-project';
import { addModuleFederationFiles } from './lib/add-module-federation-files';
import { updateModuleFederationE2eProject } from './lib/update-module-federation-e2e-project';

import { Schema } from './schema';
import remoteGenerator from '../remote/remote';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import setupSsrGenerator from '../setup-ssr/setup-ssr';
import { setupSsrForHost } from './lib/setup-ssr-for-host';

export async function hostGenerator(host: Tree, schema: Schema) {
const options = normalizeOptions(host, schema);
const tasks: GeneratorCallback[] = [];
const options = normalizeOptions<Schema>(host, schema);

const initTask = await applicationGenerator(host, {
...options,
Expand All @@ -18,6 +30,7 @@ export async function hostGenerator(host: Tree, schema: Schema) {
// Only webpack works with module federation for now.
bundler: 'webpack',
});
tasks.push(initTask);

const remotesWithPorts: { name: string; port: number }[] = [];

Expand All @@ -34,6 +47,7 @@ export async function hostGenerator(host: Tree, schema: Schema) {
e2eTestRunner: options.e2eTestRunner,
linter: options.linter,
devServerPort: remotePort,
ssr: options.ssr,
});
remotePort++;
}
Expand All @@ -43,11 +57,33 @@ export async function hostGenerator(host: Tree, schema: Schema) {
updateModuleFederationProject(host, options);
updateModuleFederationE2eProject(host, options);

if (options.ssr) {
const setupSsrTask = await setupSsrGenerator(host, {
project: options.projectName,
});
tasks.push(setupSsrTask);

const setupSsrForHostTask = await setupSsrForHost(
host,
options,
options.projectName,
remotesWithPorts
);
tasks.push(setupSsrForHostTask);

const projectConfig = readProjectConfiguration(host, options.projectName);
projectConfig.targets.server.options.webpackConfig = joinPathFragments(
projectConfig.root,
'webpack.server.config.js'
);
updateProjectConfiguration(host, options.projectName, projectConfig);
}

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

return initTask;
return runTasksInSerial(...tasks);
}

export default hostGenerator;
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,17 @@ export function addModuleFederationFiles(
join(options.appProjectRoot, 'src/bootstrap.tsx')
);

// New entry file is created here.
generateFiles(
host,
join(__dirname, `../files/module-federation`),
join(__dirname, `../files/common`),
options.appProjectRoot,
templateVariables
);

// New entry file is created here.
generateFiles(
host,
join(__dirname, `../files/common`),
join(__dirname, `../files/module-federation`),
options.appProjectRoot,
templateVariables
);
Expand Down
52 changes: 52 additions & 0 deletions packages/react/src/generators/host/lib/setup-ssr-for-host.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type { GeneratorCallback, Tree } from '@nrwl/devkit';
import {
addDependenciesToPackageJson,
generateFiles,
joinPathFragments,
names,
readProjectConfiguration,
} from '@nrwl/devkit';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';

import type { Schema } from '../schema';
import { moduleFederationNodeVersion } from '../../../utils/versions';
import { normalizeProjectName } from '../../application/lib/normalize-options';

export async function setupSsrForHost(
tree: Tree,
options: Schema,
appName: string,
defaultRemoteManifest: { name: string; port: number }[]
) {
const tasks: GeneratorCallback[] = [];
const project = readProjectConfiguration(tree, appName);

generateFiles(
tree,
joinPathFragments(__dirname, '../files/module-federation-ssr'),
project.root,
{
...options,
remotes: defaultRemoteManifest.map(({ name, port }) => {
const remote = normalizeProjectName({ ...options, name });
return {
...names(remote),
port,
};
}),
appName,
tmpl: '',
}
);

const installTask = addDependenciesToPackageJson(
tree,
{
'@module-federation/node': moduleFederationNodeVersion,
},
{}
);
tasks.push(installTask);

return runTasksInSerial(...tasks);
}
29 changes: 15 additions & 14 deletions packages/react/src/generators/host/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,30 @@ import { Linter } from '@nrwl/linter';
import { SupportedStyles } from '../../../typings';

export interface Schema {
name: string;
style: SupportedStyles;
skipFormat: boolean;
classComponent?: boolean;
compiler?: 'babel' | 'swc';
devServerPort?: number;
directory?: string;
tags?: string;
unitTestRunner: 'jest' | 'vitest' | 'none';
e2eTestRunner: 'cypress' | 'none';
globalCss?: boolean;
js?: boolean;
linter: Linter;
name: string;
pascalCaseFiles?: boolean;
classComponent?: boolean;
skipWorkspaceJson?: boolean;
js?: boolean;
globalCss?: boolean;
strict?: boolean;
remotes?: string[];
setParserOptionsProject?: boolean;
skipFormat?: boolean;
skipWorkspaceJson?: boolean;
ssr?: boolean;
standaloneConfig?: boolean;
compiler?: 'babel' | 'swc';
devServerPort?: number;
remotes?: string[];
strict?: boolean;
style: SupportedStyles;
tags?: string;
unitTestRunner: 'jest' | 'vitest' | 'none';
}

export interface NormalizedSchema extends Schema {
projectName: string;
appProjectRoot: string;
e2eProjectName: string;
projectName: string;
}
5 changes: 5 additions & 0 deletions packages/react/src/generators/host/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@
"devServerPort": {
"type": "number",
"description": "The port for the dev server of the remote app."
},
"ssr": {
"description": "Whether to configure SSR for the host application",
"type": "boolean",
"default": false
}
},
"required": ["name"],
Expand Down
3 changes: 2 additions & 1 deletion packages/react/src/generators/redux/redux.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { readJson, Tree } from '@nrwl/devkit';
import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing';
import { applicationGenerator, libraryGenerator } from '@nrwl/react';
import { Linter } from '@nrwl/linter';
import { applicationGenerator } from '../application/application';
import { libraryGenerator } from '../library/library';
import { reduxGenerator } from './redux';

describe('redux', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// @ts-check

/**
* @type {import('@nrwl/devkit').ModuleFederationConfig}
**/
const moduleFederationConfig = {
name: '<%= projectName %>',
exposes: {
'./Module': '<%= appProjectRoot %>/src/remote-entry.ts',
},
};

module.exports = moduleFederationConfig;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const { withModuleFederationForSSR } = require('@nrwl/react/module-federation');
const baseConfig = require("./module-federation.server.config");

const defaultConfig = {
...baseConfig
};

module.exports = withModuleFederationForSSR(defaultConfig);
Loading

1 comment on commit 6c59cbb

@vercel
Copy link

@vercel vercel bot commented on 6c59cbb Nov 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

nx-dev – ./

nx-dev-nrwl.vercel.app
nx-dev-git-master-nrwl.vercel.app
nx-five.vercel.app
nx.dev

Please sign in to comment.