@timber-js/app 0.2.0-alpha.161 → 0.2.0-alpha.164
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-cjklt63G.js → actions-CSDD6x7U.js} +2 -2
- package/dist/_chunks/{actions-cjklt63G.js.map → actions-CSDD6x7U.js.map} +1 -1
- package/dist/_chunks/{cache-api-CzYUlgXA.js → cache-api-eb1gydM7.js} +41 -13
- package/dist/_chunks/cache-api-eb1gydM7.js.map +1 -0
- package/dist/_chunks/{cli-schema-sync-NfLbLnDw.js → cli-schema-sync-mGfRbjh2.js} +2 -2
- package/dist/_chunks/{cli-schema-sync-NfLbLnDw.js.map → cli-schema-sync-mGfRbjh2.js.map} +1 -1
- package/dist/_chunks/{plugin-context-DeAxFRMq.js → plugin-context-BnaiU_cF.js} +37 -2
- package/dist/_chunks/plugin-context-BnaiU_cF.js.map +1 -0
- package/dist/_chunks/{walkers-9mz9T7mb.js → walkers-BL3MCMgO.js} +2 -2
- package/dist/_chunks/{walkers-9mz9T7mb.js.map → walkers-BL3MCMgO.js.map} +1 -1
- package/dist/adapters/cloudflare-kv-cache.d.ts +1 -0
- package/dist/adapters/cloudflare-kv-cache.d.ts.map +1 -1
- package/dist/adapters/cloudflare-kv-cache.js.map +1 -1
- package/dist/adapters/nitro.d.ts +11 -0
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +77 -64
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/cache/index.d.ts +3 -0
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +1 -1
- package/dist/cache/redis-handler.d.ts +1 -0
- package/dist/cache/redis-handler.d.ts.map +1 -1
- package/dist/cache/tag-aware-handler.d.ts +1 -0
- package/dist/cache/tag-aware-handler.d.ts.map +1 -1
- package/dist/cache/timber-cache.d.ts.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/client/internal.js +1 -2
- package/dist/client/internal.js.map +1 -1
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/slot-context.d.ts +29 -0
- package/dist/client/slot-context.d.ts.map +1 -0
- package/dist/client/slot-outlet.d.ts +16 -0
- package/dist/client/slot-outlet.d.ts.map +1 -0
- package/dist/client/slot-provider.d.ts +20 -0
- package/dist/client/slot-provider.d.ts.map +1 -0
- package/dist/config-types.d.ts +2 -1
- package/dist/config-types.d.ts.map +1 -1
- package/dist/dev-tools/logs.d.ts.map +1 -1
- package/dist/index.js +293 -124
- package/dist/index.js.map +1 -1
- package/dist/plugin-context.d.ts +27 -0
- package/dist/plugin-context.d.ts.map +1 -1
- package/dist/plugins/cache.d.ts.map +1 -1
- package/dist/plugins/client-chunks.d.ts.map +1 -1
- package/dist/plugins/dev-server.d.ts.map +1 -1
- package/dist/plugins/prebuilt-options-analysis.d.ts +40 -0
- package/dist/plugins/prebuilt-options-analysis.d.ts.map +1 -0
- package/dist/plugins/prebuilt.d.ts.map +1 -1
- package/dist/plugins/prerender-sugar.d.ts.map +1 -1
- package/dist/routing/index.js +2 -2
- package/dist/server/html-injector-core.d.ts +30 -9
- package/dist/server/html-injector-core.d.ts.map +1 -1
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/internal.js +346 -51
- package/dist/server/internal.js.map +1 -1
- package/dist/server/node-stream-transforms.d.ts.map +1 -1
- package/dist/server/pipeline-phases.d.ts.map +1 -1
- package/dist/server/prebuilt/cache-key.d.ts +4 -0
- package/dist/server/prebuilt/cache-key.d.ts.map +1 -1
- package/dist/server/prebuilt/key-discipline.d.ts +23 -0
- package/dist/server/prebuilt/key-discipline.d.ts.map +1 -0
- package/dist/server/prebuilt/slots.d.ts +74 -0
- package/dist/server/prebuilt/slots.d.ts.map +1 -0
- package/dist/server/prebuilt-builder.d.ts.map +1 -1
- package/dist/server/prebuilt-runtime.d.ts +12 -2
- package/dist/server/prebuilt-runtime.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/rsc-entry/deny-fallback.d.ts +29 -0
- package/dist/server/rsc-entry/deny-fallback.d.ts.map +1 -0
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/state-tree-diff.d.ts +26 -3
- package/dist/server/state-tree-diff.d.ts.map +1 -1
- package/docs/api/34-api-config.mdx +165 -3
- package/docs/learn/00-introduction.mdx +78 -44
- package/docs/learn/13-configuration.mdx +27 -8
- package/package.json +3 -2
- package/src/adapters/cloudflare-kv-cache.ts +5 -1
- package/src/adapters/nitro.ts +82 -64
- package/src/cache/index.ts +25 -2
- package/src/cache/redis-handler.ts +27 -7
- package/src/cache/tag-aware-handler.ts +5 -1
- package/src/cache/timber-cache.ts +21 -10
- package/src/client/segment-cache.ts +4 -5
- package/src/client/slot-context.ts +48 -0
- package/src/client/slot-outlet.tsx +22 -0
- package/src/client/slot-provider.tsx +25 -0
- package/src/config-types.ts +2 -1
- package/src/dev-tools/logs.ts +7 -0
- package/src/plugin-context.ts +54 -0
- package/src/plugins/cache.ts +1 -2
- package/src/plugins/client-chunks.ts +42 -1
- package/src/plugins/dev-server.ts +12 -69
- package/src/plugins/prebuilt-options-analysis.ts +175 -0
- package/src/plugins/prebuilt.ts +82 -127
- package/src/plugins/prerender-sugar.ts +1 -2
- package/src/server/html-injector-core.ts +85 -27
- package/src/server/html-injectors.ts +5 -1
- package/src/server/node-stream-transforms.ts +6 -1
- package/src/server/pipeline-phases.ts +4 -1
- package/src/server/prebuilt/cache-key.ts +74 -0
- package/src/server/prebuilt/key-discipline.ts +53 -0
- package/src/server/prebuilt/slots.ts +167 -0
- package/src/server/prebuilt-builder.ts +57 -23
- package/src/server/prebuilt-runtime.ts +144 -60
- package/src/server/route-element-builder.ts +82 -73
- package/src/server/rsc-entry/deny-fallback.ts +92 -0
- package/src/server/rsc-entry/helpers.ts +12 -10
- package/src/server/rsc-entry/index.ts +16 -70
- package/src/server/state-tree-diff.ts +49 -4
- package/dist/_chunks/cache-api-CzYUlgXA.js.map +0 -1
- package/dist/_chunks/plugin-context-DeAxFRMq.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -2,23 +2,26 @@ import { a as __esmMin, o as __exportAll, s as __toCommonJS } from "./_chunks/di
|
|
|
2
2
|
import { c as isDynamicMetadataExtension, l as isMetadataRouteServePath, r as canonicalize } from "./_chunks/canonicalize-P41GR6tY.js";
|
|
3
3
|
import { t as matchUrlParts } from "./_chunks/tree-match-D2l830j2.js";
|
|
4
4
|
import { h as swallow } from "./_chunks/logger-B_O6-mdJ.js";
|
|
5
|
-
import { i as scanRoutes } from "./_chunks/cli-schema-sync-
|
|
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-
|
|
7
|
-
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-
|
|
5
|
+
import { i as scanRoutes } from "./_chunks/cli-schema-sync-mGfRbjh2.js";
|
|
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-BL3MCMgO.js";
|
|
7
|
+
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-BnaiU_cF.js";
|
|
8
8
|
import { t as fnv1aHash } from "./_chunks/fast-hash-D6hIVt1Y.js";
|
|
9
9
|
import { t as formatSize } from "./_chunks/format-CfwjgPz9.js";
|
|
10
10
|
import { basename, dirname, extname, join, normalize, relative, resolve, win32 } from "node:path";
|
|
11
11
|
import { builtinModules, createRequire } from "node:module";
|
|
12
12
|
import react, { reactCompilerPreset } from "@vitejs/plugin-react";
|
|
13
13
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
14
|
+
import nodeHTTP from "node:http";
|
|
15
|
+
import { Readable, pipeline } from "node:stream";
|
|
16
|
+
import { pipeline as pipeline$1 } from "node:stream/promises";
|
|
17
|
+
import "node:https";
|
|
18
|
+
import "node:http2";
|
|
14
19
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
15
20
|
import { styleText } from "node:util";
|
|
16
21
|
import { constants, createGzip, gzipSync } from "node:zlib";
|
|
17
|
-
import { Readable, pipeline } from "node:stream";
|
|
18
22
|
import { copyFile, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
19
23
|
import { createHash, randomUUID } from "node:crypto";
|
|
20
24
|
import { parseAst } from "vite";
|
|
21
|
-
import http from "node:http";
|
|
22
25
|
//#region src/plugins/content.ts
|
|
23
26
|
var CONFIG_FILE_NAMES$1 = [
|
|
24
27
|
"content-collections.ts",
|
|
@@ -104,6 +107,98 @@ function timberContent(ctx) {
|
|
|
104
107
|
};
|
|
105
108
|
}
|
|
106
109
|
//#endregion
|
|
110
|
+
//#region ../../node_modules/.pnpm/srvx@0.11.17/node_modules/srvx/dist/adapters/node.mjs
|
|
111
|
+
async function sendNodeResponse(nodeRes, webRes) {
|
|
112
|
+
if (!webRes) {
|
|
113
|
+
nodeRes.statusCode = 500;
|
|
114
|
+
return endNodeResponse(nodeRes);
|
|
115
|
+
}
|
|
116
|
+
if (webRes._toNodeResponse) {
|
|
117
|
+
const res = webRes._toNodeResponse();
|
|
118
|
+
if (res.body) {
|
|
119
|
+
if (res.body instanceof ReadableStream) {
|
|
120
|
+
writeHead(nodeRes, res.status, res.statusText, res.headers);
|
|
121
|
+
return streamBody(res.body, nodeRes);
|
|
122
|
+
} else if (typeof res.body?.pipe === "function") return pipeBody(res.body, nodeRes, res.status, res.statusText, res.headers);
|
|
123
|
+
writeHead(nodeRes, res.status, res.statusText, res.headers);
|
|
124
|
+
nodeRes.write(res.body);
|
|
125
|
+
} else writeHead(nodeRes, res.status, res.statusText, res.headers);
|
|
126
|
+
return endNodeResponse(nodeRes);
|
|
127
|
+
}
|
|
128
|
+
const rawHeaders = [...webRes.headers];
|
|
129
|
+
writeHead(nodeRes, webRes.status, webRes.statusText, rawHeaders);
|
|
130
|
+
return webRes.body ? streamBody(webRes.body, nodeRes) : endNodeResponse(nodeRes);
|
|
131
|
+
}
|
|
132
|
+
function writeHead(nodeRes, status, statusText, rawHeaders) {
|
|
133
|
+
const writeHeaders = rawHeaders.flat();
|
|
134
|
+
if (!nodeRes.headersSent) if (nodeRes.req?.httpVersion === "2.0") nodeRes.writeHead(status, writeHeaders);
|
|
135
|
+
else nodeRes.writeHead(status, statusText, writeHeaders);
|
|
136
|
+
}
|
|
137
|
+
function endNodeResponse(nodeRes) {
|
|
138
|
+
return new Promise((resolve) => nodeRes.end(resolve));
|
|
139
|
+
}
|
|
140
|
+
function pipeBody(stream, nodeRes, status, statusText, headers) {
|
|
141
|
+
if (nodeRes.destroyed) {
|
|
142
|
+
stream.destroy?.();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (typeof stream.on !== "function" || typeof stream.destroy !== "function") {
|
|
146
|
+
writeHead(nodeRes, status, statusText, headers);
|
|
147
|
+
stream.pipe(nodeRes);
|
|
148
|
+
return new Promise((resolve) => nodeRes.on("close", resolve));
|
|
149
|
+
}
|
|
150
|
+
if (stream.destroyed) {
|
|
151
|
+
writeHead(nodeRes, 500, "Internal Server Error", []);
|
|
152
|
+
return endNodeResponse(nodeRes);
|
|
153
|
+
}
|
|
154
|
+
return new Promise((resolve) => {
|
|
155
|
+
function onEarlyError() {
|
|
156
|
+
stream.off("readable", onReadable);
|
|
157
|
+
stream.destroy();
|
|
158
|
+
writeHead(nodeRes, 500, "Internal Server Error", []);
|
|
159
|
+
endNodeResponse(nodeRes).then(resolve);
|
|
160
|
+
}
|
|
161
|
+
function onReadable() {
|
|
162
|
+
stream.off("error", onEarlyError);
|
|
163
|
+
if (nodeRes.destroyed) {
|
|
164
|
+
stream.destroy();
|
|
165
|
+
return resolve();
|
|
166
|
+
}
|
|
167
|
+
writeHead(nodeRes, status, statusText, headers);
|
|
168
|
+
pipeline$1(stream, nodeRes).catch(() => {}).then(() => resolve());
|
|
169
|
+
}
|
|
170
|
+
stream.once("error", onEarlyError);
|
|
171
|
+
stream.once("readable", onReadable);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function streamBody(stream, nodeRes) {
|
|
175
|
+
if (nodeRes.destroyed) {
|
|
176
|
+
stream.cancel();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const reader = stream.getReader();
|
|
180
|
+
function streamCancel(error) {
|
|
181
|
+
reader.cancel(error).catch(() => {});
|
|
182
|
+
if (error) nodeRes.destroy(error);
|
|
183
|
+
}
|
|
184
|
+
function streamHandle({ done, value }) {
|
|
185
|
+
try {
|
|
186
|
+
if (done) nodeRes.end();
|
|
187
|
+
else if (nodeRes.write(value)) reader.read().then(streamHandle, streamCancel);
|
|
188
|
+
else nodeRes.once("drain", () => reader.read().then(streamHandle, streamCancel));
|
|
189
|
+
} catch (error) {
|
|
190
|
+
streamCancel(error instanceof Error ? error : void 0);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
nodeRes.on("close", streamCancel);
|
|
194
|
+
nodeRes.on("error", streamCancel);
|
|
195
|
+
reader.read().then(streamHandle, streamCancel);
|
|
196
|
+
return reader.closed.catch(streamCancel).finally(() => {
|
|
197
|
+
nodeRes.off("close", streamCancel);
|
|
198
|
+
nodeRes.off("error", streamCancel);
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
//#endregion
|
|
107
202
|
//#region src/dev-tools/stack-classifier.ts
|
|
108
203
|
/** Parse file/line/col from a stack frame line. */
|
|
109
204
|
function parseFrame(frameLine) {
|
|
@@ -1500,7 +1595,10 @@ function createTimberMiddleware(server, ctx) {
|
|
|
1500
1595
|
try {
|
|
1501
1596
|
const webRequest = toWebRequest(req);
|
|
1502
1597
|
const wrapper = server[Symbol.for("timber:dev-request-wrapper")];
|
|
1503
|
-
|
|
1598
|
+
const finalResponse = compressResponse(webRequest, wrapper ? await wrapper(() => handler(webRequest)) : await handler(webRequest));
|
|
1599
|
+
res.writeHead(finalResponse.status, [...finalResponse.headers].flat());
|
|
1600
|
+
res.flushHeaders();
|
|
1601
|
+
await sendNodeResponse(res, finalResponse);
|
|
1504
1602
|
} catch (error) {
|
|
1505
1603
|
if (error instanceof Error) {
|
|
1506
1604
|
const displayError = error.name === "SsrStreamError" && error.cause instanceof Error ? error.cause : error;
|
|
@@ -1596,44 +1694,6 @@ function nodeReadableToWebStream(nodeStream) {
|
|
|
1596
1694
|
} });
|
|
1597
1695
|
}
|
|
1598
1696
|
/**
|
|
1599
|
-
* Write a Web Response to a Node ServerResponse.
|
|
1600
|
-
*
|
|
1601
|
-
* Copies status code, headers, and streams the body.
|
|
1602
|
-
*/
|
|
1603
|
-
async function sendWebResponse(nodeRes, webResponse) {
|
|
1604
|
-
nodeRes.statusCode = webResponse.status;
|
|
1605
|
-
webResponse.headers.forEach((value, key) => {
|
|
1606
|
-
if (key.toLowerCase() !== "set-cookie") nodeRes.setHeader(key, value);
|
|
1607
|
-
});
|
|
1608
|
-
const setCookies = webResponse.headers.getSetCookie();
|
|
1609
|
-
if (setCookies.length > 0) nodeRes.setHeader("Set-Cookie", setCookies);
|
|
1610
|
-
if (!webResponse.body) {
|
|
1611
|
-
nodeRes.end();
|
|
1612
|
-
return;
|
|
1613
|
-
}
|
|
1614
|
-
nodeRes.flushHeaders();
|
|
1615
|
-
const reader = webResponse.body.getReader();
|
|
1616
|
-
let clientDisconnected = false;
|
|
1617
|
-
const onClose = () => {
|
|
1618
|
-
clientDisconnected = true;
|
|
1619
|
-
reader.cancel("Client disconnected").catch(() => {});
|
|
1620
|
-
};
|
|
1621
|
-
nodeRes.on("close", onClose);
|
|
1622
|
-
try {
|
|
1623
|
-
while (true) {
|
|
1624
|
-
const { done, value } = await reader.read();
|
|
1625
|
-
if (done) break;
|
|
1626
|
-
nodeRes.write(value);
|
|
1627
|
-
}
|
|
1628
|
-
} catch (err) {
|
|
1629
|
-
if (!clientDisconnected) throw err;
|
|
1630
|
-
} finally {
|
|
1631
|
-
nodeRes.off("close", onClose);
|
|
1632
|
-
reader.releaseLock();
|
|
1633
|
-
if (!nodeRes.writableEnded) nodeRes.end();
|
|
1634
|
-
}
|
|
1635
|
-
}
|
|
1636
|
-
/**
|
|
1637
1697
|
* Check if a URL is a Vite-internal request that should be passed through.
|
|
1638
1698
|
*/
|
|
1639
1699
|
function isViteInternal(url) {
|
|
@@ -6521,6 +6581,7 @@ function patchConsole(server, projectRoot) {
|
|
|
6521
6581
|
console[level] = (...args) => {
|
|
6522
6582
|
originals.get(level)(...args);
|
|
6523
6583
|
if (isFrameworkInternalCaller()) return;
|
|
6584
|
+
if (typeof args[0] === "string" && args[0].includes("[console.")) return;
|
|
6524
6585
|
try {
|
|
6525
6586
|
const payload = {
|
|
6526
6587
|
level,
|
|
@@ -6748,13 +6809,39 @@ function findAppBoundary(path, appPrefix) {
|
|
|
6748
6809
|
};
|
|
6749
6810
|
}
|
|
6750
6811
|
/**
|
|
6812
|
+
* Check if a string is a bare import specifier (package name, not a path).
|
|
6813
|
+
* Bare specifiers don't start with `.`, `/`, or contain `:/`.
|
|
6814
|
+
*/
|
|
6815
|
+
function isBareSpecifier(id) {
|
|
6816
|
+
return !id.startsWith(".") && !id.startsWith("/") && !id.includes(":/");
|
|
6817
|
+
}
|
|
6818
|
+
/**
|
|
6819
|
+
* Look up facade ancestors for a module ID, handling bare package specifiers.
|
|
6820
|
+
*
|
|
6821
|
+
* The facade ancestry map is keyed by absolute resolved paths (from Rollup's
|
|
6822
|
+
* moduleIds), but the RSC plugin passes bare package names (e.g. 'streamdown')
|
|
6823
|
+
* as meta.id for npm package client references (TIM-1108). When a direct
|
|
6824
|
+
* lookup misses and the id is a bare specifier, scan map keys for a matching
|
|
6825
|
+
* node_modules path.
|
|
6826
|
+
*/
|
|
6827
|
+
function lookupFacades(id, sharedModuleFacades) {
|
|
6828
|
+
const direct = sharedModuleFacades.get(id);
|
|
6829
|
+
if (direct) return direct;
|
|
6830
|
+
if (!isBareSpecifier(id)) return void 0;
|
|
6831
|
+
const needle = `/node_modules/${id}/`;
|
|
6832
|
+
let merged;
|
|
6833
|
+
for (const [key, facades] of sharedModuleFacades) if (key.includes(needle)) if (!merged) merged = [...facades];
|
|
6834
|
+
else merged.push(...facades);
|
|
6835
|
+
return merged;
|
|
6836
|
+
}
|
|
6837
|
+
/**
|
|
6751
6838
|
* For a `shared:` client reference, check the facade ancestry map to
|
|
6752
6839
|
* see if all consuming facades belong to the same route boundary.
|
|
6753
6840
|
* Returns the boundary segment name, or null if the module is
|
|
6754
6841
|
* genuinely shared across multiple boundaries.
|
|
6755
6842
|
*/
|
|
6756
6843
|
function reclassifySharedModule(meta, appPrefix, root, sharedModuleFacades) {
|
|
6757
|
-
const facades =
|
|
6844
|
+
const facades = lookupFacades(meta.id, sharedModuleFacades);
|
|
6758
6845
|
if (!facades || facades.length === 0) return null;
|
|
6759
6846
|
const boundaries = /* @__PURE__ */ new Set();
|
|
6760
6847
|
for (const facade of facades) {
|
|
@@ -7316,7 +7403,7 @@ function timberCacheTransform(ctx) {
|
|
|
7316
7403
|
if (id.includes("node_modules")) return;
|
|
7317
7404
|
let program;
|
|
7318
7405
|
try {
|
|
7319
|
-
program =
|
|
7406
|
+
program = ctx.parseCached.parse(code);
|
|
7320
7407
|
} catch {
|
|
7321
7408
|
return;
|
|
7322
7409
|
}
|
|
@@ -7510,6 +7597,122 @@ function recordCallSiteProps(ctx, componentId, props) {
|
|
|
7510
7597
|
}
|
|
7511
7598
|
}
|
|
7512
7599
|
//#endregion
|
|
7600
|
+
//#region src/plugins/prebuilt-options-analysis.ts
|
|
7601
|
+
var EXPRESSION_WRAPPER_TYPES = /* @__PURE__ */ new Set([
|
|
7602
|
+
"TSAsExpression",
|
|
7603
|
+
"TSSatisfiesExpression",
|
|
7604
|
+
"TSNonNullExpression",
|
|
7605
|
+
"ParenthesizedExpression"
|
|
7606
|
+
]);
|
|
7607
|
+
/** Unwrap TS expression wrappers (`as`, `satisfies`, `!`, parens). */
|
|
7608
|
+
function unwrapExpression(node) {
|
|
7609
|
+
let current = node;
|
|
7610
|
+
while (EXPRESSION_WRAPPER_TYPES.has(current.type) && current.expression) current = current.expression;
|
|
7611
|
+
return current;
|
|
7612
|
+
}
|
|
7613
|
+
/**
|
|
7614
|
+
* Statically classify a `slots` value node against resolveSlotNames'
|
|
7615
|
+
* runtime rules (server/prebuilt/slots.ts):
|
|
7616
|
+
*
|
|
7617
|
+
* - `yes` — certainly an ACTIVE shape: dense, non-empty array literal of
|
|
7618
|
+
* non-empty string literals.
|
|
7619
|
+
* - `no` — certainly NOT active (absent/empty/malformed): registration
|
|
7620
|
+
* falls back to non-slot semantics with `prerender` preserved.
|
|
7621
|
+
* - `unknown` — identifiers, spreads, holes, non-literal elements.
|
|
7622
|
+
*/
|
|
7623
|
+
function classifySlotsValue(node) {
|
|
7624
|
+
const value = unwrapExpression(node);
|
|
7625
|
+
if (value.type === "ArrayExpression") {
|
|
7626
|
+
const elements = value.elements ?? [];
|
|
7627
|
+
if (elements.length === 0) return "no";
|
|
7628
|
+
let allValidStrings = true;
|
|
7629
|
+
for (const el of elements) {
|
|
7630
|
+
if (el === null) return "no";
|
|
7631
|
+
if (el.type === "SpreadElement") return "unknown";
|
|
7632
|
+
const entry = unwrapExpression(el);
|
|
7633
|
+
if (entry.type !== "Literal") {
|
|
7634
|
+
allValidStrings = false;
|
|
7635
|
+
continue;
|
|
7636
|
+
}
|
|
7637
|
+
if (typeof entry.value !== "string" || entry.value.length === 0) return "no";
|
|
7638
|
+
}
|
|
7639
|
+
return allValidStrings ? "yes" : "unknown";
|
|
7640
|
+
}
|
|
7641
|
+
if (value.type === "Literal") return "no";
|
|
7642
|
+
if (value.type === "Identifier" && value.name === "undefined") return "no";
|
|
7643
|
+
return "unknown";
|
|
7644
|
+
}
|
|
7645
|
+
/** Statically classify whether a `ttl` value gives a runtime lifetime (`ttl !== undefined`). */
|
|
7646
|
+
function classifyTtlValue(node) {
|
|
7647
|
+
const value = unwrapExpression(node);
|
|
7648
|
+
if (value.type === "Literal") return "yes";
|
|
7649
|
+
if (value.type === "Identifier" && value.name === "undefined") return "no";
|
|
7650
|
+
return "unknown";
|
|
7651
|
+
}
|
|
7652
|
+
/** Statically classify whether a `tags` value gives a runtime lifetime (`tags != null`). */
|
|
7653
|
+
function classifyTagsValue(node) {
|
|
7654
|
+
const value = unwrapExpression(node);
|
|
7655
|
+
if (value.type === "ArrayExpression") return "yes";
|
|
7656
|
+
if (value.type === "ArrowFunctionExpression" || value.type === "FunctionExpression") return "yes";
|
|
7657
|
+
if (value.type === "Literal") return value.value === null ? "no" : "yes";
|
|
7658
|
+
if (value.type === "Identifier" && value.name === "undefined") return "no";
|
|
7659
|
+
return "unknown";
|
|
7660
|
+
}
|
|
7661
|
+
/**
|
|
7662
|
+
* Can this call site's options put the component in the prerender tier?
|
|
7663
|
+
* The answer is static only when the options argument is an object
|
|
7664
|
+
* literal:
|
|
7665
|
+
*
|
|
7666
|
+
* - literal with `prerender: true` → yes
|
|
7667
|
+
* - literal with `prerender: false`/absent → no (spread-free)
|
|
7668
|
+
* - literal with statically ACTIVE `slots` + certain runtime lifetime
|
|
7669
|
+
* → no — registration strips `prerender` for active slot components
|
|
7670
|
+
* (TIM-1173), so attributing the callsite would make the post-build
|
|
7671
|
+
* hook import the RSC entry and enumerate route params for entries
|
|
7672
|
+
* the capture pass then skips (codex P2 on PR #890)
|
|
7673
|
+
* - anything else (identifier, spread, non-literal values)
|
|
7674
|
+
* → conservatively yes — the capture pass re-checks at runtime
|
|
7675
|
+
*
|
|
7676
|
+
* Properties walk in source order with last-write-wins, mirroring
|
|
7677
|
+
* object-literal evaluation: `{ prerender: false, ...opts }` may end up
|
|
7678
|
+
* prerenderable (the spread can override), while `{ ...opts,
|
|
7679
|
+
* prerender: false }` is statically false (codex P2 on PR #836).
|
|
7680
|
+
* Computed keys are treated like spreads — they may evaluate to any of
|
|
7681
|
+
* the tracked names.
|
|
7682
|
+
*/
|
|
7683
|
+
function mayPrerender(call) {
|
|
7684
|
+
if (call.arguments.length < 2) return false;
|
|
7685
|
+
const options = unwrapExpression(call.arguments[1]);
|
|
7686
|
+
if (options.type !== "ObjectExpression") return true;
|
|
7687
|
+
const properties = options.properties ?? [];
|
|
7688
|
+
let verdict = "absent";
|
|
7689
|
+
let slotsActive = "no";
|
|
7690
|
+
let ttl = "no";
|
|
7691
|
+
let tags = "no";
|
|
7692
|
+
for (const prop of properties) {
|
|
7693
|
+
if (prop.type === "SpreadElement" || prop.computed) {
|
|
7694
|
+
verdict = "unknown";
|
|
7695
|
+
slotsActive = "unknown";
|
|
7696
|
+
ttl = "unknown";
|
|
7697
|
+
tags = "unknown";
|
|
7698
|
+
continue;
|
|
7699
|
+
}
|
|
7700
|
+
if (prop.type !== "Property") continue;
|
|
7701
|
+
const property = prop;
|
|
7702
|
+
const keyName = property.key.type === "Identifier" ? property.key.name : property.key.type === "Literal" ? String(property.key.value) : void 0;
|
|
7703
|
+
if (keyName === "prerender") {
|
|
7704
|
+
const value = unwrapExpression(property.value);
|
|
7705
|
+
if (value.type === "Literal") verdict = value.value === true ? "true" : "false";
|
|
7706
|
+
else verdict = "unknown";
|
|
7707
|
+
} else if (keyName === "slots") slotsActive = classifySlotsValue(property.value);
|
|
7708
|
+
else if (keyName === "ttl") ttl = classifyTtlValue(property.value);
|
|
7709
|
+
else if (keyName === "tags") tags = classifyTagsValue(property.value);
|
|
7710
|
+
}
|
|
7711
|
+
if (verdict !== "true" && verdict !== "unknown") return false;
|
|
7712
|
+
if (slotsActive === "yes" && (ttl === "yes" || tags === "yes")) return false;
|
|
7713
|
+
return true;
|
|
7714
|
+
}
|
|
7715
|
+
//#endregion
|
|
7513
7716
|
//#region src/plugins/prebuilt-capture.ts
|
|
7514
7717
|
/**
|
|
7515
7718
|
* Post-build prebuilt capture — Node-side orchestration (TIM-1119).
|
|
@@ -7735,59 +7938,6 @@ function importInsertionOffset(program) {
|
|
|
7735
7938
|
for (const stmt of program.body) if (!(stmt.type === "ExpressionStatement" && typeof stmt.directive === "string")) return stmt.start;
|
|
7736
7939
|
return program.body.length > 0 ? program.body[program.body.length - 1].end : 0;
|
|
7737
7940
|
}
|
|
7738
|
-
/** TS/paren wrappers that may sit between a declarator and the call. */
|
|
7739
|
-
var EXPRESSION_WRAPPER_TYPES = /* @__PURE__ */ new Set([
|
|
7740
|
-
"TSAsExpression",
|
|
7741
|
-
"TSSatisfiesExpression",
|
|
7742
|
-
"TSNonNullExpression",
|
|
7743
|
-
"ParenthesizedExpression"
|
|
7744
|
-
]);
|
|
7745
|
-
function unwrapExpression(node) {
|
|
7746
|
-
let current = node;
|
|
7747
|
-
while (EXPRESSION_WRAPPER_TYPES.has(current.type) && current.expression) current = current.expression;
|
|
7748
|
-
return current;
|
|
7749
|
-
}
|
|
7750
|
-
/**
|
|
7751
|
-
* Can this call site's options put the component in the prerender tier?
|
|
7752
|
-
*
|
|
7753
|
-
* Runtime-tier call sites (`ttl`/`tags` without `prerender`) must do NO
|
|
7754
|
-
* build work — recording them would make the post-build hook import the
|
|
7755
|
-
* built RSC entry (running production startup) and load route modules for
|
|
7756
|
-
* routes that have nothing to prerender (codex P2 on PR #836). The answer
|
|
7757
|
-
* is static only when the options argument is an object literal:
|
|
7758
|
-
*
|
|
7759
|
-
* - literal with `prerender: true` → yes
|
|
7760
|
-
* - literal with `prerender: false`/absent → no (spread-free)
|
|
7761
|
-
* - anything else (identifier, spread, non-literal `prerender` value)
|
|
7762
|
-
* → conservatively yes — the capture pass re-checks the real options
|
|
7763
|
-
* at runtime and skips non-prerender registrations; over-inclusion
|
|
7764
|
-
* costs a wasted entry load, under-inclusion would silently skip
|
|
7765
|
-
* prerendering.
|
|
7766
|
-
*/
|
|
7767
|
-
function mayPrerender(call) {
|
|
7768
|
-
if (call.arguments.length < 2) return false;
|
|
7769
|
-
const options = unwrapExpression(call.arguments[1]);
|
|
7770
|
-
if (options.type !== "ObjectExpression") return true;
|
|
7771
|
-
const properties = options.properties ?? [];
|
|
7772
|
-
let verdict = "absent";
|
|
7773
|
-
for (const prop of properties) {
|
|
7774
|
-
if (prop.type === "SpreadElement") {
|
|
7775
|
-
verdict = "unknown";
|
|
7776
|
-
continue;
|
|
7777
|
-
}
|
|
7778
|
-
if (prop.type !== "Property") continue;
|
|
7779
|
-
const property = prop;
|
|
7780
|
-
if (property.computed) {
|
|
7781
|
-
verdict = "unknown";
|
|
7782
|
-
continue;
|
|
7783
|
-
}
|
|
7784
|
-
if ((property.key.type === "Identifier" ? property.key.name : property.key.type === "Literal" ? String(property.key.value) : void 0) !== "prerender") continue;
|
|
7785
|
-
const value = unwrapExpression(property.value);
|
|
7786
|
-
if (value.type === "Literal") verdict = value.value === true ? "true" : "false";
|
|
7787
|
-
else verdict = "unknown";
|
|
7788
|
-
}
|
|
7789
|
-
return verdict === "true" || verdict === "unknown";
|
|
7790
|
-
}
|
|
7791
7941
|
/**
|
|
7792
7942
|
* Collect matching call sites from the allowlisted module-scope positions:
|
|
7793
7943
|
* top-level (possibly exported) variable declarator initializers and
|
|
@@ -7874,13 +8024,8 @@ function collectPageAndLayoutFiles(root) {
|
|
|
7874
8024
|
walk(root);
|
|
7875
8025
|
return files;
|
|
7876
8026
|
}
|
|
7877
|
-
|
|
7878
|
-
|
|
7879
|
-
* declaring module and extract JSX call-site props for those bindings.
|
|
7880
|
-
* Uses `getModuleInfo` to get the RESOLVED import targets and the module's
|
|
7881
|
-
* transformed code (available on Rollup's ModuleInfo at generateBundle).
|
|
7882
|
-
*/
|
|
7883
|
-
function collectCrossModuleProps(ctx, moduleId, getInfo) {
|
|
8027
|
+
var DEFAULT_SENTINEL = "\0default";
|
|
8028
|
+
async function collectCrossModuleProps(ctx, moduleId, getInfo, resolve) {
|
|
7884
8029
|
const bindingNames = ctx.prebuiltBindingNames;
|
|
7885
8030
|
const info = getInfo(moduleId);
|
|
7886
8031
|
if (!info) return;
|
|
@@ -7891,6 +8036,7 @@ function collectCrossModuleProps(ctx, moduleId, getInfo) {
|
|
|
7891
8036
|
for (const declId of declaringImports) {
|
|
7892
8037
|
const map = bindingNames.get(declId);
|
|
7893
8038
|
for (const [name, compId] of map) {
|
|
8039
|
+
if (name === DEFAULT_SENTINEL) continue;
|
|
7894
8040
|
if (exportNameToId.has(name)) collisions.add(name);
|
|
7895
8041
|
exportNameToId.set(name, compId);
|
|
7896
8042
|
}
|
|
@@ -7907,24 +8053,32 @@ function collectCrossModuleProps(ctx, moduleId, getInfo) {
|
|
|
7907
8053
|
if (!source) return;
|
|
7908
8054
|
let program;
|
|
7909
8055
|
try {
|
|
7910
|
-
program =
|
|
8056
|
+
program = ctx.parseCached.parse(source);
|
|
7911
8057
|
} catch {
|
|
7912
8058
|
return;
|
|
7913
8059
|
}
|
|
8060
|
+
const declaringSet = new Set(declaringImports);
|
|
7914
8061
|
const localToComponentId = /* @__PURE__ */ new Map();
|
|
7915
8062
|
for (const stmt of program.body) {
|
|
7916
8063
|
if (stmt.type !== "ImportDeclaration") continue;
|
|
7917
8064
|
const importDecl = stmt;
|
|
7918
8065
|
for (const spec of importDecl.specifiers) if (spec.type === "ImportSpecifier") {
|
|
7919
8066
|
const exportedName = spec.imported?.name ?? spec.local.name;
|
|
7920
|
-
|
|
7921
|
-
|
|
8067
|
+
if (exportedName === "default") {
|
|
8068
|
+
const resolvedId = await resolve(importDecl.source.value, moduleId);
|
|
8069
|
+
if (resolvedId && declaringSet.has(resolvedId)) {
|
|
8070
|
+
const compId = bindingNames.get(resolvedId).get(DEFAULT_SENTINEL);
|
|
8071
|
+
if (compId) localToComponentId.set(spec.local.name, compId);
|
|
8072
|
+
}
|
|
8073
|
+
} else {
|
|
8074
|
+
const compId = exportNameToId.get(exportedName);
|
|
8075
|
+
if (compId) localToComponentId.set(spec.local.name, compId);
|
|
8076
|
+
}
|
|
7922
8077
|
} else if (spec.type === "ImportDefaultSpecifier") {
|
|
7923
|
-
const
|
|
7924
|
-
if (
|
|
7925
|
-
|
|
7926
|
-
|
|
7927
|
-
localToComponentId.set(spec.local.name, soleId);
|
|
8078
|
+
const resolvedId = await resolve(importDecl.source.value, moduleId);
|
|
8079
|
+
if (resolvedId && declaringSet.has(resolvedId)) {
|
|
8080
|
+
const compId = bindingNames.get(resolvedId).get(DEFAULT_SENTINEL);
|
|
8081
|
+
if (compId) localToComponentId.set(spec.local.name, compId);
|
|
7928
8082
|
}
|
|
7929
8083
|
}
|
|
7930
8084
|
}
|
|
@@ -7949,7 +8103,7 @@ function timberPrebuilt(ctx) {
|
|
|
7949
8103
|
if (this?.environment?.name === "client") return;
|
|
7950
8104
|
let program;
|
|
7951
8105
|
try {
|
|
7952
|
-
program =
|
|
8106
|
+
program = ctx.parseCached.parse(code);
|
|
7953
8107
|
} catch {
|
|
7954
8108
|
return;
|
|
7955
8109
|
}
|
|
@@ -7978,16 +8132,28 @@ function timberPrebuilt(ctx) {
|
|
|
7978
8132
|
ctx.prebuiltModuleComponents.set(id, componentIds);
|
|
7979
8133
|
const bindingMap = /* @__PURE__ */ new Map();
|
|
7980
8134
|
let componentIdIdx = 0;
|
|
7981
|
-
for (const { call, displayName } of callSites) if (!ctx.dev && mayPrerender(call))
|
|
7982
|
-
|
|
7983
|
-
|
|
7984
|
-
|
|
8135
|
+
for (const { call, stmt, displayName } of callSites) if (!ctx.dev && mayPrerender(call)) {
|
|
8136
|
+
const compId = componentIds[componentIdIdx++];
|
|
8137
|
+
if (stmt.type === "ExportDefaultDeclaration") bindingMap.set(DEFAULT_SENTINEL, compId);
|
|
8138
|
+
else bindingMap.set(displayName, compId);
|
|
8139
|
+
}
|
|
8140
|
+
for (const stmt of program.body) {
|
|
8141
|
+
if (stmt.type === "ExportDefaultDeclaration" && stmt.declaration && stmt.declaration.type === "Identifier") {
|
|
8142
|
+
const name = stmt.declaration.name;
|
|
8143
|
+
if (bindingMap.has(name) && !bindingMap.has(DEFAULT_SENTINEL)) bindingMap.set(DEFAULT_SENTINEL, bindingMap.get(name));
|
|
8144
|
+
}
|
|
8145
|
+
if (stmt.type === "ExportNamedDeclaration" && Array.isArray(stmt.specifiers)) {
|
|
8146
|
+
for (const spec of stmt.specifiers) if (spec.exported.name === "default") {
|
|
8147
|
+
const compId = bindingMap.get(spec.local.name);
|
|
8148
|
+
if (compId && !bindingMap.has(DEFAULT_SENTINEL)) bindingMap.set(DEFAULT_SENTINEL, compId);
|
|
8149
|
+
}
|
|
8150
|
+
}
|
|
7985
8151
|
}
|
|
7986
8152
|
if (bindingMap.size > 0) {
|
|
7987
8153
|
ctx.prebuiltBindingNames ??= /* @__PURE__ */ new Map();
|
|
7988
8154
|
ctx.prebuiltBindingNames.set(id, bindingMap);
|
|
7989
8155
|
}
|
|
7990
|
-
const declaredBindings = new Set(bindingMap.keys());
|
|
8156
|
+
const declaredBindings = new Set([...bindingMap.keys()].filter((k) => k !== DEFAULT_SENTINEL));
|
|
7991
8157
|
if (declaredBindings.size > 0) {
|
|
7992
8158
|
const usages = collectJsxUsages(program, declaredBindings);
|
|
7993
8159
|
for (const [bindingName, propsList] of usages) {
|
|
@@ -8013,7 +8179,7 @@ function timberPrebuilt(ctx) {
|
|
|
8013
8179
|
* SHARED components work — a component imported by several routes is
|
|
8014
8180
|
* captured under every importing route's param combos.
|
|
8015
8181
|
*/
|
|
8016
|
-
generateBundle(_options, bundle) {
|
|
8182
|
+
async generateBundle(_options, bundle) {
|
|
8017
8183
|
if (ctx.dev) return;
|
|
8018
8184
|
if (this?.environment?.name !== "rsc") return;
|
|
8019
8185
|
ctx.routePrebuiltComponents = void 0;
|
|
@@ -8021,6 +8187,9 @@ function timberPrebuilt(ctx) {
|
|
|
8021
8187
|
if (!moduleComponents || moduleComponents.size === 0 || !ctx.routeTree) return;
|
|
8022
8188
|
const self = this;
|
|
8023
8189
|
const getInfo = (id) => self.getModuleInfo?.(id) ?? null;
|
|
8190
|
+
const resolveFn = async (source, importer) => {
|
|
8191
|
+
return (await self.resolve?.(source, importer))?.id ?? null;
|
|
8192
|
+
};
|
|
8024
8193
|
const routeComponents = /* @__PURE__ */ new Map();
|
|
8025
8194
|
for (const filePath of collectPageAndLayoutFiles(ctx.routeTree.root)) {
|
|
8026
8195
|
const ids = collectReachablePrebuiltComponents(filePath, moduleComponents, getInfo);
|
|
@@ -8054,7 +8223,7 @@ function timberPrebuilt(ctx) {
|
|
|
8054
8223
|
visited.add(modId);
|
|
8055
8224
|
if (!scannedModules.has(modId)) {
|
|
8056
8225
|
scannedModules.add(modId);
|
|
8057
|
-
collectCrossModuleProps(ctx, modId, getInfo);
|
|
8226
|
+
await collectCrossModuleProps(ctx, modId, getInfo, resolveFn);
|
|
8058
8227
|
}
|
|
8059
8228
|
const modInfo = getInfo(modId);
|
|
8060
8229
|
if (!modInfo) continue;
|
|
@@ -8225,7 +8394,7 @@ function timberPrerenderSugar(ctx) {
|
|
|
8225
8394
|
if (!value) return;
|
|
8226
8395
|
let program;
|
|
8227
8396
|
try {
|
|
8228
|
-
program =
|
|
8397
|
+
program = ctx.parseCached.parse(code);
|
|
8229
8398
|
} catch {
|
|
8230
8399
|
return;
|
|
8231
8400
|
}
|
|
@@ -9043,7 +9212,7 @@ var HOLDING_PAGE_HTML = [
|
|
|
9043
9212
|
* ```
|
|
9044
9213
|
*/
|
|
9045
9214
|
function createHoldingServer() {
|
|
9046
|
-
const server =
|
|
9215
|
+
const server = nodeHTTP.createServer((req, res) => {
|
|
9047
9216
|
const accept = req.headers.accept ?? "";
|
|
9048
9217
|
if (accept.includes("application/json") && !accept.includes("text/html")) {
|
|
9049
9218
|
res.writeHead(503, {
|