From d7de8ed80ccc188e5f5aa23e7c37c15c625eb87d Mon Sep 17 00:00:00 2001 From: Katja Lutz Date: Sun, 21 Dec 2025 18:24:13 +0100 Subject: [PATCH 1/4] test: implement virtual css test --- apps/fixtures/css/src/components/test.tsx | 6 ++++++ apps/fixtures/css/src/routes/index.tsx | 1 + apps/fixtures/css/src/virtualCssPlugin.ts | 18 ++++++++++++++++++ apps/fixtures/css/vite.config.ts | 3 ++- 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 apps/fixtures/css/src/virtualCssPlugin.ts diff --git a/apps/fixtures/css/src/components/test.tsx b/apps/fixtures/css/src/components/test.tsx index 43b7de1b9..a052ba7c7 100644 --- a/apps/fixtures/css/src/components/test.tsx +++ b/apps/fixtures/css/src/components/test.tsx @@ -60,6 +60,12 @@ export const CommonTests = (props: { routeModuleClass?: string }) => ( class={props.routeModuleClass} integration="module" /> + + ({ + name: "css-fixture-virtual-css", + resolveId(source) { + if (source === id) return resolvedId; + }, + load(id) { + if (id.startsWith(resolvedId)) + return `.virtualCss { background-color: var(--color-success); }`; + }, + }) satisfies Plugin; + +export default virtualCSS; diff --git a/apps/fixtures/css/vite.config.ts b/apps/fixtures/css/vite.config.ts index 636b0a385..45a044c7a 100644 --- a/apps/fixtures/css/vite.config.ts +++ b/apps/fixtures/css/vite.config.ts @@ -2,7 +2,8 @@ import tailwindcss from "@tailwindcss/vite"; import { defineConfig } from "vite"; import { nitroV2Plugin } from "../../../packages/start-nitro-v2-vite-plugin/src"; import { solidStart } from "../../../packages/start/src/config"; +import virtualCSS from "./src/virtualCssPlugin"; export default defineConfig({ - plugins: [solidStart(), nitroV2Plugin(), tailwindcss()], + plugins: [virtualCSS(), solidStart(), nitroV2Plugin(), tailwindcss()], }); From 28e98228c5102460e9d488611498290016c51855 Mon Sep 17 00:00:00 2001 From: Katja Lutz Date: Sun, 21 Dec 2025 18:54:30 +0100 Subject: [PATCH 2/4] fix: resolve modules with vite fetchModule during dev style collection --- packages/start/src/config/manifest.ts | 3 +- packages/start/src/config/vite-utils.ts | 10 ++++-- packages/start/src/server/collect-styles.ts | 35 ++++++--------------- 3 files changed, 18 insertions(+), 30 deletions(-) diff --git a/packages/start/src/config/manifest.ts b/packages/start/src/config/manifest.ts index e1276d600..ca959947d 100644 --- a/packages/start/src/config/manifest.ts +++ b/packages/start/src/config/manifest.ts @@ -3,6 +3,7 @@ import { type PluginOption, type ViteDevServer } from "vite"; import { findStylesInModuleGraph } from "../server/collect-styles.ts"; import { VIRTUAL_MODULES } from "./constants.ts"; import { type SolidStartOptions } from "./index.ts"; +import { wrapId } from "./vite-utils.ts"; export function manifest(start: SolidStartOptions): PluginOption { let devServer: ViteDevServer = undefined!; @@ -72,7 +73,7 @@ export function manifest(start: SolidStartOptions): PluginOption { "data-vite-dev-id": "${key}", "data-vite-ref": "0", }, - children: () => import("${value}?inline").then(mod => mod.default), + children: () => import("${wrapId(value)}?inline").then(mod => mod.default), }`, ); diff --git a/packages/start/src/config/vite-utils.ts b/packages/start/src/config/vite-utils.ts index f5a7d7627..c718af9f8 100644 --- a/packages/start/src/config/vite-utils.ts +++ b/packages/start/src/config/vite-utils.ts @@ -12,6 +12,7 @@ export const FS_PREFIX = `/@fs/`; export const VALID_ID_PREFIX = `/@id/`; export const NULL_BYTE_PLACEHOLDER = `__x00__`; +const NULL_BYTE_REGEX = /^\0/; export function normalizeResolvedIdToUrl( environment: DevEnvironment, @@ -52,10 +53,13 @@ export function normalizeResolvedIdToUrl( return url; } +/** + * Inspired by: + * https://github.com/withastro/astro/blob/fddde5fad81007795eb263c7fd0cea096b8e2cba/packages/astro/src/core/util.ts#L115 + * https://github.com/vitejs/vite/blob/130e7181a55c524383c63bbfb1749d0ff7185cad/packages/vite/src/shared/utils.ts#L11 + */ export function wrapId(id: string): string { - return id.startsWith(VALID_ID_PREFIX) - ? id - : VALID_ID_PREFIX + id.replace("\0", NULL_BYTE_PLACEHOLDER); + return id.replace(NULL_BYTE_REGEX, `${VALID_ID_PREFIX}${NULL_BYTE_PLACEHOLDER}`); } export function unwrapId(id: string): string { diff --git a/packages/start/src/server/collect-styles.ts b/packages/start/src/server/collect-styles.ts index 7e4b40453..4586ead7b 100644 --- a/packages/start/src/server/collect-styles.ts +++ b/packages/start/src/server/collect-styles.ts @@ -1,30 +1,12 @@ import path from "node:path"; -import { resolve } from "pathe"; import type { DevEnvironment, EnvironmentModuleNode } from "vite"; -async function getViteModuleNode(vite: DevEnvironment, file: string) { - let nodePath = file; - let node = vite.moduleGraph.getModuleById(file); - - if (!node) { - const resolvedId = await vite.pluginContainer.resolveId(file, undefined); - if (!resolvedId) return; - - nodePath = resolvedId.id; - node = vite.moduleGraph.getModuleById(file); - } - - if (!node) { - nodePath = resolve(nodePath); - node = await vite.moduleGraph.getModuleByUrl(file); - } - - if (!node) { - await vite.moduleGraph.ensureEntryFromUrl(nodePath, false); - node = vite.moduleGraph.getModuleById(nodePath); - } - - return node; +async function getViteModuleNode(vite: DevEnvironment, file: string, importer?: string) { + try { + const res = await vite.fetchModule(file, importer); + if (!("id" in res)) return; + return vite.moduleGraph.getModuleById(res.id); + } catch (err) {} } async function findModuleDependencies( @@ -32,9 +14,10 @@ async function findModuleDependencies( file: string, deps: Set, crawledFiles = new Set(), + importer?: string, ) { crawledFiles.add(file); - const module = await getViteModuleNode(vite, file); + const module = await getViteModuleNode(vite, file, importer); if (!module?.id || deps.has(module)) return; deps.add(module); @@ -53,7 +36,7 @@ async function findModuleDependencies( if (crawledFiles.has(dep)) { continue; } - await findModuleDependencies(vite, dep, deps, crawledFiles); + await findModuleDependencies(vite, dep, deps, crawledFiles, module.id); } } From 983c75e803ed80db146ea8d73d8d07a58358ba31 Mon Sep 17 00:00:00 2001 From: Katja Lutz Date: Sun, 21 Dec 2025 20:17:23 +0100 Subject: [PATCH 3/4] fix: patch virtual module null byte in data-vite-dev-id --- packages/start/src/config/manifest.ts | 2 +- packages/start/src/server/StartServer.tsx | 2 ++ .../server/assets/PatchVirtualDevStyles.tsx | 31 +++++++++++++++++++ packages/start/src/server/spa/StartServer.tsx | 2 ++ 4 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 packages/start/src/server/assets/PatchVirtualDevStyles.tsx diff --git a/packages/start/src/config/manifest.ts b/packages/start/src/config/manifest.ts index ca959947d..093cc7209 100644 --- a/packages/start/src/config/manifest.ts +++ b/packages/start/src/config/manifest.ts @@ -70,7 +70,7 @@ export function manifest(start: SolidStartOptions): PluginOption { tag: "style", attrs: { type: "text/css", - "data-vite-dev-id": "${key}", + "data-vite-dev-id": "${wrapId(key)}", "data-vite-ref": "0", }, children: () => import("${wrapId(value)}?inline").then(mod => mod.default), diff --git a/packages/start/src/server/StartServer.tsx b/packages/start/src/server/StartServer.tsx index 97419175c..3ca15d1e1 100644 --- a/packages/start/src/server/StartServer.tsx +++ b/packages/start/src/server/StartServer.tsx @@ -5,6 +5,7 @@ import App from "solid-start:app"; import { ErrorBoundary, TopErrorBoundary } from "../shared/ErrorBoundary.tsx"; import { useAssets } from "./assets/index.ts"; +import PatchVirtualDevStyles from "./assets/PatchVirtualDevStyles.tsx"; import { getSsrManifest } from "./manifest/ssr-manifest.ts"; import type { DocumentComponentProps, PageEvent } from "./types.ts"; @@ -29,6 +30,7 @@ export function StartServer(props: { document: Component assets={} scripts={ <> +