bun-plugin-stx 0.2.293 → 0.2.294

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.
@@ -986,8 +986,8 @@ function getRouterScript() {
986
986
  // Unlike a fragment, a document holds the answer directly, so there is
987
987
  // nothing to infer from markers or ask the server for: look for the script.
988
988
  // Both emitted shapes carry data-stx-runtime \u2014 inline, and the serve-mode
989
- // src="/_stx/runtime.js" \u2014 with the content sniff behind it for a document
990
- // rendered by a server old enough not to stamp the attribute.
989
+ // src="/_stx/runtime.<hash>.js" \u2014 with the content sniff behind it for a
990
+ // document rendered by a server old enough not to stamp the attribute.
991
991
  function documentShipsRuntime(doc,html){
992
992
  if(doc&&doc.querySelector&&doc.querySelector('script[data-stx-runtime]'))return true;
993
993
  return html.indexOf("'use strict';var cloakStyle")!==-1
@@ -998,6 +998,22 @@ function getRouterScript() {
998
998
  ||(code.indexOf('_cleanupContainer')!==-1&&code.indexOf('signals runtime loading')!==-1)
999
999
  ||code.indexOf("'use strict';var cloakStyle")!==-1;
1000
1000
  }
1001
+ // The runtime and the router themselves, as external script tags.
1002
+ //
1003
+ // Serve mode links both by content hash, so after a deploy the incoming
1004
+ // page's copy has a different URL from the one this document loaded. Every
1005
+ // path below that loads a script[src] it has not seen yet would then run a
1006
+ // second router and a second runtime over the live ones \u2014 two click
1007
+ // handlers on every link, a fresh window.stx under scopes created by the
1008
+ // old one. A new version is picked up by the build-skew reload instead; it
1009
+ // is never loaded into a running page. The attrs form is for the fragment
1010
+ // path, which sees a tag as text.
1011
+ function isSharedClientScript(el){
1012
+ return !!(el&&el.hasAttribute&&(el.hasAttribute('data-stx-runtime')||el.hasAttribute('data-stx-router')));
1013
+ }
1014
+ function isSharedClientScriptAttrs(attrs){
1015
+ return /(?:^|\\s)data-stx-(?:runtime|router)(?:[\\s=]|$)/i.test(attrs||'');
1016
+ }
1001
1017
  // A fragment never carries the signals runtime: the server strips the runtime
1002
1018
  // IIFE out of it, and every re-execution path here filters it out again. So a
1003
1019
  // page that never needed a runtime cannot hydrate a fragment that does \u2014 the
@@ -1554,7 +1570,7 @@ else {
1554
1570
  // file is where the page's factory usually lives.
1555
1571
  var srcMatch=attrs.match(/\\bsrc\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s>]+))/i);
1556
1572
  var fragSrc=srcMatch&&(srcMatch[1]||srcMatch[2]||srcMatch[3]);
1557
- if(fragSrc){fragExternalScripts.push(fragSrc);return ''}
1573
+ if(fragSrc){if(!isSharedClientScriptAttrs(attrs))fragExternalScripts.push(fragSrc);return ''}
1558
1574
  if(code&&code.trim()&&!isSignalsRuntimeScript({hasAttribute:function(name){return name==='data-stx-runtime'&&attrs.indexOf('data-stx-runtime')!==-1}},code)){
1559
1575
  var slot='fragment-'+(++fragScriptId);
1560
1576
  var scoped=/(?:^|\\s)data-stx-scoped(?:\\s|=|$)/i.test(attrs);
@@ -1902,7 +1918,8 @@ else {
1902
1918
  var text=s.textContent||'';
1903
1919
  // Not discarded: queued against docBase, so a relative src resolves
1904
1920
  // against the page being navigated to rather than the current URL.
1905
- if(s.hasAttribute('src')){routedExternalScripts.push(s.getAttribute('src'));s.remove();return}
1921
+ // The runtime and router are dropped, not queued: see isSharedClientScript.
1922
+ if(s.hasAttribute('src')){if(!isSharedClientScript(s))routedExternalScripts.push(s.getAttribute('src'));s.remove();return}
1906
1923
  var type=(s.getAttribute('type')||'').trim().toLowerCase();
1907
1924
  var executable=!type||type==='text/javascript'||type==='application/javascript'||type==='module';
1908
1925
  if(!executable||!text.trim())return;
@@ -1959,6 +1976,7 @@ else {
1959
1976
  document.querySelectorAll('head script[src]').forEach(function(s){loadedSrcs[s.src]=1});
1960
1977
  var extPromises=[];
1961
1978
  doc.querySelectorAll('head script[src]').forEach(function(s){
1979
+ if(isSharedClientScript(s))return;
1962
1980
  // Fetched-document script: resolve against the page being navigated to
1963
1981
  // (docBase), not the origin root \u2014 same rationale as the <link> reconcile (#1777).
1964
1982
  var src=new URL(s.getAttribute('src'),docBase).href;
package/dist/serve.d.ts CHANGED
@@ -99,6 +99,23 @@ export declare function isProductionServe(): boolean;
99
99
  * given URL, so it gets the long immutable lifetime.
100
100
  */
101
101
  export declare function staticCacheControl(pathname: string, production?: boolean): string;
102
+ /**
103
+ * `Cache-Control` for the shared runtime and router scripts.
104
+ *
105
+ * Pages link them by content hash (getServeClientAsset), so a request whose
106
+ * hash is the current content's can never see different bytes under that URL:
107
+ * cache it for a year.
108
+ *
109
+ * Anything else is answered with today's content but must not be kept. The
110
+ * unhashed URL is what HTML rendered before content addressing still points
111
+ * at, and a stale hash is a page from the previous deploy; either one caching
112
+ * the current bytes would pin them there. `no-cache`, not a short max-age:
113
+ * Cloudflare raises a max-age below its browser-TTL setting (four hours by
114
+ * default) to that setting, which is exactly how the old fixed URL served a
115
+ * previous release's router for hours after a deploy. It leaves `no-cache`
116
+ * alone, and the ETag keeps the revalidation to a 304.
117
+ */
118
+ export declare function sharedScriptCacheControl(requestedHash: string | undefined, currentHash: string): string;
102
119
  /**
103
120
  * Render the built-in fallback 404 page.
104
121
  *
@@ -170,6 +187,8 @@ export declare function serve(options: ServeOptions): Promise<void>;
170
187
  * is the name every double-submit implementation in this ecosystem uses.
171
188
  */
172
189
  export declare const CSRF_COOKIE: 'X-CSRF-Token';
190
+ /** `/_stx/runtime.js`, `/_stx/router.<16 hex>.js`, … — the shared client scripts. */
191
+ export declare const SHARED_SCRIPT_PATH: unknown;
173
192
  /**
174
193
  * Build the candidate regexes for a dynamic route file (stacksjs/stx#1927).
175
194
  *
package/dist/serve.js CHANGED
@@ -8940,6 +8940,10 @@ function staticCacheControl(pathname, production = isProductionServe()) {
8940
8940
  const fingerprinted = /[.-][0-9a-f]{8,}\.[a-z0-9]+$/i.test(pathname);
8941
8941
  return fingerprinted ? "public, max-age=31536000, immutable" : "public, max-age=3600";
8942
8942
  }
8943
+ var SHARED_SCRIPT_PATH = /^\/_stx\/(runtime|router)(?:\.([0-9a-f]{16}))?\.js$/;
8944
+ function sharedScriptCacheControl(requestedHash, currentHash) {
8945
+ return requestedHash === currentHash ? "public, max-age=31536000, immutable" : "no-cache";
8946
+ }
8943
8947
  function render404Page(opts) {
8944
8948
  const { path, routes = [], isProduction } = opts;
8945
8949
  const baseStyles = `
@@ -9700,7 +9704,7 @@ function __stxOverlay(errs){
9700
9704
  pageMiddlewarePatterns.length = 0;
9701
9705
  await Promise.all(files.map((f) => detectPageMiddleware(f)));
9702
9706
  try {
9703
- const { Router } = await import("./chunk-nb1x6zdk.js");
9707
+ const { Router } = await import("./chunk-awcgpf66.js");
9704
9708
  const pagesDirs = patterns.map((p) => p.replace(/\/$/, "")).filter(Boolean);
9705
9709
  const router = new Router(process19.cwd(), { pagesDirs, stateDir: stateDirName() });
9706
9710
  if (!options.quiet)
@@ -10427,13 +10431,15 @@ ${block}`;
10427
10431
  }
10428
10432
  });
10429
10433
  }
10430
- if (path === "/_stx/runtime.js" || path === "/_stx/router.js") {
10434
+ const sharedScript = path.match(SHARED_SCRIPT_PATH);
10435
+ if (sharedScript) {
10431
10436
  const stx = await stxModule;
10432
- const content2 = path === "/_stx/runtime.js" ? await stx.getCachedSignalsRuntime(stxConfig.debug === true) : await stx.getCachedRouterScript();
10433
- const etag = `"${Bun.hash(content2).toString(16)}"`;
10437
+ const asset = await stx.getServeClientAsset(sharedScript[1], sharedScript[1] === "runtime" && stxConfig.debug === true);
10438
+ const content2 = asset.content;
10439
+ const etag = `"${asset.hash}"`;
10434
10440
  const headers = {
10435
10441
  "Content-Type": "application/javascript; charset=utf-8",
10436
- "Cache-Control": production ? "public, max-age=3600, stale-while-revalidate=86400" : "public, max-age=0, must-revalidate",
10442
+ "Cache-Control": sharedScriptCacheControl(sharedScript[2], asset.hash),
10437
10443
  ETag: etag,
10438
10444
  ...corsHeaders
10439
10445
  };
@@ -11090,6 +11096,7 @@ if (import.meta.main) {
11090
11096
  export {
11091
11097
  usableErrorPage,
11092
11098
  staticCacheControl,
11099
+ sharedScriptCacheControl,
11093
11100
  serve,
11094
11101
  routeSpecificity,
11095
11102
  resolveServeOpenUrl,
@@ -11105,5 +11112,6 @@ export {
11105
11112
  bundleBrowserAsset,
11106
11113
  buildDynamicRouteRegexes,
11107
11114
  boundedCache,
11115
+ SHARED_SCRIPT_PATH,
11108
11116
  CSRF_COOKIE
11109
11117
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bun-plugin-stx",
3
3
  "type": "module",
4
- "version": "0.2.293",
4
+ "version": "0.2.294",
5
5
  "description": "A Bun plugin that allows for using Laravel Blade-like syntax.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",