Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
7c888a0
fix(manifest,managers): support Rspack layered shared metadata
ScriptedAlchemy Sep 8, 2026
9da692c
fix(manifest): follow the shared-identifier layout webpack uses for l…
ScriptedAlchemy Sep 8, 2026
b5fd62f
fix(manifest): preserve provider and expose metadata across readers
ScriptedAlchemy Sep 8, 2026
77c802b
test(manifest): verify layers with rspack preview
ScriptedAlchemy Sep 9, 2026
404ce94
test(manifest): load new remotes from a published legacy host
ScriptedAlchemy Sep 11, 2026
15b864a
test(manifest): verify installed compatibility preview provenance
ScriptedAlchemy Sep 11, 2026
a5aa440
test(manifest): use corrected layers preview
ScriptedAlchemy Sep 11, 2026
a126115
refactor(manifest): separate provider metadata from layer support
ScriptedAlchemy Sep 14, 2026
7ada742
refactor(manifest): remove public shared identity metadata
ScriptedAlchemy Sep 14, 2026
a6c1aa7
fix(managers): preserve shorthand expose import arrays
ScriptedAlchemy Sep 14, 2026
884372f
fix(enhanced): match resolved shared requests by issuer layer
ScriptedAlchemy Sep 14, 2026
ffb917f
fix(manifest): read webpack shared asset layer identities
ScriptedAlchemy Sep 14, 2026
d5bacac
Merge branch 'main' into feat/rspack-layered-shared-metadata
ScriptedAlchemy Sep 14, 2026
b23ccee
feat(enhanced): align exposed and shared layer configuration
ScriptedAlchemy Sep 14, 2026
2def209
Merge commit 'd5bacac797e70437d0bd809215ab68197095f668' into codex/la…
ScriptedAlchemy Sep 14, 2026
d820029
fix(deps): keep the branch lockfile resolution after merging main
ScriptedAlchemy Sep 14, 2026
b69c99f
test(manifest): refresh layered Rspack preview provenance
ScriptedAlchemy Sep 14, 2026
00cb74b
fix(enhanced): resolve layered consumes from issuer context
ScriptedAlchemy Sep 14, 2026
41e53f4
refactor(enhanced): remove distinct empty-layer support
ScriptedAlchemy Sep 14, 2026
5c429da
fix(enhanced): require nonempty expose layers
ScriptedAlchemy Sep 14, 2026
d410bef
test(enhanced): reuse expose fixtures and cache harness
ScriptedAlchemy Sep 14, 2026
b830b22
refactor(manifest): collect enhanced webpack metadata from graph
ScriptedAlchemy Sep 15, 2026
99df24c
chore: merge manifest graph foundation into layer support
ScriptedAlchemy Sep 15, 2026
56e3d2c
test(manifest): remove unused expose map mock
ScriptedAlchemy Sep 15, 2026
4ed63e3
test(manifest): merge graph compiler test isolation fix
ScriptedAlchemy Sep 15, 2026
fb4a96f
fix(runtime): isolate shared layer variants
ScriptedAlchemy Sep 30, 2026
192c6b5
Merge commit 'b99a1c38c81e835ea2bf41fea6a5aed8aca4ef5c' into feat/rsp…
ScriptedAlchemy Sep 30, 2026
d408599
test: preserve legacy host and layered compatibility coverage
ScriptedAlchemy Sep 30, 2026
63d29b5
fix(runtime): accept legacy shared entries without shareConfig
ScriptedAlchemy Sep 30, 2026
0f24871
docs(enhanced): document expose layers
ScriptedAlchemy Sep 30, 2026
84a671f
test(manifest): pair final compiler preview with current runtime
ScriptedAlchemy Sep 30, 2026
e7d30e9
test(manifest): verify native previews through resolved package URLs
ScriptedAlchemy Sep 30, 2026
78eaaa3
test(manifest): keep graph fixtures inside checkout
ScriptedAlchemy Sep 30, 2026
8585d13
chore(manifest): refresh published preview checksums
ScriptedAlchemy Sep 30, 2026
de9bac0
chore: merge manifest rollback follow-up
ScriptedAlchemy Sep 30, 2026
5882900
chore(enhanced): merge manifest foundation and preserve layer resolution
ScriptedAlchemy Oct 9, 2026
71e1cb6
test(runtime): cover unlayered snapshot fallback identity
ScriptedAlchemy Oct 20, 2026
ad0b69f
test(runtime): execute snapshot fallback getters
ScriptedAlchemy Oct 22, 2026
531536c
ci: pin Playwright jobs to Ubuntu 24.04
ScriptedAlchemy Oct 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/expose-import-array-normalization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@module-federation/managers': patch
---

Preserve shorthand expose import arrays when normalizing manifest-enabled federation configuration.
7 changes: 7 additions & 0 deletions .changeset/layered-shared-runtime-isolation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@module-federation/runtime-core': patch
'@module-federation/sdk': patch
'@module-federation/webpack-bundler-runtime': patch
---

Preserve same-scope, same-version shared layer variants during runtime registration, selection, remote replacement, and snapshot asset matching. Keep unlayered share-scope entries compatible with older runtimes; layered requests prefer an exact layer and use an unlayered provider only when the requested layer is absent.
5 changes: 5 additions & 0 deletions .changeset/resolved-shared-layer-selection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@module-federation/enhanced': patch
---

Match relative and absolute shared requests by issuer layer so configurations for the same resolved source do not overwrite each other.
12 changes: 12 additions & 0 deletions .changeset/rspack-layered-shared-metadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@module-federation/sdk': patch
'@module-federation/managers': patch
'@module-federation/manifest': patch
'@module-federation/enhanced': patch
---

Preserve shared and exposed module layers when reading Rspack and webpack stats or regenerating manifests. Keep public expose aliases, resolved imports, requirements, and chunk assets associated, including multi-import exposes and disabled asset analysis.

Support named expose layers in enhanced Webpack. Preserve module-rule precedence and assets for merged expose chunks, and accept the same layer configuration with manifests enabled or disabled.

Resolve relative shared requests and fallbacks with an explicit issuer layer from the importing module's directory.
2 changes: 1 addition & 1 deletion .github/workflows/devtools.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ concurrency:

jobs:
main:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- name: Checkout Repository
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/e2e-rstest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:

jobs:
e2e-rstest:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- name: Checkout Repository
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/e2e-treeshake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:

jobs:
e2e-treeshake:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
permissions:
contents: read
actions: read
Expand Down
12 changes: 12 additions & 0 deletions apps/website-new/docs/en/configure/exposes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ interface PluginExposesOptions {
interface ExposesConfig {
// File entry
import: string;
// Layer in which to build the exposed module
layer?: string;
}
```

Expand All @@ -42,3 +44,13 @@ module.exports = {
],
};
```

Use `layer` in the object form to build an exposed module in a named layer:

```js
exposes: {
'./Server': { import: './src/Server.ts', layer: 'server' },
}
```

Layer names must be nonempty. With Webpack, enable `experiments.layers`. Omitting `layer` preserves the bundler's existing layer behavior; string exposes continue to work unchanged.
12 changes: 12 additions & 0 deletions apps/website-new/docs/zh/configure/exposes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ interface PluginExposesOptions {
interface ExposesConfig {
// 文件入口
import: string;
// 构建暴露模块时使用的层
layer?: string;
}
```

Expand All @@ -43,3 +45,13 @@ module.exports = {
],
};
```

在对象形式中使用 `layer`,可以在指定的层中构建暴露模块:

```js
exposes: {
'./Server': { import: './src/Server.ts', layer: 'server' },
}
```

层名不能为空字符串。使用 Webpack 时,需要启用 `experiments.layers`。省略 `layer` 会保留构建工具现有的层行为;字符串形式的 exposes 配置继续保持原有行为。
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export type ExposeOptions = {
* custom chunk name for the exposed module
*/
name: string;
layer?: string;
};
declare class ContainerEntryModule extends Module {
private _name;
Expand Down
7 changes: 6 additions & 1 deletion packages/enhanced/src/lib/container/ContainerEntryModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export type ExposeOptions = {
* custom chunk name for the exposed module
*/
name: string;
layer?: string;
};

class ContainerEntryModule extends Module {
Expand Down Expand Up @@ -169,7 +170,11 @@ class ContainerEntryModule extends Module {
);
let idx = 0;
for (const request of options.import) {
const dep = new ContainerExposedDependency(name, request);
const dep = new ContainerExposedDependency(
name,
request,
options.layer,
);
dep.loc = {
name,
index: idx++,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import type {
} from 'webpack/lib/dependencies/ModuleDependency';
declare class ContainerExposedDependency extends dependencies.ModuleDependency {
exposedName: string;
layer?: string;
request: string;
/**
* @param {string} exposedName public name
* @param {string} request request to module
*/
constructor(exposedName: string, request: string);
constructor(exposedName: string, request: string, layer?: string);
get type(): string;
get category(): string;
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,17 @@ import type {

class ContainerExposedDependency extends dependencies.ModuleDependency {
exposedName: string;
layer?: string;
override request: string;

/**
* @param {string} exposedName public name
* @param {string} request request to module
*/
constructor(exposedName: string, request: string) {
constructor(exposedName: string, request: string, layer?: string) {
super(request);
this.exposedName = exposedName;
this.layer = layer;
this.request = request;
}

Expand All @@ -48,6 +50,7 @@ class ContainerExposedDependency extends dependencies.ModuleDependency {
*/
override serialize(context: ObjectSerializerContext): void {
context.write(this.exposedName);
context.write(this.layer);
super.serialize(context);
}

Expand All @@ -56,6 +59,7 @@ class ContainerExposedDependency extends dependencies.ModuleDependency {
*/
override deserialize(context: ObjectDeserializerContext): void {
this.exposedName = context.read();
this.layer = context.read();
super.deserialize(context);
}
}
Expand Down
18 changes: 14 additions & 4 deletions packages/enhanced/src/lib/container/ContainerPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class ContainerPlugin {
(item) => ({
import: Array.isArray(item.import) ? item.import : [item.import],
name: item.name || undefined,
...(item.layer !== undefined ? { layer: item.layer } : {}),
}),
),
runtimePlugins: options.runtimePlugins,
Expand Down Expand Up @@ -297,10 +298,19 @@ class ContainerPlugin {
new ContainerEntryModuleFactory(),
);

compilation.dependencyFactories.set(
ContainerExposedDependency,
normalModuleFactory,
);
compilation.dependencyFactories.set(ContainerExposedDependency, {
create(data, callback) {
const dependency = data
.dependencies[0] as ContainerExposedDependency;
if (dependency.layer !== undefined) {
data.contextInfo = {
...data.contextInfo,
issuerLayer: dependency.layer,
};
}
normalModuleFactory.create(data, callback);
},
});

if (!compilation.dependencyFactories.has(EntryDependency)) {
compilation.dependencyFactories.set(
Expand Down
14 changes: 11 additions & 3 deletions packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,9 @@ class ConsumeSharedPlugin {
compilation.warnings.push(error);
};
const directFallback =
config.import && DIRECT_FALLBACK_REGEX.test(config.import);
config.import &&
DIRECT_FALLBACK_REGEX.test(config.import) &&
(!config.issuerLayer || ABSOLUTE_PATH_REGEX.test(config.import));
const packageName =
config.packageName ??
(ABSOLUTE_PATH_REGEX.test(request)
Expand Down Expand Up @@ -631,7 +633,7 @@ class ConsumeSharedPlugin {
);
normalModuleFactory.hooks.createModule.tapPromise(
PLUGIN_NAME,
({ resource }, { context, dependencies }) => {
({ resource }, { context, dependencies, contextInfo }) => {
// BIND `this` for createConsumeSharedModule call
const boundCreateConsumeSharedModule =
this.createConsumeSharedModule.bind(this);
Expand All @@ -642,7 +644,13 @@ class ConsumeSharedPlugin {
return Promise.resolve();
}
if (resource) {
const options = resolvedConsumes.get(resource);
const options =
resolvedConsumes.get(
createLookupKeyForSharing(resource, contextInfo.issuerLayer),
) ||
resolvedConsumes.get(
createLookupKeyForSharing(resource, undefined),
);
if (options !== undefined) {
// Use the bound function
return boundCreateConsumeSharedModule(
Expand Down
13 changes: 10 additions & 3 deletions packages/enhanced/src/lib/sharing/resolveMatchedConfigs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ export async function resolveMatchedConfigs<T extends ConsumeOptions>(
await Promise.all(
configs.map(([request, config]) => {
const resolveRequest = config.request || request;
if (RELATIVE_REQUEST_REGEX.test(resolveRequest)) {
if (config.issuerLayer && RELATIVE_REQUEST_REGEX.test(resolveRequest)) {
// A layered relative consume is resolved where its issuer imports it.
unresolved.set(createCompositeKey(resolveRequest, config), config);
return undefined;
} else if (RELATIVE_REQUEST_REGEX.test(resolveRequest)) {
// relative request
return new Promise<void>((resolve) => {
resolver.resolve(
Expand All @@ -74,14 +78,17 @@ export async function resolveMatchedConfigs<T extends ConsumeOptions>(
);
return resolve();
}
resolved.set(result as string, config);
resolved.set(
createCompositeKey(result as string, config),
config,
);
resolve();
},
);
});
} else if (ABSOLUTE_PATH_REGEX.test(resolveRequest)) {
// absolute path
resolved.set(resolveRequest, config);
resolved.set(createCompositeKey(resolveRequest, config), config);
return undefined;
} else if (resolveRequest.endsWith('/')) {
// module request prefix
Expand Down
16 changes: 14 additions & 2 deletions packages/enhanced/src/schemas/container/ContainerPlugin.check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function n(
{
const s = l;
for (const t in r)
if ('import' !== t && 'name' !== t)
if ('import' !== t && 'name' !== t && 'layer' !== t)
return ((n.errors = [{ params: { additionalProperty: t } }]), !1);
if (s === l) {
if (void 0 !== r.import) {
Expand Down Expand Up @@ -98,13 +98,25 @@ function n(
((l = u), null !== i && (u ? (i.length = u) : (i = null)));
var f = a === l;
} else f = !0;
if (f)
if (f) {
if (void 0 !== r.name) {
const t = l;
if ('string' != typeof r.name)
return ((n.errors = [{ params: { type: 'string' } }]), !1);
f = t === l;
} else f = !0;
if (f)
if (void 0 !== r.layer) {
let t = r.layer;
const e = l;
if (l === e) {
if ('string' != typeof t)
return ((n.errors = [{ params: { type: 'string' } }]), !1);
if (t.length < 1) return ((n.errors = [{ params: {} }]), !1);
}
f = e === l;
} else f = !0;
}
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions packages/enhanced/src/schemas/container/ContainerPlugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@
"name": {
"description": "Custom chunk name for the exposed module.",
"type": "string"
},
"layer": {
"description": "Layer in which to build the exposed module.",
"type": "string",
"minLength": 1
}
},
"required": ["import"]
Expand Down
5 changes: 5 additions & 0 deletions packages/enhanced/src/schemas/container/ContainerPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ export default {
description: 'Custom chunk name for the exposed module.',
type: 'string',
},
layer: {
description: 'Layer in which to build the exposed module.',
type: 'string',
minLength: 1,
},
},
required: ['import'],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const t = {
],
},
name: { type: 'string' },
layer: { type: 'string', minLength: 1 },
},
required: ['import'],
},
Expand Down Expand Up @@ -607,7 +608,7 @@ function i(
{
const r = l;
for (const t in e)
if ('import' !== t && 'name' !== t)
if ('import' !== t && 'name' !== t && 'layer' !== t)
return ((i.errors = [{ params: { additionalProperty: t } }]), !1);
if (r === l) {
if (void 0 !== e.import) {
Expand Down Expand Up @@ -652,13 +653,25 @@ function i(
((l = u), null !== a && (u ? (a.length = u) : (a = null)));
var f = n === l;
} else f = !0;
if (f)
if (f) {
if (void 0 !== e.name) {
const t = l;
if ('string' != typeof e.name)
return ((i.errors = [{ params: { type: 'string' } }]), !1);
f = t === l;
} else f = !0;
if (f)
if (void 0 !== e.layer) {
let t = e.layer;
const r = l;
if (l === r) {
if ('string' != typeof t)
return ((i.errors = [{ params: { type: 'string' } }]), !1);
if (t.length < 1) return ((i.errors = [{ params: {} }]), !1);
}
f = r === l;
} else f = !0;
}
}
}
}
Expand Down
Loading
Loading