@pyreon/zero 0.4.1 → 0.11.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.
Files changed (110) hide show
  1. package/lib/cache.js.map +1 -1
  2. package/lib/client.js.map +1 -1
  3. package/lib/config.js.map +1 -1
  4. package/lib/font.js.map +1 -1
  5. package/lib/fs-router-BkbIWqek.js.map +1 -1
  6. package/lib/fs-router-n4VA4lxu.js.map +1 -1
  7. package/lib/image-plugin.js.map +1 -1
  8. package/lib/image.js +5 -5
  9. package/lib/image.js.map +1 -1
  10. package/lib/index.js +14 -14
  11. package/lib/index.js.map +1 -1
  12. package/lib/link.js +9 -9
  13. package/lib/link.js.map +1 -1
  14. package/lib/script.js +1 -1
  15. package/lib/script.js.map +1 -1
  16. package/lib/seo.js.map +1 -1
  17. package/lib/theme.js +2 -2
  18. package/lib/theme.js.map +1 -1
  19. package/package.json +14 -13
  20. package/src/actions.ts +20 -28
  21. package/src/adapters/bun.ts +7 -7
  22. package/src/adapters/index.ts +12 -14
  23. package/src/adapters/node.ts +8 -11
  24. package/src/adapters/static.ts +3 -3
  25. package/src/api-routes.ts +23 -50
  26. package/src/app.ts +9 -13
  27. package/src/cache.ts +16 -29
  28. package/src/client.ts +8 -8
  29. package/src/compression.ts +21 -28
  30. package/src/config.ts +6 -7
  31. package/src/cors.ts +20 -28
  32. package/src/entry-server.ts +15 -19
  33. package/src/error-overlay.ts +10 -13
  34. package/src/font.ts +44 -55
  35. package/src/fs-router.ts +44 -63
  36. package/src/image-plugin.ts +53 -79
  37. package/src/image.tsx +41 -43
  38. package/src/index.ts +36 -36
  39. package/src/isr.ts +8 -8
  40. package/src/link.tsx +35 -38
  41. package/src/rate-limit.ts +15 -15
  42. package/src/script.tsx +21 -22
  43. package/src/seo.ts +47 -57
  44. package/src/sharp.d.ts +2 -6
  45. package/src/testing.ts +8 -12
  46. package/src/theme.tsx +19 -21
  47. package/src/types.ts +6 -6
  48. package/src/utils/use-intersection-observer.ts +2 -2
  49. package/src/utils/with-headers.ts +1 -4
  50. package/src/vite-plugin.ts +21 -28
  51. package/lib/types/actions.d.ts +0 -57
  52. package/lib/types/actions.d.ts.map +0 -1
  53. package/lib/types/adapters/bun.d.ts +0 -6
  54. package/lib/types/adapters/bun.d.ts.map +0 -1
  55. package/lib/types/adapters/index.d.ts +0 -10
  56. package/lib/types/adapters/index.d.ts.map +0 -1
  57. package/lib/types/adapters/node.d.ts +0 -6
  58. package/lib/types/adapters/node.d.ts.map +0 -1
  59. package/lib/types/adapters/static.d.ts +0 -7
  60. package/lib/types/adapters/static.d.ts.map +0 -1
  61. package/lib/types/api-routes.d.ts +0 -66
  62. package/lib/types/api-routes.d.ts.map +0 -1
  63. package/lib/types/app.d.ts +0 -24
  64. package/lib/types/app.d.ts.map +0 -1
  65. package/lib/types/cache.d.ts +0 -54
  66. package/lib/types/cache.d.ts.map +0 -1
  67. package/lib/types/client.d.ts +0 -19
  68. package/lib/types/client.d.ts.map +0 -1
  69. package/lib/types/compression.d.ts +0 -33
  70. package/lib/types/compression.d.ts.map +0 -1
  71. package/lib/types/config.d.ts +0 -18
  72. package/lib/types/config.d.ts.map +0 -1
  73. package/lib/types/cors.d.ts +0 -32
  74. package/lib/types/cors.d.ts.map +0 -1
  75. package/lib/types/entry-server.d.ts +0 -34
  76. package/lib/types/entry-server.d.ts.map +0 -1
  77. package/lib/types/error-overlay.d.ts +0 -6
  78. package/lib/types/error-overlay.d.ts.map +0 -1
  79. package/lib/types/font.d.ts +0 -119
  80. package/lib/types/font.d.ts.map +0 -1
  81. package/lib/types/fs-router.d.ts +0 -38
  82. package/lib/types/fs-router.d.ts.map +0 -1
  83. package/lib/types/image-plugin.d.ts +0 -79
  84. package/lib/types/image-plugin.d.ts.map +0 -1
  85. package/lib/types/image.d.ts +0 -51
  86. package/lib/types/image.d.ts.map +0 -1
  87. package/lib/types/index.d.ts +0 -37
  88. package/lib/types/index.d.ts.map +0 -1
  89. package/lib/types/isr.d.ts +0 -9
  90. package/lib/types/isr.d.ts.map +0 -1
  91. package/lib/types/link.d.ts +0 -116
  92. package/lib/types/link.d.ts.map +0 -1
  93. package/lib/types/rate-limit.d.ts +0 -34
  94. package/lib/types/rate-limit.d.ts.map +0 -1
  95. package/lib/types/script.d.ts +0 -35
  96. package/lib/types/script.d.ts.map +0 -1
  97. package/lib/types/seo.d.ts +0 -88
  98. package/lib/types/seo.d.ts.map +0 -1
  99. package/lib/types/testing.d.ts +0 -85
  100. package/lib/types/testing.d.ts.map +0 -1
  101. package/lib/types/theme.d.ts +0 -39
  102. package/lib/types/theme.d.ts.map +0 -1
  103. package/lib/types/types.d.ts +0 -109
  104. package/lib/types/types.d.ts.map +0 -1
  105. package/lib/types/utils/use-intersection-observer.d.ts +0 -10
  106. package/lib/types/utils/use-intersection-observer.d.ts.map +0 -1
  107. package/lib/types/utils/with-headers.d.ts +0 -6
  108. package/lib/types/utils/with-headers.d.ts.map +0 -1
  109. package/lib/types/vite-plugin.d.ts +0 -17
  110. package/lib/types/vite-plugin.d.ts.map +0 -1
@@ -1,7 +1,7 @@
1
- import { existsSync } from 'node:fs'
2
- import { mkdir, readFile, writeFile } from 'node:fs/promises'
3
- import { basename, extname, join } from 'node:path'
4
- import type { Plugin } from 'vite'
1
+ import { existsSync } from "node:fs"
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises"
3
+ import { basename, extname, join } from "node:path"
4
+ import type { Plugin } from "vite"
5
5
 
6
6
  let sharpWarned = false
7
7
  function warnSharpMissing() {
@@ -9,7 +9,7 @@ function warnSharpMissing() {
9
9
  sharpWarned = true
10
10
  // biome-ignore lint/suspicious/noConsole: intentional build-time warning
11
11
  console.warn(
12
- '\n[zero:image] sharp not installed — images will not be optimized. Install for full support: bun add -D sharp\n',
12
+ "\n[zero:image] sharp not installed — images will not be optimized. Install for full support: bun add -D sharp\n",
13
13
  )
14
14
  }
15
15
 
@@ -44,7 +44,7 @@ export interface ImagePluginConfig {
44
44
  include?: RegExp
45
45
  }
46
46
 
47
- export type ImageFormat = 'webp' | 'avif' | 'jpeg' | 'png'
47
+ export type ImageFormat = "webp" | "avif" | "jpeg" | "png"
48
48
 
49
49
  /** Per-format source set for <picture> <source> elements. */
50
50
  export interface FormatSource {
@@ -99,42 +99,39 @@ const IMAGE_EXT_RE = /\.(jpe?g|png|webp|avif)$/i
99
99
  */
100
100
  export function imagePlugin(config: ImagePluginConfig = {}): Plugin {
101
101
  const defaultWidths = config.widths ?? [640, 1024, 1920]
102
- const defaultFormats = config.formats ?? ['webp']
102
+ const defaultFormats = config.formats ?? ["webp"]
103
103
  const quality = config.quality ?? 80
104
104
  const placeholderSize = config.placeholderSize ?? 16
105
- const outSubDir = config.outDir ?? 'assets/img'
105
+ const outSubDir = config.outDir ?? "assets/img"
106
106
  const include = config.include ?? IMAGE_EXT_RE
107
107
 
108
- let root = ''
109
- let outDir = ''
108
+ let root = ""
109
+ let outDir = ""
110
110
  let isBuild = false
111
111
 
112
112
  return {
113
- name: 'pyreon-zero-images',
114
- enforce: 'pre',
113
+ name: "pyreon-zero-images",
114
+ enforce: "pre",
115
115
 
116
116
  configResolved(resolvedConfig) {
117
117
  root = resolvedConfig.root
118
118
  outDir = resolvedConfig.build.outDir
119
- isBuild = resolvedConfig.command === 'build'
119
+ isBuild = resolvedConfig.command === "build"
120
120
  },
121
121
 
122
122
  async resolveId(id) {
123
123
  // Handle ?optimize query on image imports
124
- if (id.includes('?optimize') && include.test(id.split('?')[0]!)) {
124
+ if (id.includes("?optimize") && include.test(id.split("?")[0]!)) {
125
125
  return `\0virtual:zero-image:${id}`
126
126
  }
127
127
  return null
128
128
  },
129
129
 
130
130
  async load(id) {
131
- if (!id.startsWith('\0virtual:zero-image:')) return null
131
+ if (!id.startsWith("\0virtual:zero-image:")) return null
132
132
 
133
- const rawPath =
134
- id.replace('\0virtual:zero-image:', '').split('?')[0] ?? id
135
- const absPath = rawPath.startsWith('/')
136
- ? join(root, 'public', rawPath)
137
- : rawPath
133
+ const rawPath = id.replace("\0virtual:zero-image:", "").split("?")[0] ?? id
134
+ const absPath = rawPath.startsWith("/") ? join(root, "public", rawPath) : rawPath
138
135
 
139
136
  if (!isBuild) {
140
137
  const result = await loadDevImage(absPath, rawPath, placeholderSize)
@@ -164,16 +161,16 @@ async function loadDevImage(
164
161
  placeholderSize: number,
165
162
  ): Promise<ProcessedImage> {
166
163
  const metadata = await getImageMetadata(absPath)
167
- const publicPath = rawPath.startsWith('/') ? rawPath : `/@fs/${absPath}`
164
+ const publicPath = rawPath.startsWith("/") ? rawPath : `/@fs/${absPath}`
168
165
 
169
166
  return {
170
167
  src: publicPath,
171
- srcset: '',
168
+ srcset: "",
172
169
  width: metadata.width,
173
170
  height: metadata.height,
174
171
  placeholder: await generateBlurPlaceholder(absPath, placeholderSize),
175
172
  formats: [],
176
- sources: [{ src: publicPath, width: metadata.width, format: 'original' }],
173
+ sources: [{ src: publicPath, width: metadata.width, format: "original" }],
177
174
  }
178
175
  }
179
176
 
@@ -181,17 +178,13 @@ async function emitProcessedSources(
181
178
  processed: ProcessedImage,
182
179
  outSubDir: string,
183
180
  ctx: {
184
- emitFile: (f: {
185
- type: 'asset'
186
- fileName: string
187
- source: Uint8Array
188
- }) => void
181
+ emitFile: (f: { type: "asset"; fileName: string; source: Uint8Array }) => void
189
182
  },
190
183
  ) {
191
184
  for (const source of processed.sources) {
192
185
  const fileName = join(outSubDir, basename(source.src))
193
186
  const content = await readFile(source.src)
194
- ctx.emitFile({ type: 'asset', fileName, source: content })
187
+ ctx.emitFile({ type: "asset", fileName, source: content })
195
188
  source.src = `/${fileName}`
196
189
  }
197
190
  }
@@ -208,11 +201,11 @@ function rebuildFormatSrcsets(processed: ProcessedImage, fallbackPath: string) {
208
201
  }
209
202
  processed.formats = [...formatGroups.entries()].map(([fmt, entries]) => ({
210
203
  type: `image/${fmt}`,
211
- srcset: entries.join(', '),
204
+ srcset: entries.join(", "),
212
205
  }))
213
206
 
214
207
  const lastFormat = processed.formats.at(-1)
215
- processed.srcset = lastFormat?.srcset ?? ''
208
+ processed.srcset = lastFormat?.srcset ?? ""
216
209
  processed.src = processed.sources.at(-1)?.src ?? fallbackPath
217
210
  }
218
211
 
@@ -227,10 +220,7 @@ interface ProcessOptions {
227
220
  outDir: string
228
221
  }
229
222
 
230
- async function processImage(
231
- absPath: string,
232
- opts: ProcessOptions,
233
- ): Promise<ProcessedImage> {
223
+ async function processImage(absPath: string, opts: ProcessOptions): Promise<ProcessedImage> {
234
224
  const metadata = await getImageMetadata(absPath)
235
225
  const ext = extname(absPath)
236
226
  const name = basename(absPath, ext)
@@ -266,26 +256,21 @@ async function processImage(
266
256
  group.push({ src: s.src, width: s.width })
267
257
  }
268
258
 
269
- const formats: FormatSource[] = [...formatGroups.entries()].map(
270
- ([fmt, group]) => ({
271
- type: `image/${fmt === 'jpeg' ? 'jpeg' : fmt}`,
272
- srcset: group.map((s) => `${s.src} ${s.width}w`).join(', '),
273
- }),
274
- )
259
+ const formats: FormatSource[] = [...formatGroups.entries()].map(([fmt, group]) => ({
260
+ type: `image/${fmt === "jpeg" ? "jpeg" : fmt}`,
261
+ srcset: group.map((s) => `${s.src} ${s.width}w`).join(", "),
262
+ }))
275
263
 
276
264
  // Fallback: last format's srcset
277
265
  const fallbackFormat = formats[formats.length - 1]
278
266
  const fallbackSources = formatGroups.get([...formatGroups.keys()].pop()!)!
279
267
 
280
268
  // Generate blur placeholder
281
- const placeholder = await generateBlurPlaceholder(
282
- absPath,
283
- opts.placeholderSize,
284
- )
269
+ const placeholder = await generateBlurPlaceholder(absPath, opts.placeholderSize)
285
270
 
286
271
  return {
287
272
  src: fallbackSources[fallbackSources.length - 1]?.src ?? absPath,
288
- srcset: fallbackFormat?.srcset ?? '',
273
+ srcset: fallbackFormat?.srcset ?? "",
289
274
  width: metadata.width,
290
275
  height: metadata.height,
291
276
  placeholder,
@@ -308,23 +293,23 @@ async function getImageMetadata(absPath: string): Promise<ImageMetadata> {
308
293
  const buffer = await readFile(absPath)
309
294
  const ext = extname(absPath).toLowerCase()
310
295
 
311
- if (ext === '.png') {
296
+ if (ext === ".png") {
312
297
  // PNG: width at bytes 16-19, height at 20-23 (big-endian)
313
298
  const width = buffer.readUInt32BE(16)
314
299
  const height = buffer.readUInt32BE(20)
315
- return { width, height, format: 'png' }
300
+ return { width, height, format: "png" }
316
301
  }
317
302
 
318
- if (ext === '.jpg' || ext === '.jpeg') {
303
+ if (ext === ".jpg" || ext === ".jpeg") {
319
304
  // JPEG: scan for SOF markers
320
305
  const dimensions = parseJpegDimensions(buffer)
321
- return { ...dimensions, format: 'jpeg' }
306
+ return { ...dimensions, format: "jpeg" }
322
307
  }
323
308
 
324
- if (ext === '.webp') {
309
+ if (ext === ".webp") {
325
310
  // WebP: VP8 header
326
311
  const dimensions = parseWebPDimensions(buffer)
327
- return { ...dimensions, format: 'webp' }
312
+ return { ...dimensions, format: "webp" }
328
313
  }
329
314
 
330
315
  // Fallback
@@ -341,13 +326,7 @@ export function parseJpegDimensions(buffer: Buffer): {
341
326
  if (buffer[offset] !== 0xff) break
342
327
  const marker = buffer[offset + 1]!
343
328
  // SOF markers (0xC0-0xCF except 0xC4, 0xC8, 0xCC)
344
- if (
345
- marker >= 0xc0 &&
346
- marker <= 0xcf &&
347
- marker !== 0xc4 &&
348
- marker !== 0xc8 &&
349
- marker !== 0xcc
350
- ) {
329
+ if (marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc) {
351
330
  const height = buffer.readUInt16BE(offset + 5)
352
331
  const width = buffer.readUInt16BE(offset + 7)
353
332
  return { width, height }
@@ -364,26 +343,24 @@ export function parseWebPDimensions(buffer: Buffer): {
364
343
  height: number
365
344
  } {
366
345
  // RIFF header: bytes 0-3 = "RIFF", 8-11 = "WEBP"
367
- const chunk = buffer.toString('ascii', 12, 16)
368
- if (chunk === 'VP8 ') {
346
+ const chunk = buffer.toString("ascii", 12, 16)
347
+ if (chunk === "VP8 ") {
369
348
  // Lossy VP8
370
349
  const width = buffer.readUInt16LE(26) & 0x3fff
371
350
  const height = buffer.readUInt16LE(28) & 0x3fff
372
351
  return { width, height }
373
352
  }
374
- if (chunk === 'VP8L') {
353
+ if (chunk === "VP8L") {
375
354
  // Lossless VP8L
376
355
  const bits = buffer.readUInt32LE(21)
377
356
  const width = (bits & 0x3fff) + 1
378
357
  const height = ((bits >> 14) & 0x3fff) + 1
379
358
  return { width, height }
380
359
  }
381
- if (chunk === 'VP8X') {
360
+ if (chunk === "VP8X") {
382
361
  // Extended VP8X
383
- const width =
384
- 1 + ((buffer[24]! | (buffer[25]! << 8) | (buffer[26]! << 16)) & 0xffffff)
385
- const height =
386
- 1 + ((buffer[27]! | (buffer[28]! << 8) | (buffer[29]! << 16)) & 0xffffff)
362
+ const width = 1 + ((buffer[24]! | (buffer[25]! << 8) | (buffer[26]! << 16)) & 0xffffff)
363
+ const height = 1 + ((buffer[27]! | (buffer[28]! << 8) | (buffer[29]! << 16)) & 0xffffff)
387
364
  return { width, height }
388
365
  }
389
366
  return { width: 0, height: 0 }
@@ -402,20 +379,20 @@ async function resizeImage(
402
379
  ): Promise<void> {
403
380
  try {
404
381
  // Try sharp (the standard Node.js image processing library)
405
- const sharp = await import('sharp').then((m) => m.default ?? m)
382
+ const sharp = await import("sharp").then((m) => m.default ?? m)
406
383
  let pipeline = sharp(input).resize(width)
407
384
 
408
385
  switch (format) {
409
- case 'webp':
386
+ case "webp":
410
387
  pipeline = pipeline.webp({ quality })
411
388
  break
412
- case 'avif':
389
+ case "avif":
413
390
  pipeline = pipeline.avif({ quality })
414
391
  break
415
- case 'jpeg':
392
+ case "jpeg":
416
393
  pipeline = pipeline.jpeg({ quality, mozjpeg: true })
417
394
  break
418
- case 'png':
395
+ case "png":
419
396
  pipeline = pipeline.png({ compressionLevel: 9 })
420
397
  break
421
398
  }
@@ -432,19 +409,16 @@ async function resizeImage(
432
409
  /**
433
410
  * Generate a tiny blur placeholder as a base64 data URI.
434
411
  */
435
- async function generateBlurPlaceholder(
436
- input: string,
437
- size: number,
438
- ): Promise<string> {
412
+ async function generateBlurPlaceholder(input: string, size: number): Promise<string> {
439
413
  try {
440
- const sharp = await import('sharp').then((m) => m.default ?? m)
414
+ const sharp = await import("sharp").then((m) => m.default ?? m)
441
415
  const buffer = await sharp(input)
442
- .resize(size, size, { fit: 'inside' })
416
+ .resize(size, size, { fit: "inside" })
443
417
  .blur(2)
444
418
  .webp({ quality: 20 })
445
419
  .toBuffer()
446
420
 
447
- return `data:image/webp;base64,${buffer.toString('base64')}`
421
+ return `data:image/webp;base64,${buffer.toString("base64")}`
448
422
  } catch {
449
423
  // sharp not available — return a transparent placeholder
450
424
  return "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1' height='1'%3E%3C/svg%3E"
package/src/image.tsx CHANGED
@@ -1,8 +1,8 @@
1
- import type { VNodeChild } from '@pyreon/core'
2
- import { createRef } from '@pyreon/core'
3
- import { signal } from '@pyreon/reactivity'
4
- import type { FormatSource } from './image-plugin'
5
- import { useIntersectionObserver } from './utils/use-intersection-observer'
1
+ import type { VNodeChild } from "@pyreon/core"
2
+ import { createRef } from "@pyreon/core"
3
+ import { signal } from "@pyreon/reactivity"
4
+ import type { FormatSource } from "./image-plugin"
5
+ import { useIntersectionObserver } from "./utils/use-intersection-observer"
6
6
 
7
7
  // ─── Image optimization component ───────────────────────────────────────────
8
8
  //
@@ -30,8 +30,8 @@ export interface ImageProps {
30
30
  /** Per-format source sets for <picture>. Provided automatically by imagePlugin. */
31
31
  formats?: FormatSource[]
32
32
  /** Loading strategy. "lazy" uses IntersectionObserver, "eager" loads immediately. Default: "lazy" */
33
- loading?: 'lazy' | 'eager'
34
- /** Mark as priority (LCP image). Disables lazy loading, adds fetchpriority="high". */
33
+ loading?: "lazy" | "eager"
34
+ /** Mark as priority (LCP image). Disables lazy loading, adds fetchPriority="high". */
35
35
  priority?: boolean
36
36
  /** Low-quality placeholder image URL or base64 data URI for blur-up effect. */
37
37
  placeholder?: string
@@ -40,9 +40,9 @@ export interface ImageProps {
40
40
  /** Inline styles. */
41
41
  style?: string
42
42
  /** CSS object-fit. Default: "cover" */
43
- fit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'
43
+ fit?: "cover" | "contain" | "fill" | "none" | "scale-down"
44
44
  /** Decode async. Default: true */
45
- decoding?: 'sync' | 'async' | 'auto'
45
+ decoding?: "sync" | "async" | "auto"
46
46
  }
47
47
 
48
48
  export interface ImageSource {
@@ -64,19 +64,19 @@ export interface ImageSource {
64
64
  * <Image src="/hero.jpg" alt="Hero" width={1200} height={630} />
65
65
  */
66
66
  export function Image(props: ImageProps): VNodeChild {
67
- const isEager = props.priority || props.loading === 'eager'
67
+ const isEager = props.priority || props.loading === "eager"
68
68
  const loaded = signal(isEager)
69
69
  const inView = signal(isEager)
70
70
  const containerRef = createRef<HTMLElement>()
71
71
 
72
72
  // Resolve srcset from string or array
73
73
  const resolvedSrcset =
74
- typeof props.srcset === 'string'
74
+ typeof props.srcset === "string"
75
75
  ? props.srcset
76
- : props.srcset?.map((s) => `${s.src} ${s.width}w`).join(', ')
76
+ : props.srcset?.map((s) => `${s.src} ${s.width}w`).join(", ")
77
77
 
78
- const sizes = props.sizes ?? '100vw'
79
- const fit = props.fit ?? 'cover'
78
+ const sizes = props.sizes ?? "100vw"
79
+ const fit = props.fit ?? "cover"
80
80
  const hasFormats = props.formats && props.formats.length > 0
81
81
  const aspectRatio = `${props.width} / ${props.height}`
82
82
 
@@ -89,39 +89,37 @@ export function Image(props: ImageProps): VNodeChild {
89
89
 
90
90
  // Static styles (don't depend on signals)
91
91
  const containerStyle = [
92
- 'position: relative',
93
- 'overflow: hidden',
92
+ "position: relative",
93
+ "overflow: hidden",
94
94
  `aspect-ratio: ${aspectRatio}`,
95
95
  `max-width: ${props.width}px`,
96
- 'width: 100%',
96
+ "width: 100%",
97
97
  props.style,
98
98
  ]
99
99
  .filter(Boolean)
100
- .join('; ')
100
+ .join("; ")
101
101
 
102
102
  const imgEl = (
103
103
  <img
104
- src={() => (inView() ? props.src : '')}
105
- srcset={() =>
106
- !hasFormats && inView() && resolvedSrcset ? resolvedSrcset : ''
107
- }
104
+ src={() => (inView() ? props.src : "")}
105
+ srcSet={() => (!hasFormats && inView() && resolvedSrcset ? resolvedSrcset : "")}
108
106
  sizes={resolvedSrcset ? sizes : undefined}
109
107
  alt={props.alt}
110
108
  width={props.width}
111
109
  height={props.height}
112
- loading={isEager ? 'eager' : 'lazy'}
113
- decoding={props.decoding ?? 'async'}
114
- fetchpriority={props.priority ? 'high' : undefined}
115
- onload={() => loaded.set(true)}
110
+ loading={isEager ? "eager" : "lazy"}
111
+ decoding={props.decoding ?? "async"}
112
+ fetchPriority={props.priority ? "high" : undefined}
113
+ onLoad={() => loaded.set(true)}
116
114
  style={() =>
117
115
  [
118
- 'display: block',
119
- 'width: 100%',
120
- 'height: 100%',
116
+ "display: block",
117
+ "width: 100%",
118
+ "height: 100%",
121
119
  `object-fit: ${fit}`,
122
- 'transition: opacity 0.3s ease',
123
- props.placeholder && !loaded() ? 'opacity: 0' : 'opacity: 1',
124
- ].join('; ')
120
+ "transition: opacity 0.3s ease",
121
+ props.placeholder && !loaded() ? "opacity: 0" : "opacity: 1",
122
+ ].join("; ")
125
123
  }
126
124
  />
127
125
  )
@@ -136,16 +134,16 @@ export function Image(props: ImageProps): VNodeChild {
136
134
  loading="eager"
137
135
  style={() =>
138
136
  [
139
- 'position: absolute',
140
- 'inset: 0',
141
- 'width: 100%',
142
- 'height: 100%',
143
- 'object-fit: cover',
144
- 'filter: blur(20px)',
145
- 'transform: scale(1.1)',
146
- 'transition: opacity 0.4s ease',
147
- loaded() ? 'opacity: 0; pointer-events: none' : 'opacity: 1',
148
- ].join('; ')
137
+ "position: absolute",
138
+ "inset: 0",
139
+ "width: 100%",
140
+ "height: 100%",
141
+ "object-fit: cover",
142
+ "filter: blur(20px)",
143
+ "transform: scale(1.1)",
144
+ "transition: opacity 0.4s ease",
145
+ loaded() ? "opacity: 0; pointer-events: none" : "opacity: 1",
146
+ ].join("; ")
149
147
  }
150
148
  />
151
149
  )}
@@ -154,7 +152,7 @@ export function Image(props: ImageProps): VNodeChild {
154
152
  {props.formats?.map((fmt) => (
155
153
  <source
156
154
  type={fmt.type}
157
- srcset={() => (inView() ? fmt.srcset : undefined)}
155
+ srcSet={() => (inView() ? (fmt.srcset ?? "") : "")}
158
156
  sizes={sizes}
159
157
  />
160
158
  ))}
package/src/index.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  // ─── Core ─────────────────────────────────────────────────────────────────────
2
2
 
3
- export type { CreateAppOptions } from './app'
4
- export { createApp } from './app'
5
- export type { CreateServerOptions } from './entry-server'
6
- export { createServer } from './entry-server'
3
+ export type { CreateAppOptions } from "./app"
4
+ export { createApp } from "./app"
5
+ export type { CreateServerOptions } from "./entry-server"
6
+ export { createServer } from "./entry-server"
7
7
 
8
8
  // ─── Vite plugin ─────────────────────────────────────────────────────────────
9
9
 
10
- export { zeroPlugin as default } from './vite-plugin'
10
+ export { zeroPlugin as default } from "./vite-plugin"
11
11
 
12
12
  // ─── File-system routing ─────────────────────────────────────────────────────
13
13
 
@@ -17,15 +17,15 @@ export {
17
17
  generateRouteModule,
18
18
  parseFileRoutes,
19
19
  scanRouteFiles,
20
- } from './fs-router'
20
+ } from "./fs-router"
21
21
 
22
22
  // ─── Config ──────────────────────────────────────────────────────────────────
23
23
 
24
- export { defineConfig, resolveConfig } from './config'
24
+ export { defineConfig, resolveConfig } from "./config"
25
25
 
26
26
  // ─── ISR ─────────────────────────────────────────────────────────────────────
27
27
 
28
- export { createISRHandler } from './isr'
28
+ export { createISRHandler } from "./isr"
29
29
 
30
30
  // ─── Adapters ────────────────────────────────────────────────────────────────
31
31
 
@@ -34,21 +34,21 @@ export {
34
34
  nodeAdapter,
35
35
  resolveAdapter,
36
36
  staticAdapter,
37
- } from './adapters'
37
+ } from "./adapters"
38
38
 
39
39
  // ─── Components ─────────────────────────────────────────────────────────────
40
40
 
41
- export type { ImageProps, ImageSource } from './image'
42
- export { Image } from './image'
43
- export type { LinkProps, LinkRenderProps, UseLinkReturn } from './link'
44
- export { createLink, Link, useLink } from './link'
45
- export type { ScriptProps, ScriptStrategy } from './script'
46
- export { Script } from './script'
41
+ export type { ImageProps, ImageSource } from "./image"
42
+ export { Image } from "./image"
43
+ export type { LinkProps, LinkRenderProps, UseLinkReturn } from "./link"
44
+ export { createLink, Link, useLink } from "./link"
45
+ export type { ScriptProps, ScriptStrategy } from "./script"
46
+ export { Script } from "./script"
47
47
 
48
48
  // ─── Middleware ──────────────────────────────────────────────────────────────
49
49
 
50
- export type { CacheConfig, CacheRule } from './cache'
51
- export { cacheMiddleware, securityHeaders, varyEncoding } from './cache'
50
+ export type { CacheConfig, CacheRule } from "./cache"
51
+ export { cacheMiddleware, securityHeaders, varyEncoding } from "./cache"
52
52
 
53
53
  // ─── Font optimization ─────────────────────────────────────────────────────
54
54
 
@@ -60,8 +60,8 @@ export type {
60
60
  GoogleFontStatic,
61
61
  GoogleFontVariable,
62
62
  LocalFont,
63
- } from './font'
64
- export { fontPlugin, fontVariables } from './font'
63
+ } from "./font"
64
+ export { fontPlugin, fontVariables } from "./font"
65
65
 
66
66
  // ─── Image processing ──────────────────────────────────────────────────────
67
67
 
@@ -70,12 +70,12 @@ export type {
70
70
  ImageFormat,
71
71
  ImagePluginConfig,
72
72
  ProcessedImage,
73
- } from './image-plugin'
74
- export { imagePlugin } from './image-plugin'
73
+ } from "./image-plugin"
74
+ export { imagePlugin } from "./image-plugin"
75
75
 
76
76
  // ─── Theme ──────────────────────────────────────────────────────────────────
77
77
 
78
- export type { Theme } from './theme'
78
+ export type { Theme } from "./theme"
79
79
  export {
80
80
  initTheme,
81
81
  resolvedTheme,
@@ -84,7 +84,7 @@ export {
84
84
  theme,
85
85
  themeScript,
86
86
  toggleTheme,
87
- } from './theme'
87
+ } from "./theme"
88
88
 
89
89
  // ─── SEO ────────────────────────────────────────────────────────────────────
90
90
 
@@ -96,14 +96,14 @@ export type {
96
96
  SeoPluginConfig,
97
97
  SitemapConfig,
98
98
  SitemapEntry,
99
- } from './seo'
99
+ } from "./seo"
100
100
  export {
101
101
  generateRobots,
102
102
  generateSitemap,
103
103
  jsonLd,
104
104
  seoMiddleware,
105
105
  seoPlugin,
106
- } from './seo'
106
+ } from "./seo"
107
107
 
108
108
  // ─── API routes ──────────────────────────────────────────────────────────────
109
109
 
@@ -113,32 +113,32 @@ export type {
113
113
  ApiRouteEntry,
114
114
  ApiRouteModule,
115
115
  HttpMethod,
116
- } from './api-routes'
117
- export { createApiMiddleware, generateApiRouteModule } from './api-routes'
116
+ } from "./api-routes"
117
+ export { createApiMiddleware, generateApiRouteModule } from "./api-routes"
118
118
 
119
119
  // ─── CORS ────────────────────────────────────────────────────────────────────
120
120
 
121
- export type { CorsConfig } from './cors'
122
- export { corsMiddleware } from './cors'
121
+ export type { CorsConfig } from "./cors"
122
+ export { corsMiddleware } from "./cors"
123
123
 
124
124
  // ─── Rate limiting ──────────────────────────────────────────────────────────
125
125
 
126
- export type { RateLimitConfig } from './rate-limit'
127
- export { rateLimitMiddleware } from './rate-limit'
126
+ export type { RateLimitConfig } from "./rate-limit"
127
+ export { rateLimitMiddleware } from "./rate-limit"
128
128
 
129
129
  // ─── Compression ────────────────────────────────────────────────────────────
130
130
 
131
- export type { CompressionConfig } from './compression'
131
+ export type { CompressionConfig } from "./compression"
132
132
  export {
133
133
  compressionMiddleware,
134
134
  compressResponse,
135
135
  isCompressible,
136
- } from './compression'
136
+ } from "./compression"
137
137
 
138
138
  // ─── Actions ─────────────────────────────────────────────────────────────────
139
139
 
140
- export type { Action, ActionContext, ActionHandler } from './actions'
141
- export { createActionMiddleware, defineAction } from './actions'
140
+ export type { Action, ActionContext, ActionHandler } from "./actions"
141
+ export { createActionMiddleware, defineAction } from "./actions"
142
142
 
143
143
  // ─── Types ───────────────────────────────────────────────────────────────────
144
144
 
@@ -153,4 +153,4 @@ export type {
153
153
  RouteMiddlewareEntry,
154
154
  RouteModule,
155
155
  ZeroConfig,
156
- } from './types'
156
+ } from "./types"