Skip to content

Commit 2dc9c6c

Browse files
committed
fix(mf): resolve version from parent package for secondary entry points
When a secondary entry point (e.g. `@mui/material/styles`) has its own `package.json` without a `version` field, ProvideSharedPlugin now walks up the directory tree to find the parent package's version instead of emitting a warning. This matches the behavior expected for packages that use the secondary entry point pattern (MUI, Emotion, Apollo Client, etc). The fix validates the relationship by checking that the shared key exactly equals `<parent_name>/<relative_path>` and stops at the `node_modules` boundary. Fixes: webpack/webpack#15864 Ref: webpack/webpack#13457 Made-with: Cursor
1 parent f15f7f4 commit 2dc9c6c

File tree

7 files changed

+86
-5
lines changed

7 files changed

+86
-5
lines changed

crates/rspack_plugin_mf/src/sharing/provide_shared_plugin.rs

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{
22
fmt,
3+
path::Path,
34
sync::{Arc, LazyLock},
45
};
56

@@ -101,6 +102,40 @@ impl ProvideSharedPlugin {
101102
)
102103
}
103104

105+
/// For secondary entry points (e.g. `@mui/material/styles`) whose own
106+
/// `package.json` has no `version`, walk up to the parent package and use
107+
/// its version — but only when the shared key matches
108+
/// `<parent_name>/<relative_path>`.
109+
fn find_parent_package_version(description_path: &Path, key: &str) -> Option<String> {
110+
let entry_dir = description_path.parent()?;
111+
let mut search_dir = entry_dir.parent();
112+
113+
while let Some(dir) = search_dir {
114+
if dir.file_name().is_some_and(|n| n == "node_modules") {
115+
break;
116+
}
117+
118+
let parent_pkg = dir.join("package.json");
119+
if parent_pkg.exists() {
120+
if let Ok(data) = std::fs::read(&parent_pkg)
121+
&& let Ok(parent) = serde_json::from_slice::<serde_json::Value>(&data)
122+
&& let Some(parent_name) = parent.get("name").and_then(|n| n.as_str())
123+
&& let Some(parent_version) = parent.get("version").and_then(|v| v.as_str())
124+
{
125+
if let Ok(rel) = entry_dir.strip_prefix(dir) {
126+
let expected_key = format!("{parent_name}/{}", rel.to_string_lossy());
127+
if key == expected_key {
128+
return Some(parent_version.to_string());
129+
}
130+
}
131+
}
132+
}
133+
search_dir = dir.parent();
134+
}
135+
136+
None
137+
}
138+
104139
#[allow(clippy::too_many_arguments)]
105140
pub async fn provide_shared_module(
106141
&self,
@@ -134,16 +169,21 @@ impl ProvideSharedPlugin {
134169
},
135170
);
136171
} else if let Some(description) = resource_data.description() {
137-
if let Some(description) = description.json().as_object()
138-
&& let Some(version) = description.get("version")
139-
&& let Some(version) = version.as_str()
140-
{
172+
let version = description
173+
.json()
174+
.as_object()
175+
.and_then(|d| d.get("version"))
176+
.and_then(|v| v.as_str())
177+
.map(|v| v.to_string())
178+
.or_else(|| Self::find_parent_package_version(description.path(), key));
179+
180+
if let Some(version) = version {
141181
self.resolved_provide_map.write().await.insert(
142182
resource.to_string(),
143183
VersionedProvideOptions {
144184
share_key: share_key.to_string(),
145185
share_scope: share_scope.clone(),
146-
version: ProvideVersion::Version(version.to_string()),
186+
version: ProvideVersion::Version(version),
147187
eager,
148188
singleton,
149189
strict_version,
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
it("should provide a secondary entry point using the parent package version", async () => {
2+
const styles = await import("@scope/pkg/styles");
3+
expect(styles).toEqual(
4+
expect.objectContaining({
5+
default: "pkg-styles"
6+
})
7+
);
8+
});
9+
10+
it("should provide the root package normally", async () => {
11+
const pkg = await import("@scope/pkg");
12+
expect(pkg).toEqual(
13+
expect.objectContaining({
14+
default: "pkg-root"
15+
})
16+
);
17+
});

tests/rspack-test/configCases/sharing/share-plugin-secondary-entry-point/node_modules/@scope/pkg/index.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/rspack-test/configCases/sharing/share-plugin-secondary-entry-point/node_modules/@scope/pkg/package.json

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/rspack-test/configCases/sharing/share-plugin-secondary-entry-point/node_modules/@scope/pkg/styles/index.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/rspack-test/configCases/sharing/share-plugin-secondary-entry-point/node_modules/@scope/pkg/styles/package.json

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const { SharePlugin } = require("@rspack/core").sharing;
2+
3+
/** @type {import("@rspack/core").Configuration} */
4+
module.exports = {
5+
mode: "development",
6+
devtool: false,
7+
plugins: [
8+
new SharePlugin({
9+
shared: {
10+
"@scope/pkg": {},
11+
"@scope/pkg/styles": {}
12+
}
13+
})
14+
]
15+
};

0 commit comments

Comments
 (0)