bun-plugin-stx 0.2.308 → 0.2.310
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/{chunk-ahmasn2t.js → chunk-zt4e86p1.js} +30 -8
- package/dist/serve.d.ts +27 -1
- package/dist/serve.js +87 -14
- package/package.json +1 -1
|
@@ -1468,8 +1468,10 @@ else {
|
|
|
1468
1468
|
// Before the swap: add each sheet the page does not have yet, as
|
|
1469
1469
|
// media="print" so it downloads without restyling the page still on screen.
|
|
1470
1470
|
// Returns a promise for them to finish (load OR error, capped at
|
|
1471
|
-
// cssLoadTimeout so a slow
|
|
1472
|
-
//
|
|
1471
|
+
// cssLoadTimeout so a slow sheet never stalls navigation), or null when
|
|
1472
|
+
// nothing new is needed and the swap can go ahead in the same turn. The
|
|
1473
|
+
// promise resolves false when a sheet failed outright, and the caller then
|
|
1474
|
+
// navigates in full rather than swap in markup that has no styles.
|
|
1473
1475
|
function preloadGeneratedCss(hrefs,base){
|
|
1474
1476
|
var pending=[];
|
|
1475
1477
|
hrefs.forEach(function(href){
|
|
@@ -1490,14 +1492,34 @@ else {
|
|
|
1490
1492
|
link.setAttribute('data-stx-css-pending','');
|
|
1491
1493
|
link.__stxCssReady=new Promise(function(resolve){
|
|
1492
1494
|
var timer=setTimeout(finish,o.cssLoadTimeout);
|
|
1493
|
-
function finish(){
|
|
1494
|
-
|
|
1495
|
-
|
|
1495
|
+
function finish(failed){
|
|
1496
|
+
clearTimeout(timer);link.onload=null;link.onerror=null;link.__stxCssReady=null;
|
|
1497
|
+
// A sheet that failed (a 5xx, a dropped connection) is gone for good:
|
|
1498
|
+
// swapping anyway painted the page with only the utilities the
|
|
1499
|
+
// previous page happened to share, which reads as a broken layout.
|
|
1500
|
+
// Drop the dead <link> so a retry fetches it again, and report it.
|
|
1501
|
+
if(failed===true&&link.parentNode)link.parentNode.removeChild(link);
|
|
1502
|
+
resolve(failed!==true);
|
|
1503
|
+
}
|
|
1504
|
+
link.onload=function(){finish(false)};
|
|
1505
|
+
link.onerror=function(){finish(true)};
|
|
1496
1506
|
});
|
|
1497
1507
|
pending.push(link.__stxCssReady);
|
|
1498
1508
|
document.head.appendChild(link);
|
|
1499
1509
|
});
|
|
1500
|
-
return pending.length?Promise.all(pending):null;
|
|
1510
|
+
return pending.length?Promise.all(pending).then(function(results){return results.every(function(ok){return ok!==false})}):null;
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
// Swap once the destination's sheets are in, or hand the navigation to the
|
|
1514
|
+
// browser when one could not be fetched \u2014 a full load retries the sheet.
|
|
1515
|
+
function afterCss(ready,url,swapNow){
|
|
1516
|
+
if(!ready)return swapNow();
|
|
1517
|
+
return ready.then(function(ok){
|
|
1518
|
+
if(ok)return swapNow();
|
|
1519
|
+
log('[router] stylesheet failed to load \u2014 full navigation to:',url);
|
|
1520
|
+
location.href=url;
|
|
1521
|
+
return false;
|
|
1522
|
+
});
|
|
1501
1523
|
}
|
|
1502
1524
|
|
|
1503
1525
|
// At the swap: make the destination's sheets live and move them to the end
|
|
@@ -1772,7 +1794,7 @@ else {
|
|
|
1772
1794
|
// jumps. Cleanup waits with it so the outgoing page stays live meanwhile.
|
|
1773
1795
|
// With nothing new to load this runs in the same turn, as it always did.
|
|
1774
1796
|
var fragCssReady=preloadGeneratedCss(fragmentCssHrefs(html),location.href);
|
|
1775
|
-
return fragCssReady
|
|
1797
|
+
return afterCss(fragCssReady,url,startFragSwap);
|
|
1776
1798
|
}
|
|
1777
1799
|
|
|
1778
1800
|
// Full document mode: parse with DOMParser and extract container content
|
|
@@ -2214,7 +2236,7 @@ else {
|
|
|
2214
2236
|
});
|
|
2215
2237
|
}
|
|
2216
2238
|
var docCssReady=preloadGeneratedCss(docCssHrefs,docCssBase);
|
|
2217
|
-
return docCssReady
|
|
2239
|
+
return afterCss(docCssReady,url,startSwap);
|
|
2218
2240
|
}
|
|
2219
2241
|
|
|
2220
2242
|
// \u2500\u2500 Link interception \u2500\u2500
|
package/dist/serve.d.ts
CHANGED
|
@@ -11,6 +11,18 @@ export declare function extractServerScriptsFromTemplate(content: string): {
|
|
|
11
11
|
serverScripts: string[]
|
|
12
12
|
templateContent: string
|
|
13
13
|
};
|
|
14
|
+
/** The params as a script-safe object literal. */
|
|
15
|
+
export declare function serializeRouteParams(params: Record<string, string>): string;
|
|
16
|
+
/** Put one request's params into a cached dynamic-route shell. */
|
|
17
|
+
export declare function fillRouteParams(html: string, params: Record<string, string>): string;
|
|
18
|
+
/**
|
|
19
|
+
* Whether a page's server scripts read its route params — `params`, or a
|
|
20
|
+
* segment by its bare name (`[id].stx` binds `id`). Such a page renders per
|
|
21
|
+
* record and must not share one cached render across them. Deliberately
|
|
22
|
+
* coarse: a comment or an unrelated local spelled the same only costs the
|
|
23
|
+
* cache, never correctness.
|
|
24
|
+
*/
|
|
25
|
+
export declare function serverScriptsReadParams(serverScripts: string[], paramNames: string[]): boolean;
|
|
14
26
|
/**
|
|
15
27
|
* A token to mint for this request, or null to leave it alone.
|
|
16
28
|
*
|
|
@@ -191,6 +203,20 @@ export declare function bundleBrowserAsset(entrypoint: string): Promise<Response
|
|
|
191
203
|
* @param options Server options with patterns and port
|
|
192
204
|
*/
|
|
193
205
|
export declare function serve(options: ServeOptions): Promise<void>;
|
|
206
|
+
/**
|
|
207
|
+
* Whether any `.stx` template under `root` uses `<StxImage>` or `@image`.
|
|
208
|
+
*
|
|
209
|
+
* Stops at the first hit. Skips dependencies, build output and hidden
|
|
210
|
+
* directories (the stx state directory among them), so it reads the project's
|
|
211
|
+
* own templates, including a framework's templates vendored into it.
|
|
212
|
+
*/
|
|
213
|
+
export declare function projectUsesImageBuiltins(root: string): Promise<boolean>;
|
|
214
|
+
/**
|
|
215
|
+
* Stands in for a dynamic route's params in a cached shell. Not valid
|
|
216
|
+
* JavaScript on its own, so a shell that escaped without being filled fails
|
|
217
|
+
* loudly instead of running with somebody else's params.
|
|
218
|
+
*/
|
|
219
|
+
export declare const ROUTE_PARAMS_PLACEHOLDER: '__STX_ROUTE_PARAMS__';
|
|
194
220
|
/**
|
|
195
221
|
* The cookie name a double-submit CSRF check reads.
|
|
196
222
|
*
|
|
@@ -258,7 +284,7 @@ export declare interface ServeOptions {
|
|
|
258
284
|
cssCacheLimit?: number
|
|
259
285
|
prewarmRenderCache?: boolean | number
|
|
260
286
|
imageWarmupGraceMs?: number
|
|
261
|
-
imageWarmup?: boolean
|
|
287
|
+
imageWarmup?: boolean | 'auto'
|
|
262
288
|
publicDir?: string
|
|
263
289
|
quiet?: boolean
|
|
264
290
|
autoIncrementPort?: boolean | number
|
package/dist/serve.js
CHANGED
|
@@ -8851,6 +8851,19 @@ function extractServerScriptsFromTemplate(content) {
|
|
|
8851
8851
|
retained.push(content.slice(retainedFrom));
|
|
8852
8852
|
return { serverScripts, templateContent: retained.join("") };
|
|
8853
8853
|
}
|
|
8854
|
+
var ROUTE_PARAMS_PLACEHOLDER = "__STX_ROUTE_PARAMS__";
|
|
8855
|
+
function serializeRouteParams(params) {
|
|
8856
|
+
return JSON.stringify(params).replace(/</g, "\\u003C").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
8857
|
+
}
|
|
8858
|
+
function fillRouteParams(html, params) {
|
|
8859
|
+
const serialized = serializeRouteParams(params);
|
|
8860
|
+
return html.replace(ROUTE_PARAMS_PLACEHOLDER, () => serialized);
|
|
8861
|
+
}
|
|
8862
|
+
function serverScriptsReadParams(serverScripts, paramNames) {
|
|
8863
|
+
const names = ["params", ...paramNames].map((name) => name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
8864
|
+
const pattern = new RegExp(`(?:^|[^\\w$.])(?:${names.join("|")})(?![\\w$])`);
|
|
8865
|
+
return serverScripts.some((script) => pattern.test(script));
|
|
8866
|
+
}
|
|
8854
8867
|
var CSRF_COOKIE = "X-CSRF-Token";
|
|
8855
8868
|
function csrfTokenToMint(req, cookies) {
|
|
8856
8869
|
const method = (req.method || "GET").toUpperCase();
|
|
@@ -9216,7 +9229,7 @@ async function serve(options) {
|
|
|
9216
9229
|
markPlaceholdersReady = resolve13;
|
|
9217
9230
|
});
|
|
9218
9231
|
let placeholdersAreReady = false;
|
|
9219
|
-
const
|
|
9232
|
+
const imageWarmupSetting = options.imageWarmup ?? stxConfig.imageWarmup ?? "auto";
|
|
9220
9233
|
const imageWarmupGraceMs = Math.max(0, options.imageWarmupGraceMs ?? 1000);
|
|
9221
9234
|
function awaitImageWarmup() {
|
|
9222
9235
|
if (placeholdersAreReady || imageWarmupGraceMs === 0)
|
|
@@ -9231,7 +9244,9 @@ async function serve(options) {
|
|
|
9231
9244
|
});
|
|
9232
9245
|
}
|
|
9233
9246
|
const stxModule = options.stxModule ? Promise.resolve(options.stxModule) : defaultStxModule;
|
|
9234
|
-
const
|
|
9247
|
+
const imageCacheDir = process19.env.STX_IMAGE_CACHE_DIR?.trim();
|
|
9248
|
+
const imageDeliveryOutputDir = imageCacheDir ? nodePath.resolve(imageCacheDir, "image-delivery") : stateDir(process19.cwd(), "image-delivery");
|
|
9249
|
+
const imagePlaceholderCachePath = imageCacheDir ? nodePath.resolve(imageCacheDir, "image-placeholders.json") : stateDir(process19.cwd(), "image-placeholders.json");
|
|
9235
9250
|
const imageDeliveryRoot = nodePath.join(imageDeliveryOutputDir, "_stx", "images");
|
|
9236
9251
|
let resolvedStxModule = null;
|
|
9237
9252
|
Promise.resolve(stxModule).then((m) => {
|
|
@@ -9711,7 +9726,7 @@ function __stxOverlay(errs){
|
|
|
9711
9726
|
pageMiddlewarePatterns.length = 0;
|
|
9712
9727
|
await Promise.all(files.map((f) => detectPageMiddleware(f)));
|
|
9713
9728
|
try {
|
|
9714
|
-
const { Router } = await import("./chunk-
|
|
9729
|
+
const { Router } = await import("./chunk-zt4e86p1.js");
|
|
9715
9730
|
const pagesDirs = patterns.map((p) => p.replace(/\/$/, "")).filter(Boolean);
|
|
9716
9731
|
const router = new Router(process19.cwd(), { pagesDirs, stateDir: stateDirName() });
|
|
9717
9732
|
if (!options.quiet)
|
|
@@ -10112,12 +10127,6 @@ function __stxOverlay(errs){
|
|
|
10112
10127
|
if (reqCtx)
|
|
10113
10128
|
reqCtx.responseStatus = extractPageResponseStatus(content) ?? 200;
|
|
10114
10129
|
const { serverScripts: dynServerScripts, templateContent } = extractServerScriptsFromTemplate(content);
|
|
10115
|
-
const context = {
|
|
10116
|
-
__filename: filePath,
|
|
10117
|
-
__dirname: nodePath.dirname(filePath),
|
|
10118
|
-
__route: routePath,
|
|
10119
|
-
__stx_runtime_head: {}
|
|
10120
|
-
};
|
|
10121
10130
|
const paramsObj = {};
|
|
10122
10131
|
for (let i = 0;i < paramNames.length; i++) {
|
|
10123
10132
|
let value = paramValues[i] ?? "";
|
|
@@ -10126,12 +10135,31 @@ function __stxOverlay(errs){
|
|
|
10126
10135
|
} catch {}
|
|
10127
10136
|
paramsObj[paramNames[i]] = value;
|
|
10128
10137
|
}
|
|
10138
|
+
const isMutating = !!reqCtx && reqCtx.method !== "GET" && reqCtx.method !== "HEAD";
|
|
10139
|
+
const skipCacheHint = /(?:^|[^\w$])__stx_skip_cache\s*=\s*true/.test(content);
|
|
10140
|
+
const cacheShell = ENABLE_HTML_CACHE && RENDER_CACHE_VARY === "source" && paramNames.length > 0 && !isMutating && !skipCacheHint && !serverScriptsReadParams(dynServerScripts, paramNames);
|
|
10141
|
+
if (reqCtx)
|
|
10142
|
+
reqCtx.params = paramsObj;
|
|
10143
|
+
if (cacheShell) {
|
|
10144
|
+
const cachedEntry = htmlCache.get(filePath);
|
|
10145
|
+
if (cachedEntry && await templateSignatureFresh(cachedEntry.signature)) {
|
|
10146
|
+
if (reqCtx) {
|
|
10147
|
+
reqCtx.responseStatus = cachedEntry.status;
|
|
10148
|
+
reqCtx.responseHeaders = cachedEntry.headers;
|
|
10149
|
+
}
|
|
10150
|
+
return fillRouteParams(cachedEntry.html, paramsObj);
|
|
10151
|
+
}
|
|
10152
|
+
}
|
|
10153
|
+
const context = {
|
|
10154
|
+
__filename: filePath,
|
|
10155
|
+
__dirname: nodePath.dirname(filePath),
|
|
10156
|
+
__route: routePath,
|
|
10157
|
+
__stx_runtime_head: {}
|
|
10158
|
+
};
|
|
10129
10159
|
for (const name of paramNames) {
|
|
10130
10160
|
context[name] = paramsObj[name];
|
|
10131
10161
|
}
|
|
10132
10162
|
context.params = paramsObj;
|
|
10133
|
-
if (reqCtx)
|
|
10134
|
-
reqCtx.params = paramsObj;
|
|
10135
10163
|
const { processDirectives, extractVariables, defaultConfig: defaultConfig4, injectRouterScript: injectRouter } = await stxModule;
|
|
10136
10164
|
injectServeLocaleContext(context);
|
|
10137
10165
|
injectServeRequestContext(context, reqCtx);
|
|
@@ -10170,7 +10198,7 @@ function __stxOverlay(errs){
|
|
|
10170
10198
|
output = await processDirectives(output, context, filePath, config5, dependencies);
|
|
10171
10199
|
applyRecordedResponse(context, reqCtx);
|
|
10172
10200
|
if (paramNames.length > 0) {
|
|
10173
|
-
const serializedParams =
|
|
10201
|
+
const serializedParams = cacheShell ? ROUTE_PARAMS_PLACEHOLDER : serializeRouteParams(paramsObj);
|
|
10174
10202
|
const paramsScript = `<script data-stx-route-params>(function(){var p=${serializedParams};window.__stx_rp=p;if(window.stx){window.stx._rp=p;if(window.stx.setRouteParams)window.stx.setRouteParams(p)}})()</script>`;
|
|
10175
10203
|
if (output.includes("</head>")) {
|
|
10176
10204
|
output = output.replace("</head>", `${paramsScript}
|
|
@@ -10189,7 +10217,14 @@ ${output}`;
|
|
|
10189
10217
|
return match;
|
|
10190
10218
|
return "";
|
|
10191
10219
|
});
|
|
10192
|
-
|
|
10220
|
+
if (!cacheShell)
|
|
10221
|
+
return output;
|
|
10222
|
+
const recordedResponse = readResponseStatus(context) !== undefined || !!readResponseHeaders(context);
|
|
10223
|
+
if (!context.__stx_skip_cache && !recordedResponse && isRenderableCacheCandidate(output) && placeholdersAreReady) {
|
|
10224
|
+
const signature = await buildTemplateSignature(filePath, dependencies);
|
|
10225
|
+
htmlCache.set(filePath, { html: output, signature, status: reqCtx?.responseStatus ?? 200, headers: reqCtx?.responseHeaders });
|
|
10226
|
+
}
|
|
10227
|
+
return fillRouteParams(output, paramsObj);
|
|
10193
10228
|
}
|
|
10194
10229
|
const startTime = performance.now();
|
|
10195
10230
|
await discoverFiles();
|
|
@@ -10292,6 +10327,7 @@ ${block}`;
|
|
|
10292
10327
|
});
|
|
10293
10328
|
}
|
|
10294
10329
|
async function runImageWarmup() {
|
|
10330
|
+
const imageWarmupEnabled = imageWarmupSetting === "auto" ? await projectUsesImageBuiltins(process19.cwd()) : imageWarmupSetting !== false;
|
|
10295
10331
|
if (!imageWarmupEnabled) {
|
|
10296
10332
|
placeholdersAreReady = true;
|
|
10297
10333
|
markPlaceholdersReady();
|
|
@@ -10302,7 +10338,7 @@ ${block}`;
|
|
|
10302
10338
|
const publicRoot = nodePath.resolve(process19.cwd(), publicDir);
|
|
10303
10339
|
const [placeholderResult, deliveryResult] = await Promise.allSettled([
|
|
10304
10340
|
stx.warmImagePlaceholders(publicRoot, {
|
|
10305
|
-
cachePath:
|
|
10341
|
+
cachePath: imagePlaceholderCachePath
|
|
10306
10342
|
}),
|
|
10307
10343
|
stx.prepareImageDelivery(publicRoot, imageDeliveryOutputDir)
|
|
10308
10344
|
]);
|
|
@@ -11109,20 +11145,56 @@ async function main() {
|
|
|
11109
11145
|
if (import.meta.main) {
|
|
11110
11146
|
main().catch(console.error);
|
|
11111
11147
|
}
|
|
11148
|
+
var IMAGE_BUILTIN_PATTERN = /<(?:StxImage|Image|stx-image|stx-img)[\s/>]|@image\s*\(/;
|
|
11149
|
+
var TEMPLATE_SCAN_SKIP = new Set(["node_modules", "dist", "build", "coverage", "vendor"]);
|
|
11150
|
+
async function projectUsesImageBuiltins(root) {
|
|
11151
|
+
const { readdir: readdir4, readFile } = await import("fs/promises");
|
|
11152
|
+
const pending = [root];
|
|
11153
|
+
while (pending.length > 0) {
|
|
11154
|
+
const dir = pending.pop();
|
|
11155
|
+
let entries;
|
|
11156
|
+
try {
|
|
11157
|
+
entries = await readdir4(dir, { withFileTypes: true });
|
|
11158
|
+
} catch {
|
|
11159
|
+
continue;
|
|
11160
|
+
}
|
|
11161
|
+
for (const entry of entries) {
|
|
11162
|
+
if (entry.name.startsWith("."))
|
|
11163
|
+
continue;
|
|
11164
|
+
const full = nodePath.join(dir, entry.name);
|
|
11165
|
+
if (entry.isDirectory()) {
|
|
11166
|
+
if (!TEMPLATE_SCAN_SKIP.has(entry.name))
|
|
11167
|
+
pending.push(full);
|
|
11168
|
+
continue;
|
|
11169
|
+
}
|
|
11170
|
+
if (!entry.isFile() || !entry.name.endsWith(".stx"))
|
|
11171
|
+
continue;
|
|
11172
|
+
try {
|
|
11173
|
+
if (IMAGE_BUILTIN_PATTERN.test(await readFile(full, "utf8")))
|
|
11174
|
+
return true;
|
|
11175
|
+
} catch {}
|
|
11176
|
+
}
|
|
11177
|
+
}
|
|
11178
|
+
return false;
|
|
11179
|
+
}
|
|
11112
11180
|
export {
|
|
11113
11181
|
usableErrorPage,
|
|
11114
11182
|
staticCacheControl,
|
|
11115
11183
|
sharedScriptCacheControl,
|
|
11184
|
+
serverScriptsReadParams,
|
|
11116
11185
|
serve,
|
|
11186
|
+
serializeRouteParams,
|
|
11117
11187
|
routeSpecificity,
|
|
11118
11188
|
responseTakesCsrfCookie,
|
|
11119
11189
|
resolveServeOpenUrl,
|
|
11120
11190
|
render404Page,
|
|
11121
11191
|
publicFileExists,
|
|
11192
|
+
projectUsesImageBuiltins,
|
|
11122
11193
|
isStaticAssetPath,
|
|
11123
11194
|
isRenderableCacheCandidate,
|
|
11124
11195
|
isRenderFailure,
|
|
11125
11196
|
isProductionServe,
|
|
11197
|
+
fillRouteParams,
|
|
11126
11198
|
extractServerScriptsFromTemplate,
|
|
11127
11199
|
csrfTokenToMint,
|
|
11128
11200
|
csrfCookieHeader,
|
|
@@ -11130,5 +11202,6 @@ export {
|
|
|
11130
11202
|
buildDynamicRouteRegexes,
|
|
11131
11203
|
boundedCache,
|
|
11132
11204
|
SHARED_SCRIPT_PATH,
|
|
11205
|
+
ROUTE_PARAMS_PLACEHOLDER,
|
|
11133
11206
|
CSRF_COOKIE
|
|
11134
11207
|
};
|
package/package.json
CHANGED