Skip to content

Commit b24d389

Browse files
committed
feat(mf): add rspack support
1 parent 1f25f30 commit b24d389

20 files changed

+32969
-13
lines changed

libs/mf-runtime/enhanced/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# @angular-architects/module-federation-runtime/enhanced
2+
3+
Secondary entry point of `@angular-architects/module-federation-runtime`. It can be used by importing from `@angular-architects/module-federation-runtime/enhanced`.
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"lib": {
3+
"entryFile": "src/index.ts"
4+
}
5+
}

libs/mf-runtime/enhanced/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './lib/init-federation';
2+
export * from './lib/loadRemoteModule';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { init } from '@module-federation/enhanced/runtime';
2+
import { FederationHost, UserOptions } from '@module-federation/runtime-core';
3+
4+
export type ManifestFile<T extends RemoteConfig = RemoteConfig> = {
5+
[key: string]: string | T;
6+
};
7+
8+
export type Manifest<T extends RemoteConfig = RemoteConfig> = {
9+
[key: string]: T;
10+
};
11+
12+
//
13+
// remoteEntry is the original used by the orignal
14+
// webpack-based plugin; entry is used by the new
15+
// Module Federation Runtime. We support both to
16+
// avoid confusion.
17+
//
18+
export type RemoteConfig =
19+
| {
20+
name: string;
21+
type: 'module' | 'script';
22+
remoteEntry: string;
23+
[key: string]: unknown;
24+
}
25+
| {
26+
name: string;
27+
type: 'module' | 'script';
28+
entry: string;
29+
[key: string]: unknown;
30+
};
31+
32+
export type InitFederationOptions = {
33+
runtimeOptions?: UserOptions;
34+
};
35+
36+
let config: Manifest = {};
37+
38+
export async function initFederation(
39+
manifest: string | ManifestFile,
40+
options?: InitFederationOptions
41+
): Promise<FederationHost> {
42+
if (typeof manifest === 'string') {
43+
config = await loadManifest(manifest);
44+
} else {
45+
config = parseConfig(manifest);
46+
}
47+
48+
const runtimeConfig = toRuntimeConfig(config, options);
49+
return init(runtimeConfig);
50+
}
51+
52+
// TODO: Consider making internal
53+
export function toRuntimeConfig(
54+
config: Manifest<RemoteConfig>,
55+
options?: InitFederationOptions
56+
): UserOptions {
57+
return {
58+
name: 'shell',
59+
...options?.runtimeOptions,
60+
remotes: [
61+
...(options?.runtimeOptions?.remotes ?? []),
62+
...toRemotes(config),
63+
],
64+
};
65+
}
66+
67+
function toRemotes(config: Manifest) {
68+
return Object.values(config).map((c) => ({
69+
name: c.name,
70+
entry: (c.entry ?? c.remoteEntry ?? '') as string,
71+
type: c.type,
72+
}));
73+
}
74+
75+
export function getManifest<T extends Manifest>(): T {
76+
return config as T;
77+
}
78+
79+
// Just needed to align with original webpack-based plugin
80+
export async function setManifest(manifest: ManifestFile) {
81+
config = parseConfig(manifest);
82+
}
83+
84+
export async function loadManifest<T extends Manifest = Manifest>(
85+
configFile: string
86+
): Promise<T> {
87+
const result = await fetch(configFile);
88+
89+
if (!result.ok) {
90+
throw Error('could not load configFile: ' + configFile);
91+
}
92+
93+
config = parseConfig(await result.json());
94+
return config as T;
95+
}
96+
97+
// TODO: Consider making internal
98+
export function parseConfig(config: ManifestFile): Manifest {
99+
const result: Manifest = {};
100+
for (const key in config) {
101+
const value = config[key];
102+
103+
let entry: RemoteConfig;
104+
if (typeof value === 'string') {
105+
entry = {
106+
name: key,
107+
remoteEntry: value,
108+
type: 'module',
109+
};
110+
} else {
111+
entry = {
112+
...value,
113+
name: key,
114+
type: value.type || 'module',
115+
};
116+
}
117+
118+
result[key] = entry;
119+
}
120+
return result;
121+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import { loadRemote } from '@module-federation/enhanced/runtime';
3+
4+
export type LoadRemoteModuleOptions<T = any> = {
5+
remoteName: string;
6+
exposedModule: string;
7+
fallback?: T;
8+
};
9+
10+
export async function loadRemoteModule<T = any>(
11+
remoteName: string,
12+
exposedModule: string
13+
): Promise<T>;
14+
export async function loadRemoteModule<T = any>(
15+
options: LoadRemoteModuleOptions
16+
): Promise<T>;
17+
export async function loadRemoteModule<T = any>(
18+
optionsOrRemoteName: LoadRemoteModuleOptions<T> | string,
19+
exposedModule?: string
20+
): Promise<T> {
21+
const options = normalize(optionsOrRemoteName, exposedModule);
22+
23+
const remote = options.remoteName;
24+
const exposed = normalizeExposed(options.exposedModule);
25+
26+
const url = [remote, exposed].join('/');
27+
28+
let result: T | null = null;
29+
let error: unknown;
30+
31+
try {
32+
result = await loadRemote<T>(url);
33+
} catch (e) {
34+
error = e;
35+
}
36+
37+
if (!error && result) {
38+
return result;
39+
}
40+
41+
if (options.fallback) {
42+
return options.fallback;
43+
}
44+
45+
if (error) {
46+
throw error;
47+
}
48+
49+
throw new Error('could not load ' + url);
50+
}
51+
52+
function normalize<T>(
53+
optionsOrRemoteName: LoadRemoteModuleOptions<T> | string,
54+
exposedModule?: string
55+
): LoadRemoteModuleOptions<T> {
56+
if (typeof optionsOrRemoteName === 'string') {
57+
return {
58+
remoteName: optionsOrRemoteName,
59+
exposedModule: exposedModule ?? '',
60+
};
61+
} else {
62+
return optionsOrRemoteName;
63+
}
64+
}
65+
66+
function normalizeExposed(exposed: string): string {
67+
if (exposed.startsWith('./')) {
68+
return exposed.substring(2);
69+
}
70+
return exposed;
71+
}

libs/mf-runtime/package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
{
22
"name": "@angular-architects/module-federation-runtime",
33
"license": "MIT",
4-
"version": "18.0.6",
4+
"version": "19.0.0-beta.0",
55
"peerDependencies": {
66
"@angular/common": ">=18.0.0",
7-
"@angular/core": ">=18.0.0"
7+
"@angular/core": ">=18.0.0",
8+
"@module-federation/enhanced": "^0.8.7",
9+
"@module-federation/runtime-core": "^0.6.15"
810
},
911
"dependencies": {
1012
"tslib": "^2.0.0"

libs/mf-runtime/tsconfig.lib.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
"types": []
99
},
1010
"exclude": [
11-
"src/**/*.spec.ts",
12-
"src/test-setup.ts",
11+
"**/*.spec.ts",
12+
"test-setup.ts",
1313
"jest.config.ts",
14-
"src/**/*.test.ts"
14+
"**/*.test.ts"
1515
],
16-
"include": ["src/**/*.ts"]
16+
"include": ["**/*.ts"]
1717
}

libs/mf-tools/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@angular-architects/module-federation-tools",
3-
"version": "18.0.6",
3+
"version": "19.0.0-beta.0",
44
"license": "MIT",
55
"peerDependencies": {},
66
"dependencies": {

libs/mf-tools/src/lib/web-components/web-component-wrapper.ts

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export type WebComponentWrapperOptions = LoadRemoteModuleOptions & {
2020
// eslint-disable-next-line @angular-eslint/component-selector
2121
selector: 'mft-wc-wrapper',
2222
template: '<div #vc></div>',
23+
standalone: false,
2324
})
2425
// eslint-disable-next-line @angular-eslint/component-class-suffix
2526
export class WebComponentWrapper implements AfterContentInit, OnChanges {

libs/mf/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@angular-architects/module-federation",
3-
"version": "18.0.6",
3+
"version": "19.0.0-beta.0",
44
"license": "MIT",
55
"repository": {
66
"type": "GitHub",
@@ -17,7 +17,7 @@
1717
"schematics": "./collection.json",
1818
"builders": "./builders.json",
1919
"dependencies": {
20-
"@angular-architects/module-federation-runtime": "18.0.6",
20+
"@angular-architects/module-federation-runtime": "19.0.0-beta.0",
2121
"word-wrap": "^1.2.3",
2222
"callsite": "^1.0.0",
2323
"node-fetch": "^2.6.7",

libs/mf/rspack.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export * from './src/rspack';
2+
export {
3+
DEFAULT_SECONARIES_SKIP_LIST,
4+
DEFAULT_SKIP_LIST,
5+
SharedMappings,
6+
findRootTsConfigJson,
7+
share,
8+
shareAll,
9+
} from './webpack';

libs/mf/runtime.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from '@angular-architects/module-federation-runtime/enhanced';

libs/mf/src/rspack/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './plugin-script-module';
2+
export * from './with-federation';
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { HtmlBasicTag, RsbuildPlugin } from '@rsbuild/core';
2+
3+
export const pluginScriptModule = (): RsbuildPlugin => ({
4+
name: 'plugin-script-module',
5+
setup: (api) => {
6+
api.modifyHTMLTags(({ headTags, bodyTags }) => {
7+
updateTags(headTags);
8+
updateTags(bodyTags);
9+
10+
return { headTags, bodyTags };
11+
});
12+
},
13+
});
14+
15+
function updateTags(tags: HtmlBasicTag[]) {
16+
tags.forEach((tag) => {
17+
if (tag.tag === 'script') {
18+
tag.attrs = {
19+
...tag.attrs,
20+
type: 'module',
21+
};
22+
}
23+
});
24+
}

0 commit comments

Comments
 (0)