@barefootjs/hono 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/hono-adapter.d.ts.map +1 -1
- package/dist/adapter/index.js +4 -1
- package/dist/app.d.ts +1 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +23 -2
- package/dist/build.js +4 -1
- package/dist/client-shim.d.ts +1 -1
- package/dist/client-shim.d.ts.map +1 -1
- package/dist/client-shim.js +3 -1
- package/dist/index.js +4 -1
- package/dist/jsx/jsx-runtime/index.d.ts +0 -12
- package/dist/jsx/jsx-runtime/index.d.ts.map +1 -1
- package/dist/request-env.d.ts +76 -0
- package/dist/request-env.d.ts.map +1 -0
- package/dist/request-env.js +34 -0
- package/dist/scripts.js +25 -4
- package/dist/search-params-ssr.d.ts +24 -0
- package/dist/search-params-ssr.d.ts.map +1 -0
- package/dist/test-render.d.ts.map +1 -1
- package/package.json +6 -2
- package/src/__tests__/async-error-boundary.test.ts +4 -0
- package/src/__tests__/request-env.test.tsx +139 -0
- package/src/__tests__/search-params-ssr.test.ts +23 -0
- package/src/adapter/hono-adapter.ts +4 -1
- package/src/app.ts +3 -0
- package/src/client-shim.ts +6 -0
- package/src/jsx/jsx-runtime/index.ts +5 -12
- package/src/request-env.ts +123 -0
- package/src/search-params-ssr.ts +44 -0
- package/src/test-render.ts +17 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hono-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/hono-adapter.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"hono-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/hono-adapter.ts"],"names":[],"mappings":"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"}
|
package/dist/adapter/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
emitAttrValue,
|
|
8
8
|
buildLoopChainExpr
|
|
9
9
|
} from "@barefootjs/jsx";
|
|
10
|
-
import { BF_SCOPE, BF_HOST, BF_AT, BF_ROOT, BF_PROPS } from "@barefootjs/shared";
|
|
10
|
+
import { BF_SCOPE, BF_HOST, BF_AT, BF_ROOT, BF_PROPS, BF_REGION } from "@barefootjs/shared";
|
|
11
11
|
function applyHonoLoopChain(loop) {
|
|
12
12
|
return buildLoopChainExpr({
|
|
13
13
|
base: loop.array,
|
|
@@ -387,6 +387,9 @@ export default ${this.componentName}` : "";
|
|
|
387
387
|
if (element.slotId) {
|
|
388
388
|
hydrationAttrs += ` bf="${element.slotId}"`;
|
|
389
389
|
}
|
|
390
|
+
if (element.regionId) {
|
|
391
|
+
hydrationAttrs += ` ${BF_REGION}="${element.regionId}"`;
|
|
392
|
+
}
|
|
390
393
|
if (children) {
|
|
391
394
|
return `<${tag}${attrs}${hydrationAttrs}>${children}</${tag}>`;
|
|
392
395
|
} else {
|
package/dist/app.d.ts
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
import type { MiddlewareHandler } from 'hono';
|
|
29
29
|
import type { HtmlEscapedString } from 'hono/utils/html';
|
|
30
30
|
import { type ImportMapManifest } from '@barefootjs/jsx/import-map';
|
|
31
|
+
import './search-params-ssr.ts';
|
|
31
32
|
/**
|
|
32
33
|
* Build manifest shape produced by `bf build`. Each compiled
|
|
33
34
|
* component is keyed by its manifest name; `__barefoot__` is the
|
package/dist/app.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAE7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAIxD,OAAO,EAAuB,KAAK,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAE7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAIxD,OAAO,EAAuB,KAAK,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AAIxF,OAAO,wBAAwB,CAAA;AAM/B;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,qBAAqB;IACpC,YAAY,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACpC,CAAC,aAAa,EAAE,MAAM,GAAG;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,SAAS,CAAA;CAChF;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,qBAAqB,EAC/B,IAAI,EAAE,MAAM,GACX,MAAM,EAAE,CAWV;AAED,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3D;AAID,MAAM,WAAW,gBAAgB;IAC/B,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,EAAE,iBAAiB,CAAA;IAC7B;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAanG;AAED,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAA;IACZ,6DAA6D;IAC7D,QAAQ,EAAE,qBAAqB,CAAA;CAChC;AAQD;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAa/F;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,GAAE,gBAAqB,GAAG,iBAAiB,GAAG,IAAI,CAelF;AAID,MAAM,WAAW,wBAAwB;IACvC,yBAAyB;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;;;OAMG;IACH,OAAO,EAAE,OAAO,CAAA;CACjB;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,wBAAwB,GAAG,iBAAiB,CAanF"}
|
package/dist/app.js
CHANGED
|
@@ -63,8 +63,29 @@ data: ${BOOT_ID}
|
|
|
63
63
|
|
|
64
64
|
// src/app.ts
|
|
65
65
|
import { html, raw } from "hono/html";
|
|
66
|
-
import { useRequestContext } from "hono/jsx-renderer";
|
|
66
|
+
import { useRequestContext as useRequestContext2 } from "hono/jsx-renderer";
|
|
67
67
|
import { renderImportMapHtml } from "@barefootjs/jsx/import-map";
|
|
68
|
+
|
|
69
|
+
// src/search-params-ssr.ts
|
|
70
|
+
import { useRequestContext } from "hono/jsx-renderer";
|
|
71
|
+
function searchFromRequestUrl(url) {
|
|
72
|
+
try {
|
|
73
|
+
return new URL(url).search;
|
|
74
|
+
} catch {
|
|
75
|
+
return "";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
globalThis.__bf_serverEnvReader = (key) => {
|
|
79
|
+
if (key !== "search")
|
|
80
|
+
return;
|
|
81
|
+
try {
|
|
82
|
+
return searchFromRequestUrl(useRequestContext().req.url);
|
|
83
|
+
} catch {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// src/app.ts
|
|
68
89
|
var DEV_RELOAD_ENDPOINT_KEY = "bfDevReloadEndpoint";
|
|
69
90
|
function manifestToScriptUrls(manifest, base) {
|
|
70
91
|
const out = [];
|
|
@@ -107,7 +128,7 @@ function BfDevReload(props = {}) {
|
|
|
107
128
|
let endpoint = props.endpoint;
|
|
108
129
|
if (!endpoint) {
|
|
109
130
|
try {
|
|
110
|
-
endpoint =
|
|
131
|
+
endpoint = useRequestContext2().get(DEV_RELOAD_ENDPOINT_KEY);
|
|
111
132
|
} catch {}
|
|
112
133
|
}
|
|
113
134
|
if (!endpoint)
|
package/dist/build.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
emitAttrValue,
|
|
8
8
|
buildLoopChainExpr
|
|
9
9
|
} from "@barefootjs/jsx";
|
|
10
|
-
import { BF_SCOPE, BF_HOST, BF_AT, BF_ROOT, BF_PROPS } from "@barefootjs/shared";
|
|
10
|
+
import { BF_SCOPE, BF_HOST, BF_AT, BF_ROOT, BF_PROPS, BF_REGION } from "@barefootjs/shared";
|
|
11
11
|
function applyHonoLoopChain(loop) {
|
|
12
12
|
return buildLoopChainExpr({
|
|
13
13
|
base: loop.array,
|
|
@@ -387,6 +387,9 @@ export default ${this.componentName}` : "";
|
|
|
387
387
|
if (element.slotId) {
|
|
388
388
|
hydrationAttrs += ` bf="${element.slotId}"`;
|
|
389
389
|
}
|
|
390
|
+
if (element.regionId) {
|
|
391
|
+
hydrationAttrs += ` ${BF_REGION}="${element.regionId}"`;
|
|
392
|
+
}
|
|
390
393
|
if (children) {
|
|
391
394
|
return `<${tag}${attrs}${hydrationAttrs}>${children}</${tag}>`;
|
|
392
395
|
} else {
|
package/dist/client-shim.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import type { Context as HonoContext } from 'hono/jsx';
|
|
18
18
|
import type { Context } from '@barefootjs/client';
|
|
19
|
-
export { createContext, splitProps, forwardProps, unwrap, __slot, } from '@barefootjs/client';
|
|
19
|
+
export { createContext, splitProps, forwardProps, unwrap, __slot, searchParams, } from '@barefootjs/client';
|
|
20
20
|
export type { Context, Reactive, Signal, Memo, CleanupFn, EffectFn, SlotMarker, Portal, PortalChildren, PortalOptions, Renderable, } from '@barefootjs/client';
|
|
21
21
|
/**
|
|
22
22
|
* Lazily map a BarefootJS Context object to a Hono context. The mapping is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-shim.d.ts","sourceRoot":"","sources":["../src/client-shim.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,OAAO,KAAK,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,UAAU,CAAA;AACtD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAEjD,OAAO,EACL,aAAa,EACb,UAAU,EACV,YAAY,EACZ,MAAM,EACN,MAAM,
|
|
1
|
+
{"version":3,"file":"client-shim.d.ts","sourceRoot":"","sources":["../src/client-shim.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,OAAO,KAAK,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,UAAU,CAAA;AACtD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAEjD,OAAO,EACL,aAAa,EACb,UAAU,EACV,YAAY,EACZ,MAAM,EACN,MAAM,EAMN,YAAY,GACb,MAAM,oBAAoB,CAAA;AAE3B,YAAY,EACV,OAAO,EACP,QAAQ,EACR,MAAM,EACN,IAAI,EACJ,SAAS,EACT,QAAQ,EACR,UAAU,EACV,MAAM,EACN,cAAc,EACd,aAAa,EACb,UAAU,GACX,MAAM,oBAAoB,CAAA;AAQ3B;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,GAAG,SAAS,CAAC,CAO/E;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAKlD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,IAAI,CAErE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EACjC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,EACjB,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,OAAO,GAChB,OAAO,CAGT;AAYD,wBAAgB,YAAY,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC,GAAG,KAAK,CAEnD;AACD,wBAAgB,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,KAAK,CAEjD;AACD,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,IAAI,GAAG,KAAK,CAEnD;AACD,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,IAAI,GAAG,KAAK,CAE7D;AACD,wBAAgB,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,CAEpE;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,IAAI,GAAG,IAAI,CAE7C;AACD,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,IAAI,GAAG,IAAI,CAE/C;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAEzC;AACD,wBAAgB,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAEvC;AAMD,wBAAgB,YAAY,CAC1B,SAAS,EAAE,OAAO,EAClB,UAAU,CAAC,EAAE,OAAO,EACpB,QAAQ,CAAC,EAAE,OAAO,GACjB,KAAK,CAEP;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAEtD;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,CAEzE;AAED,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAEhE"}
|
package/dist/client-shim.js
CHANGED
|
@@ -5,7 +5,8 @@ import {
|
|
|
5
5
|
splitProps,
|
|
6
6
|
forwardProps,
|
|
7
7
|
unwrap,
|
|
8
|
-
__slot
|
|
8
|
+
__slot,
|
|
9
|
+
searchParams
|
|
9
10
|
} from "@barefootjs/client";
|
|
10
11
|
var honoCtxBridge = new WeakMap;
|
|
11
12
|
function getHonoContext(bfCtx) {
|
|
@@ -69,6 +70,7 @@ export {
|
|
|
69
70
|
unwrap,
|
|
70
71
|
untrack,
|
|
71
72
|
splitProps,
|
|
73
|
+
searchParams,
|
|
72
74
|
provideContextSSR,
|
|
73
75
|
provideContext,
|
|
74
76
|
onMount,
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
emitAttrValue,
|
|
8
8
|
buildLoopChainExpr
|
|
9
9
|
} from "@barefootjs/jsx";
|
|
10
|
-
import { BF_SCOPE, BF_HOST, BF_AT, BF_ROOT, BF_PROPS } from "@barefootjs/shared";
|
|
10
|
+
import { BF_SCOPE, BF_HOST, BF_AT, BF_ROOT, BF_PROPS, BF_REGION } from "@barefootjs/shared";
|
|
11
11
|
function applyHonoLoopChain(loop) {
|
|
12
12
|
return buildLoopChainExpr({
|
|
13
13
|
base: loop.array,
|
|
@@ -387,6 +387,9 @@ export default ${this.componentName}` : "";
|
|
|
387
387
|
if (element.slotId) {
|
|
388
388
|
hydrationAttrs += ` bf="${element.slotId}"`;
|
|
389
389
|
}
|
|
390
|
+
if (element.regionId) {
|
|
391
|
+
hydrationAttrs += ` ${BF_REGION}="${element.regionId}"`;
|
|
392
|
+
}
|
|
390
393
|
if (children) {
|
|
391
394
|
return `<${tag}${attrs}${hydrationAttrs}>${children}</${tag}>`;
|
|
392
395
|
} else {
|
|
@@ -17,16 +17,4 @@ export declare namespace JSX {
|
|
|
17
17
|
type IntrinsicAttributes = BaseJSX.IntrinsicAttributes;
|
|
18
18
|
type ElementChildrenAttribute = BaseJSX.ElementChildrenAttribute;
|
|
19
19
|
}
|
|
20
|
-
/**
|
|
21
|
-
* BarefootJS compiler built-in: streaming async boundary.
|
|
22
|
-
*
|
|
23
|
-
* The compiler intercepts `<Async fallback={...}>` in JSX source and emits it
|
|
24
|
-
* as a `<Suspense>` node in the Hono adapter output (IRAsync → renderAsync).
|
|
25
|
-
* This declaration provides TypeScript types for source files; no runtime
|
|
26
|
-
* implementation is needed because the compiler replaces it before execution.
|
|
27
|
-
*/
|
|
28
|
-
export declare function Async(props: {
|
|
29
|
-
fallback: JSX.Element;
|
|
30
|
-
children: JSX.Element | JSX.Element[] | null | undefined;
|
|
31
|
-
}): JSX.Element;
|
|
32
20
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/jsx/jsx-runtime/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAG3F,OAAO,KAAK,EAAE,GAAG,IAAI,OAAO,EAAE,MAAM,6BAA6B,CAAA;AAEjE,MAAM,CAAC,OAAO,WAAW,GAAG,CAAC,CAAC;IAE5B,KAAK,OAAO,GAAG,OAAO,sBAAsB,EAAE,GAAG,CAAC,OAAO,CAAA;IAGzD,KAAK,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAA;IAClD,KAAK,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAA;IACtD,KAAK,wBAAwB,GAAG,OAAO,CAAC,wBAAwB,CAAA;CACjE
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/jsx/jsx-runtime/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAG3F,OAAO,KAAK,EAAE,GAAG,IAAI,OAAO,EAAE,MAAM,6BAA6B,CAAA;AAEjE,MAAM,CAAC,OAAO,WAAW,GAAG,CAAC,CAAC;IAE5B,KAAK,OAAO,GAAG,OAAO,sBAAsB,EAAE,GAAG,CAAC,OAAO,CAAA;IAGzD,KAAK,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAA;IAClD,KAAK,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAA;IACtD,KAAK,wBAAwB,GAAG,OAAO,CAAC,wBAAwB,CAAA;CACjE"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request-scoped environment values for the framework-agnostic `renderToHtml`
|
|
3
|
+
* path — the non-Hono JS hosts (h3 / Elysia / any WinterCG handler) that render
|
|
4
|
+
* BarefootJS components as plain `hono/jsx` without Hono's `jsxRenderer`.
|
|
5
|
+
* spec/router.md v0.5 ("The wedge"), #1922.
|
|
6
|
+
*
|
|
7
|
+
* Hono resolves a request's environment through `useRequestContext()` inside its
|
|
8
|
+
* `jsxRenderer` async context. `renderToHtml` has no such context, so without
|
|
9
|
+
* this an env signal (`searchParams()`, …) resolves to its empty default
|
|
10
|
+
* regardless of the request — query-dependent initial content flashes /
|
|
11
|
+
* mismatches on hydration (the client then corrects it from the live browser
|
|
12
|
+
* state).
|
|
13
|
+
*
|
|
14
|
+
* A process-wide reader mutated per request would RACE across concurrent
|
|
15
|
+
* requests (the spec explicitly forbids it), so the values are scoped with a
|
|
16
|
+
* Node `AsyncLocalStorage`: each render runs inside `runWithRequestEnv(env, …)`
|
|
17
|
+
* and the reader returns the current async context's value — race-free.
|
|
18
|
+
*
|
|
19
|
+
* The reader is published on the shared, KEYED `globalThis.__bf_serverEnvReader`
|
|
20
|
+
* seam (the same one Hono's auto-wire uses, and which `@barefootjs/client`'s env
|
|
21
|
+
* signals read on the server) — never by importing `@barefootjs/client`, so this
|
|
22
|
+
* module can't pull a second client instance. One seam serves every env signal,
|
|
23
|
+
* so a new signal (cookies, …) is a new field on {@link BfRequestEnv}, not a new
|
|
24
|
+
* wrapper function. We install at most once and capture whatever reader was
|
|
25
|
+
* already on the seam (e.g. Hono's `useRequestContext` reader): when no render
|
|
26
|
+
* scope is active — or this env doesn't carry the requested key — we delegate to
|
|
27
|
+
* it, so a process mixing Hono and `renderToHtml` hosts keeps resolving both ways.
|
|
28
|
+
*
|
|
29
|
+
* Lives behind its own subpath (`@barefootjs/hono/request-env`), imported only
|
|
30
|
+
* by the hosts that opt in, so the always-on `renderToHtml` path never loads
|
|
31
|
+
* `node:async_hooks`.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* Per-request environment values, keyed to match the `@barefootjs/client` env
|
|
35
|
+
* signals. A new env signal adds a field here; the host passes one more value —
|
|
36
|
+
* no new wrapper function.
|
|
37
|
+
*/
|
|
38
|
+
export interface BfRequestEnv {
|
|
39
|
+
/**
|
|
40
|
+
* The request's query string, with or without a leading `?` (e.g.
|
|
41
|
+
* `new URL(req.url).search` on Elysia, `getRequestURL(event).search` on h3).
|
|
42
|
+
* Backs `searchParams()`; `URLSearchParams` strips the `?`.
|
|
43
|
+
*/
|
|
44
|
+
search?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Run `fn` — typically `() => renderToHtml(node)` — with the given request
|
|
48
|
+
* environment bound for the duration of its async context, so env signals
|
|
49
|
+
* (`searchParams()`, …) resolve this request's values at SSR. The binding
|
|
50
|
+
* propagates across the awaits inside `renderToHtml` (including `async`
|
|
51
|
+
* components), and concurrent renders each see their own `env` — never a shared
|
|
52
|
+
* per-request global (which the spec forbids because it races).
|
|
53
|
+
*
|
|
54
|
+
* Usage:
|
|
55
|
+
*
|
|
56
|
+
* const html = await runWithRequestEnv(
|
|
57
|
+
* { search: new URL(req.url).search },
|
|
58
|
+
* () => renderToHtml(<Layout …>…</Layout>),
|
|
59
|
+
* )
|
|
60
|
+
*/
|
|
61
|
+
export declare function runWithRequestEnv<T>(env: BfRequestEnv, fn: () => T): T;
|
|
62
|
+
/**
|
|
63
|
+
* Wrap a WinterCG `fetch` handler so the whole request runs with its env bound —
|
|
64
|
+
* env signals (`searchParams()`, …) resolve this request's values at SSR with no
|
|
65
|
+
* per-render plumbing. Bind ONCE at the entry point and every `renderToHtml`
|
|
66
|
+
* inside (across routing, handlers, `async` components) inherits it via the
|
|
67
|
+
* async context; concurrent requests never race.
|
|
68
|
+
*
|
|
69
|
+
* export default { port, fetch: withRequestEnv(handler) }
|
|
70
|
+
*
|
|
71
|
+
* Extra args (the framework / Bun server, Workers `env` / `ctx`) pass through
|
|
72
|
+
* unchanged. The env is derived from the `Request` by {@link requestEnv}, so the
|
|
73
|
+
* host never names env keys — a future signal (cookies, …) needs no host change.
|
|
74
|
+
*/
|
|
75
|
+
export declare function withRequestEnv<A extends unknown[]>(handler: (request: Request, ...args: A) => Response | Promise<Response>): (request: Request, ...args: A) => Promise<Response>;
|
|
76
|
+
//# sourceMappingURL=request-env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-env.d.ts","sourceRoot":"","sources":["../src/request-env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAIH;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AA0BD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,GAAG,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAGtE;AAaD;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,OAAO,EAAE,EAChD,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,CAAC,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,GACtE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAGrD"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// src/request-env.ts
|
|
2
|
+
var {AsyncLocalStorage} = (() => ({}));
|
|
3
|
+
var envStore = new AsyncLocalStorage;
|
|
4
|
+
var installed = false;
|
|
5
|
+
function ensureReaderInstalled() {
|
|
6
|
+
if (installed)
|
|
7
|
+
return;
|
|
8
|
+
installed = true;
|
|
9
|
+
const holder = globalThis;
|
|
10
|
+
const prior = holder.__bf_serverEnvReader;
|
|
11
|
+
holder.__bf_serverEnvReader = (key) => {
|
|
12
|
+
const env = envStore.getStore();
|
|
13
|
+
if (env) {
|
|
14
|
+
const value = env[key];
|
|
15
|
+
if (value !== undefined)
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
return typeof prior === "function" ? prior(key) : undefined;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function runWithRequestEnv(env, fn) {
|
|
22
|
+
ensureReaderInstalled();
|
|
23
|
+
return envStore.run(env, fn);
|
|
24
|
+
}
|
|
25
|
+
function requestEnv(request) {
|
|
26
|
+
return { search: new URL(request.url).search };
|
|
27
|
+
}
|
|
28
|
+
function withRequestEnv(handler) {
|
|
29
|
+
return (request, ...args) => runWithRequestEnv(requestEnv(request), async () => handler(request, ...args));
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
withRequestEnv,
|
|
33
|
+
runWithRequestEnv
|
|
34
|
+
};
|
package/dist/scripts.js
CHANGED
|
@@ -63,8 +63,29 @@ data: ${BOOT_ID}
|
|
|
63
63
|
|
|
64
64
|
// src/app.ts
|
|
65
65
|
import { html, raw } from "hono/html";
|
|
66
|
-
import { useRequestContext } from "hono/jsx-renderer";
|
|
66
|
+
import { useRequestContext as useRequestContext2 } from "hono/jsx-renderer";
|
|
67
67
|
import { renderImportMapHtml } from "@barefootjs/jsx/import-map";
|
|
68
|
+
|
|
69
|
+
// src/search-params-ssr.ts
|
|
70
|
+
import { useRequestContext } from "hono/jsx-renderer";
|
|
71
|
+
function searchFromRequestUrl(url) {
|
|
72
|
+
try {
|
|
73
|
+
return new URL(url).search;
|
|
74
|
+
} catch {
|
|
75
|
+
return "";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
globalThis.__bf_serverEnvReader = (key) => {
|
|
79
|
+
if (key !== "search")
|
|
80
|
+
return;
|
|
81
|
+
try {
|
|
82
|
+
return searchFromRequestUrl(useRequestContext().req.url);
|
|
83
|
+
} catch {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// src/app.ts
|
|
68
89
|
var DEV_RELOAD_ENDPOINT_KEY = "bfDevReloadEndpoint";
|
|
69
90
|
function manifestToScriptUrls(manifest, base) {
|
|
70
91
|
const out = [];
|
|
@@ -107,7 +128,7 @@ function BfDevReload(props = {}) {
|
|
|
107
128
|
let endpoint = props.endpoint;
|
|
108
129
|
if (!endpoint) {
|
|
109
130
|
try {
|
|
110
|
-
endpoint =
|
|
131
|
+
endpoint = useRequestContext2().get(DEV_RELOAD_ENDPOINT_KEY);
|
|
111
132
|
} catch {}
|
|
112
133
|
}
|
|
113
134
|
if (!endpoint)
|
|
@@ -132,12 +153,12 @@ function barefootDevReload(opts) {
|
|
|
132
153
|
}
|
|
133
154
|
|
|
134
155
|
// src/scripts.tsx
|
|
135
|
-
import { useRequestContext as
|
|
156
|
+
import { useRequestContext as useRequestContext3 } from "hono/jsx-renderer";
|
|
136
157
|
import { Fragment } from "hono/jsx";
|
|
137
158
|
import { jsxDEV } from "hono/jsx/jsx-dev-runtime";
|
|
138
159
|
function BfScripts2(props = {}) {
|
|
139
160
|
try {
|
|
140
|
-
const c =
|
|
161
|
+
const c = useRequestContext3();
|
|
141
162
|
c.set("bfScriptsRendered", true);
|
|
142
163
|
const scripts = c.get("bfCollectedScripts") || [];
|
|
143
164
|
const outputSet = c.get("bfOutputScripts") || new Set;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-wire request-scoped `searchParams()` for Hono SSR (spec/router.md "The
|
|
3
|
+
* wedge", v0.5).
|
|
4
|
+
*
|
|
5
|
+
* `@barefootjs/client`'s `searchParams()` is environment-neutral: on the server
|
|
6
|
+
* it asks for the current request's query string through a reader seam. Hono
|
|
7
|
+
* exposes the active request via `useRequestContext()` (async-context scoped),
|
|
8
|
+
* so the reader resolves **per request** with no shared mutable state to race.
|
|
9
|
+
*
|
|
10
|
+
* We publish the reader on the keyed `globalThis.__bf_serverEnvReader` seam —
|
|
11
|
+
* the server-side analogue of the client's `window.__bf_*` seams — rather than
|
|
12
|
+
* importing `@barefootjs/client`. SSR templates resolve the `@barefootjs/client`
|
|
13
|
+
* specifier through the adapter's shim, so this infrastructure module stays
|
|
14
|
+
* decoupled from the client package while still wiring it. This module is
|
|
15
|
+
* imported for its side effect by `app.ts`, so any Hono SSR app that renders
|
|
16
|
+
* the BarefootJS scripts gets request-scoped `searchParams()` with no opt-in.
|
|
17
|
+
*
|
|
18
|
+
* The reader is keyed by env id, so one seam serves every env signal
|
|
19
|
+
* (`searchParams()` via `'search'` today; cookies, … later). Unknown keys
|
|
20
|
+
* return `undefined` to defer to the empty default.
|
|
21
|
+
*/
|
|
22
|
+
/** Extract the query string (including leading `?`, or `''`) from a request URL. */
|
|
23
|
+
export declare function searchFromRequestUrl(url: string): string;
|
|
24
|
+
//# sourceMappingURL=search-params-ssr.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-params-ssr.d.ts","sourceRoot":"","sources":["../src/search-params-ssr.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,oFAAoF;AACpF,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMxD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-render.d.ts","sourceRoot":"","sources":["../src/test-render.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAStD,MAAM,WAAW,aAAa;IAC5B,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,8BAA8B;IAC9B,OAAO,EAAE,eAAe,CAAA;IACxB,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC;;;;;;;;OAQG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACzC;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AA2BD,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"test-render.d.ts","sourceRoot":"","sources":["../src/test-render.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAStD,MAAM,WAAW,aAAa;IAC5B,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,8BAA8B;IAC9B,OAAO,EAAE,eAAe,CAAA;IACxB,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC;;;;;;;;OAQG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACzC;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AA2BD,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAsLjF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/hono",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Hono integration for BarefootJS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -76,6 +76,10 @@
|
|
|
76
76
|
"./render": {
|
|
77
77
|
"types": "./dist/render.d.ts",
|
|
78
78
|
"import": "./dist/render.js"
|
|
79
|
+
},
|
|
80
|
+
"./request-env": {
|
|
81
|
+
"types": "./dist/request-env.d.ts",
|
|
82
|
+
"import": "./dist/request-env.js"
|
|
79
83
|
}
|
|
80
84
|
},
|
|
81
85
|
"files": [
|
|
@@ -84,7 +88,7 @@
|
|
|
84
88
|
],
|
|
85
89
|
"scripts": {
|
|
86
90
|
"build": "bun run build:js && bun run build:types",
|
|
87
|
-
"build:js": "bun build ./src/index.ts ./src/adapter/index.ts ./src/scripts.tsx ./src/portals.tsx ./src/portal-ssr.tsx ./src/dialog-context.tsx ./src/client-shim.ts ./src/preload.tsx ./src/dev.tsx ./src/dev-worker.ts ./src/jsx/jsx-runtime/index.ts ./src/jsx/jsx-dev-runtime/index.ts ./src/async.tsx ./src/utils.ts ./src/build.ts ./src/app.ts ./src/render.ts --root ./src --outdir ./dist --format esm --external hono --external @barefootjs/client --external @barefootjs/jsx --external @barefootjs/shared",
|
|
91
|
+
"build:js": "bun build ./src/index.ts ./src/adapter/index.ts ./src/scripts.tsx ./src/portals.tsx ./src/portal-ssr.tsx ./src/dialog-context.tsx ./src/client-shim.ts ./src/preload.tsx ./src/dev.tsx ./src/dev-worker.ts ./src/jsx/jsx-runtime/index.ts ./src/jsx/jsx-dev-runtime/index.ts ./src/async.tsx ./src/utils.ts ./src/build.ts ./src/app.ts ./src/render.ts ./src/request-env.ts --root ./src --outdir ./dist --format esm --external hono --external @barefootjs/client --external @barefootjs/jsx --external @barefootjs/shared",
|
|
88
92
|
"build:types": "tsgo --emitDeclarationOnly --outDir ./dist",
|
|
89
93
|
"test": "bun test",
|
|
90
94
|
"clean": "rm -rf dist",
|
|
@@ -29,6 +29,7 @@ function templateOf(source: string): string {
|
|
|
29
29
|
describe('compiled <Async> boundary — ErrorBoundary wrapping (#1375)', () => {
|
|
30
30
|
test('emits ErrorBoundary enclosing Suspense, sharing the fallback', () => {
|
|
31
31
|
const template = templateOf(`
|
|
32
|
+
import { Async } from '@barefootjs/client'
|
|
32
33
|
export function Page() {
|
|
33
34
|
return (
|
|
34
35
|
<div>
|
|
@@ -52,6 +53,7 @@ describe('compiled <Async> boundary — ErrorBoundary wrapping (#1375)', () => {
|
|
|
52
53
|
|
|
53
54
|
test('injects the aliased ErrorBoundary import alongside Suspense', () => {
|
|
54
55
|
const template = templateOf(`
|
|
56
|
+
import { Async } from '@barefootjs/client'
|
|
55
57
|
export function Page() {
|
|
56
58
|
return (
|
|
57
59
|
<Async fallback={<p>Loading...</p>}>
|
|
@@ -71,6 +73,7 @@ describe('compiled <Async> boundary — ErrorBoundary wrapping (#1375)', () => {
|
|
|
71
73
|
// injected Hono import to duplicate the user's own `ErrorBoundary`
|
|
72
74
|
// binding (which would be a "Duplicate identifier" build error).
|
|
73
75
|
const template = templateOf(`
|
|
76
|
+
import { Async } from '@barefootjs/client'
|
|
74
77
|
import { ErrorBoundary } from './my-error-boundary'
|
|
75
78
|
export function Page() {
|
|
76
79
|
return (
|
|
@@ -91,6 +94,7 @@ describe('compiled <Async> boundary — ErrorBoundary wrapping (#1375)', () => {
|
|
|
91
94
|
|
|
92
95
|
test('each of multiple boundaries gets its own ErrorBoundary wrapper', () => {
|
|
93
96
|
const template = templateOf(`
|
|
97
|
+
import { Async } from '@barefootjs/client'
|
|
94
98
|
export function Dashboard() {
|
|
95
99
|
return (
|
|
96
100
|
<div>
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/** @jsxImportSource hono/jsx */
|
|
2
|
+
/**
|
|
3
|
+
* Request-scoped env signals for the framework-agnostic `renderToHtml` path
|
|
4
|
+
* (h3 / Elysia / … hosts), spec/router.md v0.5, #1922.
|
|
5
|
+
*
|
|
6
|
+
* `runWithRequestEnv({ search }, () => renderToHtml(node))` binds the request's
|
|
7
|
+
* env (here `searchParams()`) for that render's async context, so a host that
|
|
8
|
+
* bypasses Hono's jsxRenderer still resolves the live query at SSR — and
|
|
9
|
+
* concurrent renders never see each other's values (the spec forbids a
|
|
10
|
+
* process-wide per-request global because it races). The env object is keyed, so
|
|
11
|
+
* future signals (cookies, …) ride the same wrapper.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { describe, test, expect, beforeAll } from 'bun:test'
|
|
15
|
+
import { searchParams } from '@barefootjs/client'
|
|
16
|
+
import { renderToHtml } from '../render'
|
|
17
|
+
import { runWithRequestEnv, withRequestEnv } from '../request-env'
|
|
18
|
+
|
|
19
|
+
// A plain hono/jsx component that reads the env signal at SSR — exactly what a
|
|
20
|
+
// compiled BarefootJS component lowers `{searchParams().get('sort') ?? 'none'}`
|
|
21
|
+
// to on the Hono adapter.
|
|
22
|
+
const SortLabel = () => <p>{searchParams().get('sort') ?? 'none'}</p>
|
|
23
|
+
|
|
24
|
+
// Install a prior keyed reader on the seam BEFORE the first `runWithRequestEnv`
|
|
25
|
+
// call below — `request-env` captures whatever reader is already there at
|
|
26
|
+
// install time and delegates to it (mirrors a process where Hono's auto-wire
|
|
27
|
+
// installed its reader first). The prior answers only a sentinel key and returns
|
|
28
|
+
// `undefined` for `search`, so the searchParams tests are unaffected while the
|
|
29
|
+
// delegation suite below can assert the chain.
|
|
30
|
+
const PRIOR_KEY = 'env-test-prior'
|
|
31
|
+
beforeAll(() => {
|
|
32
|
+
;(
|
|
33
|
+
globalThis as unknown as { __bf_serverEnvReader?: (key: string) => string | undefined }
|
|
34
|
+
).__bf_serverEnvReader = (key) => (key === PRIOR_KEY ? 'from-prior' : undefined)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe('runWithRequestEnv + renderToHtml', () => {
|
|
38
|
+
test('binds searchParams() to the wrapped query', async () => {
|
|
39
|
+
const html = await runWithRequestEnv({ search: '?sort=price' }, () => renderToHtml(<SortLabel />))
|
|
40
|
+
expect(html).toBe('<p>price</p>')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('a present-but-empty value is kept (URLSearchParams.get returns "")', async () => {
|
|
44
|
+
// `?sort=` → get('sort') === '' (not null), and `'' ?? 'none'` === ''.
|
|
45
|
+
const html = await runWithRequestEnv({ search: '?sort=' }, () => renderToHtml(<SortLabel />))
|
|
46
|
+
expect(html).toBe('<p></p>')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('an absent key falls back to the author default', async () => {
|
|
50
|
+
const html = await runWithRequestEnv({ search: '?other=x' }, () => renderToHtml(<SortLabel />))
|
|
51
|
+
expect(html).toBe('<p>none</p>')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('a leading "?" is optional', async () => {
|
|
55
|
+
const html = await runWithRequestEnv({ search: 'sort=price' }, () => renderToHtml(<SortLabel />))
|
|
56
|
+
expect(html).toBe('<p>price</p>')
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('an omitted env key resolves to the empty default', async () => {
|
|
60
|
+
// `{}` carries no `search`, so the reader delegates → empty query → default.
|
|
61
|
+
const html = await runWithRequestEnv({}, () => renderToHtml(<SortLabel />))
|
|
62
|
+
expect(html).toBe('<p>none</p>')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('concurrent renders do not leak each other’s query (async-context scoped)', async () => {
|
|
66
|
+
// Interleave many renders with distinct queries; each must resolve its own.
|
|
67
|
+
const queries = Array.from({ length: 24 }, (_, i) => `?sort=v${i}`)
|
|
68
|
+
const results = await Promise.all(
|
|
69
|
+
queries.map((q, i) =>
|
|
70
|
+
runWithRequestEnv({ search: q }, async () => {
|
|
71
|
+
// Yield so the async contexts genuinely interleave before each reads.
|
|
72
|
+
await new Promise((r) => setTimeout(r, i % 5))
|
|
73
|
+
return renderToHtml(<SortLabel />)
|
|
74
|
+
}),
|
|
75
|
+
),
|
|
76
|
+
)
|
|
77
|
+
results.forEach((html, i) => expect(html).toBe(`<p>v${i}</p>`))
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('outside any scope resolves to the empty query (default)', async () => {
|
|
81
|
+
// No wrapping run() and no other reader wired in this test process → '' →
|
|
82
|
+
// get('sort') is null → author default.
|
|
83
|
+
const html = await renderToHtml(<SortLabel />)
|
|
84
|
+
expect(html).toBe('<p>none</p>')
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
describe('withRequestEnv (WinterCG fetch-handler wrapper)', () => {
|
|
89
|
+
test('binds searchParams() from the Request — the handler just renders', async () => {
|
|
90
|
+
const handler = withRequestEnv(
|
|
91
|
+
async (_req: Request) =>
|
|
92
|
+
new Response(await renderToHtml(<SortLabel />), {
|
|
93
|
+
headers: { 'content-type': 'text/html' },
|
|
94
|
+
}),
|
|
95
|
+
)
|
|
96
|
+
const res = await handler(new Request('https://x.test/list?sort=price'))
|
|
97
|
+
expect(await res.text()).toBe('<p>price</p>')
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('derives env from the Request and passes extra args (server / env / ctx) through', async () => {
|
|
101
|
+
const seen: unknown[] = []
|
|
102
|
+
const handler = withRequestEnv(async (_req: Request, a: number, b: string) => {
|
|
103
|
+
seen.push(a, b)
|
|
104
|
+
return new Response(await renderToHtml(<SortLabel />))
|
|
105
|
+
})
|
|
106
|
+
const res = await handler(new Request('https://x.test/?sort=name'), 7, 'z')
|
|
107
|
+
expect(seen).toEqual([7, 'z'])
|
|
108
|
+
expect(await res.text()).toBe('<p>name</p>')
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test('a request with no query resolves the author default', async () => {
|
|
112
|
+
const handler = withRequestEnv(async () => new Response(await renderToHtml(<SortLabel />)))
|
|
113
|
+
const res = await handler(new Request('https://x.test/list'))
|
|
114
|
+
expect(await res.text()).toBe('<p>none</p>')
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
describe('keyed seam — delegates to the prior reader', () => {
|
|
119
|
+
// The reader currently published on the seam (the chained one once
|
|
120
|
+
// `runWithRequestEnv` has installed it, else the raw prior — either way the
|
|
121
|
+
// delegation behaviour is identical for these cases).
|
|
122
|
+
const seam = () =>
|
|
123
|
+
(globalThis as unknown as { __bf_serverEnvReader: (key: string) => string | undefined })
|
|
124
|
+
.__bf_serverEnvReader
|
|
125
|
+
|
|
126
|
+
test('an unknown key delegates to the prior reader (outside any scope)', () => {
|
|
127
|
+
expect(seam()(PRIOR_KEY)).toBe('from-prior')
|
|
128
|
+
// The prior returns undefined for `search`, so a non-request render still
|
|
129
|
+
// resolves to the empty default rather than leaking a value.
|
|
130
|
+
expect(seam()('search')).toBeUndefined()
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test('a scoped env wins for its key; absent keys still delegate to the prior', () => {
|
|
134
|
+
runWithRequestEnv({ search: '?sort=price' }, () => {
|
|
135
|
+
expect(seam()('search')).toBe('?sort=price') // scoped value wins over the prior
|
|
136
|
+
expect(seam()(PRIOR_KEY)).toBe('from-prior') // key not in env → delegate
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* searchFromRequestUrl — the pure query-extraction helper the Hono SSR
|
|
3
|
+
* auto-wire publishes through the keyed `globalThis.__bf_serverEnvReader` seam
|
|
4
|
+
* (which `@barefootjs/client`'s `searchParams()` reads on the server, key
|
|
5
|
+
* `'search'`), spec/router.md v0.5.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, test, expect } from 'bun:test'
|
|
9
|
+
import { searchFromRequestUrl } from '../search-params-ssr'
|
|
10
|
+
|
|
11
|
+
describe('searchFromRequestUrl', () => {
|
|
12
|
+
test('returns the query string including the leading "?"', () => {
|
|
13
|
+
expect(searchFromRequestUrl('https://example.test/list?sort=price&page=2')).toBe('?sort=price&page=2')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('returns empty string for a query-less URL', () => {
|
|
17
|
+
expect(searchFromRequestUrl('https://example.test/list')).toBe('')
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('never throws on a malformed URL', () => {
|
|
21
|
+
expect(searchFromRequestUrl('not a url')).toBe('')
|
|
22
|
+
})
|
|
23
|
+
})
|
|
@@ -47,7 +47,7 @@ type HonoRenderCtx = {
|
|
|
47
47
|
isInsideLoop?: boolean
|
|
48
48
|
isLoopItemRoot?: boolean
|
|
49
49
|
}
|
|
50
|
-
import { BF_SCOPE, BF_HOST, BF_AT, BF_ROOT, BF_PROPS } from '@barefootjs/shared'
|
|
50
|
+
import { BF_SCOPE, BF_HOST, BF_AT, BF_ROOT, BF_PROPS, BF_REGION } from '@barefootjs/shared'
|
|
51
51
|
|
|
52
52
|
export interface HonoAdapterOptions {
|
|
53
53
|
/**
|
|
@@ -715,6 +715,9 @@ export class HonoAdapter extends JsxAdapter implements IRNodeEmitter<HonoRenderC
|
|
|
715
715
|
if (element.slotId) {
|
|
716
716
|
hydrationAttrs += ` bf="${element.slotId}"`
|
|
717
717
|
}
|
|
718
|
+
if (element.regionId) {
|
|
719
|
+
hydrationAttrs += ` ${BF_REGION}="${element.regionId}"`
|
|
720
|
+
}
|
|
718
721
|
|
|
719
722
|
if (children) {
|
|
720
723
|
return `<${tag}${attrs}${hydrationAttrs}>${children}</${tag}>`
|
package/src/app.ts
CHANGED
|
@@ -34,6 +34,9 @@ import { useRequestContext } from 'hono/jsx-renderer'
|
|
|
34
34
|
// of this runtime/Workers-bundled module while sharing one importmap renderer.
|
|
35
35
|
import { renderImportMapHtml, type ImportMapManifest } from '@barefootjs/jsx/import-map'
|
|
36
36
|
import { createDevReloader } from './dev-worker.ts'
|
|
37
|
+
// Side-effect import: auto-wires request-scoped `searchParams()` for SSR so any
|
|
38
|
+
// Hono app rendering the BarefootJS scripts gets it without an opt-in step.
|
|
39
|
+
import './search-params-ssr.ts'
|
|
37
40
|
|
|
38
41
|
const DEV_RELOAD_ENDPOINT_KEY = 'bfDevReloadEndpoint'
|
|
39
42
|
|
package/src/client-shim.ts
CHANGED
|
@@ -27,6 +27,12 @@ export {
|
|
|
27
27
|
forwardProps,
|
|
28
28
|
unwrap,
|
|
29
29
|
__slot,
|
|
30
|
+
// Request-scoped env signal (router v0.5). Unlike the reactive primitives
|
|
31
|
+
// below, `searchParams()` is meant to resolve during SSR — on the server it
|
|
32
|
+
// reads the per-request query via the injected reader (the Hono adapter wires
|
|
33
|
+
// it in `search-params-ssr.ts`), defaulting to an empty query — so the real
|
|
34
|
+
// export is re-exported, not a throwing stub.
|
|
35
|
+
searchParams,
|
|
30
36
|
} from '@barefootjs/client'
|
|
31
37
|
|
|
32
38
|
export type {
|
|
@@ -26,15 +26,8 @@ export declare namespace JSX {
|
|
|
26
26
|
type ElementChildrenAttribute = BaseJSX.ElementChildrenAttribute
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
* This declaration provides TypeScript types for source files; no runtime
|
|
35
|
-
* implementation is needed because the compiler replaces it before execution.
|
|
36
|
-
*/
|
|
37
|
-
export declare function Async(props: {
|
|
38
|
-
fallback: JSX.Element
|
|
39
|
-
children: JSX.Element | JSX.Element[] | null | undefined
|
|
40
|
-
}): JSX.Element
|
|
29
|
+
// Compiler built-ins `<Async>` / `<Region>` are import-scoped to
|
|
30
|
+
// `@barefootjs/client` (`import { Async, Region } from '@barefootjs/client'`),
|
|
31
|
+
// recognised by that import and compiled away (#1915). They are intentionally
|
|
32
|
+
// not re-declared on this JSX runtime — a bare tag-name declaration here would
|
|
33
|
+
// reintroduce the phantom-import / collision problems #1915 set out to remove.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request-scoped environment values for the framework-agnostic `renderToHtml`
|
|
3
|
+
* path — the non-Hono JS hosts (h3 / Elysia / any WinterCG handler) that render
|
|
4
|
+
* BarefootJS components as plain `hono/jsx` without Hono's `jsxRenderer`.
|
|
5
|
+
* spec/router.md v0.5 ("The wedge"), #1922.
|
|
6
|
+
*
|
|
7
|
+
* Hono resolves a request's environment through `useRequestContext()` inside its
|
|
8
|
+
* `jsxRenderer` async context. `renderToHtml` has no such context, so without
|
|
9
|
+
* this an env signal (`searchParams()`, …) resolves to its empty default
|
|
10
|
+
* regardless of the request — query-dependent initial content flashes /
|
|
11
|
+
* mismatches on hydration (the client then corrects it from the live browser
|
|
12
|
+
* state).
|
|
13
|
+
*
|
|
14
|
+
* A process-wide reader mutated per request would RACE across concurrent
|
|
15
|
+
* requests (the spec explicitly forbids it), so the values are scoped with a
|
|
16
|
+
* Node `AsyncLocalStorage`: each render runs inside `runWithRequestEnv(env, …)`
|
|
17
|
+
* and the reader returns the current async context's value — race-free.
|
|
18
|
+
*
|
|
19
|
+
* The reader is published on the shared, KEYED `globalThis.__bf_serverEnvReader`
|
|
20
|
+
* seam (the same one Hono's auto-wire uses, and which `@barefootjs/client`'s env
|
|
21
|
+
* signals read on the server) — never by importing `@barefootjs/client`, so this
|
|
22
|
+
* module can't pull a second client instance. One seam serves every env signal,
|
|
23
|
+
* so a new signal (cookies, …) is a new field on {@link BfRequestEnv}, not a new
|
|
24
|
+
* wrapper function. We install at most once and capture whatever reader was
|
|
25
|
+
* already on the seam (e.g. Hono's `useRequestContext` reader): when no render
|
|
26
|
+
* scope is active — or this env doesn't carry the requested key — we delegate to
|
|
27
|
+
* it, so a process mixing Hono and `renderToHtml` hosts keeps resolving both ways.
|
|
28
|
+
*
|
|
29
|
+
* Lives behind its own subpath (`@barefootjs/hono/request-env`), imported only
|
|
30
|
+
* by the hosts that opt in, so the always-on `renderToHtml` path never loads
|
|
31
|
+
* `node:async_hooks`.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Per-request environment values, keyed to match the `@barefootjs/client` env
|
|
38
|
+
* signals. A new env signal adds a field here; the host passes one more value —
|
|
39
|
+
* no new wrapper function.
|
|
40
|
+
*/
|
|
41
|
+
export interface BfRequestEnv {
|
|
42
|
+
/**
|
|
43
|
+
* The request's query string, with or without a leading `?` (e.g.
|
|
44
|
+
* `new URL(req.url).search` on Elysia, `getRequestURL(event).search` on h3).
|
|
45
|
+
* Backs `searchParams()`; `URLSearchParams` strips the `?`.
|
|
46
|
+
*/
|
|
47
|
+
search?: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type SeamHolder = { __bf_serverEnvReader?: (key: string) => string | undefined }
|
|
51
|
+
|
|
52
|
+
const envStore = new AsyncLocalStorage<BfRequestEnv>()
|
|
53
|
+
let installed = false
|
|
54
|
+
|
|
55
|
+
function ensureReaderInstalled(): void {
|
|
56
|
+
if (installed) return
|
|
57
|
+
installed = true
|
|
58
|
+
const holder = globalThis as unknown as SeamHolder
|
|
59
|
+
// The keyed reader already on the seam (e.g. Hono's useRequestContext-based
|
|
60
|
+
// one, installed at startup by `@barefootjs/hono/app`). We delegate to it when
|
|
61
|
+
// no render scope is active — or this env lacks the key — so a mixed Hono +
|
|
62
|
+
// renderToHtml process still resolves both ways.
|
|
63
|
+
const prior = holder.__bf_serverEnvReader
|
|
64
|
+
holder.__bf_serverEnvReader = (key) => {
|
|
65
|
+
const env = envStore.getStore()
|
|
66
|
+
if (env) {
|
|
67
|
+
const value = env[key as keyof BfRequestEnv]
|
|
68
|
+
if (value !== undefined) return value
|
|
69
|
+
}
|
|
70
|
+
return typeof prior === 'function' ? prior(key) : undefined
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Run `fn` — typically `() => renderToHtml(node)` — with the given request
|
|
76
|
+
* environment bound for the duration of its async context, so env signals
|
|
77
|
+
* (`searchParams()`, …) resolve this request's values at SSR. The binding
|
|
78
|
+
* propagates across the awaits inside `renderToHtml` (including `async`
|
|
79
|
+
* components), and concurrent renders each see their own `env` — never a shared
|
|
80
|
+
* per-request global (which the spec forbids because it races).
|
|
81
|
+
*
|
|
82
|
+
* Usage:
|
|
83
|
+
*
|
|
84
|
+
* const html = await runWithRequestEnv(
|
|
85
|
+
* { search: new URL(req.url).search },
|
|
86
|
+
* () => renderToHtml(<Layout …>…</Layout>),
|
|
87
|
+
* )
|
|
88
|
+
*/
|
|
89
|
+
export function runWithRequestEnv<T>(env: BfRequestEnv, fn: () => T): T {
|
|
90
|
+
ensureReaderInstalled()
|
|
91
|
+
return envStore.run(env, fn)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Derive the request environment from a WinterCG `Request` — the single place
|
|
96
|
+
* that maps a request to env-signal values. A new env signal extends this (and
|
|
97
|
+
* {@link BfRequestEnv}); hosts using {@link withRequestEnv} get it for free.
|
|
98
|
+
*/
|
|
99
|
+
function requestEnv(request: Request): BfRequestEnv {
|
|
100
|
+
return { search: new URL(request.url).search }
|
|
101
|
+
// When the cookie env signal lands:
|
|
102
|
+
// cookie: request.headers.get('cookie') ?? undefined
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Wrap a WinterCG `fetch` handler so the whole request runs with its env bound —
|
|
107
|
+
* env signals (`searchParams()`, …) resolve this request's values at SSR with no
|
|
108
|
+
* per-render plumbing. Bind ONCE at the entry point and every `renderToHtml`
|
|
109
|
+
* inside (across routing, handlers, `async` components) inherits it via the
|
|
110
|
+
* async context; concurrent requests never race.
|
|
111
|
+
*
|
|
112
|
+
* export default { port, fetch: withRequestEnv(handler) }
|
|
113
|
+
*
|
|
114
|
+
* Extra args (the framework / Bun server, Workers `env` / `ctx`) pass through
|
|
115
|
+
* unchanged. The env is derived from the `Request` by {@link requestEnv}, so the
|
|
116
|
+
* host never names env keys — a future signal (cookies, …) needs no host change.
|
|
117
|
+
*/
|
|
118
|
+
export function withRequestEnv<A extends unknown[]>(
|
|
119
|
+
handler: (request: Request, ...args: A) => Response | Promise<Response>,
|
|
120
|
+
): (request: Request, ...args: A) => Promise<Response> {
|
|
121
|
+
return (request, ...args) =>
|
|
122
|
+
runWithRequestEnv(requestEnv(request), async () => handler(request, ...args))
|
|
123
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-wire request-scoped `searchParams()` for Hono SSR (spec/router.md "The
|
|
3
|
+
* wedge", v0.5).
|
|
4
|
+
*
|
|
5
|
+
* `@barefootjs/client`'s `searchParams()` is environment-neutral: on the server
|
|
6
|
+
* it asks for the current request's query string through a reader seam. Hono
|
|
7
|
+
* exposes the active request via `useRequestContext()` (async-context scoped),
|
|
8
|
+
* so the reader resolves **per request** with no shared mutable state to race.
|
|
9
|
+
*
|
|
10
|
+
* We publish the reader on the keyed `globalThis.__bf_serverEnvReader` seam —
|
|
11
|
+
* the server-side analogue of the client's `window.__bf_*` seams — rather than
|
|
12
|
+
* importing `@barefootjs/client`. SSR templates resolve the `@barefootjs/client`
|
|
13
|
+
* specifier through the adapter's shim, so this infrastructure module stays
|
|
14
|
+
* decoupled from the client package while still wiring it. This module is
|
|
15
|
+
* imported for its side effect by `app.ts`, so any Hono SSR app that renders
|
|
16
|
+
* the BarefootJS scripts gets request-scoped `searchParams()` with no opt-in.
|
|
17
|
+
*
|
|
18
|
+
* The reader is keyed by env id, so one seam serves every env signal
|
|
19
|
+
* (`searchParams()` via `'search'` today; cookies, … later). Unknown keys
|
|
20
|
+
* return `undefined` to defer to the empty default.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { useRequestContext } from 'hono/jsx-renderer'
|
|
24
|
+
|
|
25
|
+
/** Extract the query string (including leading `?`, or `''`) from a request URL. */
|
|
26
|
+
export function searchFromRequestUrl(url: string): string {
|
|
27
|
+
try {
|
|
28
|
+
return new URL(url).search
|
|
29
|
+
} catch {
|
|
30
|
+
return ''
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
;(
|
|
35
|
+
globalThis as unknown as { __bf_serverEnvReader?: (key: string) => string | undefined }
|
|
36
|
+
).__bf_serverEnvReader = (key) => {
|
|
37
|
+
if (key !== 'search') return undefined
|
|
38
|
+
try {
|
|
39
|
+
return searchFromRequestUrl(useRequestContext().req.url)
|
|
40
|
+
} catch {
|
|
41
|
+
// No active request context (e.g. a non-request render) — defer to default.
|
|
42
|
+
return undefined
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/test-render.ts
CHANGED
|
@@ -143,11 +143,10 @@ export async function renderHonoComponent(options: RenderOptions): Promise<strin
|
|
|
143
143
|
childModuleWrites.push({ path: tempPath, content: readFileSync(modPath, 'utf8') })
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
let parentCode = templateFile.content
|
|
147
146
|
// Resolve each child import: re-anchor to a pre-compiled module's temp
|
|
148
147
|
// copy (`moduleTempPaths`), strip it (inlined via `components`), or
|
|
149
|
-
// leave it. Both maps key on the import specifier; match the
|
|
150
|
-
//
|
|
148
|
+
// leave it. Both maps key on the import specifier; match the importing
|
|
149
|
+
// module's path with or without a `.tsx` extension (`./badge` ↔
|
|
151
150
|
// `./badge.tsx`).
|
|
152
151
|
//
|
|
153
152
|
// Assumes one import statement per line — the marked-template adapter
|
|
@@ -155,7 +154,8 @@ export async function renderHonoComponent(options: RenderOptions): Promise<strin
|
|
|
155
154
|
// per-line scan is sufficient. A multi-line import would not match
|
|
156
155
|
// here; the unrewritten `../slot` then fails loudly at module
|
|
157
156
|
// resolution rather than rendering wrong output.
|
|
158
|
-
|
|
157
|
+
const rewriteChildImports = (code: string): string => {
|
|
158
|
+
if (componentKeys.size === 0 && moduleTempPaths.size === 0) return code
|
|
159
159
|
const matchKey = (importPath: string, keys: Iterable<string>): string | undefined => {
|
|
160
160
|
for (const key of keys) {
|
|
161
161
|
const keyWithoutExt = key.replace(/\.tsx?$/, '')
|
|
@@ -163,7 +163,7 @@ export async function renderHonoComponent(options: RenderOptions): Promise<strin
|
|
|
163
163
|
}
|
|
164
164
|
return undefined
|
|
165
165
|
}
|
|
166
|
-
|
|
166
|
+
return code
|
|
167
167
|
.split('\n')
|
|
168
168
|
.map(line => {
|
|
169
169
|
const importMatch = line.match(/^(\s*import\s+.*from\s+['"])(.+?)(['"].*)$/)
|
|
@@ -178,6 +178,18 @@ export async function renderHonoComponent(options: RenderOptions): Promise<strin
|
|
|
178
178
|
.join('\n')
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
const parentCode = rewriteChildImports(templateFile.content)
|
|
182
|
+
// Pre-compiled child modules may themselves import other pre-compiled
|
|
183
|
+
// siblings (#1467 Phase 2c: the demo root's `accordion` sibling imports
|
|
184
|
+
// `../icon`). Their committed copies keep the source specifier, which
|
|
185
|
+
// doesn't resolve from the temp dir — re-anchor through the same map so
|
|
186
|
+
// nested sibling imports land on their own temp copies. `moduleTempPaths`
|
|
187
|
+
// is fully populated before any content is rewritten, so ordering
|
|
188
|
+
// between siblings doesn't matter.
|
|
189
|
+
for (const write of childModuleWrites) {
|
|
190
|
+
write.content = rewriteChildImports(write.content)
|
|
191
|
+
}
|
|
192
|
+
|
|
181
193
|
// Combine: JSX pragma + child compiled functions + parent compiled code
|
|
182
194
|
const codeParts = ['/** @jsxImportSource hono/jsx */']
|
|
183
195
|
for (const childCode of childCodes) {
|