@tanstack/start-plugin-core 1.147.0 → 1.147.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/dev-server-plugin/dev-styles.js +2 -2
- package/dist/esm/dev-server-plugin/dev-styles.js.map +1 -1
- package/dist/esm/dev-server-plugin/plugin.js +35 -26
- package/dist/esm/dev-server-plugin/plugin.js.map +1 -1
- package/package.json +6 -6
- package/src/dev-server-plugin/dev-styles.ts +2 -2
- package/src/dev-server-plugin/plugin.ts +55 -46
|
@@ -17,13 +17,13 @@ async function collectDevStyles(opts) {
|
|
|
17
17
|
const visited = /* @__PURE__ */ new Set();
|
|
18
18
|
for (const entry of entries) {
|
|
19
19
|
const normalizedPath = entry.replace(/\\/g, "/");
|
|
20
|
-
let node =
|
|
20
|
+
let node = viteDevServer.moduleGraph.getModuleById(normalizedPath);
|
|
21
21
|
if (!node) {
|
|
22
22
|
try {
|
|
23
23
|
await viteDevServer.transformRequest(normalizedPath);
|
|
24
24
|
} catch (err) {
|
|
25
25
|
}
|
|
26
|
-
node =
|
|
26
|
+
node = viteDevServer.moduleGraph.getModuleById(normalizedPath);
|
|
27
27
|
}
|
|
28
28
|
if (node) {
|
|
29
29
|
await crawlModuleForCss(viteDevServer, node, visited, styles);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-styles.js","sources":["../../../src/dev-server-plugin/dev-styles.ts"],"sourcesContent":["/**\n * CSS collection for dev mode.\n * Crawls the Vite module graph to collect CSS from the router entry and all its dependencies.\n */\nimport type { ModuleNode, ViteDevServer } from 'vite'\n\n// CSS file extensions supported by Vite\nconst CSS_FILE_REGEX =\n /\\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\\?)/\n// URL params that indicate CSS should not be injected (e.g., ?url, ?inline)\nconst CSS_SIDE_EFFECT_FREE_PARAMS = ['url', 'inline', 'raw', 'inline-css']\n\nfunction isCssFile(file: string): boolean {\n return CSS_FILE_REGEX.test(file)\n}\n\nfunction hasCssSideEffectFreeParam(url: string): boolean {\n const queryString = url.split('?')[1]\n if (!queryString) return false\n\n const params = new URLSearchParams(queryString)\n return CSS_SIDE_EFFECT_FREE_PARAMS.some(\n (param) =>\n params.get(param) === '' &&\n !url.includes(`?${param}=`) &&\n !url.includes(`&${param}=`),\n )\n}\n\nexport interface CollectDevStylesOptions {\n viteDevServer: ViteDevServer\n entries: Array<string>\n}\n\n/**\n * Collect CSS content from the module graph starting from the given entry points.\n */\nexport async function collectDevStyles(\n opts: CollectDevStylesOptions,\n): Promise<string | undefined> {\n const { viteDevServer, entries } = opts\n const styles: Map<string, string> = new Map()\n const visited = new Set<ModuleNode>()\n\n for (const entry of entries) {\n const normalizedPath = entry.replace(/\\\\/g, '/')\n let node =
|
|
1
|
+
{"version":3,"file":"dev-styles.js","sources":["../../../src/dev-server-plugin/dev-styles.ts"],"sourcesContent":["/**\n * CSS collection for dev mode.\n * Crawls the Vite module graph to collect CSS from the router entry and all its dependencies.\n */\nimport type { ModuleNode, ViteDevServer } from 'vite'\n\n// CSS file extensions supported by Vite\nconst CSS_FILE_REGEX =\n /\\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\\?)/\n// URL params that indicate CSS should not be injected (e.g., ?url, ?inline)\nconst CSS_SIDE_EFFECT_FREE_PARAMS = ['url', 'inline', 'raw', 'inline-css']\n\nfunction isCssFile(file: string): boolean {\n return CSS_FILE_REGEX.test(file)\n}\n\nfunction hasCssSideEffectFreeParam(url: string): boolean {\n const queryString = url.split('?')[1]\n if (!queryString) return false\n\n const params = new URLSearchParams(queryString)\n return CSS_SIDE_EFFECT_FREE_PARAMS.some(\n (param) =>\n params.get(param) === '' &&\n !url.includes(`?${param}=`) &&\n !url.includes(`&${param}=`),\n )\n}\n\nexport interface CollectDevStylesOptions {\n viteDevServer: ViteDevServer\n entries: Array<string>\n}\n\n/**\n * Collect CSS content from the module graph starting from the given entry points.\n */\nexport async function collectDevStyles(\n opts: CollectDevStylesOptions,\n): Promise<string | undefined> {\n const { viteDevServer, entries } = opts\n const styles: Map<string, string> = new Map()\n const visited = new Set<ModuleNode>()\n\n for (const entry of entries) {\n const normalizedPath = entry.replace(/\\\\/g, '/')\n let node = viteDevServer.moduleGraph.getModuleById(normalizedPath)\n\n // If module isn't in the graph yet, request it to trigger transform\n if (!node) {\n try {\n await viteDevServer.transformRequest(normalizedPath)\n } catch (err) {\n // Ignore - the module might not exist yet\n }\n node = viteDevServer.moduleGraph.getModuleById(normalizedPath)\n }\n\n if (node) {\n await crawlModuleForCss(viteDevServer, node, visited, styles)\n }\n }\n\n if (styles.size === 0) return undefined\n\n return Array.from(styles.entries())\n .map(([fileName, css]) => {\n const escapedFileName = fileName\n .replace(/\\/\\*/g, '/\\\\*')\n .replace(/\\*\\//g, '*\\\\/')\n return `\\n/* ${escapedFileName} */\\n${css}`\n })\n .join('\\n')\n}\n\nasync function crawlModuleForCss(\n vite: ViteDevServer,\n node: ModuleNode,\n visited: Set<ModuleNode>,\n styles: Map<string, string>,\n): Promise<void> {\n if (visited.has(node)) return\n visited.add(node)\n\n const branches: Array<Promise<void>> = []\n\n // Ensure the module has been transformed to populate its deps\n // This is important for code-split modules that may not have been processed yet\n if (!node.ssrTransformResult) {\n try {\n await vite.transformRequest(node.url, { ssr: true })\n // Re-fetch the node to get updated state\n const updatedNode = await vite.moduleGraph.getModuleByUrl(node.url)\n if (updatedNode) {\n node = updatedNode\n }\n } catch {\n // Ignore transform errors - the module might not be transformable\n }\n }\n\n // Check if this is a CSS file\n if (\n node.file &&\n isCssFile(node.file) &&\n !hasCssSideEffectFreeParam(node.url)\n ) {\n const css = await loadCssContent(vite, node)\n if (css) {\n styles.set(node.url, css)\n }\n }\n\n // Crawl dependencies using ssrTransformResult.deps and importedModules\n // We need both because:\n // 1. ssrTransformResult.deps has resolved URLs for SSR dependencies\n // 2. importedModules may contain CSS files and code-split modules not in SSR deps\n const depsFromSsr = node.ssrTransformResult?.deps ?? []\n const urlsToVisit = new Set<string>(depsFromSsr)\n\n // Check importedModules for CSS files and additional modules\n for (const importedNode of node.importedModules) {\n if (importedNode.file && isCssFile(importedNode.file)) {\n // CSS files often don't appear in ssrTransformResult.deps, add them explicitly\n branches.push(crawlModuleForCss(vite, importedNode, visited, styles))\n } else if (!urlsToVisit.has(importedNode.url)) {\n // Also add non-CSS imports that aren't in SSR deps (e.g., code-split modules)\n urlsToVisit.add(importedNode.url)\n }\n }\n\n for (const depUrl of urlsToVisit) {\n branches.push(\n (async () => {\n const depNode = await vite.moduleGraph.getModuleByUrl(depUrl)\n if (depNode) {\n await crawlModuleForCss(vite, depNode, visited, styles)\n }\n })(),\n )\n }\n\n await Promise.all(branches)\n}\n\nasync function loadCssContent(\n vite: ViteDevServer,\n node: ModuleNode,\n): Promise<string | undefined> {\n // For ALL CSS files (including CSS modules), get the transformed content\n // and extract __vite__css. Vite's transform puts the final CSS (with hashed\n // class names for modules) into the __vite__css variable.\n const transformResult = await vite.transformRequest(node.url)\n if (!transformResult?.code) return undefined\n\n // Extract CSS content from Vite's transformed module\n return extractCssFromViteModule(transformResult.code)\n}\n\n/**\n * Extract CSS string from Vite's transformed CSS module code.\n * Vite wraps CSS content in a JS module with __vite__css variable.\n */\nfunction extractCssFromViteModule(code: string): string | undefined {\n // Match: const __vite__css = \"...\"\n const match = code.match(/const\\s+__vite__css\\s*=\\s*[\"'`]([\\s\\S]*?)[\"'`]/)\n if (match?.[1]) {\n // Unescape the string\n return match[1]\n .replace(/\\\\n/g, '\\n')\n .replace(/\\\\t/g, '\\t')\n .replace(/\\\\\"/g, '\"')\n .replace(/\\\\\\\\/g, '\\\\')\n }\n return undefined\n}\n"],"names":[],"mappings":"AAOA,MAAM,iBACJ;AAEF,MAAM,8BAA8B,CAAC,OAAO,UAAU,OAAO,YAAY;AAEzE,SAAS,UAAU,MAAuB;AACxC,SAAO,eAAe,KAAK,IAAI;AACjC;AAEA,SAAS,0BAA0B,KAAsB;AACvD,QAAM,cAAc,IAAI,MAAM,GAAG,EAAE,CAAC;AACpC,MAAI,CAAC,YAAa,QAAO;AAEzB,QAAM,SAAS,IAAI,gBAAgB,WAAW;AAC9C,SAAO,4BAA4B;AAAA,IACjC,CAAC,UACC,OAAO,IAAI,KAAK,MAAM,MACtB,CAAC,IAAI,SAAS,IAAI,KAAK,GAAG,KAC1B,CAAC,IAAI,SAAS,IAAI,KAAK,GAAG;AAAA,EAAA;AAEhC;AAUA,eAAsB,iBACpB,MAC6B;AAC7B,QAAM,EAAE,eAAe,QAAA,IAAY;AACnC,QAAM,6BAAkC,IAAA;AACxC,QAAM,8BAAc,IAAA;AAEpB,aAAW,SAAS,SAAS;AAC3B,UAAM,iBAAiB,MAAM,QAAQ,OAAO,GAAG;AAC/C,QAAI,OAAO,cAAc,YAAY,cAAc,cAAc;AAGjE,QAAI,CAAC,MAAM;AACT,UAAI;AACF,cAAM,cAAc,iBAAiB,cAAc;AAAA,MACrD,SAAS,KAAK;AAAA,MAEd;AACA,aAAO,cAAc,YAAY,cAAc,cAAc;AAAA,IAC/D;AAEA,QAAI,MAAM;AACR,YAAM,kBAAkB,eAAe,MAAM,SAAS,MAAM;AAAA,IAC9D;AAAA,EACF;AAEA,MAAI,OAAO,SAAS,EAAG,QAAO;AAE9B,SAAO,MAAM,KAAK,OAAO,QAAA,CAAS,EAC/B,IAAI,CAAC,CAAC,UAAU,GAAG,MAAM;AACxB,UAAM,kBAAkB,SACrB,QAAQ,SAAS,MAAM,EACvB,QAAQ,SAAS,MAAM;AAC1B,WAAO;AAAA,KAAQ,eAAe;AAAA,EAAQ,GAAG;AAAA,EAC3C,CAAC,EACA,KAAK,IAAI;AACd;AAEA,eAAe,kBACb,MACA,MACA,SACA,QACe;AACf,MAAI,QAAQ,IAAI,IAAI,EAAG;AACvB,UAAQ,IAAI,IAAI;AAEhB,QAAM,WAAiC,CAAA;AAIvC,MAAI,CAAC,KAAK,oBAAoB;AAC5B,QAAI;AACF,YAAM,KAAK,iBAAiB,KAAK,KAAK,EAAE,KAAK,MAAM;AAEnD,YAAM,cAAc,MAAM,KAAK,YAAY,eAAe,KAAK,GAAG;AAClE,UAAI,aAAa;AACf,eAAO;AAAA,MACT;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,MACE,KAAK,QACL,UAAU,KAAK,IAAI,KACnB,CAAC,0BAA0B,KAAK,GAAG,GACnC;AACA,UAAM,MAAM,MAAM,eAAe,MAAM,IAAI;AAC3C,QAAI,KAAK;AACP,aAAO,IAAI,KAAK,KAAK,GAAG;AAAA,IAC1B;AAAA,EACF;AAMA,QAAM,cAAc,KAAK,oBAAoB,QAAQ,CAAA;AACrD,QAAM,cAAc,IAAI,IAAY,WAAW;AAG/C,aAAW,gBAAgB,KAAK,iBAAiB;AAC/C,QAAI,aAAa,QAAQ,UAAU,aAAa,IAAI,GAAG;AAErD,eAAS,KAAK,kBAAkB,MAAM,cAAc,SAAS,MAAM,CAAC;AAAA,IACtE,WAAW,CAAC,YAAY,IAAI,aAAa,GAAG,GAAG;AAE7C,kBAAY,IAAI,aAAa,GAAG;AAAA,IAClC;AAAA,EACF;AAEA,aAAW,UAAU,aAAa;AAChC,aAAS;AAAA,OACN,YAAY;AACX,cAAM,UAAU,MAAM,KAAK,YAAY,eAAe,MAAM;AAC5D,YAAI,SAAS;AACX,gBAAM,kBAAkB,MAAM,SAAS,SAAS,MAAM;AAAA,QACxD;AAAA,MACF,GAAA;AAAA,IAAG;AAAA,EAEP;AAEA,QAAM,QAAQ,IAAI,QAAQ;AAC5B;AAEA,eAAe,eACb,MACA,MAC6B;AAI7B,QAAM,kBAAkB,MAAM,KAAK,iBAAiB,KAAK,GAAG;AAC5D,MAAI,CAAC,iBAAiB,KAAM,QAAO;AAGnC,SAAO,yBAAyB,gBAAgB,IAAI;AACtD;AAMA,SAAS,yBAAyB,MAAkC;AAElE,QAAM,QAAQ,KAAK,MAAM,gDAAgD;AACzE,MAAI,QAAQ,CAAC,GAAG;AAEd,WAAO,MAAM,CAAC,EACX,QAAQ,QAAQ,IAAI,EACpB,QAAQ,QAAQ,GAAI,EACpB,QAAQ,QAAQ,GAAG,EACnB,QAAQ,SAAS,IAAI;AAAA,EAC1B;AACA,SAAO;AACT;"}
|
|
@@ -34,6 +34,41 @@ function devServerPlugin({
|
|
|
34
34
|
`Server environment ${VITE_ENVIRONMENT_NAMES.server} not found`
|
|
35
35
|
);
|
|
36
36
|
}
|
|
37
|
+
viteDevServer.middlewares.use(async (req, res, next) => {
|
|
38
|
+
const url = req.url ?? "";
|
|
39
|
+
if (!url.startsWith("/@tanstack-start/styles.css")) {
|
|
40
|
+
return next();
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const urlObj = new URL(url, "http://localhost");
|
|
44
|
+
const routesParam = urlObj.searchParams.get("routes");
|
|
45
|
+
const routeIds = routesParam ? routesParam.split(",") : [];
|
|
46
|
+
const entries = [];
|
|
47
|
+
const routesManifest = globalThis.TSS_ROUTES_MANIFEST;
|
|
48
|
+
if (routesManifest && routeIds.length > 0) {
|
|
49
|
+
for (const routeId of routeIds) {
|
|
50
|
+
const route = routesManifest[routeId];
|
|
51
|
+
if (route?.filePath) {
|
|
52
|
+
entries.push(route.filePath);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const css = entries.length > 0 ? await collectDevStyles({
|
|
57
|
+
viteDevServer,
|
|
58
|
+
entries
|
|
59
|
+
}) : void 0;
|
|
60
|
+
res.setHeader("Content-Type", "text/css");
|
|
61
|
+
res.setHeader("Cache-Control", "no-store");
|
|
62
|
+
res.end(css ?? "");
|
|
63
|
+
} catch (e) {
|
|
64
|
+
console.error("[tanstack-start] Error collecting dev styles:", e);
|
|
65
|
+
res.setHeader("Content-Type", "text/css");
|
|
66
|
+
res.setHeader("Cache-Control", "no-store");
|
|
67
|
+
res.end(
|
|
68
|
+
`/* Error collecting styles: ${e instanceof Error ? e.message : String(e)} */`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
37
72
|
const { startConfig } = getConfig();
|
|
38
73
|
const installMiddleware = startConfig.vite?.installDevServerMiddleware;
|
|
39
74
|
if (installMiddleware === false) {
|
|
@@ -53,32 +88,6 @@ function devServerPlugin({
|
|
|
53
88
|
"cannot install vite dev server middleware for TanStack Start since the SSR environment is not a RunnableDevEnvironment"
|
|
54
89
|
);
|
|
55
90
|
}
|
|
56
|
-
viteDevServer.middlewares.use(async (req, res, next) => {
|
|
57
|
-
const url = req.url ?? "";
|
|
58
|
-
if (!url.startsWith("/@tanstack-start/styles.css")) {
|
|
59
|
-
return next();
|
|
60
|
-
}
|
|
61
|
-
const urlObj = new URL(url, "http://localhost");
|
|
62
|
-
const routesParam = urlObj.searchParams.get("routes");
|
|
63
|
-
const routeIds = routesParam ? routesParam.split(",") : [];
|
|
64
|
-
const entries = [];
|
|
65
|
-
const routesManifest = globalThis.TSS_ROUTES_MANIFEST;
|
|
66
|
-
if (routesManifest && routeIds.length > 0) {
|
|
67
|
-
for (const routeId of routeIds) {
|
|
68
|
-
const route = routesManifest[routeId];
|
|
69
|
-
if (route?.filePath) {
|
|
70
|
-
entries.push(route.filePath);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
const css = entries.length > 0 ? await collectDevStyles({
|
|
75
|
-
viteDevServer,
|
|
76
|
-
entries
|
|
77
|
-
}) : void 0;
|
|
78
|
-
res.setHeader("Content-Type", "text/css");
|
|
79
|
-
res.setHeader("Cache-Control", "no-store");
|
|
80
|
-
res.end(css ?? "");
|
|
81
|
-
});
|
|
82
91
|
viteDevServer.middlewares.use(async (req, res) => {
|
|
83
92
|
if (req.originalUrl) {
|
|
84
93
|
req.url = req.originalUrl;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sources":["../../../src/dev-server-plugin/plugin.ts"],"sourcesContent":["import { isRunnableDevEnvironment } from 'vite'\nimport { VIRTUAL_MODULES } from '@tanstack/start-server-core'\nimport { NodeRequest, sendNodeResponse } from 'srvx/node'\nimport { ENTRY_POINTS, VITE_ENVIRONMENT_NAMES } from '../constants'\nimport { resolveViteId } from '../utils'\nimport { extractHtmlScripts } from './extract-html-scripts'\nimport { collectDevStyles } from './dev-styles'\nimport type { Connect, DevEnvironment, PluginOption } from 'vite'\nimport type { GetConfigFn } from '../types'\n\nexport function devServerPlugin({\n getConfig,\n}: {\n getConfig: GetConfigFn\n}): PluginOption {\n let isTest = false\n\n let injectedHeadScripts: string | undefined\n\n return [\n {\n name: 'tanstack-start-core:dev-server',\n config(_userConfig, { mode }) {\n isTest = isTest ? isTest : mode === 'test'\n },\n async configureServer(viteDevServer) {\n if (isTest) {\n return\n }\n\n // Extract the scripts that Vite plugins would inject into the initial HTML\n const templateHtml = `<html><head></head><body></body></html>`\n const transformedHtml = await viteDevServer.transformIndexHtml(\n '/',\n templateHtml,\n )\n const scripts = extractHtmlScripts(transformedHtml)\n injectedHeadScripts = scripts\n .flatMap((script) => script.content ?? [])\n .join(';')\n\n return () => {\n const serverEnv = viteDevServer.environments[\n VITE_ENVIRONMENT_NAMES.server\n ] as DevEnvironment | undefined\n\n if (!serverEnv) {\n throw new Error(\n `Server environment ${VITE_ENVIRONMENT_NAMES.server} not found`,\n )\n }\n const { startConfig } = getConfig()\n const installMiddleware = startConfig.vite?.installDevServerMiddleware\n if (installMiddleware === false) {\n return\n }\n if (installMiddleware == undefined) {\n // do not install middleware in middlewareMode by default\n if (viteDevServer.config.server.middlewareMode) {\n return\n }\n\n // do not install middleware if SSR env in case another plugin already did\n if (\n !isRunnableDevEnvironment(serverEnv) ||\n // do not check via `isFetchableDevEnvironment` since nitro does implement the `FetchableDevEnvironment` interface but not via inheritance (which this helper checks)\n 'dispatchFetch' in serverEnv\n ) {\n return\n }\n }\n\n if (!isRunnableDevEnvironment(serverEnv)) {\n throw new Error(\n 'cannot install vite dev server middleware for TanStack Start since the SSR environment is not a RunnableDevEnvironment',\n )\n }\n\n // Middleware to serve collected CSS for dev mode\n // Security: Route IDs from query params are validated against TSS_ROUTES_MANIFEST.\n // Only routes that exist in the manifest will have their CSS collected.\n // Arbitrary file paths cannot be injected.\n viteDevServer.middlewares.use(async (req, res, next) => {\n const url = req.url ?? ''\n if (!url.startsWith('/@tanstack-start/styles.css')) {\n return next()\n }\n\n // Parse route IDs from query param\n const urlObj = new URL(url, 'http://localhost')\n const routesParam = urlObj.searchParams.get('routes')\n const routeIds = routesParam ? routesParam.split(',') : []\n\n // Build entries list from route file paths\n const entries: Array<string> = []\n\n // Look up route file paths from manifest\n // Only routes registered in the manifest are used - this prevents path injection\n const routesManifest = (globalThis as any).TSS_ROUTES_MANIFEST as\n | Record<string, { filePath: string; children?: Array<string> }>\n | undefined\n\n if (routesManifest && routeIds.length > 0) {\n for (const routeId of routeIds) {\n const route = routesManifest[routeId]\n if (route?.filePath) {\n entries.push(route.filePath)\n }\n }\n }\n\n const css =\n entries.length > 0\n ? await collectDevStyles({\n viteDevServer,\n entries,\n })\n : undefined\n\n res.setHeader('Content-Type', 'text/css')\n res.setHeader('Cache-Control', 'no-store')\n res.end(css ?? '')\n })\n\n viteDevServer.middlewares.use(async (req, res) => {\n // fix the request URL to match the original URL\n // otherwise, the request URL will '/index.html'\n if (req.originalUrl) {\n req.url = req.originalUrl\n }\n const webReq = new NodeRequest({ req, res })\n\n try {\n // Import and resolve the request by running the server request entry point\n // this request entry point must implement the `fetch` API as follows:\n /**\n * export default {\n * fetch(req: Request): Promise<Response>\n * }\n */\n const serverEntry = await serverEnv.runner.import(\n ENTRY_POINTS.server,\n )\n const webRes = await serverEntry['default'].fetch(webReq)\n\n return sendNodeResponse(res, webRes)\n } catch (e) {\n console.error(e)\n try {\n viteDevServer.ssrFixStacktrace(e as Error)\n } catch (_e) {}\n\n if (\n webReq.headers.get('content-type')?.includes('application/json')\n ) {\n return sendNodeResponse(\n res,\n new Response(\n JSON.stringify(\n {\n status: 500,\n error: 'Internal Server Error',\n message:\n 'An unexpected error occurred. Please try again later.',\n timestamp: new Date().toISOString(),\n },\n null,\n 2,\n ),\n {\n status: 500,\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n ),\n )\n }\n\n return sendNodeResponse(\n res,\n new Response(\n `\n <!DOCTYPE html>\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <title>Error</title>\n <script type=\"module\">\n import { ErrorOverlay } from '/@vite/client'\n document.body.appendChild(new ErrorOverlay(${JSON.stringify(\n prepareError(req, e),\n ).replace(/</g, '\\\\u003c')}))\n </script>\n </head>\n <body>\n </body>\n </html>\n `,\n {\n status: 500,\n headers: {\n 'Content-Type': 'text/html',\n },\n },\n ),\n )\n }\n })\n }\n },\n },\n {\n name: 'tanstack-start-core:dev-server:injected-head-scripts',\n sharedDuringBuild: true,\n applyToEnvironment: (env) => env.config.consumer === 'server',\n resolveId: {\n filter: { id: new RegExp(VIRTUAL_MODULES.injectedHeadScripts) },\n handler(_id) {\n return resolveViteId(VIRTUAL_MODULES.injectedHeadScripts)\n },\n },\n load: {\n filter: {\n id: new RegExp(resolveViteId(VIRTUAL_MODULES.injectedHeadScripts)),\n },\n handler() {\n const mod = `\n export const injectedHeadScripts = ${JSON.stringify(injectedHeadScripts) || 'undefined'}`\n return mod\n },\n },\n },\n ]\n}\n\n/**\n * Formats error for SSR message in error overlay\n * @param req\n * @param error\n * @returns\n */\nfunction prepareError(req: Connect.IncomingMessage, error: unknown) {\n const e = error as Error\n return {\n message: `An error occurred while server rendering ${req.url}:\\n\\n\\t${\n typeof e === 'string' ? e : e.message\n } `,\n stack: typeof e === 'string' ? '' : e.stack,\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin.js","sources":["../../../src/dev-server-plugin/plugin.ts"],"sourcesContent":["import { isRunnableDevEnvironment } from 'vite'\nimport { VIRTUAL_MODULES } from '@tanstack/start-server-core'\nimport { NodeRequest, sendNodeResponse } from 'srvx/node'\nimport { ENTRY_POINTS, VITE_ENVIRONMENT_NAMES } from '../constants'\nimport { resolveViteId } from '../utils'\nimport { extractHtmlScripts } from './extract-html-scripts'\nimport { collectDevStyles } from './dev-styles'\nimport type { Connect, DevEnvironment, PluginOption } from 'vite'\nimport type { GetConfigFn } from '../types'\n\nexport function devServerPlugin({\n getConfig,\n}: {\n getConfig: GetConfigFn\n}): PluginOption {\n let isTest = false\n\n let injectedHeadScripts: string | undefined\n\n return [\n {\n name: 'tanstack-start-core:dev-server',\n config(_userConfig, { mode }) {\n isTest = isTest ? isTest : mode === 'test'\n },\n async configureServer(viteDevServer) {\n if (isTest) {\n return\n }\n\n // Extract the scripts that Vite plugins would inject into the initial HTML\n const templateHtml = `<html><head></head><body></body></html>`\n const transformedHtml = await viteDevServer.transformIndexHtml(\n '/',\n templateHtml,\n )\n const scripts = extractHtmlScripts(transformedHtml)\n injectedHeadScripts = scripts\n .flatMap((script) => script.content ?? [])\n .join(';')\n\n return () => {\n const serverEnv = viteDevServer.environments[\n VITE_ENVIRONMENT_NAMES.server\n ] as DevEnvironment | undefined\n\n if (!serverEnv) {\n throw new Error(\n `Server environment ${VITE_ENVIRONMENT_NAMES.server} not found`,\n )\n }\n\n // CSS middleware is always installed - it doesn't depend on the server environment type\n // This ensures dev styles work with nitro, cloudflare, and other environments\n viteDevServer.middlewares.use(async (req, res, next) => {\n const url = req.url ?? ''\n if (!url.startsWith('/@tanstack-start/styles.css')) {\n return next()\n }\n\n try {\n // Parse route IDs from query param\n const urlObj = new URL(url, 'http://localhost')\n const routesParam = urlObj.searchParams.get('routes')\n const routeIds = routesParam ? routesParam.split(',') : []\n\n // Build entries list from route file paths\n const entries: Array<string> = []\n\n // Look up route file paths from manifest\n // Only routes registered in the manifest are used - this prevents path injection\n const routesManifest = (globalThis as any).TSS_ROUTES_MANIFEST as\n | Record<string, { filePath: string; children?: Array<string> }>\n | undefined\n\n if (routesManifest && routeIds.length > 0) {\n for (const routeId of routeIds) {\n const route = routesManifest[routeId]\n if (route?.filePath) {\n entries.push(route.filePath)\n }\n }\n }\n\n const css =\n entries.length > 0\n ? await collectDevStyles({\n viteDevServer,\n entries,\n })\n : undefined\n\n res.setHeader('Content-Type', 'text/css')\n res.setHeader('Cache-Control', 'no-store')\n res.end(css ?? '')\n } catch (e) {\n // Log error but still return valid CSS response to avoid MIME type issues\n console.error('[tanstack-start] Error collecting dev styles:', e)\n res.setHeader('Content-Type', 'text/css')\n res.setHeader('Cache-Control', 'no-store')\n res.end(\n `/* Error collecting styles: ${e instanceof Error ? e.message : String(e)} */`,\n )\n }\n })\n\n const { startConfig } = getConfig()\n const installMiddleware = startConfig.vite?.installDevServerMiddleware\n if (installMiddleware === false) {\n return\n }\n if (installMiddleware == undefined) {\n // do not install middleware in middlewareMode by default\n if (viteDevServer.config.server.middlewareMode) {\n return\n }\n\n // do not install middleware if SSR env in case another plugin already did\n if (\n !isRunnableDevEnvironment(serverEnv) ||\n // do not check via `isFetchableDevEnvironment` since nitro does implement the `FetchableDevEnvironment` interface but not via inheritance (which this helper checks)\n 'dispatchFetch' in serverEnv\n ) {\n return\n }\n }\n\n if (!isRunnableDevEnvironment(serverEnv)) {\n throw new Error(\n 'cannot install vite dev server middleware for TanStack Start since the SSR environment is not a RunnableDevEnvironment',\n )\n }\n\n viteDevServer.middlewares.use(async (req, res) => {\n // fix the request URL to match the original URL\n // otherwise, the request URL will '/index.html'\n if (req.originalUrl) {\n req.url = req.originalUrl\n }\n const webReq = new NodeRequest({ req, res })\n\n try {\n // Import and resolve the request by running the server request entry point\n // this request entry point must implement the `fetch` API as follows:\n /**\n * export default {\n * fetch(req: Request): Promise<Response>\n * }\n */\n const serverEntry = await serverEnv.runner.import(\n ENTRY_POINTS.server,\n )\n const webRes = await serverEntry['default'].fetch(webReq)\n\n return sendNodeResponse(res, webRes)\n } catch (e) {\n console.error(e)\n try {\n viteDevServer.ssrFixStacktrace(e as Error)\n } catch (_e) {}\n\n if (\n webReq.headers.get('content-type')?.includes('application/json')\n ) {\n return sendNodeResponse(\n res,\n new Response(\n JSON.stringify(\n {\n status: 500,\n error: 'Internal Server Error',\n message:\n 'An unexpected error occurred. Please try again later.',\n timestamp: new Date().toISOString(),\n },\n null,\n 2,\n ),\n {\n status: 500,\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n ),\n )\n }\n\n return sendNodeResponse(\n res,\n new Response(\n `\n <!DOCTYPE html>\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <title>Error</title>\n <script type=\"module\">\n import { ErrorOverlay } from '/@vite/client'\n document.body.appendChild(new ErrorOverlay(${JSON.stringify(\n prepareError(req, e),\n ).replace(/</g, '\\\\u003c')}))\n </script>\n </head>\n <body>\n </body>\n </html>\n `,\n {\n status: 500,\n headers: {\n 'Content-Type': 'text/html',\n },\n },\n ),\n )\n }\n })\n }\n },\n },\n {\n name: 'tanstack-start-core:dev-server:injected-head-scripts',\n sharedDuringBuild: true,\n applyToEnvironment: (env) => env.config.consumer === 'server',\n resolveId: {\n filter: { id: new RegExp(VIRTUAL_MODULES.injectedHeadScripts) },\n handler(_id) {\n return resolveViteId(VIRTUAL_MODULES.injectedHeadScripts)\n },\n },\n load: {\n filter: {\n id: new RegExp(resolveViteId(VIRTUAL_MODULES.injectedHeadScripts)),\n },\n handler() {\n const mod = `\n export const injectedHeadScripts = ${JSON.stringify(injectedHeadScripts) || 'undefined'}`\n return mod\n },\n },\n },\n ]\n}\n\n/**\n * Formats error for SSR message in error overlay\n * @param req\n * @param error\n * @returns\n */\nfunction prepareError(req: Connect.IncomingMessage, error: unknown) {\n const e = error as Error\n return {\n message: `An error occurred while server rendering ${req.url}:\\n\\n\\t${\n typeof e === 'string' ? e : e.message\n } `,\n stack: typeof e === 'string' ? '' : e.stack,\n }\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/start-plugin-core",
|
|
3
|
-
"version": "1.147.
|
|
3
|
+
"version": "1.147.2",
|
|
4
4
|
"description": "Modern and scalable routing for React applications",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -59,12 +59,12 @@
|
|
|
59
59
|
"vitefu": "^1.1.1",
|
|
60
60
|
"xmlbuilder2": "^4.0.3",
|
|
61
61
|
"zod": "^3.24.2",
|
|
62
|
-
"@tanstack/router-
|
|
63
|
-
"@tanstack/router-
|
|
62
|
+
"@tanstack/router-core": "1.147.1",
|
|
63
|
+
"@tanstack/router-generator": "1.147.1",
|
|
64
64
|
"@tanstack/router-utils": "1.143.11",
|
|
65
|
-
"@tanstack/start-client-core": "1.
|
|
66
|
-
"@tanstack/router-plugin": "1.147.
|
|
67
|
-
"@tanstack/start-server-core": "1.
|
|
65
|
+
"@tanstack/start-client-core": "1.147.1",
|
|
66
|
+
"@tanstack/router-plugin": "1.147.2",
|
|
67
|
+
"@tanstack/start-server-core": "1.147.1"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@types/babel__code-frame": "^7.0.6",
|
|
@@ -44,7 +44,7 @@ export async function collectDevStyles(
|
|
|
44
44
|
|
|
45
45
|
for (const entry of entries) {
|
|
46
46
|
const normalizedPath = entry.replace(/\\/g, '/')
|
|
47
|
-
let node =
|
|
47
|
+
let node = viteDevServer.moduleGraph.getModuleById(normalizedPath)
|
|
48
48
|
|
|
49
49
|
// If module isn't in the graph yet, request it to trigger transform
|
|
50
50
|
if (!node) {
|
|
@@ -53,7 +53,7 @@ export async function collectDevStyles(
|
|
|
53
53
|
} catch (err) {
|
|
54
54
|
// Ignore - the module might not exist yet
|
|
55
55
|
}
|
|
56
|
-
node =
|
|
56
|
+
node = viteDevServer.moduleGraph.getModuleById(normalizedPath)
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
if (node) {
|
|
@@ -49,6 +49,61 @@ export function devServerPlugin({
|
|
|
49
49
|
`Server environment ${VITE_ENVIRONMENT_NAMES.server} not found`,
|
|
50
50
|
)
|
|
51
51
|
}
|
|
52
|
+
|
|
53
|
+
// CSS middleware is always installed - it doesn't depend on the server environment type
|
|
54
|
+
// This ensures dev styles work with nitro, cloudflare, and other environments
|
|
55
|
+
viteDevServer.middlewares.use(async (req, res, next) => {
|
|
56
|
+
const url = req.url ?? ''
|
|
57
|
+
if (!url.startsWith('/@tanstack-start/styles.css')) {
|
|
58
|
+
return next()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
// Parse route IDs from query param
|
|
63
|
+
const urlObj = new URL(url, 'http://localhost')
|
|
64
|
+
const routesParam = urlObj.searchParams.get('routes')
|
|
65
|
+
const routeIds = routesParam ? routesParam.split(',') : []
|
|
66
|
+
|
|
67
|
+
// Build entries list from route file paths
|
|
68
|
+
const entries: Array<string> = []
|
|
69
|
+
|
|
70
|
+
// Look up route file paths from manifest
|
|
71
|
+
// Only routes registered in the manifest are used - this prevents path injection
|
|
72
|
+
const routesManifest = (globalThis as any).TSS_ROUTES_MANIFEST as
|
|
73
|
+
| Record<string, { filePath: string; children?: Array<string> }>
|
|
74
|
+
| undefined
|
|
75
|
+
|
|
76
|
+
if (routesManifest && routeIds.length > 0) {
|
|
77
|
+
for (const routeId of routeIds) {
|
|
78
|
+
const route = routesManifest[routeId]
|
|
79
|
+
if (route?.filePath) {
|
|
80
|
+
entries.push(route.filePath)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const css =
|
|
86
|
+
entries.length > 0
|
|
87
|
+
? await collectDevStyles({
|
|
88
|
+
viteDevServer,
|
|
89
|
+
entries,
|
|
90
|
+
})
|
|
91
|
+
: undefined
|
|
92
|
+
|
|
93
|
+
res.setHeader('Content-Type', 'text/css')
|
|
94
|
+
res.setHeader('Cache-Control', 'no-store')
|
|
95
|
+
res.end(css ?? '')
|
|
96
|
+
} catch (e) {
|
|
97
|
+
// Log error but still return valid CSS response to avoid MIME type issues
|
|
98
|
+
console.error('[tanstack-start] Error collecting dev styles:', e)
|
|
99
|
+
res.setHeader('Content-Type', 'text/css')
|
|
100
|
+
res.setHeader('Cache-Control', 'no-store')
|
|
101
|
+
res.end(
|
|
102
|
+
`/* Error collecting styles: ${e instanceof Error ? e.message : String(e)} */`,
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
|
|
52
107
|
const { startConfig } = getConfig()
|
|
53
108
|
const installMiddleware = startConfig.vite?.installDevServerMiddleware
|
|
54
109
|
if (installMiddleware === false) {
|
|
@@ -76,52 +131,6 @@ export function devServerPlugin({
|
|
|
76
131
|
)
|
|
77
132
|
}
|
|
78
133
|
|
|
79
|
-
// Middleware to serve collected CSS for dev mode
|
|
80
|
-
// Security: Route IDs from query params are validated against TSS_ROUTES_MANIFEST.
|
|
81
|
-
// Only routes that exist in the manifest will have their CSS collected.
|
|
82
|
-
// Arbitrary file paths cannot be injected.
|
|
83
|
-
viteDevServer.middlewares.use(async (req, res, next) => {
|
|
84
|
-
const url = req.url ?? ''
|
|
85
|
-
if (!url.startsWith('/@tanstack-start/styles.css')) {
|
|
86
|
-
return next()
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Parse route IDs from query param
|
|
90
|
-
const urlObj = new URL(url, 'http://localhost')
|
|
91
|
-
const routesParam = urlObj.searchParams.get('routes')
|
|
92
|
-
const routeIds = routesParam ? routesParam.split(',') : []
|
|
93
|
-
|
|
94
|
-
// Build entries list from route file paths
|
|
95
|
-
const entries: Array<string> = []
|
|
96
|
-
|
|
97
|
-
// Look up route file paths from manifest
|
|
98
|
-
// Only routes registered in the manifest are used - this prevents path injection
|
|
99
|
-
const routesManifest = (globalThis as any).TSS_ROUTES_MANIFEST as
|
|
100
|
-
| Record<string, { filePath: string; children?: Array<string> }>
|
|
101
|
-
| undefined
|
|
102
|
-
|
|
103
|
-
if (routesManifest && routeIds.length > 0) {
|
|
104
|
-
for (const routeId of routeIds) {
|
|
105
|
-
const route = routesManifest[routeId]
|
|
106
|
-
if (route?.filePath) {
|
|
107
|
-
entries.push(route.filePath)
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const css =
|
|
113
|
-
entries.length > 0
|
|
114
|
-
? await collectDevStyles({
|
|
115
|
-
viteDevServer,
|
|
116
|
-
entries,
|
|
117
|
-
})
|
|
118
|
-
: undefined
|
|
119
|
-
|
|
120
|
-
res.setHeader('Content-Type', 'text/css')
|
|
121
|
-
res.setHeader('Cache-Control', 'no-store')
|
|
122
|
-
res.end(css ?? '')
|
|
123
|
-
})
|
|
124
|
-
|
|
125
134
|
viteDevServer.middlewares.use(async (req, res) => {
|
|
126
135
|
// fix the request URL to match the original URL
|
|
127
136
|
// otherwise, the request URL will '/index.html'
|