@pyreon/zero 0.11.5 → 0.11.7
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 +22 -22
- package/lib/cache.js.map +1 -1
- package/lib/client.js.map +1 -1
- package/lib/config.js.map +1 -1
- package/lib/font.js.map +1 -1
- package/lib/fs-router-BkbIWqek.js.map +1 -1
- package/lib/fs-router-n4VA4lxu.js.map +1 -1
- package/lib/image-plugin.js.map +1 -1
- package/lib/image.js.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/link.js.map +1 -1
- package/lib/script.js.map +1 -1
- package/lib/seo.js.map +1 -1
- package/lib/theme.js.map +1 -1
- package/package.json +12 -12
- package/src/actions.ts +17 -17
- package/src/adapters/bun.ts +7 -7
- package/src/adapters/index.ts +11 -11
- package/src/adapters/node.ts +8 -8
- package/src/adapters/static.ts +3 -3
- package/src/api-routes.ts +21 -21
- package/src/app.ts +8 -8
- package/src/cache.ts +14 -14
- package/src/client.ts +8 -8
- package/src/compression.ts +19 -19
- package/src/config.ts +6 -6
- package/src/cors.ts +20 -20
- package/src/entry-server.ts +13 -13
- package/src/error-overlay.ts +9 -9
- package/src/font.ts +41 -41
- package/src/fs-router.ts +39 -39
- package/src/image-plugin.ts +45 -45
- package/src/image.tsx +39 -39
- package/src/index.ts +36 -56
- package/src/isr.ts +8 -8
- package/src/link.tsx +23 -23
- package/src/rate-limit.ts +15 -15
- package/src/script.tsx +20 -20
- package/src/seo.ts +46 -46
- package/src/sharp.d.ts +1 -1
- package/src/testing.ts +7 -7
- package/src/theme.tsx +18 -18
- package/src/types.ts +6 -6
- package/src/utils/use-intersection-observer.ts +2 -2
- package/src/vite-plugin.ts +21 -21
package/src/image-plugin.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { existsSync } from
|
|
2
|
-
import { mkdir, readFile, writeFile } from
|
|
3
|
-
import { basename, extname, join } from
|
|
4
|
-
import type { Plugin } from
|
|
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
|
-
|
|
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 =
|
|
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,39 +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 ?? [
|
|
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 ??
|
|
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:
|
|
114
|
-
enforce:
|
|
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 ===
|
|
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(
|
|
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(
|
|
131
|
+
if (!id.startsWith('\0virtual:zero-image:')) return null
|
|
132
132
|
|
|
133
|
-
const rawPath = id.replace(
|
|
134
|
-
const absPath = rawPath.startsWith(
|
|
133
|
+
const rawPath = id.replace('\0virtual:zero-image:', '').split('?')[0] ?? id
|
|
134
|
+
const absPath = rawPath.startsWith('/') ? join(root, 'public', rawPath) : rawPath
|
|
135
135
|
|
|
136
136
|
if (!isBuild) {
|
|
137
137
|
const result = await loadDevImage(absPath, rawPath, placeholderSize)
|
|
@@ -161,16 +161,16 @@ async function loadDevImage(
|
|
|
161
161
|
placeholderSize: number,
|
|
162
162
|
): Promise<ProcessedImage> {
|
|
163
163
|
const metadata = await getImageMetadata(absPath)
|
|
164
|
-
const publicPath = rawPath.startsWith(
|
|
164
|
+
const publicPath = rawPath.startsWith('/') ? rawPath : `/@fs/${absPath}`
|
|
165
165
|
|
|
166
166
|
return {
|
|
167
167
|
src: publicPath,
|
|
168
|
-
srcset:
|
|
168
|
+
srcset: '',
|
|
169
169
|
width: metadata.width,
|
|
170
170
|
height: metadata.height,
|
|
171
171
|
placeholder: await generateBlurPlaceholder(absPath, placeholderSize),
|
|
172
172
|
formats: [],
|
|
173
|
-
sources: [{ src: publicPath, width: metadata.width, format:
|
|
173
|
+
sources: [{ src: publicPath, width: metadata.width, format: 'original' }],
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
|
|
@@ -178,13 +178,13 @@ async function emitProcessedSources(
|
|
|
178
178
|
processed: ProcessedImage,
|
|
179
179
|
outSubDir: string,
|
|
180
180
|
ctx: {
|
|
181
|
-
emitFile: (f: { type:
|
|
181
|
+
emitFile: (f: { type: 'asset'; fileName: string; source: Uint8Array }) => void
|
|
182
182
|
},
|
|
183
183
|
) {
|
|
184
184
|
for (const source of processed.sources) {
|
|
185
185
|
const fileName = join(outSubDir, basename(source.src))
|
|
186
186
|
const content = await readFile(source.src)
|
|
187
|
-
ctx.emitFile({ type:
|
|
187
|
+
ctx.emitFile({ type: 'asset', fileName, source: content })
|
|
188
188
|
source.src = `/${fileName}`
|
|
189
189
|
}
|
|
190
190
|
}
|
|
@@ -201,11 +201,11 @@ function rebuildFormatSrcsets(processed: ProcessedImage, fallbackPath: string) {
|
|
|
201
201
|
}
|
|
202
202
|
processed.formats = [...formatGroups.entries()].map(([fmt, entries]) => ({
|
|
203
203
|
type: `image/${fmt}`,
|
|
204
|
-
srcset: entries.join(
|
|
204
|
+
srcset: entries.join(', '),
|
|
205
205
|
}))
|
|
206
206
|
|
|
207
207
|
const lastFormat = processed.formats.at(-1)
|
|
208
|
-
processed.srcset = lastFormat?.srcset ??
|
|
208
|
+
processed.srcset = lastFormat?.srcset ?? ''
|
|
209
209
|
processed.src = processed.sources.at(-1)?.src ?? fallbackPath
|
|
210
210
|
}
|
|
211
211
|
|
|
@@ -257,8 +257,8 @@ async function processImage(absPath: string, opts: ProcessOptions): Promise<Proc
|
|
|
257
257
|
}
|
|
258
258
|
|
|
259
259
|
const formats: FormatSource[] = [...formatGroups.entries()].map(([fmt, group]) => ({
|
|
260
|
-
type: `image/${fmt ===
|
|
261
|
-
srcset: group.map((s) => `${s.src} ${s.width}w`).join(
|
|
260
|
+
type: `image/${fmt === 'jpeg' ? 'jpeg' : fmt}`,
|
|
261
|
+
srcset: group.map((s) => `${s.src} ${s.width}w`).join(', '),
|
|
262
262
|
}))
|
|
263
263
|
|
|
264
264
|
// Fallback: last format's srcset
|
|
@@ -270,7 +270,7 @@ async function processImage(absPath: string, opts: ProcessOptions): Promise<Proc
|
|
|
270
270
|
|
|
271
271
|
return {
|
|
272
272
|
src: fallbackSources[fallbackSources.length - 1]?.src ?? absPath,
|
|
273
|
-
srcset: fallbackFormat?.srcset ??
|
|
273
|
+
srcset: fallbackFormat?.srcset ?? '',
|
|
274
274
|
width: metadata.width,
|
|
275
275
|
height: metadata.height,
|
|
276
276
|
placeholder,
|
|
@@ -293,23 +293,23 @@ async function getImageMetadata(absPath: string): Promise<ImageMetadata> {
|
|
|
293
293
|
const buffer = await readFile(absPath)
|
|
294
294
|
const ext = extname(absPath).toLowerCase()
|
|
295
295
|
|
|
296
|
-
if (ext ===
|
|
296
|
+
if (ext === '.png') {
|
|
297
297
|
// PNG: width at bytes 16-19, height at 20-23 (big-endian)
|
|
298
298
|
const width = buffer.readUInt32BE(16)
|
|
299
299
|
const height = buffer.readUInt32BE(20)
|
|
300
|
-
return { width, height, format:
|
|
300
|
+
return { width, height, format: 'png' }
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
-
if (ext ===
|
|
303
|
+
if (ext === '.jpg' || ext === '.jpeg') {
|
|
304
304
|
// JPEG: scan for SOF markers
|
|
305
305
|
const dimensions = parseJpegDimensions(buffer)
|
|
306
|
-
return { ...dimensions, format:
|
|
306
|
+
return { ...dimensions, format: 'jpeg' }
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
-
if (ext ===
|
|
309
|
+
if (ext === '.webp') {
|
|
310
310
|
// WebP: VP8 header
|
|
311
311
|
const dimensions = parseWebPDimensions(buffer)
|
|
312
|
-
return { ...dimensions, format:
|
|
312
|
+
return { ...dimensions, format: 'webp' }
|
|
313
313
|
}
|
|
314
314
|
|
|
315
315
|
// Fallback
|
|
@@ -343,21 +343,21 @@ export function parseWebPDimensions(buffer: Buffer): {
|
|
|
343
343
|
height: number
|
|
344
344
|
} {
|
|
345
345
|
// RIFF header: bytes 0-3 = "RIFF", 8-11 = "WEBP"
|
|
346
|
-
const chunk = buffer.toString(
|
|
347
|
-
if (chunk ===
|
|
346
|
+
const chunk = buffer.toString('ascii', 12, 16)
|
|
347
|
+
if (chunk === 'VP8 ') {
|
|
348
348
|
// Lossy VP8
|
|
349
349
|
const width = buffer.readUInt16LE(26) & 0x3fff
|
|
350
350
|
const height = buffer.readUInt16LE(28) & 0x3fff
|
|
351
351
|
return { width, height }
|
|
352
352
|
}
|
|
353
|
-
if (chunk ===
|
|
353
|
+
if (chunk === 'VP8L') {
|
|
354
354
|
// Lossless VP8L
|
|
355
355
|
const bits = buffer.readUInt32LE(21)
|
|
356
356
|
const width = (bits & 0x3fff) + 1
|
|
357
357
|
const height = ((bits >> 14) & 0x3fff) + 1
|
|
358
358
|
return { width, height }
|
|
359
359
|
}
|
|
360
|
-
if (chunk ===
|
|
360
|
+
if (chunk === 'VP8X') {
|
|
361
361
|
// Extended VP8X
|
|
362
362
|
const width = 1 + ((buffer[24]! | (buffer[25]! << 8) | (buffer[26]! << 16)) & 0xffffff)
|
|
363
363
|
const height = 1 + ((buffer[27]! | (buffer[28]! << 8) | (buffer[29]! << 16)) & 0xffffff)
|
|
@@ -379,20 +379,20 @@ async function resizeImage(
|
|
|
379
379
|
): Promise<void> {
|
|
380
380
|
try {
|
|
381
381
|
// Try sharp (the standard Node.js image processing library)
|
|
382
|
-
const sharp = await import(
|
|
382
|
+
const sharp = await import('sharp').then((m) => m.default ?? m)
|
|
383
383
|
let pipeline = sharp(input).resize(width)
|
|
384
384
|
|
|
385
385
|
switch (format) {
|
|
386
|
-
case
|
|
386
|
+
case 'webp':
|
|
387
387
|
pipeline = pipeline.webp({ quality })
|
|
388
388
|
break
|
|
389
|
-
case
|
|
389
|
+
case 'avif':
|
|
390
390
|
pipeline = pipeline.avif({ quality })
|
|
391
391
|
break
|
|
392
|
-
case
|
|
392
|
+
case 'jpeg':
|
|
393
393
|
pipeline = pipeline.jpeg({ quality, mozjpeg: true })
|
|
394
394
|
break
|
|
395
|
-
case
|
|
395
|
+
case 'png':
|
|
396
396
|
pipeline = pipeline.png({ compressionLevel: 9 })
|
|
397
397
|
break
|
|
398
398
|
}
|
|
@@ -411,14 +411,14 @@ async function resizeImage(
|
|
|
411
411
|
*/
|
|
412
412
|
async function generateBlurPlaceholder(input: string, size: number): Promise<string> {
|
|
413
413
|
try {
|
|
414
|
-
const sharp = await import(
|
|
414
|
+
const sharp = await import('sharp').then((m) => m.default ?? m)
|
|
415
415
|
const buffer = await sharp(input)
|
|
416
|
-
.resize(size, size, { fit:
|
|
416
|
+
.resize(size, size, { fit: 'inside' })
|
|
417
417
|
.blur(2)
|
|
418
418
|
.webp({ quality: 20 })
|
|
419
419
|
.toBuffer()
|
|
420
420
|
|
|
421
|
-
return `data:image/webp;base64,${buffer.toString(
|
|
421
|
+
return `data:image/webp;base64,${buffer.toString('base64')}`
|
|
422
422
|
} catch {
|
|
423
423
|
// sharp not available — return a transparent placeholder
|
|
424
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
|
|
2
|
-
import { createRef } from
|
|
3
|
-
import { signal } from
|
|
4
|
-
import type { FormatSource } from
|
|
5
|
-
import { useIntersectionObserver } from
|
|
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,7 +30,7 @@ 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?:
|
|
33
|
+
loading?: 'lazy' | 'eager'
|
|
34
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. */
|
|
@@ -40,9 +40,9 @@ export interface ImageProps {
|
|
|
40
40
|
/** Inline styles. */
|
|
41
41
|
style?: string
|
|
42
42
|
/** CSS object-fit. Default: "cover" */
|
|
43
|
-
fit?:
|
|
43
|
+
fit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'
|
|
44
44
|
/** Decode async. Default: true */
|
|
45
|
-
decoding?:
|
|
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 ===
|
|
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 ===
|
|
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 ??
|
|
79
|
-
const fit = props.fit ??
|
|
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,37 +89,37 @@ export function Image(props: ImageProps): VNodeChild {
|
|
|
89
89
|
|
|
90
90
|
// Static styles (don't depend on signals)
|
|
91
91
|
const containerStyle = [
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
'position: relative',
|
|
93
|
+
'overflow: hidden',
|
|
94
94
|
`aspect-ratio: ${aspectRatio}`,
|
|
95
95
|
`max-width: ${props.width}px`,
|
|
96
|
-
|
|
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={() => (!hasFormats && inView() && resolvedSrcset ? resolvedSrcset :
|
|
104
|
+
src={() => (inView() ? props.src : '')}
|
|
105
|
+
srcSet={() => (!hasFormats && inView() && resolvedSrcset ? resolvedSrcset : '')}
|
|
106
106
|
sizes={resolvedSrcset ? sizes : undefined}
|
|
107
107
|
alt={props.alt}
|
|
108
108
|
width={props.width}
|
|
109
109
|
height={props.height}
|
|
110
|
-
loading={isEager ?
|
|
111
|
-
decoding={props.decoding ??
|
|
112
|
-
fetchPriority={props.priority ?
|
|
110
|
+
loading={isEager ? 'eager' : 'lazy'}
|
|
111
|
+
decoding={props.decoding ?? 'async'}
|
|
112
|
+
fetchPriority={props.priority ? 'high' : undefined}
|
|
113
113
|
onLoad={() => loaded.set(true)}
|
|
114
114
|
style={() =>
|
|
115
115
|
[
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
'display: block',
|
|
117
|
+
'width: 100%',
|
|
118
|
+
'height: 100%',
|
|
119
119
|
`object-fit: ${fit}`,
|
|
120
|
-
|
|
121
|
-
props.placeholder && !loaded() ?
|
|
122
|
-
].join(
|
|
120
|
+
'transition: opacity 0.3s ease',
|
|
121
|
+
props.placeholder && !loaded() ? 'opacity: 0' : 'opacity: 1',
|
|
122
|
+
].join('; ')
|
|
123
123
|
}
|
|
124
124
|
/>
|
|
125
125
|
)
|
|
@@ -134,16 +134,16 @@ export function Image(props: ImageProps): VNodeChild {
|
|
|
134
134
|
loading="eager"
|
|
135
135
|
style={() =>
|
|
136
136
|
[
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
loaded() ?
|
|
146
|
-
].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('; ')
|
|
147
147
|
}
|
|
148
148
|
/>
|
|
149
149
|
)}
|
|
@@ -152,7 +152,7 @@ export function Image(props: ImageProps): VNodeChild {
|
|
|
152
152
|
{props.formats?.map((fmt) => (
|
|
153
153
|
<source
|
|
154
154
|
type={fmt.type}
|
|
155
|
-
srcSet={() => (inView() ? (fmt.srcset ??
|
|
155
|
+
srcSet={() => (inView() ? (fmt.srcset ?? '') : '')}
|
|
156
156
|
sizes={sizes}
|
|
157
157
|
/>
|
|
158
158
|
))}
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// ─── Core ─────────────────────────────────────────────────────────────────────
|
|
2
2
|
|
|
3
|
-
export type { CreateAppOptions } from
|
|
4
|
-
export { createApp } from
|
|
5
|
-
export type { CreateServerOptions } from
|
|
6
|
-
export { createServer } from
|
|
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
|
|
10
|
+
export { zeroPlugin as default } from './vite-plugin'
|
|
11
11
|
|
|
12
12
|
// ─── File-system routing ─────────────────────────────────────────────────────
|
|
13
13
|
|
|
@@ -17,38 +17,33 @@ export {
|
|
|
17
17
|
generateRouteModule,
|
|
18
18
|
parseFileRoutes,
|
|
19
19
|
scanRouteFiles,
|
|
20
|
-
} from
|
|
20
|
+
} from './fs-router'
|
|
21
21
|
|
|
22
22
|
// ─── Config ──────────────────────────────────────────────────────────────────
|
|
23
23
|
|
|
24
|
-
export { defineConfig, resolveConfig } from
|
|
24
|
+
export { defineConfig, resolveConfig } from './config'
|
|
25
25
|
|
|
26
26
|
// ─── ISR ─────────────────────────────────────────────────────────────────────
|
|
27
27
|
|
|
28
|
-
export { createISRHandler } from
|
|
28
|
+
export { createISRHandler } from './isr'
|
|
29
29
|
|
|
30
30
|
// ─── Adapters ────────────────────────────────────────────────────────────────
|
|
31
31
|
|
|
32
|
-
export {
|
|
33
|
-
bunAdapter,
|
|
34
|
-
nodeAdapter,
|
|
35
|
-
resolveAdapter,
|
|
36
|
-
staticAdapter,
|
|
37
|
-
} from "./adapters"
|
|
32
|
+
export { bunAdapter, nodeAdapter, resolveAdapter, staticAdapter } from './adapters'
|
|
38
33
|
|
|
39
34
|
// ─── Components ─────────────────────────────────────────────────────────────
|
|
40
35
|
|
|
41
|
-
export type { ImageProps, ImageSource } from
|
|
42
|
-
export { Image } from
|
|
43
|
-
export type { LinkProps, LinkRenderProps, UseLinkReturn } from
|
|
44
|
-
export { createLink, Link, useLink } from
|
|
45
|
-
export type { ScriptProps, ScriptStrategy } from
|
|
46
|
-
export { Script } from
|
|
36
|
+
export type { ImageProps, ImageSource } from './image'
|
|
37
|
+
export { Image } from './image'
|
|
38
|
+
export type { LinkProps, LinkRenderProps, UseLinkReturn } from './link'
|
|
39
|
+
export { createLink, Link, useLink } from './link'
|
|
40
|
+
export type { ScriptProps, ScriptStrategy } from './script'
|
|
41
|
+
export { Script } from './script'
|
|
47
42
|
|
|
48
43
|
// ─── Middleware ──────────────────────────────────────────────────────────────
|
|
49
44
|
|
|
50
|
-
export type { CacheConfig, CacheRule } from
|
|
51
|
-
export { cacheMiddleware, securityHeaders, varyEncoding } from
|
|
45
|
+
export type { CacheConfig, CacheRule } from './cache'
|
|
46
|
+
export { cacheMiddleware, securityHeaders, varyEncoding } from './cache'
|
|
52
47
|
|
|
53
48
|
// ─── Font optimization ─────────────────────────────────────────────────────
|
|
54
49
|
|
|
@@ -60,22 +55,17 @@ export type {
|
|
|
60
55
|
GoogleFontStatic,
|
|
61
56
|
GoogleFontVariable,
|
|
62
57
|
LocalFont,
|
|
63
|
-
} from
|
|
64
|
-
export { fontPlugin, fontVariables } from
|
|
58
|
+
} from './font'
|
|
59
|
+
export { fontPlugin, fontVariables } from './font'
|
|
65
60
|
|
|
66
61
|
// ─── Image processing ──────────────────────────────────────────────────────
|
|
67
62
|
|
|
68
|
-
export type {
|
|
69
|
-
|
|
70
|
-
ImageFormat,
|
|
71
|
-
ImagePluginConfig,
|
|
72
|
-
ProcessedImage,
|
|
73
|
-
} from "./image-plugin"
|
|
74
|
-
export { imagePlugin } from "./image-plugin"
|
|
63
|
+
export type { FormatSource, ImageFormat, ImagePluginConfig, ProcessedImage } from './image-plugin'
|
|
64
|
+
export { imagePlugin } from './image-plugin'
|
|
75
65
|
|
|
76
66
|
// ─── Theme ──────────────────────────────────────────────────────────────────
|
|
77
67
|
|
|
78
|
-
export type { Theme } from
|
|
68
|
+
export type { Theme } from './theme'
|
|
79
69
|
export {
|
|
80
70
|
initTheme,
|
|
81
71
|
resolvedTheme,
|
|
@@ -84,7 +74,7 @@ export {
|
|
|
84
74
|
theme,
|
|
85
75
|
themeScript,
|
|
86
76
|
toggleTheme,
|
|
87
|
-
} from
|
|
77
|
+
} from './theme'
|
|
88
78
|
|
|
89
79
|
// ─── SEO ────────────────────────────────────────────────────────────────────
|
|
90
80
|
|
|
@@ -96,14 +86,8 @@ export type {
|
|
|
96
86
|
SeoPluginConfig,
|
|
97
87
|
SitemapConfig,
|
|
98
88
|
SitemapEntry,
|
|
99
|
-
} from
|
|
100
|
-
export {
|
|
101
|
-
generateRobots,
|
|
102
|
-
generateSitemap,
|
|
103
|
-
jsonLd,
|
|
104
|
-
seoMiddleware,
|
|
105
|
-
seoPlugin,
|
|
106
|
-
} from "./seo"
|
|
89
|
+
} from './seo'
|
|
90
|
+
export { generateRobots, generateSitemap, jsonLd, seoMiddleware, seoPlugin } from './seo'
|
|
107
91
|
|
|
108
92
|
// ─── API routes ──────────────────────────────────────────────────────────────
|
|
109
93
|
|
|
@@ -113,32 +97,28 @@ export type {
|
|
|
113
97
|
ApiRouteEntry,
|
|
114
98
|
ApiRouteModule,
|
|
115
99
|
HttpMethod,
|
|
116
|
-
} from
|
|
117
|
-
export { createApiMiddleware, generateApiRouteModule } from
|
|
100
|
+
} from './api-routes'
|
|
101
|
+
export { createApiMiddleware, generateApiRouteModule } from './api-routes'
|
|
118
102
|
|
|
119
103
|
// ─── CORS ────────────────────────────────────────────────────────────────────
|
|
120
104
|
|
|
121
|
-
export type { CorsConfig } from
|
|
122
|
-
export { corsMiddleware } from
|
|
105
|
+
export type { CorsConfig } from './cors'
|
|
106
|
+
export { corsMiddleware } from './cors'
|
|
123
107
|
|
|
124
108
|
// ─── Rate limiting ──────────────────────────────────────────────────────────
|
|
125
109
|
|
|
126
|
-
export type { RateLimitConfig } from
|
|
127
|
-
export { rateLimitMiddleware } from
|
|
110
|
+
export type { RateLimitConfig } from './rate-limit'
|
|
111
|
+
export { rateLimitMiddleware } from './rate-limit'
|
|
128
112
|
|
|
129
113
|
// ─── Compression ────────────────────────────────────────────────────────────
|
|
130
114
|
|
|
131
|
-
export type { CompressionConfig } from
|
|
132
|
-
export {
|
|
133
|
-
compressionMiddleware,
|
|
134
|
-
compressResponse,
|
|
135
|
-
isCompressible,
|
|
136
|
-
} from "./compression"
|
|
115
|
+
export type { CompressionConfig } from './compression'
|
|
116
|
+
export { compressionMiddleware, compressResponse, isCompressible } from './compression'
|
|
137
117
|
|
|
138
118
|
// ─── Actions ─────────────────────────────────────────────────────────────────
|
|
139
119
|
|
|
140
|
-
export type { Action, ActionContext, ActionHandler } from
|
|
141
|
-
export { createActionMiddleware, defineAction } from
|
|
120
|
+
export type { Action, ActionContext, ActionHandler } from './actions'
|
|
121
|
+
export { createActionMiddleware, defineAction } from './actions'
|
|
142
122
|
|
|
143
123
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
144
124
|
|
|
@@ -153,4 +133,4 @@ export type {
|
|
|
153
133
|
RouteMiddlewareEntry,
|
|
154
134
|
RouteModule,
|
|
155
135
|
ZeroConfig,
|
|
156
|
-
} from
|
|
136
|
+
} from './types'
|
package/src/isr.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ISRConfig } from
|
|
1
|
+
import type { ISRConfig } from './types'
|
|
2
2
|
|
|
3
3
|
// ─── ISR Cache ───────────────────────────────────────────────────────────────
|
|
4
4
|
|
|
@@ -28,7 +28,7 @@ export function createISRHandler(
|
|
|
28
28
|
revalidating.add(key)
|
|
29
29
|
|
|
30
30
|
try {
|
|
31
|
-
const req = new Request(url.href, { method:
|
|
31
|
+
const req = new Request(url.href, { method: 'GET' })
|
|
32
32
|
const res = await handler(req)
|
|
33
33
|
const html = await res.text()
|
|
34
34
|
const headers: Record<string, string> = {}
|
|
@@ -46,7 +46,7 @@ export function createISRHandler(
|
|
|
46
46
|
|
|
47
47
|
return async (req: Request): Promise<Response> => {
|
|
48
48
|
// Only cache GET requests
|
|
49
|
-
if (req.method !==
|
|
49
|
+
if (req.method !== 'GET') {
|
|
50
50
|
return handler(req)
|
|
51
51
|
}
|
|
52
52
|
|
|
@@ -66,9 +66,9 @@ export function createISRHandler(
|
|
|
66
66
|
status: 200,
|
|
67
67
|
headers: {
|
|
68
68
|
...entry.headers,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
'content-type': 'text/html; charset=utf-8',
|
|
70
|
+
'x-isr-cache': age > revalidateMs ? 'STALE' : 'HIT',
|
|
71
|
+
'x-isr-age': String(Math.round(age / 1000)),
|
|
72
72
|
},
|
|
73
73
|
})
|
|
74
74
|
}
|
|
@@ -87,8 +87,8 @@ export function createISRHandler(
|
|
|
87
87
|
status: 200,
|
|
88
88
|
headers: {
|
|
89
89
|
...headers,
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
'content-type': 'text/html; charset=utf-8',
|
|
91
|
+
'x-isr-cache': 'MISS',
|
|
92
92
|
},
|
|
93
93
|
})
|
|
94
94
|
}
|