Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
60 changes: 53 additions & 7 deletions .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ permissions:
pull-requests: read

jobs:
run-playwright-tests:
name: Playwright Tests
# Dev mode tests - uses vite dev server
run-playwright-tests-dev:
name: Playwright Tests (Dev Mode)
runs-on: ubuntu-latest
container: node:20
steps:
Expand All @@ -31,19 +32,64 @@ jobs:
- name: Install Chromium Browser
run: pnpm playwright install --with-deps chromium

- name: Build Projects
- name: Build Plugin
run: pnpm build

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

- name: Run Playwright Tests
run: pnpm playwright test
- name: Run Playwright Tests (Dev)
run: pnpm playwright test --project=multi-example

- name: Upload Artifacts on Failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: test-results
name: test-results-dev
path: reports/e2e/output
retention-days: 3

# Build/Preview mode tests - validates production builds work correctly
# This catches issues like Vite 7/Rolldown CJS wrapper problems
run-playwright-tests-preview:
name: Playwright Tests (Build/Preview Mode)
runs-on: ubuntu-latest
container: node:20
steps:
- name: Checkout
uses: actions/checkout@v5

- name: Enable Corepack and Setup PNPM
run: |
corepack enable
corepack prepare pnpm@9.1.3 --activate

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

- name: Install Chromium Browser
run: pnpm playwright install --with-deps chromium

- name: Build Plugin
run: pnpm build

- name: Build Example Apps
run: pnpm --filter "multi-example-*" run build

- name: Start Application (Preview Mode)
run: |
cd examples/vite-webpack-rspack/host
nohup pnpm preview --port 4173 &
cd ../../..
pnpm exec wait-on http://localhost:4173

- name: Run Playwright Tests (Preview)
run: pnpm playwright test --project=multi-example-preview

- name: Upload Artifacts on Failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: test-results-preview
path: reports/e2e/output
retention-days: 3
9 changes: 9 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ export default defineConfig({
browserName: 'chromium',
},
},
// Test production build via preview mode - validates Vite 7/Rolldown compatibility
{
name: 'multi-example-preview',
testDir: 'e2e/vite-webpack-rspack',
use: {
baseURL: 'http://localhost:4173',
browserName: 'chromium',
},
},
],
outputDir: 'reports/e2e/output',
reporter: [
Expand Down
24 changes: 18 additions & 6 deletions src/virtualModules/virtualRemotes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,22 @@ export function getUsedRemotesMap() {
return usedRemotesMap;
}
export function generateRemotes(id: string, command: string) {
return `
const {initPromise} = require("${virtualRuntimeInitStatus.getImportId()}")
const res = initPromise.then(runtime => runtime.loadRemote(${JSON.stringify(id)}))
const exportModule = ${command !== 'build' ? '/*mf top-level-await placeholder replacement mf*/' : 'await '}initPromise.then(_ => res)
module.exports = exportModule
`;
if (command === 'build') {
// Build mode: Use ESM syntax to fix Vite 7/Rolldown compatibility
// Rolldown wraps CJS (require + module.exports) in a function, breaking top-level await
return `
import { initPromise } from "${virtualRuntimeInitStatus.getImportId()}"
const res = initPromise.then(runtime => runtime.loadRemote(${JSON.stringify(id)}))
const exportModule = await initPromise.then(_ => res)
export default exportModule
`;
} else {
// Dev mode: Use original CJS syntax for compatibility with existing plugins
return `
const {initPromise} = require("${virtualRuntimeInitStatus.getImportId()}")
const res = initPromise.then(runtime => runtime.loadRemote(${JSON.stringify(id)}))
const exportModule = /*mf top-level-await placeholder replacement mf*/initPromise.then(_ => res)
module.exports = exportModule
`;
}
}
6 changes: 6 additions & 0 deletions src/virtualModules/virtualRuntimeInitStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ export const virtualRuntimeInitStatus = new VirtualModule('runtimeInit');
export function writeRuntimeInitStatus() {
// Use globalThis singleton to ensure only one initPromise exists
const globalKey = `__mf_init__${virtualRuntimeInitStatus.getImportId()}__`;
// This module is imported by both dev and build modes
// We use a dual-export pattern that works with both CJS require() and ESM import
virtualRuntimeInitStatus.writeSync(`
const globalKey = ${JSON.stringify(globalKey)}
if (!globalThis[globalKey]) {
Expand All @@ -17,6 +19,10 @@ export function writeRuntimeInitStatus() {
initReject
}
}
// Dual exports: CJS for dev mode (require), ESM for build mode (import)
module.exports = globalThis[globalKey]
export const initPromise = globalThis[globalKey].initPromise
export const initResolve = globalThis[globalKey].initResolve
export const initReject = globalThis[globalKey].initReject
`);
}
48 changes: 33 additions & 15 deletions src/virtualModules/virtualShared_preBuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,37 @@ export function getLoadShareModulePath(pkg: string): string {
return filepath;
}
export function writeLoadShareModule(pkg: string, shareItem: ShareItem, command: string) {
loadShareCacheMap[pkg].writeSync(`
;() => import(${JSON.stringify(getPreBuildLibImportId(pkg))}).catch(() => {});
// dev uses dynamic import to separate chunks
${command !== 'build' ? `;() => import(${JSON.stringify(pkg)}).catch(() => {});` : ''}
const {initPromise} = require("${virtualRuntimeInitStatus.getImportId()}")
const res = initPromise.then(runtime => runtime.loadShare(${JSON.stringify(pkg)}, {
customShareInfo: {shareConfig:{
singleton: ${shareItem.shareConfig.singleton},
strictVersion: ${shareItem.shareConfig.strictVersion},
requiredVersion: ${JSON.stringify(shareItem.shareConfig.requiredVersion)}
}}
}))
const exportModule = ${command !== 'build' ? '/*mf top-level-await placeholder replacement mf*/' : 'await '}res.then(factory => factory())
module.exports = exportModule
`);
if (command === 'build') {
// Build mode: Use ESM syntax to fix Vite 7/Rolldown compatibility
// Rolldown wraps CJS (require + module.exports) in a function, breaking top-level await
loadShareCacheMap[pkg].writeSync(`
import { initPromise } from "${virtualRuntimeInitStatus.getImportId()}"
;() => import(${JSON.stringify(getPreBuildLibImportId(pkg))}).catch(() => {});
const res = initPromise.then(runtime => runtime.loadShare(${JSON.stringify(pkg)}, {
customShareInfo: {shareConfig:{
singleton: ${shareItem.shareConfig.singleton},
strictVersion: ${shareItem.shareConfig.strictVersion},
requiredVersion: ${JSON.stringify(shareItem.shareConfig.requiredVersion)}
}}
}))
const exportModule = await res.then(factory => factory())
export default exportModule
`);
} else {
// Dev mode: Use original CJS syntax for compatibility with existing plugins
loadShareCacheMap[pkg].writeSync(`
;() => import(${JSON.stringify(getPreBuildLibImportId(pkg))}).catch(() => {});
;() => import(${JSON.stringify(pkg)}).catch(() => {});
const {initPromise} = require("${virtualRuntimeInitStatus.getImportId()}")
const res = initPromise.then(runtime => runtime.loadShare(${JSON.stringify(pkg)}, {
customShareInfo: {shareConfig:{
singleton: ${shareItem.shareConfig.singleton},
strictVersion: ${shareItem.shareConfig.strictVersion},
requiredVersion: ${JSON.stringify(shareItem.shareConfig.requiredVersion)}
}}
}))
const exportModule = /*mf top-level-await placeholder replacement mf*/res.then(factory => factory())
module.exports = exportModule
`);
}
}
Loading