From 08be9735dc75272d1f36838d8dca02d54fb1fa89 Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Wed, 25 Feb 2026 18:45:43 +0100 Subject: [PATCH 01/34] update to-do --- .../resolveVikeConfigInternal/transpileAndExecuteFile.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts index 033feec907b..1ead8c16a17 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts @@ -4,6 +4,10 @@ export { getConfigExecutionErrorIntroMsg } export { isTemporaryBuildFile } export type { EsbuildCache } +// TODO/ai: +// - Improve transpilation by resolving any non-JavaScript import (e.g. `.svg`) to a constant string `'STATIC_FILE_NOT_AVAILABLE:/path/to/static/file'` so that + files with `env: { config: true, client: true }` can also be loaded in Node.js +// - Use utils/isScriptFile.ts#L1 + import { build, type BuildResult, From 4c95206dc224e12679c4c5b7153c57c07f43a1bf Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Wed, 25 Feb 2026 18:48:49 +0100 Subject: [PATCH 02/34] [AI] Resolve non-script imports to constant string in esbuild transpilation Non-JS imports (e.g. .svg, .css) are now resolved to 'STATIC_FILE_NOT_AVAILABLE:/path/to/file' instead of being treated as pointer imports, enabling files with `env: { config: true, client: true }` to be loaded in Node.js. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../transpileAndExecuteFile.ts | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts index 1ead8c16a17..63c61daffba 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts @@ -4,10 +4,6 @@ export { getConfigExecutionErrorIntroMsg } export { isTemporaryBuildFile } export type { EsbuildCache } -// TODO/ai: -// - Improve transpilation by resolving any non-JavaScript import (e.g. `.svg`) to a constant string `'STATIC_FILE_NOT_AVAILABLE:/path/to/static/file'` so that + files with `env: { config: true, client: true }` can also be loaded in Node.js -// - Use utils/isScriptFile.ts#L1 - import { build, type BuildResult, @@ -30,7 +26,7 @@ import { genPromise } from '../../../../utils/genPromise.js' import { assertFilePathAbsoluteFilesystem } from '../../../../utils/isFilePathAbsoluteFilesystem.js' import { isImportPathRelative } from '../../../../utils/isImportPath.js' import { isObject } from '../../../../utils/isObject.js' -import { isPlainScriptFile } from '../../../../utils/isScriptFile.js' +import { isPlainScriptFile, isScriptFile } from '../../../../utils/isScriptFile.js' import { isVitest } from '../../../../utils/isVitest.js' import { assertIsImportPathNpmPackage, isImportPathNpmPackageOrPathAlias } from '../../../../utils/parseNpmPackage.js' import { assertPosixPath, toPosixPath } from '../../../../utils/path.js' @@ -241,13 +237,23 @@ async function transpileWithEsbuild( // - isImportPathNpmPackage(str, { cannotBePathAlias: true }) assertFilePathAbsoluteFilesystem(importPathResolved) + // Non-script file (e.g. .svg, .css) => resolve to constant string so that files with + // `env: { config: true, client: true }` can also be loaded in Node.js + if (!isScriptFile(importPathResolved)) { + esbuildCache.vikeConfigDependencies.add(importPathResolved) + return { + path: importPathResolved, + namespace: 'vike-static-file', + } + } + // Should be remove this? See comment below. const isVikeExtensionImport = (path.startsWith('vike-') && path.endsWith('/config')) || importPathResolved.endsWith('+config.js') const isPointerImport = transformImports === 'all' || - // .jsx, .vue, .svg, ... => obviously not config code => pointer import + // .jsx, .tsx, .vue, .svelte, ... => template/JSX files => pointer import !isPlainScriptFile(importPathResolved) || // Import of a Vike extension config => make it a pointer import because we want to show nice error messages (that can display whether a config has been set by the user or by a Vike extension). // - Should we stop doing this? (And instead let Node.js directly load Vike extensions.) @@ -328,6 +334,12 @@ async function transpileWithEsbuild( pointerImports[importPathTranspiled] = isPointerImport return { external: true, path: importPathTranspiled } }) + build.onLoad({ filter: /.*/, namespace: 'vike-static-file' }, (args) => { + return { + contents: `export default 'STATIC_FILE_NOT_AVAILABLE:${args.path}'`, + loader: 'js', + } + }) }, }, // Track dependencies From dbbc371d129a1a4000ac52a4528444ab741df2b6 Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Wed, 25 Feb 2026 19:07:10 +0100 Subject: [PATCH 03/34] comment --- .../shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts index 63c61daffba..0f7fd4822a8 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts @@ -238,7 +238,7 @@ async function transpileWithEsbuild( assertFilePathAbsoluteFilesystem(importPathResolved) // Non-script file (e.g. .svg, .css) => resolve to constant string so that files with - // `env: { config: true, client: true }` can also be loaded in Node.js + // `meta.env: { config: true, client: true }` can also be loaded in Node.js if (!isScriptFile(importPathResolved)) { esbuildCache.vikeConfigDependencies.add(importPathResolved) return { From 78a5297b260cb667488901d4b3cbbb080859e916 Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 16:59:34 +0100 Subject: [PATCH 04/34] wip --- .../resolveVikeConfigInternal/transpileAndExecuteFile.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts index 0f7fd4822a8..22a0581cb7c 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts @@ -283,10 +283,10 @@ async function transpileWithEsbuild( return false })() - const isExternal = - isPointerImport || - // Performance: npm package imports can be externalized. (We could as well let esbuild transpile /node_modules/ code but it's useless as /node_modules/ code is already built. It would unnecessarily slow down transpilation.) - isNpmPkgImport + const isExternal = isPointerImport + // || TODO comment + // Performance: npm package imports can be externalized. (We could as well let esbuild transpile /node_modules/ code but it's useless as /node_modules/ code is already built. It would unnecessarily slow down transpilation.) + // isNpmPkgImport if (!isExternal) { // User-land config code (i.e. not runtime code) => let esbuild transpile it From 8498b7fb8f4150c81f2b093bc9833a07bbba6ab2 Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 17:02:00 +0100 Subject: [PATCH 05/34] Revert "wip" This reverts commit 82a19ffcde861d0e97f6b3a0d2d4bb007b82b643. --- .../resolveVikeConfigInternal/transpileAndExecuteFile.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts index 22a0581cb7c..0f7fd4822a8 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts @@ -283,10 +283,10 @@ async function transpileWithEsbuild( return false })() - const isExternal = isPointerImport - // || TODO comment - // Performance: npm package imports can be externalized. (We could as well let esbuild transpile /node_modules/ code but it's useless as /node_modules/ code is already built. It would unnecessarily slow down transpilation.) - // isNpmPkgImport + const isExternal = + isPointerImport || + // Performance: npm package imports can be externalized. (We could as well let esbuild transpile /node_modules/ code but it's useless as /node_modules/ code is already built. It would unnecessarily slow down transpilation.) + isNpmPkgImport if (!isExternal) { // User-land config code (i.e. not runtime code) => let esbuild transpile it From f19c1c5b6f203f2203fc68b5396d28ada6dd3109 Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 17:03:58 +0100 Subject: [PATCH 06/34] update to-do --- .../shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts index 0f7fd4822a8..c66a04d6eab 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts @@ -336,6 +336,7 @@ async function transpileWithEsbuild( }) build.onLoad({ filter: /.*/, namespace: 'vike-static-file' }, (args) => { return { + // TODO/ai also do this for each import with import attribute `with { type: 'runtime' }` contents: `export default 'STATIC_FILE_NOT_AVAILABLE:${args.path}'`, loader: 'js', } From 39006e4eadc0cadcb42ab3825659c00cc82b4316 Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 17:06:17 +0100 Subject: [PATCH 07/34] [AI] Handle `with { type: 'runtime' }` imports as static file stubs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../resolveVikeConfigInternal/transpileAndExecuteFile.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts index c66a04d6eab..a693ccc80f3 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts @@ -237,9 +237,11 @@ async function transpileWithEsbuild( // - isImportPathNpmPackage(str, { cannotBePathAlias: true }) assertFilePathAbsoluteFilesystem(importPathResolved) - // Non-script file (e.g. .svg, .css) => resolve to constant string so that files with - // `meta.env: { config: true, client: true }` can also be loaded in Node.js - if (!isScriptFile(importPathResolved)) { + // Non-script file (e.g. .svg, .css) or explicitly tagged as runtime-only + // (`import url from './logo.svg' with { type: 'runtime' }`) => resolve to + // constant string so that files with `env: { config: true, client: true }` + // can also be loaded in Node.js + if (!isScriptFile(importPathResolved) || args.with?.['type'] === 'runtime') { esbuildCache.vikeConfigDependencies.add(importPathResolved) return { path: importPathResolved, @@ -336,7 +338,6 @@ async function transpileWithEsbuild( }) build.onLoad({ filter: /.*/, namespace: 'vike-static-file' }, (args) => { return { - // TODO/ai also do this for each import with import attribute `with { type: 'runtime' }` contents: `export default 'STATIC_FILE_NOT_AVAILABLE:${args.path}'`, loader: 'js', } From d04e8527c39337bfea8b864eef78bce8ff938a65 Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 17:14:04 +0100 Subject: [PATCH 08/34] [AI] Fix: script files with 'runtime' attr should be pointer imports, not stubs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../transpileAndExecuteFile.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts index a693ccc80f3..d7bd5ed5a50 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts @@ -237,11 +237,9 @@ async function transpileWithEsbuild( // - isImportPathNpmPackage(str, { cannotBePathAlias: true }) assertFilePathAbsoluteFilesystem(importPathResolved) - // Non-script file (e.g. .svg, .css) or explicitly tagged as runtime-only - // (`import url from './logo.svg' with { type: 'runtime' }`) => resolve to - // constant string so that files with `env: { config: true, client: true }` - // can also be loaded in Node.js - if (!isScriptFile(importPathResolved) || args.with?.['type'] === 'runtime') { + // Non-script file (e.g. .svg, .css) => resolve to constant string so that files with + // `env: { config: true, client: true }` can also be loaded in Node.js + if (!isScriptFile(importPathResolved)) { esbuildCache.vikeConfigDependencies.add(importPathResolved) return { path: importPathResolved, @@ -262,7 +260,9 @@ async function transpileWithEsbuild( // - In principle, we can use the setting 'name' value of Vike extensions. // - vike@0.4.162 started soft-requiring Vike extensions to set the name config. // - In practice, it seems like it requires some (non-trivial?) refactoring. - isVikeExtensionImport + isVikeExtensionImport || + // Explicitly tagged as runtime code => pointer import + args.with?.['type'] === 'runtime' assertPosixPath(importPathResolved) // `isNpmPkgImport` => `importPathOriginal` is most likely an npm package import, but it can also be a path alias that a) looks like an npm package import and b) resolves outside of `userRootDir`. From 9dfdeb2d8a2b8b23984f5d8ab6c779f935895b30 Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 17:19:07 +0100 Subject: [PATCH 09/34] [AI] Stub all named exports for vike-static-file imports Use a cached esbuild sub-call to enumerate a file's exports, so that named imports from runtime-tagged or non-script files all resolve to the STATIC_FILE_NOT_AVAILABLE stub string. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../transpileAndExecuteFile.ts | 46 +++++++++++++++---- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts index d7bd5ed5a50..c26268a4947 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts @@ -237,9 +237,10 @@ async function transpileWithEsbuild( // - isImportPathNpmPackage(str, { cannotBePathAlias: true }) assertFilePathAbsoluteFilesystem(importPathResolved) - // Non-script file (e.g. .svg, .css) => resolve to constant string so that files with - // `env: { config: true, client: true }` can also be loaded in Node.js - if (!isScriptFile(importPathResolved)) { + // Non-script file (e.g. .svg, .css) or explicitly tagged as runtime-only + // (`import ... with { type: 'runtime' }`) => resolve to constant string so + // that files with `env: { config: true, client: true }` can also be loaded in Node.js + if (!isScriptFile(importPathResolved) || args.with?.['type'] === 'runtime') { esbuildCache.vikeConfigDependencies.add(importPathResolved) return { path: importPathResolved, @@ -260,9 +261,7 @@ async function transpileWithEsbuild( // - In principle, we can use the setting 'name' value of Vike extensions. // - vike@0.4.162 started soft-requiring Vike extensions to set the name config. // - In practice, it seems like it requires some (non-trivial?) refactoring. - isVikeExtensionImport || - // Explicitly tagged as runtime code => pointer import - args.with?.['type'] === 'runtime' + isVikeExtensionImport assertPosixPath(importPathResolved) // `isNpmPkgImport` => `importPathOriginal` is most likely an npm package import, but it can also be a path alias that a) looks like an npm package import and b) resolves outside of `userRootDir`. @@ -336,11 +335,14 @@ async function transpileWithEsbuild( pointerImports[importPathTranspiled] = isPointerImport return { external: true, path: importPathTranspiled } }) - build.onLoad({ filter: /.*/, namespace: 'vike-static-file' }, (args) => { - return { - contents: `export default 'STATIC_FILE_NOT_AVAILABLE:${args.path}'`, - loader: 'js', + build.onLoad({ filter: /.*/, namespace: 'vike-static-file' }, async (args) => { + const stub = `'STATIC_FILE_NOT_AVAILABLE:${args.path}'` + const exportNames = await getStaticFileExportNames(args.path) + const lines = [`export default ${stub}`] + for (const name of exportNames) { + if (name !== 'default') lines.push(`export const ${name} = ${stub}`) } + return { contents: lines.join('\n'), loader: 'js' } }) }, }, @@ -552,6 +554,30 @@ function cleanEsbuildErrors(errors: Message[]) { ) } +const staticFileExportNamesCache = new Map>() +function getStaticFileExportNames(filePath: string): Promise { + if (!staticFileExportNamesCache.has(filePath)) { + staticFileExportNamesCache.set(filePath, resolveStaticFileExportNames(filePath)) + } + return staticFileExportNamesCache.get(filePath)! +} +async function resolveStaticFileExportNames(filePath: string): Promise { + try { + const result = await build({ + entryPoints: [filePath], + bundle: false, + write: false, + format: 'esm', + metafile: true, + logLevel: 'silent', + }) + const output = Object.values(result.metafile!.outputs)[0] + return output?.exports ?? [] + } catch { + return [] + } +} + function installSourceMapSupport() { // Don't break Vitest's source mapping if (isVitest()) return From 91660ac539b10c3946a089082fba6ff886ca211a Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 17:27:04 +0100 Subject: [PATCH 10/34] [AI] Use transformPointerImports for with{type:'runtime'} static stubs Instead of a sub-build to enumerate exports, reuse the existing Babel-based pointer import transform: mark with{type:'runtime'} imports as 'static' in the pointerImports map so that transformPointerImports replaces each specifier with STATIC_FILE_NOT_AVAILABLE:. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../pointerImports.ts | 15 ++++-- .../transpileAndExecuteFile.ts | 53 ++++++------------- 2 files changed, 27 insertions(+), 41 deletions(-) diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/pointerImports.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/pointerImports.ts index e372572eb7b..b56b6f59438 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/pointerImports.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/pointerImports.ts @@ -36,7 +36,7 @@ function transformPointerImports( code: string, filePathToShowToUser2: string, pointerImports: - | Record + | Record // Used by ./transformPointerImports.spec.ts | 'all', // For ./transformPointerImports.spec.ts @@ -59,13 +59,18 @@ function transformPointerImports( if (pointerImports !== 'all') { assert(importPath in pointerImports) const isPointerImport = pointerImports[importPath] - assert(isPointerImport === true || isPointerImport === false) + assert(isPointerImport === true || isPointerImport === false || isPointerImport === 'static') if (!isPointerImport) return } const { start, end } = node const importStatementCode = code.slice(start, end) + const isStaticStub = pointerImports !== 'all' && pointerImports[importPath] === 'static' + + // Bare `import './file' with { type: 'runtime' }` — no specifiers, nothing to stub + if (isStaticStub && node.specifiers.length === 0) return + /* Pointer import without importing any value => doesn't make sense and doesn't have any effect. ```js // Useless @@ -73,7 +78,7 @@ function transformPointerImports( // Useless import './Layout.jsx' ``` */ - if (node.specifiers.length === 0) { + if (!isStaticStub && node.specifiers.length === 0) { const isWarning = !styleFileRE.test(importPath) let quote = indent(importStatementCode) if (isWarning) { @@ -102,6 +107,10 @@ function transformPointerImports( specifier.type === 'ImportNamespaceSpecifier', ) const importLocalName = specifier.local.name + if (isStaticStub) { + replacement += `const ${importLocalName} = 'STATIC_FILE_NOT_AVAILABLE:${importPath}';` + return + } const exportName = (() => { if (specifier.type === 'ImportDefaultSpecifier') return 'default' if (specifier.type === 'ImportNamespaceSpecifier') return '*' diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts index c26268a4947..e9ccc4b34f6 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts @@ -172,7 +172,7 @@ async function transpileWithEsbuild( bundle: true, } - const pointerImports: Record = {} + const pointerImports: Record = {} options.plugins = [ // Determine whether an import should be: // - A pointer import @@ -237,10 +237,9 @@ async function transpileWithEsbuild( // - isImportPathNpmPackage(str, { cannotBePathAlias: true }) assertFilePathAbsoluteFilesystem(importPathResolved) - // Non-script file (e.g. .svg, .css) or explicitly tagged as runtime-only - // (`import ... with { type: 'runtime' }`) => resolve to constant string so - // that files with `env: { config: true, client: true }` can also be loaded in Node.js - if (!isScriptFile(importPathResolved) || args.with?.['type'] === 'runtime') { + // Non-script file (e.g. .svg, .css) => resolve to constant string so that files with + // `env: { config: true, client: true }` can also be loaded in Node.js + if (!isScriptFile(importPathResolved)) { esbuildCache.vikeConfigDependencies.add(importPathResolved) return { path: importPathResolved, @@ -252,6 +251,10 @@ async function transpileWithEsbuild( const isVikeExtensionImport = (path.startsWith('vike-') && path.endsWith('/config')) || importPathResolved.endsWith('+config.js') + // `with { type: 'runtime' }` => treat as a static-stub pointer import so that + // transformPointerImports() replaces each specifier with STATIC_FILE_NOT_AVAILABLE + const isStaticStub = args.with?.['type'] === 'runtime' + const isPointerImport = transformImports === 'all' || // .jsx, .tsx, .vue, .svelte, ... => template/JSX files => pointer import @@ -261,7 +264,8 @@ async function transpileWithEsbuild( // - In principle, we can use the setting 'name' value of Vike extensions. // - vike@0.4.162 started soft-requiring Vike extensions to set the name config. // - In practice, it seems like it requires some (non-trivial?) refactoring. - isVikeExtensionImport + isVikeExtensionImport || + isStaticStub assertPosixPath(importPathResolved) // `isNpmPkgImport` => `importPathOriginal` is most likely an npm package import, but it can also be a path alias that a) looks like an npm package import and b) resolves outside of `userRootDir`. @@ -332,17 +336,14 @@ async function transpileWithEsbuild( // Import of config code => loaded by Node.js at build-time isNpmPkgImport, ) - pointerImports[importPathTranspiled] = isPointerImport + pointerImports[importPathTranspiled] = isStaticStub ? 'static' : isPointerImport return { external: true, path: importPathTranspiled } }) - build.onLoad({ filter: /.*/, namespace: 'vike-static-file' }, async (args) => { - const stub = `'STATIC_FILE_NOT_AVAILABLE:${args.path}'` - const exportNames = await getStaticFileExportNames(args.path) - const lines = [`export default ${stub}`] - for (const name of exportNames) { - if (name !== 'default') lines.push(`export const ${name} = ${stub}`) + build.onLoad({ filter: /.*/, namespace: 'vike-static-file' }, (args) => { + return { + contents: `export default 'STATIC_FILE_NOT_AVAILABLE:${args.path}'`, + loader: 'js', } - return { contents: lines.join('\n'), loader: 'js' } }) }, }, @@ -554,30 +555,6 @@ function cleanEsbuildErrors(errors: Message[]) { ) } -const staticFileExportNamesCache = new Map>() -function getStaticFileExportNames(filePath: string): Promise { - if (!staticFileExportNamesCache.has(filePath)) { - staticFileExportNamesCache.set(filePath, resolveStaticFileExportNames(filePath)) - } - return staticFileExportNamesCache.get(filePath)! -} -async function resolveStaticFileExportNames(filePath: string): Promise { - try { - const result = await build({ - entryPoints: [filePath], - bundle: false, - write: false, - format: 'esm', - metafile: true, - logLevel: 'silent', - }) - const output = Object.values(result.metafile!.outputs)[0] - return output?.exports ?? [] - } catch { - return [] - } -} - function installSourceMapSupport() { // Don't break Vitest's source mapping if (isVitest()) return From 7d87f837eefac3e49198d2008752963e92e960d7 Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 18:53:23 +0100 Subject: [PATCH 11/34] [AI] Fix STATIC_FILE_NOT_AVAILABLE strings leaking into runtime Convert STATIC_FILE_NOT_AVAILABLE:path strings to import statements during config value serialization, just like pointer import strings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../serialize/serializeConfigValues.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/vike/src/shared-server-client/page-configs/serialize/serializeConfigValues.ts b/packages/vike/src/shared-server-client/page-configs/serialize/serializeConfigValues.ts index 3bba268744b..79fc5b01fae 100644 --- a/packages/vike/src/shared-server-client/page-configs/serialize/serializeConfigValues.ts +++ b/packages/vike/src/shared-server-client/page-configs/serialize/serializeConfigValues.ts @@ -33,6 +33,7 @@ import { const stringifyOptions = { forbidReactElements: true as const } const REPLACE_ME_BEFORE = '__VIKE__REPLACE_ME_BEFORE__' const REPLACE_ME_AFTER = '__VIKE__REPLACE_ME_AFTER__' +const STATIC_FILE_NOT_AVAILABLE_PREFIX = 'STATIC_FILE_NOT_AVAILABLE:' // This file is never loaded on the client-side but we save it under the vike/shared/ directory in order to collocate it with parsePageConfigsSerialized() // - vike/shared/page-configs/serialize/parsePageConfigsSerialized.ts @@ -242,6 +243,22 @@ function valueToJson( const replacement = [REPLACE_ME_BEFORE, importName, REPLACE_ME_AFTER].join('') return { replacement } } + // Handle STATIC_FILE_NOT_AVAILABLE strings: these are generated during config + // execution when a non-script file (e.g. .svg) or a `with { type: 'runtime' }` + // import is encountered. They must become runtime imports, not raw strings. + if (value.startsWith(STATIC_FILE_NOT_AVAILABLE_PREFIX)) { + const importPath = value.slice(STATIC_FILE_NOT_AVAILABLE_PREFIX.length) + const { importName } = addImportStatement( + importStatements, + importPath, + 'default', + filesEnv, + configEnv, + configName, + ) + const replacement = [REPLACE_ME_BEFORE, importName, REPLACE_ME_AFTER].join('') + return { replacement } + } } }, }) From cdfb9d860ccab2c7654a58e3914981be20e2132d Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 19:02:09 +0100 Subject: [PATCH 12/34] [AI] Deduplicate import statements in virtual file serializer Multiple config values with the same STATIC_FILE_NOT_AVAILABLE (or pointer import) path were generating redundant import bindings. Use a WeakMap keyed on the importStatements array to reuse existing import names for same path+export pairs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../serialize/serializeConfigValues.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/vike/src/shared-server-client/page-configs/serialize/serializeConfigValues.ts b/packages/vike/src/shared-server-client/page-configs/serialize/serializeConfigValues.ts index 79fc5b01fae..a448b6061cd 100644 --- a/packages/vike/src/shared-server-client/page-configs/serialize/serializeConfigValues.ts +++ b/packages/vike/src/shared-server-client/page-configs/serialize/serializeConfigValues.ts @@ -392,6 +392,10 @@ function getDefinedAtFileSource(source: ConfigValueSource) { return definedAtFile } +// Deduplication registry: maps an importStatements array to its {path:export -> importName} map. +// Keyed by array reference so it's naturally scoped per virtual file. +const importDedupeRegistry = new WeakMap>() + /* * Naming: * `import { someExport as someImport } from './some-file'` @@ -410,6 +414,19 @@ function addImportStatement( configEnv: ConfigEnvInternal, configName: string, ): { importName: string } { + // Always validate env (catches env conflicts even for duplicate imports) + assertFileEnv(importPath, configEnv, configName, filesEnv) + + // Deduplicate: reuse existing import name when the same path+export was already added + let dedupeMap = importDedupeRegistry.get(importStatements) + if (!dedupeMap) { + dedupeMap = new Map() + importDedupeRegistry.set(importStatements, dedupeMap) + } + const dedupeKey = `${importPath}:${exportName}` + const existing = dedupeMap.get(dedupeKey) + if (existing) return { importName: existing } + const importCounter = importStatements.length + 1 const importName = `import${importCounter}` as const const importLiteral = (() => { @@ -423,7 +440,7 @@ function addImportStatement( })() const importStatement = `import ${importLiteral} from '${importPath}';` importStatements.push(importStatement) - assertFileEnv(importPath, configEnv, configName, filesEnv) + dedupeMap.set(dedupeKey, importName) return { importName } } From 56a44ed62c1a73f59ce715320840bddac0e68d20 Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 19:02:35 +0100 Subject: [PATCH 13/34] Revert "[AI] Deduplicate import statements in virtual file serializer" This reverts commit b3964dea5a2b4336dade2468257c145653e34027. --- .../serialize/serializeConfigValues.ts | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/packages/vike/src/shared-server-client/page-configs/serialize/serializeConfigValues.ts b/packages/vike/src/shared-server-client/page-configs/serialize/serializeConfigValues.ts index a448b6061cd..79fc5b01fae 100644 --- a/packages/vike/src/shared-server-client/page-configs/serialize/serializeConfigValues.ts +++ b/packages/vike/src/shared-server-client/page-configs/serialize/serializeConfigValues.ts @@ -392,10 +392,6 @@ function getDefinedAtFileSource(source: ConfigValueSource) { return definedAtFile } -// Deduplication registry: maps an importStatements array to its {path:export -> importName} map. -// Keyed by array reference so it's naturally scoped per virtual file. -const importDedupeRegistry = new WeakMap>() - /* * Naming: * `import { someExport as someImport } from './some-file'` @@ -414,19 +410,6 @@ function addImportStatement( configEnv: ConfigEnvInternal, configName: string, ): { importName: string } { - // Always validate env (catches env conflicts even for duplicate imports) - assertFileEnv(importPath, configEnv, configName, filesEnv) - - // Deduplicate: reuse existing import name when the same path+export was already added - let dedupeMap = importDedupeRegistry.get(importStatements) - if (!dedupeMap) { - dedupeMap = new Map() - importDedupeRegistry.set(importStatements, dedupeMap) - } - const dedupeKey = `${importPath}:${exportName}` - const existing = dedupeMap.get(dedupeKey) - if (existing) return { importName: existing } - const importCounter = importStatements.length + 1 const importName = `import${importCounter}` as const const importLiteral = (() => { @@ -440,7 +423,7 @@ function addImportStatement( })() const importStatement = `import ${importLiteral} from '${importPath}';` importStatements.push(importStatement) - dedupeMap.set(dedupeKey, importName) + assertFileEnv(importPath, configEnv, configName, filesEnv) return { importName } } From e14ae6697f479507ff0d5b63e0568c051b289652 Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 19:14:30 +0100 Subject: [PATCH 14/34] [AI] Fix STATIC_FILE_NOT_AVAILABLE storing absolute filesystem paths Store Vite-compatible paths in STATIC_FILE_NOT_AVAILABLE strings: - vike-static-file onLoad: convert abs path to root-relative (/@fs/ fallback) - isStaticStub onResolve: store Vite path (npm name or root-relative) in pointerImports dict; update pointerImports.ts to use it as the path Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../pointerImports.ts | 13 +++++++---- .../transpileAndExecuteFile.ts | 23 ++++++++++++++++--- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/pointerImports.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/pointerImports.ts index b56b6f59438..7f81eb770a3 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/pointerImports.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/pointerImports.ts @@ -36,7 +36,7 @@ function transformPointerImports( code: string, filePathToShowToUser2: string, pointerImports: - | Record + | Record // Used by ./transformPointerImports.spec.ts | 'all', // For ./transformPointerImports.spec.ts @@ -59,14 +59,19 @@ function transformPointerImports( if (pointerImports !== 'all') { assert(importPath in pointerImports) const isPointerImport = pointerImports[importPath] - assert(isPointerImport === true || isPointerImport === false || isPointerImport === 'static') + assert(typeof isPointerImport === 'boolean' || typeof isPointerImport === 'string') if (!isPointerImport) return } const { start, end } = node const importStatementCode = code.slice(start, end) - const isStaticStub = pointerImports !== 'all' && pointerImports[importPath] === 'static' + // When the value is a string it holds the Vite-compatible import path for the static stub. + const staticStubVitePath = + pointerImports !== 'all' && typeof pointerImports[importPath] === 'string' + ? (pointerImports[importPath] as string) + : null + const isStaticStub = staticStubVitePath !== null // Bare `import './file' with { type: 'runtime' }` — no specifiers, nothing to stub if (isStaticStub && node.specifiers.length === 0) return @@ -108,7 +113,7 @@ function transformPointerImports( ) const importLocalName = specifier.local.name if (isStaticStub) { - replacement += `const ${importLocalName} = 'STATIC_FILE_NOT_AVAILABLE:${importPath}';` + replacement += `const ${importLocalName} = 'STATIC_FILE_NOT_AVAILABLE:${staticStubVitePath}';` return } const exportName = (() => { diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts index e9ccc4b34f6..02d0e063a6f 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts @@ -172,7 +172,8 @@ async function transpileWithEsbuild( bundle: true, } - const pointerImports: Record = {} + // Values: false = not a pointer import; true = regular pointer import; string = static stub Vite-compatible import path + const pointerImports: Record = {} options.plugins = [ // Determine whether an import should be: // - A pointer import @@ -336,12 +337,28 @@ async function transpileWithEsbuild( // Import of config code => loaded by Node.js at build-time isNpmPkgImport, ) - pointerImports[importPathTranspiled] = isStaticStub ? 'static' : isPointerImport + pointerImports[importPathTranspiled] = isStaticStub + ? // Store the Vite-compatible path so that transformPointerImports() can generate + // a correct STATIC_FILE_NOT_AVAILABLE string (path is later used as import path + // in the serialized virtual file). + isNpmPkgImport + ? importPathOriginal // npm package name is already Vite-compatible + : (getFilePathAbsoluteUserRootDir({ filePathAbsoluteFilesystem: importPathResolved, userRootDir }) ?? + `/@fs${importPathResolved}`) + : isPointerImport return { external: true, path: importPathTranspiled } }) build.onLoad({ filter: /.*/, namespace: 'vike-static-file' }, (args) => { + // args.path is an absolute filesystem path; convert to a Vite-compatible path. + const [pathWithoutQuery, ...queryParts] = args.path.split('?') + const query = queryParts.length > 0 ? '?' + queryParts.join('?') : '' + const rootRelativePath = getFilePathAbsoluteUserRootDir({ + filePathAbsoluteFilesystem: pathWithoutQuery!, + userRootDir, + }) + const viteImportPath = (rootRelativePath ?? `/@fs${pathWithoutQuery!}`) + query return { - contents: `export default 'STATIC_FILE_NOT_AVAILABLE:${args.path}'`, + contents: `export default 'STATIC_FILE_NOT_AVAILABLE:${viteImportPath}'`, loader: 'js', } }) From 84e6096ae237888ee08be65ba942ef11638af9a0 Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 19:18:40 +0100 Subject: [PATCH 15/34] Revert "[AI] Fix STATIC_FILE_NOT_AVAILABLE storing absolute filesystem paths" This reverts commit 0384f63c48c372b1e269b6a14326afeef2cc4f14. --- .../pointerImports.ts | 13 ++++------- .../transpileAndExecuteFile.ts | 23 +++---------------- 2 files changed, 7 insertions(+), 29 deletions(-) diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/pointerImports.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/pointerImports.ts index 7f81eb770a3..b56b6f59438 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/pointerImports.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/pointerImports.ts @@ -36,7 +36,7 @@ function transformPointerImports( code: string, filePathToShowToUser2: string, pointerImports: - | Record + | Record // Used by ./transformPointerImports.spec.ts | 'all', // For ./transformPointerImports.spec.ts @@ -59,19 +59,14 @@ function transformPointerImports( if (pointerImports !== 'all') { assert(importPath in pointerImports) const isPointerImport = pointerImports[importPath] - assert(typeof isPointerImport === 'boolean' || typeof isPointerImport === 'string') + assert(isPointerImport === true || isPointerImport === false || isPointerImport === 'static') if (!isPointerImport) return } const { start, end } = node const importStatementCode = code.slice(start, end) - // When the value is a string it holds the Vite-compatible import path for the static stub. - const staticStubVitePath = - pointerImports !== 'all' && typeof pointerImports[importPath] === 'string' - ? (pointerImports[importPath] as string) - : null - const isStaticStub = staticStubVitePath !== null + const isStaticStub = pointerImports !== 'all' && pointerImports[importPath] === 'static' // Bare `import './file' with { type: 'runtime' }` — no specifiers, nothing to stub if (isStaticStub && node.specifiers.length === 0) return @@ -113,7 +108,7 @@ function transformPointerImports( ) const importLocalName = specifier.local.name if (isStaticStub) { - replacement += `const ${importLocalName} = 'STATIC_FILE_NOT_AVAILABLE:${staticStubVitePath}';` + replacement += `const ${importLocalName} = 'STATIC_FILE_NOT_AVAILABLE:${importPath}';` return } const exportName = (() => { diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts index 02d0e063a6f..e9ccc4b34f6 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts @@ -172,8 +172,7 @@ async function transpileWithEsbuild( bundle: true, } - // Values: false = not a pointer import; true = regular pointer import; string = static stub Vite-compatible import path - const pointerImports: Record = {} + const pointerImports: Record = {} options.plugins = [ // Determine whether an import should be: // - A pointer import @@ -337,28 +336,12 @@ async function transpileWithEsbuild( // Import of config code => loaded by Node.js at build-time isNpmPkgImport, ) - pointerImports[importPathTranspiled] = isStaticStub - ? // Store the Vite-compatible path so that transformPointerImports() can generate - // a correct STATIC_FILE_NOT_AVAILABLE string (path is later used as import path - // in the serialized virtual file). - isNpmPkgImport - ? importPathOriginal // npm package name is already Vite-compatible - : (getFilePathAbsoluteUserRootDir({ filePathAbsoluteFilesystem: importPathResolved, userRootDir }) ?? - `/@fs${importPathResolved}`) - : isPointerImport + pointerImports[importPathTranspiled] = isStaticStub ? 'static' : isPointerImport return { external: true, path: importPathTranspiled } }) build.onLoad({ filter: /.*/, namespace: 'vike-static-file' }, (args) => { - // args.path is an absolute filesystem path; convert to a Vite-compatible path. - const [pathWithoutQuery, ...queryParts] = args.path.split('?') - const query = queryParts.length > 0 ? '?' + queryParts.join('?') : '' - const rootRelativePath = getFilePathAbsoluteUserRootDir({ - filePathAbsoluteFilesystem: pathWithoutQuery!, - userRootDir, - }) - const viteImportPath = (rootRelativePath ?? `/@fs${pathWithoutQuery!}`) + query return { - contents: `export default 'STATIC_FILE_NOT_AVAILABLE:${viteImportPath}'`, + contents: `export default 'STATIC_FILE_NOT_AVAILABLE:${args.path}'`, loader: 'js', } }) From 6ac9c7e8972711fef5a7a1049106c5e5664a7a04 Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 19:29:43 +0100 Subject: [PATCH 16/34] [AI] Fix wrong import paths: use pointer imports for with{type:runtime} - with{type:'runtime'} script files: treat as regular pointer imports (import:path:exportName format) instead of static stubs, so export names are correctly encoded and the pointer import mechanism handles serialization. - Remove 'static' type from pointerImports map; remove isStaticStub logic from pointerImports.ts. - vike-static-file onLoad: convert abs filesystem path to Vite-compatible root-relative path (/@fs/ fallback) so the STATIC_FILE_NOT_AVAILABLE string contains the correct import path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../resolveVikeConfigInternal/pointerImports.ts | 15 +++------------ .../transpileAndExecuteFile.ts | 17 +++++++++-------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/pointerImports.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/pointerImports.ts index b56b6f59438..e372572eb7b 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/pointerImports.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/pointerImports.ts @@ -36,7 +36,7 @@ function transformPointerImports( code: string, filePathToShowToUser2: string, pointerImports: - | Record + | Record // Used by ./transformPointerImports.spec.ts | 'all', // For ./transformPointerImports.spec.ts @@ -59,18 +59,13 @@ function transformPointerImports( if (pointerImports !== 'all') { assert(importPath in pointerImports) const isPointerImport = pointerImports[importPath] - assert(isPointerImport === true || isPointerImport === false || isPointerImport === 'static') + assert(isPointerImport === true || isPointerImport === false) if (!isPointerImport) return } const { start, end } = node const importStatementCode = code.slice(start, end) - const isStaticStub = pointerImports !== 'all' && pointerImports[importPath] === 'static' - - // Bare `import './file' with { type: 'runtime' }` — no specifiers, nothing to stub - if (isStaticStub && node.specifiers.length === 0) return - /* Pointer import without importing any value => doesn't make sense and doesn't have any effect. ```js // Useless @@ -78,7 +73,7 @@ function transformPointerImports( // Useless import './Layout.jsx' ``` */ - if (!isStaticStub && node.specifiers.length === 0) { + if (node.specifiers.length === 0) { const isWarning = !styleFileRE.test(importPath) let quote = indent(importStatementCode) if (isWarning) { @@ -107,10 +102,6 @@ function transformPointerImports( specifier.type === 'ImportNamespaceSpecifier', ) const importLocalName = specifier.local.name - if (isStaticStub) { - replacement += `const ${importLocalName} = 'STATIC_FILE_NOT_AVAILABLE:${importPath}';` - return - } const exportName = (() => { if (specifier.type === 'ImportDefaultSpecifier') return 'default' if (specifier.type === 'ImportNamespaceSpecifier') return '*' diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts index e9ccc4b34f6..de60d43b10c 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts @@ -172,7 +172,7 @@ async function transpileWithEsbuild( bundle: true, } - const pointerImports: Record = {} + const pointerImports: Record = {} options.plugins = [ // Determine whether an import should be: // - A pointer import @@ -251,10 +251,6 @@ async function transpileWithEsbuild( const isVikeExtensionImport = (path.startsWith('vike-') && path.endsWith('/config')) || importPathResolved.endsWith('+config.js') - // `with { type: 'runtime' }` => treat as a static-stub pointer import so that - // transformPointerImports() replaces each specifier with STATIC_FILE_NOT_AVAILABLE - const isStaticStub = args.with?.['type'] === 'runtime' - const isPointerImport = transformImports === 'all' || // .jsx, .tsx, .vue, .svelte, ... => template/JSX files => pointer import @@ -265,7 +261,9 @@ async function transpileWithEsbuild( // - vike@0.4.162 started soft-requiring Vike extensions to set the name config. // - In practice, it seems like it requires some (non-trivial?) refactoring. isVikeExtensionImport || - isStaticStub + // `with { type: 'runtime' }` => regular pointer import so that the pointer import + // mechanism correctly encodes path + export name (import:path:exportName). + args.with?.['type'] === 'runtime' assertPosixPath(importPathResolved) // `isNpmPkgImport` => `importPathOriginal` is most likely an npm package import, but it can also be a path alias that a) looks like an npm package import and b) resolves outside of `userRootDir`. @@ -336,12 +334,15 @@ async function transpileWithEsbuild( // Import of config code => loaded by Node.js at build-time isNpmPkgImport, ) - pointerImports[importPathTranspiled] = isStaticStub ? 'static' : isPointerImport + pointerImports[importPathTranspiled] = isPointerImport return { external: true, path: importPathTranspiled } }) build.onLoad({ filter: /.*/, namespace: 'vike-static-file' }, (args) => { + // args.path is an absolute filesystem path; convert to a Vite-compatible path. + const vitePath = + getFilePathAbsoluteUserRootDir({ filePathAbsoluteFilesystem: args.path, userRootDir }) ?? `/@fs${args.path}` return { - contents: `export default 'STATIC_FILE_NOT_AVAILABLE:${args.path}'`, + contents: `export default 'STATIC_FILE_NOT_AVAILABLE:${vitePath}'`, loader: 'js', } }) From f1e79de5124b7b067d3e3911ca20ac66976c1320 Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 19:46:49 +0100 Subject: [PATCH 17/34] [AI] Strip with{type:'runtime'} import attributes from runtime bundles Add pluginStripRuntimeImportAttribute Vite plugin that removes `with { type: 'runtime' }` attributes from import statements when files are transpiled for client/server runtime. These attributes are config-time only; leaving them in runtime code breaks bundlers and runtimes that don't support unknown import attributes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- packages/vike/src/node/vite/index.ts | 3 +- .../pluginStripRuntimeImportAttribute.ts | 44 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 packages/vike/src/node/vite/plugins/pluginStripRuntimeImportAttribute.ts diff --git a/packages/vike/src/node/vite/index.ts b/packages/vike/src/node/vite/index.ts index 4cf30a747ae..29d6f50864d 100644 --- a/packages/vike/src/node/vite/index.ts +++ b/packages/vike/src/node/vite/index.ts @@ -39,7 +39,7 @@ import { pluginBuildConfig } from './plugins/build/pluginBuildConfig.js' import { pluginModuleBanner } from './plugins/build/pluginModuleBanner.js' import { pluginReplaceConstantsNonRunnableDev } from './plugins/non-runnable-dev/pluginReplaceConstantsNonRunnableDev.js' import { isVikeCliOrApi } from '../../shared-server-node/api-context.js' -import { pluginViteConfigVikeExtensions } from './plugins/pluginViteConfigVikeExtensions.js' +import { pluginStripRuntimeImportAttribute } from './plugins/pluginStripRuntimeImportAttribute.js' import { getVikeConfigInternalEarly, isOnlyResolvingUserConfig } from '../api/resolveViteConfigFromUser.js' import './assertEnvVite.js' @@ -73,6 +73,7 @@ function plugin(vikeVitePluginOptions: VikeVitePluginOptions = {}): Promise runtimeAttrRE.test(code) +// === + +function pluginStripRuntimeImportAttribute(): Plugin[] { + return [ + { + name: 'vike:stripRuntimeImportAttribute', + transform: { + filter: filterRolldown, + handler(code, id) { + runtimeAttrRE.lastIndex = 0 + if (!filterFunction(code)) return + const { magicString, getMagicStringResult } = getMagicString(code, id) + runtimeAttrRE.lastIndex = 0 + let match: RegExpExecArray | null + while ((match = runtimeAttrRE.exec(code)) !== null) { + magicString.remove(match.index, match.index + match[0].length) + } + return getMagicStringResult() + }, + }, + }, + ] +} From 73de4ed4f9e817be0fdeec87c5db5da7b1fe3286 Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 19:48:36 +0100 Subject: [PATCH 18/34] Revert "[AI] Strip with{type:'runtime'} import attributes from runtime bundles" This reverts commit 354198e6cb0a525504a3a531f5e9a9f8b3de879d. --- packages/vike/src/node/vite/index.ts | 3 +- .../pluginStripRuntimeImportAttribute.ts | 44 ------------------- 2 files changed, 1 insertion(+), 46 deletions(-) delete mode 100644 packages/vike/src/node/vite/plugins/pluginStripRuntimeImportAttribute.ts diff --git a/packages/vike/src/node/vite/index.ts b/packages/vike/src/node/vite/index.ts index 29d6f50864d..4cf30a747ae 100644 --- a/packages/vike/src/node/vite/index.ts +++ b/packages/vike/src/node/vite/index.ts @@ -39,7 +39,7 @@ import { pluginBuildConfig } from './plugins/build/pluginBuildConfig.js' import { pluginModuleBanner } from './plugins/build/pluginModuleBanner.js' import { pluginReplaceConstantsNonRunnableDev } from './plugins/non-runnable-dev/pluginReplaceConstantsNonRunnableDev.js' import { isVikeCliOrApi } from '../../shared-server-node/api-context.js' -import { pluginStripRuntimeImportAttribute } from './plugins/pluginStripRuntimeImportAttribute.js' +import { pluginViteConfigVikeExtensions } from './plugins/pluginViteConfigVikeExtensions.js' import { getVikeConfigInternalEarly, isOnlyResolvingUserConfig } from '../api/resolveViteConfigFromUser.js' import './assertEnvVite.js' @@ -73,7 +73,6 @@ function plugin(vikeVitePluginOptions: VikeVitePluginOptions = {}): Promise runtimeAttrRE.test(code) -// === - -function pluginStripRuntimeImportAttribute(): Plugin[] { - return [ - { - name: 'vike:stripRuntimeImportAttribute', - transform: { - filter: filterRolldown, - handler(code, id) { - runtimeAttrRE.lastIndex = 0 - if (!filterFunction(code)) return - const { magicString, getMagicStringResult } = getMagicString(code, id) - runtimeAttrRE.lastIndex = 0 - let match: RegExpExecArray | null - while ((match = runtimeAttrRE.exec(code)) !== null) { - magicString.remove(match.index, match.index + match[0].length) - } - return getMagicStringResult() - }, - }, - }, - ] -} From fe3c2a96dcdf07462663c47d6b688b90f28e03c3 Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 20:06:13 +0100 Subject: [PATCH 19/34] [AI] Revert STATIC_FILE_NOT_AVAILABLE handling from serializeConfigValues Config values loaded at config-time that contain STATIC_FILE_NOT_AVAILABLE strings should not be converted to runtime imports. The serializer should not be involved; the vike-static-file stub is only for preventing Node.js crashes when config files import non-script files at config-time. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../serialize/serializeConfigValues.ts | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/packages/vike/src/shared-server-client/page-configs/serialize/serializeConfigValues.ts b/packages/vike/src/shared-server-client/page-configs/serialize/serializeConfigValues.ts index 79fc5b01fae..3bba268744b 100644 --- a/packages/vike/src/shared-server-client/page-configs/serialize/serializeConfigValues.ts +++ b/packages/vike/src/shared-server-client/page-configs/serialize/serializeConfigValues.ts @@ -33,7 +33,6 @@ import { const stringifyOptions = { forbidReactElements: true as const } const REPLACE_ME_BEFORE = '__VIKE__REPLACE_ME_BEFORE__' const REPLACE_ME_AFTER = '__VIKE__REPLACE_ME_AFTER__' -const STATIC_FILE_NOT_AVAILABLE_PREFIX = 'STATIC_FILE_NOT_AVAILABLE:' // This file is never loaded on the client-side but we save it under the vike/shared/ directory in order to collocate it with parsePageConfigsSerialized() // - vike/shared/page-configs/serialize/parsePageConfigsSerialized.ts @@ -243,22 +242,6 @@ function valueToJson( const replacement = [REPLACE_ME_BEFORE, importName, REPLACE_ME_AFTER].join('') return { replacement } } - // Handle STATIC_FILE_NOT_AVAILABLE strings: these are generated during config - // execution when a non-script file (e.g. .svg) or a `with { type: 'runtime' }` - // import is encountered. They must become runtime imports, not raw strings. - if (value.startsWith(STATIC_FILE_NOT_AVAILABLE_PREFIX)) { - const importPath = value.slice(STATIC_FILE_NOT_AVAILABLE_PREFIX.length) - const { importName } = addImportStatement( - importStatements, - importPath, - 'default', - filesEnv, - configEnv, - configName, - ) - const replacement = [REPLACE_ME_BEFORE, importName, REPLACE_ME_AFTER].join('') - return { replacement } - } } }, }) From 64a0ab777c0da6aacbcbd3a7ba617a5ef9bcf99a Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 20:28:29 +0100 Subject: [PATCH 20/34] [AI] Fix: skip config-time loading of non-plain-script value files Non-plain-script value files (+docpress.tsx, +Component.vue, etc.) are runtime-only files. Even when env.config=true, they should remain as plus-file pointer imports rather than being evaluated at config-time. This is consistent with the existing pointer-import logic in transpileAndExecuteFile.ts: // .jsx, .vue, .svg, ... => obviously not config code => pointer import Previously, the vike-static-file stub allowed these files to load successfully at config-time (bypassing SVG/CSS import failures), causing them to be serialized as 'js-serialized' instead of 'plus-file'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../shared/resolveVikeConfigInternal/loadFileAtConfigTime.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/loadFileAtConfigTime.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/loadFileAtConfigTime.ts index 367deb8bf7d..151da41c342 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/loadFileAtConfigTime.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/loadFileAtConfigTime.ts @@ -21,6 +21,7 @@ import { getConfigFileExport } from './getConfigFileExport.js' import { PointerImport, resolvePointerImportData } from './resolvePointerImport.js' import type { ConfigDefinitionInternal, ConfigDefinitionsInternal } from './configDefinitionsBuiltIn.js' import { getConfigDefinedAt } from '../../../../shared-server-client/page-configs/getConfigDefinedAt.js' +import { isPlainScriptFile } from '../../../../utils/isScriptFile.js' import '../../assertEnvVite.js' assertIsNotProductionRuntime() @@ -79,6 +80,10 @@ async function loadValueFile( const configDef = getConfigDefinitionOptional(configDefinitions, configName) // Only load value files with `env.config===true` if (!configDef || !shouldBeLoadableAtBuildTime(configDef)) return + // Non-plain-script files (.tsx, .jsx, .vue, .svelte, etc.) are runtime-only and should not be + // loaded at config-time. Like pointer imports within config files, they are "obviously not config + // code" and are treated as plus-file pointer imports instead. + if (!isPlainScriptFile(interfaceValueFile.filePath.filePathAbsoluteFilesystem ?? '')) return interfaceValueFile.isNotLoaded = false assert(!interfaceValueFile.isNotLoaded) interfaceValueFile.fileExportsByConfigName = {} From 0619a5ad89bfb77c49bd40fbc600eedcbb31f373 Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 20:32:12 +0100 Subject: [PATCH 21/34] Revert "[AI] Fix: skip config-time loading of non-plain-script value files" This reverts commit a2169cbcb12a90c37e762d0d0d554512effee442. --- .../shared/resolveVikeConfigInternal/loadFileAtConfigTime.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/loadFileAtConfigTime.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/loadFileAtConfigTime.ts index 151da41c342..367deb8bf7d 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/loadFileAtConfigTime.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/loadFileAtConfigTime.ts @@ -21,7 +21,6 @@ import { getConfigFileExport } from './getConfigFileExport.js' import { PointerImport, resolvePointerImportData } from './resolvePointerImport.js' import type { ConfigDefinitionInternal, ConfigDefinitionsInternal } from './configDefinitionsBuiltIn.js' import { getConfigDefinedAt } from '../../../../shared-server-client/page-configs/getConfigDefinedAt.js' -import { isPlainScriptFile } from '../../../../utils/isScriptFile.js' import '../../assertEnvVite.js' assertIsNotProductionRuntime() @@ -80,10 +79,6 @@ async function loadValueFile( const configDef = getConfigDefinitionOptional(configDefinitions, configName) // Only load value files with `env.config===true` if (!configDef || !shouldBeLoadableAtBuildTime(configDef)) return - // Non-plain-script files (.tsx, .jsx, .vue, .svelte, etc.) are runtime-only and should not be - // loaded at config-time. Like pointer imports within config files, they are "obviously not config - // code" and are treated as plus-file pointer imports instead. - if (!isPlainScriptFile(interfaceValueFile.filePath.filePathAbsoluteFilesystem ?? '')) return interfaceValueFile.isNotLoaded = false assert(!interfaceValueFile.isNotLoaded) interfaceValueFile.fileExportsByConfigName = {} From 9605124ed9b4e3ea0eacb0e2d8917fef852d11b8 Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 21:06:24 +0100 Subject: [PATCH 22/34] Revert "[AI] Fix wrong import paths: use pointer imports for with{type:runtime}" This reverts commit 1d44d7552717c7ce559cc0880be899fb10a4b9d4. --- .../resolveVikeConfigInternal/pointerImports.ts | 15 ++++++++++++--- .../transpileAndExecuteFile.ts | 17 ++++++++--------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/pointerImports.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/pointerImports.ts index e372572eb7b..b56b6f59438 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/pointerImports.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/pointerImports.ts @@ -36,7 +36,7 @@ function transformPointerImports( code: string, filePathToShowToUser2: string, pointerImports: - | Record + | Record // Used by ./transformPointerImports.spec.ts | 'all', // For ./transformPointerImports.spec.ts @@ -59,13 +59,18 @@ function transformPointerImports( if (pointerImports !== 'all') { assert(importPath in pointerImports) const isPointerImport = pointerImports[importPath] - assert(isPointerImport === true || isPointerImport === false) + assert(isPointerImport === true || isPointerImport === false || isPointerImport === 'static') if (!isPointerImport) return } const { start, end } = node const importStatementCode = code.slice(start, end) + const isStaticStub = pointerImports !== 'all' && pointerImports[importPath] === 'static' + + // Bare `import './file' with { type: 'runtime' }` — no specifiers, nothing to stub + if (isStaticStub && node.specifiers.length === 0) return + /* Pointer import without importing any value => doesn't make sense and doesn't have any effect. ```js // Useless @@ -73,7 +78,7 @@ function transformPointerImports( // Useless import './Layout.jsx' ``` */ - if (node.specifiers.length === 0) { + if (!isStaticStub && node.specifiers.length === 0) { const isWarning = !styleFileRE.test(importPath) let quote = indent(importStatementCode) if (isWarning) { @@ -102,6 +107,10 @@ function transformPointerImports( specifier.type === 'ImportNamespaceSpecifier', ) const importLocalName = specifier.local.name + if (isStaticStub) { + replacement += `const ${importLocalName} = 'STATIC_FILE_NOT_AVAILABLE:${importPath}';` + return + } const exportName = (() => { if (specifier.type === 'ImportDefaultSpecifier') return 'default' if (specifier.type === 'ImportNamespaceSpecifier') return '*' diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts index de60d43b10c..e9ccc4b34f6 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts @@ -172,7 +172,7 @@ async function transpileWithEsbuild( bundle: true, } - const pointerImports: Record = {} + const pointerImports: Record = {} options.plugins = [ // Determine whether an import should be: // - A pointer import @@ -251,6 +251,10 @@ async function transpileWithEsbuild( const isVikeExtensionImport = (path.startsWith('vike-') && path.endsWith('/config')) || importPathResolved.endsWith('+config.js') + // `with { type: 'runtime' }` => treat as a static-stub pointer import so that + // transformPointerImports() replaces each specifier with STATIC_FILE_NOT_AVAILABLE + const isStaticStub = args.with?.['type'] === 'runtime' + const isPointerImport = transformImports === 'all' || // .jsx, .tsx, .vue, .svelte, ... => template/JSX files => pointer import @@ -261,9 +265,7 @@ async function transpileWithEsbuild( // - vike@0.4.162 started soft-requiring Vike extensions to set the name config. // - In practice, it seems like it requires some (non-trivial?) refactoring. isVikeExtensionImport || - // `with { type: 'runtime' }` => regular pointer import so that the pointer import - // mechanism correctly encodes path + export name (import:path:exportName). - args.with?.['type'] === 'runtime' + isStaticStub assertPosixPath(importPathResolved) // `isNpmPkgImport` => `importPathOriginal` is most likely an npm package import, but it can also be a path alias that a) looks like an npm package import and b) resolves outside of `userRootDir`. @@ -334,15 +336,12 @@ async function transpileWithEsbuild( // Import of config code => loaded by Node.js at build-time isNpmPkgImport, ) - pointerImports[importPathTranspiled] = isPointerImport + pointerImports[importPathTranspiled] = isStaticStub ? 'static' : isPointerImport return { external: true, path: importPathTranspiled } }) build.onLoad({ filter: /.*/, namespace: 'vike-static-file' }, (args) => { - // args.path is an absolute filesystem path; convert to a Vite-compatible path. - const vitePath = - getFilePathAbsoluteUserRootDir({ filePathAbsoluteFilesystem: args.path, userRootDir }) ?? `/@fs${args.path}` return { - contents: `export default 'STATIC_FILE_NOT_AVAILABLE:${vitePath}'`, + contents: `export default 'STATIC_FILE_NOT_AVAILABLE:${args.path}'`, loader: 'js', } }) From a0cc24ad921c7f0dc287b206965254575cf6b2ef Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 21:15:05 +0100 Subject: [PATCH 23/34] [AI] Fix: set valueIsLoadedWithImport=true when env.client or env.server When a +{configName}.js value file also runs in client or server environments (env.client or env.server), its value must remain a pointer import rather than being inlined as 'js-serialized' JSON. This prevents value files like +docpress.tsx (env: { config, client, server }) from being evaluated and inlined at config-time. Their imports (SVG files, icon packages, etc.) should only be resolved by Vite at runtime, not at config-time by esbuild. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../vike/src/node/vite/shared/resolveVikeConfigInternal.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal.ts index 0c1559ceabf..f35944c0b11 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal.ts @@ -979,7 +979,11 @@ function getConfigValueSources( ...configValueSourceCommon, ...confVal, configEnv: configEnvResolved, - valueIsLoadedWithImport: !confVal.valueIsLoaded || !isJsonValue(confVal.value), + valueIsLoadedWithImport: + !confVal.valueIsLoaded || + !isJsonValue(confVal.value) || + !!configEnvResolved.client || + !!configEnvResolved.server, valueIsDefinedByPlusValueFile: true, definedAt: { ...plusFile.filePath, From cac4792d59f9d13c8d9c25a39da779dd6e2ad1eb Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 21:22:14 +0100 Subject: [PATCH 24/34] Reapply "[AI] Strip with{type:'runtime'} import attributes from runtime bundles" This reverts commit 9d2cef2422f2ad19ae8b0ffcc549a70aafc0a4fe. --- packages/vike/src/node/vite/index.ts | 3 +- .../pluginStripRuntimeImportAttribute.ts | 44 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 packages/vike/src/node/vite/plugins/pluginStripRuntimeImportAttribute.ts diff --git a/packages/vike/src/node/vite/index.ts b/packages/vike/src/node/vite/index.ts index 4cf30a747ae..29d6f50864d 100644 --- a/packages/vike/src/node/vite/index.ts +++ b/packages/vike/src/node/vite/index.ts @@ -39,7 +39,7 @@ import { pluginBuildConfig } from './plugins/build/pluginBuildConfig.js' import { pluginModuleBanner } from './plugins/build/pluginModuleBanner.js' import { pluginReplaceConstantsNonRunnableDev } from './plugins/non-runnable-dev/pluginReplaceConstantsNonRunnableDev.js' import { isVikeCliOrApi } from '../../shared-server-node/api-context.js' -import { pluginViteConfigVikeExtensions } from './plugins/pluginViteConfigVikeExtensions.js' +import { pluginStripRuntimeImportAttribute } from './plugins/pluginStripRuntimeImportAttribute.js' import { getVikeConfigInternalEarly, isOnlyResolvingUserConfig } from '../api/resolveViteConfigFromUser.js' import './assertEnvVite.js' @@ -73,6 +73,7 @@ function plugin(vikeVitePluginOptions: VikeVitePluginOptions = {}): Promise runtimeAttrRE.test(code) +// === + +function pluginStripRuntimeImportAttribute(): Plugin[] { + return [ + { + name: 'vike:stripRuntimeImportAttribute', + transform: { + filter: filterRolldown, + handler(code, id) { + runtimeAttrRE.lastIndex = 0 + if (!filterFunction(code)) return + const { magicString, getMagicStringResult } = getMagicString(code, id) + runtimeAttrRE.lastIndex = 0 + let match: RegExpExecArray | null + while ((match = runtimeAttrRE.exec(code)) !== null) { + magicString.remove(match.index, match.index + match[0].length) + } + return getMagicStringResult() + }, + }, + }, + ] +} From b2f4057b936f6b4f50749f04de1e535b26745ce2 Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 21:24:09 +0100 Subject: [PATCH 25/34] fix missing import --- packages/vike/src/node/vite/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/vike/src/node/vite/index.ts b/packages/vike/src/node/vite/index.ts index 29d6f50864d..863396907d0 100644 --- a/packages/vike/src/node/vite/index.ts +++ b/packages/vike/src/node/vite/index.ts @@ -39,6 +39,7 @@ import { pluginBuildConfig } from './plugins/build/pluginBuildConfig.js' import { pluginModuleBanner } from './plugins/build/pluginModuleBanner.js' import { pluginReplaceConstantsNonRunnableDev } from './plugins/non-runnable-dev/pluginReplaceConstantsNonRunnableDev.js' import { isVikeCliOrApi } from '../../shared-server-node/api-context.js' +import { pluginViteConfigVikeExtensions } from './plugins/pluginViteConfigVikeExtensions.js' import { pluginStripRuntimeImportAttribute } from './plugins/pluginStripRuntimeImportAttribute.js' import { getVikeConfigInternalEarly, isOnlyResolvingUserConfig } from '../api/resolveViteConfigFromUser.js' import './assertEnvVite.js' From 8b58308042ea3ebc65658b49df08dc7d17fcf1be Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 21:37:08 +0100 Subject: [PATCH 26/34] Reapply "[AI] Fix wrong import paths: use pointer imports for with{type:runtime}" This reverts commit 57d3e2059c2cd4ed905804b0fd51df4e573ca470. --- .../resolveVikeConfigInternal/pointerImports.ts | 15 +++------------ .../transpileAndExecuteFile.ts | 17 +++++++++-------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/pointerImports.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/pointerImports.ts index b56b6f59438..e372572eb7b 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/pointerImports.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/pointerImports.ts @@ -36,7 +36,7 @@ function transformPointerImports( code: string, filePathToShowToUser2: string, pointerImports: - | Record + | Record // Used by ./transformPointerImports.spec.ts | 'all', // For ./transformPointerImports.spec.ts @@ -59,18 +59,13 @@ function transformPointerImports( if (pointerImports !== 'all') { assert(importPath in pointerImports) const isPointerImport = pointerImports[importPath] - assert(isPointerImport === true || isPointerImport === false || isPointerImport === 'static') + assert(isPointerImport === true || isPointerImport === false) if (!isPointerImport) return } const { start, end } = node const importStatementCode = code.slice(start, end) - const isStaticStub = pointerImports !== 'all' && pointerImports[importPath] === 'static' - - // Bare `import './file' with { type: 'runtime' }` — no specifiers, nothing to stub - if (isStaticStub && node.specifiers.length === 0) return - /* Pointer import without importing any value => doesn't make sense and doesn't have any effect. ```js // Useless @@ -78,7 +73,7 @@ function transformPointerImports( // Useless import './Layout.jsx' ``` */ - if (!isStaticStub && node.specifiers.length === 0) { + if (node.specifiers.length === 0) { const isWarning = !styleFileRE.test(importPath) let quote = indent(importStatementCode) if (isWarning) { @@ -107,10 +102,6 @@ function transformPointerImports( specifier.type === 'ImportNamespaceSpecifier', ) const importLocalName = specifier.local.name - if (isStaticStub) { - replacement += `const ${importLocalName} = 'STATIC_FILE_NOT_AVAILABLE:${importPath}';` - return - } const exportName = (() => { if (specifier.type === 'ImportDefaultSpecifier') return 'default' if (specifier.type === 'ImportNamespaceSpecifier') return '*' diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts index e9ccc4b34f6..de60d43b10c 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts @@ -172,7 +172,7 @@ async function transpileWithEsbuild( bundle: true, } - const pointerImports: Record = {} + const pointerImports: Record = {} options.plugins = [ // Determine whether an import should be: // - A pointer import @@ -251,10 +251,6 @@ async function transpileWithEsbuild( const isVikeExtensionImport = (path.startsWith('vike-') && path.endsWith('/config')) || importPathResolved.endsWith('+config.js') - // `with { type: 'runtime' }` => treat as a static-stub pointer import so that - // transformPointerImports() replaces each specifier with STATIC_FILE_NOT_AVAILABLE - const isStaticStub = args.with?.['type'] === 'runtime' - const isPointerImport = transformImports === 'all' || // .jsx, .tsx, .vue, .svelte, ... => template/JSX files => pointer import @@ -265,7 +261,9 @@ async function transpileWithEsbuild( // - vike@0.4.162 started soft-requiring Vike extensions to set the name config. // - In practice, it seems like it requires some (non-trivial?) refactoring. isVikeExtensionImport || - isStaticStub + // `with { type: 'runtime' }` => regular pointer import so that the pointer import + // mechanism correctly encodes path + export name (import:path:exportName). + args.with?.['type'] === 'runtime' assertPosixPath(importPathResolved) // `isNpmPkgImport` => `importPathOriginal` is most likely an npm package import, but it can also be a path alias that a) looks like an npm package import and b) resolves outside of `userRootDir`. @@ -336,12 +334,15 @@ async function transpileWithEsbuild( // Import of config code => loaded by Node.js at build-time isNpmPkgImport, ) - pointerImports[importPathTranspiled] = isStaticStub ? 'static' : isPointerImport + pointerImports[importPathTranspiled] = isPointerImport return { external: true, path: importPathTranspiled } }) build.onLoad({ filter: /.*/, namespace: 'vike-static-file' }, (args) => { + // args.path is an absolute filesystem path; convert to a Vite-compatible path. + const vitePath = + getFilePathAbsoluteUserRootDir({ filePathAbsoluteFilesystem: args.path, userRootDir }) ?? `/@fs${args.path}` return { - contents: `export default 'STATIC_FILE_NOT_AVAILABLE:${args.path}'`, + contents: `export default 'STATIC_FILE_NOT_AVAILABLE:${vitePath}'`, loader: 'js', } }) From 841553f173d9276aeb9e2b5d675001b2674011a9 Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 21:56:51 +0100 Subject: [PATCH 27/34] rm STATIC_FILE_NOT_AVAILABLE --- .../transpileAndExecuteFile.ts | 23 ++----------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts index de60d43b10c..26e16f56655 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts @@ -26,7 +26,7 @@ import { genPromise } from '../../../../utils/genPromise.js' import { assertFilePathAbsoluteFilesystem } from '../../../../utils/isFilePathAbsoluteFilesystem.js' import { isImportPathRelative } from '../../../../utils/isImportPath.js' import { isObject } from '../../../../utils/isObject.js' -import { isPlainScriptFile, isScriptFile } from '../../../../utils/isScriptFile.js' +import { isPlainScriptFile } from '../../../../utils/isScriptFile.js' import { isVitest } from '../../../../utils/isVitest.js' import { assertIsImportPathNpmPackage, isImportPathNpmPackageOrPathAlias } from '../../../../utils/parseNpmPackage.js' import { assertPosixPath, toPosixPath } from '../../../../utils/path.js' @@ -237,23 +237,13 @@ async function transpileWithEsbuild( // - isImportPathNpmPackage(str, { cannotBePathAlias: true }) assertFilePathAbsoluteFilesystem(importPathResolved) - // Non-script file (e.g. .svg, .css) => resolve to constant string so that files with - // `env: { config: true, client: true }` can also be loaded in Node.js - if (!isScriptFile(importPathResolved)) { - esbuildCache.vikeConfigDependencies.add(importPathResolved) - return { - path: importPathResolved, - namespace: 'vike-static-file', - } - } - // Should be remove this? See comment below. const isVikeExtensionImport = (path.startsWith('vike-') && path.endsWith('/config')) || importPathResolved.endsWith('+config.js') const isPointerImport = transformImports === 'all' || - // .jsx, .tsx, .vue, .svelte, ... => template/JSX files => pointer import + // .jsx, .vue, .svg, ... => obviously not config code => pointer import !isPlainScriptFile(importPathResolved) || // Import of a Vike extension config => make it a pointer import because we want to show nice error messages (that can display whether a config has been set by the user or by a Vike extension). // - Should we stop doing this? (And instead let Node.js directly load Vike extensions.) @@ -337,15 +327,6 @@ async function transpileWithEsbuild( pointerImports[importPathTranspiled] = isPointerImport return { external: true, path: importPathTranspiled } }) - build.onLoad({ filter: /.*/, namespace: 'vike-static-file' }, (args) => { - // args.path is an absolute filesystem path; convert to a Vite-compatible path. - const vitePath = - getFilePathAbsoluteUserRootDir({ filePathAbsoluteFilesystem: args.path, userRootDir }) ?? `/@fs${args.path}` - return { - contents: `export default 'STATIC_FILE_NOT_AVAILABLE:${vitePath}'`, - loader: 'js', - } - }) }, }, // Track dependencies From 1b8d254279a6be28f78695a7f8acef8a1f6825ca Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 21:59:04 +0100 Subject: [PATCH 28/34] rename: with { type: 'runtime' } => with { type: 'vike-pointer' } Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../vite/plugins/pluginStripRuntimeImportAttribute.ts | 8 ++++---- .../resolveVikeConfigInternal/transpileAndExecuteFile.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/vike/src/node/vite/plugins/pluginStripRuntimeImportAttribute.ts b/packages/vike/src/node/vite/plugins/pluginStripRuntimeImportAttribute.ts index 28fe8f5159a..383e826ee8c 100644 --- a/packages/vike/src/node/vite/plugins/pluginStripRuntimeImportAttribute.ts +++ b/packages/vike/src/node/vite/plugins/pluginStripRuntimeImportAttribute.ts @@ -1,6 +1,6 @@ export { pluginStripRuntimeImportAttribute } -// Strip `with { type: 'runtime' }` import attributes from files transpiled for the +// Strip `with { type: 'vike-pointer' }` import attributes from files transpiled for the // client/server runtime. These attributes are only meaningful at config-time (they tell // Vike to treat the import as a pointer import); leaving them in runtime bundles would // break bundlers/runtimes that don't support unknown import attributes. @@ -9,13 +9,13 @@ import type { Plugin } from 'vite' import { getMagicString } from '../shared/getMagicString.js' import '../assertEnvVite.js' -// Match `with { type: 'runtime' }` (with optional whitespace variations) -const runtimeAttrRE = /\bwith\s*\{\s*type\s*:\s*['"]runtime['"]\s*\}/g +// Match `with { type: 'vike-pointer' }` (with optional whitespace variations) +const runtimeAttrRE = /\bwith\s*\{\s*type\s*:\s*['"]vike-pointer['"]\s*\}/g // === Rolldown filter const filterRolldown = { code: { - include: 'runtime', + include: 'vike-pointer', }, } const filterFunction = (code: string) => runtimeAttrRE.test(code) diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts index 26e16f56655..996ee432baa 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts @@ -251,9 +251,9 @@ async function transpileWithEsbuild( // - vike@0.4.162 started soft-requiring Vike extensions to set the name config. // - In practice, it seems like it requires some (non-trivial?) refactoring. isVikeExtensionImport || - // `with { type: 'runtime' }` => regular pointer import so that the pointer import + // `with { type: 'vike-pointer' }` => regular pointer import so that the pointer import // mechanism correctly encodes path + export name (import:path:exportName). - args.with?.['type'] === 'runtime' + args.with?.['type'] === 'vike-pointer' assertPosixPath(importPathResolved) // `isNpmPkgImport` => `importPathOriginal` is most likely an npm package import, but it can also be a path alias that a) looks like an npm package import and b) resolves outside of `userRootDir`. From b48bbcce3bbb45dad07f7b25254055e346b365e7 Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 22:00:49 +0100 Subject: [PATCH 29/34] minor refactor: rename pluginStripRuntimeImportAttribute => pluginStripPointerImportAttribute --- packages/vike/src/node/vite/index.ts | 4 ++-- ...mportAttribute.ts => pluginStripPointerImportAttribute.ts} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename packages/vike/src/node/vite/plugins/{pluginStripRuntimeImportAttribute.ts => pluginStripPointerImportAttribute.ts} (93%) diff --git a/packages/vike/src/node/vite/index.ts b/packages/vike/src/node/vite/index.ts index 863396907d0..8dd11175aaf 100644 --- a/packages/vike/src/node/vite/index.ts +++ b/packages/vike/src/node/vite/index.ts @@ -40,7 +40,7 @@ import { pluginModuleBanner } from './plugins/build/pluginModuleBanner.js' import { pluginReplaceConstantsNonRunnableDev } from './plugins/non-runnable-dev/pluginReplaceConstantsNonRunnableDev.js' import { isVikeCliOrApi } from '../../shared-server-node/api-context.js' import { pluginViteConfigVikeExtensions } from './plugins/pluginViteConfigVikeExtensions.js' -import { pluginStripRuntimeImportAttribute } from './plugins/pluginStripRuntimeImportAttribute.js' +import { pluginStripPointerImportAttribute } from './plugins/pluginStripPointerImportAttribute.js' import { getVikeConfigInternalEarly, isOnlyResolvingUserConfig } from '../api/resolveViteConfigFromUser.js' import './assertEnvVite.js' @@ -74,7 +74,7 @@ function plugin(vikeVitePluginOptions: VikeVitePluginOptions = {}): Promise runtimeAttrRE.test(code) // === -function pluginStripRuntimeImportAttribute(): Plugin[] { +function pluginStripPointerImportAttribute(): Plugin[] { return [ { name: 'vike:stripRuntimeImportAttribute', From d44d27fa23994e186f76685ec5ee7b2ef4126a7d Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 22:03:04 +0100 Subject: [PATCH 30/34] comment --- .../src/node/vite/shared/resolveVikeConfigInternal.ts | 10 +++++++--- .../transpileAndExecuteFile.ts | 2 -- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal.ts index f35944c0b11..07ed6346272 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal.ts @@ -979,11 +979,15 @@ function getConfigValueSources( ...configValueSourceCommon, ...confVal, configEnv: configEnvResolved, + // TODO/now: rename valueIsLoadedWithImport valueLoadedViaImport valueIsLoadedWithImport: - !confVal.valueIsLoaded || - !isJsonValue(confVal.value) || + // If contains runtime code => always load via import (not strictly required, but seems to be a good default) !!configEnvResolved.client || - !!configEnvResolved.server, + !!configEnvResolved.server || + // Not possible: value isn't loaded at config-time + !confVal.valueIsLoaded || + // Not possible: value isn't serializable + !isJsonValue(confVal.value), valueIsDefinedByPlusValueFile: true, definedAt: { ...plusFile.filePath, diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts index 996ee432baa..ede6aed1947 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal/transpileAndExecuteFile.ts @@ -251,8 +251,6 @@ async function transpileWithEsbuild( // - vike@0.4.162 started soft-requiring Vike extensions to set the name config. // - In practice, it seems like it requires some (non-trivial?) refactoring. isVikeExtensionImport || - // `with { type: 'vike-pointer' }` => regular pointer import so that the pointer import - // mechanism correctly encodes path + export name (import:path:exportName). args.with?.['type'] === 'vike-pointer' assertPosixPath(importPathResolved) From d348534415077507ab9176032bab265c7f19325d Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 22:16:12 +0100 Subject: [PATCH 31/34] minor refactor --- .../pluginStripPointerImportAttribute.ts | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/packages/vike/src/node/vite/plugins/pluginStripPointerImportAttribute.ts b/packages/vike/src/node/vite/plugins/pluginStripPointerImportAttribute.ts index c580d4be5ac..8556d7f1c17 100644 --- a/packages/vike/src/node/vite/plugins/pluginStripPointerImportAttribute.ts +++ b/packages/vike/src/node/vite/plugins/pluginStripPointerImportAttribute.ts @@ -12,24 +12,19 @@ import '../assertEnvVite.js' // Match `with { type: 'vike-pointer' }` (with optional whitespace variations) const runtimeAttrRE = /\bwith\s*\{\s*type\s*:\s*['"]vike-pointer['"]\s*\}/g -// === Rolldown filter -const filterRolldown = { - code: { - include: 'vike-pointer', - }, -} -const filterFunction = (code: string) => runtimeAttrRE.test(code) -// === - function pluginStripPointerImportAttribute(): Plugin[] { return [ { - name: 'vike:stripRuntimeImportAttribute', + name: 'vike:pluginStripPointerImportAttribute', transform: { - filter: filterRolldown, + filter: { + code: { + include: 'vike-pointer', + }, + }, handler(code, id) { runtimeAttrRE.lastIndex = 0 - if (!filterFunction(code)) return + if (!runtimeAttrRE.test(code)) return const { magicString, getMagicStringResult } = getMagicString(code, id) runtimeAttrRE.lastIndex = 0 let match: RegExpExecArray | null From ffbac4fb39789f8fb80d31fe9375516cd24db883 Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 22:24:01 +0100 Subject: [PATCH 32/34] comment --- packages/vike/src/node/vite/shared/resolveVikeConfigInternal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal.ts index 07ed6346272..40bd779ea96 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal.ts @@ -981,7 +981,7 @@ function getConfigValueSources( configEnv: configEnvResolved, // TODO/now: rename valueIsLoadedWithImport valueLoadedViaImport valueIsLoadedWithImport: - // If contains runtime code => always load via import (not strictly required, but seems to be a good default) + // If +{configName}.js is (also) runtime code => always load it via import (not strictly required, but seems to be a good default) !!configEnvResolved.client || !!configEnvResolved.server || // Not possible: value isn't loaded at config-time From 0767293f2c7cb7b7d1baef20bea682a6794bdecb Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 22:24:03 +0100 Subject: [PATCH 33/34] split out vike-pointer --- packages/vike/src/node/vite/index.ts | 2 - .../pluginStripPointerImportAttribute.ts | 39 ------------------- .../transpileAndExecuteFile.ts | 3 +- 3 files changed, 1 insertion(+), 43 deletions(-) delete mode 100644 packages/vike/src/node/vite/plugins/pluginStripPointerImportAttribute.ts diff --git a/packages/vike/src/node/vite/index.ts b/packages/vike/src/node/vite/index.ts index 8dd11175aaf..4cf30a747ae 100644 --- a/packages/vike/src/node/vite/index.ts +++ b/packages/vike/src/node/vite/index.ts @@ -40,7 +40,6 @@ import { pluginModuleBanner } from './plugins/build/pluginModuleBanner.js' import { pluginReplaceConstantsNonRunnableDev } from './plugins/non-runnable-dev/pluginReplaceConstantsNonRunnableDev.js' import { isVikeCliOrApi } from '../../shared-server-node/api-context.js' import { pluginViteConfigVikeExtensions } from './plugins/pluginViteConfigVikeExtensions.js' -import { pluginStripPointerImportAttribute } from './plugins/pluginStripPointerImportAttribute.js' import { getVikeConfigInternalEarly, isOnlyResolvingUserConfig } from '../api/resolveViteConfigFromUser.js' import './assertEnvVite.js' @@ -74,7 +73,6 @@ function plugin(vikeVitePluginOptions: VikeVitePluginOptions = {}): Promise `importPathOriginal` is most likely an npm package import, but it can also be a path alias that a) looks like an npm package import and b) resolves outside of `userRootDir`. From 82ed6eafbb47842a48a4c9f9f6f9714d34d49604 Mon Sep 17 00:00:00 2001 From: Romuald Brillout Date: Thu, 26 Feb 2026 22:29:33 +0100 Subject: [PATCH 34/34] comment --- .../vike/src/node/vite/shared/resolveVikeConfigInternal.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal.ts b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal.ts index 40bd779ea96..614cc19e896 100644 --- a/packages/vike/src/node/vite/shared/resolveVikeConfigInternal.ts +++ b/packages/vike/src/node/vite/shared/resolveVikeConfigInternal.ts @@ -984,9 +984,9 @@ function getConfigValueSources( // If +{configName}.js is (also) runtime code => always load it via import (not strictly required, but seems to be a good default) !!configEnvResolved.client || !!configEnvResolved.server || - // Not possible: value isn't loaded at config-time + // No choice: value isn't loaded at config-time !confVal.valueIsLoaded || - // Not possible: value isn't serializable + // No choice: value isn't serializable !isJsonValue(confVal.value), valueIsDefinedByPlusValueFile: true, definedAt: {