Skip to content
Closed
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
5 changes: 5 additions & 0 deletions .changeset/loose-donuts-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@module-federation/vite': minor
---

feat: add target option to configuration for better tree-shaking
5 changes: 5 additions & 0 deletions .changeset/perky-mangos-say.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@module-federation/vite': minor
---

feat: allow all v7 version of vite
5 changes: 1 addition & 4 deletions .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: multi-example - e2e tests
name: multi-example e2e tests

on:
push:
Expand Down Expand Up @@ -34,9 +34,6 @@ jobs:
- name: Build Projects
run: pnpm build

- name: Start Application multi-example
run: nohup pnpm run multi-example & pnpm exec wait-on http://localhost:5173;

- name: Run Playwright Tests
run: pnpm playwright test

Expand Down
44 changes: 44 additions & 0 deletions .github/workflows/integration-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Integration Test

on:
pull_request:
branches:
- main

permissions:
pull-requests: read

jobs:
integration-test:
name: Integration (vite ${{ matrix.vite-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
vite-version: ['5', '6', '7']
steps:
- name: Checkout
uses: actions/checkout@v5

- name: PNPM Install
uses: pnpm/action-setup@v4
with:
version: 10.28.2

- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: 24
cache: 'pnpm'
registry-url: https://registry.npmjs.org/

- run: corepack enable

- name: Install NPM Dependencies
run: pnpm install --frozen-lockfile

- name: Override vite version
run: pnpm add -Dw vite@${{ matrix.vite-version }}

- name: Run Integration Tests
run: pnpm test:integration
33 changes: 33 additions & 0 deletions e2e/vite-vite/tests/host-preview.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { expect, test } from '@playwright/test';

/**
* These tests run against the host preview (port 5175) which loads remote
* modules from the remote preview (port 5176). Both must be running.
*
* This exercises the full Module Federation pipeline in build mode:
* shared deps, default imports, named imports, CJS interop, etc.
*/
test.describe('vite-vite host preview', () => {
test('renders host app with React shared dep', async ({ page }) => {
await page.goto('/');
const heading = page.getByRole('heading', { name: 'MF HOST Demo', exact: true });
await expect(heading).toBeVisible();
});

test('renders Emotion styled component from remote', async ({ page }) => {
await page.goto('/');
// EmotionDemo uses `import styled from '@emotion/styled'` (default import).
// This breaks if the ESM shims plugin doesn't handle default export interop.
const emotionText = page.getByText('Heading with a green background and yellow text.');
await expect(emotionText).toBeVisible();
});

test('renders Styled Components demo from remote', async ({ page }) => {
await page.goto('/');
const heading = page.getByRole('heading', {
name: 'Styled Components Demo',
exact: true,
});
await expect(heading).toBeVisible();
});
});
9 changes: 9 additions & 0 deletions e2e/vite-vite/tests/remote-preview.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { expect, test } from '@playwright/test';

test.describe('vite-vite remote preview', () => {
test('renders the remote app', async ({ page }) => {
await page.goto('/');
const heading = page.getByRole('heading', { name: 'Vite + React', exact: true });
await expect(heading).toBeVisible();
});
});
2 changes: 1 addition & 1 deletion examples/rust-vite/vite-remote/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"devDependencies": {
"@swc/core": "~1.6.0",
"@vitejs/plugin-react": "^4.3.1",
"vite": "^5.3.1",
"vite": "catalog:",
"vite-plugin-top-level-await": "^1.4.1"
}
}
4 changes: 2 additions & 2 deletions examples/vite-vite/vite-host/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"scripts": {
"dev": "vite --force",
"build": "vite build",
"preview": "vite build && vite preview"
"preview": "vite build && vite serve"
},
"dependencies": {
"@emotion/react": "^11.13.0",
Expand All @@ -23,7 +23,7 @@
"devDependencies": {
"@swc/core": "~1.7.10",
"@vitejs/plugin-react": "^5.1.2",
"vite": "^7.1.7",
"vite": "catalog:",
"vite-plugin-top-level-await": "^1.4.4"
}
}
7 changes: 5 additions & 2 deletions examples/vite-vite/vite-host/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import topLevelAwait from 'vite-plugin-top-level-await';
// https://vitejs.dev/config/
export default defineConfig({
server: {
open: true,
open: false,
port: 5175,
},
preview: {
Expand All @@ -28,8 +28,11 @@ export default defineConfig({
vue: {},
'react/': {
requiredVersion: '18',
singleton: true,
},
'react-dom': {
singleton: true,
},
'react-dom': {},
'ag-grid-community': {},
'ag-grid-react': {},
'@emotion/react': {},
Expand Down
4 changes: 2 additions & 2 deletions examples/vite-vite/vite-remote/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite build&&vite preview"
"preview": "vite build && vite serve"
},
"dependencies": {
"@emotion/react": "^11.13.0",
Expand All @@ -24,7 +24,7 @@
"devDependencies": {
"@swc/core": "~1.7.10",
"@vitejs/plugin-react": "^5.1.2",
"vite": "^7.1.7",
"vite": "catalog:",
"vite-plugin-top-level-await": "^1.4.4"
}
}
9 changes: 5 additions & 4 deletions examples/vite-vite/vite-remote/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import topLevelAwait from 'vite-plugin-top-level-await';
// https://vitejs.dev/config/
export default defineConfig({
server: {
open: true,
open: false,
port: 5176,
origin: 'http://localhost:5176',
},
Expand All @@ -33,12 +33,13 @@ export default defineConfig({
manifest: true,
shared: {
vue: {},
'react/': {},
'react/': { singleton: true },
react: {
singleton: true,
requiredVersion: '18',
},
'react-dom/': {},
'react-dom': {},
'react-dom/': { singleton: true },
'react-dom': { singleton: true },
'styled-components': { singleton: true },
'ag-grid-community/': {},
'ag-grid-react': {},
Expand Down
2 changes: 1 addition & 1 deletion examples/vite-webpack-rspack/dynamic-remote/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@
"@vitejs/plugin-react": "^4.3.1",
"globals": "^15.9.0",
"tailwindcss": "^3.4.13",
"vite": "^5.4.1"
"vite": "catalog:"
}
}
2 changes: 1 addition & 1 deletion examples/vite-webpack-rspack/host/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@
"@vitejs/plugin-react": "^4.3.1",
"globals": "^15.9.0",
"tailwindcss": "^3.4.13",
"vite": "^5.4.1"
"vite": "catalog:"
}
}
2 changes: 1 addition & 1 deletion examples/vite-webpack-rspack/remote/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
"@vitejs/plugin-react": "^4.3.1",
"globals": "^15.9.0",
"tailwindcss": "^3.4.13",
"vite": "^5.4.1"
"vite": "catalog:"
}
}
2 changes: 1 addition & 1 deletion examples/vite-webpack-rspack/tests-remote/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@
"@vitejs/plugin-react": "^4.3.1",
"globals": "^15.9.0",
"tailwindcss": "^3.4.13",
"vite": "^5.4.1"
"vite": "catalog:"
}
}
46 changes: 46 additions & 0 deletions integration/build.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { resolve } from 'path';
import { describe, expect, it } from 'vitest';
import { buildFixture, FIXTURES } from './helpers/build';
import { findAsset, findChunk, getAllChunkCode, getChunkNames } from './helpers/matchers';

const BASIC_REMOTE_MF_OPTIONS = {
exposes: {
'./exposed': resolve(FIXTURES, 'basic-remote', 'exposed-module.js'),
},
};

describe('build', () => {
describe('remote', () => {
it('produces a remoteEntry chunk', async () => {
const output = await buildFixture({ mfOptions: BASIC_REMOTE_MF_OPTIONS });
const chunks = getChunkNames(output);
expect(chunks.some((name) => name.includes('remoteEntry'))).toBe(true);
});

it('remoteEntry contains federation runtime init with correct name', async () => {
const output = await buildFixture({ mfOptions: BASIC_REMOTE_MF_OPTIONS });
const remoteEntry = findChunk(output, 'remoteEntry');
expect(remoteEntry).toBeDefined();
expect(remoteEntry!.code).toContain('basicRemote');
expect(remoteEntry!.code).toContain('moduleCache');
});

it('exposed module content is included in output', async () => {
const output = await buildFixture({ mfOptions: BASIC_REMOTE_MF_OPTIONS });
const allCode = getAllChunkCode(output);
expect(allCode).toContain('Hello');
});

it('generates mf-manifest.json when manifest is enabled', async () => {
const manifestOutput = await buildFixture({
mfOptions: { ...BASIC_REMOTE_MF_OPTIONS, manifest: true },
});

const manifest = findAsset(manifestOutput, 'mf-manifest.json');
expect(manifest).toBeDefined();

const parsed = JSON.parse(manifest!.source as string);
expect(parsed).toHaveProperty('exposes');
});
});
});
73 changes: 73 additions & 0 deletions integration/css-manifest.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { resolve } from 'path';
import { describe, expect, it } from 'vitest';
import type { ModuleFederationOptions } from '../src/utils/normalizeModuleFederationOptions';
import { buildFixture, FIXTURES } from './helpers/build';
import { parseManifest } from './helpers/matchers';

const CSS_BASE_MF_OPTIONS = {
name: 'cssRemote',
filename: 'remoteEntry.js',
exposes: {
'./widget': resolve(FIXTURES, 'css-remote', 'exposed-module.js'),
},
manifest: true,
dts: false,
} satisfies Partial<ModuleFederationOptions>;

interface ManifestExpose {
id: string;
name: string;
path: string;
assets: {
js: { sync: string[]; async: string[] };
css: { sync: string[]; async: string[] };
};
}

describe('css manifest', () => {
it('tracks CSS and JS assets under the correct expose', async () => {
const output = await buildFixture({
fixture: 'css-remote',
mfOptions: CSS_BASE_MF_OPTIONS,
});
const manifest = parseManifest(output) as Record<string, unknown>;
expect(manifest).toBeDefined();
expect(manifest).toHaveProperty('exposes');

const exposes = manifest.exposes as ManifestExpose[];
const widget = exposes.find((e) => e.name === 'widget');
expect(widget).toBeDefined();

const allCssFiles = [...widget!.assets.css.sync, ...widget!.assets.css.async];
expect(allCssFiles.length).toBeGreaterThanOrEqual(1);
for (const file of allCssFiles) {
expect(file).toMatch(/\.css$/);
}

const allJsFiles = [...widget!.assets.js.sync, ...widget!.assets.js.async];
expect(allJsFiles.length).toBeGreaterThanOrEqual(1);
for (const file of allJsFiles) {
expect(file).toMatch(/\.js$/);
}
});

it('adds CSS to all exposes when bundleAllCSS is enabled', async () => {
const output = await buildFixture({
fixture: 'css-remote',
mfOptions: { ...CSS_BASE_MF_OPTIONS, bundleAllCSS: true },
});
const manifest = parseManifest(output) as Record<string, unknown>;
expect(manifest).toBeDefined();

const exposes = manifest.exposes as ManifestExpose[];
expect(exposes.length).toBeGreaterThanOrEqual(1);

for (const expose of exposes) {
const cssCount = expose.assets.css.sync.length + expose.assets.css.async.length;
expect(
cssCount,
`expose "${expose.name}" should have at least one CSS asset`
).toBeGreaterThanOrEqual(1);
}
});
});
Loading