Skip to content

Commit 1979bfc

Browse files
committed
mf: ssr
1 parent 1f370f4 commit 1979bfc

File tree

15 files changed

+305
-7
lines changed

15 files changed

+305
-7
lines changed

libs/mf/collection.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@
1212
"factory": "./src/schematics/mf/schematic",
1313
"schema": "./src/schematics/mf/schema.json",
1414
"description": "Initialize an angular project for webpack module federation"
15-
}
15+
},
16+
"nguniversal": {
17+
"factory": "./src/schematics/nguniversal/schematic",
18+
"schema": "./src/schematics/nguniversal/schema.json",
19+
"description": "Adds MF support to an Angular Universal project"
20+
},
21+
"boot-async": {
22+
"factory": "./src/schematics/boot-async/schematic",
23+
"schema": "./src/schematics/boot-async/schema.json",
24+
"description": "Enables or disables async bootstrapping"
25+
}
26+
1627
}
1728
}

libs/mf/nguniversal.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './src/universal/create-fetch';

libs/mf/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@angular-architects/module-federation",
3-
"version": "12.2.1",
3+
"version": "12.3.0-beta.9",
44
"license": "MIT",
55
"repository": {
66
"type": "GitHub",
@@ -17,9 +17,10 @@
1717
"schematics": "./collection.json",
1818
"builders": "./builders.json",
1919
"dependencies": {
20-
"ngx-build-plus": "^12.0.0",
20+
"ngx-build-plus": "^12.1.0-beta.3",
2121
"@angular-architects/module-federation-runtime": "^12.2.0",
2222
"word-wrap": "^1.2.3",
23-
"callsite": "^1.0.0"
23+
"callsite": "^1.0.0",
24+
"node-fetch": "^2.6.1"
2425
}
2526
}

libs/mf/src/nguniversal.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './universal/create-fetch';
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export interface BootAsyncSchema {
2+
project: string;
3+
async: boolean;
4+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"$schema": "http://json-schema.org/schema",
3+
"$id": "mf",
4+
"title": "",
5+
"type": "object",
6+
"properties": {
7+
"project": {
8+
"type": "string",
9+
"description": "The project to add module federation",
10+
"x-prompt": "Project name (press enter for default project)"
11+
},
12+
"async": {
13+
"type": "boolean",
14+
"description": "Enable or disable async bootstrapping",
15+
"$default": {
16+
"$source": "argv",
17+
"index": 0
18+
}
19+
}
20+
}
21+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import path = require("path");
2+
import { noop } from "rxjs";
3+
import { getWorkspaceFileName } from "../mf/schematic";
4+
import { BootAsyncSchema } from "./schema";
5+
import { Rule } from '@angular-devkit/schematics';
6+
7+
export default function bootAsync(options: BootAsyncSchema): Rule {
8+
9+
return async function (tree) {
10+
const workspaceFileName = getWorkspaceFileName(tree);
11+
12+
const workspace =
13+
JSON.parse(tree.read(workspaceFileName).toString('utf8'));
14+
15+
if (!options.project) {
16+
options.project = workspace.defaultProject;
17+
}
18+
19+
if (!options.project) {
20+
throw new Error(`No default project found. Please specifiy a project name!`)
21+
}
22+
23+
const projectName = options.project;
24+
const projectConfig = workspace.projects[projectName];
25+
26+
if (!projectConfig?.architect?.build?.options?.main) {
27+
throw new Error(`architect.build.options.main not found for project ` + projectName);
28+
}
29+
30+
const currentMain = projectConfig.architect.build.options.main;
31+
const newMainFile = options.async ? 'main.ts' : 'bootstrap.ts';
32+
const newMain = path.join(path.dirname(currentMain), newMainFile).replace(/\\/g, '/');
33+
projectConfig.architect.build.options.main = newMain;
34+
35+
tree.overwrite(workspaceFileName, JSON.stringify(workspace, null, 2));
36+
37+
return noop();
38+
}
39+
}

libs/mf/src/schematics/mf/schematic.ts

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,42 @@ export function add(options: MfSchematicSchema): Rule {
5050
return config(options);
5151
}
5252

53+
export function adjustSSR(sourceRoot: string, ssrMappings: string): Rule {
54+
return async function (tree, context) {
55+
56+
const server = path.join(sourceRoot, 'server.ts');
57+
58+
if (!tree.exists(server)) {
59+
return;
60+
}
61+
62+
let content = tree.read(server).toString('utf-8');
63+
64+
const imports = `import { CustomResourceLoader } from '@nguniversal/common/clover/server/src/custom-resource-loader';
65+
import { createFetch } from '@angular-architects/module-federation/nguniversal';
66+
`;
67+
68+
content = imports + content;
69+
content = content.replace('const ssrEngine = new Engine();', `
70+
// Without mappings, remotes are loaded via HTTP
71+
const mappings = ${ssrMappings};
72+
73+
// Monkey Patching Angular Universal for Module Federation
74+
CustomResourceLoader.prototype.fetch = createFetch(mappings);
75+
76+
const ssrEngine = new Engine();
77+
`);
78+
79+
// Compensate for issue with version 12.0.0
80+
content = content.replace(
81+
'const HOST = `http://localhost:${PORT}`;',
82+
'const HOST = `localhost:${PORT}`;');
83+
84+
tree.overwrite(server, content);
85+
86+
}
87+
}
88+
5389
function makeMainAsync(main: string): Rule {
5490
return async function (tree, context) {
5591

@@ -68,7 +104,7 @@ function makeMainAsync(main: string): Rule {
68104
}
69105
}
70106

71-
function getWorkspaceFileName(tree: Tree): string {
107+
export function getWorkspaceFileName(tree: Tree): string {
72108
if (tree.exists('angular.json')) {
73109
return 'angular.json';
74110
}
@@ -122,6 +158,7 @@ export default function config (options: MfSchematicSchema): Rule {
122158
}
123159

124160
const projectRoot: string = projectConfig.root;
161+
const projectSourceRoot: string = projectConfig.sourceRoot;
125162

126163
const configPath = path.join(projectRoot, 'webpack.config.js').replace(/\\/g, '/');
127164
const configProdPath = path.join(projectRoot, 'webpack.prod.config.js').replace(/\\/g, '/');
@@ -170,18 +207,41 @@ export default function config (options: MfSchematicSchema): Rule {
170207
projectConfig.architect.test.options.extraWebpackConfig = configPath;
171208
}
172209

210+
if (projectConfig?.architect?.['extract-i18n']?.options) {
211+
projectConfig.architect['extract-i18n'].options.extraWebpackConfig = configPath;
212+
}
213+
214+
updateServerBuilder(projectConfig, configPath);
215+
const ssrMappings = generateSsrMappings(workspace, projectName);
216+
173217
tree.overwrite(workspaceFileName, JSON.stringify(workspace, null, '\t'));
174218

175219
updatePackageJson(tree);
176220

177221
return chain([
178222
makeMainAsync(main),
223+
adjustSSR(projectSourceRoot, ssrMappings),
179224
externalSchematic('ngx-build-plus', 'ng-add', { project: options.project }),
180225
]);
181226

182227
}
183228
}
184229

230+
export function updateServerBuilder(projectConfig: any, configPath: string) {
231+
232+
if (projectConfig?.architect?.server) {
233+
projectConfig.architect.server.builder = 'ngx-build-plus:server';
234+
}
235+
236+
if (projectConfig?.architect?.server?.options) {
237+
projectConfig.architect.server.options.extraWebpackConfig = configPath;
238+
}
239+
240+
if (projectConfig?.architect?.server?.configurations?.production) {
241+
projectConfig.architect.server.configurations.production.extraWebpackConfig = configPath;
242+
}
243+
}
244+
185245
function generateRemoteConfig(workspace: any, projectName: string) {
186246
let remotes = '';
187247
for (const p in workspace.projects) {
@@ -204,3 +264,30 @@ function generateRemoteConfig(workspace: any, projectName: string) {
204264
return remotes;
205265
}
206266

267+
export function generateSsrMappings(workspace: any, projectName: string): string {
268+
let remotes = '{\n';
269+
270+
const projectOutPath = workspace.projects[projectName].architect.build.options.outputPath;
271+
272+
for (const p in workspace.projects) {
273+
const project = workspace.projects[p];
274+
const projectType = project.projectType ?? 'application';
275+
276+
if (p !== projectName
277+
&& projectType === 'application'
278+
&& project?.architect?.serve
279+
&& project?.architect?.build) {
280+
281+
const pPort = project.architect.serve.options?.port ?? 4200;
282+
const outPath = project.architect.build.options.outputPath;
283+
const relOutPath = path.relative(projectOutPath, outPath).replace(/\\/g, '/') + '/';
284+
285+
remotes += `\t// 'http://localhost:${pPort}/': join(__dirname, '${relOutPath}')\n`;
286+
}
287+
}
288+
289+
remotes += '}';
290+
291+
return remotes;
292+
}
293+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export interface NgUniversalSchema {
2+
project: string;
3+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"$schema": "http://json-schema.org/schema",
3+
"$id": "mf",
4+
"title": "",
5+
"type": "object",
6+
"properties": {
7+
"project": {
8+
"type": "string",
9+
"description": "The project to add module federation",
10+
"$default": {
11+
"$source": "argv",
12+
"index": 0
13+
},
14+
"x-prompt": "Project name (press enter for default project)"
15+
}
16+
}
17+
}

0 commit comments

Comments
 (0)