From 7d8ead31eb6a4d27d2d9b5766dc4c16cafcbbbbe Mon Sep 17 00:00:00 2001 From: Steve McNiven-Scott Date: Wed, 9 Sep 2026 19:59:08 -0400 Subject: [PATCH] fix(vite): replace the browser preload helper before esbuild minifies it Fixes #11423 --- .../helpers/dynamic-import-plugin.spec.ts | 21 ++++++++++++++++++- .../vite/helpers/dynamic-import-plugin.ts | 13 +++++++----- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/packages/vite/helpers/dynamic-import-plugin.spec.ts b/packages/vite/helpers/dynamic-import-plugin.spec.ts index cc3445c6ad..fc54821424 100644 --- a/packages/vite/helpers/dynamic-import-plugin.spec.ts +++ b/packages/vite/helpers/dynamic-import-plugin.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { transformDynamicImports } from './dynamic-import-plugin.js'; +import { dynamicImportPlugin, transformDynamicImports } from './dynamic-import-plugin.js'; const vitePreloadHelper = `const scriptRel = /* @__PURE__ */ (function detectScriptRel() { const relList = typeof document !== "undefined" && document.createElement("link").relList; @@ -37,4 +37,23 @@ describe('dynamic import plugin', () => { const source = 'export const value = 42;'; expect(transformDynamicImports(source)).toBe(source); }); + + // Minified builds rename __vitePreload before generateBundle; the browser + // helper then survived and its window.dispatchEvent masked every dynamic + // import failure as "window is not defined". + it('replaces the helper in renderChunk as a post plugin, ahead of esbuild minification', () => { + const plugin = dynamicImportPlugin() as any; + expect(plugin.enforce).toBe('post'); + expect(plugin.generateBundle).toBeUndefined(); + const result = plugin.renderChunk(`${vitePreloadHelper}\nconst page = () => __vitePreload(() => import('./page.js'), []);`); + expect(result.code).not.toContain('document'); + expect(result.code).toContain("import('~/page.js')"); + }); + + it('returns null from renderChunk when nothing changes, including on already-transformed code', () => { + const plugin = dynamicImportPlugin() as any; + expect(plugin.renderChunk('export const value = 42;')).toBeNull(); + const once = transformDynamicImports(`${vitePreloadHelper}\nexport const page = () => __vitePreload(() => import('./page.js'), []);`); + expect(plugin.renderChunk(once)).toBeNull(); + }); }); diff --git a/packages/vite/helpers/dynamic-import-plugin.ts b/packages/vite/helpers/dynamic-import-plugin.ts index 9bdb1944ea..937edd569c 100644 --- a/packages/vite/helpers/dynamic-import-plugin.ts +++ b/packages/vite/helpers/dynamic-import-plugin.ts @@ -62,15 +62,18 @@ export function transformDynamicImports(code: string) { // Fix NativeScript dynamic imports by transforming paths and simplifying __vitePreload. // Vite still emits its browser preload helper when modulePreload is disabled: // https://github.com/vitejs/vite/issues/13952 +// Runs in renderChunk as a post plugin: user post plugins precede Vite's +// esbuild minifier, so `__vitePreload` is still named when we look for it. export function dynamicImportPlugin() { return { name: 'nativescript-dynamic-import-fix', - generateBundle(_options, bundle) { - for (const chunk of Object.values(bundle) as any) { - if (chunk.type === 'chunk') { - chunk.code = transformDynamicImports(chunk.code); - } + enforce: 'post' as const, + renderChunk(code: string) { + const transformed = transformDynamicImports(code); + if (transformed === code) { + return null; } + return { code: transformed, map: null }; }, }; }