@bleedingdev/modern-js-ultramodern-app-tools 3.9.0-ultramodern.16 → 3.9.0-ultramodern.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,236 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.n = (module)=>{
5
+ var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
6
+ __webpack_require__.d(getter, {
7
+ a: getter
8
+ });
9
+ return getter;
10
+ };
11
+ })();
12
+ (()=>{
13
+ __webpack_require__.d = (exports1, getters, values)=>{
14
+ var define = (defs, kind)=>{
15
+ for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
16
+ enumerable: true,
17
+ [kind]: defs[key]
18
+ });
19
+ };
20
+ define(getters, "get");
21
+ define(values, "value");
22
+ };
23
+ })();
24
+ (()=>{
25
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
26
+ })();
27
+ (()=>{
28
+ __webpack_require__.r = (exports1)=>{
29
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
30
+ value: 'Module'
31
+ });
32
+ Object.defineProperty(exports1, '__esModule', {
33
+ value: true
34
+ });
35
+ };
36
+ })();
37
+ var __webpack_exports__ = {};
38
+ __webpack_require__.r(__webpack_exports__);
39
+ __webpack_require__.d(__webpack_exports__, {
40
+ FederationPrivateContextsPlugin: ()=>FederationPrivateContextsPlugin,
41
+ resolveFrameworkSharedPackages: ()=>resolveFrameworkSharedPackages,
42
+ ultramodernModuleFederationSharedPlugin: ()=>ultramodernModuleFederationSharedPlugin,
43
+ withFrameworkShared: ()=>withFrameworkShared,
44
+ withReactJsxRuntimeShared: ()=>withReactJsxRuntimeShared
45
+ });
46
+ const external_node_fs_namespaceObject = require("node:fs");
47
+ const external_node_path_namespaceObject = require("node:path");
48
+ var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
49
+ const REACT_JSX_RUNTIMES = [
50
+ 'react/jsx-runtime',
51
+ 'react/jsx-dev-runtime'
52
+ ];
53
+ const MODULE_FEDERATION_CHAIN_IDS = [
54
+ 'plugin-module-federation',
55
+ 'plugin-module-federation-server'
56
+ ];
57
+ const FRAMEWORK_SHARES = [
58
+ {
59
+ packageName: '@modern-js/runtime',
60
+ prefix: '@modern-js/runtime/',
61
+ contextsRequest: '@modern-js/runtime/context',
62
+ contexts: /[\\/]core[\\/]context[\\/](?:index|runtime)\.[cm]?[jt]sx?$/
63
+ },
64
+ {
65
+ packageName: '@modern-js/plugin-i18n',
66
+ prefix: '@modern-js/plugin-i18n/runtime/',
67
+ contextsRequest: '@modern-js/plugin-i18n/runtime/contexts',
68
+ contexts: /[\\/]runtime[\\/]contexts\.[cm]?[jt]sx?$/
69
+ },
70
+ {
71
+ packageName: '@modern-js/bff-effect',
72
+ prefix: '@modern-js/bff-effect/',
73
+ contextsRequest: '@modern-js/bff-effect/context',
74
+ contexts: /[\\/]effect[\\/]context\.[cm]?[jt]sx?$/
75
+ }
76
+ ];
77
+ const loadsCode = (target)=>'string' == typeof target ? !target.endsWith('.d.ts') : Boolean(target) && 'object' == typeof target && Object.entries(target).some(([condition, value])=>'types' !== condition && loadsCode(value));
78
+ const exportedRequests = (packageName, prefix, exports1)=>Object.entries(exports1 && 'object' == typeof exports1 ? exports1 : {}).filter(([subpath, target])=>'./package.json' !== subpath && !subpath.includes('*') && loadsCode(target)).map(([subpath])=>`${packageName}${subpath.slice(1)}`).filter((request)=>request.startsWith(prefix)).sort();
79
+ const findInstalledManifest = (appDirectory, packageName)=>{
80
+ for(let directory = appDirectory;;){
81
+ const manifest = external_node_path_default().join(directory, 'node_modules', packageName, 'package.json');
82
+ if ((0, external_node_fs_namespaceObject.existsSync)(manifest)) return manifest;
83
+ const parent = external_node_path_default().dirname(directory);
84
+ if (parent === directory) return;
85
+ directory = parent;
86
+ }
87
+ };
88
+ const resolveFrameworkSharedPackages = (appDirectory)=>FRAMEWORK_SHARES.flatMap((share)=>{
89
+ const manifest = findInstalledManifest(appDirectory, share.packageName);
90
+ if (!manifest) return [];
91
+ const { version, exports: exports1 } = JSON.parse((0, external_node_fs_namespaceObject.readFileSync)(manifest, 'utf8'));
92
+ return [
93
+ {
94
+ ...share,
95
+ version,
96
+ requests: exportedRequests(share.packageName, share.prefix, exports1),
97
+ directory: (0, external_node_fs_namespaceObject.realpathSync)(external_node_path_default().dirname(manifest))
98
+ }
99
+ ];
100
+ });
101
+ const findShared = (entries, request)=>{
102
+ for (const entry of entries){
103
+ if (entry === request) return {};
104
+ if ('object' == typeof entry && Object.hasOwn(entry, request)) {
105
+ const config = entry[request];
106
+ return 'string' == typeof config ? {
107
+ requiredVersion: config
108
+ } : config;
109
+ }
110
+ }
111
+ };
112
+ const toEntries = (shared)=>Array.isArray(shared) ? shared : [
113
+ shared
114
+ ];
115
+ const withShared = (shared, missing)=>{
116
+ const entries = toEntries(shared);
117
+ const added = Object.fromEntries(Object.entries(missing).filter(([request])=>!findShared(entries, request)));
118
+ if (0 === Object.keys(added).length) return shared;
119
+ if (!shared) return added;
120
+ return Array.isArray(shared) ? [
121
+ ...shared,
122
+ added
123
+ ] : {
124
+ ...shared,
125
+ ...added
126
+ };
127
+ };
128
+ const withReactJsxRuntimeShared = (shared)=>{
129
+ if (!shared || 'object' != typeof shared) return shared;
130
+ const react = findShared(toEntries(shared), 'react');
131
+ if (!react) return shared;
132
+ return withShared(shared, Object.fromEntries(REACT_JSX_RUNTIMES.map((request)=>[
133
+ request,
134
+ {
135
+ ...void 0 === react.requiredVersion ? {} : {
136
+ requiredVersion: react.requiredVersion
137
+ },
138
+ singleton: true,
139
+ treeShaking: false
140
+ }
141
+ ])));
142
+ };
143
+ const withFrameworkShared = (shared, packages)=>withShared(shared, Object.fromEntries(packages.flatMap(({ requests, version })=>requests.map((request)=>[
144
+ request,
145
+ {
146
+ requiredVersion: version,
147
+ singleton: true,
148
+ treeShaking: false
149
+ }
150
+ ]))));
151
+ const federationConfig = (options)=>options.mfConfig && 'object' == typeof options.mfConfig ? federationConfig(options.mfConfig) : options;
152
+ const withDefaults = (options, packages)=>options.mfConfig && 'object' == typeof options.mfConfig ? {
153
+ ...options,
154
+ mfConfig: withDefaults(options.mfConfig, packages)
155
+ } : {
156
+ ...options,
157
+ shared: withFrameworkShared(withReactJsxRuntimeShared(options.shared), packages)
158
+ };
159
+ const hasExposes = (options)=>{
160
+ const { exposes } = federationConfig(options);
161
+ return Array.isArray(exposes) ? exposes.length > 0 : Boolean(exposes) && Object.keys(exposes).length > 0;
162
+ };
163
+ const SHARED_MODULE_TYPES = new Set([
164
+ 'consume-shared-module',
165
+ 'provide-module'
166
+ ]);
167
+ const resourceOf = (module)=>module && 'resource' in module && 'string' == typeof module.resource ? module.resource : void 0;
168
+ const describeModule = (module)=>module ? resourceOf(module) ?? module.identifier() : 'an entry';
169
+ const CONTAINER_ENTRY_DEPENDENCY = 'container entry';
170
+ const buildsContainer = (compilation)=>[
171
+ ...compilation.entries.values()
172
+ ].some(({ dependencies })=>dependencies.some(({ type })=>type === CONTAINER_ENTRY_DEPENDENCY));
173
+ class FederationPrivateContextsPlugin {
174
+ apply(compiler) {
175
+ const name = 'FederationPrivateContextsPlugin';
176
+ const definitionOf = (module)=>{
177
+ const resource = resourceOf(module);
178
+ return void 0 === resource ? void 0 : this.packages.find(({ contexts, directory })=>contexts?.test(resource) && resource.startsWith(directory + external_node_path_default().sep));
179
+ };
180
+ compiler.hooks.compilation.tap(name, (compilation)=>{
181
+ compilation.hooks.finishModules.tap(name, (modules)=>{
182
+ if (!buildsContainer(compilation)) return;
183
+ for (const module of modules){
184
+ const share = definitionOf(module);
185
+ if (!share) continue;
186
+ const importer = compilation.moduleGraph.getIncomingConnections(module).map((connection)=>connection.originModule).find((origin)=>!(origin && SHARED_MODULE_TYPES.has(origin.type)) && definitionOf(origin) !== share);
187
+ if (void 0 !== importer) compilation.errors.push(new compiler.webpack.WebpackError(`[ultramodern] This Module Federation build bundles a private copy of ${share.contextsRequest}: ${describeModule(importer)} imports ${describeModule(module)} directly. It would not see the host's request state. Share the "${share.prefix}" subpaths and import the contexts through "${share.contextsRequest}".`));
188
+ }
189
+ });
190
+ });
191
+ }
192
+ constructor(packages){
193
+ this.packages = packages;
194
+ }
195
+ }
196
+ const ultramodernModuleFederationSharedPlugin = ()=>({
197
+ name: '@modern-js/ultramodern-module-federation-shared',
198
+ pre: [
199
+ '@modern-js/plugin-module-federation',
200
+ '@modern-js/plugin-module-federation-ssr'
201
+ ],
202
+ setup (api) {
203
+ api.modifyBundlerChain((chain)=>{
204
+ const ids = MODULE_FEDERATION_CHAIN_IDS.filter((id)=>chain.plugins.has(id));
205
+ if (0 === ids.length) return;
206
+ const packages = resolveFrameworkSharedPackages(api.getAppContext().appDirectory);
207
+ let exposes = false;
208
+ for (const id of ids){
209
+ const [options] = chain.plugin(id).get('args');
210
+ exposes ||= hasExposes(options);
211
+ chain.plugin(id).tap(([options, ...rest])=>[
212
+ withDefaults(options, packages),
213
+ ...rest
214
+ ]);
215
+ }
216
+ if (exposes) chain.plugin('ultramodern-federation-private-contexts').use(FederationPrivateContextsPlugin, [
217
+ packages
218
+ ]);
219
+ });
220
+ }
221
+ });
222
+ exports.FederationPrivateContextsPlugin = __webpack_exports__.FederationPrivateContextsPlugin;
223
+ exports.resolveFrameworkSharedPackages = __webpack_exports__.resolveFrameworkSharedPackages;
224
+ exports.ultramodernModuleFederationSharedPlugin = __webpack_exports__.ultramodernModuleFederationSharedPlugin;
225
+ exports.withFrameworkShared = __webpack_exports__.withFrameworkShared;
226
+ exports.withReactJsxRuntimeShared = __webpack_exports__.withReactJsxRuntimeShared;
227
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
228
+ "FederationPrivateContextsPlugin",
229
+ "resolveFrameworkSharedPackages",
230
+ "ultramodernModuleFederationSharedPlugin",
231
+ "withFrameworkShared",
232
+ "withReactJsxRuntimeShared"
233
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
234
+ Object.defineProperty(exports, '__esModule', {
235
+ value: true
236
+ });
@@ -51,6 +51,7 @@ const release_identity_namespaceObject = require("@modern-js/app-tools-extension
51
51
  const runtime_package_resolution_namespaceObject = require("@modern-js/app-tools-extensions/runtime-package-resolution");
52
52
  const cli_namespaceObject = require("@modern-js/plugin/cli");
53
53
  const core_namespaceObject = require("@rsbuild/core");
54
+ const external_module_federation_shared_plugin_js_namespaceObject = require("./module-federation-shared-plugin.js");
54
55
  const DEFAULT_OTLP_ENDPOINT = 'http://127.0.0.1:4318/v1/logs';
55
56
  const DEFAULT_VICTORIA_METRICS_ENDPOINT = 'http://127.0.0.1:8428/api/v1/import/prometheus';
56
57
  const resolveReactRouterPackageDir = (appDirectory)=>{
@@ -111,6 +112,9 @@ const createPresetUltramodernConfig = (options = {})=>{
111
112
  output: {
112
113
  precompress: true
113
114
  },
115
+ plugins: [
116
+ (0, external_module_federation_shared_plugin_js_namespaceObject.ultramodernModuleFederationSharedPlugin)()
117
+ ],
114
118
  server,
115
119
  source: {
116
120
  reactCompiler: true,
@@ -0,0 +1,176 @@
1
+ import { existsSync, readFileSync, realpathSync } from "node:fs";
2
+ import node_path from "node:path";
3
+ const REACT_JSX_RUNTIMES = [
4
+ 'react/jsx-runtime',
5
+ 'react/jsx-dev-runtime'
6
+ ];
7
+ const MODULE_FEDERATION_CHAIN_IDS = [
8
+ 'plugin-module-federation',
9
+ 'plugin-module-federation-server'
10
+ ];
11
+ const FRAMEWORK_SHARES = [
12
+ {
13
+ packageName: '@modern-js/runtime',
14
+ prefix: '@modern-js/runtime/',
15
+ contextsRequest: '@modern-js/runtime/context',
16
+ contexts: /[\\/]core[\\/]context[\\/](?:index|runtime)\.[cm]?[jt]sx?$/
17
+ },
18
+ {
19
+ packageName: '@modern-js/plugin-i18n',
20
+ prefix: '@modern-js/plugin-i18n/runtime/',
21
+ contextsRequest: '@modern-js/plugin-i18n/runtime/contexts',
22
+ contexts: /[\\/]runtime[\\/]contexts\.[cm]?[jt]sx?$/
23
+ },
24
+ {
25
+ packageName: '@modern-js/bff-effect',
26
+ prefix: '@modern-js/bff-effect/',
27
+ contextsRequest: '@modern-js/bff-effect/context',
28
+ contexts: /[\\/]effect[\\/]context\.[cm]?[jt]sx?$/
29
+ }
30
+ ];
31
+ const loadsCode = (target)=>'string' == typeof target ? !target.endsWith('.d.ts') : Boolean(target) && 'object' == typeof target && Object.entries(target).some(([condition, value])=>'types' !== condition && loadsCode(value));
32
+ const exportedRequests = (packageName, prefix, exports)=>Object.entries(exports && 'object' == typeof exports ? exports : {}).filter(([subpath, target])=>'./package.json' !== subpath && !subpath.includes('*') && loadsCode(target)).map(([subpath])=>`${packageName}${subpath.slice(1)}`).filter((request)=>request.startsWith(prefix)).sort();
33
+ const findInstalledManifest = (appDirectory, packageName)=>{
34
+ for(let directory = appDirectory;;){
35
+ const manifest = node_path.join(directory, 'node_modules', packageName, 'package.json');
36
+ if (existsSync(manifest)) return manifest;
37
+ const parent = node_path.dirname(directory);
38
+ if (parent === directory) return;
39
+ directory = parent;
40
+ }
41
+ };
42
+ const resolveFrameworkSharedPackages = (appDirectory)=>FRAMEWORK_SHARES.flatMap((share)=>{
43
+ const manifest = findInstalledManifest(appDirectory, share.packageName);
44
+ if (!manifest) return [];
45
+ const { version, exports } = JSON.parse(readFileSync(manifest, 'utf8'));
46
+ return [
47
+ {
48
+ ...share,
49
+ version,
50
+ requests: exportedRequests(share.packageName, share.prefix, exports),
51
+ directory: realpathSync(node_path.dirname(manifest))
52
+ }
53
+ ];
54
+ });
55
+ const findShared = (entries, request)=>{
56
+ for (const entry of entries){
57
+ if (entry === request) return {};
58
+ if ('object' == typeof entry && Object.hasOwn(entry, request)) {
59
+ const config = entry[request];
60
+ return 'string' == typeof config ? {
61
+ requiredVersion: config
62
+ } : config;
63
+ }
64
+ }
65
+ };
66
+ const toEntries = (shared)=>Array.isArray(shared) ? shared : [
67
+ shared
68
+ ];
69
+ const withShared = (shared, missing)=>{
70
+ const entries = toEntries(shared);
71
+ const added = Object.fromEntries(Object.entries(missing).filter(([request])=>!findShared(entries, request)));
72
+ if (0 === Object.keys(added).length) return shared;
73
+ if (!shared) return added;
74
+ return Array.isArray(shared) ? [
75
+ ...shared,
76
+ added
77
+ ] : {
78
+ ...shared,
79
+ ...added
80
+ };
81
+ };
82
+ const withReactJsxRuntimeShared = (shared)=>{
83
+ if (!shared || 'object' != typeof shared) return shared;
84
+ const react = findShared(toEntries(shared), 'react');
85
+ if (!react) return shared;
86
+ return withShared(shared, Object.fromEntries(REACT_JSX_RUNTIMES.map((request)=>[
87
+ request,
88
+ {
89
+ ...void 0 === react.requiredVersion ? {} : {
90
+ requiredVersion: react.requiredVersion
91
+ },
92
+ singleton: true,
93
+ treeShaking: false
94
+ }
95
+ ])));
96
+ };
97
+ const withFrameworkShared = (shared, packages)=>withShared(shared, Object.fromEntries(packages.flatMap(({ requests, version })=>requests.map((request)=>[
98
+ request,
99
+ {
100
+ requiredVersion: version,
101
+ singleton: true,
102
+ treeShaking: false
103
+ }
104
+ ]))));
105
+ const federationConfig = (options)=>options.mfConfig && 'object' == typeof options.mfConfig ? federationConfig(options.mfConfig) : options;
106
+ const withDefaults = (options, packages)=>options.mfConfig && 'object' == typeof options.mfConfig ? {
107
+ ...options,
108
+ mfConfig: withDefaults(options.mfConfig, packages)
109
+ } : {
110
+ ...options,
111
+ shared: withFrameworkShared(withReactJsxRuntimeShared(options.shared), packages)
112
+ };
113
+ const hasExposes = (options)=>{
114
+ const { exposes } = federationConfig(options);
115
+ return Array.isArray(exposes) ? exposes.length > 0 : Boolean(exposes) && Object.keys(exposes).length > 0;
116
+ };
117
+ const SHARED_MODULE_TYPES = new Set([
118
+ 'consume-shared-module',
119
+ 'provide-module'
120
+ ]);
121
+ const resourceOf = (module)=>module && 'resource' in module && 'string' == typeof module.resource ? module.resource : void 0;
122
+ const describeModule = (module)=>module ? resourceOf(module) ?? module.identifier() : 'an entry';
123
+ const CONTAINER_ENTRY_DEPENDENCY = 'container entry';
124
+ const buildsContainer = (compilation)=>[
125
+ ...compilation.entries.values()
126
+ ].some(({ dependencies })=>dependencies.some(({ type })=>type === CONTAINER_ENTRY_DEPENDENCY));
127
+ class FederationPrivateContextsPlugin {
128
+ apply(compiler) {
129
+ const name = 'FederationPrivateContextsPlugin';
130
+ const definitionOf = (module)=>{
131
+ const resource = resourceOf(module);
132
+ return void 0 === resource ? void 0 : this.packages.find(({ contexts, directory })=>contexts?.test(resource) && resource.startsWith(directory + node_path.sep));
133
+ };
134
+ compiler.hooks.compilation.tap(name, (compilation)=>{
135
+ compilation.hooks.finishModules.tap(name, (modules)=>{
136
+ if (!buildsContainer(compilation)) return;
137
+ for (const module of modules){
138
+ const share = definitionOf(module);
139
+ if (!share) continue;
140
+ const importer = compilation.moduleGraph.getIncomingConnections(module).map((connection)=>connection.originModule).find((origin)=>!(origin && SHARED_MODULE_TYPES.has(origin.type)) && definitionOf(origin) !== share);
141
+ if (void 0 !== importer) compilation.errors.push(new compiler.webpack.WebpackError(`[ultramodern] This Module Federation build bundles a private copy of ${share.contextsRequest}: ${describeModule(importer)} imports ${describeModule(module)} directly. It would not see the host's request state. Share the "${share.prefix}" subpaths and import the contexts through "${share.contextsRequest}".`));
142
+ }
143
+ });
144
+ });
145
+ }
146
+ constructor(packages){
147
+ this.packages = packages;
148
+ }
149
+ }
150
+ const ultramodernModuleFederationSharedPlugin = ()=>({
151
+ name: '@modern-js/ultramodern-module-federation-shared',
152
+ pre: [
153
+ '@modern-js/plugin-module-federation',
154
+ '@modern-js/plugin-module-federation-ssr'
155
+ ],
156
+ setup (api) {
157
+ api.modifyBundlerChain((chain)=>{
158
+ const ids = MODULE_FEDERATION_CHAIN_IDS.filter((id)=>chain.plugins.has(id));
159
+ if (0 === ids.length) return;
160
+ const packages = resolveFrameworkSharedPackages(api.getAppContext().appDirectory);
161
+ let exposes = false;
162
+ for (const id of ids){
163
+ const [options] = chain.plugin(id).get('args');
164
+ exposes ||= hasExposes(options);
165
+ chain.plugin(id).tap(([options, ...rest])=>[
166
+ withDefaults(options, packages),
167
+ ...rest
168
+ ]);
169
+ }
170
+ if (exposes) chain.plugin('ultramodern-federation-private-contexts').use(FederationPrivateContextsPlugin, [
171
+ packages
172
+ ]);
173
+ });
174
+ }
175
+ });
176
+ export { FederationPrivateContextsPlugin, resolveFrameworkSharedPackages, ultramodernModuleFederationSharedPlugin, withFrameworkShared, withReactJsxRuntimeShared };
@@ -7,6 +7,7 @@ import { resolveUltramodernReleaseIdentity } from "@modern-js/app-tools-extensio
7
7
  import { findHostingModuleDirectory } from "@modern-js/app-tools-extensions/runtime-package-resolution";
8
8
  import { mergeConfig } from "@modern-js/plugin/cli";
9
9
  import { rspack } from "@rsbuild/core";
10
+ import { ultramodernModuleFederationSharedPlugin } from "./module-federation-shared-plugin.mjs";
10
11
  const DEFAULT_OTLP_ENDPOINT = 'http://127.0.0.1:4318/v1/logs';
11
12
  const DEFAULT_VICTORIA_METRICS_ENDPOINT = 'http://127.0.0.1:8428/api/v1/import/prometheus';
12
13
  const resolveReactRouterPackageDir = (appDirectory)=>{
@@ -67,6 +68,9 @@ const createPresetUltramodernConfig = (options = {})=>{
67
68
  output: {
68
69
  precompress: true
69
70
  },
71
+ plugins: [
72
+ ultramodernModuleFederationSharedPlugin()
73
+ ],
70
74
  server,
71
75
  source: {
72
76
  reactCompiler: true,
@@ -0,0 +1,177 @@
1
+ import "node:module";
2
+ import { existsSync, readFileSync, realpathSync } from "node:fs";
3
+ import node_path from "node:path";
4
+ const REACT_JSX_RUNTIMES = [
5
+ 'react/jsx-runtime',
6
+ 'react/jsx-dev-runtime'
7
+ ];
8
+ const MODULE_FEDERATION_CHAIN_IDS = [
9
+ 'plugin-module-federation',
10
+ 'plugin-module-federation-server'
11
+ ];
12
+ const FRAMEWORK_SHARES = [
13
+ {
14
+ packageName: '@modern-js/runtime',
15
+ prefix: '@modern-js/runtime/',
16
+ contextsRequest: '@modern-js/runtime/context',
17
+ contexts: /[\\/]core[\\/]context[\\/](?:index|runtime)\.[cm]?[jt]sx?$/
18
+ },
19
+ {
20
+ packageName: '@modern-js/plugin-i18n',
21
+ prefix: '@modern-js/plugin-i18n/runtime/',
22
+ contextsRequest: '@modern-js/plugin-i18n/runtime/contexts',
23
+ contexts: /[\\/]runtime[\\/]contexts\.[cm]?[jt]sx?$/
24
+ },
25
+ {
26
+ packageName: '@modern-js/bff-effect',
27
+ prefix: '@modern-js/bff-effect/',
28
+ contextsRequest: '@modern-js/bff-effect/context',
29
+ contexts: /[\\/]effect[\\/]context\.[cm]?[jt]sx?$/
30
+ }
31
+ ];
32
+ const loadsCode = (target)=>'string' == typeof target ? !target.endsWith('.d.ts') : Boolean(target) && 'object' == typeof target && Object.entries(target).some(([condition, value])=>'types' !== condition && loadsCode(value));
33
+ const exportedRequests = (packageName, prefix, exports)=>Object.entries(exports && 'object' == typeof exports ? exports : {}).filter(([subpath, target])=>'./package.json' !== subpath && !subpath.includes('*') && loadsCode(target)).map(([subpath])=>`${packageName}${subpath.slice(1)}`).filter((request)=>request.startsWith(prefix)).sort();
34
+ const findInstalledManifest = (appDirectory, packageName)=>{
35
+ for(let directory = appDirectory;;){
36
+ const manifest = node_path.join(directory, 'node_modules', packageName, 'package.json');
37
+ if (existsSync(manifest)) return manifest;
38
+ const parent = node_path.dirname(directory);
39
+ if (parent === directory) return;
40
+ directory = parent;
41
+ }
42
+ };
43
+ const resolveFrameworkSharedPackages = (appDirectory)=>FRAMEWORK_SHARES.flatMap((share)=>{
44
+ const manifest = findInstalledManifest(appDirectory, share.packageName);
45
+ if (!manifest) return [];
46
+ const { version, exports } = JSON.parse(readFileSync(manifest, 'utf8'));
47
+ return [
48
+ {
49
+ ...share,
50
+ version,
51
+ requests: exportedRequests(share.packageName, share.prefix, exports),
52
+ directory: realpathSync(node_path.dirname(manifest))
53
+ }
54
+ ];
55
+ });
56
+ const findShared = (entries, request)=>{
57
+ for (const entry of entries){
58
+ if (entry === request) return {};
59
+ if ('object' == typeof entry && Object.hasOwn(entry, request)) {
60
+ const config = entry[request];
61
+ return 'string' == typeof config ? {
62
+ requiredVersion: config
63
+ } : config;
64
+ }
65
+ }
66
+ };
67
+ const toEntries = (shared)=>Array.isArray(shared) ? shared : [
68
+ shared
69
+ ];
70
+ const withShared = (shared, missing)=>{
71
+ const entries = toEntries(shared);
72
+ const added = Object.fromEntries(Object.entries(missing).filter(([request])=>!findShared(entries, request)));
73
+ if (0 === Object.keys(added).length) return shared;
74
+ if (!shared) return added;
75
+ return Array.isArray(shared) ? [
76
+ ...shared,
77
+ added
78
+ ] : {
79
+ ...shared,
80
+ ...added
81
+ };
82
+ };
83
+ const withReactJsxRuntimeShared = (shared)=>{
84
+ if (!shared || 'object' != typeof shared) return shared;
85
+ const react = findShared(toEntries(shared), 'react');
86
+ if (!react) return shared;
87
+ return withShared(shared, Object.fromEntries(REACT_JSX_RUNTIMES.map((request)=>[
88
+ request,
89
+ {
90
+ ...void 0 === react.requiredVersion ? {} : {
91
+ requiredVersion: react.requiredVersion
92
+ },
93
+ singleton: true,
94
+ treeShaking: false
95
+ }
96
+ ])));
97
+ };
98
+ const withFrameworkShared = (shared, packages)=>withShared(shared, Object.fromEntries(packages.flatMap(({ requests, version })=>requests.map((request)=>[
99
+ request,
100
+ {
101
+ requiredVersion: version,
102
+ singleton: true,
103
+ treeShaking: false
104
+ }
105
+ ]))));
106
+ const federationConfig = (options)=>options.mfConfig && 'object' == typeof options.mfConfig ? federationConfig(options.mfConfig) : options;
107
+ const withDefaults = (options, packages)=>options.mfConfig && 'object' == typeof options.mfConfig ? {
108
+ ...options,
109
+ mfConfig: withDefaults(options.mfConfig, packages)
110
+ } : {
111
+ ...options,
112
+ shared: withFrameworkShared(withReactJsxRuntimeShared(options.shared), packages)
113
+ };
114
+ const hasExposes = (options)=>{
115
+ const { exposes } = federationConfig(options);
116
+ return Array.isArray(exposes) ? exposes.length > 0 : Boolean(exposes) && Object.keys(exposes).length > 0;
117
+ };
118
+ const SHARED_MODULE_TYPES = new Set([
119
+ 'consume-shared-module',
120
+ 'provide-module'
121
+ ]);
122
+ const resourceOf = (module)=>module && 'resource' in module && 'string' == typeof module.resource ? module.resource : void 0;
123
+ const describeModule = (module)=>module ? resourceOf(module) ?? module.identifier() : 'an entry';
124
+ const CONTAINER_ENTRY_DEPENDENCY = 'container entry';
125
+ const buildsContainer = (compilation)=>[
126
+ ...compilation.entries.values()
127
+ ].some(({ dependencies })=>dependencies.some(({ type })=>type === CONTAINER_ENTRY_DEPENDENCY));
128
+ class FederationPrivateContextsPlugin {
129
+ apply(compiler) {
130
+ const name = 'FederationPrivateContextsPlugin';
131
+ const definitionOf = (module)=>{
132
+ const resource = resourceOf(module);
133
+ return void 0 === resource ? void 0 : this.packages.find(({ contexts, directory })=>contexts?.test(resource) && resource.startsWith(directory + node_path.sep));
134
+ };
135
+ compiler.hooks.compilation.tap(name, (compilation)=>{
136
+ compilation.hooks.finishModules.tap(name, (modules)=>{
137
+ if (!buildsContainer(compilation)) return;
138
+ for (const module of modules){
139
+ const share = definitionOf(module);
140
+ if (!share) continue;
141
+ const importer = compilation.moduleGraph.getIncomingConnections(module).map((connection)=>connection.originModule).find((origin)=>!(origin && SHARED_MODULE_TYPES.has(origin.type)) && definitionOf(origin) !== share);
142
+ if (void 0 !== importer) compilation.errors.push(new compiler.webpack.WebpackError(`[ultramodern] This Module Federation build bundles a private copy of ${share.contextsRequest}: ${describeModule(importer)} imports ${describeModule(module)} directly. It would not see the host's request state. Share the "${share.prefix}" subpaths and import the contexts through "${share.contextsRequest}".`));
143
+ }
144
+ });
145
+ });
146
+ }
147
+ constructor(packages){
148
+ this.packages = packages;
149
+ }
150
+ }
151
+ const ultramodernModuleFederationSharedPlugin = ()=>({
152
+ name: '@modern-js/ultramodern-module-federation-shared',
153
+ pre: [
154
+ '@modern-js/plugin-module-federation',
155
+ '@modern-js/plugin-module-federation-ssr'
156
+ ],
157
+ setup (api) {
158
+ api.modifyBundlerChain((chain)=>{
159
+ const ids = MODULE_FEDERATION_CHAIN_IDS.filter((id)=>chain.plugins.has(id));
160
+ if (0 === ids.length) return;
161
+ const packages = resolveFrameworkSharedPackages(api.getAppContext().appDirectory);
162
+ let exposes = false;
163
+ for (const id of ids){
164
+ const [options] = chain.plugin(id).get('args');
165
+ exposes ||= hasExposes(options);
166
+ chain.plugin(id).tap(([options, ...rest])=>[
167
+ withDefaults(options, packages),
168
+ ...rest
169
+ ]);
170
+ }
171
+ if (exposes) chain.plugin('ultramodern-federation-private-contexts').use(FederationPrivateContextsPlugin, [
172
+ packages
173
+ ]);
174
+ });
175
+ }
176
+ });
177
+ export { FederationPrivateContextsPlugin, resolveFrameworkSharedPackages, ultramodernModuleFederationSharedPlugin, withFrameworkShared, withReactJsxRuntimeShared };
@@ -8,6 +8,7 @@ import { resolveUltramodernReleaseIdentity } from "@modern-js/app-tools-extensio
8
8
  import { findHostingModuleDirectory } from "@modern-js/app-tools-extensions/runtime-package-resolution";
9
9
  import { mergeConfig } from "@modern-js/plugin/cli";
10
10
  import { rspack } from "@rsbuild/core";
11
+ import { ultramodernModuleFederationSharedPlugin } from "./module-federation-shared-plugin.mjs";
11
12
  const DEFAULT_OTLP_ENDPOINT = 'http://127.0.0.1:4318/v1/logs';
12
13
  const DEFAULT_VICTORIA_METRICS_ENDPOINT = 'http://127.0.0.1:8428/api/v1/import/prometheus';
13
14
  const resolveReactRouterPackageDir = (appDirectory)=>{
@@ -68,6 +69,9 @@ const createPresetUltramodernConfig = (options = {})=>{
68
69
  output: {
69
70
  precompress: true
70
71
  },
72
+ plugins: [
73
+ ultramodernModuleFederationSharedPlugin()
74
+ ],
71
75
  server,
72
76
  source: {
73
77
  reactCompiler: true,
@@ -0,0 +1,45 @@
1
+ import type { AppTools, CliPlugin } from '@modern-js/app-tools';
2
+ import type { Rspack } from '@rsbuild/core';
3
+ export type FrameworkSharedPackage = {
4
+ prefix: string;
5
+ version: string;
6
+ /** Exported subpath requests under `prefix` that load code. */
7
+ requests: string[];
8
+ /** Real path of the installed package directory. */
9
+ directory: string;
10
+ contextsRequest?: string;
11
+ contexts?: RegExp;
12
+ };
13
+ /** The framework packages the app installs, with their installed versions. */
14
+ export declare const resolveFrameworkSharedPackages: (appDirectory: string) => FrameworkSharedPackage[];
15
+ /**
16
+ * Share `react/jsx-runtime` and `react/jsx-dev-runtime` as singletons at the
17
+ * shared `react` version whenever `react` itself is shared. Entries the app
18
+ * configured explicitly are kept as written.
19
+ */
20
+ export declare const withReactJsxRuntimeShared: <T>(shared: T) => T;
21
+ /**
22
+ * Share every exported subpath of the framework packages as a singleton at
23
+ * the installed version. Entries the app configured explicitly are kept.
24
+ */
25
+ export declare const withFrameworkShared: <T>(shared: T, packages: FrameworkSharedPackage[]) => T;
26
+ /**
27
+ * Fails a federation build that exposes modules when a framework context file
28
+ * is imported other than through its shared request. That remote would run
29
+ * with its own contexts and never see the host's request state.
30
+ *
31
+ * The check runs only when the compilation really builds a container: an
32
+ * environment may drop the federation plugin after the chain was configured
33
+ * (the Cloudflare workerd SSR graph does), and a graph with no container has
34
+ * no host to share contexts with.
35
+ */
36
+ export declare class FederationPrivateContextsPlugin {
37
+ private readonly packages;
38
+ constructor(packages: FrameworkSharedPackage[]);
39
+ apply(compiler: Rspack.Compiler): void;
40
+ }
41
+ /**
42
+ * Apply UltraModern's Module Federation share defaults to the federation
43
+ * plugins that `@module-federation/modern-js-v3` registered on the chain.
44
+ */
45
+ export declare const ultramodernModuleFederationSharedPlugin: () => CliPlugin<AppTools>;
@@ -0,0 +1,45 @@
1
+ import type { AppTools, CliPlugin } from '@modern-js/app-tools';
2
+ import type { Rspack } from '@rsbuild/core';
3
+ export type FrameworkSharedPackage = {
4
+ prefix: string;
5
+ version: string;
6
+ /** Exported subpath requests under `prefix` that load code. */
7
+ requests: string[];
8
+ /** Real path of the installed package directory. */
9
+ directory: string;
10
+ contextsRequest?: string;
11
+ contexts?: RegExp;
12
+ };
13
+ /** The framework packages the app installs, with their installed versions. */
14
+ export declare const resolveFrameworkSharedPackages: (appDirectory: string) => FrameworkSharedPackage[];
15
+ /**
16
+ * Share `react/jsx-runtime` and `react/jsx-dev-runtime` as singletons at the
17
+ * shared `react` version whenever `react` itself is shared. Entries the app
18
+ * configured explicitly are kept as written.
19
+ */
20
+ export declare const withReactJsxRuntimeShared: <T>(shared: T) => T;
21
+ /**
22
+ * Share every exported subpath of the framework packages as a singleton at
23
+ * the installed version. Entries the app configured explicitly are kept.
24
+ */
25
+ export declare const withFrameworkShared: <T>(shared: T, packages: FrameworkSharedPackage[]) => T;
26
+ /**
27
+ * Fails a federation build that exposes modules when a framework context file
28
+ * is imported other than through its shared request. That remote would run
29
+ * with its own contexts and never see the host's request state.
30
+ *
31
+ * The check runs only when the compilation really builds a container: an
32
+ * environment may drop the federation plugin after the chain was configured
33
+ * (the Cloudflare workerd SSR graph does), and a graph with no container has
34
+ * no host to share contexts with.
35
+ */
36
+ export declare class FederationPrivateContextsPlugin {
37
+ private readonly packages;
38
+ constructor(packages: FrameworkSharedPackage[]);
39
+ apply(compiler: Rspack.Compiler): void;
40
+ }
41
+ /**
42
+ * Apply UltraModern's Module Federation share defaults to the federation
43
+ * plugins that `@module-federation/modern-js-v3` registered on the chain.
44
+ */
45
+ export declare const ultramodernModuleFederationSharedPlugin: () => CliPlugin<AppTools>;
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "directory": "packages/solutions/ultramodern-app-tools"
12
12
  },
13
13
  "license": "MIT",
14
- "version": "3.9.0-ultramodern.16",
14
+ "version": "3.9.0-ultramodern.18",
15
15
  "files": [
16
16
  "dist",
17
17
  "src"
@@ -62,26 +62,26 @@
62
62
  "node": ">=26.7.0"
63
63
  },
64
64
  "dependencies": {
65
- "@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.9.0-ultramodern.16",
66
- "@modern-js/app-tools-extensions": "npm:@bleedingdev/modern-js-app-tools-extensions@3.9.0-ultramodern.16",
67
- "@modern-js/i18n-integration": "npm:@bleedingdev/modern-js-i18n-integration@3.9.0-ultramodern.16",
68
- "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.0-ultramodern.16",
69
- "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.9.0-ultramodern.16",
70
- "@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.0-ultramodern.16",
71
- "@modern-js/runtime-renderer-extensions": "npm:@bleedingdev/modern-js-runtime-renderer-extensions@3.9.0-ultramodern.16",
72
- "@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.9.0-ultramodern.16",
73
- "@modern-js/server-runtime-extensions": "npm:@bleedingdev/modern-js-server-runtime-extensions@3.9.0-ultramodern.16",
65
+ "@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.9.0-ultramodern.18",
66
+ "@modern-js/app-tools-extensions": "npm:@bleedingdev/modern-js-app-tools-extensions@3.9.0-ultramodern.18",
67
+ "@modern-js/i18n-integration": "npm:@bleedingdev/modern-js-i18n-integration@3.9.0-ultramodern.18",
68
+ "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.0-ultramodern.18",
69
+ "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.9.0-ultramodern.18",
70
+ "@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.0-ultramodern.18",
71
+ "@modern-js/runtime-renderer-extensions": "npm:@bleedingdev/modern-js-runtime-renderer-extensions@3.9.0-ultramodern.18",
72
+ "@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.9.0-ultramodern.18",
73
+ "@modern-js/server-runtime-extensions": "npm:@bleedingdev/modern-js-server-runtime-extensions@3.9.0-ultramodern.18",
74
74
  "@rsbuild/core": "2.2.9"
75
75
  },
76
76
  "devDependencies": {
77
- "@modern-js/backend-federation-contracts": "npm:@bleedingdev/modern-js-backend-federation-contracts@3.9.0-ultramodern.16",
78
- "@modern-js/builder": "npm:@bleedingdev/modern-js-builder@3.9.0-ultramodern.16",
79
- "@modern-js/plugin-bff": "npm:@bleedingdev/modern-js-plugin-bff@3.9.0-ultramodern.16",
80
- "@modern-js/plugin-tanstack": "npm:@bleedingdev/modern-js-plugin-tanstack@3.9.0-ultramodern.16",
81
- "@modern-js/prod-server": "npm:@bleedingdev/modern-js-prod-server@3.9.0-ultramodern.16",
82
- "@modern-js/server": "npm:@bleedingdev/modern-js-server@3.9.0-ultramodern.16",
83
- "@modern-js/tsconfig": "npm:@bleedingdev/modern-js-tsconfig@3.9.0-ultramodern.16",
84
- "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.9.0-ultramodern.16",
77
+ "@modern-js/backend-federation-contracts": "npm:@bleedingdev/modern-js-backend-federation-contracts@3.9.0-ultramodern.18",
78
+ "@modern-js/builder": "npm:@bleedingdev/modern-js-builder@3.9.0-ultramodern.18",
79
+ "@modern-js/plugin-bff": "npm:@bleedingdev/modern-js-plugin-bff@3.9.0-ultramodern.18",
80
+ "@modern-js/plugin-tanstack": "npm:@bleedingdev/modern-js-plugin-tanstack@3.9.0-ultramodern.18",
81
+ "@modern-js/prod-server": "npm:@bleedingdev/modern-js-prod-server@3.9.0-ultramodern.18",
82
+ "@modern-js/server": "npm:@bleedingdev/modern-js-server@3.9.0-ultramodern.18",
83
+ "@modern-js/tsconfig": "npm:@bleedingdev/modern-js-tsconfig@3.9.0-ultramodern.18",
84
+ "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.9.0-ultramodern.18",
85
85
  "@rslib/core": "1.0.2",
86
86
  "@scripts/rstest-config": "2.66.0",
87
87
  "@types/node": "^26.6.2",
@@ -0,0 +1,367 @@
1
+ import { existsSync, readFileSync, realpathSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import type { AppTools, CliPlugin } from '@modern-js/app-tools';
4
+ import type { Rspack } from '@rsbuild/core';
5
+
6
+ type SharedConfig = { requiredVersion?: unknown } & Record<string, unknown>;
7
+ type SharedRecord = Record<string, string | SharedConfig>;
8
+ type Shared = SharedRecord | Array<string | SharedRecord>;
9
+
10
+ // The compiled JSX in every federated bundle imports these React entries.
11
+ // Sharing `react` without them gives each bundle its own JSX runtime, so a
12
+ // remote's elements reach the host's React with a foreign runtime identity.
13
+ const REACT_JSX_RUNTIMES = ['react/jsx-runtime', 'react/jsx-dev-runtime'];
14
+
15
+ // Chain ids under which `@module-federation/modern-js-v3` registers its
16
+ // browser and server federation plugins.
17
+ const MODULE_FEDERATION_CHAIN_IDS = [
18
+ 'plugin-module-federation',
19
+ 'plugin-module-federation-server',
20
+ ];
21
+
22
+ /**
23
+ * Framework packages whose subpaths share module state: React contexts and
24
+ * Effect's request storage. Every exported subpath under `prefix` is shared,
25
+ * and each subpath reaches that state through a package self-reference (for
26
+ * example `@modern-js/runtime/context`), so a remote never evaluates its own
27
+ * copy. Subpaths are listed from the package's `exports` rather than shared by
28
+ * prefix: the app aliases unexported ones such as `@modern-js/runtime/registry`
29
+ * to its own generated modules, which must never be shared.
30
+ * `contexts` matches the files that define that state (React contexts, the
31
+ * Effect request storage): only a shared module or another of those files may
32
+ * import them.
33
+ */
34
+ const FRAMEWORK_SHARES = [
35
+ {
36
+ packageName: '@modern-js/runtime',
37
+ prefix: '@modern-js/runtime/',
38
+ contextsRequest: '@modern-js/runtime/context',
39
+ contexts: /[\\/]core[\\/]context[\\/](?:index|runtime)\.[cm]?[jt]sx?$/,
40
+ },
41
+ {
42
+ packageName: '@modern-js/plugin-i18n',
43
+ prefix: '@modern-js/plugin-i18n/runtime/',
44
+ contextsRequest: '@modern-js/plugin-i18n/runtime/contexts',
45
+ contexts: /[\\/]runtime[\\/]contexts\.[cm]?[jt]sx?$/,
46
+ },
47
+ {
48
+ packageName: '@modern-js/bff-effect',
49
+ prefix: '@modern-js/bff-effect/',
50
+ contextsRequest: '@modern-js/bff-effect/context',
51
+ contexts: /[\\/]effect[\\/]context\.[cm]?[jt]sx?$/,
52
+ },
53
+ ] as const;
54
+
55
+ export type FrameworkSharedPackage = {
56
+ prefix: string;
57
+ version: string;
58
+ /** Exported subpath requests under `prefix` that load code. */
59
+ requests: string[];
60
+ /** Real path of the installed package directory. */
61
+ directory: string;
62
+ contextsRequest?: string;
63
+ contexts?: RegExp;
64
+ };
65
+
66
+ // A types-only export or a wildcard pattern names no module to share.
67
+ const loadsCode = (target: unknown): boolean =>
68
+ typeof target === 'string'
69
+ ? !target.endsWith('.d.ts')
70
+ : Boolean(target) &&
71
+ typeof target === 'object' &&
72
+ Object.entries(target as object).some(
73
+ ([condition, value]) => condition !== 'types' && loadsCode(value),
74
+ );
75
+
76
+ const exportedRequests = (
77
+ packageName: string,
78
+ prefix: string,
79
+ exports: unknown,
80
+ ) =>
81
+ Object.entries(
82
+ exports && typeof exports === 'object' ? (exports as object) : {},
83
+ )
84
+ .filter(
85
+ ([subpath, target]) =>
86
+ subpath !== './package.json' &&
87
+ !subpath.includes('*') &&
88
+ loadsCode(target),
89
+ )
90
+ .map(([subpath]) => `${packageName}${subpath.slice(1)}`)
91
+ .filter(request => request.startsWith(prefix))
92
+ .sort();
93
+
94
+ /**
95
+ * The app's `node_modules` lookup for `packageName`. `NODE_PATH` is ignored:
96
+ * a package the app does not install is not the app's to share.
97
+ */
98
+ const findInstalledManifest = (appDirectory: string, packageName: string) => {
99
+ for (let directory = appDirectory; ; ) {
100
+ const manifest = path.join(
101
+ directory,
102
+ 'node_modules',
103
+ packageName,
104
+ 'package.json',
105
+ );
106
+ if (existsSync(manifest)) return manifest;
107
+ const parent = path.dirname(directory);
108
+ if (parent === directory) return undefined;
109
+ directory = parent;
110
+ }
111
+ };
112
+
113
+ /** The framework packages the app installs, with their installed versions. */
114
+ export const resolveFrameworkSharedPackages = (
115
+ appDirectory: string,
116
+ ): FrameworkSharedPackage[] =>
117
+ FRAMEWORK_SHARES.flatMap(share => {
118
+ const manifest = findInstalledManifest(appDirectory, share.packageName);
119
+ if (!manifest) return [];
120
+ const { version, exports } = JSON.parse(readFileSync(manifest, 'utf8')) as {
121
+ version: string;
122
+ exports?: unknown;
123
+ };
124
+ return [
125
+ {
126
+ ...share,
127
+ version,
128
+ requests: exportedRequests(share.packageName, share.prefix, exports),
129
+ directory: realpathSync(path.dirname(manifest)),
130
+ },
131
+ ];
132
+ });
133
+
134
+ const findShared = (
135
+ entries: Array<string | SharedRecord>,
136
+ request: string,
137
+ ): SharedConfig | undefined => {
138
+ for (const entry of entries) {
139
+ if (entry === request) return {};
140
+ if (typeof entry === 'object' && Object.hasOwn(entry, request)) {
141
+ const config = entry[request];
142
+ return typeof config === 'string' ? { requiredVersion: config } : config;
143
+ }
144
+ }
145
+ return undefined;
146
+ };
147
+
148
+ const toEntries = (shared: unknown) =>
149
+ (Array.isArray(shared) ? shared : [shared]) as Array<string | SharedRecord>;
150
+
151
+ /** Add `missing` to `shared`, keeping every entry the app configured. */
152
+ const withShared = <T>(shared: T, missing: SharedRecord): T => {
153
+ const entries = toEntries(shared);
154
+ const added = Object.fromEntries(
155
+ Object.entries(missing).filter(
156
+ ([request]) => !findShared(entries, request),
157
+ ),
158
+ );
159
+ if (Object.keys(added).length === 0) return shared;
160
+ if (!shared) return added as T;
161
+ return (
162
+ Array.isArray(shared) ? [...shared, added] : { ...shared, ...added }
163
+ ) as T;
164
+ };
165
+
166
+ /**
167
+ * Share `react/jsx-runtime` and `react/jsx-dev-runtime` as singletons at the
168
+ * shared `react` version whenever `react` itself is shared. Entries the app
169
+ * configured explicitly are kept as written.
170
+ */
171
+ export const withReactJsxRuntimeShared = <T>(shared: T): T => {
172
+ if (!shared || typeof shared !== 'object') return shared;
173
+ const react = findShared(toEntries(shared), 'react');
174
+ if (!react) return shared;
175
+ return withShared(
176
+ shared,
177
+ Object.fromEntries(
178
+ REACT_JSX_RUNTIMES.map(request => [
179
+ request,
180
+ {
181
+ ...(react.requiredVersion === undefined
182
+ ? {}
183
+ : { requiredVersion: react.requiredVersion }),
184
+ singleton: true,
185
+ treeShaking: false,
186
+ },
187
+ ]),
188
+ ),
189
+ );
190
+ };
191
+
192
+ /**
193
+ * Share every exported subpath of the framework packages as a singleton at
194
+ * the installed version. Entries the app configured explicitly are kept.
195
+ */
196
+ export const withFrameworkShared = <T>(
197
+ shared: T,
198
+ packages: FrameworkSharedPackage[],
199
+ ): T =>
200
+ withShared(
201
+ shared,
202
+ Object.fromEntries(
203
+ packages.flatMap(({ requests, version }) =>
204
+ requests.map(request => [
205
+ request,
206
+ { requiredVersion: version, singleton: true, treeShaking: false },
207
+ ]),
208
+ ),
209
+ ),
210
+ );
211
+
212
+ type FederationPluginOptions = {
213
+ shared?: Shared;
214
+ exposes?: unknown;
215
+ mfConfig?: unknown;
216
+ };
217
+
218
+ // `secondarySharedTreeShaking` wraps the federation config in `mfConfig`.
219
+ const federationConfig = (
220
+ options: FederationPluginOptions,
221
+ ): FederationPluginOptions =>
222
+ options.mfConfig && typeof options.mfConfig === 'object'
223
+ ? federationConfig(options.mfConfig as FederationPluginOptions)
224
+ : options;
225
+
226
+ const withDefaults = (
227
+ options: FederationPluginOptions,
228
+ packages: FrameworkSharedPackage[],
229
+ ): FederationPluginOptions =>
230
+ options.mfConfig && typeof options.mfConfig === 'object'
231
+ ? {
232
+ ...options,
233
+ mfConfig: withDefaults(
234
+ options.mfConfig as FederationPluginOptions,
235
+ packages,
236
+ ),
237
+ }
238
+ : {
239
+ ...options,
240
+ shared: withFrameworkShared(
241
+ withReactJsxRuntimeShared(options.shared),
242
+ packages,
243
+ ),
244
+ };
245
+
246
+ const hasExposes = (options: FederationPluginOptions) => {
247
+ const { exposes } = federationConfig(options);
248
+ return Array.isArray(exposes)
249
+ ? exposes.length > 0
250
+ : Boolean(exposes) && Object.keys(exposes as object).length > 0;
251
+ };
252
+
253
+ const SHARED_MODULE_TYPES = new Set([
254
+ 'consume-shared-module',
255
+ 'provide-module',
256
+ ]);
257
+
258
+ // Only normal modules carry a file resource.
259
+ const resourceOf = (module: Rspack.Module | null) =>
260
+ module && 'resource' in module && typeof module.resource === 'string'
261
+ ? module.resource
262
+ : undefined;
263
+
264
+ const describeModule = (module: Rspack.Module | null) =>
265
+ module ? (resourceOf(module) ?? module.identifier()) : 'an entry';
266
+
267
+ // The dependency type of a Module Federation container entry, the same in
268
+ // Rspack and webpack.
269
+ const CONTAINER_ENTRY_DEPENDENCY = 'container entry';
270
+
271
+ const buildsContainer = (compilation: Rspack.Compilation) =>
272
+ [...compilation.entries.values()].some(({ dependencies }) =>
273
+ dependencies.some(({ type }) => type === CONTAINER_ENTRY_DEPENDENCY),
274
+ );
275
+
276
+ /**
277
+ * Fails a federation build that exposes modules when a framework context file
278
+ * is imported other than through its shared request. That remote would run
279
+ * with its own contexts and never see the host's request state.
280
+ *
281
+ * The check runs only when the compilation really builds a container: an
282
+ * environment may drop the federation plugin after the chain was configured
283
+ * (the Cloudflare workerd SSR graph does), and a graph with no container has
284
+ * no host to share contexts with.
285
+ */
286
+ export class FederationPrivateContextsPlugin {
287
+ constructor(private readonly packages: FrameworkSharedPackage[]) {}
288
+
289
+ apply(compiler: Rspack.Compiler) {
290
+ const name = 'FederationPrivateContextsPlugin';
291
+ const definitionOf = (module: Rspack.Module | null) => {
292
+ const resource = resourceOf(module);
293
+ return resource === undefined
294
+ ? undefined
295
+ : this.packages.find(
296
+ ({ contexts, directory }) =>
297
+ contexts?.test(resource) &&
298
+ resource.startsWith(directory + path.sep),
299
+ );
300
+ };
301
+ compiler.hooks.compilation.tap(name, compilation => {
302
+ compilation.hooks.finishModules.tap(name, modules => {
303
+ if (!buildsContainer(compilation)) return;
304
+ for (const module of modules) {
305
+ const share = definitionOf(module);
306
+ if (!share) continue;
307
+ const importer = compilation.moduleGraph
308
+ .getIncomingConnections(module)
309
+ .map(connection => connection.originModule)
310
+ .find(
311
+ origin =>
312
+ !(origin && SHARED_MODULE_TYPES.has(origin.type)) &&
313
+ definitionOf(origin) !== share,
314
+ );
315
+ if (importer === undefined) continue;
316
+ compilation.errors.push(
317
+ new compiler.webpack.WebpackError(
318
+ `[ultramodern] This Module Federation build bundles a private copy of ${share.contextsRequest}: ${describeModule(importer)} imports ${describeModule(module)} directly. It would not see the host's request state. Share the "${share.prefix}" subpaths and import the contexts through "${share.contextsRequest}".`,
319
+ ),
320
+ );
321
+ }
322
+ });
323
+ });
324
+ }
325
+ }
326
+
327
+ /**
328
+ * Apply UltraModern's Module Federation share defaults to the federation
329
+ * plugins that `@module-federation/modern-js-v3` registered on the chain.
330
+ */
331
+ export const ultramodernModuleFederationSharedPlugin =
332
+ (): CliPlugin<AppTools> => ({
333
+ name: '@modern-js/ultramodern-module-federation-shared',
334
+ pre: [
335
+ '@modern-js/plugin-module-federation',
336
+ '@modern-js/plugin-module-federation-ssr',
337
+ ],
338
+ setup(api) {
339
+ api.modifyBundlerChain(chain => {
340
+ const ids = MODULE_FEDERATION_CHAIN_IDS.filter(id =>
341
+ chain.plugins.has(id),
342
+ );
343
+ if (ids.length === 0) return;
344
+ const packages = resolveFrameworkSharedPackages(
345
+ api.getAppContext().appDirectory,
346
+ );
347
+ let exposes = false;
348
+ for (const id of ids) {
349
+ const [options] = chain.plugin(id).get('args') as [
350
+ FederationPluginOptions,
351
+ ];
352
+ exposes ||= hasExposes(options);
353
+ chain
354
+ .plugin(id)
355
+ .tap(([options, ...rest]) => [
356
+ withDefaults(options, packages),
357
+ ...rest,
358
+ ]);
359
+ }
360
+ if (exposes) {
361
+ chain
362
+ .plugin('ultramodern-federation-private-contexts')
363
+ .use(FederationPrivateContextsPlugin, [packages]);
364
+ }
365
+ });
366
+ },
367
+ });
@@ -7,6 +7,7 @@ import { resolveUltramodernReleaseIdentity } from '@modern-js/app-tools-extensio
7
7
  import { findHostingModuleDirectory } from '@modern-js/app-tools-extensions/runtime-package-resolution';
8
8
  import { mergeConfig } from '@modern-js/plugin/cli';
9
9
  import { type RspackChain, rspack } from '@rsbuild/core';
10
+ import { ultramodernModuleFederationSharedPlugin } from './module-federation-shared-plugin';
10
11
  import type { AppUserConfig } from './types';
11
12
 
12
13
  const DEFAULT_OTLP_ENDPOINT = 'http://127.0.0.1:4318/v1/logs';
@@ -214,8 +215,11 @@ export const createPresetUltramodernConfig = (
214
215
  // Keep build artifacts predictable across apps.
215
216
  precompress: true,
216
217
  },
218
+ plugins: [ultramodernModuleFederationSharedPlugin()],
217
219
  server,
218
220
  source: {
221
+ // Client code only: the builder applies React Compiler to `web`
222
+ // environments and never to node/workerSSR/BFF graphs.
219
223
  reactCompiler: true,
220
224
  ...(deliveryUnit
221
225
  ? {