@timber-js/app 0.2.0-alpha.167 → 0.2.0-alpha.169
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks/{actions-TSxpXLHJ.js → actions-O_LsyCE4.js} +3 -3
- package/dist/_chunks/{actions-TSxpXLHJ.js.map → actions-O_LsyCE4.js.map} +1 -1
- package/dist/_chunks/{cache-api-DzpQQOEx.js → cache-api-B-lhk9p4.js} +56 -11
- package/dist/_chunks/cache-api-B-lhk9p4.js.map +1 -0
- package/dist/_chunks/{cli-schema-sync-wX-i90Og.js → cli-schema-sync-B73L6pMq.js} +2 -2
- package/dist/_chunks/{cli-schema-sync-wX-i90Og.js.map → cli-schema-sync-B73L6pMq.js.map} +1 -1
- package/dist/_chunks/json-lossy-check-ClNvBM_3.js +63 -0
- package/dist/_chunks/json-lossy-check-ClNvBM_3.js.map +1 -0
- package/dist/_chunks/{logger-t3uxAmbX.js → logger-AWfuX-KJ.js} +2 -19
- package/dist/_chunks/logger-AWfuX-KJ.js.map +1 -0
- package/dist/_chunks/{walkers-Cfwvl-UC.js → walkers-CoOC8Hga.js} +2 -2
- package/dist/_chunks/{walkers-Cfwvl-UC.js.map → walkers-CoOC8Hga.js.map} +1 -1
- package/dist/adapters/cloudflare-kv-cache.d.ts.map +1 -1
- package/dist/adapters/cloudflare-kv-cache.js +9 -2
- package/dist/adapters/cloudflare-kv-cache.js.map +1 -1
- package/dist/cache/cache-api.d.ts.map +1 -1
- package/dist/cache/index.d.ts +1 -1
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +1 -1
- package/dist/cache/json-lossy-check.d.ts +11 -0
- package/dist/cache/json-lossy-check.d.ts.map +1 -0
- package/dist/cache/redis-handler.d.ts +42 -0
- package/dist/cache/redis-handler.d.ts.map +1 -1
- package/dist/cache/singleflight.d.ts.map +1 -1
- package/dist/cache/stores/cloudflare-kv.d.ts +1 -1
- package/dist/cache/stores/cloudflare-kv.d.ts.map +1 -1
- package/dist/cache/stores/memory.d.ts +1 -1
- package/dist/cache/stores/memory.d.ts.map +1 -1
- package/dist/cache/stores/redis.d.ts +1 -1
- package/dist/cache/stores/redis.d.ts.map +1 -1
- package/dist/cache/stores/vercel.d.ts +1 -1
- package/dist/cache/stores/vercel.d.ts.map +1 -1
- package/dist/cache/tag-aware-handler.d.ts.map +1 -1
- package/dist/cache/timber-cache.d.ts.map +1 -1
- package/dist/cdn/cloudflare-purge.d.ts.map +1 -1
- package/dist/cdn/fastly-purge.d.ts.map +1 -1
- package/dist/cdn/workers-cache-purge.d.ts.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3 -2
- package/dist/cli.js.map +1 -1
- package/dist/client/browser-entry/router-init.d.ts +1 -0
- package/dist/client/browser-entry/router-init.d.ts.map +1 -1
- package/dist/client/child-segment-context.d.ts +2 -2
- package/dist/client/child-segment-context.d.ts.map +1 -1
- package/dist/client/error-boundary.d.ts.map +1 -1
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/internal.js +4 -3
- package/dist/client/internal.js.map +1 -1
- package/dist/client/router.d.ts +6 -0
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/segment-update-context.d.ts +3 -3
- package/dist/client/segment-update-context.d.ts.map +1 -1
- package/dist/client/slot-outlet.d.ts +1 -1
- package/dist/codec.d.ts.map +1 -1
- package/dist/codec.js +2 -1
- package/dist/codec.js.map +1 -1
- package/dist/config-types.d.ts +16 -37
- package/dist/config-types.d.ts.map +1 -1
- package/dist/config-validation.d.ts.map +1 -1
- package/dist/dev-tools/instrumentation.d.ts.map +1 -1
- package/dist/fonts/pipeline.d.ts +19 -0
- package/dist/fonts/pipeline.d.ts.map +1 -1
- package/dist/fonts/transform.d.ts.map +1 -1
- package/dist/fonts/virtual-modules.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +344 -84
- package/dist/index.js.map +1 -1
- package/dist/plugin-context.d.ts.map +1 -1
- package/dist/plugins/adapter-build.d.ts +1 -0
- package/dist/plugins/adapter-build.d.ts.map +1 -1
- package/dist/plugins/entries.d.ts +27 -3
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/server-bundle.d.ts.map +1 -1
- package/dist/routing/index.js +2 -2
- package/dist/server/action-client.d.ts +1 -1
- package/dist/server/action-client.d.ts.map +1 -1
- package/dist/server/body-limits.d.ts.map +1 -1
- package/dist/server/form-data.d.ts.map +1 -1
- package/dist/server/html-injector-core.d.ts.map +1 -1
- package/dist/server/index.js +20 -3
- package/dist/server/index.js.map +1 -1
- package/dist/server/internal.js +24 -40
- package/dist/server/internal.js.map +1 -1
- package/dist/server/middleware-runner.d.ts +0 -17
- package/dist/server/middleware-runner.d.ts.map +1 -1
- package/dist/server/param-coercion.d.ts.map +1 -1
- package/dist/server/pipeline-phases.d.ts +6 -3
- package/dist/server/pipeline-phases.d.ts.map +1 -1
- package/dist/server/pipeline.d.ts +18 -0
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/prebuilt/capture-state.d.ts.map +1 -1
- package/dist/server/prebuilt/synthetic-store.d.ts.map +1 -1
- package/dist/server/primitives.d.ts.map +1 -1
- package/dist/server/render-timeout.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/rsc-entry/action-middleware-runner.d.ts +14 -14
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-route.d.ts +1 -0
- package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
- package/dist/server/rsc-entry/revalidate-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts +44 -74
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts.map +1 -1
- package/dist/server/safe-load.d.ts.map +1 -1
- package/dist/server/ssr-entry.d.ts.map +1 -1
- package/dist/shared/redirect-type.d.ts +2 -2
- package/dist/shared/redirect-type.d.ts.map +1 -1
- package/dist/shims/font-google.d.ts.map +1 -1
- package/dist/shims/image.d.ts +120 -120
- package/dist/shims/image.d.ts.map +1 -1
- package/docs/api/32-api-cache.mdx +112 -0
- package/docs/api/34-api-config.mdx +0 -26
- package/docs/learn/09-caching.mdx +121 -5
- package/docs/learn/12-client-navigation.mdx +9 -1
- package/docs/learn/13-configuration.mdx +6 -8
- package/docs/learn/14-deploying.mdx +18 -18
- package/docs/more/04-metadata-and-fonts.mdx +1 -1
- package/package.json +6 -6
- package/src/adapters/cloudflare-kv-cache.ts +27 -6
- package/src/cache/index.ts +1 -1
- package/src/cache/json-lossy-check.ts +75 -0
- package/src/cache/redis-handler.ts +65 -14
- package/src/cache/timber-cache.ts +10 -2
- package/src/client/browser-entry/index.ts +2 -0
- package/src/client/browser-entry/router-init.ts +2 -0
- package/src/client/router.ts +13 -3
- package/src/codec.ts +3 -1
- package/src/config-types.ts +16 -37
- package/src/config-validation.ts +7 -5
- package/src/fonts/pipeline.ts +32 -0
- package/src/fonts/transform.ts +30 -24
- package/src/plugins/adapter-build.ts +131 -13
- package/src/plugins/entries.ts +40 -43
- package/src/plugins/fonts.ts +30 -0
- package/src/plugins/server-bundle.ts +7 -15
- package/src/server/action-client.ts +1 -1
- package/src/server/middleware-runner.ts +0 -44
- package/src/server/param-coercion.ts +1 -0
- package/src/server/pipeline-phases.ts +11 -13
- package/src/server/pipeline.ts +51 -3
- package/src/server/route-element-builder.ts +10 -1
- package/src/server/rsc-entry/action-middleware-runner.ts +14 -14
- package/src/server/rsc-entry/index.ts +30 -40
- package/src/server/rsc-entry/render-route.ts +3 -1
- package/src/server/rsc-entry/wrap-action-dispatch.ts +109 -372
- package/dist/_chunks/cache-api-DzpQQOEx.js.map +0 -1
- package/dist/_chunks/logger-t3uxAmbX.js.map +0 -1
- package/dist/_chunks/tree-match-D2l830j2.js +0 -102
- package/dist/_chunks/tree-match-D2l830j2.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { a as __esmMin, o as __exportAll, s as __toCommonJS } from "./_chunks/dist-BA3u1z3W.js";
|
|
2
2
|
import { c as isDynamicMetadataExtension, l as isMetadataRouteServePath, r as canonicalize } from "./_chunks/canonicalize-P41GR6tY.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { i as
|
|
6
|
-
import { a as generateRouteMap, c as fileHasDirective, d as getPrerenderExport, i as validateSchemaAgainstRoutes, l as fileHasExport, n as collectInterceptionRewrites, o as fileHasAnyExport, s as fileHasDefaultExport, t as collectLeafRoutes, u as fileHasStarExport } from "./_chunks/walkers-Cfwvl-UC.js";
|
|
3
|
+
import { h as swallow } from "./_chunks/logger-AWfuX-KJ.js";
|
|
4
|
+
import { i as scanRoutes } from "./_chunks/cli-schema-sync-B73L6pMq.js";
|
|
5
|
+
import { a as generateRouteMap, c as fileHasDirective, d as getPrerenderExport, i as validateSchemaAgainstRoutes, l as fileHasExport, n as collectInterceptionRewrites, o as fileHasAnyExport, s as fileHasDefaultExport, t as collectLeafRoutes, u as fileHasStarExport } from "./_chunks/walkers-CoOC8Hga.js";
|
|
7
6
|
import { a as resolveAppDir, c as createNoopTimer, n as loadTimberConfigFile, o as resolveBuildDir, r as mergeFileConfig, s as resolveClientJavascript, t as createPluginContext } from "./_chunks/plugin-context---kTF5v8.js";
|
|
8
7
|
import { t as fnv1aHash } from "./_chunks/fast-hash-D6hIVt1Y.js";
|
|
9
8
|
import { t as formatSize } from "./_chunks/format-CfwjgPz9.js";
|
|
@@ -107,11 +106,18 @@ function timberContent(ctx) {
|
|
|
107
106
|
};
|
|
108
107
|
}
|
|
109
108
|
//#endregion
|
|
110
|
-
//#region ../../node_modules/.pnpm/srvx@0.11.
|
|
111
|
-
|
|
109
|
+
//#region ../../node_modules/.pnpm/srvx@0.11.22/node_modules/srvx/dist/adapters/node.mjs
|
|
110
|
+
function sendNodeResponse(nodeRes, webRes) {
|
|
111
|
+
try {
|
|
112
|
+
return _sendNodeResponse(nodeRes, webRes, false) || Promise.resolve();
|
|
113
|
+
} catch (error) {
|
|
114
|
+
return Promise.reject(error);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function _sendNodeResponse(nodeRes, webRes, detached) {
|
|
112
118
|
if (!webRes) {
|
|
113
119
|
nodeRes.statusCode = 500;
|
|
114
|
-
return endNodeResponse(nodeRes);
|
|
120
|
+
return endNodeResponse(nodeRes, detached);
|
|
115
121
|
}
|
|
116
122
|
if (webRes._toNodeResponse) {
|
|
117
123
|
const res = webRes._toNodeResponse();
|
|
@@ -123,18 +129,22 @@ async function sendNodeResponse(nodeRes, webRes) {
|
|
|
123
129
|
writeHead(nodeRes, res.status, res.statusText, res.headers);
|
|
124
130
|
nodeRes.write(res.body);
|
|
125
131
|
} else writeHead(nodeRes, res.status, res.statusText, res.headers);
|
|
126
|
-
return endNodeResponse(nodeRes);
|
|
132
|
+
return endNodeResponse(nodeRes, detached);
|
|
127
133
|
}
|
|
128
|
-
const rawHeaders = [
|
|
134
|
+
const rawHeaders = [];
|
|
135
|
+
for (const [key, value] of webRes.headers) rawHeaders.push(key, value);
|
|
129
136
|
writeHead(nodeRes, webRes.status, webRes.statusText, rawHeaders);
|
|
130
|
-
return webRes.body ? streamBody(webRes.body, nodeRes) : endNodeResponse(nodeRes);
|
|
137
|
+
return webRes.body ? streamBody(webRes.body, nodeRes) : endNodeResponse(nodeRes, detached);
|
|
131
138
|
}
|
|
132
139
|
function writeHead(nodeRes, status, statusText, rawHeaders) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
else nodeRes.writeHead(status, statusText, writeHeaders);
|
|
140
|
+
if (!nodeRes.headersSent) if (nodeRes.req?.httpVersion === "2.0") nodeRes.writeHead(status, rawHeaders);
|
|
141
|
+
else nodeRes.writeHead(status, statusText, rawHeaders);
|
|
136
142
|
}
|
|
137
|
-
function endNodeResponse(nodeRes) {
|
|
143
|
+
function endNodeResponse(nodeRes, detached) {
|
|
144
|
+
if (detached) {
|
|
145
|
+
nodeRes.end();
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
138
148
|
return new Promise((resolve) => nodeRes.end(resolve));
|
|
139
149
|
}
|
|
140
150
|
function pipeBody(stream, nodeRes, status, statusText, headers) {
|
|
@@ -1227,10 +1237,11 @@ function validateConfig(config) {
|
|
|
1227
1237
|
suggestion: "Example: budget: { firstLoadJs: 150_000 } for a ~150 kB limit."
|
|
1228
1238
|
});
|
|
1229
1239
|
}
|
|
1240
|
+
const MOVED_TO_CACHE_FILE = /* @__PURE__ */ new Set(["cacheHandler", "cdnPurge"]);
|
|
1230
1241
|
for (const key of Object.keys(config)) if (!knownKeys.has(key)) errors.push({
|
|
1231
1242
|
field: key,
|
|
1232
1243
|
message: `Unknown config option: "${key}".`,
|
|
1233
|
-
suggestion: `Check for typos. Known options: ${[...knownKeys].sort().join(", ")}.`
|
|
1244
|
+
suggestion: MOVED_TO_CACHE_FILE.has(key) ? `"${key}" has moved to timber.cache.ts. Export cacheHandler as default, cdnPurge as a named export.` : `Check for typos. Known options: ${[...knownKeys].sort().join(", ")}.`
|
|
1234
1245
|
});
|
|
1235
1246
|
return errors;
|
|
1236
1247
|
}
|
|
@@ -1304,12 +1315,9 @@ var init_config_validation = __esmMin((() => {
|
|
|
1304
1315
|
debug: true,
|
|
1305
1316
|
clientJavascript: true,
|
|
1306
1317
|
adapter: true,
|
|
1307
|
-
cacheHandler: true,
|
|
1308
|
-
cdnPurge: true,
|
|
1309
1318
|
allowedOrigins: true,
|
|
1310
1319
|
csrf: true,
|
|
1311
1320
|
limits: true,
|
|
1312
|
-
actions: true,
|
|
1313
1321
|
forms: true,
|
|
1314
1322
|
pageExtensions: true,
|
|
1315
1323
|
slowRequestMs: true,
|
|
@@ -1323,6 +1331,7 @@ var init_config_validation = __esmMin((() => {
|
|
|
1323
1331
|
reactCompiler: true,
|
|
1324
1332
|
sitemap: true,
|
|
1325
1333
|
buildDir: true,
|
|
1334
|
+
clientSegmentCache: true,
|
|
1326
1335
|
topLoader: true,
|
|
1327
1336
|
budget: true
|
|
1328
1337
|
}));
|
|
@@ -1337,7 +1346,7 @@ var init_config_validation = __esmMin((() => {
|
|
|
1337
1346
|
"virtual:timber-config": "Runtime config (timber.config.ts)",
|
|
1338
1347
|
"virtual:timber-route-manifest": "Route manifest (app/ file tree)",
|
|
1339
1348
|
"virtual:timber-instrumentation": "Instrumentation (instrumentation.ts)",
|
|
1340
|
-
"virtual:timber-cache-handler": "Cache handler (
|
|
1349
|
+
"virtual:timber-cache-handler": "Cache handler (timber.cache.ts)",
|
|
1341
1350
|
"virtual:timber-build-manifest": "Build manifest (asset mapping)"
|
|
1342
1351
|
};
|
|
1343
1352
|
REQUIRED_PEERS = [
|
|
@@ -1466,6 +1475,105 @@ function compressWithGzip(body) {
|
|
|
1466
1475
|
return Readable.toWeb(gzip);
|
|
1467
1476
|
}
|
|
1468
1477
|
//#endregion
|
|
1478
|
+
//#region src/server/tree-match.ts
|
|
1479
|
+
/**
|
|
1480
|
+
* Match URL path parts against a segment tree.
|
|
1481
|
+
*
|
|
1482
|
+
* Walks the tree depth-first with backtracking, applying priority
|
|
1483
|
+
* ordering globally across group boundaries. Returns the matched
|
|
1484
|
+
* segment chain and collected params, or null if no route matches.
|
|
1485
|
+
*
|
|
1486
|
+
* By default, a match requires a terminal node (one with `page` or
|
|
1487
|
+
* `route`) at the leaf. Pass `{ requireTerminal: false }` to match
|
|
1488
|
+
* any segment (used by metadata route matching).
|
|
1489
|
+
*
|
|
1490
|
+
* @param root The root segment node to start matching from.
|
|
1491
|
+
* @param parts URL path parts (e.g. ['blog', 'hello-world']).
|
|
1492
|
+
* Empty array matches the root itself (if terminal).
|
|
1493
|
+
*/
|
|
1494
|
+
function matchUrlParts(root, parts, options) {
|
|
1495
|
+
const chain = [];
|
|
1496
|
+
const params = {};
|
|
1497
|
+
if (matchNode(root, parts, 0, chain, params, options?.requireTerminal ?? true)) return {
|
|
1498
|
+
chain,
|
|
1499
|
+
params
|
|
1500
|
+
};
|
|
1501
|
+
return null;
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1504
|
+
* Flatten children through group segments.
|
|
1505
|
+
*
|
|
1506
|
+
* Groups are transparent for URL matching — their non-group descendants
|
|
1507
|
+
* are returned with the chain of group nodes leading to them. This lets
|
|
1508
|
+
* the caller apply priority ordering (static > dynamic > catch-all >
|
|
1509
|
+
* optional-catch-all) across all groups uniformly instead of per-group.
|
|
1510
|
+
*/
|
|
1511
|
+
function collectEffectiveChildren(node, groupChain = []) {
|
|
1512
|
+
const result = [];
|
|
1513
|
+
for (const child of node.children) if (child.segmentType === "group") {
|
|
1514
|
+
const nested = collectEffectiveChildren(child, [...groupChain, child]);
|
|
1515
|
+
result.push(...nested);
|
|
1516
|
+
} else result.push({
|
|
1517
|
+
child,
|
|
1518
|
+
groupChain
|
|
1519
|
+
});
|
|
1520
|
+
return result;
|
|
1521
|
+
}
|
|
1522
|
+
/** A node is terminal if it can serve a response (has a page or route handler). */
|
|
1523
|
+
function isTerminal(node) {
|
|
1524
|
+
return !!(node.page || node.route);
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* Recursive depth-first matcher with backtracking.
|
|
1528
|
+
*
|
|
1529
|
+
* Mutates `chain` and `params` in place for performance; backtracks
|
|
1530
|
+
* (restores previous state) when a candidate doesn't lead to a match.
|
|
1531
|
+
*/
|
|
1532
|
+
function matchNode(node, parts, index, chain, params, requireTerminal) {
|
|
1533
|
+
chain.push(node);
|
|
1534
|
+
if (index >= parts.length) {
|
|
1535
|
+
if (!requireTerminal || isTerminal(node)) return true;
|
|
1536
|
+
for (const child of node.children) if (child.segmentType === "group") {
|
|
1537
|
+
if (matchNode(child, parts, index, chain, params, requireTerminal)) return true;
|
|
1538
|
+
}
|
|
1539
|
+
const effective = collectEffectiveChildren(node);
|
|
1540
|
+
for (const { child, groupChain } of effective) if (child.segmentType === "optional-catch-all" && (!requireTerminal || isTerminal(child))) {
|
|
1541
|
+
chain.push(...groupChain, child);
|
|
1542
|
+
return true;
|
|
1543
|
+
}
|
|
1544
|
+
chain.pop();
|
|
1545
|
+
return false;
|
|
1546
|
+
}
|
|
1547
|
+
const part = parts[index];
|
|
1548
|
+
const effective = collectEffectiveChildren(node);
|
|
1549
|
+
for (const { child, groupChain } of effective) if (child.segmentType === "static" && child.segmentName === part) {
|
|
1550
|
+
chain.push(...groupChain);
|
|
1551
|
+
if (matchNode(child, parts, index + 1, chain, params, requireTerminal)) return true;
|
|
1552
|
+
chain.length -= groupChain.length;
|
|
1553
|
+
}
|
|
1554
|
+
for (const { child, groupChain } of effective) if (child.segmentType === "dynamic" && child.paramName) {
|
|
1555
|
+
chain.push(...groupChain);
|
|
1556
|
+
const prev = params[child.paramName];
|
|
1557
|
+
params[child.paramName] = part;
|
|
1558
|
+
if (matchNode(child, parts, index + 1, chain, params, requireTerminal)) return true;
|
|
1559
|
+
chain.length -= groupChain.length;
|
|
1560
|
+
if (prev !== void 0) params[child.paramName] = prev;
|
|
1561
|
+
else delete params[child.paramName];
|
|
1562
|
+
}
|
|
1563
|
+
for (const { child, groupChain } of effective) if (child.segmentType === "catch-all" && child.paramName && (!requireTerminal || isTerminal(child))) {
|
|
1564
|
+
chain.push(...groupChain, child);
|
|
1565
|
+
params[child.paramName] = parts.slice(index);
|
|
1566
|
+
return true;
|
|
1567
|
+
}
|
|
1568
|
+
for (const { child, groupChain } of effective) if (child.segmentType === "optional-catch-all" && child.paramName && (!requireTerminal || isTerminal(child))) {
|
|
1569
|
+
chain.push(...groupChain, child);
|
|
1570
|
+
params[child.paramName] = parts.slice(index);
|
|
1571
|
+
return true;
|
|
1572
|
+
}
|
|
1573
|
+
chain.pop();
|
|
1574
|
+
return false;
|
|
1575
|
+
}
|
|
1576
|
+
//#endregion
|
|
1469
1577
|
//#region src/plugins/dev-server.ts
|
|
1470
1578
|
var RSC_ENTRY_ID = "virtual:timber-rsc-entry";
|
|
1471
1579
|
/**
|
|
@@ -1892,6 +2000,7 @@ function generateConfigModule(ctx) {
|
|
|
1892
2000
|
sitemap: ctx.config.sitemap,
|
|
1893
2001
|
forms: serializeFormsConfig(ctx.config.forms),
|
|
1894
2002
|
deploymentId: ctx.deploymentId ?? null,
|
|
2003
|
+
clientSegmentCache: ctx.config.clientSegmentCache ?? false,
|
|
1895
2004
|
devBrowserLogs: ctx.config.devBrowserLogs
|
|
1896
2005
|
};
|
|
1897
2006
|
return [
|
|
@@ -1904,12 +2013,12 @@ function generateConfigModule(ctx) {
|
|
|
1904
2013
|
].join("\n");
|
|
1905
2014
|
}
|
|
1906
2015
|
/**
|
|
1907
|
-
* Detect
|
|
2016
|
+
* Detect a user convention file at both root and src/.
|
|
1908
2017
|
*
|
|
1909
|
-
*
|
|
1910
|
-
*
|
|
2018
|
+
* Root takes precedence over src/, extension priority: .ts > .js > .mjs.
|
|
2019
|
+
* Used for instrumentation.ts, timber.cache.ts, and similar file conventions.
|
|
1911
2020
|
*/
|
|
1912
|
-
function
|
|
2021
|
+
function detectConventionFile(root, basename) {
|
|
1913
2022
|
const extensions = [
|
|
1914
2023
|
".ts",
|
|
1915
2024
|
".js",
|
|
@@ -1917,11 +2026,25 @@ function detectInstrumentationFile(root) {
|
|
|
1917
2026
|
];
|
|
1918
2027
|
const dirs = [root, resolve(root, "src")];
|
|
1919
2028
|
for (const dir of dirs) for (const ext of extensions) {
|
|
1920
|
-
const candidate = resolve(dir,
|
|
2029
|
+
const candidate = resolve(dir, `${basename}${ext}`);
|
|
1921
2030
|
if (existsSync(candidate)) return candidate;
|
|
1922
2031
|
}
|
|
1923
2032
|
return null;
|
|
1924
2033
|
}
|
|
2034
|
+
function detectInstrumentationFile(root) {
|
|
2035
|
+
return detectConventionFile(root, "instrumentation");
|
|
2036
|
+
}
|
|
2037
|
+
/**
|
|
2038
|
+
* Detect the user's timber.cache file.
|
|
2039
|
+
*
|
|
2040
|
+
* This file holds runtime cache instances (cacheHandler, cdnPurge)
|
|
2041
|
+
* that cannot be JSON-serialized. Separating them from timber.config.ts
|
|
2042
|
+
* prevents the RSC build from pulling build-time deps (adapters, MDX,
|
|
2043
|
+
* rollup) into the runtime bundle. See TIM-1211.
|
|
2044
|
+
*/
|
|
2045
|
+
function detectCacheFile(root) {
|
|
2046
|
+
return detectConventionFile(root, "timber.cache");
|
|
2047
|
+
}
|
|
1925
2048
|
/**
|
|
1926
2049
|
* Generate the virtual:timber-instrumentation module source.
|
|
1927
2050
|
*
|
|
@@ -1951,50 +2074,37 @@ function generateInstrumentationModule(instrumentationPath) {
|
|
|
1951
2074
|
].join("\n");
|
|
1952
2075
|
}
|
|
1953
2076
|
/**
|
|
1954
|
-
* Detect the user's timber.config file at the project root.
|
|
1955
|
-
* Returns the absolute path or null if not found.
|
|
1956
|
-
*/
|
|
1957
|
-
function detectConfigFile(root) {
|
|
1958
|
-
for (const name of [
|
|
1959
|
-
"timber.config.ts",
|
|
1960
|
-
"timber.config.js",
|
|
1961
|
-
"timber.config.mjs"
|
|
1962
|
-
]) {
|
|
1963
|
-
const candidate = resolve(root, name);
|
|
1964
|
-
if (existsSync(candidate)) return candidate;
|
|
1965
|
-
}
|
|
1966
|
-
return null;
|
|
1967
|
-
}
|
|
1968
|
-
/**
|
|
1969
2077
|
* Generate the virtual:timber-cache-handler module source.
|
|
1970
2078
|
*
|
|
1971
|
-
* When the user's
|
|
1972
|
-
* dynamically imports
|
|
1973
|
-
*
|
|
1974
|
-
*
|
|
1975
|
-
*
|
|
2079
|
+
* When the user's timber.cache.ts exists, generates a module that
|
|
2080
|
+
* dynamically imports it and extracts cacheHandler and cdnPurge.
|
|
2081
|
+
* These are class instances (e.g. RedisCacheHandler) that cannot be
|
|
2082
|
+
* JSON-serialized into virtual:timber-config, so they must be loaded
|
|
2083
|
+
* at runtime via dynamic import.
|
|
2084
|
+
*
|
|
2085
|
+
* timber.cache.ts exports: default cacheHandler, named cdnPurge.
|
|
2086
|
+
*
|
|
2087
|
+
* See TIM-1211, TIM-599.
|
|
1976
2088
|
*/
|
|
1977
|
-
function generateCacheHandlerModule(
|
|
1978
|
-
if (
|
|
2089
|
+
function generateCacheHandlerModule(cachePath) {
|
|
2090
|
+
if (cachePath) return [
|
|
1979
2091
|
"// Auto-generated cache handler loader — do not edit.",
|
|
1980
2092
|
"// Generated by timber-entries plugin.",
|
|
1981
2093
|
"",
|
|
1982
2094
|
`export default async function loadCacheHandler() {`,
|
|
1983
|
-
` const mod = await import(${JSON.stringify(
|
|
1984
|
-
`
|
|
1985
|
-
` return config.cacheHandler ?? null;`,
|
|
2095
|
+
` const mod = await import(${JSON.stringify(cachePath)});`,
|
|
2096
|
+
` return mod.default ?? null;`,
|
|
1986
2097
|
`}`,
|
|
1987
2098
|
"",
|
|
1988
2099
|
`export async function loadCdnPurgeHandler() {`,
|
|
1989
|
-
` const mod = await import(${JSON.stringify(
|
|
1990
|
-
`
|
|
1991
|
-
` return config.cdnPurge ?? null;`,
|
|
2100
|
+
` const mod = await import(${JSON.stringify(cachePath)});`,
|
|
2101
|
+
` return mod.cdnPurge ?? null;`,
|
|
1992
2102
|
`}`
|
|
1993
2103
|
].join("\n");
|
|
1994
2104
|
return [
|
|
1995
2105
|
"// Auto-generated cache handler loader — do not edit.",
|
|
1996
2106
|
"// Generated by timber-entries plugin.",
|
|
1997
|
-
"// No
|
|
2107
|
+
"// No timber.cache.ts found.",
|
|
1998
2108
|
"",
|
|
1999
2109
|
`export default async function loadCacheHandler() {`,
|
|
2000
2110
|
` return null;`,
|
|
@@ -2042,7 +2152,7 @@ function timberEntries(ctx) {
|
|
|
2042
2152
|
load(id) {
|
|
2043
2153
|
if (id === RESOLVED_CONFIG_ID) return generateConfigModule(ctx);
|
|
2044
2154
|
if (id === RESOLVED_INSTRUMENTATION_ID) return generateInstrumentationModule(detectInstrumentationFile(ctx.root));
|
|
2045
|
-
if (id === RESOLVED_CACHE_HANDLER_ID) return generateCacheHandlerModule(
|
|
2155
|
+
if (id === RESOLVED_CACHE_HANDLER_ID) return generateCacheHandlerModule(detectCacheFile(ctx.root));
|
|
2046
2156
|
return null;
|
|
2047
2157
|
}
|
|
2048
2158
|
};
|
|
@@ -4610,6 +4720,30 @@ var FontPipeline = class {
|
|
|
4610
4720
|
const familyKey = family.toLowerCase();
|
|
4611
4721
|
for (const [id, entry] of this._entries) if (entry.importer === importer && entry.family.toLowerCase() === familyKey) this._entries.delete(id);
|
|
4612
4722
|
}
|
|
4723
|
+
/** True if at least one entry is registered under `importer`. */
|
|
4724
|
+
hasImporter(importer) {
|
|
4725
|
+
for (const entry of this._entries.values()) if (entry.importer === importer) return true;
|
|
4726
|
+
return false;
|
|
4727
|
+
}
|
|
4728
|
+
/**
|
|
4729
|
+
* Drop **every** registry entry that originated from `importer`,
|
|
4730
|
+
* regardless of family.
|
|
4731
|
+
*
|
|
4732
|
+
* Called at the start of the transform hook before re-registering the
|
|
4733
|
+
* current extraction set. This handles the cases `pruneFor` misses:
|
|
4734
|
+
*
|
|
4735
|
+
* - **Family rename** (Inter → Roboto): the old family's entries are
|
|
4736
|
+
* cleared because we prune by importer, not by (importer, family).
|
|
4737
|
+
* - **Font call deleted**: the old entries are cleared even though no
|
|
4738
|
+
* new call provides a family to prune against.
|
|
4739
|
+
*
|
|
4740
|
+
* Other importers' fonts are untouched.
|
|
4741
|
+
*
|
|
4742
|
+
* See TIM-1087.
|
|
4743
|
+
*/
|
|
4744
|
+
pruneImporter(importer) {
|
|
4745
|
+
for (const [id, entry] of this._entries) if (entry.importer === importer) this._entries.delete(id);
|
|
4746
|
+
}
|
|
4613
4747
|
/** Drop every registered font. Used by tests and rebuild flows. */
|
|
4614
4748
|
clear() {
|
|
4615
4749
|
this._entries.clear();
|
|
@@ -5788,6 +5922,7 @@ function runFontsTransform(code, id, pipeline, emitError) {
|
|
|
5788
5922
|
}
|
|
5789
5923
|
if (analysis.callSites.length === 0 && analysis.bindings.length === 0) return null;
|
|
5790
5924
|
const s = new MagicString(code);
|
|
5925
|
+
const pending = [];
|
|
5791
5926
|
for (const site of analysis.callSites) if (site.binding.provider === "google") {
|
|
5792
5927
|
const family = site.binding.importedName.replace(/_/g, " ");
|
|
5793
5928
|
const config = extractGoogleFontConfigFromNode(site.configNode);
|
|
@@ -5796,27 +5931,32 @@ function runFontsTransform(code, id, pipeline, emitError) {
|
|
|
5796
5931
|
const className = familyToClassName(family);
|
|
5797
5932
|
const fontStack = buildFontStack(family);
|
|
5798
5933
|
const display = config.display ?? "swap";
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5934
|
+
pending.push({
|
|
5935
|
+
site,
|
|
5936
|
+
extracted: {
|
|
5937
|
+
id: fontId,
|
|
5938
|
+
family,
|
|
5939
|
+
provider: "google",
|
|
5940
|
+
weights: normalizeWeightArray(config.weight),
|
|
5941
|
+
styles: normalizeStyleArray(config.style),
|
|
5942
|
+
subsets: config.subsets ?? ["latin"],
|
|
5943
|
+
display,
|
|
5944
|
+
variable: config.variable,
|
|
5945
|
+
className,
|
|
5946
|
+
fontFamily: fontStack,
|
|
5947
|
+
importer: id
|
|
5948
|
+
}
|
|
5949
|
+
});
|
|
5815
5950
|
} else {
|
|
5816
5951
|
const config = extractLocalFontConfigFromNode(site.configNode);
|
|
5817
5952
|
if (!config) emitError(`Could not statically analyze local font config at line ${site.line}, column ${site.column}. Ensure src is a string or array of { path, weight?, style? } objects.`);
|
|
5818
|
-
|
|
5819
|
-
|
|
5953
|
+
pending.push({
|
|
5954
|
+
site,
|
|
5955
|
+
extracted: processLocalFont(config, id)
|
|
5956
|
+
});
|
|
5957
|
+
}
|
|
5958
|
+
pipeline.pruneImporter(id);
|
|
5959
|
+
for (const { extracted, site } of pending) {
|
|
5820
5960
|
pipeline.register(extracted);
|
|
5821
5961
|
s.overwrite(site.replaceStart, site.replaceEnd, buildFontResultLiteral(extracted));
|
|
5822
5962
|
}
|
|
@@ -5986,6 +6126,25 @@ function writeFontManifest(pipeline, buildManifest, base = "/") {
|
|
|
5986
6126
|
//#endregion
|
|
5987
6127
|
//#region src/plugins/fonts.ts
|
|
5988
6128
|
/**
|
|
6129
|
+
* timber-fonts — Vite sub-plugin shell for build-time font processing.
|
|
6130
|
+
*
|
|
6131
|
+
* This file is intentionally thin: it wires the Vite plugin hooks to the
|
|
6132
|
+
* pipeline + per-concern modules under `../fonts/`. All real logic lives
|
|
6133
|
+
* in:
|
|
6134
|
+
*
|
|
6135
|
+
* - `../fonts/pipeline.ts` — `FontPipeline` (state, mutators)
|
|
6136
|
+
* - `../fonts/transform.ts` — transform-hook logic, parsing helpers
|
|
6137
|
+
* - `../fonts/dev-middleware.ts` — dev font binary server
|
|
6138
|
+
* - `../fonts/virtual-modules.ts` — virtual module ID constants + source
|
|
6139
|
+
*
|
|
6140
|
+
* The public exports below are the stable API that the test suite and
|
|
6141
|
+
* other parts of the framework import. Anything new should be added to
|
|
6142
|
+
* one of the per-concern modules and re-exported here only if external
|
|
6143
|
+
* consumers need it.
|
|
6144
|
+
*
|
|
6145
|
+
* Design doc: 24-fonts.md
|
|
6146
|
+
*/
|
|
6147
|
+
/**
|
|
5989
6148
|
* Create the timber-fonts Vite plugin.
|
|
5990
6149
|
*/
|
|
5991
6150
|
function timberFonts(ctx) {
|
|
@@ -6119,6 +6278,27 @@ function timberFonts(ctx) {
|
|
|
6119
6278
|
*/
|
|
6120
6279
|
buildStart() {},
|
|
6121
6280
|
/**
|
|
6281
|
+
* Prune stale font entries when a module that previously imported fonts
|
|
6282
|
+
* no longer contains any font import. The transform hook's `filter.code`
|
|
6283
|
+
* skips such modules, so without this hook the old entries would persist
|
|
6284
|
+
* until dev restart. See TIM-1087.
|
|
6285
|
+
*/
|
|
6286
|
+
async hotUpdate({ type, file, read }) {
|
|
6287
|
+
if (!pipeline.hasImporter(file)) return;
|
|
6288
|
+
if (type === "delete") {
|
|
6289
|
+
pipeline.pruneImporter(file);
|
|
6290
|
+
return;
|
|
6291
|
+
}
|
|
6292
|
+
const code = await read();
|
|
6293
|
+
if (code.includes("@timber/fonts/") || code.includes("@timber-js/app/fonts/") || code.includes("next/font/")) return;
|
|
6294
|
+
try {
|
|
6295
|
+
parseAst(code, { lang: "tsx" });
|
|
6296
|
+
} catch {
|
|
6297
|
+
return;
|
|
6298
|
+
}
|
|
6299
|
+
pipeline.pruneImporter(file);
|
|
6300
|
+
},
|
|
6301
|
+
/**
|
|
6122
6302
|
* Scan source files for font function calls and extract static config.
|
|
6123
6303
|
*
|
|
6124
6304
|
* Delegates to `runFontsTransform`. Errors raised through the
|
|
@@ -8669,6 +8849,84 @@ function timberAdapterBuild(ctx) {
|
|
|
8669
8849
|
};
|
|
8670
8850
|
}
|
|
8671
8851
|
/**
|
|
8852
|
+
* Parse the RSC assets manifest at `manifestPath`. Tries JSON.parse first
|
|
8853
|
+
* (fast path for plain-string manifests); falls back to a dynamic import()
|
|
8854
|
+
* for manifests that contain runtime expressions from renderBuiltUrl.
|
|
8855
|
+
* Returns null if both paths fail.
|
|
8856
|
+
*/
|
|
8857
|
+
async function parseRscManifest(manifestPath, content) {
|
|
8858
|
+
const jsonStr = content.replace(/^export default\s*/, "").replace(/;?\s*$/, "");
|
|
8859
|
+
try {
|
|
8860
|
+
return JSON.parse(jsonStr);
|
|
8861
|
+
} catch {}
|
|
8862
|
+
try {
|
|
8863
|
+
return (await import(`${pathToFileURL(manifestPath).href}?t=${Date.now()}`)).default ?? null;
|
|
8864
|
+
} catch (err) {
|
|
8865
|
+
swallow(err, `failed to evaluate RSC assets manifest: ${manifestPath}`, { level: "warn" });
|
|
8866
|
+
return null;
|
|
8867
|
+
}
|
|
8868
|
+
}
|
|
8869
|
+
/**
|
|
8870
|
+
* Replace all `"js": [...]` array contents with `"js": []` using balanced
|
|
8871
|
+
* bracket matching. Handles nested brackets from runtime expressions like
|
|
8872
|
+
* `__rbu("path/to/[slug]/file.js")`.
|
|
8873
|
+
*/
|
|
8874
|
+
function clearJsArrays(src) {
|
|
8875
|
+
const pattern = /"js"\s*:\s*\[/g;
|
|
8876
|
+
let result = "";
|
|
8877
|
+
let lastEnd = 0;
|
|
8878
|
+
let match;
|
|
8879
|
+
while ((match = pattern.exec(src)) !== null) {
|
|
8880
|
+
const arrayStart = match.index + match[0].length;
|
|
8881
|
+
let depth = 1;
|
|
8882
|
+
let i = arrayStart;
|
|
8883
|
+
while (i < src.length && depth > 0) {
|
|
8884
|
+
if (src[i] === "[") depth++;
|
|
8885
|
+
else if (src[i] === "]") depth--;
|
|
8886
|
+
i++;
|
|
8887
|
+
}
|
|
8888
|
+
result += src.slice(lastEnd, match.index) + "\"js\": []";
|
|
8889
|
+
lastEnd = i;
|
|
8890
|
+
}
|
|
8891
|
+
result += src.slice(lastEnd);
|
|
8892
|
+
return result;
|
|
8893
|
+
}
|
|
8894
|
+
/**
|
|
8895
|
+
* Replace `"bootstrapScriptContent": <value>` with an empty string. The
|
|
8896
|
+
* value may be a JS expression (e.g., concatenation with runtime helpers),
|
|
8897
|
+
* not a simple string literal. Scans forward from the colon, tracking
|
|
8898
|
+
* balanced parens and brackets, and stops at the next unbalanced `,` or `}`.
|
|
8899
|
+
*/
|
|
8900
|
+
function clearBootstrapScriptContent(src) {
|
|
8901
|
+
const idx = src.indexOf("\"bootstrapScriptContent\"");
|
|
8902
|
+
if (idx === -1) return src;
|
|
8903
|
+
const colonIdx = src.indexOf(":", idx + 24);
|
|
8904
|
+
if (colonIdx === -1) return src;
|
|
8905
|
+
let valStart = colonIdx + 1;
|
|
8906
|
+
while (valStart < src.length && /\s/.test(src[valStart])) valStart++;
|
|
8907
|
+
let i = valStart;
|
|
8908
|
+
let depth = 0;
|
|
8909
|
+
let inString = false;
|
|
8910
|
+
let stringChar = "";
|
|
8911
|
+
while (i < src.length) {
|
|
8912
|
+
const ch = src[i];
|
|
8913
|
+
if (inString) {
|
|
8914
|
+
if (ch === "\\") {
|
|
8915
|
+
i += 2;
|
|
8916
|
+
continue;
|
|
8917
|
+
}
|
|
8918
|
+
if (ch === stringChar) inString = false;
|
|
8919
|
+
} else if (ch === "\"" || ch === "'") {
|
|
8920
|
+
inString = true;
|
|
8921
|
+
stringChar = ch;
|
|
8922
|
+
} else if (ch === "(" || ch === "[") depth++;
|
|
8923
|
+
else if (ch === ")" || ch === "]") depth--;
|
|
8924
|
+
else if (depth === 0 && (ch === "," || ch === "}" || ch === "\n")) break;
|
|
8925
|
+
i++;
|
|
8926
|
+
}
|
|
8927
|
+
return src.slice(0, idx) + "\"bootstrapScriptContent\": \"\"" + src.slice(i);
|
|
8928
|
+
}
|
|
8929
|
+
/**
|
|
8672
8930
|
* Strip JS references from the RSC plugin's assets manifest files.
|
|
8673
8931
|
*
|
|
8674
8932
|
* The RSC plugin writes `__vite_rsc_assets_manifest.js` to rsc/ and ssr/
|
|
@@ -8693,13 +8951,8 @@ async function mergeRscAssetsManifest(buildDir, root, buildManifest, routeClient
|
|
|
8693
8951
|
} catch {
|
|
8694
8952
|
return;
|
|
8695
8953
|
}
|
|
8696
|
-
const
|
|
8697
|
-
|
|
8698
|
-
try {
|
|
8699
|
-
manifest = JSON.parse(jsonStr);
|
|
8700
|
-
} catch {
|
|
8701
|
-
return;
|
|
8702
|
-
}
|
|
8954
|
+
const manifest = await parseRscManifest(manifestPath, content);
|
|
8955
|
+
if (!manifest) return;
|
|
8703
8956
|
const serverResources = manifest.serverResources;
|
|
8704
8957
|
if (serverResources) {
|
|
8705
8958
|
for (const [relativePath, deps] of Object.entries(serverResources)) if (deps.css && deps.css.length > 0) {
|
|
@@ -8730,16 +8983,23 @@ async function stripJsFromRscAssetsManifests(buildDir) {
|
|
|
8730
8983
|
continue;
|
|
8731
8984
|
}
|
|
8732
8985
|
const jsonStr = content.replace(/^export default\s*/, "").replace(/;?\s*$/, "");
|
|
8733
|
-
let
|
|
8986
|
+
let isStrictJson = true;
|
|
8987
|
+
let manifest = null;
|
|
8734
8988
|
try {
|
|
8735
8989
|
manifest = JSON.parse(jsonStr);
|
|
8736
|
-
} catch
|
|
8737
|
-
|
|
8990
|
+
} catch {
|
|
8991
|
+
isStrictJson = false;
|
|
8992
|
+
}
|
|
8993
|
+
if (!isStrictJson) {
|
|
8994
|
+
let rewritten = content;
|
|
8995
|
+
rewritten = clearJsArrays(rewritten);
|
|
8996
|
+
rewritten = clearBootstrapScriptContent(rewritten);
|
|
8997
|
+
await writeFile(path, rewritten);
|
|
8738
8998
|
continue;
|
|
8739
8999
|
}
|
|
8740
|
-
const deps = manifest
|
|
9000
|
+
const deps = manifest?.clientReferenceDeps;
|
|
8741
9001
|
if (deps) for (const entry of Object.values(deps)) entry.js = [];
|
|
8742
|
-
manifest.bootstrapScriptContent = "";
|
|
9002
|
+
if (manifest) manifest.bootstrapScriptContent = "";
|
|
8743
9003
|
await writeFile(path, `export default ${JSON.stringify(manifest, null, 2)};\n`);
|
|
8744
9004
|
}
|
|
8745
9005
|
}
|