@mfeflow/rsbuild 0.0.0-stage → 0.1.0

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sanjeev Sharma
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,44 @@
1
- # Temporary Holding Version
1
+ # @mfeflow/rsbuild
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Rsbuild presets for Module Federation 2.0 micro-frontends. `createShellConfig` and `createRemoteConfig` return an Rsbuild config with React, the shared singletons, the manifest, generated types and the ESM Module Federation runtime already set up.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ pnpm add -D @mfeflow/rsbuild @rsbuild/core
9
+ ```
10
+
11
+ `@rsbuild/core` (^2.0.0) is a peer dependency.
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ // rsbuild.config.ts of a remote
17
+ import { createRemoteConfig } from '@mfeflow/rsbuild';
18
+
19
+ export default createRemoteConfig({
20
+ name: 'cart',
21
+ port: 4312,
22
+ exposes: { './App': './src/exposed/App.tsx' },
23
+ });
24
+ ```
25
+
26
+ ```ts
27
+ // rsbuild.config.ts of the shell
28
+ import { createShellConfig } from '@mfeflow/rsbuild';
29
+
30
+ export default createShellConfig({ name: 'storefront', port: 4300 });
31
+ ```
32
+
33
+ Merge extra settings with `mergeRsbuildConfig` from `@rsbuild/core`. `shared: ['some-lib']` adds more singletons. With `eager: false` the shell entry must only `import('./bootstrap')`, and the preset throws if it does not.
34
+
35
+ ## Module Federation sharing
36
+
37
+ Bucket: **config**. Used at build, lint or test time only, never shipped to the browser, so it is not shared through Module Federation.
38
+
39
+ ## Links
40
+
41
+ - [mfeflow repository](https://github.com/mfeflow/mfeflow) and its README
42
+ - [Documentation](https://mfeflow.github.io/mfeflow/)
43
+
44
+ MIT licensed.
@@ -0,0 +1,19 @@
1
+ import type { RsbuildPlugin } from '@rsbuild/core';
2
+ /** True for a CommonJS build inside an `@module-federation/*` package's `dist`. */
3
+ export declare function isMfCjs(file: string): boolean;
4
+ /** `value` with the first `from` replaced by `to`, taken literally (no `$&` or `$1` expansion). */
5
+ export declare function swapPath(value: string, from: string, to: string): string;
6
+ /**
7
+ * The ESM twin of a Module Federation `.cjs` file: the same name ending in
8
+ * `.js` or `.mjs` in the same directory. Throws if there is none, so a new
9
+ * Module Federation release that drops the twin fails the build instead of
10
+ * quietly shipping the CommonJS runtime again.
11
+ */
12
+ export declare function esmTwin(file: string, exists?: (f: string) => boolean): string;
13
+ /**
14
+ * Rspack resolves the Module Federation runtime with `require.resolve`, which
15
+ * bundles the CommonJS builds of sdk, runtime-core and bundler-runtime. Nothing
16
+ * in those tree-shakes (spike: shell 34.9 → 29.4 KB gzip, remote entry 13.7 →
17
+ * 7.7 KB). This plugin points them at their ESM twins.
18
+ */
19
+ export declare function pluginMfEsmRuntime(): RsbuildPlugin;
@@ -0,0 +1,103 @@
1
+ import type { RsbuildConfig } from '@rsbuild/core';
2
+ import { type SharedConfig } from './shared.js';
3
+ export { esmTwin, isMfCjs, pluginMfEsmRuntime } from './esm-runtime.js';
4
+ export { RUNTIME_CONFIG_URL, runtimeConfigEnv, runtimeConfigEnvs, runtimeConfigHandler, runtimeConfigSetup, } from './runtime-config.js';
5
+ export { DEFAULT_SINGLETONS, installedVersionFrom, packageNameOf, resolvableFrom, sharedSingletons, type SharedConfig, } from './shared.js';
6
+ export interface RemoteOptions {
7
+ /** Module Federation name, e.g. `cart`. */
8
+ name: string;
9
+ /** `{ './App': './src/exposed/App.tsx' }`. */
10
+ exposes: Readonly<Record<string, string>>;
11
+ port?: number;
12
+ /** Extra packages to share as singletons. */
13
+ shared?: readonly string[];
14
+ /** Generate types for the shell (`@mf-types.zip`). Default true. */
15
+ types?: boolean;
16
+ /**
17
+ * Set when this remote loads other remotes. By default remote code for
18
+ * consuming remotes is stripped from the build (smaller entry).
19
+ */
20
+ consumesRemotes?: boolean;
21
+ /** Swap the CommonJS Module Federation runtime for its ESM build. Default true. */
22
+ esmRuntime?: boolean;
23
+ /** App directory. Default `process.cwd()`. */
24
+ cwd?: string;
25
+ }
26
+ export interface FederationOptimization {
27
+ disableRemote?: true;
28
+ disableSnapshot?: true;
29
+ target?: 'web';
30
+ }
31
+ export interface FederationOptions {
32
+ name: string;
33
+ exposes?: Record<string, string>;
34
+ remotes?: Record<string, string>;
35
+ shared: Record<string, SharedConfig>;
36
+ manifest: boolean;
37
+ dts: false | {
38
+ generateTypes: boolean;
39
+ consumeTypes: false | {
40
+ typesOnBuild: true;
41
+ abortOnError: boolean;
42
+ };
43
+ };
44
+ runtimePlugins?: string[];
45
+ experiments: {
46
+ externalRuntime?: true;
47
+ provideExternalRuntime?: true;
48
+ optimization: FederationOptimization;
49
+ };
50
+ }
51
+ /** Module Federation options for a remote. Exported for tests and custom setups. */
52
+ export declare function remoteFederationOptions(o: RemoteOptions, canResolve: (s: string) => boolean, versionOf?: (s: string) => string | undefined): FederationOptions;
53
+ /** Rsbuild config for a remote. `rsbuild.config.ts`: `export default createRemoteConfig({ name, exposes })`. */
54
+ export declare function createRemoteConfig(o: RemoteOptions): RsbuildConfig;
55
+ export interface ShellOptions {
56
+ name: string;
57
+ port?: number;
58
+ shared?: readonly string[];
59
+ /**
60
+ * Remotes whose types the shell typechecks against, as `{ cart: 'https://…/mf-manifest.json' }`.
61
+ * Used only when `MFEFLOW_TYPES=1` is set (types-only builds); normal builds
62
+ * register remotes at runtime from the registry index.
63
+ */
64
+ typesFrom?: Readonly<Record<string, string>>;
65
+ /** More Module Federation runtime plugins (absolute paths). Must not import shared modules. */
66
+ runtimePlugins?: readonly string[];
67
+ /** Swap the CommonJS Module Federation runtime for its ESM build. Default true. */
68
+ esmRuntime?: boolean;
69
+ /**
70
+ * Ship shared singletons in the initial chunk so the shell needs no async
71
+ * bootstrap boundary. Default true. With `false` the singletons load
72
+ * asynchronously, so the entry (`src/index.ts`) must only `import('./bootstrap')`;
73
+ * a synchronous bootstrap fails at runtime with "Shared module is not available
74
+ * for eager consumption". `createShellConfig` throws when the entry has no
75
+ * dynamic `import()`.
76
+ */
77
+ eager?: boolean;
78
+ /**
79
+ * Keep the manifest/snapshot loader. Default: only in non-production builds,
80
+ * where dev overrides point at a local remote's manifest. Production shells
81
+ * register remote entry URLs and need no snapshot code, so the runtime
82
+ * refuses (and reports `MFE_NO_REMOTE_ENTRY` for) a registry entry that has
83
+ * only a manifest URL. Pass `snapshot: true` while publishing does not yet
84
+ * write `remoteEntry`.
85
+ */
86
+ snapshot?: boolean;
87
+ cwd?: string;
88
+ env?: Readonly<Record<string, string | undefined>>;
89
+ }
90
+ /** Whether the shell keeps the manifest/snapshot loader. */
91
+ export declare function snapshotEnabled(o: Pick<ShellOptions, 'snapshot' | 'env'>): boolean;
92
+ /** Module Federation options for a shell. Exported for tests and custom setups. */
93
+ export declare function shellFederationOptions(o: ShellOptions, canResolve: (s: string) => boolean, resolvePath: (s: string) => string, versionOf?: (s: string) => string | undefined): FederationOptions;
94
+ /**
95
+ * Throws when a shell built with `eager: false` has an entry that never calls
96
+ * `import()`: its shared singletons would not be ready when the entry runs.
97
+ * An entry file that does not exist is left for the bundler to report.
98
+ */
99
+ export declare function assertAsyncBoundary(entryFile: string, read?: (file: string) => string | undefined): void;
100
+ /** The `version` of the app's `package.json`, or `0.0.0` when it has none. */
101
+ export declare function appVersion(cwd: string): string;
102
+ /** Rsbuild config for the shell (host). */
103
+ export declare function createShellConfig(o: ShellOptions): RsbuildConfig;
package/dist/index.js ADDED
@@ -0,0 +1,337 @@
1
+ import node_fs from "node:fs";
2
+ import node_path from "node:path";
3
+ import { createRequire } from "node:module";
4
+ import { parse } from "es-module-lexer/js";
5
+ import { pluginReact } from "@rsbuild/plugin-react";
6
+ import { pluginModuleFederation } from "@module-federation/rsbuild-plugin";
7
+ const CJS_FILE = /\.cjs$/;
8
+ const PATH_REQUEST = /^(?:\.\.?(?:[\\/]|$)|[\\/]|[A-Za-z]:[\\/])/;
9
+ const MF_DIST = /[\\/]@module-federation[\\/][^\\/]+[\\/]dist[\\/]/;
10
+ function isMfCjs(file) {
11
+ return CJS_FILE.test(file) && MF_DIST.test(file);
12
+ }
13
+ function swapPath(value, from, to) {
14
+ return value.replace(from, ()=>to);
15
+ }
16
+ function esmTwin(file, exists = node_fs.existsSync) {
17
+ const twin = [
18
+ file.replace(/\.cjs$/, '.js'),
19
+ file.replace(/\.cjs$/, '.mjs')
20
+ ].find((f)=>exists(f));
21
+ if (void 0 === twin) throw new Error(`@mfeflow/rsbuild: no ESM build next to ${file}. Set esmRuntime: false to keep the CommonJS runtime.`);
22
+ return twin;
23
+ }
24
+ function pluginMfEsmRuntime() {
25
+ return {
26
+ name: 'mfeflow:mf-esm-runtime',
27
+ setup (api) {
28
+ api.modifyRspackConfig((config, { rspack })=>{
29
+ config.module ??= {};
30
+ config.module.rules ??= [];
31
+ config.module.rules.push({
32
+ test: CJS_FILE,
33
+ include: MF_DIST,
34
+ type: "javascript/auto"
35
+ });
36
+ config.plugins ??= [];
37
+ config.plugins.push(new rspack.NormalModuleReplacementPlugin(CJS_FILE, (data)=>{
38
+ const created = data.createData;
39
+ if (created?.resource !== void 0 && isMfCjs(created.resource)) {
40
+ const cjs = created.resource;
41
+ const esm = esmTwin(cjs);
42
+ for (const key of [
43
+ 'request',
44
+ 'userRequest',
45
+ 'resource'
46
+ ]){
47
+ const value = created[key];
48
+ if ('string' == typeof value) created[key] = swapPath(value, cjs, esm);
49
+ }
50
+ } else if (PATH_REQUEST.test(data.request)) {
51
+ const target = node_path.resolve(data.context ?? '', data.request);
52
+ if (isMfCjs(target)) data.request = esmTwin(target);
53
+ }
54
+ }));
55
+ });
56
+ }
57
+ };
58
+ }
59
+ const RUNTIME_CONFIG_URL = '/config/runtime-config.json';
60
+ const ENV_NAME = /^[a-z0-9-]+$/;
61
+ const ENV_FILE = /^runtime-config\.([a-z0-9-]+)\.json$/;
62
+ function runtimeConfigEnv(env) {
63
+ const name = void 0 === env.MFE_ENV || '' === env.MFE_ENV ? 'dev' : env.MFE_ENV;
64
+ if (!ENV_NAME.test(name)) throw new Error(`@mfeflow/rsbuild: MFE_ENV="${name}" is not an environment name (lowercase letters, digits, -)`);
65
+ return name;
66
+ }
67
+ function runtimeConfigEnvs(cwd) {
68
+ try {
69
+ return node_fs.readdirSync(node_path.join(cwd, 'public', 'config')).flatMap((name)=>ENV_FILE.exec(name)?.[1] ?? []).toSorted((a, b)=>a.localeCompare(b));
70
+ } catch {
71
+ return [];
72
+ }
73
+ }
74
+ function runtimeConfigHandler(cwd, env) {
75
+ return (req, res, next)=>{
76
+ const pathname = (req.url ?? '').split('?')[0];
77
+ if (pathname !== RUNTIME_CONFIG_URL || 'GET' !== req.method && 'HEAD' !== req.method) return void next();
78
+ const file = node_path.join(cwd, 'public', 'config', `runtime-config.${env}.json`);
79
+ let body;
80
+ try {
81
+ body = node_fs.readFileSync(file, 'utf8');
82
+ } catch {
83
+ const found = runtimeConfigEnvs(cwd).join(', ');
84
+ res.statusCode = 404;
85
+ res.setHeader('Content-Type', 'text/plain; charset=utf-8');
86
+ res.end(`MFE_ENV=${env} has no public/config/runtime-config.${env}.json (found: ${found})`);
87
+ return;
88
+ }
89
+ res.statusCode = 200;
90
+ res.setHeader('Content-Type', 'application/json; charset=utf-8');
91
+ res.setHeader('Cache-Control', 'no-store');
92
+ res.end('HEAD' === req.method ? void 0 : body);
93
+ };
94
+ }
95
+ function runtimeConfigSetup(cwd, env) {
96
+ if (0 === runtimeConfigEnvs(cwd).length) return;
97
+ const handler = runtimeConfigHandler(cwd, runtimeConfigEnv(env));
98
+ return ({ server })=>{
99
+ server.middlewares.use(handler);
100
+ };
101
+ }
102
+ const DEFAULT_SINGLETONS = [
103
+ 'react',
104
+ 'react/jsx-runtime',
105
+ 'react/jsx-dev-runtime',
106
+ 'react-dom',
107
+ 'react-dom/client',
108
+ 'react-router',
109
+ '@tanstack/react-query',
110
+ 'i18next',
111
+ 'react-i18next',
112
+ '@mfeflow/runtime',
113
+ '@mfeflow/analytics',
114
+ '@mfeflow/analytics/react',
115
+ '@mfeflow/telemetry',
116
+ '@mfeflow/storage',
117
+ '@mfeflow/storage/react',
118
+ '@mfeflow/theme',
119
+ '@mfeflow/theme/react',
120
+ '@mfeflow/i18n',
121
+ '@mfeflow/i18n/react'
122
+ ];
123
+ function packageNameOf(specifier) {
124
+ const parts = specifier.split('/');
125
+ return specifier.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0] ?? specifier;
126
+ }
127
+ function resolvableFrom(cwd) {
128
+ const require = createRequire(node_path.join(cwd, 'package.json'));
129
+ return (specifier)=>{
130
+ try {
131
+ require.resolve(specifier);
132
+ return true;
133
+ } catch {
134
+ return false;
135
+ }
136
+ };
137
+ }
138
+ function installedVersionFrom(cwd) {
139
+ const require = createRequire(node_path.join(cwd, 'package.json'));
140
+ return (specifier)=>{
141
+ const name = packageNameOf(specifier);
142
+ let file;
143
+ try {
144
+ file = require.resolve(name);
145
+ } catch {
146
+ return;
147
+ }
148
+ let dir = node_path.dirname(file);
149
+ for(;;){
150
+ const candidate = node_path.join(dir, 'package.json');
151
+ if (node_fs.existsSync(candidate)) {
152
+ const pkg = JSON.parse(node_fs.readFileSync(candidate, 'utf8'));
153
+ if ('object' == typeof pkg && null !== pkg && Reflect.get(pkg, 'name') === name) {
154
+ const version = Reflect.get(pkg, 'version');
155
+ return 'string' == typeof version ? version : void 0;
156
+ }
157
+ }
158
+ const parent = node_path.dirname(dir);
159
+ if (parent === dir) return;
160
+ dir = parent;
161
+ }
162
+ };
163
+ }
164
+ function sharedSingletons(canResolve, extra = [], versionOf = ()=>void 0, eager = false) {
165
+ const out = {};
166
+ const add = (name)=>{
167
+ const version = versionOf(name);
168
+ out[name] = {
169
+ singleton: true,
170
+ ...void 0 === version ? {} : {
171
+ requiredVersion: `^${version}`
172
+ },
173
+ ...eager ? {
174
+ eager: true
175
+ } : {}
176
+ };
177
+ };
178
+ for (const name of DEFAULT_SINGLETONS)if (canResolve(name)) add(name);
179
+ for (const name of extra){
180
+ if (!canResolve(name)) throw new Error(`@mfeflow/rsbuild: shared package "${name}" cannot be resolved; is it installed?`);
181
+ add(name);
182
+ }
183
+ return out;
184
+ }
185
+ const NAME = /^[a-z][a-z0-9_]*$/;
186
+ function checkName(name) {
187
+ if (!NAME.test(name)) throw new Error(`@mfeflow/rsbuild: "${name}" is not a valid Module Federation name (lowercase letters, digits, _)`);
188
+ }
189
+ function remoteFederationOptions(o, canResolve, versionOf) {
190
+ checkName(o.name);
191
+ if (0 === Object.keys(o.exposes).length) throw new Error(`@mfeflow/rsbuild: remote "${o.name}" exposes nothing`);
192
+ return {
193
+ name: o.name,
194
+ exposes: {
195
+ ...o.exposes
196
+ },
197
+ shared: sharedSingletons(canResolve, o.shared, versionOf),
198
+ manifest: true,
199
+ dts: false === o.types ? false : {
200
+ generateTypes: true,
201
+ consumeTypes: false
202
+ },
203
+ experiments: {
204
+ externalRuntime: true,
205
+ optimization: {
206
+ target: 'web',
207
+ ...true === o.consumesRemotes ? {} : {
208
+ disableRemote: true
209
+ }
210
+ }
211
+ }
212
+ };
213
+ }
214
+ function createRemoteConfig(o) {
215
+ const cwd = o.cwd ?? process.cwd();
216
+ return {
217
+ plugins: [
218
+ pluginReact(),
219
+ ...false === o.esmRuntime ? [] : [
220
+ pluginMfEsmRuntime()
221
+ ],
222
+ pluginModuleFederation(remoteFederationOptions(o, resolvableFrom(cwd), installedVersionFrom(cwd)))
223
+ ],
224
+ source: {
225
+ entry: {
226
+ index: './src/index.ts'
227
+ }
228
+ },
229
+ output: {
230
+ assetPrefix: 'auto'
231
+ },
232
+ server: void 0 === o.port ? {} : {
233
+ port: o.port
234
+ }
235
+ };
236
+ }
237
+ function snapshotEnabled(o) {
238
+ return o.snapshot ?? 'production' !== (o.env ?? process.env).NODE_ENV;
239
+ }
240
+ function shellFederationOptions(o, canResolve, resolvePath, versionOf) {
241
+ checkName(o.name);
242
+ const env = o.env ?? process.env;
243
+ const typesBuild = '1' === env.MFEFLOW_TYPES && void 0 !== o.typesFrom;
244
+ const snapshot = snapshotEnabled(o);
245
+ const remotes = {};
246
+ if (typesBuild) for (const [remote, url] of Object.entries(o.typesFrom ?? {})){
247
+ checkName(remote);
248
+ remotes[remote] = `${remote}@${url}`;
249
+ }
250
+ return {
251
+ name: o.name,
252
+ remotes,
253
+ shared: sharedSingletons(canResolve, o.shared, versionOf, false !== o.eager),
254
+ manifest: true,
255
+ dts: typesBuild ? {
256
+ generateTypes: false,
257
+ consumeTypes: {
258
+ typesOnBuild: true,
259
+ abortOnError: true
260
+ }
261
+ } : false,
262
+ runtimePlugins: [
263
+ ...canResolve('@mfeflow/runtime/plugins/report') ? [
264
+ resolvePath('@mfeflow/runtime/plugins/report')
265
+ ] : [],
266
+ ...canResolve('@mfeflow/runtime/plugins/entry-retry') ? [
267
+ resolvePath('@mfeflow/runtime/plugins/entry-retry')
268
+ ] : [],
269
+ ...o.runtimePlugins ?? []
270
+ ],
271
+ experiments: {
272
+ provideExternalRuntime: true,
273
+ optimization: {
274
+ target: 'web',
275
+ ...snapshot ? {} : {
276
+ disableSnapshot: true
277
+ }
278
+ }
279
+ }
280
+ };
281
+ }
282
+ const RELATIVE_SPECIFIER = /^\.\.?\//;
283
+ function importsRelativeModule(source) {
284
+ try {
285
+ return parse(source)[0].some((i)=>'dynamic' === i.type && !i.glob && !i.probablyTypeOnly && void 0 !== i.specifier && RELATIVE_SPECIFIER.test(i.specifier));
286
+ } catch {
287
+ return true;
288
+ }
289
+ }
290
+ function assertAsyncBoundary(entryFile, read = (f)=>node_fs.existsSync(f) ? node_fs.readFileSync(f, 'utf8') : void 0) {
291
+ const source = read(entryFile);
292
+ if (void 0 === source || importsRelativeModule(source)) return;
293
+ throw new Error(`@mfeflow/rsbuild: eager: false needs an async bootstrap boundary, but ${entryFile} has no import() of a relative module. Make it \`import('./bootstrap')\` (shared singletons load asynchronously, so a synchronous bootstrap fails with "Shared module is not available for eager consumption"), or drop eager: false.`);
294
+ }
295
+ function appVersion(cwd) {
296
+ try {
297
+ const pkg = JSON.parse(node_fs.readFileSync(node_path.join(cwd, 'package.json'), 'utf8'));
298
+ const version = 'object' == typeof pkg && null !== pkg ? Reflect.get(pkg, 'version') : void 0;
299
+ return 'string' == typeof version ? version : '0.0.0';
300
+ } catch {
301
+ return '0.0.0';
302
+ }
303
+ }
304
+ function createShellConfig(o) {
305
+ const cwd = o.cwd ?? process.cwd();
306
+ const require = createRequire(node_path.join(cwd, 'package.json'));
307
+ if (false === o.eager) assertAsyncBoundary(node_path.join(cwd, 'src/index.ts'));
308
+ const setup = runtimeConfigSetup(cwd, o.env ?? process.env);
309
+ return {
310
+ plugins: [
311
+ pluginReact(),
312
+ ...false === o.esmRuntime ? [] : [
313
+ pluginMfEsmRuntime()
314
+ ],
315
+ pluginModuleFederation(shellFederationOptions(o, resolvableFrom(cwd), (s)=>require.resolve(s)))
316
+ ],
317
+ source: {
318
+ entry: {
319
+ index: './src/index.ts'
320
+ },
321
+ define: {
322
+ __MFEFLOW_SNAPSHOT__: JSON.stringify(snapshotEnabled(o)),
323
+ __MFEFLOW_APP_VERSION__: JSON.stringify(appVersion(cwd))
324
+ }
325
+ },
326
+ server: {
327
+ historyApiFallback: true,
328
+ ...void 0 === o.port ? {} : {
329
+ port: o.port
330
+ },
331
+ ...void 0 === setup ? {} : {
332
+ setup
333
+ }
334
+ }
335
+ };
336
+ }
337
+ export { DEFAULT_SINGLETONS, RUNTIME_CONFIG_URL, appVersion, assertAsyncBoundary, createRemoteConfig, createShellConfig, esmTwin, installedVersionFrom, isMfCjs, packageNameOf, pluginMfEsmRuntime, remoteFederationOptions, resolvableFrom, runtimeConfigEnv, runtimeConfigEnvs, runtimeConfigHandler, runtimeConfigSetup, sharedSingletons, shellFederationOptions, snapshotEnabled };
@@ -0,0 +1,22 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import type { RsbuildConfig } from '@rsbuild/core';
3
+ type ServerSetup = NonNullable<NonNullable<RsbuildConfig['server']>['setup']>;
4
+ type ServerSetupFn = Extract<ServerSetup, (...args: never[]) => unknown>;
5
+ /** Where the shell fetches its runtime config from. */
6
+ export declare const RUNTIME_CONFIG_URL = "/config/runtime-config.json";
7
+ /** The environment whose runtime config the dev and preview servers answer with: `MFE_ENV`, default `dev`. */
8
+ export declare function runtimeConfigEnv(env: Readonly<Record<string, string | undefined>>): string;
9
+ /** The `<env>` of every `public/config/runtime-config.<env>.json`, sorted. */
10
+ export declare function runtimeConfigEnvs(cwd: string): string[];
11
+ /**
12
+ * Answers `/config/runtime-config.json` with `runtime-config.<env>.json`, read on every request so an
13
+ * edit shows on reload. A missing file for the chosen env is a 404 that says which envs exist.
14
+ */
15
+ export declare function runtimeConfigHandler(cwd: string, env: string): (req: IncomingMessage, res: ServerResponse, next: () => void) => void;
16
+ /**
17
+ * The `server.setup` for a shell with `public/config/runtime-config.<env>.json` files, or undefined
18
+ * without them. It serves the chosen env's file at `/config/runtime-config.json` in the dev and
19
+ * preview servers; a deployed build gets the same file copied there by the deploy step.
20
+ */
21
+ export declare function runtimeConfigSetup(cwd: string, env: Readonly<Record<string, string | undefined>>): ServerSetupFn | undefined;
22
+ export {};
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Packages every mfeflow app shares as singletons, when the app depends on
3
+ * them. Subpaths are listed on purpose: Module Federation shares each import
4
+ * specifier separately, and `react-dom/client` bypassing the share was a real
5
+ * React 19 bug (module-federation/core#4727).
6
+ */
7
+ export declare const DEFAULT_SINGLETONS: readonly string[];
8
+ export interface SharedConfig {
9
+ singleton: true;
10
+ /** `^<installed version>`. Set explicitly because Module Federation otherwise reads the raw spec from package.json, which is `catalog:` or `workspace:*` in pnpm workspaces. */
11
+ requiredVersion?: string;
12
+ /** Ships the package in the initial chunk instead of an async one (shell only). */
13
+ eager?: true;
14
+ }
15
+ /** `react-dom/client` → `react-dom`, `@mfeflow/theme/react` → `@mfeflow/theme`. */
16
+ export declare function packageNameOf(specifier: string): string;
17
+ /** Can `specifier` be resolved from the app in `cwd`? */
18
+ export declare function resolvableFrom(cwd: string): (specifier: string) => boolean;
19
+ /** Installed version of the package behind `specifier`, found by walking up from its entry file. */
20
+ export declare function installedVersionFrom(cwd: string): (specifier: string) => string | undefined;
21
+ /**
22
+ * The `shared` map for an app: the default singletons it can resolve, plus
23
+ * `extra`. Unresolvable defaults are skipped so a remote without i18n does not
24
+ * fail to build; unresolvable extras throw, because they are a typo.
25
+ */
26
+ export declare function sharedSingletons(canResolve: (specifier: string) => boolean, extra?: readonly string[], versionOf?: (specifier: string) => string | undefined, eager?: boolean): Record<string, SharedConfig>;
package/package.json CHANGED
@@ -1,6 +1,67 @@
1
1
  {
2
2
  "name": "@mfeflow/rsbuild",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Rsbuild presets for Module Federation 2.0 micro-frontends: createShellConfig and createRemoteConfig with shared singletons, external runtime, manifests, types and error reporting set up.",
5
+ "keywords": [
6
+ "micro-frontend",
7
+ "mfe",
8
+ "module-federation",
9
+ "module-federation-2",
10
+ "rsbuild",
11
+ "rspack",
12
+ "preset",
13
+ "react"
14
+ ],
15
+ "homepage": "https://mfeflow.github.io/mfeflow/",
16
+ "bugs": "https://github.com/mfeflow/mfeflow/issues",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/mfeflow/mfeflow.git",
20
+ "directory": "packages/rsbuild"
21
+ },
22
+ "license": "MIT",
23
+ "author": "Sanjeev Sharma <speedsharma.ai@gmail.com>",
24
+ "type": "module",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "import": "./dist/index.js",
29
+ "default": "./dist/index.js"
30
+ }
31
+ },
32
+ "files": [
33
+ "dist"
34
+ ],
35
+ "engines": {
36
+ "node": ">=22.12"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public",
40
+ "provenance": true
41
+ },
42
+ "dependencies": {
43
+ "@module-federation/rsbuild-plugin": "2.9.2",
44
+ "@rsbuild/plugin-react": "2.1.1",
45
+ "es-module-lexer": "3.0.4"
46
+ },
47
+ "peerDependencies": {
48
+ "@rsbuild/core": "^2.0.0"
49
+ },
50
+ "devDependencies": {
51
+ "@rsbuild/core": "2.3.0",
52
+ "@rslib/core": "1.0.5",
53
+ "@types/node": "26.6.6",
54
+ "@types/react": "19.3.0",
55
+ "@types/react-dom": "19.3.0",
56
+ "react": "19.3.1",
57
+ "react-dom": "19.3.1",
58
+ "typescript": "7.0.2"
59
+ },
60
+ "mfeflow": {
61
+ "bucket": "config"
62
+ },
63
+ "scripts": {
64
+ "build": "rslib build",
65
+ "typecheck": "tsc --noEmit"
66
+ }
6
67
  }