@takazudo/zfb 2.22.1 → 3.1.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/README.md +13 -16
- package/dist/config.d.ts +77 -25
- package/dist/config.js +27 -6
- package/dist/config.js.map +1 -1
- package/dist/content.d.ts +8 -22
- package/dist/content.js +6 -28
- package/dist/content.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/island-boundary.d.ts +4 -0
- package/dist/island-boundary.js +42 -0
- package/dist/island-boundary.js.map +1 -0
- package/dist/island.d.ts +2 -118
- package/dist/island.js +4 -284
- package/dist/island.js.map +1 -1
- package/dist/jsx-types.d.ts +2 -38
- package/dist/jsx-types.js +3 -10
- package/dist/jsx-types.js.map +1 -1
- package/dist/plugins.d.ts +40 -0
- package/dist/plugins.js.map +1 -1
- package/dist/runtime.d.ts +26 -77
- package/dist/runtime.js +307 -340
- package/dist/runtime.js.map +1 -1
- package/dist/zudo-react/client.d.ts +3 -0
- package/dist/zudo-react/client.js +3 -0
- package/dist/zudo-react/client.js.map +1 -0
- package/dist/zudo-react/description.d.ts +16 -0
- package/dist/zudo-react/description.js +64 -0
- package/dist/zudo-react/description.js.map +1 -0
- package/dist/zudo-react/dom-bindings.d.ts +13 -0
- package/dist/zudo-react/dom-bindings.js +58 -0
- package/dist/zudo-react/dom-bindings.js.map +1 -0
- package/dist/zudo-react/escape.d.ts +2 -0
- package/dist/zudo-react/escape.js +7 -0
- package/dist/zudo-react/escape.js.map +1 -0
- package/dist/zudo-react/forms.d.ts +29 -0
- package/dist/zudo-react/forms.js +371 -0
- package/dist/zudo-react/forms.js.map +1 -0
- package/dist/zudo-react/hydrate.d.ts +4 -0
- package/dist/zudo-react/hydrate.js +912 -0
- package/dist/zudo-react/hydrate.js.map +1 -0
- package/dist/zudo-react/index.d.ts +47 -0
- package/dist/zudo-react/index.js +13 -0
- package/dist/zudo-react/index.js.map +1 -0
- package/dist/zudo-react/island-root-type.d.ts +1 -0
- package/dist/zudo-react/island-root-type.js +2 -0
- package/dist/zudo-react/island-root-type.js.map +1 -0
- package/dist/zudo-react/jsx-dev-runtime.d.ts +8 -0
- package/dist/zudo-react/jsx-dev-runtime.js +6 -0
- package/dist/zudo-react/jsx-dev-runtime.js.map +1 -0
- package/dist/zudo-react/jsx-runtime.d.ts +5 -0
- package/dist/zudo-react/jsx-runtime.js +7 -0
- package/dist/zudo-react/jsx-runtime.js.map +1 -0
- package/dist/zudo-react/jsx-types.d.ts +216 -0
- package/dist/zudo-react/jsx-types.js +2 -0
- package/dist/zudo-react/jsx-types.js.map +1 -0
- package/dist/zudo-react/props-transport.d.ts +2 -0
- package/dist/zudo-react/props-transport.js +95 -0
- package/dist/zudo-react/props-transport.js.map +1 -0
- package/dist/zudo-react/reactive-types.d.ts +8 -0
- package/dist/zudo-react/reactive-types.js +2 -0
- package/dist/zudo-react/reactive-types.js.map +1 -0
- package/dist/zudo-react/reactive.d.ts +20 -0
- package/dist/zudo-react/reactive.js +176 -0
- package/dist/zudo-react/reactive.js.map +1 -0
- package/dist/zudo-react/render-html.d.ts +3 -0
- package/dist/zudo-react/render-html.js +535 -0
- package/dist/zudo-react/render-html.js.map +1 -0
- package/dist/zudo-react/root.d.ts +20 -0
- package/dist/zudo-react/root.js +74 -0
- package/dist/zudo-react/root.js.map +1 -0
- package/dist/zudo-react/scheduler.d.ts +12 -0
- package/dist/zudo-react/scheduler.js +113 -0
- package/dist/zudo-react/scheduler.js.map +1 -0
- package/dist/zudo-react/scope.d.ts +42 -0
- package/dist/zudo-react/scope.js +220 -0
- package/dist/zudo-react/scope.js.map +1 -0
- package/dist/zudo-react/server.d.ts +15 -0
- package/dist/zudo-react/server.js +11 -0
- package/dist/zudo-react/server.js.map +1 -0
- package/dist/zudo-react/structure.d.ts +16 -0
- package/dist/zudo-react/structure.js +49 -0
- package/dist/zudo-react/structure.js.map +1 -0
- package/dist/zudo-react/vocabulary.d.ts +12 -0
- package/dist/zudo-react/vocabulary.js +96 -0
- package/dist/zudo-react/vocabulary.js.map +1 -0
- package/package.json +28 -24
package/dist/island.d.ts
CHANGED
|
@@ -1,130 +1,14 @@
|
|
|
1
1
|
import type { VNode } from "./jsx-types.js";
|
|
2
|
+
import type { Description } from "./zudo-react/description.js";
|
|
2
3
|
import { type When } from "./types.js";
|
|
3
4
|
export { resolveWhen } from "./types.js";
|
|
4
|
-
/**
|
|
5
|
-
* Marker attribute the SSR wrapper writes when the child component should
|
|
6
|
-
* be hydrated client-side. The hydration runtime queries
|
|
7
|
-
* `[data-${HYDRATE_MARKER_ATTR}]` to find islands.
|
|
8
|
-
*/
|
|
9
5
|
export declare const HYDRATE_MARKER_ATTR = "data-zfb-island";
|
|
10
|
-
/**
|
|
11
|
-
* Marker attribute the SSR wrapper writes when SSR is being skipped (the
|
|
12
|
-
* client:only-equivalent path). The hydration runtime queries
|
|
13
|
-
* `[data-${SKIP_SSR_MARKER_ATTR}]` to find these placeholders and renders
|
|
14
|
-
* the real component into them on hydration — there is no server output
|
|
15
|
-
* to patch up.
|
|
16
|
-
*/
|
|
17
6
|
export declare const SKIP_SSR_MARKER_ATTR = "data-zfb-island-skip-ssr";
|
|
18
|
-
/** Fallback name surfaced when child identity cannot be determined. */
|
|
19
|
-
export declare const ANONYMOUS_COMPONENT_NAME = "Anonymous";
|
|
20
|
-
/**
|
|
21
|
-
* Attribute the SSR wrapper writes to ferry the wrapped component's props
|
|
22
|
-
* across the SSR → hydrate boundary. The hydration runtime parses this
|
|
23
|
-
* with `JSON.parse` and forwards the result to the per-island `mount()`
|
|
24
|
-
* call so the hydrated component sees the same props the SSR pass did.
|
|
25
|
-
*
|
|
26
|
-
* Omitted entirely when the wrapped child has no own data props (other
|
|
27
|
-
* than `children`) — `readProps` already falls back to `{}` when the
|
|
28
|
-
* attribute is missing, and emitting `data-props=""` would just bloat
|
|
29
|
-
* the SSR markup.
|
|
30
|
-
*/
|
|
31
|
-
export declare const PROPS_DATA_ATTR = "data-props";
|
|
32
|
-
/** Props for `<Island>`. */
|
|
33
7
|
export interface IslandProps {
|
|
34
|
-
/** Hydration scheduling strategy. Defaults to `"load"`. */
|
|
35
8
|
when?: When;
|
|
36
|
-
/**
|
|
37
|
-
* CSS media query string for `when="media"` islands. The island hydrates
|
|
38
|
-
* when this query first matches (including later viewport changes). The
|
|
39
|
-
* runtime uses `window.matchMedia(media)` to register a listener.
|
|
40
|
-
*
|
|
41
|
-
* Ignored when `when` is not `"media"` (a dev warning is emitted).
|
|
42
|
-
* Required when `when === "media"` (omitting it falls back to DEFAULT_WHEN
|
|
43
|
-
* with a dev warning).
|
|
44
|
-
*/
|
|
45
9
|
media?: string;
|
|
46
|
-
/**
|
|
47
|
-
* If supplied, switches the island into SSR-skip mode (Astro's
|
|
48
|
-
* `client:only` equivalent). The wrapper emits the
|
|
49
|
-
* `data-zfb-island-skip-ssr` marker, the heavy `children` are **not**
|
|
50
|
-
* evaluated server-side, and `ssrFallback` is rendered in their place.
|
|
51
|
-
* On hydration the client runtime swaps in the real component.
|
|
52
|
-
*/
|
|
53
10
|
ssrFallback?: VNode;
|
|
54
|
-
/**
|
|
55
|
-
* Server-rendered children, hydrated client-side once `when` fires.
|
|
56
|
-
*
|
|
57
|
-
* Typed as `VNode` (structural union) rather than `ReactNode` so
|
|
58
|
-
* non-React frameworks can implement `IslandProps` without a React
|
|
59
|
-
* type dependency (BCI-4).
|
|
60
|
-
*/
|
|
61
11
|
children?: VNode;
|
|
62
12
|
}
|
|
63
|
-
|
|
64
|
-
* Public JSX-element shape returned by [`Island`]. Intentionally widened
|
|
65
|
-
* to a structural type so consumers don't infer through the internal
|
|
66
|
-
* `{ type, props, key }` VNode shape of either Preact or React. Both
|
|
67
|
-
* jsx-runtimes accept this object on either side of the boundary.
|
|
68
|
-
*/
|
|
69
|
-
export type IslandElement = {
|
|
70
|
-
readonly type: string;
|
|
71
|
-
readonly props: Readonly<Record<string, unknown>>;
|
|
72
|
-
readonly key: unknown;
|
|
73
|
-
};
|
|
74
|
-
/**
|
|
75
|
-
* `<Island>` JSX wrapper.
|
|
76
|
-
*
|
|
77
|
-
* Returns a JSX element shape compatible with both Preact and React. The
|
|
78
|
-
* runtime tag is `"div"`. In the default (hydrate) mode the wrapper emits
|
|
79
|
-
* `data-zfb-island="ComponentName"` and `data-when="<resolved-when>"`. In
|
|
80
|
-
* SSR-skip mode (when `ssrFallback` is provided) it emits
|
|
81
|
-
* `data-zfb-island-skip-ssr="ComponentName"` instead and renders the
|
|
82
|
-
* fallback rather than the heavy child.
|
|
83
|
-
*
|
|
84
|
-
* The component-name string is derived from the child JSX element's type
|
|
85
|
-
* identity (`type.displayName ?? type.name`). For string-typed children
|
|
86
|
-
* (host elements) the tag name is used. If no usable identity can be
|
|
87
|
-
* recovered, [`ANONYMOUS_COMPONENT_NAME`] is used so the marker still
|
|
88
|
-
* lines up with the hydration shim's manifest lookup.
|
|
89
|
-
*
|
|
90
|
-
* The return type is the public [`IslandElement`] shape — the internal
|
|
91
|
-
* VNode structure is deliberately not leaked so consumers never type-infer
|
|
92
|
-
* through it.
|
|
93
|
-
*/
|
|
13
|
+
export type IslandElement = Description;
|
|
94
14
|
export declare function Island(props: IslandProps): IslandElement;
|
|
95
|
-
/**
|
|
96
|
-
* Pull a component-name string out of a JSX child.
|
|
97
|
-
*
|
|
98
|
-
* Both Preact and React store rendered VNodes as plain objects whose
|
|
99
|
-
* `.type` field is either:
|
|
100
|
-
* - the component function (look at `displayName ?? name`),
|
|
101
|
-
* - or the host element tag name as a string.
|
|
102
|
-
*
|
|
103
|
-
* If `children` is an array (multiple children), the first child with a
|
|
104
|
-
* usable identity wins. This is intentional: the typical island shape is
|
|
105
|
-
* `<Island><Foo /></Island>` (single child); when the caller wraps a
|
|
106
|
-
* fragment-like list we still want a deterministic, debuggable name.
|
|
107
|
-
*
|
|
108
|
-
* Exported for tests; not re-exported from `index.ts`.
|
|
109
|
-
*/
|
|
110
|
-
export declare function captureComponentName(children: unknown): string;
|
|
111
|
-
/**
|
|
112
|
-
* Serialize the wrapped child's data props as a JSON string the runtime
|
|
113
|
-
* can parse out of the `data-props` attribute on the Island marker div.
|
|
114
|
-
*
|
|
115
|
-
* Mirrors [`captureComponentName`]'s array handling: when `children` is
|
|
116
|
-
* an array (multiple JSX siblings), the first child whose own props
|
|
117
|
-
* yield a non-empty serialization wins. This keeps the "first
|
|
118
|
-
* identifiable child" contract consistent across both attributes —
|
|
119
|
-
* whatever the marker name points at is what the data-props payload
|
|
120
|
-
* describes.
|
|
121
|
-
*
|
|
122
|
-
* Returns `undefined` (not `"{}"` and not `""`) when no usable props
|
|
123
|
-
* exist. The runtime's `readProps` already maps a missing attribute to
|
|
124
|
-
* `{}`, so omitting the attribute keeps the SSR markup smaller and
|
|
125
|
-
* preserves the invariant that the attribute, when present, always
|
|
126
|
-
* parses to a non-empty record.
|
|
127
|
-
*
|
|
128
|
-
* Exported for tests; not re-exported from `index.ts`.
|
|
129
|
-
*/
|
|
130
|
-
export declare function captureSerializableProps(children: unknown): string | undefined;
|
package/dist/island.js
CHANGED
|
@@ -1,137 +1,13 @@
|
|
|
1
|
-
// Build-time
|
|
2
|
-
|
|
3
|
-
// The wrapper is intentionally JSX-runtime-agnostic: it does not import
|
|
4
|
-
// preact or react and never calls h() / createElement directly. Instead it
|
|
5
|
-
// returns a plain object with a fixed shape that both Preact's and React's
|
|
6
|
-
// jsx-runtime accept when the JSX transform turns the call site into a
|
|
7
|
-
// jsx(Island, props) invocation.
|
|
8
|
-
//
|
|
9
|
-
// At build time, running through the SSR renderer (embedded V8 host):
|
|
10
|
-
//
|
|
11
|
-
// <Island when="visible"><Counter /></Island>
|
|
12
|
-
//
|
|
13
|
-
// renders as:
|
|
14
|
-
//
|
|
15
|
-
// <div data-zfb-island="Counter" data-when="visible">
|
|
16
|
-
// <Counter />
|
|
17
|
-
// </div>
|
|
18
|
-
//
|
|
19
|
-
// The component-name attribute (`data-zfb-island="ComponentName"`) is filled
|
|
20
|
-
// in *here* by reading the child's JSX type identity (`displayName` first,
|
|
21
|
-
// then `name`). Sub 3's hydration shim then `querySelectorAll`s these
|
|
22
|
-
// markers and looks each one up in the islands manifest produced by the
|
|
23
|
-
// scanner (see `crates/zfb-islands/src/manifest.rs` for the contract).
|
|
24
|
-
//
|
|
25
|
-
// SSR-skip mode: when the caller passes `ssrFallback`, the heavy child is
|
|
26
|
-
// **not** evaluated at SSR time. Instead the wrapper emits a different
|
|
27
|
-
// marker:
|
|
28
|
-
//
|
|
29
|
-
// <Island ssrFallback={<div>Loading…</div>}><HeavyClientOnly /></Island>
|
|
30
|
-
// →
|
|
31
|
-
// <div data-zfb-island-skip-ssr="HeavyClientOnly">
|
|
32
|
-
// <div>Loading…</div>
|
|
33
|
-
// </div>
|
|
34
|
-
//
|
|
35
|
-
// On the client the hydration runtime distinguishes hydrate vs. render by
|
|
36
|
-
// which marker attribute is present. This is the equivalent of Astro's
|
|
37
|
-
// `client:only="preact"`.
|
|
38
|
-
//
|
|
39
|
-
// When validation: in development we console.warn for unknown `when`
|
|
40
|
-
// values and fall back to the default. In production we silently fall
|
|
41
|
-
// back to keep the bundle path small.
|
|
42
|
-
import { jsx } from "react/jsx-runtime";
|
|
1
|
+
// Build-time Island wrapper for the owned zudo-react runtime.
|
|
2
|
+
import { ownedIslandBoundary } from "./island-boundary.js";
|
|
43
3
|
import { DEFAULT_WHEN, resolveWhen } from "./types.js";
|
|
44
|
-
// Re-export `resolveWhen` for back-compat: tests and downstream consumers
|
|
45
|
-
// historically imported it from `./island.js`. The implementation lives in
|
|
46
|
-
// `./types.js` so the runtime scheduler can pull it in without dragging
|
|
47
|
-
// the JSX wrapper along for the ride.
|
|
48
4
|
export { resolveWhen } from "./types.js";
|
|
49
|
-
/**
|
|
50
|
-
* Marker attribute the SSR wrapper writes when the child component should
|
|
51
|
-
* be hydrated client-side. The hydration runtime queries
|
|
52
|
-
* `[data-${HYDRATE_MARKER_ATTR}]` to find islands.
|
|
53
|
-
*/
|
|
54
5
|
export const HYDRATE_MARKER_ATTR = "data-zfb-island";
|
|
55
|
-
/**
|
|
56
|
-
* Marker attribute the SSR wrapper writes when SSR is being skipped (the
|
|
57
|
-
* client:only-equivalent path). The hydration runtime queries
|
|
58
|
-
* `[data-${SKIP_SSR_MARKER_ATTR}]` to find these placeholders and renders
|
|
59
|
-
* the real component into them on hydration — there is no server output
|
|
60
|
-
* to patch up.
|
|
61
|
-
*/
|
|
62
6
|
export const SKIP_SSR_MARKER_ATTR = "data-zfb-island-skip-ssr";
|
|
63
|
-
/** Fallback name surfaced when child identity cannot be determined. */
|
|
64
|
-
export const ANONYMOUS_COMPONENT_NAME = "Anonymous";
|
|
65
|
-
/**
|
|
66
|
-
* Attribute the SSR wrapper writes to ferry the wrapped component's props
|
|
67
|
-
* across the SSR → hydrate boundary. The hydration runtime parses this
|
|
68
|
-
* with `JSON.parse` and forwards the result to the per-island `mount()`
|
|
69
|
-
* call so the hydrated component sees the same props the SSR pass did.
|
|
70
|
-
*
|
|
71
|
-
* Omitted entirely when the wrapped child has no own data props (other
|
|
72
|
-
* than `children`) — `readProps` already falls back to `{}` when the
|
|
73
|
-
* attribute is missing, and emitting `data-props=""` would just bloat
|
|
74
|
-
* the SSR markup.
|
|
75
|
-
*/
|
|
76
|
-
export const PROPS_DATA_ATTR = "data-props";
|
|
77
|
-
/**
|
|
78
|
-
* `<Island>` JSX wrapper.
|
|
79
|
-
*
|
|
80
|
-
* Returns a JSX element shape compatible with both Preact and React. The
|
|
81
|
-
* runtime tag is `"div"`. In the default (hydrate) mode the wrapper emits
|
|
82
|
-
* `data-zfb-island="ComponentName"` and `data-when="<resolved-when>"`. In
|
|
83
|
-
* SSR-skip mode (when `ssrFallback` is provided) it emits
|
|
84
|
-
* `data-zfb-island-skip-ssr="ComponentName"` instead and renders the
|
|
85
|
-
* fallback rather than the heavy child.
|
|
86
|
-
*
|
|
87
|
-
* The component-name string is derived from the child JSX element's type
|
|
88
|
-
* identity (`type.displayName ?? type.name`). For string-typed children
|
|
89
|
-
* (host elements) the tag name is used. If no usable identity can be
|
|
90
|
-
* recovered, [`ANONYMOUS_COMPONENT_NAME`] is used so the marker still
|
|
91
|
-
* lines up with the hydration shim's manifest lookup.
|
|
92
|
-
*
|
|
93
|
-
* The return type is the public [`IslandElement`] shape — the internal
|
|
94
|
-
* VNode structure is deliberately not leaked so consumers never type-infer
|
|
95
|
-
* through it.
|
|
96
|
-
*/
|
|
97
7
|
export function Island(props) {
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
const media = resolvedWhen.media;
|
|
101
|
-
const componentName = captureComponentName(props.children);
|
|
102
|
-
const isSkipSsr = props.ssrFallback !== undefined;
|
|
103
|
-
// Always source props from `props.children` (the heavy component VNode),
|
|
104
|
-
// never from `ssrFallback` — the fallback is just SSR placeholder markup;
|
|
105
|
-
// the hydrated component is the child, so its props are what `mount()`
|
|
106
|
-
// needs at hydrate time. (Same rationale as `captureComponentName`.)
|
|
107
|
-
const dataProps = captureSerializableProps(props.children);
|
|
108
|
-
if (isSkipSsr) {
|
|
109
|
-
const skipSsrProps = {
|
|
110
|
-
[SKIP_SSR_MARKER_ATTR]: componentName,
|
|
111
|
-
"data-when": when,
|
|
112
|
-
};
|
|
113
|
-
if (when === "media" && media !== undefined)
|
|
114
|
-
skipSsrProps["data-media"] = media;
|
|
115
|
-
if (dataProps !== undefined)
|
|
116
|
-
skipSsrProps[PROPS_DATA_ATTR] = dataProps;
|
|
117
|
-
return makeWrapper(skipSsrProps, props.ssrFallback ?? null);
|
|
118
|
-
}
|
|
119
|
-
const hydrateProps = {
|
|
120
|
-
[HYDRATE_MARKER_ATTR]: componentName,
|
|
121
|
-
"data-when": when,
|
|
122
|
-
};
|
|
123
|
-
if (when === "media" && media !== undefined)
|
|
124
|
-
hydrateProps["data-media"] = media;
|
|
125
|
-
if (dataProps !== undefined)
|
|
126
|
-
hydrateProps[PROPS_DATA_ATTR] = dataProps;
|
|
127
|
-
return makeWrapper(hydrateProps, props.children);
|
|
8
|
+
const { when, media } = resolveMediaProps(props);
|
|
9
|
+
return ownedIslandBoundary(props.children, props.ssrFallback, when, media);
|
|
128
10
|
}
|
|
129
|
-
/**
|
|
130
|
-
* Validate and resolve the `when` / `media` props together.
|
|
131
|
-
*
|
|
132
|
-
* - `when="media"` without `media` → warn + fall back to `DEFAULT_WHEN`.
|
|
133
|
-
* - `media` without `when="media"` → warn-and-ignore; `media` is dropped.
|
|
134
|
-
*/
|
|
135
11
|
function resolveMediaProps(props) {
|
|
136
12
|
const when = resolveWhen(props.when);
|
|
137
13
|
const media = props.media;
|
|
@@ -153,160 +29,4 @@ function resolveMediaProps(props) {
|
|
|
153
29
|
}
|
|
154
30
|
return { when, media };
|
|
155
31
|
}
|
|
156
|
-
/**
|
|
157
|
-
* Construct the wrapper element through the **automatic JSX runtime
|
|
158
|
-
* factory**, called explicitly so this file stays plain `.ts` (no JSX
|
|
159
|
-
* syntax).
|
|
160
|
-
*
|
|
161
|
-
* Why the explicit `jsx(...)` call rather than a hand-rolled
|
|
162
|
-
* `{ type, props, key }` object literal: a plain literal is only valid for
|
|
163
|
-
* Preact (whose diff path / `preact-render-to-string` recognise a VNode by
|
|
164
|
-
* `vnode.constructor === undefined`). React's renderer rejects such an
|
|
165
|
-
* object as a child with "Objects are not valid as a React child"
|
|
166
|
-
* (minified error #31) because a real React element carries
|
|
167
|
-
* `$$typeof: Symbol.for("react.element")`, which a literal cannot fake
|
|
168
|
-
* portably. Calling `jsx` from `react/jsx-runtime` mints a real element:
|
|
169
|
-
* in React mode it resolves natively; in Preact mode the engine rewrites
|
|
170
|
-
* `react/jsx-runtime` → `preact/jsx-runtime` (bundler.rs ~2886), so the
|
|
171
|
-
* Preact runtime mints the element. Same result the JSX `<div>` delegation
|
|
172
|
-
* produced — but as plain `.ts`.
|
|
173
|
-
*
|
|
174
|
-
* Why `.ts` and NOT `.tsx`: esbuild rewrites a `.js` import specifier to
|
|
175
|
-
* `.ts` but NOT to `.tsx`. The barrel `index.ts` imports `./island.js`;
|
|
176
|
-
* when this file was `island.tsx`, source consumers (the dev `exports`
|
|
177
|
-
* point at `src/*.ts`, e.g. the node-free template / smoke path) failed
|
|
178
|
-
* with `Could not resolve "./island.js"`. The published `dist` worked
|
|
179
|
-
* (tsc emits a real `island.js`), which masked the regression in
|
|
180
|
-
* pnpm-pack/dist probes. Keeping this a `.ts` file makes the barrel resolve
|
|
181
|
-
* `./island.js` → `./island.ts` from source everywhere, while tsc still
|
|
182
|
-
* emits `dist/island.js` for the published package.
|
|
183
|
-
*
|
|
184
|
-
* The cast back to `IslandElement` keeps the public type tight; the
|
|
185
|
-
* concrete element shape produced by the runtime is invisible to type
|
|
186
|
-
* consumers.
|
|
187
|
-
*/
|
|
188
|
-
function makeWrapper(props, children) {
|
|
189
|
-
return jsx("div", { ...props, children });
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Pull a component-name string out of a JSX child.
|
|
193
|
-
*
|
|
194
|
-
* Both Preact and React store rendered VNodes as plain objects whose
|
|
195
|
-
* `.type` field is either:
|
|
196
|
-
* - the component function (look at `displayName ?? name`),
|
|
197
|
-
* - or the host element tag name as a string.
|
|
198
|
-
*
|
|
199
|
-
* If `children` is an array (multiple children), the first child with a
|
|
200
|
-
* usable identity wins. This is intentional: the typical island shape is
|
|
201
|
-
* `<Island><Foo /></Island>` (single child); when the caller wraps a
|
|
202
|
-
* fragment-like list we still want a deterministic, debuggable name.
|
|
203
|
-
*
|
|
204
|
-
* Exported for tests; not re-exported from `index.ts`.
|
|
205
|
-
*/
|
|
206
|
-
export function captureComponentName(children) {
|
|
207
|
-
if (Array.isArray(children)) {
|
|
208
|
-
for (const child of children) {
|
|
209
|
-
const name = nameFromSingle(child);
|
|
210
|
-
if (name)
|
|
211
|
-
return name;
|
|
212
|
-
}
|
|
213
|
-
return ANONYMOUS_COMPONENT_NAME;
|
|
214
|
-
}
|
|
215
|
-
return nameFromSingle(children) || ANONYMOUS_COMPONENT_NAME;
|
|
216
|
-
}
|
|
217
|
-
function nameFromSingle(child) {
|
|
218
|
-
if (!child || typeof child !== "object")
|
|
219
|
-
return "";
|
|
220
|
-
const c = child;
|
|
221
|
-
const t = c.type;
|
|
222
|
-
if (typeof t === "function") {
|
|
223
|
-
const fn = t;
|
|
224
|
-
if (typeof fn.displayName === "string" && fn.displayName)
|
|
225
|
-
return fn.displayName;
|
|
226
|
-
if (typeof fn.name === "string" && fn.name)
|
|
227
|
-
return fn.name;
|
|
228
|
-
return "";
|
|
229
|
-
}
|
|
230
|
-
if (typeof t === "string" && t)
|
|
231
|
-
return t;
|
|
232
|
-
return "";
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* Serialize the wrapped child's data props as a JSON string the runtime
|
|
236
|
-
* can parse out of the `data-props` attribute on the Island marker div.
|
|
237
|
-
*
|
|
238
|
-
* Mirrors [`captureComponentName`]'s array handling: when `children` is
|
|
239
|
-
* an array (multiple JSX siblings), the first child whose own props
|
|
240
|
-
* yield a non-empty serialization wins. This keeps the "first
|
|
241
|
-
* identifiable child" contract consistent across both attributes —
|
|
242
|
-
* whatever the marker name points at is what the data-props payload
|
|
243
|
-
* describes.
|
|
244
|
-
*
|
|
245
|
-
* Returns `undefined` (not `"{}"` and not `""`) when no usable props
|
|
246
|
-
* exist. The runtime's `readProps` already maps a missing attribute to
|
|
247
|
-
* `{}`, so omitting the attribute keeps the SSR markup smaller and
|
|
248
|
-
* preserves the invariant that the attribute, when present, always
|
|
249
|
-
* parses to a non-empty record.
|
|
250
|
-
*
|
|
251
|
-
* Exported for tests; not re-exported from `index.ts`.
|
|
252
|
-
*/
|
|
253
|
-
export function captureSerializableProps(children) {
|
|
254
|
-
if (Array.isArray(children)) {
|
|
255
|
-
for (const child of children) {
|
|
256
|
-
const json = propsFromSingle(child);
|
|
257
|
-
if (json !== undefined)
|
|
258
|
-
return json;
|
|
259
|
-
}
|
|
260
|
-
return undefined;
|
|
261
|
-
}
|
|
262
|
-
return propsFromSingle(children);
|
|
263
|
-
}
|
|
264
|
-
function propsFromSingle(child) {
|
|
265
|
-
if (!child || typeof child !== "object")
|
|
266
|
-
return undefined;
|
|
267
|
-
const c = child;
|
|
268
|
-
const raw = c.props;
|
|
269
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
270
|
-
return undefined;
|
|
271
|
-
// Exclude `children` from the serialized payload: the SSR pass already
|
|
272
|
-
// emitted the rendered children into the DOM (the hydration target),
|
|
273
|
-
// and JSX child nodes are typically VNode trees with non-serializable
|
|
274
|
-
// shapes (functions, circular refs) that would either bloat the
|
|
275
|
-
// payload or throw inside JSON.stringify. The hydration runtime
|
|
276
|
-
// re-renders into the existing DOM, so it does not need the JSX
|
|
277
|
-
// children replayed through props.
|
|
278
|
-
//
|
|
279
|
-
// Note: JSON.stringify already silently drops function / symbol /
|
|
280
|
-
// undefined values from the output, so those don't need explicit
|
|
281
|
-
// pre-filtering here.
|
|
282
|
-
const propsRecord = raw;
|
|
283
|
-
let hasOwn = false;
|
|
284
|
-
const filtered = {};
|
|
285
|
-
for (const key of Object.keys(propsRecord)) {
|
|
286
|
-
if (key === "children")
|
|
287
|
-
continue;
|
|
288
|
-
filtered[key] = propsRecord[key];
|
|
289
|
-
hasOwn = true;
|
|
290
|
-
}
|
|
291
|
-
if (!hasOwn)
|
|
292
|
-
return undefined;
|
|
293
|
-
let json;
|
|
294
|
-
try {
|
|
295
|
-
json = JSON.stringify(filtered);
|
|
296
|
-
}
|
|
297
|
-
catch {
|
|
298
|
-
// Circular references, BigInt, or any other non-serializable input
|
|
299
|
-
// — silently fall through (no `data-props`) rather than ship a
|
|
300
|
-
// partial payload. The runtime already handles the missing-attribute
|
|
301
|
-
// case by returning `{}` from `readProps`.
|
|
302
|
-
return undefined;
|
|
303
|
-
}
|
|
304
|
-
// JSON.stringify can also return `undefined` (when the top-level value
|
|
305
|
-
// serializes to nothing) or `"{}"` (when every key was a function /
|
|
306
|
-
// symbol / undefined and got dropped). Treat both as "nothing useful
|
|
307
|
-
// to ship" so the marker stays clean.
|
|
308
|
-
if (json === undefined || json === "{}")
|
|
309
|
-
return undefined;
|
|
310
|
-
return json;
|
|
311
|
-
}
|
|
312
32
|
//# sourceMappingURL=island.js.map
|
package/dist/island.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"island.js","sourceRoot":"","sources":["../src/island.ts"],"names":[],"mappings":"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","sourcesContent":["// Build-time `<Island when=\"...\">` JSX wrapper.\n//\n// The wrapper is intentionally JSX-runtime-agnostic: it does not import\n// preact or react and never calls h() / createElement directly. Instead it\n// returns a plain object with a fixed shape that both Preact's and React's\n// jsx-runtime accept when the JSX transform turns the call site into a\n// jsx(Island, props) invocation.\n//\n// At build time, running through the SSR renderer (embedded V8 host):\n//\n// <Island when=\"visible\"><Counter /></Island>\n//\n// renders as:\n//\n// <div data-zfb-island=\"Counter\" data-when=\"visible\">\n// <Counter />\n// </div>\n//\n// The component-name attribute (`data-zfb-island=\"ComponentName\"`) is filled\n// in *here* by reading the child's JSX type identity (`displayName` first,\n// then `name`). Sub 3's hydration shim then `querySelectorAll`s these\n// markers and looks each one up in the islands manifest produced by the\n// scanner (see `crates/zfb-islands/src/manifest.rs` for the contract).\n//\n// SSR-skip mode: when the caller passes `ssrFallback`, the heavy child is\n// **not** evaluated at SSR time. Instead the wrapper emits a different\n// marker:\n//\n// <Island ssrFallback={<div>Loading…</div>}><HeavyClientOnly /></Island>\n// →\n// <div data-zfb-island-skip-ssr=\"HeavyClientOnly\">\n// <div>Loading…</div>\n// </div>\n//\n// On the client the hydration runtime distinguishes hydrate vs. render by\n// which marker attribute is present. This is the equivalent of Astro's\n// `client:only=\"preact\"`.\n//\n// When validation: in development we console.warn for unknown `when`\n// values and fall back to the default. In production we silently fall\n// back to keep the bundle path small.\n\nimport { jsx } from \"react/jsx-runtime\";\n\nimport type { VNode } from \"./jsx-types.js\";\nimport { DEFAULT_WHEN, resolveWhen, type When } from \"./types.js\";\n\n// Re-export `resolveWhen` for back-compat: tests and downstream consumers\n// historically imported it from `./island.js`. The implementation lives in\n// `./types.js` so the runtime scheduler can pull it in without dragging\n// the JSX wrapper along for the ride.\nexport { resolveWhen } from \"./types.js\";\n\n/**\n * Marker attribute the SSR wrapper writes when the child component should\n * be hydrated client-side. The hydration runtime queries\n * `[data-${HYDRATE_MARKER_ATTR}]` to find islands.\n */\nexport const HYDRATE_MARKER_ATTR = \"data-zfb-island\";\n\n/**\n * Marker attribute the SSR wrapper writes when SSR is being skipped (the\n * client:only-equivalent path). The hydration runtime queries\n * `[data-${SKIP_SSR_MARKER_ATTR}]` to find these placeholders and renders\n * the real component into them on hydration — there is no server output\n * to patch up.\n */\nexport const SKIP_SSR_MARKER_ATTR = \"data-zfb-island-skip-ssr\";\n\n/** Fallback name surfaced when child identity cannot be determined. */\nexport const ANONYMOUS_COMPONENT_NAME = \"Anonymous\";\n\n/**\n * Attribute the SSR wrapper writes to ferry the wrapped component's props\n * across the SSR → hydrate boundary. The hydration runtime parses this\n * with `JSON.parse` and forwards the result to the per-island `mount()`\n * call so the hydrated component sees the same props the SSR pass did.\n *\n * Omitted entirely when the wrapped child has no own data props (other\n * than `children`) — `readProps` already falls back to `{}` when the\n * attribute is missing, and emitting `data-props=\"\"` would just bloat\n * the SSR markup.\n */\nexport const PROPS_DATA_ATTR = \"data-props\";\n\n/** Props for `<Island>`. */\nexport interface IslandProps {\n /** Hydration scheduling strategy. Defaults to `\"load\"`. */\n when?: When;\n /**\n * CSS media query string for `when=\"media\"` islands. The island hydrates\n * when this query first matches (including later viewport changes). The\n * runtime uses `window.matchMedia(media)` to register a listener.\n *\n * Ignored when `when` is not `\"media\"` (a dev warning is emitted).\n * Required when `when === \"media\"` (omitting it falls back to DEFAULT_WHEN\n * with a dev warning).\n */\n media?: string;\n /**\n * If supplied, switches the island into SSR-skip mode (Astro's\n * `client:only` equivalent). The wrapper emits the\n * `data-zfb-island-skip-ssr` marker, the heavy `children` are **not**\n * evaluated server-side, and `ssrFallback` is rendered in their place.\n * On hydration the client runtime swaps in the real component.\n */\n ssrFallback?: VNode;\n /**\n * Server-rendered children, hydrated client-side once `when` fires.\n *\n * Typed as `VNode` (structural union) rather than `ReactNode` so\n * non-React frameworks can implement `IslandProps` without a React\n * type dependency (BCI-4).\n */\n children?: VNode;\n}\n\n/**\n * Public JSX-element shape returned by [`Island`]. Intentionally widened\n * to a structural type so consumers don't infer through the internal\n * `{ type, props, key }` VNode shape of either Preact or React. Both\n * jsx-runtimes accept this object on either side of the boundary.\n */\nexport type IslandElement = {\n readonly type: string;\n readonly props: Readonly<Record<string, unknown>>;\n readonly key: unknown;\n};\n\n/**\n * `<Island>` JSX wrapper.\n *\n * Returns a JSX element shape compatible with both Preact and React. The\n * runtime tag is `\"div\"`. In the default (hydrate) mode the wrapper emits\n * `data-zfb-island=\"ComponentName\"` and `data-when=\"<resolved-when>\"`. In\n * SSR-skip mode (when `ssrFallback` is provided) it emits\n * `data-zfb-island-skip-ssr=\"ComponentName\"` instead and renders the\n * fallback rather than the heavy child.\n *\n * The component-name string is derived from the child JSX element's type\n * identity (`type.displayName ?? type.name`). For string-typed children\n * (host elements) the tag name is used. If no usable identity can be\n * recovered, [`ANONYMOUS_COMPONENT_NAME`] is used so the marker still\n * lines up with the hydration shim's manifest lookup.\n *\n * The return type is the public [`IslandElement`] shape — the internal\n * VNode structure is deliberately not leaked so consumers never type-infer\n * through it.\n */\nexport function Island(props: IslandProps): IslandElement {\n const resolvedWhen = resolveMediaProps(props);\n const when = resolvedWhen.when;\n const media = resolvedWhen.media;\n const componentName = captureComponentName(props.children);\n const isSkipSsr = props.ssrFallback !== undefined;\n // Always source props from `props.children` (the heavy component VNode),\n // never from `ssrFallback` — the fallback is just SSR placeholder markup;\n // the hydrated component is the child, so its props are what `mount()`\n // needs at hydrate time. (Same rationale as `captureComponentName`.)\n const dataProps = captureSerializableProps(props.children);\n\n if (isSkipSsr) {\n const skipSsrProps: Record<string, unknown> = {\n [SKIP_SSR_MARKER_ATTR]: componentName,\n \"data-when\": when,\n };\n if (when === \"media\" && media !== undefined) skipSsrProps[\"data-media\"] = media;\n if (dataProps !== undefined) skipSsrProps[PROPS_DATA_ATTR] = dataProps;\n return makeWrapper(skipSsrProps, props.ssrFallback ?? null);\n }\n\n const hydrateProps: Record<string, unknown> = {\n [HYDRATE_MARKER_ATTR]: componentName,\n \"data-when\": when,\n };\n if (when === \"media\" && media !== undefined) hydrateProps[\"data-media\"] = media;\n if (dataProps !== undefined) hydrateProps[PROPS_DATA_ATTR] = dataProps;\n return makeWrapper(hydrateProps, props.children);\n}\n\n/**\n * Validate and resolve the `when` / `media` props together.\n *\n * - `when=\"media\"` without `media` → warn + fall back to `DEFAULT_WHEN`.\n * - `media` without `when=\"media\"` → warn-and-ignore; `media` is dropped.\n */\nfunction resolveMediaProps(props: IslandProps): { when: When; media: string | undefined } {\n const when = resolveWhen(props.when);\n const media = props.media;\n\n if (when === \"media\" && !media) {\n if (typeof process !== \"undefined\" && process.env && process.env[\"NODE_ENV\"] !== \"production\") {\n // eslint-disable-next-line no-console\n console.warn(\n `[zfb] <Island when=\"media\"> requires the \\`media\\` prop (a CSS media query string). ` +\n `Falling back to \"${DEFAULT_WHEN}\".`,\n );\n }\n return { when: DEFAULT_WHEN, media: undefined };\n }\n\n if (media && when !== \"media\") {\n if (typeof process !== \"undefined\" && process.env && process.env[\"NODE_ENV\"] !== \"production\") {\n // eslint-disable-next-line no-console\n console.warn(\n `[zfb] The \\`media\\` prop is only used when \\`when=\"media\"\\`. ` +\n `Current when=\"${when}\" — \\`media\\` will be ignored.`,\n );\n }\n return { when, media: undefined };\n }\n\n return { when, media };\n}\n\n/**\n * Construct the wrapper element through the **automatic JSX runtime\n * factory**, called explicitly so this file stays plain `.ts` (no JSX\n * syntax).\n *\n * Why the explicit `jsx(...)` call rather than a hand-rolled\n * `{ type, props, key }` object literal: a plain literal is only valid for\n * Preact (whose diff path / `preact-render-to-string` recognise a VNode by\n * `vnode.constructor === undefined`). React's renderer rejects such an\n * object as a child with \"Objects are not valid as a React child\"\n * (minified error #31) because a real React element carries\n * `$$typeof: Symbol.for(\"react.element\")`, which a literal cannot fake\n * portably. Calling `jsx` from `react/jsx-runtime` mints a real element:\n * in React mode it resolves natively; in Preact mode the engine rewrites\n * `react/jsx-runtime` → `preact/jsx-runtime` (bundler.rs ~2886), so the\n * Preact runtime mints the element. Same result the JSX `<div>` delegation\n * produced — but as plain `.ts`.\n *\n * Why `.ts` and NOT `.tsx`: esbuild rewrites a `.js` import specifier to\n * `.ts` but NOT to `.tsx`. The barrel `index.ts` imports `./island.js`;\n * when this file was `island.tsx`, source consumers (the dev `exports`\n * point at `src/*.ts`, e.g. the node-free template / smoke path) failed\n * with `Could not resolve \"./island.js\"`. The published `dist` worked\n * (tsc emits a real `island.js`), which masked the regression in\n * pnpm-pack/dist probes. Keeping this a `.ts` file makes the barrel resolve\n * `./island.js` → `./island.ts` from source everywhere, while tsc still\n * emits `dist/island.js` for the published package.\n *\n * The cast back to `IslandElement` keeps the public type tight; the\n * concrete element shape produced by the runtime is invisible to type\n * consumers.\n */\nfunction makeWrapper(props: Record<string, unknown>, children: unknown): IslandElement {\n return jsx(\"div\", { ...props, children }) as unknown as IslandElement;\n}\n\n/**\n * Pull a component-name string out of a JSX child.\n *\n * Both Preact and React store rendered VNodes as plain objects whose\n * `.type` field is either:\n * - the component function (look at `displayName ?? name`),\n * - or the host element tag name as a string.\n *\n * If `children` is an array (multiple children), the first child with a\n * usable identity wins. This is intentional: the typical island shape is\n * `<Island><Foo /></Island>` (single child); when the caller wraps a\n * fragment-like list we still want a deterministic, debuggable name.\n *\n * Exported for tests; not re-exported from `index.ts`.\n */\nexport function captureComponentName(children: unknown): string {\n if (Array.isArray(children)) {\n for (const child of children) {\n const name = nameFromSingle(child);\n if (name) return name;\n }\n return ANONYMOUS_COMPONENT_NAME;\n }\n return nameFromSingle(children) || ANONYMOUS_COMPONENT_NAME;\n}\n\nfunction nameFromSingle(child: unknown): string {\n if (!child || typeof child !== \"object\") return \"\";\n const c = child as { type?: unknown };\n const t = c.type;\n if (typeof t === \"function\") {\n const fn = t as { displayName?: unknown; name?: unknown };\n if (typeof fn.displayName === \"string\" && fn.displayName) return fn.displayName;\n if (typeof fn.name === \"string\" && fn.name) return fn.name;\n return \"\";\n }\n if (typeof t === \"string\" && t) return t;\n return \"\";\n}\n\n/**\n * Serialize the wrapped child's data props as a JSON string the runtime\n * can parse out of the `data-props` attribute on the Island marker div.\n *\n * Mirrors [`captureComponentName`]'s array handling: when `children` is\n * an array (multiple JSX siblings), the first child whose own props\n * yield a non-empty serialization wins. This keeps the \"first\n * identifiable child\" contract consistent across both attributes —\n * whatever the marker name points at is what the data-props payload\n * describes.\n *\n * Returns `undefined` (not `\"{}\"` and not `\"\"`) when no usable props\n * exist. The runtime's `readProps` already maps a missing attribute to\n * `{}`, so omitting the attribute keeps the SSR markup smaller and\n * preserves the invariant that the attribute, when present, always\n * parses to a non-empty record.\n *\n * Exported for tests; not re-exported from `index.ts`.\n */\nexport function captureSerializableProps(children: unknown): string | undefined {\n if (Array.isArray(children)) {\n for (const child of children) {\n const json = propsFromSingle(child);\n if (json !== undefined) return json;\n }\n return undefined;\n }\n return propsFromSingle(children);\n}\n\nfunction propsFromSingle(child: unknown): string | undefined {\n if (!child || typeof child !== \"object\") return undefined;\n const c = child as { props?: unknown };\n const raw = c.props;\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return undefined;\n\n // Exclude `children` from the serialized payload: the SSR pass already\n // emitted the rendered children into the DOM (the hydration target),\n // and JSX child nodes are typically VNode trees with non-serializable\n // shapes (functions, circular refs) that would either bloat the\n // payload or throw inside JSON.stringify. The hydration runtime\n // re-renders into the existing DOM, so it does not need the JSX\n // children replayed through props.\n //\n // Note: JSON.stringify already silently drops function / symbol /\n // undefined values from the output, so those don't need explicit\n // pre-filtering here.\n const propsRecord = raw as Record<string, unknown>;\n let hasOwn = false;\n const filtered: Record<string, unknown> = {};\n for (const key of Object.keys(propsRecord)) {\n if (key === \"children\") continue;\n filtered[key] = propsRecord[key];\n hasOwn = true;\n }\n if (!hasOwn) return undefined;\n\n let json: string | undefined;\n try {\n json = JSON.stringify(filtered);\n } catch {\n // Circular references, BigInt, or any other non-serializable input\n // — silently fall through (no `data-props`) rather than ship a\n // partial payload. The runtime already handles the missing-attribute\n // case by returning `{}` from `readProps`.\n return undefined;\n }\n\n // JSON.stringify can also return `undefined` (when the top-level value\n // serializes to nothing) or `\"{}\"` (when every key was a function /\n // symbol / undefined and got dropped). Treat both as \"nothing useful\n // to ship\" so the marker stays clean.\n if (json === undefined || json === \"{}\") return undefined;\n return json;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"island.js","sourceRoot":"","sources":["../src/island.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAG3D,OAAO,EAAE,YAAY,EAAE,WAAW,EAAa,MAAM,YAAY,CAAC;AAElE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,MAAM,CAAC,MAAM,mBAAmB,GAAG,iBAAiB,CAAC;AACrD,MAAM,CAAC,MAAM,oBAAoB,GAAG,0BAA0B,CAAC;AAW/D,MAAM,UAAU,MAAM,CAAC,KAAkB;IACvC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;IACjD,OAAO,mBAAmB,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,WAAW,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;AAC7E,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAkB;IAC3C,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;IAE1B,IAAI,IAAI,KAAK,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC;QAC/B,IAAI,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,YAAY,EAAE,CAAC;YAC9F,sCAAsC;YACtC,OAAO,CAAC,IAAI,CACV,sFAAsF;gBACpF,oBAAoB,YAAY,IAAI,CACvC,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IAClD,CAAC;IAED,IAAI,KAAK,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QAC9B,IAAI,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,YAAY,EAAE,CAAC;YAC9F,sCAAsC;YACtC,OAAO,CAAC,IAAI,CACV,+DAA+D;gBAC7D,iBAAiB,IAAI,gCAAgC,CACxD,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IACpC,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AACzB,CAAC","sourcesContent":["// Build-time Island wrapper for the owned zudo-react runtime.\nimport { ownedIslandBoundary } from \"./island-boundary.js\";\nimport type { VNode } from \"./jsx-types.js\";\nimport type { Description } from \"./zudo-react/description.js\";\nimport { DEFAULT_WHEN, resolveWhen, type When } from \"./types.js\";\n\nexport { resolveWhen } from \"./types.js\";\nexport const HYDRATE_MARKER_ATTR = \"data-zfb-island\";\nexport const SKIP_SSR_MARKER_ATTR = \"data-zfb-island-skip-ssr\";\n\nexport interface IslandProps {\n when?: When;\n media?: string;\n ssrFallback?: VNode;\n children?: VNode;\n}\n\nexport type IslandElement = Description;\n\nexport function Island(props: IslandProps): IslandElement {\n const { when, media } = resolveMediaProps(props);\n return ownedIslandBoundary(props.children, props.ssrFallback, when, media);\n}\n\nfunction resolveMediaProps(props: IslandProps): { when: When; media: string | undefined } {\n const when = resolveWhen(props.when);\n const media = props.media;\n\n if (when === \"media\" && !media) {\n if (typeof process !== \"undefined\" && process.env && process.env[\"NODE_ENV\"] !== \"production\") {\n // eslint-disable-next-line no-console\n console.warn(\n `[zfb] <Island when=\"media\"> requires the \\`media\\` prop (a CSS media query string). ` +\n `Falling back to \"${DEFAULT_WHEN}\".`,\n );\n }\n return { when: DEFAULT_WHEN, media: undefined };\n }\n\n if (media && when !== \"media\") {\n if (typeof process !== \"undefined\" && process.env && process.env[\"NODE_ENV\"] !== \"production\") {\n // eslint-disable-next-line no-console\n console.warn(\n `[zfb] The \\`media\\` prop is only used when \\`when=\"media\"\\`. ` +\n `Current when=\"${when}\" — \\`media\\` will be ignored.`,\n );\n }\n return { when, media: undefined };\n }\n\n return { when, media };\n}\n"]}
|
package/dist/jsx-types.d.ts
CHANGED
|
@@ -1,46 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Structural JSX element (VNode). Framework-agnostic: both Preact and React
|
|
3
|
-
* produce objects that satisfy this shape when a JSX element is rendered.
|
|
4
|
-
*
|
|
5
|
-
* The `props` bag may itself contain `children` (nested VNodes) as well as
|
|
6
|
-
* HTML attribute values (strings, numbers, booleans). All fields are widened
|
|
7
|
-
* to `unknown` so the type stays opaque to callers that don't know which
|
|
8
|
-
* framework produced the value.
|
|
9
|
-
*
|
|
10
|
-
* `type` accepts BOTH function components (call signature) AND class
|
|
11
|
-
* components (construct signature). Preact and React both expose
|
|
12
|
-
* `ComponentType = FunctionComponent | ComponentClass`, and the JSX runtime
|
|
13
|
-
* stores either on `.type`. Without the construct signature, valid Preact
|
|
14
|
-
* `<MyComponent />` expressions fail to assign to `VNode` at the framework
|
|
15
|
-
* boundary (e.g. inside `<Island>` children) — even though class components
|
|
16
|
-
* are rare in modern Preact, the `ComponentType` union includes them.
|
|
17
|
-
*/
|
|
1
|
+
/** Opaque description-shaped object accepted at the SDK boundary. */
|
|
18
2
|
export type VNodeObject = {
|
|
19
3
|
readonly type: string | ((...args: unknown[]) => unknown) | (new (...args: unknown[]) => unknown);
|
|
20
4
|
readonly props: Readonly<Record<string, unknown>>;
|
|
21
5
|
readonly key: unknown;
|
|
22
6
|
};
|
|
23
|
-
/**
|
|
24
|
-
* A renderable JSX node: a primitive, `null`, `undefined`, an array, a
|
|
25
|
-
* structured VNode object, or a bare `object`.
|
|
26
|
-
*
|
|
27
|
-
* This union covers every value both Preact and React treat as valid
|
|
28
|
-
* `children`. The bare `object` member is required to make Preact's
|
|
29
|
-
* `ComponentChildren` / `VNode<{}>` assignable without casts — Preact's own
|
|
30
|
-
* `ComponentChild` uses the same widening pattern. The loosening is safe
|
|
31
|
-
* because all SDK runtime code duck-types `unknown` at the boundary
|
|
32
|
-
* (island.ts, content.ts) and the Rust pipeline is type-erased.
|
|
33
|
-
*
|
|
34
|
-
* Importantly, the union does **not** reference any framework-specific type,
|
|
35
|
-
* so packages that depend on `zfb` need not install a React / Preact `@types`
|
|
36
|
-
* package to satisfy this type.
|
|
37
|
-
*
|
|
38
|
-
* **Name-collision note for Preact consumers:** if your file already has
|
|
39
|
-
* `import { VNode } from "preact"`, use a qualifier on the zfb import:
|
|
40
|
-
* `import type { VNode as ZfbVNode } from "@takazudo/zfb"`.
|
|
41
|
-
*/
|
|
7
|
+
/** Broad authored child input; the owned renderer validates actual children. */
|
|
42
8
|
export type VNode = string | number | boolean | null | undefined | bigint | VNodeArray | VNodeObject | object;
|
|
43
9
|
export interface VNodeArray extends ReadonlyArray<VNode> {
|
|
44
10
|
}
|
|
45
|
-
/** @deprecated Use `VNode` instead. */
|
|
46
|
-
export type ReactNode = VNode;
|
package/dist/jsx-types.js
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// BCI-4: `IslandProps.children` is now typed as `VNode` (a structural union)
|
|
5
|
-
// rather than the old `ReactNode` alias, so non-React frameworks can
|
|
6
|
-
// implement `IslandProps` without any React type dependency at the type level.
|
|
7
|
-
//
|
|
8
|
-
// Both Preact and React's `children` accept this structural union; the build
|
|
9
|
-
// is type-erased so the actual rendering happens in the user's app under
|
|
10
|
-
// whichever framework adapter they chose.
|
|
1
|
+
// Structural input types used by SDK helpers that inspect authored children.
|
|
2
|
+
// The owned Island boundary validates descriptions and component identity
|
|
3
|
+
// at runtime before rendering.
|
|
11
4
|
export {};
|
|
12
5
|
//# sourceMappingURL=jsx-types.js.map
|
package/dist/jsx-types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jsx-types.js","sourceRoot":"","sources":["../src/jsx-types.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"jsx-types.js","sourceRoot":"","sources":["../src/jsx-types.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,0EAA0E;AAC1E,+BAA+B","sourcesContent":["// Structural input types used by SDK helpers that inspect authored children.\n// The owned Island boundary validates descriptions and component identity\n// at runtime before rendering.\n\n/** Opaque description-shaped object accepted at the SDK boundary. */\nexport type VNodeObject = {\n readonly type: string | ((...args: unknown[]) => unknown) | (new (...args: unknown[]) => unknown);\n readonly props: Readonly<Record<string, unknown>>;\n readonly key: unknown;\n};\n\n/** Broad authored child input; the owned renderer validates actual children. */\nexport type VNode =\n | string\n | number\n | boolean\n | null\n | undefined\n | bigint\n | VNodeArray\n | VNodeObject\n | object;\n\nexport interface VNodeArray extends ReadonlyArray<VNode> {}\n"]}
|
package/dist/plugins.d.ts
CHANGED
|
@@ -63,6 +63,16 @@ export type ZfbRouteManifest = {
|
|
|
63
63
|
export type ZfbBuildHookContext = {
|
|
64
64
|
/** Project root — the directory containing `zfb.config.ts`. */
|
|
65
65
|
projectRoot: string;
|
|
66
|
+
/**
|
|
67
|
+
* Absolute directory for the plugin's opaque intermediates
|
|
68
|
+
* (`<scratch root>/plugins`; `<projectRoot>/.zfb-build/plugins` by
|
|
69
|
+
* default). It is not an importable route location: generated route
|
|
70
|
+
* modules placed here are not staged into the bundle. zfb does not
|
|
71
|
+
* create it, so `mkdir(scratchDir, { recursive: true })` and use a
|
|
72
|
+
* `<scratchDir>/<plugin-name>/` subdirectory. Under `--scratch-dir`,
|
|
73
|
+
* plugin isolation only holds if the plugin adopts this directory.
|
|
74
|
+
*/
|
|
75
|
+
scratchDir: string;
|
|
66
76
|
/** Resolved absolute path of the build output directory. */
|
|
67
77
|
outDir: string;
|
|
68
78
|
/** The full loaded `ZfbConfig` (data-only view). */
|
|
@@ -118,6 +128,16 @@ export type ZfbDevMiddlewareHandler = (req: ZfbDevMiddlewareRequest) => Promise<
|
|
|
118
128
|
*/
|
|
119
129
|
export type ZfbDevMiddlewareContext = {
|
|
120
130
|
projectRoot: string;
|
|
131
|
+
/**
|
|
132
|
+
* Absolute directory for the plugin's opaque intermediates
|
|
133
|
+
* (`<scratch root>/plugins`; `<projectRoot>/.zfb-build/plugins` by
|
|
134
|
+
* default). It is not an importable route location: generated route
|
|
135
|
+
* modules placed here are not staged into the bundle. zfb does not
|
|
136
|
+
* create it, so `mkdir(scratchDir, { recursive: true })` and use a
|
|
137
|
+
* `<scratchDir>/<plugin-name>/` subdirectory. Under `--scratch-dir`,
|
|
138
|
+
* plugin isolation only holds if the plugin adopts this directory.
|
|
139
|
+
*/
|
|
140
|
+
scratchDir: string;
|
|
121
141
|
config: import("./config.js").ZfbConfig;
|
|
122
142
|
options: Record<string, unknown>;
|
|
123
143
|
logger: ZfbPluginLogger;
|
|
@@ -150,6 +170,16 @@ export type ZfbPreviewMiddlewareHandler = (req: ZfbDevMiddlewareRequest) => Prom
|
|
|
150
170
|
*/
|
|
151
171
|
export type ZfbPreviewMiddlewareContext = {
|
|
152
172
|
projectRoot: string;
|
|
173
|
+
/**
|
|
174
|
+
* Absolute directory for the plugin's opaque intermediates
|
|
175
|
+
* (`<scratch root>/plugins`; `<projectRoot>/.zfb-build/plugins` by
|
|
176
|
+
* default). It is not an importable route location: generated route
|
|
177
|
+
* modules placed here are not staged into the bundle. zfb does not
|
|
178
|
+
* create it, so `mkdir(scratchDir, { recursive: true })` and use a
|
|
179
|
+
* `<scratchDir>/<plugin-name>/` subdirectory. Under `--scratch-dir`,
|
|
180
|
+
* plugin isolation only holds if the plugin adopts this directory.
|
|
181
|
+
*/
|
|
182
|
+
scratchDir: string;
|
|
153
183
|
config: import("./config.js").ZfbConfig;
|
|
154
184
|
options: Record<string, unknown>;
|
|
155
185
|
logger: ZfbPluginLogger;
|
|
@@ -258,6 +288,16 @@ export type ZfbSetupContext = {
|
|
|
258
288
|
command: "build" | "dev" | "preview";
|
|
259
289
|
/** Project root — the directory containing `zfb.config.ts`. */
|
|
260
290
|
projectRoot: string;
|
|
291
|
+
/**
|
|
292
|
+
* Absolute directory for the plugin's opaque intermediates
|
|
293
|
+
* (`<scratch root>/plugins`; `<projectRoot>/.zfb-build/plugins` by
|
|
294
|
+
* default). It is not an importable route location: generated route
|
|
295
|
+
* modules placed here are not staged into the bundle. zfb does not
|
|
296
|
+
* create it, so `mkdir(scratchDir, { recursive: true })` and use a
|
|
297
|
+
* `<scratchDir>/<plugin-name>/` subdirectory. Under `--scratch-dir`,
|
|
298
|
+
* plugin isolation only holds if the plugin adopts this directory.
|
|
299
|
+
*/
|
|
300
|
+
scratchDir: string;
|
|
261
301
|
/** The full loaded `ZfbConfig` (data-only view). */
|
|
262
302
|
config: import("./config.js").ZfbConfig;
|
|
263
303
|
/** Plugin-specific options block, copied verbatim from `PluginConfig.options`. */
|