@pracht/vite-plugin 0.8.0 → 0.10.0
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/README.md +23 -9
- package/dist/index.d.mts +43 -1
- package/dist/index.mjs +376 -90
- package/dist/pages-router-KcCRkhnf.mjs +588 -0
- package/dist/pages-router.d.mts +3 -1
- package/dist/pages-router.mjs +1 -1
- package/package.json +5 -3
- package/dist/pages-router-BQlG21oC.mjs +0 -319
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as scanPagesDirectory, n as generatePagesManifestSource, o as createRouteLoaderHints } from "./pages-router-
|
|
1
|
+
import { c as createRouteStaticPathsHints, d as extensionGlob, f as normalizeAdditionalExtensions, i as scanPagesDirectory, l as DEFAULT_ROUTE_EXTENSIONS, n as generatePagesManifestSource, o as createRouteHeadHints, p as withAdditionalExtensions, s as createRouteLoaderHints, u as LEGACY_BARE_ROUTE_EXTENSIONS } from "./pages-router-KcCRkhnf.mjs";
|
|
2
2
|
import { createRequire, isBuiltin } from "node:module";
|
|
3
3
|
import { preactSsrPrecompile } from "@pracht/preact-ssr-precompile";
|
|
4
4
|
import preact from "@preact/preset-vite";
|
|
@@ -9,6 +9,7 @@ import { PRACHT_GRAPH_ONLY_ENV } from "@pracht/core/server";
|
|
|
9
9
|
import { CAPABILITY_SETTLED_EVENT, CAPABILITY_TRANSPORT_HEADER, CONFIRMATION_HEADER } from "@pracht/capabilities";
|
|
10
10
|
import { extractCapabilityProjection, extractCapabilityRegistrations, extractDefineAppObjectBody, scanTopLevelProperties } from "@pracht/capabilities/static";
|
|
11
11
|
import { createNodeServerEntryModule } from "@pracht/adapter-node";
|
|
12
|
+
import { Readable } from "node:stream";
|
|
12
13
|
import { applyDefaultSecurityHeaders, resolveRegistryModule } from "@pracht/core";
|
|
13
14
|
//#region src/client-module-query.ts
|
|
14
15
|
const CLIENT_MODULE_QUERY = "pracht-client";
|
|
@@ -28,7 +29,6 @@ function stripPrachtClientModuleQuery(id) {
|
|
|
28
29
|
function getRolldownLang(id) {
|
|
29
30
|
const path = stripPrachtClientModuleQuery(id).split("?")[0];
|
|
30
31
|
if (/\.(c|m)?tsx$/i.test(path)) return "tsx";
|
|
31
|
-
if (/\.tsrx$/i.test(path)) return "tsx";
|
|
32
32
|
if (/\.(c|m)?ts$/i.test(path)) return "ts";
|
|
33
33
|
if (/\.(c|m)?jsx$/i.test(path)) return "jsx";
|
|
34
34
|
if (/\.mdx?$/i.test(path)) return "jsx";
|
|
@@ -1258,7 +1258,15 @@ const PRACHT_CAPABILITIES_MODULE_ID = "virtual:pracht/capabilities";
|
|
|
1258
1258
|
const PRACHT_WEBMCP_MODULE_ID = "virtual:pracht/webmcp";
|
|
1259
1259
|
const CLIENT_BROWSER_PATH = "/@pracht/client.js";
|
|
1260
1260
|
const ISLANDS_CLIENT_BROWSER_PATH = "/@pracht/islands.js";
|
|
1261
|
-
|
|
1261
|
+
/**
|
|
1262
|
+
* `base` prefixes every emitted asset URL. Vite normalizes it to leading and
|
|
1263
|
+
* trailing slashes; a CDN base (absolute or protocol-relative) is used as-is,
|
|
1264
|
+
* so the manifest paths still resolve.
|
|
1265
|
+
*/
|
|
1266
|
+
function assetUrl(file, base) {
|
|
1267
|
+
return `${base}${file}`;
|
|
1268
|
+
}
|
|
1269
|
+
function readClientBuildAssets(root = process.cwd(), base = "/") {
|
|
1262
1270
|
const manifestPath = ["dist/client/.vite/manifest.json", "dist/.vite/manifest.json"].map((candidate) => resolve(root, candidate)).find((candidate) => existsSync(candidate));
|
|
1263
1271
|
if (!manifestPath) return {
|
|
1264
1272
|
clientEntryUrl: null,
|
|
@@ -1276,22 +1284,22 @@ function readClientBuildAssets(root = process.cwd()) {
|
|
|
1276
1284
|
if (!entry.src) continue;
|
|
1277
1285
|
const deps = collectTransitiveDeps(manifest, key);
|
|
1278
1286
|
const manifestKey = stripPrachtClientModuleQuery(entry.src);
|
|
1279
|
-
if (deps.css.length > 0) cssManifest[manifestKey] = deps.css.map((f) =>
|
|
1280
|
-
if (deps.js.length > 0) jsManifest[manifestKey] = deps.js.map((f) =>
|
|
1287
|
+
if (deps.css.length > 0) cssManifest[manifestKey] = deps.css.map((f) => assetUrl(f, base));
|
|
1288
|
+
if (deps.js.length > 0) jsManifest[manifestKey] = deps.js.map((f) => assetUrl(f, base));
|
|
1281
1289
|
}
|
|
1282
|
-
addEntryDeps(manifest, jsManifest, PRACHT_CLIENT_MODULE_ID, clientEntry);
|
|
1283
|
-
addEntryDeps(manifest, jsManifest, PRACHT_ISLANDS_CLIENT_MODULE_ID, islandsEntry);
|
|
1290
|
+
addEntryDeps(manifest, jsManifest, PRACHT_CLIENT_MODULE_ID, clientEntry, base);
|
|
1291
|
+
addEntryDeps(manifest, jsManifest, PRACHT_ISLANDS_CLIENT_MODULE_ID, islandsEntry, base);
|
|
1284
1292
|
return {
|
|
1285
|
-
clientEntryUrl: clientEntry ?
|
|
1286
|
-
islandsEntryUrl: islandsEntry ?
|
|
1293
|
+
clientEntryUrl: clientEntry ? assetUrl(clientEntry.file, base) : null,
|
|
1294
|
+
islandsEntryUrl: islandsEntry ? assetUrl(islandsEntry.file, base) : null,
|
|
1287
1295
|
cssManifest,
|
|
1288
1296
|
jsManifest
|
|
1289
1297
|
};
|
|
1290
1298
|
}
|
|
1291
|
-
function addEntryDeps(manifest, jsManifest, entryKey, entry) {
|
|
1299
|
+
function addEntryDeps(manifest, jsManifest, entryKey, entry, base) {
|
|
1292
1300
|
if (!entry) return;
|
|
1293
1301
|
const deps = collectTransitiveDeps(manifest, entryKey).js.filter((file) => file !== entry.file);
|
|
1294
|
-
if (deps.length > 0) jsManifest[entryKey] = deps.map((file) =>
|
|
1302
|
+
if (deps.length > 0) jsManifest[entryKey] = deps.map((file) => assetUrl(file, base));
|
|
1295
1303
|
}
|
|
1296
1304
|
function collectTransitiveDeps(manifest, key) {
|
|
1297
1305
|
const css = /* @__PURE__ */ new Set();
|
|
@@ -1343,13 +1351,16 @@ function createDefaultNodeAdapter() {
|
|
|
1343
1351
|
}
|
|
1344
1352
|
//#endregion
|
|
1345
1353
|
//#region src/plugin-options.ts
|
|
1354
|
+
const CLIENT_FEATURE_DEFAULTS = { prefetch: true };
|
|
1346
1355
|
const DEFAULTS = {
|
|
1356
|
+
client: CLIENT_FEATURE_DEFAULTS,
|
|
1347
1357
|
appFile: "/src/routes.ts",
|
|
1348
1358
|
middlewareDir: "/src/middleware",
|
|
1349
1359
|
routesDir: "/src/routes",
|
|
1350
1360
|
shellsDir: "/src/shells",
|
|
1351
1361
|
apiDir: "/src/api",
|
|
1352
1362
|
serverDir: "/src/server",
|
|
1363
|
+
additionalExtensions: [],
|
|
1353
1364
|
islandsDir: "/src/islands",
|
|
1354
1365
|
capabilitiesDir: "/src/capabilities",
|
|
1355
1366
|
adapter: createDefaultNodeAdapter(),
|
|
@@ -1368,6 +1379,8 @@ function resolveOptions(options) {
|
|
|
1368
1379
|
...options
|
|
1369
1380
|
};
|
|
1370
1381
|
if (resolved.llmsTxt === void 0) resolved.llmsTxt = false;
|
|
1382
|
+
resolved.client = resolveClientOptions(options.client);
|
|
1383
|
+
resolved.additionalExtensions = normalizeAdditionalExtensions(resolved.additionalExtensions);
|
|
1371
1384
|
if (!new Set([
|
|
1372
1385
|
"spa",
|
|
1373
1386
|
"ssr",
|
|
@@ -1380,6 +1393,20 @@ function resolveOptions(options) {
|
|
|
1380
1393
|
validateLlmsTxt(resolved.llmsTxt);
|
|
1381
1394
|
return resolved;
|
|
1382
1395
|
}
|
|
1396
|
+
function resolveClientOptions(client) {
|
|
1397
|
+
if (client === void 0) return CLIENT_FEATURE_DEFAULTS;
|
|
1398
|
+
if (typeof client !== "object" || client === null) throw new Error("pracht({ client }) expects an options object.");
|
|
1399
|
+
const resolved = { ...CLIENT_FEATURE_DEFAULTS };
|
|
1400
|
+
for (const key of Object.keys(CLIENT_FEATURE_DEFAULTS)) {
|
|
1401
|
+
const value = client[key];
|
|
1402
|
+
if (value === void 0) continue;
|
|
1403
|
+
if (typeof value !== "boolean") throw new Error(`pracht({ client: { ${key} } }) expects a boolean, got ${JSON.stringify(value)}.`);
|
|
1404
|
+
resolved[key] = value;
|
|
1405
|
+
}
|
|
1406
|
+
const unknown = Object.keys(client).filter((key) => !(key in CLIENT_FEATURE_DEFAULTS));
|
|
1407
|
+
if (unknown.length > 0) throw new Error(`pracht({ client }) does not accept ${unknown.map((key) => JSON.stringify(key)).join(", ")}. Known features: ${Object.keys(CLIENT_FEATURE_DEFAULTS).join(", ")}.`);
|
|
1408
|
+
return resolved;
|
|
1409
|
+
}
|
|
1383
1410
|
const LLMS_TXT_SECTIONS = new Set([
|
|
1384
1411
|
"pages",
|
|
1385
1412
|
"api",
|
|
@@ -1582,7 +1609,7 @@ function createPrachtCapabilitiesClientModuleSource(options = {}, buildOptions =
|
|
|
1582
1609
|
"// Generated by @pracht/vite-plugin from the app manifest capability registrations.",
|
|
1583
1610
|
"// Contains only http-exposed capability names, endpoints, and effects —",
|
|
1584
1611
|
"// capability modules themselves are server-only and never reach the client graph.",
|
|
1585
|
-
"import { createUseCapability } from \"@pracht/core\";",
|
|
1612
|
+
"import { createUseCapability, ensureCapabilityRevalidation, withBase } from \"@pracht/core\";",
|
|
1586
1613
|
"",
|
|
1587
1614
|
`const endpoints = Object.assign(Object.create(null), JSON.parse(${JSON.stringify(JSON.stringify(endpoints))}));`,
|
|
1588
1615
|
"",
|
|
@@ -1598,7 +1625,7 @@ function createPrachtCapabilitiesClientModuleSource(options = {}, buildOptions =
|
|
|
1598
1625
|
" } else if (opts && opts.confirm) {",
|
|
1599
1626
|
` headers.set(${JSON.stringify(CONFIRMATION_HEADER)}, opts.confirm);`,
|
|
1600
1627
|
" }",
|
|
1601
|
-
" response = await fetch(endpoint.path, {",
|
|
1628
|
+
" response = await fetch(withBase(endpoint.path), {",
|
|
1602
1629
|
" method: endpoint.method,",
|
|
1603
1630
|
" headers,",
|
|
1604
1631
|
" body: JSON.stringify(input === undefined ? {} : input),",
|
|
@@ -1643,6 +1670,7 @@ function createPrachtCapabilitiesClientModuleSource(options = {}, buildOptions =
|
|
|
1643
1670
|
"}",
|
|
1644
1671
|
"",
|
|
1645
1672
|
"export async function callCapability(name, input, opts) {",
|
|
1673
|
+
" ensureCapabilityRevalidation();",
|
|
1646
1674
|
" const endpoint = endpoints[name];",
|
|
1647
1675
|
" if (!endpoint) {",
|
|
1648
1676
|
" return {",
|
|
@@ -1791,15 +1819,6 @@ function hasWebmcpCapabilities(options = {}, root = process.cwd()) {
|
|
|
1791
1819
|
}
|
|
1792
1820
|
//#endregion
|
|
1793
1821
|
//#region src/plugin-codegen.ts
|
|
1794
|
-
const ROUTE_MODULE_EXTENSIONS = new Set([
|
|
1795
|
-
".ts",
|
|
1796
|
-
".tsx",
|
|
1797
|
-
".js",
|
|
1798
|
-
".jsx",
|
|
1799
|
-
".md",
|
|
1800
|
-
".mdx",
|
|
1801
|
-
".tsrx"
|
|
1802
|
-
]);
|
|
1803
1822
|
const NON_FULL_HYDRATION_RE = /hydration\s*:\s*["'](?:islands|none)["']/;
|
|
1804
1823
|
const FULL_HYDRATION_RE = /hydration\s*:\s*["']full["']/;
|
|
1805
1824
|
const PAGES_NON_FULL_HYDRATION_RE = /export\s+const\s+HYDRATION\s*=\s*["'](?:islands|none)["']/;
|
|
@@ -1818,7 +1837,7 @@ function findMatching(source, start, open, close) {
|
|
|
1818
1837
|
}
|
|
1819
1838
|
return -1;
|
|
1820
1839
|
}
|
|
1821
|
-
function scanFiles(dir, files) {
|
|
1840
|
+
function scanFiles(dir, files, extensions) {
|
|
1822
1841
|
let entries;
|
|
1823
1842
|
try {
|
|
1824
1843
|
entries = readdirSync(dir);
|
|
@@ -1833,15 +1852,15 @@ function scanFiles(dir, files) {
|
|
|
1833
1852
|
} catch {
|
|
1834
1853
|
continue;
|
|
1835
1854
|
}
|
|
1836
|
-
if (stat.isDirectory()) scanFiles(abs, files);
|
|
1837
|
-
else if (
|
|
1855
|
+
if (stat.isDirectory()) scanFiles(abs, files, extensions);
|
|
1856
|
+
else if (extensions.has(extname(entry))) files.push(abs);
|
|
1838
1857
|
}
|
|
1839
1858
|
}
|
|
1840
1859
|
function createNonFullHydrationExcludes(resolved, root = process.cwd()) {
|
|
1841
1860
|
const excludes = /* @__PURE__ */ new Set();
|
|
1842
1861
|
if (resolved.pagesDir) {
|
|
1843
1862
|
const files = [];
|
|
1844
|
-
scanFiles(resolve(root, resolved.pagesDir.replace(/^\//, "")), files);
|
|
1863
|
+
scanFiles(resolve(root, resolved.pagesDir.replace(/^\//, "")), files, withAdditionalExtensions(DEFAULT_ROUTE_EXTENSIONS, resolved.additionalExtensions));
|
|
1845
1864
|
for (const file of files) try {
|
|
1846
1865
|
if (PAGES_NON_FULL_HYDRATION_RE.test(readFileSync(file, "utf-8"))) excludes.add(`!/${toPosixPath$1(file).replace(toPosixPath$1(root).replace(/\/$/, "") + "/", "")}`);
|
|
1847
1866
|
} catch {}
|
|
@@ -1891,15 +1910,18 @@ function createPrachtClientModuleSource(options = {}, buildOptions = {}) {
|
|
|
1891
1910
|
const resolved = resolveOptions(options);
|
|
1892
1911
|
const isPagesMode = !!resolved.pagesDir;
|
|
1893
1912
|
const routeLoaderHints = createRouteLoaderHintsForVirtualModules(resolved, buildOptions.root);
|
|
1913
|
+
const routeHeadHints = createRouteHeadHintsForVirtualModules(resolved, buildOptions.root);
|
|
1914
|
+
const routeStaticPathsHints = createRouteStaticPathsHintsForVirtualModules(resolved, buildOptions.root);
|
|
1894
1915
|
const appImport = isPagesMode ? generatePagesAppInlineSource(resolved, buildOptions.root) : `import { app } from ${JSON.stringify(resolved.appFile)};`;
|
|
1916
|
+
const bareRouteExtensions = [...withAdditionalExtensions(LEGACY_BARE_ROUTE_EXTENSIONS, resolved.additionalExtensions)];
|
|
1895
1917
|
const dirPrefix = isPagesMode ? resolved.pagesDir : resolved.routesDir;
|
|
1896
1918
|
const routeGlob = `${dirPrefix}/**/*.{ts,tsx,js,jsx,md,mdx}`;
|
|
1897
|
-
const
|
|
1919
|
+
const additionalRouteGlob = `${dirPrefix}/**/*.${extensionGlob(bareRouteExtensions)}`;
|
|
1898
1920
|
const routeExcludes = createNonFullHydrationExcludes(resolved, buildOptions.root);
|
|
1899
1921
|
const routeGlobPattern = routeExcludes.length > 0 ? [routeGlob, ...routeExcludes] : routeGlob;
|
|
1900
|
-
const
|
|
1922
|
+
const additionalRouteGlobPattern = additionalRouteGlob && routeExcludes.length > 0 ? [additionalRouteGlob, ...routeExcludes] : additionalRouteGlob;
|
|
1901
1923
|
const shellGlob = isPagesMode ? `${resolved.pagesDir}/**/_app.{ts,tsx,js,jsx}` : `${resolved.shellsDir}/**/*.{ts,tsx,js,jsx,md,mdx}`;
|
|
1902
|
-
const
|
|
1924
|
+
const additionalShellGlob = isPagesMode ? `${resolved.pagesDir}/**/_app.${extensionGlob(bareRouteExtensions)}` : `${resolved.shellsDir}/**/*.${extensionGlob(bareRouteExtensions)}`;
|
|
1903
1925
|
const appFilePosix = resolved.appFile.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
1904
1926
|
const appDir = (appFilePosix.startsWith("/") ? appFilePosix : `/${appFilePosix}`).replace(/\/[^/]*$/, "") || "/";
|
|
1905
1927
|
return [
|
|
@@ -1907,17 +1929,19 @@ function createPrachtClientModuleSource(options = {}, buildOptions = {}) {
|
|
|
1907
1929
|
appImport,
|
|
1908
1930
|
"",
|
|
1909
1931
|
`const routeLoaderHints = ${JSON.stringify(routeLoaderHints)};`,
|
|
1932
|
+
`const routeHeadHints = ${JSON.stringify(routeHeadHints)};`,
|
|
1933
|
+
`const routeStaticPathsHints = ${JSON.stringify(routeStaticPathsHints)};`,
|
|
1910
1934
|
`const routeModules = {`,
|
|
1911
1935
|
` ...import.meta.glob(${JSON.stringify(routeGlobPattern)}, { query: ${JSON.stringify(PRACHT_CLIENT_MODULE_QUERY)} }),`,
|
|
1912
|
-
` ...import.meta.glob(${JSON.stringify(
|
|
1936
|
+
` ...import.meta.glob(${JSON.stringify(additionalRouteGlobPattern)}),`,
|
|
1913
1937
|
`};`,
|
|
1914
1938
|
`const shellModules = {`,
|
|
1915
1939
|
` ...import.meta.glob(${JSON.stringify(shellGlob)}, { query: ${JSON.stringify(PRACHT_CLIENT_MODULE_QUERY)} }),`,
|
|
1916
|
-
` ...import.meta.glob(${JSON.stringify(
|
|
1940
|
+
` ...import.meta.glob(${JSON.stringify(additionalShellGlob)}),`,
|
|
1917
1941
|
`};`,
|
|
1918
1942
|
"",
|
|
1919
1943
|
"const resolvedApp = resolveApp(app);",
|
|
1920
|
-
"
|
|
1944
|
+
"applyRouteHints(resolvedApp, routeLoaderHints, routeHeadHints, routeStaticPathsHints);",
|
|
1921
1945
|
"",
|
|
1922
1946
|
...createApplyRouteLoaderHintsSource(),
|
|
1923
1947
|
`const APP_DIR = ${JSON.stringify(appDir)};`,
|
|
@@ -2013,7 +2037,9 @@ function createPrachtServerModuleSource(options = {}, buildOptions = {}) {
|
|
|
2013
2037
|
const isPagesMode = !!resolved.pagesDir;
|
|
2014
2038
|
const registrySource = createPrachtRegistryModuleSource(resolved);
|
|
2015
2039
|
const routeLoaderHints = createRouteLoaderHintsForVirtualModules(resolved, buildOptions.root);
|
|
2016
|
-
const
|
|
2040
|
+
const routeHeadHints = createRouteHeadHintsForVirtualModules(resolved, buildOptions.root);
|
|
2041
|
+
const routeStaticPathsHints = createRouteStaticPathsHintsForVirtualModules(resolved, buildOptions.root);
|
|
2042
|
+
const clientBuild = buildOptions.isBuild ? readClientBuildAssets(buildOptions.root, buildOptions.base ?? "/") : {
|
|
2017
2043
|
clientEntryUrl: null,
|
|
2018
2044
|
islandsEntryUrl: null,
|
|
2019
2045
|
cssManifest: {},
|
|
@@ -2025,7 +2051,10 @@ function createPrachtServerModuleSource(options = {}, buildOptions = {}) {
|
|
|
2025
2051
|
let prachtImports = adapter?.serverImports ? adapter.serverImports + "\nimport { prerenderApp } from \"@pracht/core/server\";" : "import { resolveApp, resolveApiRoutes, prerenderApp } from \"@pracht/core/server\";";
|
|
2026
2052
|
if (llmsTxtConfig) prachtImports += "\nimport { buildLlmsTxt } from \"@pracht/core/server\";";
|
|
2027
2053
|
const appImport = isPagesMode ? generatePagesAppInlineSource(resolved, buildOptions.root) : `import { app } from ${JSON.stringify(resolved.appFile)};`;
|
|
2028
|
-
const
|
|
2054
|
+
const devBase = buildOptions.base ?? "/";
|
|
2055
|
+
const withDevBase = (path) => devBase === "/" ? path : `${devBase}${path.slice(1)}`;
|
|
2056
|
+
const clientEntryUrl = buildOptions.isBuild ? clientBuild.clientEntryUrl : withDevBase(CLIENT_BROWSER_PATH);
|
|
2057
|
+
const islandsEntryUrl = buildOptions.isBuild ? clientBuild.islandsEntryUrl : withDevBase(ISLANDS_CLIENT_BROWSER_PATH);
|
|
2029
2058
|
const islandsGlob = `${resolved.islandsDir}/**/*.{ts,tsx,js,jsx}`;
|
|
2030
2059
|
const source = [
|
|
2031
2060
|
prachtImports,
|
|
@@ -2033,6 +2062,8 @@ function createPrachtServerModuleSource(options = {}, buildOptions = {}) {
|
|
|
2033
2062
|
appImport,
|
|
2034
2063
|
"",
|
|
2035
2064
|
`const routeLoaderHints = ${JSON.stringify(routeLoaderHints)};`,
|
|
2065
|
+
`const routeHeadHints = ${JSON.stringify(routeHeadHints)};`,
|
|
2066
|
+
`const routeStaticPathsHints = ${JSON.stringify(routeStaticPathsHints)};`,
|
|
2036
2067
|
...createApplyRouteLoaderHintsSource(),
|
|
2037
2068
|
registrySource,
|
|
2038
2069
|
"",
|
|
@@ -2044,10 +2075,13 @@ function createPrachtServerModuleSource(options = {}, buildOptions = {}) {
|
|
|
2044
2075
|
"export const islandFiles = Object.keys(islandModules);",
|
|
2045
2076
|
"",
|
|
2046
2077
|
"export const resolvedApp = resolveApp(app);",
|
|
2047
|
-
"
|
|
2078
|
+
"applyRouteHints(resolvedApp, routeLoaderHints, routeHeadHints, routeStaticPathsHints);",
|
|
2048
2079
|
`export const apiRoutes = resolveApiRoutes(Object.keys(apiModules), ${JSON.stringify(resolved.apiDir)});`,
|
|
2049
2080
|
`export const buildTarget = ${JSON.stringify(adapter?.id ?? "node")};`,
|
|
2050
|
-
`export const
|
|
2081
|
+
`export const staticTarget = ${JSON.stringify(adapter?.staticTarget === true)};`,
|
|
2082
|
+
`export const buildBase = ${JSON.stringify(buildOptions.base ?? "/")};`,
|
|
2083
|
+
`export const configuredBase = ${JSON.stringify(buildOptions.configuredBase)};`,
|
|
2084
|
+
`export const clientEntryUrl = ${JSON.stringify(clientEntryUrl ?? "/@pracht/client.js")};`,
|
|
2051
2085
|
`export const islandsEntryUrl = ${JSON.stringify(islandsEntryUrl ?? null)};`,
|
|
2052
2086
|
`export const islandsBootstrapRequired = ${JSON.stringify(islandsBootstrapRequired)};`,
|
|
2053
2087
|
`export const cssManifest = ${JSON.stringify(clientBuild.cssManifest)};`,
|
|
@@ -2075,15 +2109,25 @@ function createPrachtServerModuleSource(options = {}, buildOptions = {}) {
|
|
|
2075
2109
|
*/
|
|
2076
2110
|
function createPrachtDevModuleSource(options = {}, buildOptions = {}) {
|
|
2077
2111
|
const resolved = resolveOptions(options);
|
|
2112
|
+
const routeLoaderHints = createRouteLoaderHintsForVirtualModules(resolved, buildOptions.root);
|
|
2113
|
+
const routeHeadHints = createRouteHeadHintsForVirtualModules(resolved, buildOptions.root);
|
|
2114
|
+
const routeStaticPathsHints = createRouteStaticPathsHintsForVirtualModules(resolved, buildOptions.root);
|
|
2078
2115
|
return [
|
|
2079
2116
|
"import { resolveApp, resolveApiRoutes } from \"@pracht/core/server\";",
|
|
2080
2117
|
resolved.pagesDir ? generatePagesAppInlineSource(resolved, buildOptions.root) : `import { app } from ${JSON.stringify(resolved.appFile)};`,
|
|
2081
2118
|
"",
|
|
2119
|
+
`const routeLoaderHints = ${JSON.stringify(routeLoaderHints)};`,
|
|
2120
|
+
`const routeHeadHints = ${JSON.stringify(routeHeadHints)};`,
|
|
2121
|
+
`const routeStaticPathsHints = ${JSON.stringify(routeStaticPathsHints)};`,
|
|
2122
|
+
...createApplyRouteLoaderHintsSource(),
|
|
2082
2123
|
createPrachtRegistryModuleSource(resolved),
|
|
2083
2124
|
"",
|
|
2084
2125
|
"export const resolvedApp = resolveApp(app);",
|
|
2126
|
+
"applyRouteHints(resolvedApp, routeLoaderHints, routeHeadHints, routeStaticPathsHints);",
|
|
2085
2127
|
`export const apiRoutes = resolveApiRoutes(Object.keys(apiModules), ${JSON.stringify(resolved.apiDir)});`,
|
|
2086
2128
|
`export const buildTarget = ${JSON.stringify(resolved.adapter?.id ?? "node")};`,
|
|
2129
|
+
`export const staticTarget = ${JSON.stringify(resolved.adapter?.staticTarget === true)};`,
|
|
2130
|
+
`export const buildBase = ${JSON.stringify(buildOptions.base ?? "/")};`,
|
|
2087
2131
|
""
|
|
2088
2132
|
].join("\n");
|
|
2089
2133
|
}
|
|
@@ -2108,7 +2152,7 @@ function resolveLlmsTxtConfig(resolved, root = process.cwd()) {
|
|
|
2108
2152
|
}
|
|
2109
2153
|
function createApplyRouteLoaderHintsSource() {
|
|
2110
2154
|
return [
|
|
2111
|
-
"function
|
|
2155
|
+
"function applyRouteHints(resolvedApp, routeLoaderHints, routeHeadHints, routeStaticPathsHints) {",
|
|
2112
2156
|
" for (const route of resolvedApp.routes) {",
|
|
2113
2157
|
" const hint = routeLoaderHints[route.file];",
|
|
2114
2158
|
" if (hint === true) {",
|
|
@@ -2116,14 +2160,52 @@ function createApplyRouteLoaderHintsSource() {
|
|
|
2116
2160
|
" } else if (typeof route.hasLoader === 'undefined' && typeof hint === 'boolean') {",
|
|
2117
2161
|
" route.hasLoader = hint;",
|
|
2118
2162
|
" }",
|
|
2163
|
+
" const routeHeadHint = routeHeadHints[route.file];",
|
|
2164
|
+
" const shellHeadHint = route.shellFile ? routeHeadHints[route.shellFile] : undefined;",
|
|
2165
|
+
" const hasCompleteHeadHints = typeof routeHeadHint === 'boolean' &&",
|
|
2166
|
+
" (!route.shellFile || typeof shellHeadHint === 'boolean');",
|
|
2167
|
+
" if (routeHeadHint === true || shellHeadHint === true) {",
|
|
2168
|
+
" route.hasHead = true;",
|
|
2169
|
+
" } else if (typeof route.hasHead === 'undefined' && hasCompleteHeadHints) {",
|
|
2170
|
+
" route.hasHead = routeHeadHint === true || shellHeadHint === true;",
|
|
2171
|
+
" }",
|
|
2172
|
+
" // Only ever narrows to false, and only when the scan actually saw the",
|
|
2173
|
+
" // module: an unknown route file keeps today's conservative behavior.",
|
|
2174
|
+
" const staticPathsHint = routeStaticPathsHints[route.file];",
|
|
2175
|
+
" if (typeof route.hasStaticPaths === 'undefined' && typeof staticPathsHint === 'boolean') {",
|
|
2176
|
+
" route.hasStaticPaths = staticPathsHint;",
|
|
2177
|
+
" }",
|
|
2119
2178
|
" }",
|
|
2120
2179
|
"}",
|
|
2121
2180
|
""
|
|
2122
2181
|
];
|
|
2123
2182
|
}
|
|
2183
|
+
function createRouteHeadHintsForVirtualModules(options, root = process.cwd()) {
|
|
2184
|
+
const appFileDir = dirname(resolve(root, options.appFile.slice(1)));
|
|
2185
|
+
const directories = options.pagesDir ? [[options.pagesDir, resolve(root, options.pagesDir.slice(1))]] : [[options.routesDir, resolve(root, options.routesDir.slice(1))], [options.shellsDir, resolve(root, options.shellsDir.slice(1))]];
|
|
2186
|
+
return Object.assign({}, ...directories.map(([prefix, directory]) => createRouteHeadHints(directory, {
|
|
2187
|
+
additionalExtensions: options.additionalExtensions,
|
|
2188
|
+
appFileDir,
|
|
2189
|
+
rootRelativePrefix: prefix
|
|
2190
|
+
})));
|
|
2191
|
+
}
|
|
2192
|
+
/**
|
|
2193
|
+
* `getStaticPaths()` presence per route file. Only routes matter — a shell
|
|
2194
|
+
* cannot enumerate paths — so unlike the head hints this skips the shells
|
|
2195
|
+
* directory.
|
|
2196
|
+
*/
|
|
2197
|
+
function createRouteStaticPathsHintsForVirtualModules(options, root = process.cwd()) {
|
|
2198
|
+
const appFileDir = dirname(resolve(root, options.appFile.slice(1)));
|
|
2199
|
+
const routesPrefix = options.pagesDir || options.routesDir;
|
|
2200
|
+
return createRouteStaticPathsHints(resolve(root, routesPrefix.slice(1)), {
|
|
2201
|
+
additionalExtensions: options.additionalExtensions,
|
|
2202
|
+
appFileDir,
|
|
2203
|
+
rootRelativePrefix: routesPrefix
|
|
2204
|
+
});
|
|
2205
|
+
}
|
|
2124
2206
|
function createRouteLoaderHintsForVirtualModules(options, root = process.cwd()) {
|
|
2125
2207
|
if (options.pagesDir) {
|
|
2126
|
-
const pages = scanPagesDirectory(resolve(root, options.pagesDir.slice(1)));
|
|
2208
|
+
const pages = scanPagesDirectory(resolve(root, options.pagesDir.slice(1)), options.additionalExtensions);
|
|
2127
2209
|
const hints = {};
|
|
2128
2210
|
for (const page of pages) {
|
|
2129
2211
|
const key = `${options.pagesDir}/${page.relativePath.replace(/\\/g, "/")}`;
|
|
@@ -2133,6 +2215,7 @@ function createRouteLoaderHintsForVirtualModules(options, root = process.cwd())
|
|
|
2133
2215
|
}
|
|
2134
2216
|
const appFileDir = dirname(resolve(root, options.appFile.slice(1)));
|
|
2135
2217
|
return createRouteLoaderHints(resolve(root, options.routesDir.slice(1)), {
|
|
2218
|
+
additionalExtensions: options.additionalExtensions,
|
|
2136
2219
|
appFileDir,
|
|
2137
2220
|
rootRelativePrefix: options.routesDir
|
|
2138
2221
|
});
|
|
@@ -2141,18 +2224,19 @@ function createPrachtRegistryModuleSource(options = {}) {
|
|
|
2141
2224
|
const resolved = resolveOptions(options);
|
|
2142
2225
|
const apiGlobs = [`${resolved.apiDir}/**/*.{ts,js,tsx,jsx}`, `!${resolved.apiDir}/**/*.d.ts`];
|
|
2143
2226
|
const isPagesMode = !!resolved.pagesDir;
|
|
2227
|
+
const bareRouteExtensions = [...withAdditionalExtensions(LEGACY_BARE_ROUTE_EXTENSIONS, resolved.additionalExtensions)];
|
|
2144
2228
|
const routeGlob = isPagesMode ? `${resolved.pagesDir}/**/*.{ts,tsx,js,jsx,md,mdx}` : `${resolved.routesDir}/**/*.{ts,tsx,js,jsx,md,mdx}`;
|
|
2145
|
-
const
|
|
2229
|
+
const additionalRouteGlob = `${isPagesMode ? resolved.pagesDir : resolved.routesDir}/**/*.${extensionGlob(bareRouteExtensions)}`;
|
|
2146
2230
|
const shellGlob = isPagesMode ? `${resolved.pagesDir}/**/_app.{ts,tsx,js,jsx}` : `${resolved.shellsDir}/**/*.{ts,tsx,js,jsx,md,mdx}`;
|
|
2147
|
-
const
|
|
2231
|
+
const additionalShellGlob = isPagesMode ? `${resolved.pagesDir}/**/_app.${extensionGlob(bareRouteExtensions)}` : `${resolved.shellsDir}/**/*.${extensionGlob(bareRouteExtensions)}`;
|
|
2148
2232
|
return [
|
|
2149
2233
|
`export const routeModules = {`,
|
|
2150
2234
|
` ...import.meta.glob(${JSON.stringify(routeGlob)}),`,
|
|
2151
|
-
` ...import.meta.glob(${JSON.stringify(
|
|
2235
|
+
` ...import.meta.glob(${JSON.stringify(additionalRouteGlob)}),`,
|
|
2152
2236
|
`};`,
|
|
2153
2237
|
`export const shellModules = {`,
|
|
2154
2238
|
` ...import.meta.glob(${JSON.stringify(shellGlob)}),`,
|
|
2155
|
-
` ...import.meta.glob(${JSON.stringify(
|
|
2239
|
+
` ...import.meta.glob(${JSON.stringify(additionalShellGlob)}),`,
|
|
2156
2240
|
`};`,
|
|
2157
2241
|
`export const middlewareModules = import.meta.glob(${JSON.stringify(`${resolved.middlewareDir}/**/*.{ts,tsx,js,jsx}`)});`,
|
|
2158
2242
|
`export const apiModules = import.meta.glob(${JSON.stringify(apiGlobs)});`,
|
|
@@ -2176,13 +2260,15 @@ function clearPagesAppSourceCache() {
|
|
|
2176
2260
|
function generatePagesAppInlineSource(options, root = process.cwd()) {
|
|
2177
2261
|
const absPagesDir = resolve(root, options.pagesDir.slice(1));
|
|
2178
2262
|
const cacheKey = JSON.stringify({
|
|
2263
|
+
additionalExtensions: options.additionalExtensions,
|
|
2179
2264
|
absPagesDir,
|
|
2180
2265
|
pagesDefaultRender: options.pagesDefaultRender,
|
|
2181
2266
|
pagesDirPrefix: options.pagesDir
|
|
2182
2267
|
});
|
|
2183
2268
|
const cached = pagesAppSourceCache.get(cacheKey);
|
|
2184
2269
|
if (cached) return cached;
|
|
2185
|
-
const source = generatePagesManifestSource(scanPagesDirectory(absPagesDir), {
|
|
2270
|
+
const source = generatePagesManifestSource(scanPagesDirectory(absPagesDir, options.additionalExtensions), {
|
|
2271
|
+
additionalExtensions: options.additionalExtensions,
|
|
2186
2272
|
pagesDir: absPagesDir,
|
|
2187
2273
|
pagesDefaultRender: options.pagesDefaultRender,
|
|
2188
2274
|
pagesDirPrefix: options.pagesDir
|
|
@@ -2197,8 +2283,46 @@ const DEFAULT_MAX_BODY_SIZE = 1024 * 1024;
|
|
|
2197
2283
|
const CSS_MODULE_URL_RE = /\.(?:css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/;
|
|
2198
2284
|
const DEVTOOLS_JSON_PATH = "/_pracht.json";
|
|
2199
2285
|
const LLMS_TXT_PATH = "/llms.txt";
|
|
2286
|
+
function isEventStreamContentType(contentType) {
|
|
2287
|
+
return contentType.split(";", 1)[0]?.trim().toLowerCase() === "text/event-stream";
|
|
2288
|
+
}
|
|
2289
|
+
/**
|
|
2290
|
+
* Adapter-owned dev servers can route every browser request through their
|
|
2291
|
+
* platform runtime before Vite's transform middleware gets a chance to serve
|
|
2292
|
+
* Pracht's stable virtual client entries. Serve those two entries at their
|
|
2293
|
+
* public, base-prefixed URLs while leaving every other request to the adapter.
|
|
2294
|
+
*/
|
|
2295
|
+
function createOwnedDevEntryMiddleware(server) {
|
|
2296
|
+
const base = server.config.base || "/";
|
|
2297
|
+
return async (req, res, next) => {
|
|
2298
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
2299
|
+
if (method !== "GET" && method !== "HEAD") return next();
|
|
2300
|
+
const requestUrl = new URL(req.url ?? "/", "http://localhost");
|
|
2301
|
+
const pathname = base === "/" ? requestUrl.pathname : requestUrl.pathname.startsWith(base) ? `/${requestUrl.pathname.slice(base.length)}` : null;
|
|
2302
|
+
if (pathname !== "/@pracht/client.js" && pathname !== "/@pracht/islands.js") return next();
|
|
2303
|
+
try {
|
|
2304
|
+
const result = await server.transformRequest(`${pathname}${requestUrl.search}`);
|
|
2305
|
+
if (!result) return next();
|
|
2306
|
+
if (result.etag && req.headers["if-none-match"] === result.etag) {
|
|
2307
|
+
res.statusCode = 304;
|
|
2308
|
+
res.end();
|
|
2309
|
+
return;
|
|
2310
|
+
}
|
|
2311
|
+
res.statusCode = 200;
|
|
2312
|
+
res.setHeader("content-type", "text/javascript");
|
|
2313
|
+
res.setHeader("cache-control", "no-cache");
|
|
2314
|
+
if (result.etag) res.setHeader("etag", result.etag);
|
|
2315
|
+
for (const [name, value] of Object.entries(server.config.server.headers ?? {})) if (value !== void 0) res.setHeader(name, value);
|
|
2316
|
+
res.end(method === "HEAD" ? void 0 : result.code);
|
|
2317
|
+
} catch (error) {
|
|
2318
|
+
next(error);
|
|
2319
|
+
}
|
|
2320
|
+
};
|
|
2321
|
+
}
|
|
2200
2322
|
function createDevSSRMiddleware(server, options = {}) {
|
|
2201
2323
|
const maxBodySize = options.maxBodySize ?? DEFAULT_MAX_BODY_SIZE;
|
|
2324
|
+
const devBase = server.config.base || "/";
|
|
2325
|
+
const withDevBase = (path) => devBase === "/" || !path.startsWith("/") ? path : `${devBase}${path.slice(1)}`;
|
|
2202
2326
|
let warnedDevtoolsCollision = false;
|
|
2203
2327
|
let warnedLlmsTxtCollision = false;
|
|
2204
2328
|
if (options.llmsTxt && typeof server.config.publicDir === "string") {
|
|
@@ -2223,6 +2347,7 @@ function createDevSSRMiddleware(server, options = {}) {
|
|
|
2223
2347
|
await serveDevtools(server, res, {
|
|
2224
2348
|
apiRoutes: serverMod.apiRoutes ?? [],
|
|
2225
2349
|
app: serverMod.resolvedApp,
|
|
2350
|
+
base: devBase,
|
|
2226
2351
|
url,
|
|
2227
2352
|
wantsJson: requestUrl.pathname === DEVTOOLS_JSON_PATH
|
|
2228
2353
|
});
|
|
@@ -2243,10 +2368,10 @@ function createDevSSRMiddleware(server, options = {}) {
|
|
|
2243
2368
|
return;
|
|
2244
2369
|
}
|
|
2245
2370
|
if (shouldBypassDevSSR(requestUrl, req, routeMatchers)) return next();
|
|
2246
|
-
if (isDevNotFoundRequest(requestUrl, req, routeMatchers)) return serveDevNotFound(server, res, next, url, requestUrl.pathname, routeMatchers);
|
|
2371
|
+
if (isDevNotFoundRequest(requestUrl, req, routeMatchers)) return serveDevNotFound(server, res, next, url, requestUrl.pathname, routeMatchers, devBase);
|
|
2247
2372
|
let webRequest;
|
|
2248
2373
|
try {
|
|
2249
|
-
webRequest = await nodeToWebRequest(req, maxBodySize);
|
|
2374
|
+
webRequest = await nodeToWebRequest(req, maxBodySize, devBase);
|
|
2250
2375
|
} catch (err) {
|
|
2251
2376
|
if (err instanceof Error && err.message === "Request body too large") {
|
|
2252
2377
|
res.statusCode = 413;
|
|
@@ -2261,8 +2386,8 @@ function createDevSSRMiddleware(server, options = {}) {
|
|
|
2261
2386
|
registry: serverMod.registry,
|
|
2262
2387
|
request: webRequest,
|
|
2263
2388
|
debugErrors: true,
|
|
2264
|
-
clientEntryUrl: CLIENT_BROWSER_PATH,
|
|
2265
|
-
islandsEntryUrl: ISLANDS_CLIENT_BROWSER_PATH,
|
|
2389
|
+
clientEntryUrl: withDevBase(CLIENT_BROWSER_PATH),
|
|
2390
|
+
islandsEntryUrl: withDevBase(ISLANDS_CLIENT_BROWSER_PATH),
|
|
2266
2391
|
islandsBootstrapRequired: serverMod.islandsBootstrapRequired === true,
|
|
2267
2392
|
apiRoutes: serverMod.apiRoutes,
|
|
2268
2393
|
timings
|
|
@@ -2270,8 +2395,27 @@ function createDevSSRMiddleware(server, options = {}) {
|
|
|
2270
2395
|
const responseContentType = response.headers.get("content-type") ?? "";
|
|
2271
2396
|
if (response.status === 404 && !responseContentType.includes("application/json") && !routeMatchers.app?.notFound) return next();
|
|
2272
2397
|
const contentType = response.headers.get("content-type") ?? "";
|
|
2398
|
+
if (response.body && isEventStreamContentType(contentType)) {
|
|
2399
|
+
res.statusCode = response.status;
|
|
2400
|
+
response.headers.forEach((value, key) => {
|
|
2401
|
+
res.setHeader(key, value);
|
|
2402
|
+
});
|
|
2403
|
+
const source = Readable.fromWeb(response.body);
|
|
2404
|
+
if (res.destroyed || res.writableEnded) {
|
|
2405
|
+
source.destroy();
|
|
2406
|
+
return;
|
|
2407
|
+
}
|
|
2408
|
+
res.on("close", () => {
|
|
2409
|
+
if (!res.writableFinished) source.destroy();
|
|
2410
|
+
});
|
|
2411
|
+
source.on("error", () => {
|
|
2412
|
+
res.destroy();
|
|
2413
|
+
});
|
|
2414
|
+
source.pipe(res);
|
|
2415
|
+
return;
|
|
2416
|
+
}
|
|
2273
2417
|
let body = await response.text();
|
|
2274
|
-
if (contentType.includes("text/html")) body = await server
|
|
2418
|
+
if (contentType.includes("text/html")) body = await transformDevHtml(server, url, body, devBase);
|
|
2275
2419
|
res.statusCode = response.status;
|
|
2276
2420
|
response.headers.forEach((value, key) => {
|
|
2277
2421
|
res.setHeader(key, value);
|
|
@@ -2280,7 +2424,56 @@ function createDevSSRMiddleware(server, options = {}) {
|
|
|
2280
2424
|
if (serverTiming) res.setHeader("Server-Timing", serverTiming);
|
|
2281
2425
|
res.end(body);
|
|
2282
2426
|
} catch (error) {
|
|
2283
|
-
await handleDevError(server, req, res, next, url, error);
|
|
2427
|
+
await handleDevError(server, req, res, next, url, error, devBase);
|
|
2428
|
+
}
|
|
2429
|
+
};
|
|
2430
|
+
}
|
|
2431
|
+
/**
|
|
2432
|
+
* Vite's HTML transform adds `config.base` to root-absolute asset attributes.
|
|
2433
|
+
* Pracht's runtime has already added it to URLs produced by `withBase()` — the
|
|
2434
|
+
* client entry, route-state preloads, image endpoints, and user-authored asset
|
|
2435
|
+
* URLs — while Vite-owned or module-graph URLs still need the transform. Hide
|
|
2436
|
+
* the already-based strings while the hooks run, then restore them afterward,
|
|
2437
|
+
* so each producer applies the deploy base exactly once.
|
|
2438
|
+
*/
|
|
2439
|
+
async function transformDevHtml(server, url, html, base) {
|
|
2440
|
+
if (base === "/") return server.transformIndexHtml(url, html);
|
|
2441
|
+
const assetUrls = protectRootAbsoluteAssetAttributes(html);
|
|
2442
|
+
let placeholder = "https://pracht.invalid/__PRACHT_DEV_BASE_PLACEHOLDER__/";
|
|
2443
|
+
while (assetUrls.html.includes(placeholder)) placeholder += "_";
|
|
2444
|
+
const protectedHtml = assetUrls.html.replaceAll(base, placeholder);
|
|
2445
|
+
const transformedHtml = await server.transformIndexHtml(url, protectedHtml);
|
|
2446
|
+
return assetUrls.restore(transformedHtml.replaceAll(placeholder, base));
|
|
2447
|
+
}
|
|
2448
|
+
const HTML_ASSET_URL_ATTRIBUTE_RE = /(\s(?:src|href|xlink:href|data|srcset|imagesrcset|poster|content)\s*=\s*)(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/gi;
|
|
2449
|
+
/**
|
|
2450
|
+
* Runtime-rendered root-absolute asset URLs have already reached their final
|
|
2451
|
+
* public meaning: raw `/logo.svg` deliberately stays at the origin root,
|
|
2452
|
+
* while `withBase("/logo.svg")` is already under the deploy base. Vite's dev
|
|
2453
|
+
* HTML pass prefixes both, unlike production SSR, so hide complete attribute
|
|
2454
|
+
* values behind inert external URLs until that pass finishes.
|
|
2455
|
+
*/
|
|
2456
|
+
function protectRootAbsoluteAssetAttributes(html) {
|
|
2457
|
+
let markerPrefix = "https://pracht.invalid/__PRACHT_DEV_ASSET_PLACEHOLDER__/";
|
|
2458
|
+
while (html.includes(markerPrefix)) markerPrefix += "_";
|
|
2459
|
+
const replacements = [];
|
|
2460
|
+
return {
|
|
2461
|
+
html: html.replace(HTML_ASSET_URL_ATTRIBUTE_RE, (match, prefix, doubleQuoted, singleQuoted, unquoted) => {
|
|
2462
|
+
const value = doubleQuoted ?? singleQuoted ?? unquoted ?? "";
|
|
2463
|
+
if (!(/(?:srcset)\s*=\s*$/i.test(prefix) ? /(?:^|,)\s*\/(?!\/)/.test(value) : /^\s*\/(?!\/)/.test(value))) return match;
|
|
2464
|
+
const marker = `${markerPrefix}${replacements.length}`;
|
|
2465
|
+
replacements.push({
|
|
2466
|
+
marker,
|
|
2467
|
+
value
|
|
2468
|
+
});
|
|
2469
|
+
if (doubleQuoted !== void 0) return `${prefix}"${marker}"`;
|
|
2470
|
+
if (singleQuoted !== void 0) return `${prefix}'${marker}'`;
|
|
2471
|
+
return `${prefix}${marker}`;
|
|
2472
|
+
}),
|
|
2473
|
+
restore(transformedHtml) {
|
|
2474
|
+
let restoredHtml = transformedHtml;
|
|
2475
|
+
for (const { marker, value } of replacements) restoredHtml = restoredHtml.replaceAll(marker, value);
|
|
2476
|
+
return restoredHtml;
|
|
2284
2477
|
}
|
|
2285
2478
|
};
|
|
2286
2479
|
}
|
|
@@ -2292,7 +2485,7 @@ function createDevSSRMiddleware(server, options = {}) {
|
|
|
2292
2485
|
* avoid a first-paint FOUC.
|
|
2293
2486
|
*/
|
|
2294
2487
|
async function createDevCssManifest(server, options) {
|
|
2295
|
-
const route = options.matchAppRoute(options.app, options.pathname)?.route ?? options.app.notFound;
|
|
2488
|
+
const route = options.pathname === null ? void 0 : options.matchAppRoute(options.app, options.pathname)?.route ?? options.app.notFound;
|
|
2296
2489
|
if (!route) return {};
|
|
2297
2490
|
const manifest = {};
|
|
2298
2491
|
const modules = [...route.shellFile ? [{
|
|
@@ -2341,18 +2534,19 @@ function collectDevCssUrls(entry) {
|
|
|
2341
2534
|
}
|
|
2342
2535
|
return [...urls];
|
|
2343
2536
|
}
|
|
2344
|
-
function injectDevCssLinks(html, manifest) {
|
|
2537
|
+
function injectDevCssLinks(html, manifest, base = "/") {
|
|
2345
2538
|
if (!html.includes("</head>")) return html;
|
|
2346
|
-
const tags = [...new Set(Object.values(manifest).flat())].map((url) => escapeHtmlAttribute(url)).filter((escapedUrl) => !html.includes(`href="${escapedUrl}"`)).map((escapedUrl) => `<link rel="stylesheet" href="${escapedUrl}">`);
|
|
2539
|
+
const tags = [...new Set(Object.values(manifest).flat().map((url) => base === "/" || !url.startsWith("/") ? url : `${base}${url.slice(1)}`))].map((url) => escapeHtmlAttribute(url)).filter((escapedUrl) => !html.includes(`href="${escapedUrl}"`)).map((escapedUrl) => `<link rel="stylesheet" href="${escapedUrl}">`);
|
|
2347
2540
|
if (tags.length === 0) return html;
|
|
2348
2541
|
return html.replace("</head>", ` ${tags.join("\n ")}\n </head>`);
|
|
2349
2542
|
}
|
|
2350
|
-
async function injectDevCssForPath(server, path, html) {
|
|
2351
|
-
return injectDevCssLinks(html, await createDevCssManifest(server, await resolveDevCssContextForPath(server, path)));
|
|
2543
|
+
async function injectDevCssForPath(server, path, html, options = {}) {
|
|
2544
|
+
return injectDevCssLinks(html, await createDevCssManifest(server, await resolveDevCssContextForPath(server, path, options)), server.config.base || "/");
|
|
2352
2545
|
}
|
|
2353
|
-
async function resolveDevCssContextForPath(server, path) {
|
|
2546
|
+
async function resolveDevCssContextForPath(server, path, options = {}) {
|
|
2354
2547
|
const [framework, serverMod] = await Promise.all([server.ssrLoadModule("@pracht/core/server"), server.ssrLoadModule(PRACHT_DEV_MODULE_ID)]);
|
|
2355
|
-
const
|
|
2548
|
+
const publicPathname = new URL(path, "http://localhost").pathname;
|
|
2549
|
+
const pathname = options.basePathRetained ? framework.stripBase(publicPathname) : publicPathname;
|
|
2356
2550
|
return {
|
|
2357
2551
|
app: serverMod.resolvedApp,
|
|
2358
2552
|
matchAppRoute: framework.matchAppRoute,
|
|
@@ -2374,7 +2568,7 @@ function createDevCssInjectionMiddleware(server) {
|
|
|
2374
2568
|
next();
|
|
2375
2569
|
return;
|
|
2376
2570
|
}
|
|
2377
|
-
const contextPromise = resolveDevCssContextForPath(server, req.url ?? "/").catch((error) => {
|
|
2571
|
+
const contextPromise = resolveDevCssContextForPath(server, req.url ?? "/", { basePathRetained: true }).catch((error) => {
|
|
2378
2572
|
if (!warned) {
|
|
2379
2573
|
warned = true;
|
|
2380
2574
|
server.config.logger.warn(`[pracht] Could not discover development stylesheets: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -2405,7 +2599,7 @@ function createDevCssInjectionMiddleware(server) {
|
|
|
2405
2599
|
try {
|
|
2406
2600
|
const context = await contextPromise;
|
|
2407
2601
|
const manifest = context ? await createDevCssManifest(server, context) : null;
|
|
2408
|
-
originalEnd(manifest ? injectDevCssLinks(body.toString("utf-8"), manifest) : body.toString("utf-8"), done);
|
|
2602
|
+
originalEnd(manifest ? injectDevCssLinks(body.toString("utf-8"), manifest, server.config.base || "/") : body.toString("utf-8"), done);
|
|
2409
2603
|
} catch {
|
|
2410
2604
|
originalEnd(body, done);
|
|
2411
2605
|
}
|
|
@@ -2453,13 +2647,13 @@ async function serveDevtools(server, res, options) {
|
|
|
2453
2647
|
res.end(JSON.stringify(graph, null, 2));
|
|
2454
2648
|
return;
|
|
2455
2649
|
}
|
|
2456
|
-
let html = devtools.buildDevtoolsHtml(graph);
|
|
2650
|
+
let html = devtools.buildDevtoolsHtml(graph, { base: options.base });
|
|
2457
2651
|
html = await server.transformIndexHtml(options.url, html);
|
|
2458
2652
|
res.statusCode = 200;
|
|
2459
2653
|
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
2460
2654
|
res.end(html);
|
|
2461
2655
|
}
|
|
2462
|
-
async function handleDevError(server, req, res, next, url, error) {
|
|
2656
|
+
async function handleDevError(server, req, res, next, url, error, base) {
|
|
2463
2657
|
if (error instanceof Error) server.ssrFixStacktrace(error);
|
|
2464
2658
|
if (req.headers["x-pracht-route-state-request"] === "1") {
|
|
2465
2659
|
res.statusCode = 500;
|
|
@@ -2476,7 +2670,8 @@ async function handleDevError(server, req, res, next, url, error) {
|
|
|
2476
2670
|
let html = buildErrorOverlayHtml({
|
|
2477
2671
|
message: error instanceof Error ? error.message : String(error),
|
|
2478
2672
|
stack: error instanceof Error ? error.stack : void 0,
|
|
2479
|
-
root: server.config.root
|
|
2673
|
+
root: server.config.root,
|
|
2674
|
+
base
|
|
2480
2675
|
});
|
|
2481
2676
|
html = await server.transformIndexHtml(url, html);
|
|
2482
2677
|
res.statusCode = 500;
|
|
@@ -2505,11 +2700,12 @@ function isDevNotFoundRequest(requestUrl, req, options = {}) {
|
|
|
2505
2700
|
if (!accept.includes("text/html") && !accept.includes("application/xhtml+xml")) return false;
|
|
2506
2701
|
return !matchesResolvedRoute(url.pathname, options);
|
|
2507
2702
|
}
|
|
2508
|
-
async function serveDevNotFound(server, res, next, url, pathname, options) {
|
|
2703
|
+
async function serveDevNotFound(server, res, next, url, pathname, options, base) {
|
|
2509
2704
|
try {
|
|
2510
2705
|
const { buildDevNotFoundHtml } = await server.ssrLoadModule("@pracht/core/dev-404");
|
|
2511
2706
|
let html = buildDevNotFoundHtml({
|
|
2512
2707
|
apiRoutes: options.apiRoutes.map((route) => ({ path: route.path })),
|
|
2708
|
+
base,
|
|
2513
2709
|
requestedPath: pathname,
|
|
2514
2710
|
routes: options.app.routes.map((route) => ({
|
|
2515
2711
|
path: route.path,
|
|
@@ -2604,10 +2800,11 @@ const DEV_ASSET_EXTENSIONS = new Set([
|
|
|
2604
2800
|
".woff2",
|
|
2605
2801
|
".xml"
|
|
2606
2802
|
]);
|
|
2607
|
-
async function nodeToWebRequest(req, maxBodySize) {
|
|
2803
|
+
async function nodeToWebRequest(req, maxBodySize, base = "/") {
|
|
2608
2804
|
const protocol = "http";
|
|
2609
2805
|
const host = req.headers.host ?? "localhost";
|
|
2610
|
-
const
|
|
2806
|
+
const path = req.url ?? "/";
|
|
2807
|
+
const url = new URL(base === "/" || !path.startsWith("/") ? path : `${base}${path.slice(1)}`, `${protocol}://${host}`);
|
|
2611
2808
|
const method = req.method ?? "GET";
|
|
2612
2809
|
const headers = new Headers();
|
|
2613
2810
|
for (const [key, value] of Object.entries(req.headers)) {
|
|
@@ -2635,14 +2832,34 @@ async function nodeToWebRequest(req, maxBodySize) {
|
|
|
2635
2832
|
}
|
|
2636
2833
|
//#endregion
|
|
2637
2834
|
//#region src/index.ts
|
|
2835
|
+
function reachesHeadBearingModule(modules, serverRoot, headHints) {
|
|
2836
|
+
const pending = [...modules];
|
|
2837
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2838
|
+
while (pending.length > 0) {
|
|
2839
|
+
const module = pending.pop();
|
|
2840
|
+
if (!module || seen.has(module)) continue;
|
|
2841
|
+
seen.add(module);
|
|
2842
|
+
const modulePath = module.file ?? module.id?.split("?", 1)[0];
|
|
2843
|
+
if (modulePath) {
|
|
2844
|
+
const normalizedPath = toPosixPath(modulePath);
|
|
2845
|
+
if (headHints[normalizedPath.startsWith(serverRoot) ? normalizedPath.slice(serverRoot.length) : normalizedPath] === true) return true;
|
|
2846
|
+
}
|
|
2847
|
+
if (module.importers) pending.push(...module.importers);
|
|
2848
|
+
}
|
|
2849
|
+
return false;
|
|
2850
|
+
}
|
|
2638
2851
|
function pracht(options = {}) {
|
|
2639
2852
|
const resolved = resolveOptions(options);
|
|
2640
2853
|
const isPagesMode = !!resolved.pagesDir;
|
|
2641
2854
|
let root = process.cwd();
|
|
2642
2855
|
let routeFileDirs = [];
|
|
2856
|
+
let clientRouteHeadHints = {};
|
|
2857
|
+
const routeFileExtensions = withAdditionalExtensions(DEFAULT_ROUTE_EXTENSIONS, resolved.additionalExtensions);
|
|
2643
2858
|
let capabilityModulePaths = /* @__PURE__ */ new Set();
|
|
2644
2859
|
if (isPagesMode && options.appFile) console.warn("[pracht] Both `pagesDir` and `appFile` are set. `pagesDir` takes precedence — `appFile` will be ignored.");
|
|
2645
2860
|
let isBuild = false;
|
|
2861
|
+
let base = "/";
|
|
2862
|
+
let configuredBase;
|
|
2646
2863
|
const prachtPlugin = {
|
|
2647
2864
|
name: "pracht",
|
|
2648
2865
|
enforce: "pre",
|
|
@@ -2654,13 +2871,17 @@ function pracht(options = {}) {
|
|
|
2654
2871
|
const envDir = _config.envDir ? resolve(configRoot, _config.envDir) : configRoot;
|
|
2655
2872
|
const publicEnvDefine = JSON.stringify(loadEnv(env.mode, envDir, PUBLIC_ENV_PREFIX));
|
|
2656
2873
|
const agentSurfaceDefine = env.command === "build" ? String(hasAgentSurface(resolved, configRoot)) : "true";
|
|
2874
|
+
const staticTargetDefine = String(env.command === "build" && resolved.adapter.staticTarget === true);
|
|
2875
|
+
const clientFeatureDefines = { __PRACHT_CLIENT_PREFETCH__: String(resolved.client.prefetch) };
|
|
2657
2876
|
return {
|
|
2658
2877
|
appType: "custom",
|
|
2659
2878
|
envPrefix: ["VITE_", PUBLIC_ENV_PREFIX],
|
|
2660
2879
|
resolve: { dedupe: PREACT_DEDUPE },
|
|
2661
2880
|
define: {
|
|
2662
2881
|
__PRACHT_PUBLIC_ENV__: publicEnvDefine,
|
|
2663
|
-
__PRACHT_AGENT_SURFACE__: agentSurfaceDefine
|
|
2882
|
+
__PRACHT_AGENT_SURFACE__: agentSurfaceDefine,
|
|
2883
|
+
__PRACHT_STATIC_TARGET__: staticTargetDefine,
|
|
2884
|
+
...clientFeatureDefines
|
|
2664
2885
|
},
|
|
2665
2886
|
...isSSRBuild ? {} : { build: { rollupOptions: {
|
|
2666
2887
|
...wantsIslandsEntry ? { input: [PRACHT_ISLANDS_CLIENT_MODULE_ID] } : {},
|
|
@@ -2683,12 +2904,15 @@ function pracht(options = {}) {
|
|
|
2683
2904
|
external: ["node:module"]
|
|
2684
2905
|
} } },
|
|
2685
2906
|
build: { rollupOptions: { external: [/^cloudflare:/] } }
|
|
2686
|
-
} : {}
|
|
2907
|
+
} : {},
|
|
2908
|
+
...!isEdge && isSSRBuild || env.command === "serve" ? { ssr: { noExternal: [PRACHT_SSR_NO_EXTERNAL] } } : {}
|
|
2687
2909
|
};
|
|
2688
2910
|
},
|
|
2689
2911
|
configResolved(config) {
|
|
2912
|
+
assertSafeRootAbsoluteDeployBase(config.base);
|
|
2690
2913
|
root = config.root;
|
|
2691
2914
|
isBuild = config.command === "build";
|
|
2915
|
+
base = config.base;
|
|
2692
2916
|
routeFileDirs = computeRouteFileDirs(root, resolved);
|
|
2693
2917
|
capabilityModulePaths = new Set(resolveCapabilityModulePaths(resolved, root).map(canonicalFilePath));
|
|
2694
2918
|
},
|
|
@@ -2704,11 +2928,19 @@ function pracht(options = {}) {
|
|
|
2704
2928
|
},
|
|
2705
2929
|
load(id) {
|
|
2706
2930
|
if (isIslandsClientModule(id)) return createPrachtIslandsClientModuleSource(resolved, { root });
|
|
2707
|
-
if (isClientModule(id))
|
|
2708
|
-
|
|
2931
|
+
if (isClientModule(id)) {
|
|
2932
|
+
clientRouteHeadHints = createRouteHeadHintsForVirtualModules(resolved, root);
|
|
2933
|
+
return createPrachtClientModuleSource(resolved, { root });
|
|
2934
|
+
}
|
|
2935
|
+
if (isDevModule(id)) return createPrachtDevModuleSource(resolved, {
|
|
2936
|
+
root,
|
|
2937
|
+
base
|
|
2938
|
+
});
|
|
2709
2939
|
if (isServerModule(id)) return createPrachtServerModuleSource(resolved, {
|
|
2710
2940
|
root,
|
|
2711
|
-
isBuild
|
|
2941
|
+
isBuild,
|
|
2942
|
+
base,
|
|
2943
|
+
configuredBase
|
|
2712
2944
|
});
|
|
2713
2945
|
if (isCapabilitiesModule(id)) return createPrachtCapabilitiesClientModuleSource(resolved, { root });
|
|
2714
2946
|
if (isWebmcpModule(id)) return createPrachtWebmcpModuleSource(resolved, { root });
|
|
@@ -2727,6 +2959,7 @@ function pracht(options = {}) {
|
|
|
2727
2959
|
configureServer(server) {
|
|
2728
2960
|
if (isPagesMode) watchPagesDirectory(server, resolved, root);
|
|
2729
2961
|
if (resolved.adapter.ownsDevServer) {
|
|
2962
|
+
server.middlewares.use(createOwnedDevEntryMiddleware(server));
|
|
2730
2963
|
server.middlewares.use(createDevCssInjectionMiddleware(server));
|
|
2731
2964
|
return;
|
|
2732
2965
|
}
|
|
@@ -2745,14 +2978,29 @@ function pracht(options = {}) {
|
|
|
2745
2978
|
return html;
|
|
2746
2979
|
}
|
|
2747
2980
|
},
|
|
2748
|
-
handleHotUpdate({ file, server }) {
|
|
2981
|
+
handleHotUpdate({ file, modules = [], server }) {
|
|
2749
2982
|
const serverRoot = toPosixPath(server.config.root);
|
|
2750
2983
|
const normalizedFile = toPosixPath(file);
|
|
2751
2984
|
const relative = normalizedFile.startsWith(serverRoot) ? normalizedFile.slice(serverRoot.length) : normalizedFile;
|
|
2985
|
+
const changesRouteHeadSource = isPagesMode ? relative.startsWith(resolved.pagesDir) : relative.startsWith(resolved.routesDir) || relative.startsWith(resolved.shellsDir);
|
|
2986
|
+
const changesRouteHeadDependency = reachesHeadBearingModule(modules, serverRoot, clientRouteHeadHints);
|
|
2987
|
+
let shouldReloadClientHead = changesRouteHeadDependency;
|
|
2988
|
+
let clientHeadModule;
|
|
2989
|
+
if (changesRouteHeadSource || changesRouteHeadDependency) clientHeadModule = server.moduleGraph.getModuleById(PRACHT_CLIENT_MODULE_ID);
|
|
2990
|
+
if (changesRouteHeadSource) {
|
|
2991
|
+
const previousHint = clientRouteHeadHints[relative];
|
|
2992
|
+
try {
|
|
2993
|
+
const nextHints = createRouteHeadHintsForVirtualModules(resolved, root);
|
|
2994
|
+
shouldReloadClientHead ||= previousHint === true || nextHints[relative] === true;
|
|
2995
|
+
clientRouteHeadHints = nextHints;
|
|
2996
|
+
} catch {
|
|
2997
|
+
shouldReloadClientHead = true;
|
|
2998
|
+
}
|
|
2999
|
+
} else if (changesRouteHeadDependency && clientHeadModule) server.moduleGraph.invalidateModule(clientHeadModule);
|
|
2752
3000
|
if (isPagesMode && relative.startsWith(resolved.pagesDir)) {
|
|
2753
3001
|
clearPagesAppSourceCache();
|
|
2754
3002
|
invalidateVirtualModules(server);
|
|
2755
|
-
sendServerOnlyFullReload(server, file);
|
|
3003
|
+
if (!sendServerOnlyFullReload(server, file) && shouldReloadClientHead && clientHeadModule) return [...new Set([...modules, clientHeadModule])];
|
|
2756
3004
|
return;
|
|
2757
3005
|
}
|
|
2758
3006
|
if (!isPagesMode && relative === resolved.appFile) {
|
|
@@ -2772,7 +3020,7 @@ function pracht(options = {}) {
|
|
|
2772
3020
|
if (serverMod) server.moduleGraph.invalidateModule(serverMod);
|
|
2773
3021
|
const devMod = server.moduleGraph.getModuleById(PRACHT_DEV_MODULE_ID);
|
|
2774
3022
|
if (devMod) server.moduleGraph.invalidateModule(devMod);
|
|
2775
|
-
if (relative.startsWith(resolved.routesDir)) {
|
|
3023
|
+
if (relative.startsWith(resolved.routesDir) || relative.startsWith(resolved.shellsDir)) {
|
|
2776
3024
|
const clientMod = server.moduleGraph.getModuleById(PRACHT_CLIENT_MODULE_ID);
|
|
2777
3025
|
if (clientMod) server.moduleGraph.invalidateModule(clientMod);
|
|
2778
3026
|
}
|
|
@@ -2790,7 +3038,16 @@ function pracht(options = {}) {
|
|
|
2790
3038
|
if (capabilityMod) server.moduleGraph.invalidateModule(capabilityMod);
|
|
2791
3039
|
}
|
|
2792
3040
|
}
|
|
2793
|
-
sendServerOnlyFullReload(server, file);
|
|
3041
|
+
if (!sendServerOnlyFullReload(server, file) && shouldReloadClientHead && clientHeadModule) return [...new Set([...modules, clientHeadModule])];
|
|
3042
|
+
}
|
|
3043
|
+
};
|
|
3044
|
+
const configuredBasePlugin = {
|
|
3045
|
+
name: "pracht:configured-base",
|
|
3046
|
+
config: {
|
|
3047
|
+
order: "post",
|
|
3048
|
+
handler(config) {
|
|
3049
|
+
configuredBase = typeof config.base === "string" ? config.base : void 0;
|
|
3050
|
+
}
|
|
2794
3051
|
}
|
|
2795
3052
|
};
|
|
2796
3053
|
const clientModuleTransformPlugin = {
|
|
@@ -2798,7 +3055,7 @@ function pracht(options = {}) {
|
|
|
2798
3055
|
enforce: "post",
|
|
2799
3056
|
transform(code, id, transformOptions) {
|
|
2800
3057
|
if (!transformOptions?.ssr && isCapabilityModule(id, capabilityModulePaths)) throw new Error(`[pracht] Capability module ${JSON.stringify(toPosixPath(id))} was imported by client code. Capability modules are server-only — their run() implementation and its imports would be bundled for every visitor. Call the capability instead: \`callCapability\`/\`capabilities\` from "virtual:pracht/capabilities" in the browser, or \`invokeCapability\` from "@pracht/core/server" in loaders, middleware, and API routes.`);
|
|
2801
|
-
if (!(isPrachtClientModuleId(id) || !transformOptions?.ssr && isRouteOrShellFile(id, routeFileDirs))) return null;
|
|
3058
|
+
if (!(isPrachtClientModuleId(id) || !transformOptions?.ssr && isRouteOrShellFile(id, routeFileDirs, routeFileExtensions))) return null;
|
|
2802
3059
|
const transformed = stripServerOnlyExportsForClient(code, id);
|
|
2803
3060
|
if (transformed === code) return null;
|
|
2804
3061
|
return {
|
|
@@ -2812,7 +3069,7 @@ function pracht(options = {}) {
|
|
|
2812
3069
|
name: "pracht:optimize-deps-entries",
|
|
2813
3070
|
enforce: "post",
|
|
2814
3071
|
config(config) {
|
|
2815
|
-
return withPrachtOptimizeDepsEntries(config,
|
|
3072
|
+
return withPrachtOptimizeDepsEntries(config, resolved, createPrachtOptimizeDepsInclude(config.root ?? process.cwd()));
|
|
2816
3073
|
}
|
|
2817
3074
|
};
|
|
2818
3075
|
const precompilePlugin = resolved.precompileSsrJsx ? preactSsrPrecompile({
|
|
@@ -2823,6 +3080,7 @@ function pracht(options = {}) {
|
|
|
2823
3080
|
...precompilePlugin ? [precompilePlugin] : [],
|
|
2824
3081
|
...preact(),
|
|
2825
3082
|
prachtPlugin,
|
|
3083
|
+
configuredBasePlugin,
|
|
2826
3084
|
clientModuleTransformPlugin,
|
|
2827
3085
|
...edgeRuntimeSafetyPlugin ? [edgeRuntimeSafetyPlugin] : [],
|
|
2828
3086
|
createEnvSafetyPlugin(resolved.envSafety)
|
|
@@ -2832,6 +3090,26 @@ function pracht(options = {}) {
|
|
|
2832
3090
|
plugins.push(optimizeDepsEntriesPlugin);
|
|
2833
3091
|
return plugins;
|
|
2834
3092
|
}
|
|
3093
|
+
function assertSafeRootAbsoluteDeployBase(base) {
|
|
3094
|
+
if (typeof base !== "string" || !base.startsWith("/") || base.startsWith("//")) return;
|
|
3095
|
+
let safe = !base.includes("?") && !base.includes("#");
|
|
3096
|
+
if (safe) try {
|
|
3097
|
+
const segments = base.split("/");
|
|
3098
|
+
safe = segments.every((segment, index) => {
|
|
3099
|
+
if (segment === "" && index !== 0 && index !== segments.length - 1) return false;
|
|
3100
|
+
const decoded = decodeURIComponent(segment);
|
|
3101
|
+
if (decoded === "." || decoded === "..") return false;
|
|
3102
|
+
for (const character of decoded) {
|
|
3103
|
+
const codePoint = character.codePointAt(0);
|
|
3104
|
+
if (character === "/" || character === "\\" || codePoint === 0 || codePoint !== void 0 && (codePoint <= 31 || codePoint === 127)) return false;
|
|
3105
|
+
}
|
|
3106
|
+
return true;
|
|
3107
|
+
});
|
|
3108
|
+
} catch {
|
|
3109
|
+
safe = false;
|
|
3110
|
+
}
|
|
3111
|
+
if (!safe) throw new Error(`[pracht] Vite \`base\` is set to ${JSON.stringify(base)}, but root-absolute deploy bases must contain safe URL segments. Repeated slashes, malformed escapes, and segments that decode to a path separator, \`.\`, \`..\`, NUL, or another control character are not allowed.`);
|
|
3112
|
+
}
|
|
2835
3113
|
function isGraphOnlyMode() {
|
|
2836
3114
|
return process.env[PRACHT_GRAPH_ONLY_ENV] === "1";
|
|
2837
3115
|
}
|
|
@@ -2915,6 +3193,7 @@ const PRACHT_OPTIMIZE_DEPS_INCLUDE = [
|
|
|
2915
3193
|
"@pracht/core/manifest"
|
|
2916
3194
|
];
|
|
2917
3195
|
const PREACT_DEDUPE = ["preact", "preact-render-to-string"];
|
|
3196
|
+
const PRACHT_SSR_NO_EXTERNAL = /^@pracht\//;
|
|
2918
3197
|
function createPrachtOptimizeDepsInclude(root) {
|
|
2919
3198
|
try {
|
|
2920
3199
|
if (!toPosixPath(createRequire(join(root, "package.json")).resolve("@pracht/core/package.json")).includes("/node_modules/")) return [];
|
|
@@ -2923,8 +3202,9 @@ function createPrachtOptimizeDepsInclude(root) {
|
|
|
2923
3202
|
return [];
|
|
2924
3203
|
}
|
|
2925
3204
|
}
|
|
2926
|
-
function withPrachtOptimizeDepsEntries(config,
|
|
2927
|
-
const
|
|
3205
|
+
function withPrachtOptimizeDepsEntries(config, resolved, prachtInclude) {
|
|
3206
|
+
const prachtEntries = createPrachtOptimizeDepsEntries(resolved, config.optimizeDeps?.extensions);
|
|
3207
|
+
const environments = Object.fromEntries(Object.entries(config.environments ?? {}).map(([name, environment]) => [name, { optimizeDeps: { entries: mergeOptimizeDepsEntries(environment.optimizeDeps?.entries, createPrachtOptimizeDepsEntries(resolved, environment.optimizeDeps?.extensions ?? config.optimizeDeps?.extensions)) } }]));
|
|
2928
3208
|
return {
|
|
2929
3209
|
optimizeDeps: {
|
|
2930
3210
|
entries: mergeOptimizeDepsEntries(config.optimizeDeps?.entries, prachtEntries),
|
|
@@ -2933,9 +3213,24 @@ function withPrachtOptimizeDepsEntries(config, prachtEntries, prachtInclude) {
|
|
|
2933
3213
|
...Object.keys(environments).length > 0 ? { environments } : {}
|
|
2934
3214
|
};
|
|
2935
3215
|
}
|
|
2936
|
-
|
|
3216
|
+
const VITE_SCANNABLE_ROUTE_EXTENSIONS = new Set([
|
|
3217
|
+
".ts",
|
|
3218
|
+
".tsx",
|
|
3219
|
+
".js",
|
|
3220
|
+
".jsx",
|
|
3221
|
+
".mts",
|
|
3222
|
+
".mjs",
|
|
3223
|
+
".cts",
|
|
3224
|
+
".cjs",
|
|
3225
|
+
".vue",
|
|
3226
|
+
".svelte",
|
|
3227
|
+
".astro",
|
|
3228
|
+
".imba"
|
|
3229
|
+
]);
|
|
3230
|
+
function createPrachtOptimizeDepsEntries(resolved, optimizerExtensions) {
|
|
2937
3231
|
const scriptExtensions = "{ts,tsx,js,jsx}";
|
|
2938
|
-
const
|
|
3232
|
+
const explicitlyScannable = new Set(optimizerExtensions ?? []);
|
|
3233
|
+
const routeExtensions = extensionGlob([...new Set([...DEFAULT_ROUTE_EXTENSIONS, ...resolved.additionalExtensions])].filter((extension) => VITE_SCANNABLE_ROUTE_EXTENSIONS.has(extension) || explicitlyScannable.has(extension)));
|
|
2939
3234
|
const apiDir = toOptimizeDepsEntry(resolved.apiDir);
|
|
2940
3235
|
const apiEntries = [`${apiDir}/**/*.{ts,js,tsx,jsx}`, `!${apiDir}/**/*.d.ts`];
|
|
2941
3236
|
const entries = resolved.pagesDir ? [
|
|
@@ -2985,15 +3280,6 @@ function invalidateVirtualModules(server) {
|
|
|
2985
3280
|
if (serverMod) server.moduleGraph.invalidateModule(serverMod);
|
|
2986
3281
|
if (devMod) server.moduleGraph.invalidateModule(devMod);
|
|
2987
3282
|
}
|
|
2988
|
-
const ROUTE_FILE_EXTENSIONS = new Set([
|
|
2989
|
-
".ts",
|
|
2990
|
-
".tsx",
|
|
2991
|
-
".js",
|
|
2992
|
-
".jsx",
|
|
2993
|
-
".md",
|
|
2994
|
-
".mdx",
|
|
2995
|
-
".tsrx"
|
|
2996
|
-
]);
|
|
2997
3283
|
function computeRouteFileDirs(root, resolved) {
|
|
2998
3284
|
return (resolved.pagesDir ? [resolved.pagesDir] : [resolved.routesDir, resolved.shellsDir]).map((dir) => canonicalFilePath(resolveConfigPath(root, dir))).map(withTrailingSep);
|
|
2999
3285
|
}
|
|
@@ -3024,7 +3310,7 @@ function canonicalFilePath(path) {
|
|
|
3024
3310
|
return toPosixPath(path);
|
|
3025
3311
|
}
|
|
3026
3312
|
}
|
|
3027
|
-
function isRouteOrShellFile(id, dirs) {
|
|
3313
|
+
function isRouteOrShellFile(id, dirs, extensions) {
|
|
3028
3314
|
if (dirs.length === 0) return false;
|
|
3029
3315
|
const queryStart = id.indexOf("?");
|
|
3030
3316
|
const path = queryStart === -1 ? id : id.slice(0, queryStart);
|
|
@@ -3032,7 +3318,7 @@ function isRouteOrShellFile(id, dirs) {
|
|
|
3032
3318
|
const extIndex = path.lastIndexOf(".");
|
|
3033
3319
|
if (extIndex === -1) return false;
|
|
3034
3320
|
const ext = path.slice(extIndex);
|
|
3035
|
-
if (!
|
|
3321
|
+
if (!extensions.has(ext)) return false;
|
|
3036
3322
|
const normalized = toPosixPath(path);
|
|
3037
3323
|
return dirs.some((dir) => normalized.startsWith(dir));
|
|
3038
3324
|
}
|