@pracht/vite-plugin 0.7.6 → 0.9.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 +31 -9
- package/dist/index.d.mts +42 -0
- package/dist/index.mjs +597 -101
- package/dist/pages-router-KcCRkhnf.mjs +588 -0
- package/dist/pages-router.d.mts +5 -1
- package/dist/pages-router.mjs +1 -1
- package/package.json +7 -5
- package/dist/pages-router-CStJY2Zp.mjs +0 -254
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { i as scanPagesDirectory, n as generatePagesManifestSource, o as createRouteLoaderHints } from "./pages-router-
|
|
2
|
-
import { createRequire } from "node:module";
|
|
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
|
+
import { createRequire, isBuiltin } from "node:module";
|
|
3
3
|
import { preactSsrPrecompile } from "@pracht/preact-ssr-precompile";
|
|
4
4
|
import preact from "@preact/preset-vite";
|
|
5
5
|
import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
6
6
|
import { dirname, extname, join, resolve } from "node:path";
|
|
7
7
|
import { loadEnv, parseAst } from "vite";
|
|
8
|
+
import { PRACHT_GRAPH_ONLY_ENV } from "@pracht/core/server";
|
|
8
9
|
import { CAPABILITY_SETTLED_EVENT, CAPABILITY_TRANSPORT_HEADER, CONFIRMATION_HEADER } from "@pracht/capabilities";
|
|
9
|
-
import { extractCapabilityProjection, extractCapabilityRegistrations } from "@pracht/capabilities/static";
|
|
10
|
+
import { extractCapabilityProjection, extractCapabilityRegistrations, extractDefineAppObjectBody, scanTopLevelProperties } from "@pracht/capabilities/static";
|
|
10
11
|
import { createNodeServerEntryModule } from "@pracht/adapter-node";
|
|
11
|
-
import {
|
|
12
|
+
import { Readable } from "node:stream";
|
|
13
|
+
import { applyDefaultSecurityHeaders, resolveRegistryModule } from "@pracht/core";
|
|
12
14
|
//#region src/client-module-query.ts
|
|
13
15
|
const CLIENT_MODULE_QUERY = "pracht-client";
|
|
14
16
|
const PRACHT_CLIENT_MODULE_QUERY = `?${CLIENT_MODULE_QUERY}`;
|
|
@@ -27,7 +29,6 @@ function stripPrachtClientModuleQuery(id) {
|
|
|
27
29
|
function getRolldownLang(id) {
|
|
28
30
|
const path = stripPrachtClientModuleQuery(id).split("?")[0];
|
|
29
31
|
if (/\.(c|m)?tsx$/i.test(path)) return "tsx";
|
|
30
|
-
if (/\.tsrx$/i.test(path)) return "tsx";
|
|
31
32
|
if (/\.(c|m)?ts$/i.test(path)) return "ts";
|
|
32
33
|
if (/\.(c|m)?jsx$/i.test(path)) return "jsx";
|
|
33
34
|
if (/\.mdx?$/i.test(path)) return "jsx";
|
|
@@ -1257,7 +1258,15 @@ const PRACHT_CAPABILITIES_MODULE_ID = "virtual:pracht/capabilities";
|
|
|
1257
1258
|
const PRACHT_WEBMCP_MODULE_ID = "virtual:pracht/webmcp";
|
|
1258
1259
|
const CLIENT_BROWSER_PATH = "/@pracht/client.js";
|
|
1259
1260
|
const ISLANDS_CLIENT_BROWSER_PATH = "/@pracht/islands.js";
|
|
1260
|
-
|
|
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 = "/") {
|
|
1261
1270
|
const manifestPath = ["dist/client/.vite/manifest.json", "dist/.vite/manifest.json"].map((candidate) => resolve(root, candidate)).find((candidate) => existsSync(candidate));
|
|
1262
1271
|
if (!manifestPath) return {
|
|
1263
1272
|
clientEntryUrl: null,
|
|
@@ -1275,22 +1284,22 @@ function readClientBuildAssets(root = process.cwd()) {
|
|
|
1275
1284
|
if (!entry.src) continue;
|
|
1276
1285
|
const deps = collectTransitiveDeps(manifest, key);
|
|
1277
1286
|
const manifestKey = stripPrachtClientModuleQuery(entry.src);
|
|
1278
|
-
if (deps.css.length > 0) cssManifest[manifestKey] = deps.css.map((f) =>
|
|
1279
|
-
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));
|
|
1280
1289
|
}
|
|
1281
|
-
addEntryDeps(manifest, jsManifest, PRACHT_CLIENT_MODULE_ID, clientEntry);
|
|
1282
|
-
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);
|
|
1283
1292
|
return {
|
|
1284
|
-
clientEntryUrl: clientEntry ?
|
|
1285
|
-
islandsEntryUrl: islandsEntry ?
|
|
1293
|
+
clientEntryUrl: clientEntry ? assetUrl(clientEntry.file, base) : null,
|
|
1294
|
+
islandsEntryUrl: islandsEntry ? assetUrl(islandsEntry.file, base) : null,
|
|
1286
1295
|
cssManifest,
|
|
1287
1296
|
jsManifest
|
|
1288
1297
|
};
|
|
1289
1298
|
}
|
|
1290
|
-
function addEntryDeps(manifest, jsManifest, entryKey, entry) {
|
|
1299
|
+
function addEntryDeps(manifest, jsManifest, entryKey, entry, base) {
|
|
1291
1300
|
if (!entry) return;
|
|
1292
1301
|
const deps = collectTransitiveDeps(manifest, entryKey).js.filter((file) => file !== entry.file);
|
|
1293
|
-
if (deps.length > 0) jsManifest[entryKey] = deps.map((file) =>
|
|
1302
|
+
if (deps.length > 0) jsManifest[entryKey] = deps.map((file) => assetUrl(file, base));
|
|
1294
1303
|
}
|
|
1295
1304
|
function collectTransitiveDeps(manifest, key) {
|
|
1296
1305
|
const css = /* @__PURE__ */ new Set();
|
|
@@ -1349,6 +1358,7 @@ const DEFAULTS = {
|
|
|
1349
1358
|
shellsDir: "/src/shells",
|
|
1350
1359
|
apiDir: "/src/api",
|
|
1351
1360
|
serverDir: "/src/server",
|
|
1361
|
+
additionalExtensions: [],
|
|
1352
1362
|
islandsDir: "/src/islands",
|
|
1353
1363
|
capabilitiesDir: "/src/capabilities",
|
|
1354
1364
|
adapter: createDefaultNodeAdapter(),
|
|
@@ -1367,6 +1377,13 @@ function resolveOptions(options) {
|
|
|
1367
1377
|
...options
|
|
1368
1378
|
};
|
|
1369
1379
|
if (resolved.llmsTxt === void 0) resolved.llmsTxt = false;
|
|
1380
|
+
resolved.additionalExtensions = normalizeAdditionalExtensions(resolved.additionalExtensions);
|
|
1381
|
+
if (!new Set([
|
|
1382
|
+
"spa",
|
|
1383
|
+
"ssr",
|
|
1384
|
+
"ssg",
|
|
1385
|
+
"isg"
|
|
1386
|
+
]).has(resolved.pagesDefaultRender)) throw new Error("pracht({ pagesDefaultRender }) expects \"spa\", \"ssr\", \"ssg\", or \"isg\".");
|
|
1370
1387
|
if (!Number.isInteger(resolved.prerenderConcurrency) || resolved.prerenderConcurrency <= 0) throw new Error("pracht({ prerenderConcurrency }) expects a positive integer.");
|
|
1371
1388
|
if (!Number.isInteger(resolved.maxBodySize) || resolved.maxBodySize <= 0) throw new Error("pracht({ maxBodySize }) expects a positive integer number of bytes.");
|
|
1372
1389
|
validateBudgets(resolved.budgets);
|
|
@@ -1416,6 +1433,82 @@ function validateBudgets(budgets) {
|
|
|
1416
1433
|
* rather than silently dropping an endpoint.
|
|
1417
1434
|
*/
|
|
1418
1435
|
/**
|
|
1436
|
+
* Whether the app can reach the agent surface at all — registered capabilities
|
|
1437
|
+
* or a `defineApp({ agents })` config. Drives the `__PRACHT_AGENT_SURFACE__`
|
|
1438
|
+
* define, which lets the bundler drop the capability and Web Bot Auth runtimes
|
|
1439
|
+
* from the server bundle of apps that use neither.
|
|
1440
|
+
*
|
|
1441
|
+
* Deliberately one-sided: it only answers `false` when the manifest is readable
|
|
1442
|
+
* and provably free of both. An unreadable manifest, a parse failure, or any
|
|
1443
|
+
* spread inside the manifest file (which could carry registrations this
|
|
1444
|
+
* analyzer cannot see) answers `true`, so the runtime keeps deciding for
|
|
1445
|
+
* itself. Being wrong the other way would 404 a capability in production that
|
|
1446
|
+
* works in dev.
|
|
1447
|
+
*/
|
|
1448
|
+
function hasAgentSurface(options = {}, root = process.cwd()) {
|
|
1449
|
+
const resolved = resolveOptions(options);
|
|
1450
|
+
if (resolved.pagesDir) return false;
|
|
1451
|
+
const appFileAbs = resolve(root, resolved.appFile.replace(/^\//, ""));
|
|
1452
|
+
let manifestSource;
|
|
1453
|
+
try {
|
|
1454
|
+
manifestSource = readFileSync(appFileAbs, "utf-8");
|
|
1455
|
+
} catch {
|
|
1456
|
+
return true;
|
|
1457
|
+
}
|
|
1458
|
+
const appBody = extractDefineAppObjectBody(manifestSource);
|
|
1459
|
+
if (appBody === null) return true;
|
|
1460
|
+
const properties = scanTopLevelProperties(appBody);
|
|
1461
|
+
if (properties.has("agents") || properties.has("capabilities")) return true;
|
|
1462
|
+
if (/\b(?:agents|capabilities)\b/.test(appBody)) return true;
|
|
1463
|
+
if (appBody.includes("...") || hasOpaqueTopLevelProperty(appBody)) return true;
|
|
1464
|
+
try {
|
|
1465
|
+
return extractCapabilityRegistrations(manifestSource).length > 0;
|
|
1466
|
+
} catch {
|
|
1467
|
+
return true;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
/** Whether an object literal body contains an opaque key at its top level. */
|
|
1471
|
+
function hasOpaqueTopLevelProperty(objectBody) {
|
|
1472
|
+
let braces = 0;
|
|
1473
|
+
let brackets = 0;
|
|
1474
|
+
let parentheses = 0;
|
|
1475
|
+
let expectingKey = true;
|
|
1476
|
+
for (let index = 0; index < objectBody.length; index += 1) {
|
|
1477
|
+
const char = objectBody[index];
|
|
1478
|
+
const next = objectBody[index + 1];
|
|
1479
|
+
if (char === "\"" || char === "'" || char === "`") {
|
|
1480
|
+
const quote = char;
|
|
1481
|
+
for (index += 1; index < objectBody.length; index += 1) if (objectBody[index] === "\\") index += 1;
|
|
1482
|
+
else if (objectBody[index] === quote) break;
|
|
1483
|
+
continue;
|
|
1484
|
+
}
|
|
1485
|
+
if (char === "/" && next === "/") {
|
|
1486
|
+
index = objectBody.indexOf("\n", index + 2);
|
|
1487
|
+
if (index === -1) break;
|
|
1488
|
+
continue;
|
|
1489
|
+
}
|
|
1490
|
+
if (char === "/" && next === "*") {
|
|
1491
|
+
const end = objectBody.indexOf("*/", index + 2);
|
|
1492
|
+
if (end === -1) return true;
|
|
1493
|
+
index = end + 1;
|
|
1494
|
+
continue;
|
|
1495
|
+
}
|
|
1496
|
+
if (char === "/") return true;
|
|
1497
|
+
const atTopLevel = braces === 0 && brackets === 0 && parentheses === 0;
|
|
1498
|
+
if (atTopLevel && expectingKey && char === "[") return true;
|
|
1499
|
+
if (atTopLevel && expectingKey && char === "\\") return true;
|
|
1500
|
+
if (atTopLevel && char === ":") expectingKey = false;
|
|
1501
|
+
if (atTopLevel && char === ",") expectingKey = true;
|
|
1502
|
+
if (char === "{") braces += 1;
|
|
1503
|
+
else if (char === "}") braces -= 1;
|
|
1504
|
+
else if (char === "[") brackets += 1;
|
|
1505
|
+
else if (char === "]") brackets -= 1;
|
|
1506
|
+
else if (char === "(") parentheses += 1;
|
|
1507
|
+
else if (char === ")") parentheses -= 1;
|
|
1508
|
+
}
|
|
1509
|
+
return false;
|
|
1510
|
+
}
|
|
1511
|
+
/**
|
|
1419
1512
|
* Extract capability registrations (name → module path) from the app
|
|
1420
1513
|
* manifest source and their exposure metadata from each capability source.
|
|
1421
1514
|
* Pages-router apps have no manifest, so capabilities are manifest-mode only.
|
|
@@ -1489,7 +1582,7 @@ function extractCapabilityMetadata(name, file, source) {
|
|
|
1489
1582
|
*/
|
|
1490
1583
|
function createPrachtCapabilitiesClientModuleSource(options = {}, buildOptions = {}) {
|
|
1491
1584
|
const capabilities = extractCapabilities(options, buildOptions.root);
|
|
1492
|
-
const endpoints =
|
|
1585
|
+
const endpoints = Object.create(null);
|
|
1493
1586
|
for (const capability of capabilities) if (capability.httpPath) endpoints[capability.name] = {
|
|
1494
1587
|
method: "POST",
|
|
1495
1588
|
path: capability.httpPath,
|
|
@@ -1499,7 +1592,9 @@ function createPrachtCapabilitiesClientModuleSource(options = {}, buildOptions =
|
|
|
1499
1592
|
"// Generated by @pracht/vite-plugin from the app manifest capability registrations.",
|
|
1500
1593
|
"// Contains only http-exposed capability names, endpoints, and effects —",
|
|
1501
1594
|
"// capability modules themselves are server-only and never reach the client graph.",
|
|
1502
|
-
|
|
1595
|
+
"import { createUseCapability, withBase } from \"@pracht/core\";",
|
|
1596
|
+
"",
|
|
1597
|
+
`const endpoints = Object.assign(Object.create(null), JSON.parse(${JSON.stringify(JSON.stringify(endpoints))}));`,
|
|
1503
1598
|
"",
|
|
1504
1599
|
"export const capabilityEndpoints = endpoints;",
|
|
1505
1600
|
"",
|
|
@@ -1508,10 +1603,12 @@ function createPrachtCapabilitiesClientModuleSource(options = {}, buildOptions =
|
|
|
1508
1603
|
" try {",
|
|
1509
1604
|
" const headers = new Headers(opts && opts.headers);",
|
|
1510
1605
|
" headers.set(\"content-type\", \"application/json\");",
|
|
1511
|
-
" if (opts && opts.
|
|
1606
|
+
" if (opts && opts.prepare) {",
|
|
1607
|
+
` headers.delete(${JSON.stringify(CONFIRMATION_HEADER)});`,
|
|
1608
|
+
" } else if (opts && opts.confirm) {",
|
|
1512
1609
|
` headers.set(${JSON.stringify(CONFIRMATION_HEADER)}, opts.confirm);`,
|
|
1513
1610
|
" }",
|
|
1514
|
-
" response = await fetch(endpoint.path, {",
|
|
1611
|
+
" response = await fetch(withBase(endpoint.path), {",
|
|
1515
1612
|
" method: endpoint.method,",
|
|
1516
1613
|
" headers,",
|
|
1517
1614
|
" body: JSON.stringify(input === undefined ? {} : input),",
|
|
@@ -1524,8 +1621,9 @@ function createPrachtCapabilitiesClientModuleSource(options = {}, buildOptions =
|
|
|
1524
1621
|
" error: { code: \"network_error\", message: String((error && error.message) || error) },",
|
|
1525
1622
|
" };",
|
|
1526
1623
|
" }",
|
|
1624
|
+
" let result;",
|
|
1527
1625
|
" try {",
|
|
1528
|
-
"
|
|
1626
|
+
" result = await response.json();",
|
|
1529
1627
|
" } catch {",
|
|
1530
1628
|
" return {",
|
|
1531
1629
|
" ok: false,",
|
|
@@ -1535,6 +1633,23 @@ function createPrachtCapabilitiesClientModuleSource(options = {}, buildOptions =
|
|
|
1535
1633
|
" },",
|
|
1536
1634
|
" };",
|
|
1537
1635
|
" }",
|
|
1636
|
+
" if (",
|
|
1637
|
+
" !result || typeof result !== \"object\" ||",
|
|
1638
|
+
" (result.ok !== true && result.ok !== false) ||",
|
|
1639
|
+
" (result.ok === true && !(\"data\" in result)) ||",
|
|
1640
|
+
" (result.ok === false &&",
|
|
1641
|
+
" (!result.error || typeof result.error !== \"object\" ||",
|
|
1642
|
+
" typeof result.error.code !== \"string\" || typeof result.error.message !== \"string\"))",
|
|
1643
|
+
" ) {",
|
|
1644
|
+
" return {",
|
|
1645
|
+
" ok: false,",
|
|
1646
|
+
" error: {",
|
|
1647
|
+
" code: \"invalid_response\",",
|
|
1648
|
+
" message: `Capability endpoint returned an invalid envelope (status ${response.status}).`,",
|
|
1649
|
+
" },",
|
|
1650
|
+
" };",
|
|
1651
|
+
" }",
|
|
1652
|
+
" return result;",
|
|
1538
1653
|
"}",
|
|
1539
1654
|
"",
|
|
1540
1655
|
"export async function callCapability(name, input, opts) {",
|
|
@@ -1565,6 +1680,38 @@ function createPrachtCapabilitiesClientModuleSource(options = {}, buildOptions =
|
|
|
1565
1680
|
" } catch {}",
|
|
1566
1681
|
" return result;",
|
|
1567
1682
|
"}",
|
|
1683
|
+
"",
|
|
1684
|
+
"// Nested client: dotted capability names become object paths, so",
|
|
1685
|
+
"// `capabilities.notes.search(input)` calls `callCapability(\"notes.search\", input)`.",
|
|
1686
|
+
"// Built from the same endpoint table, so there is one dispatch path.",
|
|
1687
|
+
"function buildCapabilityClient(names) {",
|
|
1688
|
+
" const root = Object.create(null);",
|
|
1689
|
+
" for (const name of names) {",
|
|
1690
|
+
" const segments = name.split(\".\");",
|
|
1691
|
+
" const leaf = segments.pop();",
|
|
1692
|
+
" let node = root;",
|
|
1693
|
+
" for (const segment of segments) {",
|
|
1694
|
+
" // A name that is both a namespace and a leaf (`a` plus `a.b`) would",
|
|
1695
|
+
" // collide; the namespace wins and the leaf stays reachable through",
|
|
1696
|
+
" // callCapability(). `pracht verify` reports the shadowed name.",
|
|
1697
|
+
" if (typeof node[segment] !== \"object\" || node[segment] === null) {",
|
|
1698
|
+
" node[segment] = Object.create(null);",
|
|
1699
|
+
" }",
|
|
1700
|
+
" node = node[segment];",
|
|
1701
|
+
" }",
|
|
1702
|
+
" if (typeof node[leaf] !== \"object\") {",
|
|
1703
|
+
" node[leaf] = (input, opts) => callCapability(name, input, opts);",
|
|
1704
|
+
" }",
|
|
1705
|
+
" }",
|
|
1706
|
+
" return root;",
|
|
1707
|
+
"}",
|
|
1708
|
+
"",
|
|
1709
|
+
"export const capabilities = /*@__PURE__*/ buildCapabilityClient(Object.keys(endpoints));",
|
|
1710
|
+
"",
|
|
1711
|
+
"// The hook's implementation lives in @pracht/core (typed and unit-tested);",
|
|
1712
|
+
"// only the app-specific dispatch is bound here, so every projection shares",
|
|
1713
|
+
"// one call path. Pure-annotated: apps that never call it pay nothing.",
|
|
1714
|
+
"export const useCapability = /*@__PURE__*/ createUseCapability(callCapability);",
|
|
1568
1715
|
""
|
|
1569
1716
|
].join("\n");
|
|
1570
1717
|
}
|
|
@@ -1654,15 +1801,6 @@ function hasWebmcpCapabilities(options = {}, root = process.cwd()) {
|
|
|
1654
1801
|
}
|
|
1655
1802
|
//#endregion
|
|
1656
1803
|
//#region src/plugin-codegen.ts
|
|
1657
|
-
const ROUTE_MODULE_EXTENSIONS = new Set([
|
|
1658
|
-
".ts",
|
|
1659
|
-
".tsx",
|
|
1660
|
-
".js",
|
|
1661
|
-
".jsx",
|
|
1662
|
-
".md",
|
|
1663
|
-
".mdx",
|
|
1664
|
-
".tsrx"
|
|
1665
|
-
]);
|
|
1666
1804
|
const NON_FULL_HYDRATION_RE = /hydration\s*:\s*["'](?:islands|none)["']/;
|
|
1667
1805
|
const FULL_HYDRATION_RE = /hydration\s*:\s*["']full["']/;
|
|
1668
1806
|
const PAGES_NON_FULL_HYDRATION_RE = /export\s+const\s+HYDRATION\s*=\s*["'](?:islands|none)["']/;
|
|
@@ -1681,7 +1819,7 @@ function findMatching(source, start, open, close) {
|
|
|
1681
1819
|
}
|
|
1682
1820
|
return -1;
|
|
1683
1821
|
}
|
|
1684
|
-
function scanFiles(dir, files) {
|
|
1822
|
+
function scanFiles(dir, files, extensions) {
|
|
1685
1823
|
let entries;
|
|
1686
1824
|
try {
|
|
1687
1825
|
entries = readdirSync(dir);
|
|
@@ -1696,15 +1834,15 @@ function scanFiles(dir, files) {
|
|
|
1696
1834
|
} catch {
|
|
1697
1835
|
continue;
|
|
1698
1836
|
}
|
|
1699
|
-
if (stat.isDirectory()) scanFiles(abs, files);
|
|
1700
|
-
else if (
|
|
1837
|
+
if (stat.isDirectory()) scanFiles(abs, files, extensions);
|
|
1838
|
+
else if (extensions.has(extname(entry))) files.push(abs);
|
|
1701
1839
|
}
|
|
1702
1840
|
}
|
|
1703
1841
|
function createNonFullHydrationExcludes(resolved, root = process.cwd()) {
|
|
1704
1842
|
const excludes = /* @__PURE__ */ new Set();
|
|
1705
1843
|
if (resolved.pagesDir) {
|
|
1706
1844
|
const files = [];
|
|
1707
|
-
scanFiles(resolve(root, resolved.pagesDir.replace(/^\//, "")), files);
|
|
1845
|
+
scanFiles(resolve(root, resolved.pagesDir.replace(/^\//, "")), files, withAdditionalExtensions(DEFAULT_ROUTE_EXTENSIONS, resolved.additionalExtensions));
|
|
1708
1846
|
for (const file of files) try {
|
|
1709
1847
|
if (PAGES_NON_FULL_HYDRATION_RE.test(readFileSync(file, "utf-8"))) excludes.add(`!/${toPosixPath$1(file).replace(toPosixPath$1(root).replace(/\/$/, "") + "/", "")}`);
|
|
1710
1848
|
} catch {}
|
|
@@ -1754,15 +1892,18 @@ function createPrachtClientModuleSource(options = {}, buildOptions = {}) {
|
|
|
1754
1892
|
const resolved = resolveOptions(options);
|
|
1755
1893
|
const isPagesMode = !!resolved.pagesDir;
|
|
1756
1894
|
const routeLoaderHints = createRouteLoaderHintsForVirtualModules(resolved, buildOptions.root);
|
|
1895
|
+
const routeHeadHints = createRouteHeadHintsForVirtualModules(resolved, buildOptions.root);
|
|
1896
|
+
const routeStaticPathsHints = createRouteStaticPathsHintsForVirtualModules(resolved, buildOptions.root);
|
|
1757
1897
|
const appImport = isPagesMode ? generatePagesAppInlineSource(resolved, buildOptions.root) : `import { app } from ${JSON.stringify(resolved.appFile)};`;
|
|
1898
|
+
const bareRouteExtensions = [...withAdditionalExtensions(LEGACY_BARE_ROUTE_EXTENSIONS, resolved.additionalExtensions)];
|
|
1758
1899
|
const dirPrefix = isPagesMode ? resolved.pagesDir : resolved.routesDir;
|
|
1759
1900
|
const routeGlob = `${dirPrefix}/**/*.{ts,tsx,js,jsx,md,mdx}`;
|
|
1760
|
-
const
|
|
1901
|
+
const additionalRouteGlob = `${dirPrefix}/**/*.${extensionGlob(bareRouteExtensions)}`;
|
|
1761
1902
|
const routeExcludes = createNonFullHydrationExcludes(resolved, buildOptions.root);
|
|
1762
1903
|
const routeGlobPattern = routeExcludes.length > 0 ? [routeGlob, ...routeExcludes] : routeGlob;
|
|
1763
|
-
const
|
|
1904
|
+
const additionalRouteGlobPattern = additionalRouteGlob && routeExcludes.length > 0 ? [additionalRouteGlob, ...routeExcludes] : additionalRouteGlob;
|
|
1764
1905
|
const shellGlob = isPagesMode ? `${resolved.pagesDir}/**/_app.{ts,tsx,js,jsx}` : `${resolved.shellsDir}/**/*.{ts,tsx,js,jsx,md,mdx}`;
|
|
1765
|
-
const
|
|
1906
|
+
const additionalShellGlob = isPagesMode ? `${resolved.pagesDir}/**/_app.${extensionGlob(bareRouteExtensions)}` : `${resolved.shellsDir}/**/*.${extensionGlob(bareRouteExtensions)}`;
|
|
1766
1907
|
const appFilePosix = resolved.appFile.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
1767
1908
|
const appDir = (appFilePosix.startsWith("/") ? appFilePosix : `/${appFilePosix}`).replace(/\/[^/]*$/, "") || "/";
|
|
1768
1909
|
return [
|
|
@@ -1770,17 +1911,19 @@ function createPrachtClientModuleSource(options = {}, buildOptions = {}) {
|
|
|
1770
1911
|
appImport,
|
|
1771
1912
|
"",
|
|
1772
1913
|
`const routeLoaderHints = ${JSON.stringify(routeLoaderHints)};`,
|
|
1914
|
+
`const routeHeadHints = ${JSON.stringify(routeHeadHints)};`,
|
|
1915
|
+
`const routeStaticPathsHints = ${JSON.stringify(routeStaticPathsHints)};`,
|
|
1773
1916
|
`const routeModules = {`,
|
|
1774
1917
|
` ...import.meta.glob(${JSON.stringify(routeGlobPattern)}, { query: ${JSON.stringify(PRACHT_CLIENT_MODULE_QUERY)} }),`,
|
|
1775
|
-
` ...import.meta.glob(${JSON.stringify(
|
|
1918
|
+
` ...import.meta.glob(${JSON.stringify(additionalRouteGlobPattern)}),`,
|
|
1776
1919
|
`};`,
|
|
1777
1920
|
`const shellModules = {`,
|
|
1778
1921
|
` ...import.meta.glob(${JSON.stringify(shellGlob)}, { query: ${JSON.stringify(PRACHT_CLIENT_MODULE_QUERY)} }),`,
|
|
1779
|
-
` ...import.meta.glob(${JSON.stringify(
|
|
1922
|
+
` ...import.meta.glob(${JSON.stringify(additionalShellGlob)}),`,
|
|
1780
1923
|
`};`,
|
|
1781
1924
|
"",
|
|
1782
1925
|
"const resolvedApp = resolveApp(app);",
|
|
1783
|
-
"
|
|
1926
|
+
"applyRouteHints(resolvedApp, routeLoaderHints, routeHeadHints, routeStaticPathsHints);",
|
|
1784
1927
|
"",
|
|
1785
1928
|
...createApplyRouteLoaderHintsSource(),
|
|
1786
1929
|
`const APP_DIR = ${JSON.stringify(appDir)};`,
|
|
@@ -1876,7 +2019,9 @@ function createPrachtServerModuleSource(options = {}, buildOptions = {}) {
|
|
|
1876
2019
|
const isPagesMode = !!resolved.pagesDir;
|
|
1877
2020
|
const registrySource = createPrachtRegistryModuleSource(resolved);
|
|
1878
2021
|
const routeLoaderHints = createRouteLoaderHintsForVirtualModules(resolved, buildOptions.root);
|
|
1879
|
-
const
|
|
2022
|
+
const routeHeadHints = createRouteHeadHintsForVirtualModules(resolved, buildOptions.root);
|
|
2023
|
+
const routeStaticPathsHints = createRouteStaticPathsHintsForVirtualModules(resolved, buildOptions.root);
|
|
2024
|
+
const clientBuild = buildOptions.isBuild ? readClientBuildAssets(buildOptions.root, buildOptions.base ?? "/") : {
|
|
1880
2025
|
clientEntryUrl: null,
|
|
1881
2026
|
islandsEntryUrl: null,
|
|
1882
2027
|
cssManifest: {},
|
|
@@ -1884,10 +2029,14 @@ function createPrachtServerModuleSource(options = {}, buildOptions = {}) {
|
|
|
1884
2029
|
};
|
|
1885
2030
|
const adapter = resolved.adapter;
|
|
1886
2031
|
const llmsTxtConfig = resolveLlmsTxtConfig(resolved, buildOptions.root);
|
|
2032
|
+
const islandsBootstrapRequired = hasWebmcpCapabilities(resolved, buildOptions.root);
|
|
1887
2033
|
let prachtImports = adapter?.serverImports ? adapter.serverImports + "\nimport { prerenderApp } from \"@pracht/core/server\";" : "import { resolveApp, resolveApiRoutes, prerenderApp } from \"@pracht/core/server\";";
|
|
1888
2034
|
if (llmsTxtConfig) prachtImports += "\nimport { buildLlmsTxt } from \"@pracht/core/server\";";
|
|
1889
2035
|
const appImport = isPagesMode ? generatePagesAppInlineSource(resolved, buildOptions.root) : `import { app } from ${JSON.stringify(resolved.appFile)};`;
|
|
1890
|
-
const
|
|
2036
|
+
const devBase = buildOptions.base ?? "/";
|
|
2037
|
+
const withDevBase = (path) => devBase === "/" ? path : `${devBase}${path.slice(1)}`;
|
|
2038
|
+
const clientEntryUrl = buildOptions.isBuild ? clientBuild.clientEntryUrl : withDevBase(CLIENT_BROWSER_PATH);
|
|
2039
|
+
const islandsEntryUrl = buildOptions.isBuild ? clientBuild.islandsEntryUrl : withDevBase(ISLANDS_CLIENT_BROWSER_PATH);
|
|
1891
2040
|
const islandsGlob = `${resolved.islandsDir}/**/*.{ts,tsx,js,jsx}`;
|
|
1892
2041
|
const source = [
|
|
1893
2042
|
prachtImports,
|
|
@@ -1895,6 +2044,8 @@ function createPrachtServerModuleSource(options = {}, buildOptions = {}) {
|
|
|
1895
2044
|
appImport,
|
|
1896
2045
|
"",
|
|
1897
2046
|
`const routeLoaderHints = ${JSON.stringify(routeLoaderHints)};`,
|
|
2047
|
+
`const routeHeadHints = ${JSON.stringify(routeHeadHints)};`,
|
|
2048
|
+
`const routeStaticPathsHints = ${JSON.stringify(routeStaticPathsHints)};`,
|
|
1898
2049
|
...createApplyRouteLoaderHintsSource(),
|
|
1899
2050
|
registrySource,
|
|
1900
2051
|
"",
|
|
@@ -1906,11 +2057,15 @@ function createPrachtServerModuleSource(options = {}, buildOptions = {}) {
|
|
|
1906
2057
|
"export const islandFiles = Object.keys(islandModules);",
|
|
1907
2058
|
"",
|
|
1908
2059
|
"export const resolvedApp = resolveApp(app);",
|
|
1909
|
-
"
|
|
2060
|
+
"applyRouteHints(resolvedApp, routeLoaderHints, routeHeadHints, routeStaticPathsHints);",
|
|
1910
2061
|
`export const apiRoutes = resolveApiRoutes(Object.keys(apiModules), ${JSON.stringify(resolved.apiDir)});`,
|
|
1911
2062
|
`export const buildTarget = ${JSON.stringify(adapter?.id ?? "node")};`,
|
|
1912
|
-
`export const
|
|
2063
|
+
`export const staticTarget = ${JSON.stringify(adapter?.staticTarget === true)};`,
|
|
2064
|
+
`export const buildBase = ${JSON.stringify(buildOptions.base ?? "/")};`,
|
|
2065
|
+
`export const configuredBase = ${JSON.stringify(buildOptions.configuredBase)};`,
|
|
2066
|
+
`export const clientEntryUrl = ${JSON.stringify(clientEntryUrl ?? "/@pracht/client.js")};`,
|
|
1913
2067
|
`export const islandsEntryUrl = ${JSON.stringify(islandsEntryUrl ?? null)};`,
|
|
2068
|
+
`export const islandsBootstrapRequired = ${JSON.stringify(islandsBootstrapRequired)};`,
|
|
1914
2069
|
`export const cssManifest = ${JSON.stringify(clientBuild.cssManifest)};`,
|
|
1915
2070
|
`export const jsManifest = ${JSON.stringify(clientBuild.jsManifest)};`,
|
|
1916
2071
|
`export const prerenderConcurrency = ${JSON.stringify(resolved.prerenderConcurrency)};`,
|
|
@@ -1936,15 +2091,25 @@ function createPrachtServerModuleSource(options = {}, buildOptions = {}) {
|
|
|
1936
2091
|
*/
|
|
1937
2092
|
function createPrachtDevModuleSource(options = {}, buildOptions = {}) {
|
|
1938
2093
|
const resolved = resolveOptions(options);
|
|
2094
|
+
const routeLoaderHints = createRouteLoaderHintsForVirtualModules(resolved, buildOptions.root);
|
|
2095
|
+
const routeHeadHints = createRouteHeadHintsForVirtualModules(resolved, buildOptions.root);
|
|
2096
|
+
const routeStaticPathsHints = createRouteStaticPathsHintsForVirtualModules(resolved, buildOptions.root);
|
|
1939
2097
|
return [
|
|
1940
2098
|
"import { resolveApp, resolveApiRoutes } from \"@pracht/core/server\";",
|
|
1941
2099
|
resolved.pagesDir ? generatePagesAppInlineSource(resolved, buildOptions.root) : `import { app } from ${JSON.stringify(resolved.appFile)};`,
|
|
1942
2100
|
"",
|
|
2101
|
+
`const routeLoaderHints = ${JSON.stringify(routeLoaderHints)};`,
|
|
2102
|
+
`const routeHeadHints = ${JSON.stringify(routeHeadHints)};`,
|
|
2103
|
+
`const routeStaticPathsHints = ${JSON.stringify(routeStaticPathsHints)};`,
|
|
2104
|
+
...createApplyRouteLoaderHintsSource(),
|
|
1943
2105
|
createPrachtRegistryModuleSource(resolved),
|
|
1944
2106
|
"",
|
|
1945
2107
|
"export const resolvedApp = resolveApp(app);",
|
|
2108
|
+
"applyRouteHints(resolvedApp, routeLoaderHints, routeHeadHints, routeStaticPathsHints);",
|
|
1946
2109
|
`export const apiRoutes = resolveApiRoutes(Object.keys(apiModules), ${JSON.stringify(resolved.apiDir)});`,
|
|
1947
2110
|
`export const buildTarget = ${JSON.stringify(resolved.adapter?.id ?? "node")};`,
|
|
2111
|
+
`export const staticTarget = ${JSON.stringify(resolved.adapter?.staticTarget === true)};`,
|
|
2112
|
+
`export const buildBase = ${JSON.stringify(buildOptions.base ?? "/")};`,
|
|
1948
2113
|
""
|
|
1949
2114
|
].join("\n");
|
|
1950
2115
|
}
|
|
@@ -1964,11 +2129,12 @@ function resolveLlmsTxtConfig(resolved, root = process.cwd()) {
|
|
|
1964
2129
|
if (description) config.description = description;
|
|
1965
2130
|
if (resolved.llmsTxt.origin) config.origin = resolved.llmsTxt.origin;
|
|
1966
2131
|
if (resolved.llmsTxt.include) config.include = resolved.llmsTxt.include;
|
|
2132
|
+
if (resolved.llmsTxt.exclude?.length) config.exclude = resolved.llmsTxt.exclude;
|
|
1967
2133
|
return config;
|
|
1968
2134
|
}
|
|
1969
2135
|
function createApplyRouteLoaderHintsSource() {
|
|
1970
2136
|
return [
|
|
1971
|
-
"function
|
|
2137
|
+
"function applyRouteHints(resolvedApp, routeLoaderHints, routeHeadHints, routeStaticPathsHints) {",
|
|
1972
2138
|
" for (const route of resolvedApp.routes) {",
|
|
1973
2139
|
" const hint = routeLoaderHints[route.file];",
|
|
1974
2140
|
" if (hint === true) {",
|
|
@@ -1976,14 +2142,52 @@ function createApplyRouteLoaderHintsSource() {
|
|
|
1976
2142
|
" } else if (typeof route.hasLoader === 'undefined' && typeof hint === 'boolean') {",
|
|
1977
2143
|
" route.hasLoader = hint;",
|
|
1978
2144
|
" }",
|
|
2145
|
+
" const routeHeadHint = routeHeadHints[route.file];",
|
|
2146
|
+
" const shellHeadHint = route.shellFile ? routeHeadHints[route.shellFile] : undefined;",
|
|
2147
|
+
" const hasCompleteHeadHints = typeof routeHeadHint === 'boolean' &&",
|
|
2148
|
+
" (!route.shellFile || typeof shellHeadHint === 'boolean');",
|
|
2149
|
+
" if (routeHeadHint === true || shellHeadHint === true) {",
|
|
2150
|
+
" route.hasHead = true;",
|
|
2151
|
+
" } else if (typeof route.hasHead === 'undefined' && hasCompleteHeadHints) {",
|
|
2152
|
+
" route.hasHead = routeHeadHint === true || shellHeadHint === true;",
|
|
2153
|
+
" }",
|
|
2154
|
+
" // Only ever narrows to false, and only when the scan actually saw the",
|
|
2155
|
+
" // module: an unknown route file keeps today's conservative behavior.",
|
|
2156
|
+
" const staticPathsHint = routeStaticPathsHints[route.file];",
|
|
2157
|
+
" if (typeof route.hasStaticPaths === 'undefined' && typeof staticPathsHint === 'boolean') {",
|
|
2158
|
+
" route.hasStaticPaths = staticPathsHint;",
|
|
2159
|
+
" }",
|
|
1979
2160
|
" }",
|
|
1980
2161
|
"}",
|
|
1981
2162
|
""
|
|
1982
2163
|
];
|
|
1983
2164
|
}
|
|
2165
|
+
function createRouteHeadHintsForVirtualModules(options, root = process.cwd()) {
|
|
2166
|
+
const appFileDir = dirname(resolve(root, options.appFile.slice(1)));
|
|
2167
|
+
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))]];
|
|
2168
|
+
return Object.assign({}, ...directories.map(([prefix, directory]) => createRouteHeadHints(directory, {
|
|
2169
|
+
additionalExtensions: options.additionalExtensions,
|
|
2170
|
+
appFileDir,
|
|
2171
|
+
rootRelativePrefix: prefix
|
|
2172
|
+
})));
|
|
2173
|
+
}
|
|
2174
|
+
/**
|
|
2175
|
+
* `getStaticPaths()` presence per route file. Only routes matter — a shell
|
|
2176
|
+
* cannot enumerate paths — so unlike the head hints this skips the shells
|
|
2177
|
+
* directory.
|
|
2178
|
+
*/
|
|
2179
|
+
function createRouteStaticPathsHintsForVirtualModules(options, root = process.cwd()) {
|
|
2180
|
+
const appFileDir = dirname(resolve(root, options.appFile.slice(1)));
|
|
2181
|
+
const routesPrefix = options.pagesDir || options.routesDir;
|
|
2182
|
+
return createRouteStaticPathsHints(resolve(root, routesPrefix.slice(1)), {
|
|
2183
|
+
additionalExtensions: options.additionalExtensions,
|
|
2184
|
+
appFileDir,
|
|
2185
|
+
rootRelativePrefix: routesPrefix
|
|
2186
|
+
});
|
|
2187
|
+
}
|
|
1984
2188
|
function createRouteLoaderHintsForVirtualModules(options, root = process.cwd()) {
|
|
1985
2189
|
if (options.pagesDir) {
|
|
1986
|
-
const pages = scanPagesDirectory(resolve(root, options.pagesDir.slice(1)));
|
|
2190
|
+
const pages = scanPagesDirectory(resolve(root, options.pagesDir.slice(1)), options.additionalExtensions);
|
|
1987
2191
|
const hints = {};
|
|
1988
2192
|
for (const page of pages) {
|
|
1989
2193
|
const key = `${options.pagesDir}/${page.relativePath.replace(/\\/g, "/")}`;
|
|
@@ -1993,28 +2197,31 @@ function createRouteLoaderHintsForVirtualModules(options, root = process.cwd())
|
|
|
1993
2197
|
}
|
|
1994
2198
|
const appFileDir = dirname(resolve(root, options.appFile.slice(1)));
|
|
1995
2199
|
return createRouteLoaderHints(resolve(root, options.routesDir.slice(1)), {
|
|
2200
|
+
additionalExtensions: options.additionalExtensions,
|
|
1996
2201
|
appFileDir,
|
|
1997
2202
|
rootRelativePrefix: options.routesDir
|
|
1998
2203
|
});
|
|
1999
2204
|
}
|
|
2000
2205
|
function createPrachtRegistryModuleSource(options = {}) {
|
|
2001
2206
|
const resolved = resolveOptions(options);
|
|
2207
|
+
const apiGlobs = [`${resolved.apiDir}/**/*.{ts,js,tsx,jsx}`, `!${resolved.apiDir}/**/*.d.ts`];
|
|
2002
2208
|
const isPagesMode = !!resolved.pagesDir;
|
|
2209
|
+
const bareRouteExtensions = [...withAdditionalExtensions(LEGACY_BARE_ROUTE_EXTENSIONS, resolved.additionalExtensions)];
|
|
2003
2210
|
const routeGlob = isPagesMode ? `${resolved.pagesDir}/**/*.{ts,tsx,js,jsx,md,mdx}` : `${resolved.routesDir}/**/*.{ts,tsx,js,jsx,md,mdx}`;
|
|
2004
|
-
const
|
|
2211
|
+
const additionalRouteGlob = `${isPagesMode ? resolved.pagesDir : resolved.routesDir}/**/*.${extensionGlob(bareRouteExtensions)}`;
|
|
2005
2212
|
const shellGlob = isPagesMode ? `${resolved.pagesDir}/**/_app.{ts,tsx,js,jsx}` : `${resolved.shellsDir}/**/*.{ts,tsx,js,jsx,md,mdx}`;
|
|
2006
|
-
const
|
|
2213
|
+
const additionalShellGlob = isPagesMode ? `${resolved.pagesDir}/**/_app.${extensionGlob(bareRouteExtensions)}` : `${resolved.shellsDir}/**/*.${extensionGlob(bareRouteExtensions)}`;
|
|
2007
2214
|
return [
|
|
2008
2215
|
`export const routeModules = {`,
|
|
2009
2216
|
` ...import.meta.glob(${JSON.stringify(routeGlob)}),`,
|
|
2010
|
-
` ...import.meta.glob(${JSON.stringify(
|
|
2217
|
+
` ...import.meta.glob(${JSON.stringify(additionalRouteGlob)}),`,
|
|
2011
2218
|
`};`,
|
|
2012
2219
|
`export const shellModules = {`,
|
|
2013
2220
|
` ...import.meta.glob(${JSON.stringify(shellGlob)}),`,
|
|
2014
|
-
` ...import.meta.glob(${JSON.stringify(
|
|
2221
|
+
` ...import.meta.glob(${JSON.stringify(additionalShellGlob)}),`,
|
|
2015
2222
|
`};`,
|
|
2016
2223
|
`export const middlewareModules = import.meta.glob(${JSON.stringify(`${resolved.middlewareDir}/**/*.{ts,tsx,js,jsx}`)});`,
|
|
2017
|
-
`export const apiModules = import.meta.glob(${JSON.stringify(
|
|
2224
|
+
`export const apiModules = import.meta.glob(${JSON.stringify(apiGlobs)});`,
|
|
2018
2225
|
`export const dataModules = import.meta.glob(${JSON.stringify(`${resolved.serverDir}/**/*.{ts,js,tsx,jsx}`)});`,
|
|
2019
2226
|
`export const capabilityModules = import.meta.glob(${JSON.stringify(`${resolved.capabilitiesDir}/**/*.{ts,js,tsx,jsx}`)});`,
|
|
2020
2227
|
"",
|
|
@@ -2035,13 +2242,15 @@ function clearPagesAppSourceCache() {
|
|
|
2035
2242
|
function generatePagesAppInlineSource(options, root = process.cwd()) {
|
|
2036
2243
|
const absPagesDir = resolve(root, options.pagesDir.slice(1));
|
|
2037
2244
|
const cacheKey = JSON.stringify({
|
|
2245
|
+
additionalExtensions: options.additionalExtensions,
|
|
2038
2246
|
absPagesDir,
|
|
2039
2247
|
pagesDefaultRender: options.pagesDefaultRender,
|
|
2040
2248
|
pagesDirPrefix: options.pagesDir
|
|
2041
2249
|
});
|
|
2042
2250
|
const cached = pagesAppSourceCache.get(cacheKey);
|
|
2043
2251
|
if (cached) return cached;
|
|
2044
|
-
const source = generatePagesManifestSource(scanPagesDirectory(absPagesDir), {
|
|
2252
|
+
const source = generatePagesManifestSource(scanPagesDirectory(absPagesDir, options.additionalExtensions), {
|
|
2253
|
+
additionalExtensions: options.additionalExtensions,
|
|
2045
2254
|
pagesDir: absPagesDir,
|
|
2046
2255
|
pagesDefaultRender: options.pagesDefaultRender,
|
|
2047
2256
|
pagesDirPrefix: options.pagesDir
|
|
@@ -2056,8 +2265,46 @@ const DEFAULT_MAX_BODY_SIZE = 1024 * 1024;
|
|
|
2056
2265
|
const CSS_MODULE_URL_RE = /\.(?:css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/;
|
|
2057
2266
|
const DEVTOOLS_JSON_PATH = "/_pracht.json";
|
|
2058
2267
|
const LLMS_TXT_PATH = "/llms.txt";
|
|
2268
|
+
function isEventStreamContentType(contentType) {
|
|
2269
|
+
return contentType.split(";", 1)[0]?.trim().toLowerCase() === "text/event-stream";
|
|
2270
|
+
}
|
|
2271
|
+
/**
|
|
2272
|
+
* Adapter-owned dev servers can route every browser request through their
|
|
2273
|
+
* platform runtime before Vite's transform middleware gets a chance to serve
|
|
2274
|
+
* Pracht's stable virtual client entries. Serve those two entries at their
|
|
2275
|
+
* public, base-prefixed URLs while leaving every other request to the adapter.
|
|
2276
|
+
*/
|
|
2277
|
+
function createOwnedDevEntryMiddleware(server) {
|
|
2278
|
+
const base = server.config.base || "/";
|
|
2279
|
+
return async (req, res, next) => {
|
|
2280
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
2281
|
+
if (method !== "GET" && method !== "HEAD") return next();
|
|
2282
|
+
const requestUrl = new URL(req.url ?? "/", "http://localhost");
|
|
2283
|
+
const pathname = base === "/" ? requestUrl.pathname : requestUrl.pathname.startsWith(base) ? `/${requestUrl.pathname.slice(base.length)}` : null;
|
|
2284
|
+
if (pathname !== "/@pracht/client.js" && pathname !== "/@pracht/islands.js") return next();
|
|
2285
|
+
try {
|
|
2286
|
+
const result = await server.transformRequest(`${pathname}${requestUrl.search}`);
|
|
2287
|
+
if (!result) return next();
|
|
2288
|
+
if (result.etag && req.headers["if-none-match"] === result.etag) {
|
|
2289
|
+
res.statusCode = 304;
|
|
2290
|
+
res.end();
|
|
2291
|
+
return;
|
|
2292
|
+
}
|
|
2293
|
+
res.statusCode = 200;
|
|
2294
|
+
res.setHeader("content-type", "text/javascript");
|
|
2295
|
+
res.setHeader("cache-control", "no-cache");
|
|
2296
|
+
if (result.etag) res.setHeader("etag", result.etag);
|
|
2297
|
+
for (const [name, value] of Object.entries(server.config.server.headers ?? {})) if (value !== void 0) res.setHeader(name, value);
|
|
2298
|
+
res.end(method === "HEAD" ? void 0 : result.code);
|
|
2299
|
+
} catch (error) {
|
|
2300
|
+
next(error);
|
|
2301
|
+
}
|
|
2302
|
+
};
|
|
2303
|
+
}
|
|
2059
2304
|
function createDevSSRMiddleware(server, options = {}) {
|
|
2060
2305
|
const maxBodySize = options.maxBodySize ?? DEFAULT_MAX_BODY_SIZE;
|
|
2306
|
+
const devBase = server.config.base || "/";
|
|
2307
|
+
const withDevBase = (path) => devBase === "/" || !path.startsWith("/") ? path : `${devBase}${path.slice(1)}`;
|
|
2061
2308
|
let warnedDevtoolsCollision = false;
|
|
2062
2309
|
let warnedLlmsTxtCollision = false;
|
|
2063
2310
|
if (options.llmsTxt && typeof server.config.publicDir === "string") {
|
|
@@ -2082,6 +2329,7 @@ function createDevSSRMiddleware(server, options = {}) {
|
|
|
2082
2329
|
await serveDevtools(server, res, {
|
|
2083
2330
|
apiRoutes: serverMod.apiRoutes ?? [],
|
|
2084
2331
|
app: serverMod.resolvedApp,
|
|
2332
|
+
base: devBase,
|
|
2085
2333
|
url,
|
|
2086
2334
|
wantsJson: requestUrl.pathname === DEVTOOLS_JSON_PATH
|
|
2087
2335
|
});
|
|
@@ -2095,14 +2343,17 @@ function createDevSSRMiddleware(server, options = {}) {
|
|
|
2095
2343
|
const llmsTxt = await serverMod.generateLlmsTxt();
|
|
2096
2344
|
res.statusCode = 200;
|
|
2097
2345
|
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
2346
|
+
applyDefaultSecurityHeaders(new Headers()).forEach((value, key) => {
|
|
2347
|
+
res.setHeader(key, value);
|
|
2348
|
+
});
|
|
2098
2349
|
res.end(llmsTxt);
|
|
2099
2350
|
return;
|
|
2100
2351
|
}
|
|
2101
2352
|
if (shouldBypassDevSSR(requestUrl, req, routeMatchers)) return next();
|
|
2102
|
-
if (isDevNotFoundRequest(requestUrl, req, routeMatchers)) return serveDevNotFound(server, res, next, url, requestUrl.pathname, routeMatchers);
|
|
2353
|
+
if (isDevNotFoundRequest(requestUrl, req, routeMatchers)) return serveDevNotFound(server, res, next, url, requestUrl.pathname, routeMatchers, devBase);
|
|
2103
2354
|
let webRequest;
|
|
2104
2355
|
try {
|
|
2105
|
-
webRequest = await nodeToWebRequest(req, maxBodySize);
|
|
2356
|
+
webRequest = await nodeToWebRequest(req, maxBodySize, devBase);
|
|
2106
2357
|
} catch (err) {
|
|
2107
2358
|
if (err instanceof Error && err.message === "Request body too large") {
|
|
2108
2359
|
res.statusCode = 413;
|
|
@@ -2117,15 +2368,36 @@ function createDevSSRMiddleware(server, options = {}) {
|
|
|
2117
2368
|
registry: serverMod.registry,
|
|
2118
2369
|
request: webRequest,
|
|
2119
2370
|
debugErrors: true,
|
|
2120
|
-
clientEntryUrl: CLIENT_BROWSER_PATH,
|
|
2371
|
+
clientEntryUrl: withDevBase(CLIENT_BROWSER_PATH),
|
|
2372
|
+
islandsEntryUrl: withDevBase(ISLANDS_CLIENT_BROWSER_PATH),
|
|
2373
|
+
islandsBootstrapRequired: serverMod.islandsBootstrapRequired === true,
|
|
2121
2374
|
apiRoutes: serverMod.apiRoutes,
|
|
2122
2375
|
timings
|
|
2123
2376
|
});
|
|
2124
2377
|
const responseContentType = response.headers.get("content-type") ?? "";
|
|
2125
2378
|
if (response.status === 404 && !responseContentType.includes("application/json") && !routeMatchers.app?.notFound) return next();
|
|
2126
|
-
const contentType = response.headers.get("content-type") ?? "
|
|
2379
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
2380
|
+
if (response.body && isEventStreamContentType(contentType)) {
|
|
2381
|
+
res.statusCode = response.status;
|
|
2382
|
+
response.headers.forEach((value, key) => {
|
|
2383
|
+
res.setHeader(key, value);
|
|
2384
|
+
});
|
|
2385
|
+
const source = Readable.fromWeb(response.body);
|
|
2386
|
+
if (res.destroyed || res.writableEnded) {
|
|
2387
|
+
source.destroy();
|
|
2388
|
+
return;
|
|
2389
|
+
}
|
|
2390
|
+
res.on("close", () => {
|
|
2391
|
+
if (!res.writableFinished) source.destroy();
|
|
2392
|
+
});
|
|
2393
|
+
source.on("error", () => {
|
|
2394
|
+
res.destroy();
|
|
2395
|
+
});
|
|
2396
|
+
source.pipe(res);
|
|
2397
|
+
return;
|
|
2398
|
+
}
|
|
2127
2399
|
let body = await response.text();
|
|
2128
|
-
if (contentType.includes("text/html")) body = await server
|
|
2400
|
+
if (contentType.includes("text/html")) body = await transformDevHtml(server, url, body, devBase);
|
|
2129
2401
|
res.statusCode = response.status;
|
|
2130
2402
|
response.headers.forEach((value, key) => {
|
|
2131
2403
|
res.setHeader(key, value);
|
|
@@ -2134,7 +2406,56 @@ function createDevSSRMiddleware(server, options = {}) {
|
|
|
2134
2406
|
if (serverTiming) res.setHeader("Server-Timing", serverTiming);
|
|
2135
2407
|
res.end(body);
|
|
2136
2408
|
} catch (error) {
|
|
2137
|
-
await handleDevError(server, req, res, next, url, error);
|
|
2409
|
+
await handleDevError(server, req, res, next, url, error, devBase);
|
|
2410
|
+
}
|
|
2411
|
+
};
|
|
2412
|
+
}
|
|
2413
|
+
/**
|
|
2414
|
+
* Vite's HTML transform adds `config.base` to root-absolute asset attributes.
|
|
2415
|
+
* Pracht's runtime has already added it to URLs produced by `withBase()` — the
|
|
2416
|
+
* client entry, route-state preloads, image endpoints, and user-authored asset
|
|
2417
|
+
* URLs — while Vite-owned or module-graph URLs still need the transform. Hide
|
|
2418
|
+
* the already-based strings while the hooks run, then restore them afterward,
|
|
2419
|
+
* so each producer applies the deploy base exactly once.
|
|
2420
|
+
*/
|
|
2421
|
+
async function transformDevHtml(server, url, html, base) {
|
|
2422
|
+
if (base === "/") return server.transformIndexHtml(url, html);
|
|
2423
|
+
const assetUrls = protectRootAbsoluteAssetAttributes(html);
|
|
2424
|
+
let placeholder = "https://pracht.invalid/__PRACHT_DEV_BASE_PLACEHOLDER__/";
|
|
2425
|
+
while (assetUrls.html.includes(placeholder)) placeholder += "_";
|
|
2426
|
+
const protectedHtml = assetUrls.html.replaceAll(base, placeholder);
|
|
2427
|
+
const transformedHtml = await server.transformIndexHtml(url, protectedHtml);
|
|
2428
|
+
return assetUrls.restore(transformedHtml.replaceAll(placeholder, base));
|
|
2429
|
+
}
|
|
2430
|
+
const HTML_ASSET_URL_ATTRIBUTE_RE = /(\s(?:src|href|xlink:href|data|srcset|imagesrcset|poster|content)\s*=\s*)(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/gi;
|
|
2431
|
+
/**
|
|
2432
|
+
* Runtime-rendered root-absolute asset URLs have already reached their final
|
|
2433
|
+
* public meaning: raw `/logo.svg` deliberately stays at the origin root,
|
|
2434
|
+
* while `withBase("/logo.svg")` is already under the deploy base. Vite's dev
|
|
2435
|
+
* HTML pass prefixes both, unlike production SSR, so hide complete attribute
|
|
2436
|
+
* values behind inert external URLs until that pass finishes.
|
|
2437
|
+
*/
|
|
2438
|
+
function protectRootAbsoluteAssetAttributes(html) {
|
|
2439
|
+
let markerPrefix = "https://pracht.invalid/__PRACHT_DEV_ASSET_PLACEHOLDER__/";
|
|
2440
|
+
while (html.includes(markerPrefix)) markerPrefix += "_";
|
|
2441
|
+
const replacements = [];
|
|
2442
|
+
return {
|
|
2443
|
+
html: html.replace(HTML_ASSET_URL_ATTRIBUTE_RE, (match, prefix, doubleQuoted, singleQuoted, unquoted) => {
|
|
2444
|
+
const value = doubleQuoted ?? singleQuoted ?? unquoted ?? "";
|
|
2445
|
+
if (!(/(?:srcset)\s*=\s*$/i.test(prefix) ? /(?:^|,)\s*\/(?!\/)/.test(value) : /^\s*\/(?!\/)/.test(value))) return match;
|
|
2446
|
+
const marker = `${markerPrefix}${replacements.length}`;
|
|
2447
|
+
replacements.push({
|
|
2448
|
+
marker,
|
|
2449
|
+
value
|
|
2450
|
+
});
|
|
2451
|
+
if (doubleQuoted !== void 0) return `${prefix}"${marker}"`;
|
|
2452
|
+
if (singleQuoted !== void 0) return `${prefix}'${marker}'`;
|
|
2453
|
+
return `${prefix}${marker}`;
|
|
2454
|
+
}),
|
|
2455
|
+
restore(transformedHtml) {
|
|
2456
|
+
let restoredHtml = transformedHtml;
|
|
2457
|
+
for (const { marker, value } of replacements) restoredHtml = restoredHtml.replaceAll(marker, value);
|
|
2458
|
+
return restoredHtml;
|
|
2138
2459
|
}
|
|
2139
2460
|
};
|
|
2140
2461
|
}
|
|
@@ -2146,7 +2467,7 @@ function createDevSSRMiddleware(server, options = {}) {
|
|
|
2146
2467
|
* avoid a first-paint FOUC.
|
|
2147
2468
|
*/
|
|
2148
2469
|
async function createDevCssManifest(server, options) {
|
|
2149
|
-
const route = options.matchAppRoute(options.app, options.pathname)?.route ?? options.app.notFound;
|
|
2470
|
+
const route = options.pathname === null ? void 0 : options.matchAppRoute(options.app, options.pathname)?.route ?? options.app.notFound;
|
|
2150
2471
|
if (!route) return {};
|
|
2151
2472
|
const manifest = {};
|
|
2152
2473
|
const modules = [...route.shellFile ? [{
|
|
@@ -2195,18 +2516,19 @@ function collectDevCssUrls(entry) {
|
|
|
2195
2516
|
}
|
|
2196
2517
|
return [...urls];
|
|
2197
2518
|
}
|
|
2198
|
-
function injectDevCssLinks(html, manifest) {
|
|
2519
|
+
function injectDevCssLinks(html, manifest, base = "/") {
|
|
2199
2520
|
if (!html.includes("</head>")) return html;
|
|
2200
|
-
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}">`);
|
|
2521
|
+
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}">`);
|
|
2201
2522
|
if (tags.length === 0) return html;
|
|
2202
2523
|
return html.replace("</head>", ` ${tags.join("\n ")}\n </head>`);
|
|
2203
2524
|
}
|
|
2204
|
-
async function injectDevCssForPath(server, path, html) {
|
|
2205
|
-
return injectDevCssLinks(html, await createDevCssManifest(server, await resolveDevCssContextForPath(server, path)));
|
|
2525
|
+
async function injectDevCssForPath(server, path, html, options = {}) {
|
|
2526
|
+
return injectDevCssLinks(html, await createDevCssManifest(server, await resolveDevCssContextForPath(server, path, options)), server.config.base || "/");
|
|
2206
2527
|
}
|
|
2207
|
-
async function resolveDevCssContextForPath(server, path) {
|
|
2528
|
+
async function resolveDevCssContextForPath(server, path, options = {}) {
|
|
2208
2529
|
const [framework, serverMod] = await Promise.all([server.ssrLoadModule("@pracht/core/server"), server.ssrLoadModule(PRACHT_DEV_MODULE_ID)]);
|
|
2209
|
-
const
|
|
2530
|
+
const publicPathname = new URL(path, "http://localhost").pathname;
|
|
2531
|
+
const pathname = options.basePathRetained ? framework.stripBase(publicPathname) : publicPathname;
|
|
2210
2532
|
return {
|
|
2211
2533
|
app: serverMod.resolvedApp,
|
|
2212
2534
|
matchAppRoute: framework.matchAppRoute,
|
|
@@ -2228,7 +2550,7 @@ function createDevCssInjectionMiddleware(server) {
|
|
|
2228
2550
|
next();
|
|
2229
2551
|
return;
|
|
2230
2552
|
}
|
|
2231
|
-
const contextPromise = resolveDevCssContextForPath(server, req.url ?? "/").catch((error) => {
|
|
2553
|
+
const contextPromise = resolveDevCssContextForPath(server, req.url ?? "/", { basePathRetained: true }).catch((error) => {
|
|
2232
2554
|
if (!warned) {
|
|
2233
2555
|
warned = true;
|
|
2234
2556
|
server.config.logger.warn(`[pracht] Could not discover development stylesheets: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -2259,7 +2581,7 @@ function createDevCssInjectionMiddleware(server) {
|
|
|
2259
2581
|
try {
|
|
2260
2582
|
const context = await contextPromise;
|
|
2261
2583
|
const manifest = context ? await createDevCssManifest(server, context) : null;
|
|
2262
|
-
originalEnd(manifest ? injectDevCssLinks(body.toString("utf-8"), manifest) : body.toString("utf-8"), done);
|
|
2584
|
+
originalEnd(manifest ? injectDevCssLinks(body.toString("utf-8"), manifest, server.config.base || "/") : body.toString("utf-8"), done);
|
|
2263
2585
|
} catch {
|
|
2264
2586
|
originalEnd(body, done);
|
|
2265
2587
|
}
|
|
@@ -2307,13 +2629,13 @@ async function serveDevtools(server, res, options) {
|
|
|
2307
2629
|
res.end(JSON.stringify(graph, null, 2));
|
|
2308
2630
|
return;
|
|
2309
2631
|
}
|
|
2310
|
-
let html = devtools.buildDevtoolsHtml(graph);
|
|
2632
|
+
let html = devtools.buildDevtoolsHtml(graph, { base: options.base });
|
|
2311
2633
|
html = await server.transformIndexHtml(options.url, html);
|
|
2312
2634
|
res.statusCode = 200;
|
|
2313
2635
|
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
2314
2636
|
res.end(html);
|
|
2315
2637
|
}
|
|
2316
|
-
async function handleDevError(server, req, res, next, url, error) {
|
|
2638
|
+
async function handleDevError(server, req, res, next, url, error, base) {
|
|
2317
2639
|
if (error instanceof Error) server.ssrFixStacktrace(error);
|
|
2318
2640
|
if (req.headers["x-pracht-route-state-request"] === "1") {
|
|
2319
2641
|
res.statusCode = 500;
|
|
@@ -2330,7 +2652,8 @@ async function handleDevError(server, req, res, next, url, error) {
|
|
|
2330
2652
|
let html = buildErrorOverlayHtml({
|
|
2331
2653
|
message: error instanceof Error ? error.message : String(error),
|
|
2332
2654
|
stack: error instanceof Error ? error.stack : void 0,
|
|
2333
|
-
root: server.config.root
|
|
2655
|
+
root: server.config.root,
|
|
2656
|
+
base
|
|
2334
2657
|
});
|
|
2335
2658
|
html = await server.transformIndexHtml(url, html);
|
|
2336
2659
|
res.statusCode = 500;
|
|
@@ -2359,11 +2682,12 @@ function isDevNotFoundRequest(requestUrl, req, options = {}) {
|
|
|
2359
2682
|
if (!accept.includes("text/html") && !accept.includes("application/xhtml+xml")) return false;
|
|
2360
2683
|
return !matchesResolvedRoute(url.pathname, options);
|
|
2361
2684
|
}
|
|
2362
|
-
async function serveDevNotFound(server, res, next, url, pathname, options) {
|
|
2685
|
+
async function serveDevNotFound(server, res, next, url, pathname, options, base) {
|
|
2363
2686
|
try {
|
|
2364
2687
|
const { buildDevNotFoundHtml } = await server.ssrLoadModule("@pracht/core/dev-404");
|
|
2365
2688
|
let html = buildDevNotFoundHtml({
|
|
2366
2689
|
apiRoutes: options.apiRoutes.map((route) => ({ path: route.path })),
|
|
2690
|
+
base,
|
|
2367
2691
|
requestedPath: pathname,
|
|
2368
2692
|
routes: options.app.routes.map((route) => ({
|
|
2369
2693
|
path: route.path,
|
|
@@ -2444,6 +2768,8 @@ const DEV_ASSET_EXTENSIONS = new Set([
|
|
|
2444
2768
|
".js",
|
|
2445
2769
|
".json",
|
|
2446
2770
|
".map",
|
|
2771
|
+
".markdown",
|
|
2772
|
+
".md",
|
|
2447
2773
|
".mjs",
|
|
2448
2774
|
".pdf",
|
|
2449
2775
|
".png",
|
|
@@ -2456,10 +2782,11 @@ const DEV_ASSET_EXTENSIONS = new Set([
|
|
|
2456
2782
|
".woff2",
|
|
2457
2783
|
".xml"
|
|
2458
2784
|
]);
|
|
2459
|
-
async function nodeToWebRequest(req, maxBodySize) {
|
|
2785
|
+
async function nodeToWebRequest(req, maxBodySize, base = "/") {
|
|
2460
2786
|
const protocol = "http";
|
|
2461
2787
|
const host = req.headers.host ?? "localhost";
|
|
2462
|
-
const
|
|
2788
|
+
const path = req.url ?? "/";
|
|
2789
|
+
const url = new URL(base === "/" || !path.startsWith("/") ? path : `${base}${path.slice(1)}`, `${protocol}://${host}`);
|
|
2463
2790
|
const method = req.method ?? "GET";
|
|
2464
2791
|
const headers = new Headers();
|
|
2465
2792
|
for (const [key, value] of Object.entries(req.headers)) {
|
|
@@ -2487,14 +2814,34 @@ async function nodeToWebRequest(req, maxBodySize) {
|
|
|
2487
2814
|
}
|
|
2488
2815
|
//#endregion
|
|
2489
2816
|
//#region src/index.ts
|
|
2817
|
+
function reachesHeadBearingModule(modules, serverRoot, headHints) {
|
|
2818
|
+
const pending = [...modules];
|
|
2819
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2820
|
+
while (pending.length > 0) {
|
|
2821
|
+
const module = pending.pop();
|
|
2822
|
+
if (!module || seen.has(module)) continue;
|
|
2823
|
+
seen.add(module);
|
|
2824
|
+
const modulePath = module.file ?? module.id?.split("?", 1)[0];
|
|
2825
|
+
if (modulePath) {
|
|
2826
|
+
const normalizedPath = toPosixPath(modulePath);
|
|
2827
|
+
if (headHints[normalizedPath.startsWith(serverRoot) ? normalizedPath.slice(serverRoot.length) : normalizedPath] === true) return true;
|
|
2828
|
+
}
|
|
2829
|
+
if (module.importers) pending.push(...module.importers);
|
|
2830
|
+
}
|
|
2831
|
+
return false;
|
|
2832
|
+
}
|
|
2490
2833
|
function pracht(options = {}) {
|
|
2491
2834
|
const resolved = resolveOptions(options);
|
|
2492
2835
|
const isPagesMode = !!resolved.pagesDir;
|
|
2493
2836
|
let root = process.cwd();
|
|
2494
2837
|
let routeFileDirs = [];
|
|
2838
|
+
let clientRouteHeadHints = {};
|
|
2839
|
+
const routeFileExtensions = withAdditionalExtensions(DEFAULT_ROUTE_EXTENSIONS, resolved.additionalExtensions);
|
|
2495
2840
|
let capabilityModulePaths = /* @__PURE__ */ new Set();
|
|
2496
2841
|
if (isPagesMode && options.appFile) console.warn("[pracht] Both `pagesDir` and `appFile` are set. `pagesDir` takes precedence — `appFile` will be ignored.");
|
|
2497
2842
|
let isBuild = false;
|
|
2843
|
+
let base = "/";
|
|
2844
|
+
let configuredBase;
|
|
2498
2845
|
const prachtPlugin = {
|
|
2499
2846
|
name: "pracht",
|
|
2500
2847
|
enforce: "pre",
|
|
@@ -2502,13 +2849,20 @@ function pracht(options = {}) {
|
|
|
2502
2849
|
const isEdge = resolved.adapter.edge === true;
|
|
2503
2850
|
const isSSRBuild = env.isSsrBuild;
|
|
2504
2851
|
const configRoot = _config.root ?? process.cwd();
|
|
2505
|
-
const wantsIslandsEntry = env.command === "build" && !isSSRBuild && existsSync(resolveConfigPath(configRoot, resolved.islandsDir));
|
|
2852
|
+
const wantsIslandsEntry = env.command === "build" && !isSSRBuild && (existsSync(resolveConfigPath(configRoot, resolved.islandsDir)) || hasWebmcpCapabilities(resolved, configRoot));
|
|
2506
2853
|
const envDir = _config.envDir ? resolve(configRoot, _config.envDir) : configRoot;
|
|
2507
2854
|
const publicEnvDefine = JSON.stringify(loadEnv(env.mode, envDir, PUBLIC_ENV_PREFIX));
|
|
2855
|
+
const agentSurfaceDefine = env.command === "build" ? String(hasAgentSurface(resolved, configRoot)) : "true";
|
|
2856
|
+
const staticTargetDefine = String(env.command === "build" && resolved.adapter.staticTarget === true);
|
|
2508
2857
|
return {
|
|
2509
2858
|
appType: "custom",
|
|
2510
2859
|
envPrefix: ["VITE_", PUBLIC_ENV_PREFIX],
|
|
2511
|
-
|
|
2860
|
+
resolve: { dedupe: PREACT_DEDUPE },
|
|
2861
|
+
define: {
|
|
2862
|
+
__PRACHT_PUBLIC_ENV__: publicEnvDefine,
|
|
2863
|
+
__PRACHT_AGENT_SURFACE__: agentSurfaceDefine,
|
|
2864
|
+
__PRACHT_STATIC_TARGET__: staticTargetDefine
|
|
2865
|
+
},
|
|
2512
2866
|
...isSSRBuild ? {} : { build: { rollupOptions: {
|
|
2513
2867
|
...wantsIslandsEntry ? { input: [PRACHT_ISLANDS_CLIENT_MODULE_ID] } : {},
|
|
2514
2868
|
output: { manualChunks(id) {
|
|
@@ -2520,13 +2874,25 @@ function pracht(options = {}) {
|
|
|
2520
2874
|
noExternal: true,
|
|
2521
2875
|
target: "webworker"
|
|
2522
2876
|
},
|
|
2877
|
+
environments: { ssr: { resolve: {
|
|
2878
|
+
conditions: [
|
|
2879
|
+
"worker",
|
|
2880
|
+
"module",
|
|
2881
|
+
"browser",
|
|
2882
|
+
"development|production"
|
|
2883
|
+
],
|
|
2884
|
+
external: ["node:module"]
|
|
2885
|
+
} } },
|
|
2523
2886
|
build: { rollupOptions: { external: [/^cloudflare:/] } }
|
|
2524
|
-
} : {}
|
|
2887
|
+
} : {},
|
|
2888
|
+
...!isEdge && isSSRBuild ? { ssr: { noExternal: [PRACHT_SSR_NO_EXTERNAL] } } : {}
|
|
2525
2889
|
};
|
|
2526
2890
|
},
|
|
2527
2891
|
configResolved(config) {
|
|
2892
|
+
assertSafeRootAbsoluteDeployBase(config.base);
|
|
2528
2893
|
root = config.root;
|
|
2529
2894
|
isBuild = config.command === "build";
|
|
2895
|
+
base = config.base;
|
|
2530
2896
|
routeFileDirs = computeRouteFileDirs(root, resolved);
|
|
2531
2897
|
capabilityModulePaths = new Set(resolveCapabilityModulePaths(resolved, root).map(canonicalFilePath));
|
|
2532
2898
|
},
|
|
@@ -2542,11 +2908,19 @@ function pracht(options = {}) {
|
|
|
2542
2908
|
},
|
|
2543
2909
|
load(id) {
|
|
2544
2910
|
if (isIslandsClientModule(id)) return createPrachtIslandsClientModuleSource(resolved, { root });
|
|
2545
|
-
if (isClientModule(id))
|
|
2546
|
-
|
|
2911
|
+
if (isClientModule(id)) {
|
|
2912
|
+
clientRouteHeadHints = createRouteHeadHintsForVirtualModules(resolved, root);
|
|
2913
|
+
return createPrachtClientModuleSource(resolved, { root });
|
|
2914
|
+
}
|
|
2915
|
+
if (isDevModule(id)) return createPrachtDevModuleSource(resolved, {
|
|
2916
|
+
root,
|
|
2917
|
+
base
|
|
2918
|
+
});
|
|
2547
2919
|
if (isServerModule(id)) return createPrachtServerModuleSource(resolved, {
|
|
2548
2920
|
root,
|
|
2549
|
-
isBuild
|
|
2921
|
+
isBuild,
|
|
2922
|
+
base,
|
|
2923
|
+
configuredBase
|
|
2550
2924
|
});
|
|
2551
2925
|
if (isCapabilitiesModule(id)) return createPrachtCapabilitiesClientModuleSource(resolved, { root });
|
|
2552
2926
|
if (isWebmcpModule(id)) return createPrachtWebmcpModuleSource(resolved, { root });
|
|
@@ -2565,6 +2939,7 @@ function pracht(options = {}) {
|
|
|
2565
2939
|
configureServer(server) {
|
|
2566
2940
|
if (isPagesMode) watchPagesDirectory(server, resolved, root);
|
|
2567
2941
|
if (resolved.adapter.ownsDevServer) {
|
|
2942
|
+
server.middlewares.use(createOwnedDevEntryMiddleware(server));
|
|
2568
2943
|
server.middlewares.use(createDevCssInjectionMiddleware(server));
|
|
2569
2944
|
return;
|
|
2570
2945
|
}
|
|
@@ -2583,14 +2958,29 @@ function pracht(options = {}) {
|
|
|
2583
2958
|
return html;
|
|
2584
2959
|
}
|
|
2585
2960
|
},
|
|
2586
|
-
handleHotUpdate({ file, server }) {
|
|
2961
|
+
handleHotUpdate({ file, modules = [], server }) {
|
|
2587
2962
|
const serverRoot = toPosixPath(server.config.root);
|
|
2588
2963
|
const normalizedFile = toPosixPath(file);
|
|
2589
2964
|
const relative = normalizedFile.startsWith(serverRoot) ? normalizedFile.slice(serverRoot.length) : normalizedFile;
|
|
2965
|
+
const changesRouteHeadSource = isPagesMode ? relative.startsWith(resolved.pagesDir) : relative.startsWith(resolved.routesDir) || relative.startsWith(resolved.shellsDir);
|
|
2966
|
+
const changesRouteHeadDependency = reachesHeadBearingModule(modules, serverRoot, clientRouteHeadHints);
|
|
2967
|
+
let shouldReloadClientHead = changesRouteHeadDependency;
|
|
2968
|
+
let clientHeadModule;
|
|
2969
|
+
if (changesRouteHeadSource || changesRouteHeadDependency) clientHeadModule = server.moduleGraph.getModuleById(PRACHT_CLIENT_MODULE_ID);
|
|
2970
|
+
if (changesRouteHeadSource) {
|
|
2971
|
+
const previousHint = clientRouteHeadHints[relative];
|
|
2972
|
+
try {
|
|
2973
|
+
const nextHints = createRouteHeadHintsForVirtualModules(resolved, root);
|
|
2974
|
+
shouldReloadClientHead ||= previousHint === true || nextHints[relative] === true;
|
|
2975
|
+
clientRouteHeadHints = nextHints;
|
|
2976
|
+
} catch {
|
|
2977
|
+
shouldReloadClientHead = true;
|
|
2978
|
+
}
|
|
2979
|
+
} else if (changesRouteHeadDependency && clientHeadModule) server.moduleGraph.invalidateModule(clientHeadModule);
|
|
2590
2980
|
if (isPagesMode && relative.startsWith(resolved.pagesDir)) {
|
|
2591
2981
|
clearPagesAppSourceCache();
|
|
2592
2982
|
invalidateVirtualModules(server);
|
|
2593
|
-
sendServerOnlyFullReload(server, file);
|
|
2983
|
+
if (!sendServerOnlyFullReload(server, file) && shouldReloadClientHead && clientHeadModule) return [...new Set([...modules, clientHeadModule])];
|
|
2594
2984
|
return;
|
|
2595
2985
|
}
|
|
2596
2986
|
if (!isPagesMode && relative === resolved.appFile) {
|
|
@@ -2610,7 +3000,7 @@ function pracht(options = {}) {
|
|
|
2610
3000
|
if (serverMod) server.moduleGraph.invalidateModule(serverMod);
|
|
2611
3001
|
const devMod = server.moduleGraph.getModuleById(PRACHT_DEV_MODULE_ID);
|
|
2612
3002
|
if (devMod) server.moduleGraph.invalidateModule(devMod);
|
|
2613
|
-
if (relative.startsWith(resolved.routesDir)) {
|
|
3003
|
+
if (relative.startsWith(resolved.routesDir) || relative.startsWith(resolved.shellsDir)) {
|
|
2614
3004
|
const clientMod = server.moduleGraph.getModuleById(PRACHT_CLIENT_MODULE_ID);
|
|
2615
3005
|
if (clientMod) server.moduleGraph.invalidateModule(clientMod);
|
|
2616
3006
|
}
|
|
@@ -2628,7 +3018,16 @@ function pracht(options = {}) {
|
|
|
2628
3018
|
if (capabilityMod) server.moduleGraph.invalidateModule(capabilityMod);
|
|
2629
3019
|
}
|
|
2630
3020
|
}
|
|
2631
|
-
sendServerOnlyFullReload(server, file);
|
|
3021
|
+
if (!sendServerOnlyFullReload(server, file) && shouldReloadClientHead && clientHeadModule) return [...new Set([...modules, clientHeadModule])];
|
|
3022
|
+
}
|
|
3023
|
+
};
|
|
3024
|
+
const configuredBasePlugin = {
|
|
3025
|
+
name: "pracht:configured-base",
|
|
3026
|
+
config: {
|
|
3027
|
+
order: "post",
|
|
3028
|
+
handler(config) {
|
|
3029
|
+
configuredBase = typeof config.base === "string" ? config.base : void 0;
|
|
3030
|
+
}
|
|
2632
3031
|
}
|
|
2633
3032
|
};
|
|
2634
3033
|
const clientModuleTransformPlugin = {
|
|
@@ -2636,7 +3035,7 @@ function pracht(options = {}) {
|
|
|
2636
3035
|
enforce: "post",
|
|
2637
3036
|
transform(code, id, transformOptions) {
|
|
2638
3037
|
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.`);
|
|
2639
|
-
if (!(isPrachtClientModuleId(id) || !transformOptions?.ssr && isRouteOrShellFile(id, routeFileDirs))) return null;
|
|
3038
|
+
if (!(isPrachtClientModuleId(id) || !transformOptions?.ssr && isRouteOrShellFile(id, routeFileDirs, routeFileExtensions))) return null;
|
|
2640
3039
|
const transformed = stripServerOnlyExportsForClient(code, id);
|
|
2641
3040
|
if (transformed === code) return null;
|
|
2642
3041
|
return {
|
|
@@ -2645,11 +3044,12 @@ function pracht(options = {}) {
|
|
|
2645
3044
|
};
|
|
2646
3045
|
}
|
|
2647
3046
|
};
|
|
3047
|
+
const edgeRuntimeSafetyPlugin = resolved.adapter.edge ? createEdgeRuntimeSafetyPlugin() : null;
|
|
2648
3048
|
const optimizeDepsEntriesPlugin = {
|
|
2649
3049
|
name: "pracht:optimize-deps-entries",
|
|
2650
3050
|
enforce: "post",
|
|
2651
3051
|
config(config) {
|
|
2652
|
-
return withPrachtOptimizeDepsEntries(config,
|
|
3052
|
+
return withPrachtOptimizeDepsEntries(config, resolved, createPrachtOptimizeDepsInclude(config.root ?? process.cwd()));
|
|
2653
3053
|
}
|
|
2654
3054
|
};
|
|
2655
3055
|
const precompilePlugin = resolved.precompileSsrJsx ? preactSsrPrecompile({
|
|
@@ -2660,14 +3060,99 @@ function pracht(options = {}) {
|
|
|
2660
3060
|
...precompilePlugin ? [precompilePlugin] : [],
|
|
2661
3061
|
...preact(),
|
|
2662
3062
|
prachtPlugin,
|
|
3063
|
+
configuredBasePlugin,
|
|
2663
3064
|
clientModuleTransformPlugin,
|
|
3065
|
+
...edgeRuntimeSafetyPlugin ? [edgeRuntimeSafetyPlugin] : [],
|
|
2664
3066
|
createEnvSafetyPlugin(resolved.envSafety)
|
|
2665
3067
|
];
|
|
2666
|
-
const adapterPlugins = resolved.adapter.vitePlugins?.();
|
|
3068
|
+
const adapterPlugins = isGraphOnlyMode() ? resolved.adapter.graphVitePlugins?.() : resolved.adapter.vitePlugins?.();
|
|
2667
3069
|
if (adapterPlugins?.length) plugins.push(...adapterPlugins);
|
|
2668
3070
|
plugins.push(optimizeDepsEntriesPlugin);
|
|
2669
3071
|
return plugins;
|
|
2670
3072
|
}
|
|
3073
|
+
function assertSafeRootAbsoluteDeployBase(base) {
|
|
3074
|
+
if (typeof base !== "string" || !base.startsWith("/") || base.startsWith("//")) return;
|
|
3075
|
+
let safe = !base.includes("?") && !base.includes("#");
|
|
3076
|
+
if (safe) try {
|
|
3077
|
+
const segments = base.split("/");
|
|
3078
|
+
safe = segments.every((segment, index) => {
|
|
3079
|
+
if (segment === "" && index !== 0 && index !== segments.length - 1) return false;
|
|
3080
|
+
const decoded = decodeURIComponent(segment);
|
|
3081
|
+
if (decoded === "." || decoded === "..") return false;
|
|
3082
|
+
for (const character of decoded) {
|
|
3083
|
+
const codePoint = character.codePointAt(0);
|
|
3084
|
+
if (character === "/" || character === "\\" || codePoint === 0 || codePoint !== void 0 && (codePoint <= 31 || codePoint === 127)) return false;
|
|
3085
|
+
}
|
|
3086
|
+
return true;
|
|
3087
|
+
});
|
|
3088
|
+
} catch {
|
|
3089
|
+
safe = false;
|
|
3090
|
+
}
|
|
3091
|
+
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.`);
|
|
3092
|
+
}
|
|
3093
|
+
function isGraphOnlyMode() {
|
|
3094
|
+
return process.env[PRACHT_GRAPH_ONLY_ENV] === "1";
|
|
3095
|
+
}
|
|
3096
|
+
function createEdgeRuntimeSafetyPlugin() {
|
|
3097
|
+
let isSsrBuild = false;
|
|
3098
|
+
return {
|
|
3099
|
+
name: "pracht:edge-runtime-safety",
|
|
3100
|
+
apply: "build",
|
|
3101
|
+
enforce: "post",
|
|
3102
|
+
configResolved(config) {
|
|
3103
|
+
isSsrBuild = !!config.build.ssr;
|
|
3104
|
+
},
|
|
3105
|
+
generateBundle(_options, bundle) {
|
|
3106
|
+
const consumer = this.environment?.config?.consumer;
|
|
3107
|
+
if (!(consumer ? consumer === "server" : isSsrBuild)) return;
|
|
3108
|
+
const survivors = [];
|
|
3109
|
+
for (const [fileName, output] of Object.entries(bundle)) {
|
|
3110
|
+
if (output.type !== "chunk") continue;
|
|
3111
|
+
for (const specifier of collectNodeBuiltinImports(this.parse(output.code))) survivors.push({
|
|
3112
|
+
chunk: fileName,
|
|
3113
|
+
specifier
|
|
3114
|
+
});
|
|
3115
|
+
}
|
|
3116
|
+
if (survivors.length === 0) return;
|
|
3117
|
+
this.error([
|
|
3118
|
+
"[pracht] Edge server bundle retains Node.js builtin imports that are unavailable at runtime:",
|
|
3119
|
+
...survivors.map(({ chunk, specifier }) => ` - ${specifier} in ${chunk}`),
|
|
3120
|
+
"Remove the Node-only dependency or move that route to a Node deployment target."
|
|
3121
|
+
].join("\n"));
|
|
3122
|
+
}
|
|
3123
|
+
};
|
|
3124
|
+
}
|
|
3125
|
+
function collectNodeBuiltinImports(program) {
|
|
3126
|
+
const imports = /* @__PURE__ */ new Set();
|
|
3127
|
+
function sourceValue(node) {
|
|
3128
|
+
if (!node || typeof node !== "object" || !("value" in node)) return null;
|
|
3129
|
+
return typeof node.value === "string" ? node.value : null;
|
|
3130
|
+
}
|
|
3131
|
+
function visit(node) {
|
|
3132
|
+
if (Array.isArray(node)) {
|
|
3133
|
+
for (const item of node) visit(item);
|
|
3134
|
+
return;
|
|
3135
|
+
}
|
|
3136
|
+
if (!node || typeof node !== "object") return;
|
|
3137
|
+
const record = node;
|
|
3138
|
+
const type = record.type;
|
|
3139
|
+
if (type === "ImportDeclaration" || type === "ExportAllDeclaration" || type === "ExportNamedDeclaration" || type === "ImportExpression") {
|
|
3140
|
+
const specifier = sourceValue(record.source);
|
|
3141
|
+
if (specifier && isBuiltin(specifier)) imports.add(specifier);
|
|
3142
|
+
} else if (type === "CallExpression") {
|
|
3143
|
+
const callee = record.callee;
|
|
3144
|
+
const isImport = callee?.type === "Import";
|
|
3145
|
+
const isRequire = callee?.type === "Identifier" && callee.name === "require";
|
|
3146
|
+
if (isImport || isRequire) {
|
|
3147
|
+
const specifier = sourceValue(record.arguments?.[0]);
|
|
3148
|
+
if (specifier && isBuiltin(specifier)) imports.add(specifier);
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
for (const value of Object.values(record)) visit(value);
|
|
3152
|
+
}
|
|
3153
|
+
visit(program);
|
|
3154
|
+
return imports;
|
|
3155
|
+
}
|
|
2671
3156
|
const MANIFEST_CORE_IMPORTS = new Set([
|
|
2672
3157
|
"defineApp",
|
|
2673
3158
|
"group",
|
|
@@ -2687,6 +3172,8 @@ const PRACHT_OPTIMIZE_DEPS_INCLUDE = [
|
|
|
2687
3172
|
"@pracht/core/islands-client",
|
|
2688
3173
|
"@pracht/core/manifest"
|
|
2689
3174
|
];
|
|
3175
|
+
const PREACT_DEDUPE = ["preact", "preact-render-to-string"];
|
|
3176
|
+
const PRACHT_SSR_NO_EXTERNAL = /^@pracht\//;
|
|
2690
3177
|
function createPrachtOptimizeDepsInclude(root) {
|
|
2691
3178
|
try {
|
|
2692
3179
|
if (!toPosixPath(createRequire(join(root, "package.json")).resolve("@pracht/core/package.json")).includes("/node_modules/")) return [];
|
|
@@ -2695,8 +3182,9 @@ function createPrachtOptimizeDepsInclude(root) {
|
|
|
2695
3182
|
return [];
|
|
2696
3183
|
}
|
|
2697
3184
|
}
|
|
2698
|
-
function withPrachtOptimizeDepsEntries(config,
|
|
2699
|
-
const
|
|
3185
|
+
function withPrachtOptimizeDepsEntries(config, resolved, prachtInclude) {
|
|
3186
|
+
const prachtEntries = createPrachtOptimizeDepsEntries(resolved, config.optimizeDeps?.extensions);
|
|
3187
|
+
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)) } }]));
|
|
2700
3188
|
return {
|
|
2701
3189
|
optimizeDeps: {
|
|
2702
3190
|
entries: mergeOptimizeDepsEntries(config.optimizeDeps?.entries, prachtEntries),
|
|
@@ -2705,13 +3193,30 @@ function withPrachtOptimizeDepsEntries(config, prachtEntries, prachtInclude) {
|
|
|
2705
3193
|
...Object.keys(environments).length > 0 ? { environments } : {}
|
|
2706
3194
|
};
|
|
2707
3195
|
}
|
|
2708
|
-
|
|
3196
|
+
const VITE_SCANNABLE_ROUTE_EXTENSIONS = new Set([
|
|
3197
|
+
".ts",
|
|
3198
|
+
".tsx",
|
|
3199
|
+
".js",
|
|
3200
|
+
".jsx",
|
|
3201
|
+
".mts",
|
|
3202
|
+
".mjs",
|
|
3203
|
+
".cts",
|
|
3204
|
+
".cjs",
|
|
3205
|
+
".vue",
|
|
3206
|
+
".svelte",
|
|
3207
|
+
".astro",
|
|
3208
|
+
".imba"
|
|
3209
|
+
]);
|
|
3210
|
+
function createPrachtOptimizeDepsEntries(resolved, optimizerExtensions) {
|
|
2709
3211
|
const scriptExtensions = "{ts,tsx,js,jsx}";
|
|
2710
|
-
const
|
|
3212
|
+
const explicitlyScannable = new Set(optimizerExtensions ?? []);
|
|
3213
|
+
const routeExtensions = extensionGlob([...new Set([...DEFAULT_ROUTE_EXTENSIONS, ...resolved.additionalExtensions])].filter((extension) => VITE_SCANNABLE_ROUTE_EXTENSIONS.has(extension) || explicitlyScannable.has(extension)));
|
|
3214
|
+
const apiDir = toOptimizeDepsEntry(resolved.apiDir);
|
|
3215
|
+
const apiEntries = [`${apiDir}/**/*.{ts,js,tsx,jsx}`, `!${apiDir}/**/*.d.ts`];
|
|
2711
3216
|
const entries = resolved.pagesDir ? [
|
|
2712
3217
|
`${toOptimizeDepsEntry(resolved.pagesDir)}/**/*.${routeExtensions}`,
|
|
2713
3218
|
`${toOptimizeDepsEntry(resolved.middlewareDir)}/**/*.${scriptExtensions}`,
|
|
2714
|
-
|
|
3219
|
+
...apiEntries,
|
|
2715
3220
|
`${toOptimizeDepsEntry(resolved.serverDir)}/**/*.{ts,js,tsx,jsx}`,
|
|
2716
3221
|
`${toOptimizeDepsEntry(resolved.islandsDir)}/**/*.${scriptExtensions}`
|
|
2717
3222
|
] : [
|
|
@@ -2719,7 +3224,7 @@ function createPrachtOptimizeDepsEntries(resolved) {
|
|
|
2719
3224
|
`${toOptimizeDepsEntry(resolved.routesDir)}/**/*.${routeExtensions}`,
|
|
2720
3225
|
`${toOptimizeDepsEntry(resolved.shellsDir)}/**/*.${routeExtensions}`,
|
|
2721
3226
|
`${toOptimizeDepsEntry(resolved.middlewareDir)}/**/*.${scriptExtensions}`,
|
|
2722
|
-
|
|
3227
|
+
...apiEntries,
|
|
2723
3228
|
`${toOptimizeDepsEntry(resolved.serverDir)}/**/*.{ts,js,tsx,jsx}`,
|
|
2724
3229
|
`${toOptimizeDepsEntry(resolved.islandsDir)}/**/*.${scriptExtensions}`,
|
|
2725
3230
|
`${toOptimizeDepsEntry(resolved.capabilitiesDir)}/**/*.{ts,js,tsx,jsx}`
|
|
@@ -2755,15 +3260,6 @@ function invalidateVirtualModules(server) {
|
|
|
2755
3260
|
if (serverMod) server.moduleGraph.invalidateModule(serverMod);
|
|
2756
3261
|
if (devMod) server.moduleGraph.invalidateModule(devMod);
|
|
2757
3262
|
}
|
|
2758
|
-
const ROUTE_FILE_EXTENSIONS = new Set([
|
|
2759
|
-
".ts",
|
|
2760
|
-
".tsx",
|
|
2761
|
-
".js",
|
|
2762
|
-
".jsx",
|
|
2763
|
-
".md",
|
|
2764
|
-
".mdx",
|
|
2765
|
-
".tsrx"
|
|
2766
|
-
]);
|
|
2767
3263
|
function computeRouteFileDirs(root, resolved) {
|
|
2768
3264
|
return (resolved.pagesDir ? [resolved.pagesDir] : [resolved.routesDir, resolved.shellsDir]).map((dir) => canonicalFilePath(resolveConfigPath(root, dir))).map(withTrailingSep);
|
|
2769
3265
|
}
|
|
@@ -2794,7 +3290,7 @@ function canonicalFilePath(path) {
|
|
|
2794
3290
|
return toPosixPath(path);
|
|
2795
3291
|
}
|
|
2796
3292
|
}
|
|
2797
|
-
function isRouteOrShellFile(id, dirs) {
|
|
3293
|
+
function isRouteOrShellFile(id, dirs, extensions) {
|
|
2798
3294
|
if (dirs.length === 0) return false;
|
|
2799
3295
|
const queryStart = id.indexOf("?");
|
|
2800
3296
|
const path = queryStart === -1 ? id : id.slice(0, queryStart);
|
|
@@ -2802,7 +3298,7 @@ function isRouteOrShellFile(id, dirs) {
|
|
|
2802
3298
|
const extIndex = path.lastIndexOf(".");
|
|
2803
3299
|
if (extIndex === -1) return false;
|
|
2804
3300
|
const ext = path.slice(extIndex);
|
|
2805
|
-
if (!
|
|
3301
|
+
if (!extensions.has(ext)) return false;
|
|
2806
3302
|
const normalized = toPosixPath(path);
|
|
2807
3303
|
return dirs.some((dir) => normalized.startsWith(dir));
|
|
2808
3304
|
}
|