Skip to content

Commit e39f3bf

Browse files
amir-zahediclaudeCopilot
authored
fix(gdu): namespace runtime public path per MFE (#411)
* fix(gdu): namespace runtime public path per MFE to prevent collisions When multiple Vite-built MFEs load on the same page, they each set globalThis.__GDU_PUBLIC_PATH__ from their entry chunk. The last MFE to load wins, so earlier MFEs resolve their lazy-loaded CSS chunks from the wrong CDN path (e.g. fmo-app-shell CSS fetched from fmo-booking/, returning 403). Replace the single global with a per-MFE map: globalThis.__GDU_PUBLIC_PATHS__[projectName] Each MFE reads only its own slot, so concurrent MFEs no longer clobber each other's chunk resolution. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: Applies formatting * chore(gdu): add changeset for runtime public path collision fix Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Update packages/gdu/config/vite/plugins/runtimePublicPath.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix(gdu): use esbuild for CSS minification to support ::view-transition-* LightningCSS 1.31.1 (latest) cannot parse ::view-transition-* pseudo- elements, causing build failures for MFEs that use View Transitions CSS. Switch to esbuild for CSS minification which handles them correctly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent cf3db7c commit e39f3bf

File tree

8 files changed

+73
-32
lines changed

8 files changed

+73
-32
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
"gdu": patch
3+
---
4+
5+
fix(gdu): namespace runtime public path per MFE to prevent collisions
6+
7+
When multiple Vite-built MFEs load on the same page, they each set
8+
`globalThis.__GDU_PUBLIC_PATH__` from their entry chunk. The last MFE to
9+
load overwrites the global, causing earlier MFEs to resolve lazy-loaded CSS
10+
chunks from the wrong CDN path (e.g. fmo-app-shell CSS fetched from
11+
fmo-booking/, returning 403).
12+
13+
Replaced the single global with a per-MFE map
14+
`globalThis.__GDU_PUBLIC_PATHS__[projectName]` so each MFE reads only its
15+
own slot and concurrent MFEs no longer clobber each other's chunk resolution.

packages/gdu/config/vite/plugins/mfeEnvTokens.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,14 @@ import type { VitePlugin } from '../types';
1414
* cannot be constant-folded, so all tokens stay inside the `mfe-configs` chunk
1515
* where `tokenReplacement.sh` can find them.
1616
*/
17-
export function mfeEnvTokens(
18-
envTokens: Record<string, string>,
19-
): VitePlugin {
17+
export function mfeEnvTokens(envTokens: Record<string, string>): VitePlugin {
2018
const keys = Object.keys(envTokens);
2119
if (keys.length === 0) {
2220
return { name: 'gdu-mfe-env-tokens' };
2321
}
2422

2523
const escapedKeys = keys.map((k) =>
26-
k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'),
24+
k.replace(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`),
2725
);
2826
const pattern = new RegExp(
2927
`\\bprocess\\.env\\.(${escapedKeys.join('|')})\\b`,
@@ -48,7 +46,7 @@ export function mfeEnvTokens(
4846
(_, key) => `globalThis.__MFE_ENV__[${JSON.stringify(key)}]`,
4947
);
5048

51-
return result !== code ? { code: result, map: null } : null;
49+
return result === code ? null : { code: result, map: null };
5250
},
5351

5452
renderChunk(code, chunk) {

packages/gdu/config/vite/plugins/overdrive-export-manifest.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@ interface OverdriveExportEntry {
1313
originalName?: string;
1414
}
1515

16-
export const OVERDRIVE_EXPORT_MANIFEST: Record<
17-
string,
18-
OverdriveExportEntry
19-
> = {
16+
export const OVERDRIVE_EXPORT_MANIFEST: Record<string, OverdriveExportEntry> = {
2017
// ─── Components (73 directories, 110 exports) ────────────────────────
2118

2219
// Actions

packages/gdu/config/vite/plugins/overdrive-manifest-generator.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ type Manifest = Record<string, ManifestEntry>;
2020

2121
type OverdriveCategory = 'components' | 'hooks' | 'styles' | 'themes' | 'utils';
2222

23-
const RE_EXPORT_NAMED =
24-
/export\s*\{([^}]+)\}\s*from\s*["']([^"']+)["']/g;
23+
const RE_EXPORT_NAMED = /export\s*\{([^}]+)\}\s*from\s*["']([^"']+)["']/g;
2524

2625
function parseReExports(
2726
source: string,
@@ -37,7 +36,8 @@ function parseReExports(
3736

3837
const names = namesStr.split(',').map((part) => {
3938
const trimmed = part.trim();
40-
const asMatch = /^([a-zA-Z_$][\w$]*)\s+as\s+([a-zA-Z_$][\w$]*)$/.exec(trimmed);
39+
const asMatch =
40+
/^([a-zA-Z_$][\w$]*)\s+as\s+([a-zA-Z_$][\w$]*)$/.exec(trimmed);
4141
if (asMatch) {
4242
return { local: asMatch[1], exported: asMatch[2] };
4343
}
@@ -152,11 +152,19 @@ function generateManifestSource(manifest: Manifest): string {
152152
const lines: string[] = [];
153153

154154
lines.push(`/**`);
155-
lines.push(` * Auto-generated manifest mapping every named export from \`@autoguru/overdrive\``);
156-
lines.push(` * to its deep import path. Used by the Vite barrel-splitting plugin to rewrite`);
157-
lines.push(` * barrel imports into granular deep imports for optimal tree-shaking.`);
155+
lines.push(
156+
` * Auto-generated manifest mapping every named export from \`@autoguru/overdrive\``,
157+
);
158+
lines.push(
159+
` * to its deep import path. Used by the Vite barrel-splitting plugin to rewrite`,
160+
);
161+
lines.push(
162+
` * barrel imports into granular deep imports for optimal tree-shaking.`,
163+
);
158164
lines.push(` *`);
159-
lines.push(` * Regenerate with: npx tsx packages/gdu/config/vite/plugins/overdrive-manifest-generator.ts`);
165+
lines.push(
166+
` * Regenerate with: npx tsx packages/gdu/config/vite/plugins/overdrive-manifest-generator.ts`,
167+
);
160168
lines.push(` *`);
161169
lines.push(` * Total exports: ${entries.length}`);
162170
lines.push(` */`);
@@ -187,7 +195,9 @@ function generateManifestSource(manifest: Manifest): string {
187195

188196
lines.push('} as const;');
189197
lines.push('');
190-
lines.push("export const OVERDRIVE_PACKAGE = '@autoguru/overdrive' as const;");
198+
lines.push(
199+
"export const OVERDRIVE_PACKAGE = '@autoguru/overdrive' as const;",
200+
);
191201
lines.push('');
192202
lines.push('export const OVERDRIVE_EXPORT_COUNT = Object.keys(');
193203
lines.push('\tOVERDRIVE_EXPORT_MANIFEST,');
@@ -259,8 +269,7 @@ function main() {
259269
// 4. Parse top-level barrel for aliases and utils
260270
const topBarrel = path.join(distPath, 'index.js');
261271
if (fs.existsSync(topBarrel)) {
262-
const { manifest: topManifest } =
263-
processTopLevelBarrel(topBarrel);
272+
const { manifest: topManifest } = processTopLevelBarrel(topBarrel);
264273
Object.assign(manifest, topManifest);
265274
console.log(
266275
` Aliases & utils: ${Object.keys(topManifest).length} exports`,

packages/gdu/config/vite/plugins/overdriveBarrelSplit.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,12 @@ export function overdriveBarrelSplit(): VitePlugin {
4444

4545
const result = code.replace(
4646
importRegex,
47-
(match, typeModifier: string | undefined, specifiersStr: string, quote: string) => {
47+
(
48+
match,
49+
typeModifier: string | undefined,
50+
specifiersStr: string,
51+
quote: string,
52+
) => {
4853
const isTypeOnly = Boolean(typeModifier);
4954
const specifiers = parseSpecifiers(specifiersStr);
5055

@@ -53,7 +58,11 @@ export function overdriveBarrelSplit(): VitePlugin {
5358
// Group resolved specifiers by deep path; collect unresolved as residual
5459
const grouped = new Map<
5560
string,
56-
Array<{ local: string; imported: string; isType: boolean }>
61+
Array<{
62+
local: string;
63+
imported: string;
64+
isType: boolean;
65+
}>
5766
>();
5867
const residual: typeof specifiers = [];
5968

packages/gdu/config/vite/plugins/runtimePublicPath.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@ import type { VitePlugin } from '../types';
88
* #{PUBLIC_PATH_BASE} Octopus token cannot survive inside minified JS.
99
*
1010
* This plugin:
11-
* 1. Prepends entry chunks with a global that captures the CDN base from
12-
* import.meta.url (e.g. https://mfe.au-dev.autoguru.com/fmo-booking/)
13-
* 2. Patches the Vite preload helper so it reads the global instead of
14-
* using the hardcoded "/" base value.
11+
* 1. Prepends entry chunks with a namespaced global that captures the CDN
12+
* base from import.meta.url (e.g. https://mfe.au-dev.autoguru.com/fmo-booking/)
13+
* 2. Patches the Vite preload helper so it reads the namespaced global
14+
* instead of using the hardcoded "/" base value.
15+
*
16+
* Each MFE gets its own key inside `globalThis.__GDU_PUBLIC_PATHS__` so that
17+
* multiple MFEs on the same page don't clobber each other's public path.
1518
*/
16-
export function runtimePublicPath(): VitePlugin {
19+
export function runtimePublicPath(projectName: string): VitePlugin {
20+
const key = JSON.stringify(projectName);
21+
1722
return {
1823
name: 'gdu-runtime-public-path',
1924
apply: 'build',
@@ -22,23 +27,24 @@ export function runtimePublicPath(): VitePlugin {
2227
let modified = code;
2328
let changed = false;
2429

25-
// 1. Entry chunks: derive the public path from import.meta.url.
30+
// 1. Entry chunks: derive the public path from import.meta.url
31+
// and store it in a per-MFE slot on a shared global object.
2632
// e.g. "https://cdn/fmo-booking/main-abc.js" → "https://cdn/fmo-booking/"
2733
if (chunk.isEntry) {
2834
modified =
29-
'globalThis.__GDU_PUBLIC_PATH__=new URL(".",import.meta.url).href;' +
35+
`(globalThis.__GDU_PUBLIC_PATHS__=globalThis.__GDU_PUBLIC_PATHS__||{})[${key}]=new URL(".",import.meta.url).href;` +
3036
modified;
3137
changed = true;
3238
}
3339

3440
// 2. Patch the Vite preload helper's URL resolver function.
3541
// At renderChunk time (before final minification), the code is:
3642
// assetsURL = function(dep) { return "/" + dep; };
37-
// We replace the hardcoded "/" with the runtime global.
43+
// We replace the hardcoded "/" with the per-MFE runtime path.
3844
if (modified.includes('modulepreload')) {
3945
const patched = modified.replace(
40-
/return\s*["'`]\/["'`]\s*\+\s*(\w)/,
41-
'return(globalThis.__GDU_PUBLIC_PATH__||"/")+$1',
46+
/return\s*["'`]\/["'`]\s*\+\s*(\w+)/,
47+
`return((globalThis.__GDU_PUBLIC_PATHS__||{})[${key}]||"/")+$1`,
4248
);
4349
if (patched !== modified) {
4450
modified = patched;

packages/gdu/config/vite/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export interface InlineConfig {
103103
emptyOutDir?: boolean;
104104
sourcemap?: boolean | 'hidden';
105105
minify?: boolean | string;
106+
cssMinify?: boolean | 'esbuild' | 'lightningcss';
106107
reportCompressedSize?: boolean;
107108
chunkSizeWarningLimit?: number;
108109
rolldownOptions?: RolldownInputOptions & {

packages/gdu/config/vite/vite.config.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ export const baseViteOptions = ({
149149
emptyOutDir: true,
150150
sourcemap: 'hidden',
151151
minify: true,
152+
// LightningCSS cannot parse ::view-transition-* pseudo-elements,
153+
// causing minification failures. Use esbuild for CSS minification.
154+
cssMinify: 'esbuild',
152155
reportCompressedSize: false,
153156
chunkSizeWarningLimit: 1000,
154157
rolldownOptions: {
@@ -233,7 +236,10 @@ export const makeViteConfig = (
233236

234237
// Add the runtimePublicPath plugin for dynamic chunk resolution.
235238
// The manifest keeps bare filenames — the Lambda adds the CDN prefix.
236-
const plugins = [...(base.plugins || []), runtimePublicPath()];
239+
const plugins = [
240+
...(base.plugins || []),
241+
runtimePublicPath(getProjectName()),
242+
];
237243

238244
return {
239245
...base,

0 commit comments

Comments
 (0)