@takazudo/zfb 0.1.0-next.9 → 0.1.0-next.91
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/CHANGELOG.md +26 -0
- package/README.md +81 -23
- package/bin/detect-musl.mjs +55 -0
- package/bin/zfb.mjs +67 -14
- package/dist/client-script.d.ts +54 -0
- package/dist/client-script.js +67 -0
- package/dist/client-script.js.map +1 -0
- package/dist/config.d.ts +682 -30
- package/dist/config.js +43 -3
- package/dist/config.js.map +1 -1
- package/dist/content.d.ts +43 -2
- package/dist/content.js +101 -27
- package/dist/content.js.map +1 -1
- package/dist/frontmatter.d.ts +0 -1
- package/dist/frontmatter.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/island.d.ts +10 -1
- package/dist/island.js +67 -28
- package/dist/island.js.map +1 -1
- package/dist/jsx-types.d.ts +16 -7
- package/dist/jsx-types.js.map +1 -1
- package/dist/paginate.d.ts +0 -1
- package/dist/paginate.js.map +1 -1
- package/dist/plugins.d.ts +140 -22
- package/dist/plugins.js.map +1 -1
- package/dist/runtime.d.ts +17 -3
- package/dist/runtime.js +279 -30
- package/dist/runtime.js.map +1 -1
- package/dist/slugify.d.ts +68 -0
- package/dist/slugify.js +197 -0
- package/dist/slugify.js.map +1 -0
- package/dist/types.d.ts +4 -2
- package/dist/types.js +2 -2
- package/dist/types.js.map +1 -1
- package/dist/wasm.d.ts +4 -0
- package/dist/wasm.js +2 -0
- package/dist/wasm.js.map +1 -0
- package/package.json +22 -7
- package/dist/config.d.ts.map +0 -1
- package/dist/content.d.ts.map +0 -1
- package/dist/frontmatter.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/island.d.ts.map +0 -1
- package/dist/jsx-types.d.ts.map +0 -1
- package/dist/paginate.d.ts.map +0 -1
- package/dist/plugins.d.ts.map +0 -1
- package/dist/runtime.d.ts.map +0 -1
- package/dist/types.d.ts.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
> **Newer releases:** see https://takazudomodular.com/pj/zudo-front-builder/docs/changelog/ for v0.1.0-next.5 and later. Entries below are historical (kept for npm readers).
|
|
4
4
|
|
|
5
|
+
## Unreleased
|
|
6
|
+
|
|
7
|
+
### New features
|
|
8
|
+
|
|
9
|
+
**`VNode`, `VNodeArray`, `VNodeObject` exported from `"@takazudo/zfb"`** (#972):
|
|
10
|
+
|
|
11
|
+
The structural JSX-node types are now part of the public API:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import type { VNode, VNodeArray, VNodeObject } from "@takazudo/zfb";
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`VNode` now includes a bare `object` member (matching Preact's own `ComponentChild` design), making Preact's `ComponentChildren`, `VNode<Props>`, `JSX.Element`, and `JSX.Element[]` all assignable at `Island` input boundaries (`children` and `ssrFallback`) with zero `as unknown as` casts.
|
|
18
|
+
|
|
19
|
+
**Name-collision caveat for Preact consumers:** if a consumer file already has `import { VNode } from "preact"`, use a qualified import to avoid the clash:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import type { VNode as ZfbVNode } from "@takazudo/zfb";
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Breaking changes (pre-1.0)
|
|
26
|
+
|
|
27
|
+
**`linkValidation.allowExternal` removed** (#925):
|
|
28
|
+
|
|
29
|
+
The `allowExternal` config knob has been removed. It was accepted but never did anything — external URL network validation is out of scope. Migration: delete `allowExternal` from your `linkValidation` config; external URLs continue to be silently skipped (unchanged runtime behaviour).
|
|
30
|
+
|
|
5
31
|
## 0.1.0-next.4
|
|
6
32
|
|
|
7
33
|
### Bug fixes
|
package/README.md
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
The public SDK module for [zfb][zfb-site]: islands, content collections,
|
|
6
6
|
pagination, config, plugins, and frontmatter helpers. User pages reach this
|
|
7
|
-
package through the bare specifier `"zfb"` — the
|
|
8
|
-
|
|
7
|
+
package through the bare specifier `"zfb"` — the build pipeline aliases that
|
|
8
|
+
specifier to `@takazudo/zfb` so user TSX can write:
|
|
9
9
|
|
|
10
10
|
```tsx
|
|
11
11
|
import { Island } from "zfb";
|
|
@@ -29,16 +29,22 @@ npm install @takazudo/zfb
|
|
|
29
29
|
This package is the canonical TypeScript source for the `zfb` SDK
|
|
30
30
|
surface. Today it covers:
|
|
31
31
|
|
|
32
|
-
- `<Island when="visible|idle|load">` — JSX wrapper
|
|
33
|
-
for client-side hydration.
|
|
32
|
+
- `<Island when="visible|idle|load|media" media="...">` — JSX wrapper
|
|
33
|
+
that marks a region for client-side hydration. Passing `ssrFallback`
|
|
34
|
+
switches to the SSR-skip marker (`data-zfb-island-skip-ssr`).
|
|
34
35
|
- `scheduleHydrate(target, when, fire)` — the runtime branching helper
|
|
35
36
|
consumed by the hydration runtime.
|
|
37
|
+
- `mountIslands(manifest)`, `mountNewIslands()`,
|
|
38
|
+
`cancelPendingIslands()`, and `unmountIslands(...)` — public island
|
|
39
|
+
lifecycle helpers used by the generated islands runtime and by the
|
|
40
|
+
client router after body swaps.
|
|
36
41
|
- `When`, `WHEN_VALUES`, `DEFAULT_WHEN`, `isWhen`, `resolveWhen` — type
|
|
37
|
-
and runtime utilities pinning the spelling of the
|
|
38
|
-
- `getCollection(name)`, `
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
and runtime utilities pinning the spelling of the four modes.
|
|
43
|
+
- `getCollection(name)`, `getEntry(name, slug)`, and
|
|
44
|
+
`parseFrontmatter(raw)` — content collection helpers exported from
|
|
45
|
+
`zfb/content`. `parseFrontmatter` is part of the public SDK surface so
|
|
46
|
+
consumers can write custom content loaders that reuse the v0
|
|
47
|
+
frontmatter parser without re-implementing it.
|
|
42
48
|
- `defaultComponents` — eleven-entry per-element override map (`h2`, `h3`,
|
|
43
49
|
`h4`, `p`, `a`, `strong`, `blockquote`, `ul`, `ol`, `table`, `code`)
|
|
44
50
|
ported from zudo-doc's `htmlOverrides` convention. **`h1` is deliberately
|
|
@@ -53,12 +59,23 @@ surface. Today it covers:
|
|
|
53
59
|
|
|
54
60
|
<entry.Content components={{ ...defaultComponents, h2: MyFancyH2 }} />
|
|
55
61
|
```
|
|
62
|
+
- `mergeMdxComponents(globalSlot, perCall)` — precedence merge helper for
|
|
63
|
+
the MDX component map (`defaultComponents` < global slot < per-call
|
|
64
|
+
overrides).
|
|
56
65
|
- `paginate(items, opts)`, plus `PaginatedPage<T>` / `PaginateRoute<T>` —
|
|
57
66
|
exported from `zfb/paginate`.
|
|
58
67
|
- `defineConfig(config)` — exported from `zfb/config` for the
|
|
59
68
|
`zfb.config.ts` form (the recommended way to author a zfb project's
|
|
60
69
|
configuration; the back-compat `zfb.config.json` form is still
|
|
61
70
|
supported).
|
|
71
|
+
- `definePlugin(plugin)` — identity helper exported from `zfb/plugins`
|
|
72
|
+
and the root barrel so plugin authors get typed lifecycle hooks without
|
|
73
|
+
changing runtime behavior.
|
|
74
|
+
- `clientScript(name)` — SSR helper that returns the stable URL for a
|
|
75
|
+
named client-script asset.
|
|
76
|
+
- `slugify(input)` and `SlugAllocator` — exported from the root barrel and
|
|
77
|
+
the `zfb/slugify` subpath for heading-id parity with the Rust content
|
|
78
|
+
pipeline.
|
|
62
79
|
|
|
63
80
|
The package is JSX-runtime-agnostic: the `Island` component does not
|
|
64
81
|
import preact or react, so it works under either framework adapter
|
|
@@ -89,35 +106,61 @@ export default function Page() {
|
|
|
89
106
|
<Island when="visible">
|
|
90
107
|
<Counter />
|
|
91
108
|
</Island>
|
|
109
|
+
|
|
110
|
+
{/* Hydrate when a CSS media query first matches. */}
|
|
111
|
+
<Island when="media" media="(max-width: 720px)">
|
|
112
|
+
<Counter />
|
|
113
|
+
</Island>
|
|
114
|
+
|
|
115
|
+
{/* Skip SSR for the heavy child and render a placeholder instead. */}
|
|
116
|
+
<Island when="idle" ssrFallback={<div>Loading…</div>}>
|
|
117
|
+
<Counter />
|
|
118
|
+
</Island>
|
|
92
119
|
</>
|
|
93
120
|
);
|
|
94
121
|
}
|
|
95
122
|
```
|
|
96
123
|
|
|
97
|
-
## The
|
|
124
|
+
## The four `when=` modes
|
|
98
125
|
|
|
99
|
-
| `when` | Trigger
|
|
100
|
-
| ----------- |
|
|
101
|
-
| `"load"` | Synchronous, immediate fire after registration. **Default.**
|
|
102
|
-
| `"idle"` | `requestIdleCallback`
|
|
103
|
-
| `"visible"` | `IntersectionObserver`, threshold 0, first intersection only
|
|
126
|
+
| `when` | Trigger | Fallback |
|
|
127
|
+
| ----------- | -------------------------------------------------------------- | -------------------------------------------------- |
|
|
128
|
+
| `"load"` | Synchronous, immediate fire after registration. **Default.** | n/a |
|
|
129
|
+
| `"idle"` | `requestIdleCallback` | `setTimeout(0)` when not available |
|
|
130
|
+
| `"visible"` | `IntersectionObserver`, threshold 0, first intersection only | Immediate fire when `IntersectionObserver` is missing |
|
|
131
|
+
| `"media"` | `matchMedia(media)`, first matching change only | Immediate fire when `matchMedia` or `media` is missing |
|
|
104
132
|
|
|
105
133
|
Unknown values produce a `console.warn` in development builds and fall
|
|
106
134
|
back to `"load"`.
|
|
107
135
|
|
|
136
|
+
`when="media"` requires a `media` prop containing a CSS media query
|
|
137
|
+
string. Supplying `media` with any other `when` value is ignored and
|
|
138
|
+
warns in development builds.
|
|
139
|
+
|
|
108
140
|
## Build-time output
|
|
109
141
|
|
|
110
|
-
The wrapper is intentionally type-erased at the JSX boundary.
|
|
111
|
-
|
|
142
|
+
The wrapper is intentionally type-erased at the JSX boundary. It reads
|
|
143
|
+
the wrapped child's JSX type identity (`displayName`, then `name`, then
|
|
144
|
+
host tag name) and writes the component name immediately. At the call
|
|
145
|
+
site, `<Island when="visible"><Counter count={1} /></Island>` renders as:
|
|
112
146
|
|
|
113
147
|
```html
|
|
114
|
-
<div data-zfb-island data-when="visible"
|
|
148
|
+
<div data-zfb-island="Counter" data-when="visible" data-props='{"count":1}'>
|
|
149
|
+
<!-- rendered child output -->
|
|
150
|
+
</div>
|
|
115
151
|
```
|
|
116
152
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
153
|
+
`data-props` carries the wrapped child's serializable own props across
|
|
154
|
+
the SSR-to-hydration boundary. The wrapper omits `children`, omits the
|
|
155
|
+
attribute entirely when there is no useful props payload, and the runtime
|
|
156
|
+
falls back to `{}` when the attribute is missing or malformed.
|
|
157
|
+
|
|
158
|
+
When `ssrFallback` is supplied, the heavy child is not rendered at SSR
|
|
159
|
+
time. The wrapper writes `data-zfb-island-skip-ssr="ComponentName"`
|
|
160
|
+
instead of `data-zfb-island`, still includes `data-when` / `data-media`
|
|
161
|
+
and `data-props` when applicable, and renders the fallback markup inside
|
|
162
|
+
the wrapper. The client runtime treats that marker as a render target
|
|
163
|
+
rather than a hydration target.
|
|
121
164
|
|
|
122
165
|
## Runtime helper
|
|
123
166
|
|
|
@@ -125,7 +168,13 @@ The hydration runtime imports (or inlines) `scheduleHydrate` from this
|
|
|
125
168
|
package:
|
|
126
169
|
|
|
127
170
|
```ts
|
|
128
|
-
import {
|
|
171
|
+
import {
|
|
172
|
+
scheduleHydrate,
|
|
173
|
+
mountIslands,
|
|
174
|
+
mountNewIslands,
|
|
175
|
+
cancelPendingIslands,
|
|
176
|
+
unmountIslands,
|
|
177
|
+
} from "@takazudo/zfb/runtime";
|
|
129
178
|
|
|
130
179
|
for (const el of document.querySelectorAll<HTMLElement>("[data-zfb-island]")) {
|
|
131
180
|
const when = el.getAttribute("data-when") ?? "load";
|
|
@@ -137,6 +186,15 @@ for (const el of document.querySelectorAll<HTMLElement>("[data-zfb-island]")) {
|
|
|
137
186
|
if hydration has not fired yet. After firing, calling `cancel` is a
|
|
138
187
|
no-op.
|
|
139
188
|
|
|
189
|
+
`mountIslands(manifest)` captures the generated island manifest and
|
|
190
|
+
mounts both hydrated markers (`data-zfb-island`) and SSR-skip markers
|
|
191
|
+
(`data-zfb-island-skip-ssr`). `mountNewIslands()` re-walks the current
|
|
192
|
+
document after a client-router body swap using that captured manifest.
|
|
193
|
+
`cancelPendingIslands()` cancels deferred `idle` / `visible` / `media`
|
|
194
|
+
schedules before a swap, and `unmountIslands(root, incomingBody)` runs
|
|
195
|
+
framework cleanup for discarded islands while preserving matching
|
|
196
|
+
`data-zfb-transition-persist` islands.
|
|
197
|
+
|
|
140
198
|
## Markdown / GFM config
|
|
141
199
|
|
|
142
200
|
`ZfbConfig.markdown.gfm` controls which GitHub-Flavored-Markdown
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Extracted from zfb.mjs so the detection decision can be unit-tested
|
|
2
|
+
// without triggering zfb.mjs's top-level exec/spawn side effects. Pure and
|
|
3
|
+
// dependency-injectable: production callers can omit every option.
|
|
4
|
+
import { readdirSync } from "node:fs";
|
|
5
|
+
|
|
6
|
+
const MUSL_LOADER_DIR = "/lib";
|
|
7
|
+
const MUSL_LOADER_PREFIX = "ld-musl-";
|
|
8
|
+
|
|
9
|
+
// True if a musl dynamic loader file is present (e.g. Alpine's
|
|
10
|
+
// /lib/ld-musl-x86_64.so.1). Reading the directory and matching the prefix
|
|
11
|
+
// covers every arch's loader filename with one probe instead of hardcoding
|
|
12
|
+
// an existsSync path per arch. Mirrors the loader-file probe used by the
|
|
13
|
+
// widely-adopted `detect-libc` npm package.
|
|
14
|
+
function hasMuslLoaderFile(readdirSyncFn) {
|
|
15
|
+
try {
|
|
16
|
+
return readdirSyncFn(MUSL_LOADER_DIR).some((name) => name.startsWith(MUSL_LOADER_PREFIX));
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function defaultGetReport() {
|
|
23
|
+
return process.report?.getReport?.();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// True if Node's process.report exposes a glibc runtime version. glibc-built
|
|
27
|
+
// Node populates header.glibcVersionRuntime; musl-built Node does not. Used
|
|
28
|
+
// only as a fallback signal when the loader-file probe above is inconclusive.
|
|
29
|
+
function hasGlibcVersionRuntime(getReportFn) {
|
|
30
|
+
try {
|
|
31
|
+
return Boolean(getReportFn()?.header?.glibcVersionRuntime);
|
|
32
|
+
} catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Detects musl libc (e.g. Alpine) on Linux, where the gnu-built platform
|
|
39
|
+
* binary would otherwise fail with an opaque dynamic-loader exec error
|
|
40
|
+
* instead of a clear "not supported" message. Always returns false on
|
|
41
|
+
* non-linux platforms (darwin, win32) — zero behavior change there.
|
|
42
|
+
*/
|
|
43
|
+
export function detectMuslLinux({
|
|
44
|
+
platform = process.platform,
|
|
45
|
+
readdirSyncFn = readdirSync,
|
|
46
|
+
getReportFn = defaultGetReport,
|
|
47
|
+
} = {}) {
|
|
48
|
+
if (platform !== "linux") return false;
|
|
49
|
+
// The glibc runtime report is authoritative when present: a Node process
|
|
50
|
+
// that reports a glibc version is definitely glibc-linked and will work,
|
|
51
|
+
// regardless of an unrelated musl loader file sitting on the filesystem
|
|
52
|
+
// (e.g. a `musl` package installed on Debian/Ubuntu for other reasons).
|
|
53
|
+
if (hasGlibcVersionRuntime(getReportFn)) return false;
|
|
54
|
+
return hasMuslLoaderFile(readdirSyncFn);
|
|
55
|
+
}
|
package/bin/zfb.mjs
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Followed biome's pattern: pure os/cpu lookup → resolve platform package → spawn binary.
|
|
3
3
|
// See: https://github.com/biomejs/biome (packages/js/biome/bin/biome.mjs reference)
|
|
4
|
+
// Divergence from biome: async spawn + signal forwarding instead of spawnSync.
|
|
5
|
+
// biome is a short-lived CLI, but `zfb dev` is a long-running server — without
|
|
6
|
+
// forwarding, SIGTERM to the wrapper orphans the native binary (PPID 1) and
|
|
7
|
+
// leaves the dev-server port bound (issue #873).
|
|
4
8
|
import { existsSync } from "node:fs";
|
|
5
|
-
import {
|
|
9
|
+
import { spawn } from "node:child_process";
|
|
6
10
|
import { createRequire } from "node:module";
|
|
11
|
+
import { constants as osConstants } from "node:os";
|
|
7
12
|
import { join } from "node:path";
|
|
13
|
+
import { detectMuslLinux } from "./detect-musl.mjs";
|
|
8
14
|
|
|
9
15
|
const require = createRequire(import.meta.url);
|
|
10
16
|
|
|
@@ -27,6 +33,18 @@ if (!pkg) {
|
|
|
27
33
|
process.exit(1);
|
|
28
34
|
}
|
|
29
35
|
|
|
36
|
+
// The gnu-built binary above matches on os/cpu but not on libc: an
|
|
37
|
+
// Alpine/musl-libc Linux install passes the os/cpu check yet would still
|
|
38
|
+
// fail to exec, with a raw, confusing dynamic-loader error. Detect and fail
|
|
39
|
+
// with a clear message before attempting to resolve/spawn the binary.
|
|
40
|
+
if (detectMuslLinux()) {
|
|
41
|
+
console.error(
|
|
42
|
+
"[zfb] musl/Alpine is not supported yet — no @takazudo/zfb-linux-*-musl package exists.\n" +
|
|
43
|
+
" Use a glibc-based Linux image (e.g. Debian/Ubuntu) instead of Alpine.",
|
|
44
|
+
);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
30
48
|
let binPath;
|
|
31
49
|
try {
|
|
32
50
|
// Resolve the platform package's package.json to get the install directory.
|
|
@@ -57,26 +75,61 @@ if (!existsSync(binPath)) {
|
|
|
57
75
|
process.exit(1);
|
|
58
76
|
}
|
|
59
77
|
|
|
60
|
-
const
|
|
78
|
+
const child = spawn(binPath, process.argv.slice(2), { stdio: "inherit" });
|
|
61
79
|
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
80
|
+
// Forward termination signals to the child. Supervisors (concurrently
|
|
81
|
+
// --kill-others, Playwright webServer teardown, timeout(1), CI runners)
|
|
82
|
+
// signal only the wrapper; without forwarding, the native server survives
|
|
83
|
+
// with PPID 1 and keeps its port bound. Terminal Ctrl+C already signals the
|
|
84
|
+
// whole foreground group, so the child may receive SIGINT twice — the native
|
|
85
|
+
// binary treats repeated signals as the same shutdown request.
|
|
86
|
+
// SIGHUP is POSIX-only: on Windows child.kill("SIGHUP") throws (libuv only
|
|
87
|
+
// emulates SIGINT/SIGTERM/SIGKILL/SIGQUIT), and the OS tears down console
|
|
88
|
+
// processes on window close anyway.
|
|
89
|
+
const forwardedSignals =
|
|
90
|
+
process.platform === "win32" ? ["SIGINT", "SIGTERM"] : ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
91
|
+
const signalForwarders = new Map();
|
|
92
|
+
for (const sig of forwardedSignals) {
|
|
93
|
+
const forward = () => child.kill(sig);
|
|
94
|
+
signalForwarders.set(sig, forward);
|
|
95
|
+
process.on(sig, forward);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Surface spawn failures clearly. Without this, a 0644 binary (EACCES) is
|
|
99
|
+
// silently swallowed and the process exits with code 1 and no message —
|
|
100
|
+
// making it impossible for the user to diagnose a corrupted/incomplete npm
|
|
101
|
+
// install. (Issue #447 / #441 — pnpm publish strips the executable bit.)
|
|
102
|
+
child.on("error", (error) => {
|
|
103
|
+
if (error.code === "EACCES") {
|
|
69
104
|
process.stderr.write(
|
|
70
105
|
`[zfb] binary is not executable; was the install corrupt?\n` +
|
|
71
106
|
` ${binPath}\n` +
|
|
72
107
|
` Try reinstalling: npm install --include=optional\n`,
|
|
73
108
|
);
|
|
74
109
|
} else {
|
|
75
|
-
process.stderr.write(
|
|
76
|
-
`[zfb] failed to spawn binary: ${result.error.message}\n` + ` ${binPath}\n`,
|
|
77
|
-
);
|
|
110
|
+
process.stderr.write(`[zfb] failed to spawn binary: ${error.message}\n` + ` ${binPath}\n`);
|
|
78
111
|
}
|
|
79
112
|
process.exit(1);
|
|
80
|
-
}
|
|
113
|
+
});
|
|
81
114
|
|
|
82
|
-
|
|
115
|
+
child.on("exit", (code, signal) => {
|
|
116
|
+
if (signal) {
|
|
117
|
+
// Re-raise the child's termination signal on ourselves so the caller
|
|
118
|
+
// sees the real cause of death (e.g. WIFSIGNALED), not a plain exit code.
|
|
119
|
+
// Remove our forwarding listener first or it would swallow the re-raise.
|
|
120
|
+
const forward = signalForwarders.get(signal);
|
|
121
|
+
if (forward) {
|
|
122
|
+
process.off(signal, forward);
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
process.kill(process.pid, signal);
|
|
126
|
+
} catch {
|
|
127
|
+
// Signal cannot be re-raised on this platform (Windows emulation).
|
|
128
|
+
}
|
|
129
|
+
// Reached only if the re-raised signal did not terminate us — fall back
|
|
130
|
+
// to the shell's 128+n convention for death-by-signal.
|
|
131
|
+
const signum = osConstants.signals[signal];
|
|
132
|
+
process.exit(signum ? 128 + signum : 1);
|
|
133
|
+
}
|
|
134
|
+
process.exit(code ?? 1);
|
|
135
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSR helper for referencing a client-script asset URL in a page or layout.
|
|
3
|
+
*
|
|
4
|
+
* ## Usage
|
|
5
|
+
*
|
|
6
|
+
* ```tsx
|
|
7
|
+
* import { clientScript } from "@takazudo/zfb";
|
|
8
|
+
*
|
|
9
|
+
* export default function MyPage() {
|
|
10
|
+
* return (
|
|
11
|
+
* <html>
|
|
12
|
+
* <head>
|
|
13
|
+
* <script type="module" src={clientScript("search-widget")} />
|
|
14
|
+
* </head>
|
|
15
|
+
* <body>…</body>
|
|
16
|
+
* </html>
|
|
17
|
+
* );
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* ## How it works
|
|
22
|
+
*
|
|
23
|
+
* `clientScript(name)` returns the **stable URL** for the named client-script
|
|
24
|
+
* entry: `${base}/assets/client/<name>.js`. The production build pipeline
|
|
25
|
+
* (`ProductionAssetPipeline`) rewrites every occurrence of this stable URL in
|
|
26
|
+
* rendered HTML to the content-hashed equivalent
|
|
27
|
+
* (`/assets/client/<name>-<hash>.js`), so the hash never needs to be known at
|
|
28
|
+
* SSR time.
|
|
29
|
+
*
|
|
30
|
+
* The `base` prefix is read from `globalThis.__zfb?.base` (emitted by the
|
|
31
|
+
* bundler when at least one `*.client.*` file exists in the project). For
|
|
32
|
+
* root-mounted sites (`base` is absent or `"/"`), the prefix is the empty
|
|
33
|
+
* string and `clientScript("search-widget")` returns
|
|
34
|
+
* `"/assets/client/search-widget.js"`. For a sub-path deploy
|
|
35
|
+
* (`base="/foo/"`), it returns `"/foo/assets/client/search-widget.js"`.
|
|
36
|
+
*
|
|
37
|
+
* ## SSR-only note (v1)
|
|
38
|
+
*
|
|
39
|
+
* This is an SSR-time helper. Calling it in browser-executed code works but
|
|
40
|
+
* the base prefix (`globalThis.__zfb.base`) is currently not shipped to the
|
|
41
|
+
* browser — `clientScript()` will return the unprefixed stable URL
|
|
42
|
+
* (`/assets/client/<name>.js`) in that context. For the common use-case of
|
|
43
|
+
* rendering a `<script src>` tag at SSR time this is not a problem.
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* Returns the base-prefixed stable public URL for the named client-script entry.
|
|
47
|
+
*
|
|
48
|
+
* `name` is the entry name (file stem minus `.client`, e.g. `"search-widget"`
|
|
49
|
+
* for `search-widget.client.ts`).
|
|
50
|
+
*
|
|
51
|
+
* The returned URL is `${base}/assets/client/<name>.js`. The production
|
|
52
|
+
* pipeline rewrites it to the hashed URL; dev serves the stable URL directly.
|
|
53
|
+
*/
|
|
54
|
+
export declare function clientScript(name: string): string;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSR helper for referencing a client-script asset URL in a page or layout.
|
|
3
|
+
*
|
|
4
|
+
* ## Usage
|
|
5
|
+
*
|
|
6
|
+
* ```tsx
|
|
7
|
+
* import { clientScript } from "@takazudo/zfb";
|
|
8
|
+
*
|
|
9
|
+
* export default function MyPage() {
|
|
10
|
+
* return (
|
|
11
|
+
* <html>
|
|
12
|
+
* <head>
|
|
13
|
+
* <script type="module" src={clientScript("search-widget")} />
|
|
14
|
+
* </head>
|
|
15
|
+
* <body>…</body>
|
|
16
|
+
* </html>
|
|
17
|
+
* );
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* ## How it works
|
|
22
|
+
*
|
|
23
|
+
* `clientScript(name)` returns the **stable URL** for the named client-script
|
|
24
|
+
* entry: `${base}/assets/client/<name>.js`. The production build pipeline
|
|
25
|
+
* (`ProductionAssetPipeline`) rewrites every occurrence of this stable URL in
|
|
26
|
+
* rendered HTML to the content-hashed equivalent
|
|
27
|
+
* (`/assets/client/<name>-<hash>.js`), so the hash never needs to be known at
|
|
28
|
+
* SSR time.
|
|
29
|
+
*
|
|
30
|
+
* The `base` prefix is read from `globalThis.__zfb?.base` (emitted by the
|
|
31
|
+
* bundler when at least one `*.client.*` file exists in the project). For
|
|
32
|
+
* root-mounted sites (`base` is absent or `"/"`), the prefix is the empty
|
|
33
|
+
* string and `clientScript("search-widget")` returns
|
|
34
|
+
* `"/assets/client/search-widget.js"`. For a sub-path deploy
|
|
35
|
+
* (`base="/foo/"`), it returns `"/foo/assets/client/search-widget.js"`.
|
|
36
|
+
*
|
|
37
|
+
* ## SSR-only note (v1)
|
|
38
|
+
*
|
|
39
|
+
* This is an SSR-time helper. Calling it in browser-executed code works but
|
|
40
|
+
* the base prefix (`globalThis.__zfb.base`) is currently not shipped to the
|
|
41
|
+
* browser — `clientScript()` will return the unprefixed stable URL
|
|
42
|
+
* (`/assets/client/<name>.js`) in that context. For the common use-case of
|
|
43
|
+
* rendering a `<script src>` tag at SSR time this is not a problem.
|
|
44
|
+
*/
|
|
45
|
+
/** Stable public-URL prefix for client-script entries, matching the Rust constant. */
|
|
46
|
+
const CLIENT_SCRIPTS_URL_PREFIX = "/assets/client/";
|
|
47
|
+
/**
|
|
48
|
+
* Returns the base-prefixed stable public URL for the named client-script entry.
|
|
49
|
+
*
|
|
50
|
+
* `name` is the entry name (file stem minus `.client`, e.g. `"search-widget"`
|
|
51
|
+
* for `search-widget.client.ts`).
|
|
52
|
+
*
|
|
53
|
+
* The returned URL is `${base}/assets/client/<name>.js`. The production
|
|
54
|
+
* pipeline rewrites it to the hashed URL; dev serves the stable URL directly.
|
|
55
|
+
*/
|
|
56
|
+
export function clientScript(name) {
|
|
57
|
+
// `globalThis.__zfb?.base` is set to the resolved base prefix by the
|
|
58
|
+
// bundler when the project has at least one `*.client.*` entry (#978).
|
|
59
|
+
// For root-mounted or no-base builds the value is `""`, yielding
|
|
60
|
+
// `/assets/client/<name>.js` as expected. The `?? ""` fallback handles
|
|
61
|
+
// the edge case where __zfb.base was not emitted (zero-script project,
|
|
62
|
+
// or a browser context where the slot was never populated).
|
|
63
|
+
const base = globalThis.__zfb;
|
|
64
|
+
const prefix = base?.base ?? "";
|
|
65
|
+
return `${prefix}${CLIENT_SCRIPTS_URL_PREFIX}${name}.js`;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=client-script.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-script.js","sourceRoot":"","sources":["../src/client-script.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,sFAAsF;AACtF,MAAM,yBAAyB,GAAG,iBAAiB,CAAC;AAEpD;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,qEAAqE;IACrE,uEAAuE;IACvE,iEAAiE;IACjE,uEAAuE;IACvE,uEAAuE;IACvE,4DAA4D;IAC5D,MAAM,IAAI,GAAI,UAAsC,CAAC,KAAsC,CAAC;IAC5F,MAAM,MAAM,GAAG,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC;IAChC,OAAO,GAAG,MAAM,GAAG,yBAAyB,GAAG,IAAI,KAAK,CAAC;AAC3D,CAAC","sourcesContent":["/**\n * SSR helper for referencing a client-script asset URL in a page or layout.\n *\n * ## Usage\n *\n * ```tsx\n * import { clientScript } from \"@takazudo/zfb\";\n *\n * export default function MyPage() {\n * return (\n * <html>\n * <head>\n * <script type=\"module\" src={clientScript(\"search-widget\")} />\n * </head>\n * <body>…</body>\n * </html>\n * );\n * }\n * ```\n *\n * ## How it works\n *\n * `clientScript(name)` returns the **stable URL** for the named client-script\n * entry: `${base}/assets/client/<name>.js`. The production build pipeline\n * (`ProductionAssetPipeline`) rewrites every occurrence of this stable URL in\n * rendered HTML to the content-hashed equivalent\n * (`/assets/client/<name>-<hash>.js`), so the hash never needs to be known at\n * SSR time.\n *\n * The `base` prefix is read from `globalThis.__zfb?.base` (emitted by the\n * bundler when at least one `*.client.*` file exists in the project). For\n * root-mounted sites (`base` is absent or `\"/\"`), the prefix is the empty\n * string and `clientScript(\"search-widget\")` returns\n * `\"/assets/client/search-widget.js\"`. For a sub-path deploy\n * (`base=\"/foo/\"`), it returns `\"/foo/assets/client/search-widget.js\"`.\n *\n * ## SSR-only note (v1)\n *\n * This is an SSR-time helper. Calling it in browser-executed code works but\n * the base prefix (`globalThis.__zfb.base`) is currently not shipped to the\n * browser — `clientScript()` will return the unprefixed stable URL\n * (`/assets/client/<name>.js`) in that context. For the common use-case of\n * rendering a `<script src>` tag at SSR time this is not a problem.\n */\n\n/** Stable public-URL prefix for client-script entries, matching the Rust constant. */\nconst CLIENT_SCRIPTS_URL_PREFIX = \"/assets/client/\";\n\n/**\n * Returns the base-prefixed stable public URL for the named client-script entry.\n *\n * `name` is the entry name (file stem minus `.client`, e.g. `\"search-widget\"`\n * for `search-widget.client.ts`).\n *\n * The returned URL is `${base}/assets/client/<name>.js`. The production\n * pipeline rewrites it to the hashed URL; dev serves the stable URL directly.\n */\nexport function clientScript(name: string): string {\n // `globalThis.__zfb?.base` is set to the resolved base prefix by the\n // bundler when the project has at least one `*.client.*` entry (#978).\n // For root-mounted or no-base builds the value is `\"\"`, yielding\n // `/assets/client/<name>.js` as expected. The `?? \"\"` fallback handles\n // the edge case where __zfb.base was not emitted (zero-script project,\n // or a browser context where the slot was never populated).\n const base = (globalThis as Record<string, unknown>).__zfb as { base?: string } | undefined;\n const prefix = base?.base ?? \"\";\n return `${prefix}${CLIENT_SCRIPTS_URL_PREFIX}${name}.js`;\n}\n"]}
|