@pyreon/zero 0.24.4 → 0.24.6
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/package.json +10 -39
- package/src/actions.ts +0 -196
- package/src/adapters/bun.ts +0 -114
- package/src/adapters/cloudflare.ts +0 -166
- package/src/adapters/index.ts +0 -61
- package/src/adapters/netlify.ts +0 -154
- package/src/adapters/node.ts +0 -163
- package/src/adapters/static.ts +0 -42
- package/src/adapters/validate.ts +0 -23
- package/src/adapters/vercel.ts +0 -182
- package/src/adapters/warn-missing-env.ts +0 -49
- package/src/ai.ts +0 -623
- package/src/api-routes.ts +0 -219
- package/src/app.ts +0 -92
- package/src/cache.ts +0 -136
- package/src/client.ts +0 -143
- package/src/compression.ts +0 -116
- package/src/config.ts +0 -35
- package/src/cors.ts +0 -94
- package/src/csp.ts +0 -226
- package/src/entry-server.ts +0 -224
- package/src/env.ts +0 -344
- package/src/error-overlay.ts +0 -118
- package/src/favicon.ts +0 -841
- package/src/font.ts +0 -511
- package/src/fs-router.ts +0 -1519
- package/src/i18n-routing.ts +0 -533
- package/src/icon.tsx +0 -182
- package/src/icons-plugin.ts +0 -296
- package/src/image-plugin.ts +0 -751
- package/src/image-types.ts +0 -60
- package/src/image.tsx +0 -340
- package/src/index.ts +0 -92
- package/src/isr.ts +0 -394
- package/src/link.tsx +0 -304
- package/src/logger.ts +0 -144
- package/src/manifest.ts +0 -787
- package/src/meta.tsx +0 -354
- package/src/middleware.ts +0 -65
- package/src/not-found.ts +0 -44
- package/src/og-image.ts +0 -378
- package/src/rate-limit.ts +0 -140
- package/src/script.tsx +0 -260
- package/src/seo.ts +0 -617
- package/src/server.ts +0 -89
- package/src/sharp.d.ts +0 -22
- package/src/ssg-plugin.ts +0 -1582
- package/src/testing.ts +0 -146
- package/src/theme.tsx +0 -257
- package/src/types.ts +0 -624
- package/src/utils/use-intersection-observer.ts +0 -36
- package/src/utils/with-headers.ts +0 -13
- package/src/vercel-revalidate-handler.ts +0 -204
- package/src/vite-plugin.ts +0 -848
package/src/script.tsx
DELETED
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
import type { Ref, VNodeChild } from '@pyreon/core'
|
|
2
|
-
import { createRef, onMount, onUnmount } from '@pyreon/core'
|
|
3
|
-
import { signal } from '@pyreon/reactivity'
|
|
4
|
-
import { useIntersectionObserver } from './utils/use-intersection-observer'
|
|
5
|
-
|
|
6
|
-
// ─── Script optimization component ─────────────────────────────────────────
|
|
7
|
-
//
|
|
8
|
-
// <Script> provides optimized third-party script loading:
|
|
9
|
-
// - Defer loading until after hydration
|
|
10
|
-
// - Load on idle (requestIdleCallback)
|
|
11
|
-
// - Load on interaction (click, scroll, etc.)
|
|
12
|
-
// - Load on viewport entry
|
|
13
|
-
//
|
|
14
|
-
// Three levels of API (mirrors @pyreon/zero/link and @pyreon/zero/image):
|
|
15
|
-
//
|
|
16
|
-
// 1. useScript(props) — composable returning load-state signals + sentinel ref
|
|
17
|
-
// 2. createScript(Comp) — HOC wrapping any component with script load behavior
|
|
18
|
-
// 3. Script — default sentinel-or-null component (built on createScript)
|
|
19
|
-
|
|
20
|
-
export interface ScriptProps {
|
|
21
|
-
/** Script source URL. */
|
|
22
|
-
src: string
|
|
23
|
-
/** Loading strategy. Default: "afterHydration" */
|
|
24
|
-
strategy?: ScriptStrategy
|
|
25
|
-
/** Inline script content (alternative to src). */
|
|
26
|
-
children?: string
|
|
27
|
-
/** Script id for deduplication. */
|
|
28
|
-
id?: string
|
|
29
|
-
/** Async attribute. Default: true */
|
|
30
|
-
async?: boolean
|
|
31
|
-
/** onLoad callback — fires when the `<script>` finishes loading. */
|
|
32
|
-
onLoad?: () => void
|
|
33
|
-
/** onError callback — fires when the `<script>` fails to load. */
|
|
34
|
-
onError?: (error: Error) => void
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export type ScriptStrategy =
|
|
38
|
-
| 'beforeHydration'
|
|
39
|
-
| 'afterHydration'
|
|
40
|
-
| 'onIdle'
|
|
41
|
-
| 'onInteraction'
|
|
42
|
-
| 'onViewport'
|
|
43
|
-
|
|
44
|
-
/** Return type of {@link useScript}. */
|
|
45
|
-
export interface UseScriptReturn {
|
|
46
|
-
/** Ref — attach to the sentinel element for `onViewport` strategy. Undefined for other strategies. */
|
|
47
|
-
sentinelRef: Ref<HTMLElement> | undefined
|
|
48
|
-
/** Whether the script has finished loading (onLoad fired). */
|
|
49
|
-
loaded: () => boolean
|
|
50
|
-
/** Whether the script load failed (onError fired). */
|
|
51
|
-
errored: () => boolean
|
|
52
|
-
/** Whether the script is in the strategy state machine awaiting a trigger (idle/interaction/viewport). */
|
|
53
|
-
pending: () => boolean
|
|
54
|
-
/** Whether the consumer needs to render a sentinel element (only true for `onViewport`). */
|
|
55
|
-
needsSentinel: boolean
|
|
56
|
-
/** Imperatively trigger the script load. Already invoked automatically by the strategy. */
|
|
57
|
-
load: () => void
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** Props passed to a custom component via {@link createScript}. */
|
|
61
|
-
export interface ScriptRenderProps {
|
|
62
|
-
/** Ref — attach to whatever sentinel element you render (only matters for `onViewport`). */
|
|
63
|
-
sentinelRef: Ref<HTMLElement> | undefined
|
|
64
|
-
/** Whether the script is in viewport-wait mode (true → render a sentinel; false → render null). */
|
|
65
|
-
needsSentinel: boolean
|
|
66
|
-
/** Whether the script has finished loading (onLoad fired). */
|
|
67
|
-
loaded: () => boolean
|
|
68
|
-
/** Whether the script load failed (onError fired). */
|
|
69
|
-
errored: () => boolean
|
|
70
|
-
/** Whether the script is in the strategy state machine awaiting a trigger. */
|
|
71
|
-
pending: () => boolean
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Composable that provides all script loading behavior — strategy state
|
|
76
|
-
* machine (afterHydration / onIdle / onInteraction / onViewport),
|
|
77
|
-
* deduplication, load/error tracking.
|
|
78
|
-
*
|
|
79
|
-
* Returns reactive signals (`loaded`, `errored`, `pending`) so consumers
|
|
80
|
-
* can render loading indicators, retry buttons, or analytics-readiness
|
|
81
|
-
* gates without re-implementing the strategy machine.
|
|
82
|
-
*
|
|
83
|
-
* @example
|
|
84
|
-
* function MyScript(props: ScriptProps) {
|
|
85
|
-
* const s = useScript(props)
|
|
86
|
-
* return (
|
|
87
|
-
* <>
|
|
88
|
-
* {() => s.loaded() ? <Analytics /> : <Skeleton />}
|
|
89
|
-
* {() => s.needsSentinel && <div ref={s.sentinelRef} style="width:0;height:0" />}
|
|
90
|
-
* </>
|
|
91
|
-
* )
|
|
92
|
-
* }
|
|
93
|
-
*/
|
|
94
|
-
export function useScript(props: ScriptProps): UseScriptReturn {
|
|
95
|
-
const strategy = props.strategy ?? 'afterHydration'
|
|
96
|
-
const loaded = signal(false)
|
|
97
|
-
const errored = signal(false)
|
|
98
|
-
const pending = signal(strategy !== 'beforeHydration' && strategy !== 'afterHydration')
|
|
99
|
-
const sentinelRef = strategy === 'onViewport' ? createRef<HTMLElement>() : undefined
|
|
100
|
-
|
|
101
|
-
function loadScript() {
|
|
102
|
-
// Only invoked from `onMount` or strategy triggers — explicit guard
|
|
103
|
-
// documents the SSR-safety contract at the callsite (the rule can't
|
|
104
|
-
// AST-trace the indirect call).
|
|
105
|
-
if (typeof document === 'undefined') return
|
|
106
|
-
// Deduplication — short-circuit if a script with the same id exists.
|
|
107
|
-
if (props.id && document.getElementById(props.id)) {
|
|
108
|
-
loaded.set(true)
|
|
109
|
-
pending.set(false)
|
|
110
|
-
return
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const script = document.createElement('script')
|
|
114
|
-
if (props.src) script.src = props.src
|
|
115
|
-
if (props.id) script.id = props.id
|
|
116
|
-
script.async = props.async !== false
|
|
117
|
-
|
|
118
|
-
script.onload = () => {
|
|
119
|
-
loaded.set(true)
|
|
120
|
-
pending.set(false)
|
|
121
|
-
props.onLoad?.()
|
|
122
|
-
}
|
|
123
|
-
script.onerror = () => {
|
|
124
|
-
errored.set(true)
|
|
125
|
-
pending.set(false)
|
|
126
|
-
props.onError?.(new Error(`Failed to load: ${props.src}`))
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (props.children && !props.src) {
|
|
130
|
-
script.textContent = props.children
|
|
131
|
-
// Inline scripts have no async load event — mark loaded synchronously
|
|
132
|
-
// post-append so consumers can react. setTimeout 0 keeps the order
|
|
133
|
-
// (DOM append → script body executes → next microtask → signals update).
|
|
134
|
-
setTimeout(() => {
|
|
135
|
-
loaded.set(true)
|
|
136
|
-
pending.set(false)
|
|
137
|
-
}, 0)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
document.head.appendChild(script)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
onMount(() => {
|
|
144
|
-
switch (strategy) {
|
|
145
|
-
case 'beforeHydration':
|
|
146
|
-
// Already in HTML — do nothing.
|
|
147
|
-
loaded.set(true)
|
|
148
|
-
pending.set(false)
|
|
149
|
-
break
|
|
150
|
-
|
|
151
|
-
case 'afterHydration':
|
|
152
|
-
// Load immediately after mount (hydration is complete).
|
|
153
|
-
loadScript()
|
|
154
|
-
break
|
|
155
|
-
|
|
156
|
-
case 'onIdle':
|
|
157
|
-
if ('requestIdleCallback' in window) {
|
|
158
|
-
requestIdleCallback(() => loadScript(), { timeout: 5000 })
|
|
159
|
-
} else {
|
|
160
|
-
setTimeout(loadScript, 200)
|
|
161
|
-
}
|
|
162
|
-
break
|
|
163
|
-
|
|
164
|
-
case 'onInteraction': {
|
|
165
|
-
const events = ['click', 'scroll', 'keydown', 'touchstart']
|
|
166
|
-
function handler() {
|
|
167
|
-
for (const e of events) document.removeEventListener(e, handler)
|
|
168
|
-
loadScript()
|
|
169
|
-
}
|
|
170
|
-
for (const e of events) {
|
|
171
|
-
document.addEventListener(e, handler, { once: true, passive: true })
|
|
172
|
-
}
|
|
173
|
-
onUnmount(() => {
|
|
174
|
-
for (const e of events) document.removeEventListener(e, handler)
|
|
175
|
-
})
|
|
176
|
-
break
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
case 'onViewport':
|
|
180
|
-
// Handled below via useIntersectionObserver on the sentinel ref.
|
|
181
|
-
break
|
|
182
|
-
}
|
|
183
|
-
return undefined
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
if (strategy === 'onViewport') {
|
|
187
|
-
useIntersectionObserver(
|
|
188
|
-
() => sentinelRef!.current ?? undefined,
|
|
189
|
-
() => loadScript(),
|
|
190
|
-
)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
return {
|
|
194
|
-
sentinelRef,
|
|
195
|
-
loaded,
|
|
196
|
-
errored,
|
|
197
|
-
pending,
|
|
198
|
-
needsSentinel: strategy === 'onViewport',
|
|
199
|
-
load: loadScript,
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Higher-order component that wraps any component with script load behavior.
|
|
205
|
-
*
|
|
206
|
-
* The wrapped component receives {@link ScriptRenderProps} with the sentinel
|
|
207
|
-
* ref, load-state signals, and a `needsSentinel` flag. Use this when you want
|
|
208
|
-
* to render a loading indicator, retry button, or custom analytics-readiness
|
|
209
|
-
* gate around the script load.
|
|
210
|
-
*
|
|
211
|
-
* @example
|
|
212
|
-
* // Script with a loading indicator
|
|
213
|
-
* const TrackedScript = createScript((props) => (
|
|
214
|
-
* <>
|
|
215
|
-
* {() => props.pending() && <Spinner />}
|
|
216
|
-
* {() => props.errored() && <button onClick={() => location.reload()}>Retry</button>}
|
|
217
|
-
* {props.needsSentinel && <div ref={props.sentinelRef} style="width:0;height:0" />}
|
|
218
|
-
* </>
|
|
219
|
-
* ))
|
|
220
|
-
*
|
|
221
|
-
* <TrackedScript src="/analytics.js" strategy="onIdle" />
|
|
222
|
-
*/
|
|
223
|
-
export function createScript(
|
|
224
|
-
Component: (p: ScriptRenderProps) => any,
|
|
225
|
-
): (props: ScriptProps) => any {
|
|
226
|
-
return function WrappedScript(props: ScriptProps) {
|
|
227
|
-
const s = useScript(props)
|
|
228
|
-
return (
|
|
229
|
-
<Component
|
|
230
|
-
sentinelRef={s.sentinelRef}
|
|
231
|
-
needsSentinel={s.needsSentinel}
|
|
232
|
-
loaded={s.loaded}
|
|
233
|
-
errored={s.errored}
|
|
234
|
-
pending={s.pending}
|
|
235
|
-
/>
|
|
236
|
-
)
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Default optimized script component. Renders a 0×0 sentinel `<div>` for the
|
|
242
|
-
* `onViewport` strategy (so IntersectionObserver has an element to observe),
|
|
243
|
-
* `null` for every other strategy.
|
|
244
|
-
*
|
|
245
|
-
* @example
|
|
246
|
-
* // Load analytics after page is interactive
|
|
247
|
-
* <Script src="https://analytics.example.com/script.js" strategy="onIdle" />
|
|
248
|
-
*
|
|
249
|
-
* // Load chat widget when user scrolls
|
|
250
|
-
* <Script src="/chat-widget.js" strategy="onViewport" />
|
|
251
|
-
*
|
|
252
|
-
* // Inline script with deferred execution
|
|
253
|
-
* <Script strategy="afterHydration">
|
|
254
|
-
* {`console.log("App hydrated!")`}
|
|
255
|
-
* </Script>
|
|
256
|
-
*/
|
|
257
|
-
export const Script: (props: ScriptProps) => VNodeChild = createScript((props) => {
|
|
258
|
-
if (!props.needsSentinel) return null
|
|
259
|
-
return <div ref={props.sentinelRef} style="width:0;height:0;overflow:hidden" />
|
|
260
|
-
})
|