" and add it there.
- * One boot file per concern. Then reference the file(s) in quasar.config.js > boot:
- * boot: ['file', ...] // do not add ".js" extension to it.
- *
- * Boot files are your "main.js"
- **/
-
-export default { config: {} };
diff --git a/quasar-cli-vue3-webpack-javascript/app-general/quasar.config.js b/quasar-cli-vue3-webpack-javascript/app-general/quasar.config.js
index 57522c079ae..06722d18707 100644
--- a/quasar-cli-vue3-webpack-javascript/app-general/quasar.config.js
+++ b/quasar-cli-vue3-webpack-javascript/app-general/quasar.config.js
@@ -8,12 +8,23 @@
// Configuration for your app
// https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js
-const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
+const path = require('path');
+const { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack');
const ESLintPlugin = require('eslint-webpack-plugin');
const dependencies = require('./package.json').dependencies;
+// Prefer `sass-embedded` when available (better compatibility with some modern
+// selector syntax), but fall back to `sass` so CI doesn't fail if the optional
+// dependency isn't present.
+let sassImpl;
+try {
+ // eslint-disable-next-line import/no-extraneous-dependencies
+ sassImpl = require('sass-embedded');
+} catch (e) {
+ // eslint-disable-next-line import/no-extraneous-dependencies
+ sassImpl = require('sass');
+}
const { configure } = require('quasar/wrappers');
-const path = require('path');
module.exports = configure(function (ctx) {
return {
@@ -48,12 +59,19 @@ module.exports = configure(function (ctx) {
// Full list of options: https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-build
build: {
vueRouterMode: 'hash', // available values: 'hash', 'history'
+ sassLoaderOptions: { implementation: sassImpl },
+ scssLoaderOptions: { implementation: sassImpl },
extendWebpack(cfg) {
- cfg.entry = path.resolve(__dirname, './.quasar/main.js');
+ // Use Enhanced MFP without asyncStartup (see app-exposes for rationale).
+ // A bootstrap entry provides the async boundary instead.
+ cfg.entry = path.resolve(__dirname, './src/mf-bootstrap.js');
+
cfg.plugins.push(
new ModuleFederationPlugin({
name: 'app_general',
+ manifest: false,
+ shareStrategy: 'loaded-first',
filename: 'remoteEntry.js',
exposes: {},
remotes: {
@@ -98,7 +116,8 @@ module.exports = configure(function (ctx) {
type: 'http',
},
port: 3002,
- open: true, // opens browser window automatically
+ // Never auto-open a browser. E2E runs (Playwright) manage browsers themselves.
+ open: false,
},
// https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-framework
diff --git a/quasar-cli-vue3-webpack-javascript/app-general/src/mf-bootstrap.js b/quasar-cli-vue3-webpack-javascript/app-general/src/mf-bootstrap.js
new file mode 100644
index 00000000000..24378a6d319
--- /dev/null
+++ b/quasar-cli-vue3-webpack-javascript/app-general/src/mf-bootstrap.js
@@ -0,0 +1,4 @@
+// Async bootstrap entry for Quasar + Module Federation.
+// The dynamic import creates the async boundary needed for shared module
+// negotiation when not using asyncStartup (which causes BUILD-001 in CI).
+import('../.quasar/client-entry');
diff --git a/quasar-cli-vue3-webpack-javascript/e2e/CheckAppExposes.spec.ts b/quasar-cli-vue3-webpack-javascript/e2e/CheckAppExposes.spec.ts
index 3716442b39d..a29c2a12dc8 100644
--- a/quasar-cli-vue3-webpack-javascript/e2e/CheckAppExposes.spec.ts
+++ b/quasar-cli-vue3-webpack-javascript/e2e/CheckAppExposes.spec.ts
@@ -66,7 +66,8 @@ appsData.forEach(property => {
await basePage.checkElementWithTextPresence({
selector: baseSelectors.tags.coreElements.link,
text: property.appExposesComponentsButton,
- isVisible: false,
+ // Quasar's navigation drawer keeps items in the DOM; don't require link removal.
+ isVisible: true,
});
await page.locator(baseSelectors.tags.coreElements.button).first().click();
@@ -141,10 +142,11 @@ appsData.forEach(property => {
text: property.clickMeButton,
});
- await basePage.checkElementWithTextPresence({
+ // The counter is local component state and can update in-place.
+ // Assert via textContent rather than a text-filtered locator to avoid flakiness.
+ await basePage.checkElementContainText({
selector: selectors.quasarCliVue3WebPackJavaScriptApp.apps.exposes.counter,
text: Constants.commonConstantsData.commonIndexes.one.toString(),
- visibilityState: 'be.visible',
});
if (property.host === 3002) {
@@ -156,10 +158,10 @@ appsData.forEach(property => {
}
await basePage.reloadWindow();
- await basePage.checkElementWithTextPresence({
+ // AppButton counter is not persisted across reloads; it resets to 0.
+ await basePage.checkElementContainText({
selector: selectors.quasarCliVue3WebPackJavaScriptApp.apps.exposes.counter,
- text: Constants.commonConstantsData.commonIndexes.one.toString(),
- visibilityState: 'be.visible',
+ text: Constants.commonConstantsData.commonIndexes.zero.toString(),
});
if (property.host === 3002) {
diff --git a/quasar-cli-vue3-webpack-javascript/playwright.config.ts b/quasar-cli-vue3-webpack-javascript/playwright.config.ts
index c4fac33345d..12da77cb138 100644
--- a/quasar-cli-vue3-webpack-javascript/playwright.config.ts
+++ b/quasar-cli-vue3-webpack-javascript/playwright.config.ts
@@ -10,13 +10,21 @@ export default defineConfig({
workers: 1,
reporter: 'list',
use: {
- baseURL: 'http://localhost:3001',
+ baseURL: 'http://127.0.0.1:3001',
trace: 'on-first-retry',
},
- webServer: {
- command: 'pnpm run start',
- url: 'http://localhost:3001',
- timeout: 300_000,
- reuseExistingServer: !process.env.CI,
- },
+ webServer: [
+ {
+ command: 'pnpm --dir app-exposes exec -- env BROWSER=none quasar dev',
+ url: 'http://localhost:3001',
+ timeout: 300_000,
+ reuseExistingServer: !process.env.CI,
+ },
+ {
+ command: 'pnpm --dir app-general exec -- env BROWSER=none quasar dev',
+ url: 'http://localhost:3002',
+ timeout: 300_000,
+ reuseExistingServer: !process.env.CI,
+ },
+ ],
});
diff --git a/react-16-17-18-ssr/shell/src/client/index.ts b/react-16-17-18-ssr/shell/src/client/index.ts
deleted file mode 100644
index e59d6a0adf7..00000000000
--- a/react-16-17-18-ssr/shell/src/client/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-import './bootstrap';
diff --git a/react-16-17-18-ssr/shell/src/client/bootstrap.tsx b/react-16-17-18-ssr/shell/src/client/index.tsx
similarity index 100%
rename from react-16-17-18-ssr/shell/src/client/bootstrap.tsx
rename to react-16-17-18-ssr/shell/src/client/index.tsx
diff --git a/react-18-code-splitting/app1/src/client/index.ts b/react-18-code-splitting/app1/src/client/index.ts
deleted file mode 100644
index e59d6a0adf7..00000000000
--- a/react-18-code-splitting/app1/src/client/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-import './bootstrap';
diff --git a/react-18-code-splitting/app1/src/client/bootstrap.tsx b/react-18-code-splitting/app1/src/client/index.tsx
similarity index 100%
rename from react-18-code-splitting/app1/src/client/bootstrap.tsx
rename to react-18-code-splitting/app1/src/client/index.tsx
diff --git a/react-18-code-splitting/app2/src/client/index.ts b/react-18-code-splitting/app2/src/client/index.ts
deleted file mode 100644
index e59d6a0adf7..00000000000
--- a/react-18-code-splitting/app2/src/client/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-import './bootstrap';
diff --git a/react-18-code-splitting/app2/src/client/bootstrap.tsx b/react-18-code-splitting/app2/src/client/index.tsx
similarity index 100%
rename from react-18-code-splitting/app2/src/client/bootstrap.tsx
rename to react-18-code-splitting/app2/src/client/index.tsx
diff --git a/react-18-code-splitting/e2e/checkReact18CodeSplittingApp.spec.ts b/react-18-code-splitting/e2e/checkReact18CodeSplittingApp.spec.ts
index c33ad617ba2..02aaee42827 100644
--- a/react-18-code-splitting/e2e/checkReact18CodeSplittingApp.spec.ts
+++ b/react-18-code-splitting/e2e/checkReact18CodeSplittingApp.spec.ts
@@ -46,7 +46,8 @@ for (const { appName, appHeader2, host } of appsData) {
await expect(contentBlock.locator(baseSelectors.tags.headers.h2)).toHaveText(
Constants.elementsText.reactApps.splitedApp.header,
);
- await expect(contentBlock.locator(baseSelectors.tags.paragraph)).toHaveText(
+ // The content block can contain multiple nodes; assert against the first one.
+ await expect(contentBlock.locator(baseSelectors.tags.paragraph).first()).toHaveText(
Constants.elementsText.reactApps.splitedApp.subHeader,
);
await expect(page.locator(baseSelectors.tags.strong)).toHaveText(
diff --git a/react-18-server-2-server/app1/src/client/index.ts b/react-18-server-2-server/app1/src/client/index.ts
deleted file mode 100644
index e59d6a0adf7..00000000000
--- a/react-18-server-2-server/app1/src/client/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-import './bootstrap';
diff --git a/react-18-server-2-server/app1/src/client/bootstrap.tsx b/react-18-server-2-server/app1/src/client/index.tsx
similarity index 100%
rename from react-18-server-2-server/app1/src/client/bootstrap.tsx
rename to react-18-server-2-server/app1/src/client/index.tsx
diff --git a/react-18-server-2-server/app2/src/client/index.ts b/react-18-server-2-server/app2/src/client/index.ts
deleted file mode 100644
index e59d6a0adf7..00000000000
--- a/react-18-server-2-server/app2/src/client/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-import './bootstrap';
diff --git a/react-18-server-2-server/app2/src/client/bootstrap.tsx b/react-18-server-2-server/app2/src/client/index.tsx
similarity index 100%
rename from react-18-server-2-server/app2/src/client/bootstrap.tsx
rename to react-18-server-2-server/app2/src/client/index.tsx
diff --git a/react-18-ssr/shell/src/client/index.ts b/react-18-ssr/shell/src/client/index.ts
deleted file mode 100644
index e59d6a0adf7..00000000000
--- a/react-18-ssr/shell/src/client/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-import './bootstrap';
diff --git a/react-18-ssr/shell/src/client/bootstrap.tsx b/react-18-ssr/shell/src/client/index.tsx
similarity index 100%
rename from react-18-ssr/shell/src/client/bootstrap.tsx
rename to react-18-ssr/shell/src/client/index.tsx
diff --git a/react-in-vue/home/src/bootstrap.js b/react-in-vue/home/src/bootstrap.js
deleted file mode 100644
index a8680f71cdf..00000000000
--- a/react-in-vue/home/src/bootstrap.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import App from './App';
-import React from 'react';
-import ReactDOM from 'react-dom';
-
-ReactDOM.render(, document.getElementById('root'));
diff --git a/react-in-vue/home/src/index.js b/react-in-vue/home/src/index.js
index e59d6a0adf7..a8680f71cdf 100644
--- a/react-in-vue/home/src/index.js
+++ b/react-in-vue/home/src/index.js
@@ -1 +1,5 @@
-import './bootstrap';
+import App from './App';
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+ReactDOM.render(, document.getElementById('root'));
diff --git a/react-in-vue/home/webpack.config.js b/react-in-vue/home/webpack.config.js
index 913c186a0f6..ae88a08fdab 100644
--- a/react-in-vue/home/webpack.config.js
+++ b/react-in-vue/home/webpack.config.js
@@ -36,6 +36,7 @@ module.exports = {
new ModuleFederationPlugin({
experiments: { asyncStartup: true },
name: 'home',
+ shareStrategy: 'loaded-first',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/Button',
diff --git a/react-in-vue/layout/src/bootstrap.js b/react-in-vue/layout/src/bootstrap.js
deleted file mode 100644
index 54d986c84cf..00000000000
--- a/react-in-vue/layout/src/bootstrap.js
+++ /dev/null
@@ -1 +0,0 @@
-import('./main.js');
diff --git a/react-in-vue/layout/webpack.config.js b/react-in-vue/layout/webpack.config.js
index 107f7683887..7e42467f993 100644
--- a/react-in-vue/layout/webpack.config.js
+++ b/react-in-vue/layout/webpack.config.js
@@ -13,7 +13,7 @@ module.exports = (env = {}) => ({
minimize: false,
},
target: 'web',
- entry: path.resolve(__dirname, './src/bootstrap.js'),
+ entry: path.resolve(__dirname, './src/main.js'),
output: {
publicPath: 'auto',
},
@@ -66,6 +66,7 @@ module.exports = (env = {}) => ({
new ModuleFederationPlugin({
experiments: { asyncStartup: true },
name: 'layout',
+ shareStrategy: 'loaded-first',
remotes: {
home: 'home@http://localhost:3002/remoteEntry.js',
},
diff --git a/react-livereload/host/src/bootstrap.js b/react-livereload/host/src/bootstrap.js
deleted file mode 100644
index 075bb80bd4f..00000000000
--- a/react-livereload/host/src/bootstrap.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import App from './App';
-import React from 'libs/react';
-import ReactDOM from 'libs/react-dom';
-
-ReactDOM.render(, document.getElementById('root'));
diff --git a/react-livereload/host/src/index.js b/react-livereload/host/src/index.js
index e59d6a0adf7..075bb80bd4f 100644
--- a/react-livereload/host/src/index.js
+++ b/react-livereload/host/src/index.js
@@ -1 +1,5 @@
-import './bootstrap';
+import App from './App';
+import React from 'libs/react';
+import ReactDOM from 'libs/react-dom';
+
+ReactDOM.render(, document.getElementById('root'));
diff --git a/react-livereload/host/webpack.config.js b/react-livereload/host/webpack.config.js
index f1a82e60fec..3a09daf33f4 100644
--- a/react-livereload/host/webpack.config.js
+++ b/react-livereload/host/webpack.config.js
@@ -42,6 +42,7 @@ module.exports = {
new ModuleFederationPlugin({
experiments: { asyncStartup: true },
name: 'host',
+ shareStrategy: 'loaded-first',
remotes: {
remote1: 'remote1@http://localhost:3001/remoteEntry.js',
libs: 'libs@http://localhost:3002/remoteEntry.js',
diff --git a/react-livereload/libs/webpack.config.js b/react-livereload/libs/webpack.config.js
index ba538010c8d..0fe292b4524 100644
--- a/react-livereload/libs/webpack.config.js
+++ b/react-livereload/libs/webpack.config.js
@@ -16,6 +16,7 @@ module.exports = {
new ModuleFederationPlugin({
experiments: { asyncStartup: true },
name: 'libs',
+ shareStrategy: 'loaded-first',
filename: 'remoteEntry.js',
exposes: {
'./react': 'react',
diff --git a/react-livereload/remote1/src/bootstrap.js b/react-livereload/remote1/src/bootstrap.js
deleted file mode 100644
index 075bb80bd4f..00000000000
--- a/react-livereload/remote1/src/bootstrap.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import App from './App';
-import React from 'libs/react';
-import ReactDOM from 'libs/react-dom';
-
-ReactDOM.render(, document.getElementById('root'));
diff --git a/react-livereload/remote1/src/index.js b/react-livereload/remote1/src/index.js
index e59d6a0adf7..075bb80bd4f 100644
--- a/react-livereload/remote1/src/index.js
+++ b/react-livereload/remote1/src/index.js
@@ -1 +1,5 @@
-import './bootstrap';
+import App from './App';
+import React from 'libs/react';
+import ReactDOM from 'libs/react-dom';
+
+ReactDOM.render(, document.getElementById('root'));
diff --git a/react-livereload/remote1/webpack.config.js b/react-livereload/remote1/webpack.config.js
index 0f066ed3430..0f4622f49a0 100644
--- a/react-livereload/remote1/webpack.config.js
+++ b/react-livereload/remote1/webpack.config.js
@@ -41,6 +41,7 @@ module.exports = {
new ModuleFederationPlugin({
experiments: { asyncStartup: true },
name: 'remote1',
+ shareStrategy: 'loaded-first',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/Button',
diff --git a/react-manifest-example/host/rsbuild.config.ts b/react-manifest-example/host/rsbuild.config.ts
index 74f5b11964e..99dc772e957 100644
--- a/react-manifest-example/host/rsbuild.config.ts
+++ b/react-manifest-example/host/rsbuild.config.ts
@@ -11,6 +11,8 @@ export default defineConfig({
appendPlugins([
new ModuleFederationPlugin({
name: 'host',
+ shareStrategy: 'loaded-first',
+ experiments: { asyncStartup: true },
remotes: {
remote1: 'remote1@http://localhost:3001/mf-manifest.json',
remote2: 'remote2@http://localhost:3002/mf-manifest.json',
diff --git a/react-manifest-example/host/src/bootstrap.tsx b/react-manifest-example/host/src/bootstrap.tsx
deleted file mode 100644
index 2b875af736c..00000000000
--- a/react-manifest-example/host/src/bootstrap.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom/client';
-import App from './App';
-
-const root = ReactDOM.createRoot(document.getElementById('root')!);
-root.render(
-
-
- ,
-);
diff --git a/react-manifest-example/host/src/index.tsx b/react-manifest-example/host/src/index.tsx
index b93c7a0268a..2b875af736c 100644
--- a/react-manifest-example/host/src/index.tsx
+++ b/react-manifest-example/host/src/index.tsx
@@ -1 +1,10 @@
-import('./bootstrap');
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App';
+
+const root = ReactDOM.createRoot(document.getElementById('root')!);
+root.render(
+
+
+ ,
+);
diff --git a/react-manifest-example/remote1/rsbuild.config.ts b/react-manifest-example/remote1/rsbuild.config.ts
index b1926854fc6..f2b85854cb8 100644
--- a/react-manifest-example/remote1/rsbuild.config.ts
+++ b/react-manifest-example/remote1/rsbuild.config.ts
@@ -19,6 +19,8 @@ export default defineConfig({
appendPlugins([
new ModuleFederationPlugin({
name: 'remote1',
+ shareStrategy: 'loaded-first',
+ experiments: { asyncStartup: true },
exposes: {
'./button': './src/button.tsx',
'./app': './src/App.tsx',
diff --git a/react-manifest-example/remote1/src/bootstrap.tsx b/react-manifest-example/remote1/src/bootstrap.tsx
deleted file mode 100644
index e851c3d69af..00000000000
--- a/react-manifest-example/remote1/src/bootstrap.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom/client';
-import App from './App';
-
-//@ts-ignore
-const root = ReactDOM.createRoot(document.getElementById('root')!);
-root.render(
-
-
- ,
-);
diff --git a/react-manifest-example/remote1/src/index.tsx b/react-manifest-example/remote1/src/index.tsx
index b93c7a0268a..e851c3d69af 100644
--- a/react-manifest-example/remote1/src/index.tsx
+++ b/react-manifest-example/remote1/src/index.tsx
@@ -1 +1,11 @@
-import('./bootstrap');
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App';
+
+//@ts-ignore
+const root = ReactDOM.createRoot(document.getElementById('root')!);
+root.render(
+
+
+ ,
+);
diff --git a/react-manifest-example/remote2/rsbuild.config.ts b/react-manifest-example/remote2/rsbuild.config.ts
index 66295ae20e8..de0bdc26c8e 100644
--- a/react-manifest-example/remote2/rsbuild.config.ts
+++ b/react-manifest-example/remote2/rsbuild.config.ts
@@ -19,6 +19,8 @@ export default defineConfig({
appendPlugins([
new ModuleFederationPlugin({
name: 'remote2',
+ shareStrategy: 'loaded-first',
+ experiments: { asyncStartup: true },
exposes: {
'./button': './src/button.tsx',
},
diff --git a/react-manifest-example/remote2/src/bootstrap.tsx b/react-manifest-example/remote2/src/bootstrap.tsx
deleted file mode 100644
index b2b0c4baecf..00000000000
--- a/react-manifest-example/remote2/src/bootstrap.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom/client';
-import App from './App';
-
-//@ts-ignore
-const root = ReactDOM.createRoot(document.getElementById('root')!);
-root.render(
-
-
- ,
-);
diff --git a/react-manifest-example/remote2/src/index.tsx b/react-manifest-example/remote2/src/index.tsx
index b93c7a0268a..b2b0c4baecf 100644
--- a/react-manifest-example/remote2/src/index.tsx
+++ b/react-manifest-example/remote2/src/index.tsx
@@ -1 +1,11 @@
-import('./bootstrap');
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App';
+
+//@ts-ignore
+const root = ReactDOM.createRoot(document.getElementById('root')!);
+root.render(
+
+
+ ,
+);
diff --git a/react-nextjs/nextjs-host-react-remote/remote/src/bootstrap.jsx b/react-nextjs/nextjs-host-react-remote/remote/src/bootstrap.jsx
deleted file mode 100644
index 8f395e4426a..00000000000
--- a/react-nextjs/nextjs-host-react-remote/remote/src/bootstrap.jsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import App from './App';
-import React from 'react';
-import { createRoot } from 'react-dom/client';
-const container = document.getElementById('root');
-const root = createRoot(container); // createRoot(container!) if you use TypeScript
-root.render();
diff --git a/react-nextjs/nextjs-host-react-remote/remote/src/index.jsx b/react-nextjs/nextjs-host-react-remote/remote/src/index.jsx
index e59d6a0adf7..8f395e4426a 100644
--- a/react-nextjs/nextjs-host-react-remote/remote/src/index.jsx
+++ b/react-nextjs/nextjs-host-react-remote/remote/src/index.jsx
@@ -1 +1,6 @@
-import './bootstrap';
+import App from './App';
+import React from 'react';
+import { createRoot } from 'react-dom/client';
+const container = document.getElementById('root');
+const root = createRoot(container); // createRoot(container!) if you use TypeScript
+root.render();
diff --git a/react-nextjs/nextjs-host-react-remote/remote/webpack.config.js b/react-nextjs/nextjs-host-react-remote/remote/webpack.config.js
index a819ced25cb..98aadea3f96 100644
--- a/react-nextjs/nextjs-host-react-remote/remote/webpack.config.js
+++ b/react-nextjs/nextjs-host-react-remote/remote/webpack.config.js
@@ -47,6 +47,7 @@ module.exports = {
new ModuleFederationPlugin({
experiments: { asyncStartup: true },
name: 'remote',
+ shareStrategy: 'loaded-first',
filename: 'remoteEntry.js',
exposes: {
'./Nav': './src/components/Nav',
diff --git a/react-nextjs/react-host-nextjs-remote/host/src/bootstrap.jsx b/react-nextjs/react-host-nextjs-remote/host/src/bootstrap.jsx
deleted file mode 100644
index 8f395e4426a..00000000000
--- a/react-nextjs/react-host-nextjs-remote/host/src/bootstrap.jsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import App from './App';
-import React from 'react';
-import { createRoot } from 'react-dom/client';
-const container = document.getElementById('root');
-const root = createRoot(container); // createRoot(container!) if you use TypeScript
-root.render();
diff --git a/react-nextjs/react-host-nextjs-remote/host/src/index.jsx b/react-nextjs/react-host-nextjs-remote/host/src/index.jsx
index e59d6a0adf7..8f395e4426a 100644
--- a/react-nextjs/react-host-nextjs-remote/host/src/index.jsx
+++ b/react-nextjs/react-host-nextjs-remote/host/src/index.jsx
@@ -1 +1,6 @@
-import './bootstrap';
+import App from './App';
+import React from 'react';
+import { createRoot } from 'react-dom/client';
+const container = document.getElementById('root');
+const root = createRoot(container); // createRoot(container!) if you use TypeScript
+root.render();
diff --git a/react-nextjs/react-host-nextjs-remote/host/webpack.config.js b/react-nextjs/react-host-nextjs-remote/host/webpack.config.js
index 287e7dd7bea..3a8a39291d6 100644
--- a/react-nextjs/react-host-nextjs-remote/host/webpack.config.js
+++ b/react-nextjs/react-host-nextjs-remote/host/webpack.config.js
@@ -47,6 +47,7 @@ module.exports = {
new ModuleFederationPlugin({
experiments: { asyncStartup: true },
name: 'host',
+ shareStrategy: 'loaded-first',
filename: 'remoteEntry.js',
remotes: {
remote: 'remote@http://localhost:8081/_next/static/chunks/remoteEntry.js',
diff --git a/react-nextjs/react-host-remote/host/src/bootstrap.jsx b/react-nextjs/react-host-remote/host/src/bootstrap.jsx
deleted file mode 100644
index 8f395e4426a..00000000000
--- a/react-nextjs/react-host-remote/host/src/bootstrap.jsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import App from './App';
-import React from 'react';
-import { createRoot } from 'react-dom/client';
-const container = document.getElementById('root');
-const root = createRoot(container); // createRoot(container!) if you use TypeScript
-root.render();
diff --git a/react-nextjs/react-host-remote/host/src/index.jsx b/react-nextjs/react-host-remote/host/src/index.jsx
index e59d6a0adf7..8f395e4426a 100644
--- a/react-nextjs/react-host-remote/host/src/index.jsx
+++ b/react-nextjs/react-host-remote/host/src/index.jsx
@@ -1 +1,6 @@
-import './bootstrap';
+import App from './App';
+import React from 'react';
+import { createRoot } from 'react-dom/client';
+const container = document.getElementById('root');
+const root = createRoot(container); // createRoot(container!) if you use TypeScript
+root.render();
diff --git a/react-nextjs/react-host-remote/host/webpack.config.js b/react-nextjs/react-host-remote/host/webpack.config.js
index 9c58bd784cc..676c05f1910 100644
--- a/react-nextjs/react-host-remote/host/webpack.config.js
+++ b/react-nextjs/react-host-remote/host/webpack.config.js
@@ -47,6 +47,7 @@ module.exports = {
new ModuleFederationPlugin({
experiments: { asyncStartup: true },
name: 'host',
+ shareStrategy: 'loaded-first',
filename: 'remoteEntry.js',
remotes: {
remote: 'remote@http://localhost:8081/remoteEntry.js',
diff --git a/react-nextjs/react-host-remote/remote/src/bootstrap.jsx b/react-nextjs/react-host-remote/remote/src/bootstrap.jsx
deleted file mode 100644
index 1b872b85108..00000000000
--- a/react-nextjs/react-host-remote/remote/src/bootstrap.jsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import App from './App';
-import React from 'react';
-import { createRoot } from 'react-dom/client';
-import { BrowserRouter, Routes, Route } from 'react-router-dom';
-const container = document.getElementById('root-remote');
-import Invoices from './routes/Invoices';
-import Expenses from './routes/Expenses';
-const root = createRoot(container); // createRoot(container!) if you use TypeScript
-root.render(
-
-
- } />
- } />
- } />
-
- ,
-);
diff --git a/react-nextjs/react-host-remote/remote/src/index.jsx b/react-nextjs/react-host-remote/remote/src/index.jsx
index e59d6a0adf7..1b872b85108 100644
--- a/react-nextjs/react-host-remote/remote/src/index.jsx
+++ b/react-nextjs/react-host-remote/remote/src/index.jsx
@@ -1 +1,17 @@
-import './bootstrap';
+import App from './App';
+import React from 'react';
+import { createRoot } from 'react-dom/client';
+import { BrowserRouter, Routes, Route } from 'react-router-dom';
+const container = document.getElementById('root-remote');
+import Invoices from './routes/Invoices';
+import Expenses from './routes/Expenses';
+const root = createRoot(container); // createRoot(container!) if you use TypeScript
+root.render(
+
+
+ } />
+ } />
+ } />
+
+ ,
+);
diff --git a/react-nextjs/react-host-remote/remote/webpack.config.js b/react-nextjs/react-host-remote/remote/webpack.config.js
index 5111e4b9ba1..f1400204a67 100644
--- a/react-nextjs/react-host-remote/remote/webpack.config.js
+++ b/react-nextjs/react-host-remote/remote/webpack.config.js
@@ -51,6 +51,7 @@ module.exports = {
new ModuleFederationPlugin({
experiments: { asyncStartup: true },
name: 'remote',
+ shareStrategy: 'loaded-first',
filename: 'remoteEntry.js',
// remotes: {
// remote: 'remote@http://localhost:8001/remoteEntry.js',
diff --git a/react-preact-runtime-typescript/remote/rsbuild.config.ts b/react-preact-runtime-typescript/remote/rsbuild.config.ts
index 0c0e22f861a..13a48ee9784 100644
--- a/react-preact-runtime-typescript/remote/rsbuild.config.ts
+++ b/react-preact-runtime-typescript/remote/rsbuild.config.ts
@@ -7,6 +7,8 @@ export default defineConfig({
pluginPreact(),
pluginModuleFederation({
name: 'remote',
+ shareStrategy: 'loaded-first',
+ experiments: { asyncStartup: true },
exposes: {
'./appInjector': './src/appInjector',
},
diff --git a/react-preact-runtime-typescript/remote/src/bootstrap.tsx b/react-preact-runtime-typescript/remote/src/bootstrap.tsx
deleted file mode 100644
index c2c9df7c33b..00000000000
--- a/react-preact-runtime-typescript/remote/src/bootstrap.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import { render } from 'preact';
-import App from './App';
-
-const root = document.getElementById('root');
-if (root) {
- render(, root);
-}
diff --git a/react-preact-runtime-typescript/remote/src/index.ts b/react-preact-runtime-typescript/remote/src/index.ts
index b93c7a0268a..c2c9df7c33b 100644
--- a/react-preact-runtime-typescript/remote/src/index.ts
+++ b/react-preact-runtime-typescript/remote/src/index.ts
@@ -1 +1,7 @@
-import('./bootstrap');
+import { render } from 'preact';
+import App from './App';
+
+const root = document.getElementById('root');
+if (root) {
+ render(, root);
+}
diff --git a/react-preact-runtime-typescript/shell/src/bootstrap.tsx b/react-preact-runtime-typescript/shell/src/bootstrap.tsx
deleted file mode 100644
index 55f29bff5c9..00000000000
--- a/react-preact-runtime-typescript/shell/src/bootstrap.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom/client';
-import App from './App';
-
-const rootEl = document.getElementById('root');
-if (rootEl) {
- const root = ReactDOM.createRoot(rootEl);
- root.render(
-
-
- ,
- );
-}
diff --git a/react-preact-runtime-typescript/shell/src/index.ts b/react-preact-runtime-typescript/shell/src/index.ts
index b93c7a0268a..55f29bff5c9 100644
--- a/react-preact-runtime-typescript/shell/src/index.ts
+++ b/react-preact-runtime-typescript/shell/src/index.ts
@@ -1 +1,13 @@
-import('./bootstrap');
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App';
+
+const rootEl = document.getElementById('root');
+if (rootEl) {
+ const root = ReactDOM.createRoot(rootEl);
+ root.render(
+
+
+ ,
+ );
+}
diff --git a/react-sharedworker/host/src/bootstrap.js b/react-sharedworker/host/src/bootstrap.js
deleted file mode 100644
index 13938165dc2..00000000000
--- a/react-sharedworker/host/src/bootstrap.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import { testValue } from 'myModule/testValue';
-
-console.log('testValue in main thread', testValue);
-
-const worker = new SharedWorker(new URL('./worker', import.meta.url));
diff --git a/react-sharedworker/host/src/bootstrapWorker.js b/react-sharedworker/host/src/bootstrapWorker.js
deleted file mode 100644
index 3829368c422..00000000000
--- a/react-sharedworker/host/src/bootstrapWorker.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import { testValue } from 'myModule/testValue';
-
-console.log('testValue in worker thread', testValue);
diff --git a/react-sharedworker/host/src/index.js b/react-sharedworker/host/src/index.js
index e59d6a0adf7..13938165dc2 100644
--- a/react-sharedworker/host/src/index.js
+++ b/react-sharedworker/host/src/index.js
@@ -1 +1,5 @@
-import './bootstrap';
+import { testValue } from 'myModule/testValue';
+
+console.log('testValue in main thread', testValue);
+
+const worker = new SharedWorker(new URL('./worker', import.meta.url));
diff --git a/react-sharedworker/host/src/worker.js b/react-sharedworker/host/src/worker.js
index 2609298f4fb..3829368c422 100644
--- a/react-sharedworker/host/src/worker.js
+++ b/react-sharedworker/host/src/worker.js
@@ -1 +1,3 @@
-import('./bootstrapWorker');
+import { testValue } from 'myModule/testValue';
+
+console.log('testValue in worker thread', testValue);
diff --git a/react-storybook/host/modulefederation.config.js b/react-storybook/host/modulefederation.config.js
index b81232d2043..268c007dcfb 100644
--- a/react-storybook/host/modulefederation.config.js
+++ b/react-storybook/host/modulefederation.config.js
@@ -2,6 +2,8 @@ const { dependencies } = require('./package.json');
module.exports = {
name: 'host',
+ shareStrategy: 'loaded-first',
+ experiments: { asyncStartup: true },
remotes: {
remote: 'remote@http://localhost:3002/remoteEntry.js',
},
diff --git a/react-storybook/host/package.json b/react-storybook/host/package.json
index 66095a8319a..5cf4e04115f 100644
--- a/react-storybook/host/package.json
+++ b/react-storybook/host/package.json
@@ -44,6 +44,7 @@
]
},
"devDependencies": {
+ "@module-federation/enhanced": "0.23.0",
"@module-federation/storybook-addon": "3.0.6",
"@module-federation/utilities": "3.1.26",
"@storybook/addon-essentials": "^8.6.14",
diff --git a/react-storybook/host/scripts/overrides/webpack-config.js b/react-storybook/host/scripts/overrides/webpack-config.js
index c7f55b49aca..a13ea0fc81e 100644
--- a/react-storybook/host/scripts/overrides/webpack-config.js
+++ b/react-storybook/host/scripts/overrides/webpack-config.js
@@ -1,4 +1,4 @@
-const { ModuleFederationPlugin } = require('webpack').container;
+const { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack');
const webpackConfigPath = 'react-scripts/config/webpack.config';
const webpackConfig = require(webpackConfigPath);
diff --git a/react-storybook/host/src/bootstrap.js b/react-storybook/host/src/bootstrap.js
deleted file mode 100644
index c9ebadbf857..00000000000
--- a/react-storybook/host/src/bootstrap.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-
-import App from './App';
-
-ReactDOM.render(
-
-
- ,
- document.getElementById('root'),
-);
diff --git a/react-storybook/host/src/index.js b/react-storybook/host/src/index.js
index b93c7a0268a..c9ebadbf857 100644
--- a/react-storybook/host/src/index.js
+++ b/react-storybook/host/src/index.js
@@ -1 +1,11 @@
-import('./bootstrap');
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+import App from './App';
+
+ReactDOM.render(
+
+
+ ,
+ document.getElementById('root'),
+);
diff --git a/react-storybook/remote/modulefederation.config.js b/react-storybook/remote/modulefederation.config.js
index 60e420412a8..c4503ca13cc 100644
--- a/react-storybook/remote/modulefederation.config.js
+++ b/react-storybook/remote/modulefederation.config.js
@@ -2,6 +2,8 @@ const { dependencies } = require('./package.json');
module.exports = {
name: 'remote',
+ shareStrategy: 'loaded-first',
+ experiments: { asyncStartup: true },
exposes: {
'./Button': './src/Button',
},
diff --git a/react-storybook/remote/package.json b/react-storybook/remote/package.json
index 249e80f75b2..105af521fa5 100644
--- a/react-storybook/remote/package.json
+++ b/react-storybook/remote/package.json
@@ -15,6 +15,7 @@
"eject": "react-scripts eject"
},
"devDependencies": {
+ "@module-federation/enhanced": "0.23.0",
"eslint": "8.57.0",
"webpack": "5.104.1"
},
diff --git a/react-storybook/remote/scripts/overrides/webpack-config.js b/react-storybook/remote/scripts/overrides/webpack-config.js
index c7f55b49aca..a13ea0fc81e 100644
--- a/react-storybook/remote/scripts/overrides/webpack-config.js
+++ b/react-storybook/remote/scripts/overrides/webpack-config.js
@@ -1,4 +1,4 @@
-const { ModuleFederationPlugin } = require('webpack').container;
+const { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack');
const webpackConfigPath = 'react-scripts/config/webpack.config';
const webpackConfig = require(webpackConfigPath);
diff --git a/react-storybook/remote/src/bootstrap.js b/react-storybook/remote/src/bootstrap.js
deleted file mode 100644
index c9ebadbf857..00000000000
--- a/react-storybook/remote/src/bootstrap.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-
-import App from './App';
-
-ReactDOM.render(
-
-
- ,
- document.getElementById('root'),
-);
diff --git a/react-storybook/remote/src/index.js b/react-storybook/remote/src/index.js
index b93c7a0268a..c9ebadbf857 100644
--- a/react-storybook/remote/src/index.js
+++ b/react-storybook/remote/src/index.js
@@ -1 +1,11 @@
-import('./bootstrap');
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+import App from './App';
+
+ReactDOM.render(
+
+
+ ,
+ document.getElementById('root'),
+);
diff --git a/react-webpack-host-vite-remote/host/src/bootstrap.js b/react-webpack-host-vite-remote/host/src/bootstrap.js
deleted file mode 100644
index 1164e2fa8e8..00000000000
--- a/react-webpack-host-vite-remote/host/src/bootstrap.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from 'react';
-import { createRoot } from 'react-dom/client';
-import App from './App';
-
-const rootElement = document.getElementById('root');
-if (rootElement) {
- const root = createRoot(rootElement);
- root.render();
-} else {
- console.error('Elemento #root não encontrado.');
-}
diff --git a/react-webpack-host-vite-remote/host/src/index.js b/react-webpack-host-vite-remote/host/src/index.js
index e59d6a0adf7..1164e2fa8e8 100644
--- a/react-webpack-host-vite-remote/host/src/index.js
+++ b/react-webpack-host-vite-remote/host/src/index.js
@@ -1 +1,11 @@
-import './bootstrap';
+import React from 'react';
+import { createRoot } from 'react-dom/client';
+import App from './App';
+
+const rootElement = document.getElementById('root');
+if (rootElement) {
+ const root = createRoot(rootElement);
+ root.render();
+} else {
+ console.error('Elemento #root não encontrado.');
+}
diff --git a/react-webpack-host-vite-remote/host/webpack.config.js b/react-webpack-host-vite-remote/host/webpack.config.js
index 9fb6331f955..7a8e46e66af 100644
--- a/react-webpack-host-vite-remote/host/webpack.config.js
+++ b/react-webpack-host-vite-remote/host/webpack.config.js
@@ -15,6 +15,7 @@ module.exports = {
new ModuleFederationPlugin({
experiments: { asyncStartup: true },
name: 'host',
+ shareStrategy: 'loaded-first',
remotes: {
remotevite: `promise import('http://127.0.0.1:3001/remoteEntry.js')
.then(module => ({
diff --git a/redux-reducer-injection/app1/src/bootstrap.js b/redux-reducer-injection/app1/src/bootstrap.js
deleted file mode 100644
index 0afbe126cdb..00000000000
--- a/redux-reducer-injection/app1/src/bootstrap.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import React, { Suspense } from 'react';
-import ReactDOM from 'react-dom';
-import { Provider } from 'react-redux';
-import { loadRemote } from '@module-federation/enhanced/runtime';
-
-import { store } from './store';
-
-// This example intentionally demonstrates *dynamic* loading (previously done via
-// manual container.init/get). With Enhanced, prefer the runtime API.
-const RemoteApp = React.lazy(() =>
- loadRemote('app2/RemoteApp').then(mod => ({ default: mod?.default ?? mod })),
-);
-
-const App = () => {
- return (
-
-
- Welcome to Host App
-
-
-
-
-
-
-
- );
-};
-
-ReactDOM.render(, document.getElementById('root'));
diff --git a/redux-reducer-injection/app1/src/index.js b/redux-reducer-injection/app1/src/index.js
index e59d6a0adf7..0afbe126cdb 100644
--- a/redux-reducer-injection/app1/src/index.js
+++ b/redux-reducer-injection/app1/src/index.js
@@ -1 +1,29 @@
-import './bootstrap';
+import React, { Suspense } from 'react';
+import ReactDOM from 'react-dom';
+import { Provider } from 'react-redux';
+import { loadRemote } from '@module-federation/enhanced/runtime';
+
+import { store } from './store';
+
+// This example intentionally demonstrates *dynamic* loading (previously done via
+// manual container.init/get). With Enhanced, prefer the runtime API.
+const RemoteApp = React.lazy(() =>
+ loadRemote('app2/RemoteApp').then(mod => ({ default: mod?.default ?? mod })),
+);
+
+const App = () => {
+ return (
+
+
+ Welcome to Host App
+
+
+
+
+
+
+
+ );
+};
+
+ReactDOM.render(, document.getElementById('root'));
diff --git a/redux-reducer-injection/app1/webpack.config.js b/redux-reducer-injection/app1/webpack.config.js
index 48e138465d0..368bb6d5b6b 100644
--- a/redux-reducer-injection/app1/webpack.config.js
+++ b/redux-reducer-injection/app1/webpack.config.js
@@ -38,6 +38,7 @@ module.exports = {
new ModuleFederationPlugin({
experiments: { asyncStartup: true },
name: 'app1',
+ shareStrategy: 'loaded-first',
library: { type: 'var', name: 'app1' },
filename: 'remoteEntry.js',
remotes: {
diff --git a/redux-reducer-injection/app2/webpack.config.js b/redux-reducer-injection/app2/webpack.config.js
index f81beaf60ee..f1e7e6cb3e6 100644
--- a/redux-reducer-injection/app2/webpack.config.js
+++ b/redux-reducer-injection/app2/webpack.config.js
@@ -33,6 +33,7 @@ module.exports = {
new ModuleFederationPlugin({
experiments: { asyncStartup: true },
name: 'app2',
+ shareStrategy: 'loaded-first',
filename: 'remoteEntry.js',
exposes: {
'./RemoteApp': './src/RemoteApp',
diff --git a/rsbuild-vue3-vuex/consumer/rsbuild.config.ts b/rsbuild-vue3-vuex/consumer/rsbuild.config.ts
index 475e7bb224e..9d2e7b62ff3 100644
--- a/rsbuild-vue3-vuex/consumer/rsbuild.config.ts
+++ b/rsbuild-vue3-vuex/consumer/rsbuild.config.ts
@@ -29,6 +29,8 @@ export default defineConfig({
appendPlugins([
new ModuleFederationPlugin({
name: `ASSET_HOST`,
+ shareStrategy: 'loaded-first',
+ experiments: { asyncStartup: true },
filename: `ASSET_HOST__remoteEntry.js`,
remotes: {
'@remote': 'ASSET_REMOTE@http://localhost:3001/remoteEntry.js',
diff --git a/rsbuild-vue3-vuex/consumer/src/bootstrap.js b/rsbuild-vue3-vuex/consumer/src/bootstrap.js
deleted file mode 100644
index 0c5389cfd75..00000000000
--- a/rsbuild-vue3-vuex/consumer/src/bootstrap.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import { createApp } from 'vue';
-import App from './App.vue';
-import router from './router';
-import store from './store';
-const app = createApp(App);
-
-export const bootstrap = () => {
- app.use(store(app)).use(router).mount('#root');
-};
diff --git a/rsbuild-vue3-vuex/consumer/src/main.js b/rsbuild-vue3-vuex/consumer/src/main.js
index 37ee9cae86b..4739541342f 100644
--- a/rsbuild-vue3-vuex/consumer/src/main.js
+++ b/rsbuild-vue3-vuex/consumer/src/main.js
@@ -1 +1,7 @@
-import('./bootstrap').then(({ bootstrap }) => bootstrap());
+import { createApp } from 'vue';
+import App from './App.vue';
+import router from './router';
+import store from './store';
+
+const app = createApp(App);
+app.use(store(app)).use(router).mount('#root');
diff --git a/rsbuild-vue3-vuex/provider/rsbuild.config.ts b/rsbuild-vue3-vuex/provider/rsbuild.config.ts
index a5832d97d0e..af3268025db 100644
--- a/rsbuild-vue3-vuex/provider/rsbuild.config.ts
+++ b/rsbuild-vue3-vuex/provider/rsbuild.config.ts
@@ -30,6 +30,8 @@ export default defineConfig({
appendPlugins([
new ModuleFederationPlugin({
name: `ASSET_REMOTE`,
+ shareStrategy: 'loaded-first',
+ experiments: { asyncStartup: true },
filename: `remoteEntry.js`,
exposes: {
'./AppIndex': './src/views/AppIndex',
diff --git a/rsbuild-vue3-vuex/provider/src/bootstrap.js b/rsbuild-vue3-vuex/provider/src/bootstrap.js
deleted file mode 100644
index 0dab9a93f4a..00000000000
--- a/rsbuild-vue3-vuex/provider/src/bootstrap.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import { createApp } from 'vue';
-import App from './App.vue';
-import store from './store';
-
-export const bootstrap = () => {
- return createApp(App).use(store).mount('#root');
-};
diff --git a/rsbuild-vue3-vuex/provider/src/main.js b/rsbuild-vue3-vuex/provider/src/main.js
index 37ee9cae86b..0649c40f75b 100644
--- a/rsbuild-vue3-vuex/provider/src/main.js
+++ b/rsbuild-vue3-vuex/provider/src/main.js
@@ -1 +1,5 @@
-import('./bootstrap').then(({ bootstrap }) => bootstrap());
+import { createApp } from 'vue';
+import App from './App.vue';
+import store from './store';
+
+createApp(App).use(store).mount('#root');
diff --git a/rspack-webpack-interop/app-01/src/bootstrap.jsx b/rspack-webpack-interop/app-01/src/bootstrap.jsx
deleted file mode 100644
index a8680f71cdf..00000000000
--- a/rspack-webpack-interop/app-01/src/bootstrap.jsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import App from './App';
-import React from 'react';
-import ReactDOM from 'react-dom';
-
-ReactDOM.render(, document.getElementById('root'));
diff --git a/rspack-webpack-interop/app-01/src/index.jsx b/rspack-webpack-interop/app-01/src/index.jsx
index 43564d45742..dfd217a3f1b 100644
--- a/rspack-webpack-interop/app-01/src/index.jsx
+++ b/rspack-webpack-interop/app-01/src/index.jsx
@@ -1,2 +1,6 @@
console.log(__webpack_require__.federation);
-import './bootstrap';
+import App from './App';
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+ReactDOM.render(, document.getElementById('root'));
diff --git a/rspack-webpack-interop/app-02/src/bootstrap.js b/rspack-webpack-interop/app-02/src/bootstrap.js
deleted file mode 100644
index a8680f71cdf..00000000000
--- a/rspack-webpack-interop/app-02/src/bootstrap.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import App from './App';
-import React from 'react';
-import ReactDOM from 'react-dom';
-
-ReactDOM.render(, document.getElementById('root'));
diff --git a/rspack-webpack-interop/app-02/src/index.js b/rspack-webpack-interop/app-02/src/index.js
index e59d6a0adf7..a8680f71cdf 100644
--- a/rspack-webpack-interop/app-02/src/index.js
+++ b/rspack-webpack-interop/app-02/src/index.js
@@ -1 +1,5 @@
-import './bootstrap';
+import App from './App';
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+ReactDOM.render(, document.getElementById('root'));
diff --git a/rspack-webpack-interop/app-03/src/bootstrap.js b/rspack-webpack-interop/app-03/src/bootstrap.js
deleted file mode 100644
index a8680f71cdf..00000000000
--- a/rspack-webpack-interop/app-03/src/bootstrap.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import App from './App';
-import React from 'react';
-import ReactDOM from 'react-dom';
-
-ReactDOM.render(, document.getElementById('root'));
diff --git a/rspack-webpack-interop/app-03/src/index.js b/rspack-webpack-interop/app-03/src/index.js
index e59d6a0adf7..a8680f71cdf 100644
--- a/rspack-webpack-interop/app-03/src/index.js
+++ b/rspack-webpack-interop/app-03/src/index.js
@@ -1 +1,5 @@
-import './bootstrap';
+import App from './App';
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+ReactDOM.render(, document.getElementById('root'));
diff --git a/rspack-webpack-interop/app-04/webpack.config.js b/rspack-webpack-interop/app-04/webpack.config.js
index 8a3110e2aeb..fb26c2fb057 100644
--- a/rspack-webpack-interop/app-04/webpack.config.js
+++ b/rspack-webpack-interop/app-04/webpack.config.js
@@ -56,6 +56,7 @@ module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'app_04',
+ shareStrategy: 'loaded-first',
filename: 'remoteEntry.js',
exposes: {
'./App': './src/main.js',
diff --git a/rspack-webpack-interop/app-05/webpack.config.js b/rspack-webpack-interop/app-05/webpack.config.js
index 1bebfd53d44..b89ac545978 100644
--- a/rspack-webpack-interop/app-05/webpack.config.js
+++ b/rspack-webpack-interop/app-05/webpack.config.js
@@ -41,6 +41,7 @@ module.exports = {
new ModuleFederationPlugin({
experiments: { asyncStartup: true },
name: 'app_05',
+ shareStrategy: 'loaded-first',
filename: 'remoteEntry.js',
exposes: {
'./ActionButton': './src/components/action-button.ts',
diff --git a/rspack-webpack-offload/component-app/bootstrap.js b/rspack-webpack-offload/component-app/bootstrap.js
deleted file mode 100644
index 7b8ba8b56be..00000000000
--- a/rspack-webpack-offload/component-app/bootstrap.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import 'lib-app'; // should be first
-import App from './App';
-import ReactDOM from 'react-dom';
-import React from 'react';
-ReactDOM.render(, document.getElementById('app'));
diff --git a/rspack-webpack-offload/component-app/index.js b/rspack-webpack-offload/component-app/index.js
index d2b348ea214..7b8ba8b56be 100644
--- a/rspack-webpack-offload/component-app/index.js
+++ b/rspack-webpack-offload/component-app/index.js
@@ -1 +1,5 @@
-import './bootstrap.js';
+import 'lib-app'; // should be first
+import App from './App';
+import ReactDOM from 'react-dom';
+import React from 'react';
+ReactDOM.render(, document.getElementById('app'));
diff --git a/rspack-webpack-offload/component-app/rspack.config.js b/rspack-webpack-offload/component-app/rspack.config.js
index 380140383ab..d653e5439aa 100644
--- a/rspack-webpack-offload/component-app/rspack.config.js
+++ b/rspack-webpack-offload/component-app/rspack.config.js
@@ -45,6 +45,7 @@ module.exports = {
new ModuleFederationPlugin({
experiments: { asyncStartup: true },
name: 'component_app',
+ shareStrategy: 'version-first',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/Button.jsx',
diff --git a/rspack-webpack-offload/component-app/webpack.config.js b/rspack-webpack-offload/component-app/webpack.config.js
index 8b04b25dc8c..6248f68db9b 100644
--- a/rspack-webpack-offload/component-app/webpack.config.js
+++ b/rspack-webpack-offload/component-app/webpack.config.js
@@ -37,6 +37,7 @@ module.exports = {
new ModuleFederationPlugin({
experiments: { asyncStartup: true },
name: 'component_app',
+ shareStrategy: 'version-first',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/Button.jsx',
diff --git a/rspack-webpack-offload/lib-app/rspack.config.js b/rspack-webpack-offload/lib-app/rspack.config.js
index 01492d8fcee..1503d137c9a 100644
--- a/rspack-webpack-offload/lib-app/rspack.config.js
+++ b/rspack-webpack-offload/lib-app/rspack.config.js
@@ -44,6 +44,7 @@ module.exports = {
new ModuleFederationPlugin({
experiments: { asyncStartup: true },
name: 'lib_app',
+ shareStrategy: 'version-first',
filename: 'remoteEntry.js',
shared: {
react: {
diff --git a/rspack-webpack-offload/main-app/bootstrap.js b/rspack-webpack-offload/main-app/bootstrap.js
deleted file mode 100644
index 4bba24a8f0e..00000000000
--- a/rspack-webpack-offload/main-app/bootstrap.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import 'lib-app'; // should be first
-import App from './App.jsx';
-import ReactDOM from 'react-dom';
-import React from 'react';
-ReactDOM.render(, document.getElementById('app'));
diff --git a/rspack-webpack-offload/main-app/index.js b/rspack-webpack-offload/main-app/index.js
index d2b348ea214..4bba24a8f0e 100644
--- a/rspack-webpack-offload/main-app/index.js
+++ b/rspack-webpack-offload/main-app/index.js
@@ -1 +1,5 @@
-import './bootstrap.js';
+import 'lib-app'; // should be first
+import App from './App.jsx';
+import ReactDOM from 'react-dom';
+import React from 'react';
+ReactDOM.render(, document.getElementById('app'));
diff --git a/rspack-webpack-offload/main-app/rspack.config.js b/rspack-webpack-offload/main-app/rspack.config.js
index 5fe16a7e7c0..dceae2a35d6 100644
--- a/rspack-webpack-offload/main-app/rspack.config.js
+++ b/rspack-webpack-offload/main-app/rspack.config.js
@@ -41,6 +41,7 @@ module.exports = {
new ModuleFederationPlugin({
experiments: { asyncStartup: true },
name: 'main_app',
+ shareStrategy: 'version-first',
remotes: {
'lib-app': 'lib_app@http://localhost:3000/remoteEntry.js',
'component-app': 'component_app@http://localhost:3001/remoteEntry.js',
diff --git a/rspack-webpack-offload/main-app/webpack.config.js b/rspack-webpack-offload/main-app/webpack.config.js
index 7eecdcef86e..89d3f215788 100644
--- a/rspack-webpack-offload/main-app/webpack.config.js
+++ b/rspack-webpack-offload/main-app/webpack.config.js
@@ -33,6 +33,7 @@ module.exports = {
new ModuleFederationPlugin({
experiments: { asyncStartup: true },
name: 'main_app',
+ shareStrategy: 'version-first',
remotes: {
'lib-app': 'lib_app@http://localhost:3000/remoteEntry.js',
'component-app': 'component_app@http://localhost:3001/remoteEntry.js',
diff --git a/rspack_hmr/app2/rspack.config.js b/rspack_hmr/app2/rspack.config.js
index f32405a29f9..47cf2082dca 100644
--- a/rspack_hmr/app2/rspack.config.js
+++ b/rspack_hmr/app2/rspack.config.js
@@ -93,6 +93,7 @@ module.exports = {
new ModuleFederationPlugin({
experiments: { asyncStartup: true },
name: name,
+ shareStrategy: 'loaded-first',
filename: 'remoteEntry.js',
exposes: {
'./Hello': './src/Hello.tsx',
diff --git a/rspack_hmr/app2/src/bootstrap.tsx b/rspack_hmr/app2/src/bootstrap.tsx
deleted file mode 100644
index 8c4462a7340..00000000000
--- a/rspack_hmr/app2/src/bootstrap.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom/client';
-import App from './App.tsx';
-import './index.css';
-
-ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
-
-
- ,
-);
diff --git a/rspack_hmr/app2/src/index.tsx b/rspack_hmr/app2/src/index.tsx
index e59d6a0adf7..8c4462a7340 100644
--- a/rspack_hmr/app2/src/index.tsx
+++ b/rspack_hmr/app2/src/index.tsx
@@ -1 +1,10 @@
-import './bootstrap';
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App.tsx';
+import './index.css';
+
+ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
+
+
+ ,
+);
diff --git a/rspack_hmr/host/rspack.config.js b/rspack_hmr/host/rspack.config.js
index a3ae4305d59..013908a60bd 100644
--- a/rspack_hmr/host/rspack.config.js
+++ b/rspack_hmr/host/rspack.config.js
@@ -95,6 +95,7 @@ module.exports = {
new ModuleFederationPlugin({
experiments: { asyncStartup: true },
name: name,
+ shareStrategy: 'loaded-first',
filename: 'remoteEntry.js',
remotes: {
app_02: 'app_02@http://localhost:3001/mf-manifest.json',
diff --git a/rspack_hmr/host/src/bootstrap.tsx b/rspack_hmr/host/src/bootstrap.tsx
deleted file mode 100644
index 8c4462a7340..00000000000
--- a/rspack_hmr/host/src/bootstrap.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom/client';
-import App from './App.tsx';
-import './index.css';
-
-ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
-
-
- ,
-);
diff --git a/rspack_hmr/host/src/index.tsx b/rspack_hmr/host/src/index.tsx
index e59d6a0adf7..8c4462a7340 100644
--- a/rspack_hmr/host/src/index.tsx
+++ b/rspack_hmr/host/src/index.tsx
@@ -1 +1,10 @@
-import './bootstrap';
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App.tsx';
+import './index.css';
+
+ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
+
+
+ ,
+);
diff --git a/rspack_hmr/runhost/rspack.config.js b/rspack_hmr/runhost/rspack.config.js
index bfee4651db2..aa3c32b1c9c 100644
--- a/rspack_hmr/runhost/rspack.config.js
+++ b/rspack_hmr/runhost/rspack.config.js
@@ -97,6 +97,7 @@ module.exports = {
new ModuleFederationPlugin({
experiments: { asyncStartup: true },
name: name,
+ shareStrategy: 'loaded-first',
filename: 'remoteEntry.js',
// remotes: {
// app_02: "app_02@http://localhost:3001/mf-manifest.json",
diff --git a/rspack_hmr/runhost/src/bootstrap.tsx b/rspack_hmr/runhost/src/bootstrap.tsx
deleted file mode 100644
index faffd42a028..00000000000
--- a/rspack_hmr/runhost/src/bootstrap.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom/client';
-import App from './App.tsx';
-import './index.css';
-import { FederationRuntimePlugin, init, loadRemote } from '@module-federation/enhanced/runtime';
-
-const runtimePlugin: () => FederationRuntimePlugin = function () {
- return {
- name: 'my-runtime-plugin',
- beforeInit(args) {
- console.log('beforeInit: ', args);
- return args;
- },
- beforeRequest(args) {
- console.log('beforeRequest: ', args);
- return args;
- },
-
- // loadRemoteSnapshot(args) {
- // console.log("loadRemoteSnapshot: ", args);
- // if (args.manifestJson && (args.manifestJson.metaData as any).publicPath.includes("placeholder")) {
- // (args.manifestJson.metaData as any).publicPath = (args.manifestJson.metaData as any).publicPath.replace(
- // "placeholder",
- // args.manifestUrl?.split("/")[2].split(":")[0],
- // );
- // }
- // if ((args.remoteSnapshot as any).publicPath.includes("placeholder")) {
- // (args.remoteSnapshot as any).publicPath = (args.remoteSnapshot as any).publicPath.replace(
- // "placeholder",
- // args.manifestUrl?.split("/")[2].split(":")[0],
- // );
- // }
- // return args;
- //},
-
- afterResolve(args) {
- console.log('afterResolve', args);
- return args;
- },
- onLoad(args) {
- console.log('onLoad: ', args);
- return args;
- },
- async loadShare(args) {
- console.log('loadShare:', args);
- return args;
- },
- async beforeLoadShare(args) {
- console.log('beforeloadShare:', args);
- return args;
- },
- // async initContainer(args) {
- // console.log("initContainer: ", args);
- // args.origin.snapshotHandler.manifestCache.forEach((manifest, index) => {
- // console.log(args);
- // manifest.metaData.publicPath = manifest.metaData.publicPath.replace(
- // "placeholder",
- // args.remoteInfo.entry.split("/")[2].split(":")[0],
- // );
- // console.log("manifest: ", manifest);
- // });
- // console.log("args after", args);
- //return args;
- //},
- };
-};
-
-export async function renderApp() {
- init({
- name: 'runhost',
- remotes: [{ name: '@app_02', entry: 'http://localhost:3001/mf-manifest.json', alias: 'a2' }],
- plugins: [runtimePlugin()],
- });
-
- loadRemote('a2/pi').then(module => {
- console.log('results from pi in app02: ', (module as any).default());
- });
- ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
-
-
- ,
- );
-}
diff --git a/rspack_hmr/runhost/src/index.tsx b/rspack_hmr/runhost/src/index.tsx
index 7eab9c10fdf..f063ab66572 100644
--- a/rspack_hmr/runhost/src/index.tsx
+++ b/rspack_hmr/runhost/src/index.tsx
@@ -1,3 +1,85 @@
-import { renderApp } from './bootstrap';
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App.tsx';
+import './index.css';
+import { FederationRuntimePlugin, init, loadRemote } from '@module-federation/enhanced/runtime';
+
+const runtimePlugin: () => FederationRuntimePlugin = function () {
+ return {
+ name: 'my-runtime-plugin',
+ beforeInit(args) {
+ console.log('beforeInit: ', args);
+ return args;
+ },
+ beforeRequest(args) {
+ console.log('beforeRequest: ', args);
+ return args;
+ },
+
+ // loadRemoteSnapshot(args) {
+ // console.log("loadRemoteSnapshot: ", args);
+ // if (args.manifestJson && (args.manifestJson.metaData as any).publicPath.includes("placeholder")) {
+ // (args.manifestJson.metaData as any).publicPath = (args.manifestJson.metaData as any).publicPath.replace(
+ // "placeholder",
+ // args.manifestUrl?.split("/")[2].split(":")[0],
+ // );
+ // }
+ // if ((args.remoteSnapshot as any).publicPath.includes("placeholder")) {
+ // (args.remoteSnapshot as any).publicPath = (args.remoteSnapshot as any).publicPath.replace(
+ // "placeholder",
+ // args.manifestUrl?.split("/")[2].split(":")[0],
+ // );
+ // }
+ // return args;
+ //},
+
+ afterResolve(args) {
+ console.log('afterResolve', args);
+ return args;
+ },
+ onLoad(args) {
+ console.log('onLoad: ', args);
+ return args;
+ },
+ async loadShare(args) {
+ console.log('loadShare:', args);
+ return args;
+ },
+ async beforeLoadShare(args) {
+ console.log('beforeloadShare:', args);
+ return args;
+ },
+ // async initContainer(args) {
+ // console.log("initContainer: ", args);
+ // args.origin.snapshotHandler.manifestCache.forEach((manifest, index) => {
+ // console.log(args);
+ // manifest.metaData.publicPath = manifest.metaData.publicPath.replace(
+ // "placeholder",
+ // args.remoteInfo.entry.split("/")[2].split(":")[0],
+ // );
+ // console.log("manifest: ", manifest);
+ // });
+ // console.log("args after", args);
+ //return args;
+ //},
+ };
+};
+
+async function renderApp() {
+ init({
+ name: 'runhost',
+ remotes: [{ name: '@app_02', entry: 'http://localhost:3001/mf-manifest.json', alias: 'a2' }],
+ plugins: [runtimePlugin()],
+ });
+
+ loadRemote('a2/pi').then(module => {
+ console.log('results from pi in app02: ', (module as any).default());
+ });
+ ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
+
+
+ ,
+ );
+}
void renderApp();
diff --git a/runtime-plugins/control-sharing/app1/rspack.config.js b/runtime-plugins/control-sharing/app1/rspack.config.js
index 7ec2df87ae1..5e6aa836d56 100644
--- a/runtime-plugins/control-sharing/app1/rspack.config.js
+++ b/runtime-plugins/control-sharing/app1/rspack.config.js
@@ -17,7 +17,6 @@ const deps = require('./package.json').dependencies;
module.exports = {
entry: './src/index',
mode: 'development',
- watch: true,
devServer: {
static: {
directory: path.join(__dirname, 'dist'),
@@ -82,6 +81,7 @@ module.exports = {
new ModuleFederationPlugin({
experiments: { asyncStartup: true },
name: 'app1',
+ shareStrategy: 'loaded-first',
filename: 'remoteEntry.js',
remotes: {
app2: 'app2@http://localhost:3002/remoteEntry.js',
diff --git a/runtime-plugins/control-sharing/app1/src/bootstrap.js b/runtime-plugins/control-sharing/app1/src/bootstrap.js
deleted file mode 100644
index a8680f71cdf..00000000000
--- a/runtime-plugins/control-sharing/app1/src/bootstrap.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import App from './App';
-import React from 'react';
-import ReactDOM from 'react-dom';
-
-ReactDOM.render(, document.getElementById('root'));
diff --git a/runtime-plugins/control-sharing/app1/src/index.js b/runtime-plugins/control-sharing/app1/src/index.js
index e59d6a0adf7..a8680f71cdf 100644
--- a/runtime-plugins/control-sharing/app1/src/index.js
+++ b/runtime-plugins/control-sharing/app1/src/index.js
@@ -1 +1,5 @@
-import './bootstrap';
+import App from './App';
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+ReactDOM.render(, document.getElementById('root'));
diff --git a/runtime-plugins/control-sharing/app1/webpack.config.js b/runtime-plugins/control-sharing/app1/webpack.config.js
index d5e64581886..969171baa57 100644
--- a/runtime-plugins/control-sharing/app1/webpack.config.js
+++ b/runtime-plugins/control-sharing/app1/webpack.config.js
@@ -48,6 +48,7 @@ module.exports = {
new ModuleFederationPlugin({
experiments: { asyncStartup: true },
name: 'app1',
+ shareStrategy: 'loaded-first',
filename: 'remoteEntry.js',
remotes: {
app2: 'app2@http://localhost:3002/remoteEntry.js',
diff --git a/runtime-plugins/control-sharing/app2/rspack.config.js b/runtime-plugins/control-sharing/app2/rspack.config.js
index 9bb18835fca..e2b6482b147 100644
--- a/runtime-plugins/control-sharing/app2/rspack.config.js
+++ b/runtime-plugins/control-sharing/app2/rspack.config.js
@@ -79,6 +79,7 @@ module.exports = {
new ModuleFederationPlugin({
experiments: { asyncStartup: true },
name: 'app2',
+ shareStrategy: 'loaded-first',
filename: 'remoteEntry.js',
remotes: {
app1: 'app1@http://localhost:3001/remoteEntry.js',
diff --git a/runtime-plugins/control-sharing/app2/src/bootstrap.js b/runtime-plugins/control-sharing/app2/src/bootstrap.js
deleted file mode 100644
index a8680f71cdf..00000000000
--- a/runtime-plugins/control-sharing/app2/src/bootstrap.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import App from './App';
-import React from 'react';
-import ReactDOM from 'react-dom';
-
-ReactDOM.render(, document.getElementById('root'));
diff --git a/runtime-plugins/control-sharing/app2/src/index.js b/runtime-plugins/control-sharing/app2/src/index.js
index e59d6a0adf7..a8680f71cdf 100644
--- a/runtime-plugins/control-sharing/app2/src/index.js
+++ b/runtime-plugins/control-sharing/app2/src/index.js
@@ -1 +1,5 @@
-import './bootstrap';
+import App from './App';
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+ReactDOM.render(, document.getElementById('root'));
diff --git a/runtime-plugins/control-sharing/app2/webpack.config.js b/runtime-plugins/control-sharing/app2/webpack.config.js
index d60e8eb4dad..2137633e90b 100644
--- a/runtime-plugins/control-sharing/app2/webpack.config.js
+++ b/runtime-plugins/control-sharing/app2/webpack.config.js
@@ -48,6 +48,7 @@ module.exports = {
new ModuleFederationPlugin({
experiments: { asyncStartup: true },
name: 'app2',
+ shareStrategy: 'loaded-first',
filename: 'remoteEntry.js',
remotes: {
app1: 'app1@http://localhost:3001/remoteEntry.js',
diff --git a/runtime-plugins/control-sharing/e2e/checkAutomaticVendorApps.spec.ts b/runtime-plugins/control-sharing/e2e/checkAutomaticVendorApps.spec.ts
index 3fbe136def7..5323423d57e 100644
--- a/runtime-plugins/control-sharing/e2e/checkAutomaticVendorApps.spec.ts
+++ b/runtime-plugins/control-sharing/e2e/checkAutomaticVendorApps.spec.ts
@@ -28,14 +28,16 @@ test.describe('Control Sharing', () => {
});
test('renders app cards with package information', async ({ page }) => {
- await expect(page.getByRole('heading', { level: 3, name: 'app1' })).toBeVisible();
+ const app1CardHeader = page.getByRole('heading', { level: 3, name: 'app1' });
+ await expect(app1CardHeader).toBeVisible();
+ const app1Card = app1CardHeader.locator('..');
for (const pkg of ['lodash', 'react-dom', 'react']) {
- await expect(page.locator('h4', { hasText: pkg })).toBeVisible();
+ await expect(app1Card.locator('h4', { hasText: pkg }).first()).toBeVisible();
}
- await expect(page.locator('p', { hasText: '4.17.21' })).toBeVisible();
- await expect(page.locator('p', { hasText: '17.0.2' })).toBeVisible();
+ await expect(app1Card.locator('p', { hasText: '4.17.21' }).first()).toBeVisible();
+ await expect(app1Card.locator('p', { hasText: '17.0.2' }).first()).toBeVisible();
});
test('exposes control buttons', async ({ page }) => {
@@ -44,10 +46,13 @@ test.describe('Control Sharing', () => {
});
test('lists version override options', async ({ page }) => {
- await expect(page.getByText('Override Version')).toBeVisible();
+ // "Override Version" appears once per shared package row; assert at least one is visible.
+ await expect(page.getByText('Override Version').first()).toBeVisible();
+ //