Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 26 additions & 0 deletions e2e/vite-webpack-rspack/tests/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,32 @@ test.describe('Dynamic remote', () => {
await expect(signUpBanner).toBeVisible();
await expect(specialPromoBanner).not.toBeVisible();
});

test('verifies shared lodash dependency', async ({ page, baseURL }) => {
await page.goto(baseURL!);
const showAdToggle = page.getByRole('checkbox', { name: 'Show Dynamic Ad', exact: true });

// Check that lodash version is displayed in SpecialPromo banner
await showAdToggle.check({ force: true });

const specialPromoBanner = page.getByRole('heading', { level: 2, name: 'Up to 50% off!', exact: true });
await expect(specialPromoBanner).toBeVisible();

const lodashVersionDisplay = page.getByTestId('lodash-version-display');
await expect(lodashVersionDisplay).toBeVisible();
const versionText = await lodashVersionDisplay.textContent();
expect(versionText).toMatch(/Shared lodash v\d+\.\d+\.\d+/);

// Toggle off and on again to check SignUpBanner
await showAdToggle.uncheck({ force: true });
await showAdToggle.check({ force: true });

const signUpBanner = page.getByRole('heading', { level: 2, name: 'Sign up now!', exact: true });
await expect(signUpBanner).toBeVisible();
await expect(lodashVersionDisplay).toBeVisible();
const versionText2 = await lodashVersionDisplay.textContent();
expect(versionText2).toMatch(/Shared lodash v\d+\.\d+\.\d+/);
});
});

test.describe('Tests remote', () => {
Expand Down
5 changes: 3 additions & 2 deletions examples/vite-webpack-rspack/dynamic-remote/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
"preview": "vite preview"
},
"dependencies": {
"@module-federation/vite": "workspace:*",
"lodash": "^4.17.21",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"@module-federation/vite": "workspace:*"
"react-dom": "^18.3.1"
},
"devDependencies": {
"@eslint/js": "^9.9.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import _ from 'lodash';

const SignUpBanner = () => {
return (
<div className="bg-blue-500 text-white p-4 m-4 rounded ring-offset-ring-4 flex justify-between">
<div>
<h2 className="text-xl">Sign up now!</h2>
<p>Get started with our amazing service today.</p>
<p data-testid="lodash-version-display">Shared lodash v{_.VERSION}</p>
<button className="bg-white text-blue-500 p-2 rounded mt-2">Sign up</button>
</div>
<img src="https://picsum.photos/200/200" alt="Random" className="mt-2" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import _ from 'lodash';

const SpecialPromo = () => {
return (
<div className="bg-red-500 text-white p-4 m-4 rounded ring-offset-ring-4 flex justify-between">
<div>
<h2 className="text-xl">Up to 50% off!</h2>
<p data-testid="lodash-version-display">Shared lodash v{_.VERSION}</p>
<p>Only for a limited time.</p>
</div>
<img src="https://picsum.photos/200/200" alt="Random" className="mt-2" />
Expand Down
6 changes: 5 additions & 1 deletion examples/vite-webpack-rspack/dynamic-remote/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ export default defineConfig({
'./SignUpBanner': './src/SignUpBanner.jsx',
'./SpecialPromo': './src/SpecialPromo.jsx',
},
shared: ['react', 'react-dom'],
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
lodash: { singleton: true, import: false },
},
}),
],
server: {
Expand Down
1 change: 1 addition & 0 deletions examples/vite-webpack-rspack/host/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
},
"dependencies": {
"@module-federation/vite": "workspace:*",
"lodash": "^4.17.21",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
Expand Down
3 changes: 3 additions & 0 deletions examples/vite-webpack-rspack/host/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { Toggle } from './components/Toggle';
import { useDynamicImport } from './hooks/useDynamicImport';
import './index.css';

import _ from 'lodash';
_.VERSION;

const RemoteProduct = lazy(
() =>
// @ts-ignore
Expand Down
3 changes: 2 additions & 1 deletion examples/vite-webpack-rspack/host/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const mfConfig = {
type: 'module',
},
},
shared: ['react', 'react-dom'],
shared: ['react', 'react-dom', 'lodash'],
};

// https://vitejs.dev/config/
Expand All @@ -38,5 +38,6 @@ export default defineConfig({
],
build: {
target: 'chrome89',
minify: false,
},
});
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
"homepage": "https://github.com/module-federation/vite#readme",
"packageManager": "[email protected]",
"dependencies": {
"@module-federation/runtime": "^0.17.1",
"@module-federation/runtime": "^0.18.3",
"@module-federation/sdk": "^0.18.3",
"@rollup/pluginutils": "^5.1.0",
"defu": "^6.1.4",
"estree-walker": "^2",
Expand Down
55 changes: 36 additions & 19 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion src/utils/normalizeModuleFederationOptions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { SharedConfig, ShareStrategy } from '@module-federation/runtime/types';
import type { sharePlugin } from '@module-federation/sdk';

export type RemoteEntryType =
| 'var'
Expand Down Expand Up @@ -104,7 +105,7 @@ export interface ShareItem {
version: string | undefined;
scope: string;
from: string;
shareConfig: SharedConfig;
shareConfig: SharedConfig & sharePlugin.SharedConfig;
}

function removePathFromNpmPackage(packageString: string): string {
Expand Down Expand Up @@ -157,6 +158,7 @@ function normalizeShareItem(
| string
| {
name: string;
import: sharePlugin.SharedConfig['import'];
version?: string;
shareScope?: string;
singleton?: boolean;
Expand Down Expand Up @@ -192,6 +194,7 @@ function normalizeShareItem(
scope: 'default',
from: '',
shareConfig: {
import: undefined,
singleton: false,
requiredVersion: version ? `^${version}` : '*',
},
Expand All @@ -203,6 +206,7 @@ function normalizeShareItem(
version: shareItem.version || version,
scope: shareItem.shareScope || 'default',
shareConfig: {
import: typeof shareItem === 'object' ? shareItem.import : undefined,
singleton: shareItem.singleton || false,
requiredVersion: shareItem.requiredVersion || (version ? `^${version}` : '*'),
strictVersion: !!shareItem.strictVersion,
Expand Down Expand Up @@ -289,6 +293,7 @@ export type ModuleFederationOptions = {
singleton?: boolean;
requiredVersion?: string;
strictVersion?: boolean;
import?: sharePlugin.SharedConfig['import'];
}
>
| undefined;
Expand Down
28 changes: 19 additions & 9 deletions src/virtualModules/virtualRemoteEntry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,23 @@ export function writeLocalSharedImportMap() {
export function generateLocalSharedImportMap() {
const options = getNormalizeModuleFederationOptions();
return `
import {loadShare} from "@module-federation/runtime";
const importMap = {
${Array.from(getUsedShares())
.sort()
.map(
(pkg) => `
.map((pkg) => {
const shareItem = getNormalizeShareItem(pkg);
return `
${JSON.stringify(pkg)}: async () => {
let pkg = await import("${getPreBuildLibImportId(pkg)}")
return pkg
${
shareItem?.shareConfig.import === false
? `throw new Error(\`Shared module '\${${JSON.stringify(pkg)}}' must be provided by host\`);`
: `let pkg = await import("${getPreBuildLibImportId(pkg)}");
return pkg;`
}
}
`
)
`;
})
.join(',')}
}
const usedShared = {
Expand All @@ -65,8 +71,11 @@ export function generateLocalSharedImportMap() {
loaded: false,
from: ${JSON.stringify(options.name)},
async get () {
if (${shareItem.shareConfig.import === false}) {
throw new Error(\`Shared module '\${${JSON.stringify(key)}}' must be provided by host\`);
}
usedShared[${JSON.stringify(key)}].loaded = true
const {${JSON.stringify(key)}: pkgDynamicImport} = importMap
const {${JSON.stringify(key)}: pkgDynamicImport} = importMap
const res = await pkgDynamicImport()
const exportModule = {...res}
// All npm packages pre-built by vite will be converted to esm
Expand All @@ -80,7 +89,8 @@ export function generateLocalSharedImportMap() {
},
shareConfig: {
singleton: ${shareItem.shareConfig.singleton},
requiredVersion: ${JSON.stringify(shareItem.shareConfig.requiredVersion)}
requiredVersion: ${JSON.stringify(shareItem.shareConfig.requiredVersion)},
${shareItem.shareConfig.import === false ? 'import: false,' : ''}
}
}
`;
Expand Down Expand Up @@ -178,7 +188,7 @@ const hostAutoInitModule = new VirtualModule('hostAutoInit', HOST_AUTO_INIT_TAG)
export function writeHostAutoInit() {
hostAutoInitModule.writeSync(`
const remoteEntryPromise = import("${REMOTE_ENTRY_ID}")
// __tla only serves as a hack for vite-plugin-top-level-await.
// __tla only serves as a hack for vite-plugin-top-level-await.
Promise.resolve(remoteEntryPromise)
.then(remoteEntry => {
return Promise.resolve(remoteEntry.__tla)
Expand Down