@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/theme.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { VNodeChild } from
|
|
2
|
-
import { onMount, onUnmount } from
|
|
3
|
-
import { effect, signal } from
|
|
1
|
+
import type { VNodeChild } from '@pyreon/core'
|
|
2
|
+
import { onMount, onUnmount } from '@pyreon/core'
|
|
3
|
+
import { effect, signal } from '@pyreon/reactivity'
|
|
4
4
|
|
|
5
5
|
// ─── Theme system ───────────────────────────────────────────────────────────
|
|
6
6
|
//
|
|
@@ -10,19 +10,19 @@ import { effect, signal } from "@pyreon/reactivity"
|
|
|
10
10
|
// - No flash of wrong theme (inline script in HTML)
|
|
11
11
|
// - Reactive theme signal for components
|
|
12
12
|
|
|
13
|
-
export type Theme =
|
|
13
|
+
export type Theme = 'light' | 'dark' | 'system'
|
|
14
14
|
|
|
15
|
-
const STORAGE_KEY =
|
|
15
|
+
const STORAGE_KEY = 'zero-theme'
|
|
16
16
|
|
|
17
17
|
/** Reactive theme signal. */
|
|
18
|
-
export const theme = signal<Theme>(
|
|
18
|
+
export const theme = signal<Theme>('system')
|
|
19
19
|
|
|
20
20
|
/** Computed resolved theme (what's actually applied). */
|
|
21
|
-
export function resolvedTheme():
|
|
21
|
+
export function resolvedTheme(): 'light' | 'dark' {
|
|
22
22
|
const t = theme()
|
|
23
|
-
if (t ===
|
|
24
|
-
if (typeof window ===
|
|
25
|
-
return window.matchMedia(
|
|
23
|
+
if (t === 'system') {
|
|
24
|
+
if (typeof window === 'undefined') return 'dark'
|
|
25
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
|
26
26
|
}
|
|
27
27
|
return t
|
|
28
28
|
}
|
|
@@ -30,13 +30,13 @@ export function resolvedTheme(): "light" | "dark" {
|
|
|
30
30
|
/** Toggle between light and dark. */
|
|
31
31
|
export function toggleTheme() {
|
|
32
32
|
const current = resolvedTheme()
|
|
33
|
-
setTheme(current ===
|
|
33
|
+
setTheme(current === 'dark' ? 'light' : 'dark')
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
/** Set theme explicitly. */
|
|
37
37
|
export function setTheme(t: Theme) {
|
|
38
38
|
theme.set(t)
|
|
39
|
-
if (typeof document !==
|
|
39
|
+
if (typeof document !== 'undefined') {
|
|
40
40
|
document.documentElement.dataset.theme = resolvedTheme()
|
|
41
41
|
try {
|
|
42
42
|
localStorage.setItem(STORAGE_KEY, t)
|
|
@@ -55,7 +55,7 @@ export function initTheme() {
|
|
|
55
55
|
// Read persisted preference
|
|
56
56
|
try {
|
|
57
57
|
const stored = localStorage.getItem(STORAGE_KEY) as Theme | null
|
|
58
|
-
if (stored ===
|
|
58
|
+
if (stored === 'light' || stored === 'dark' || stored === 'system') {
|
|
59
59
|
theme.set(stored)
|
|
60
60
|
}
|
|
61
61
|
} catch {
|
|
@@ -66,14 +66,14 @@ export function initTheme() {
|
|
|
66
66
|
document.documentElement.dataset.theme = resolvedTheme()
|
|
67
67
|
|
|
68
68
|
// Watch for system preference changes
|
|
69
|
-
const mq = window.matchMedia(
|
|
69
|
+
const mq = window.matchMedia('(prefers-color-scheme: dark)')
|
|
70
70
|
function onChange() {
|
|
71
|
-
if (theme() ===
|
|
71
|
+
if (theme() === 'system') {
|
|
72
72
|
document.documentElement.dataset.theme = resolvedTheme()
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
-
mq.addEventListener(
|
|
76
|
-
onUnmount(() => mq.removeEventListener(
|
|
75
|
+
mq.addEventListener('change', onChange)
|
|
76
|
+
onUnmount(() => mq.removeEventListener('change', onChange))
|
|
77
77
|
|
|
78
78
|
// Re-apply when theme signal changes
|
|
79
79
|
const dispose = effect(() => {
|
|
@@ -105,7 +105,7 @@ export function ThemeToggle(props: { class?: string; style?: string }): VNodeChi
|
|
|
105
105
|
type="button"
|
|
106
106
|
>
|
|
107
107
|
{() =>
|
|
108
|
-
resolvedTheme() ===
|
|
108
|
+
resolvedTheme() === 'dark' ? (
|
|
109
109
|
<svg
|
|
110
110
|
width="18"
|
|
111
111
|
height="18"
|
package/src/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ComponentFn } from
|
|
2
|
-
import type { NavigationGuard } from
|
|
3
|
-
import type { Middleware } from
|
|
1
|
+
import type { ComponentFn } from '@pyreon/core'
|
|
2
|
+
import type { NavigationGuard } from '@pyreon/router'
|
|
3
|
+
import type { Middleware } from '@pyreon/server'
|
|
4
4
|
|
|
5
5
|
// ─── Route module conventions ────────────────────────────────────────────────
|
|
6
6
|
|
|
@@ -43,7 +43,7 @@ export interface RouteMeta {
|
|
|
43
43
|
|
|
44
44
|
// ─── Rendering modes ─────────────────────────────────────────────────────────
|
|
45
45
|
|
|
46
|
-
export type RenderMode =
|
|
46
|
+
export type RenderMode = 'ssr' | 'ssg' | 'spa' | 'isr'
|
|
47
47
|
|
|
48
48
|
export interface ISRConfig {
|
|
49
49
|
/** Revalidation interval in seconds. */
|
|
@@ -62,7 +62,7 @@ export interface ZeroConfig {
|
|
|
62
62
|
/** SSR options. */
|
|
63
63
|
ssr?: {
|
|
64
64
|
/** Streaming mode. Default: "string" */
|
|
65
|
-
mode?:
|
|
65
|
+
mode?: 'string' | 'stream'
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/** SSG options — only used when mode is "ssg". */
|
|
@@ -75,7 +75,7 @@ export interface ZeroConfig {
|
|
|
75
75
|
isr?: ISRConfig
|
|
76
76
|
|
|
77
77
|
/** Deploy adapter. Default: "node" */
|
|
78
|
-
adapter?:
|
|
78
|
+
adapter?: 'node' | 'bun' | 'static'
|
|
79
79
|
|
|
80
80
|
/** Base URL path. Default: "/" */
|
|
81
81
|
base?: string
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { onMount, onUnmount } from
|
|
1
|
+
import { onMount, onUnmount } from '@pyreon/core'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Observes an element and calls `onIntersect` once it enters the viewport.
|
|
@@ -11,7 +11,7 @@ import { onMount, onUnmount } from "@pyreon/core"
|
|
|
11
11
|
export function useIntersectionObserver(
|
|
12
12
|
getElement: () => HTMLElement | undefined,
|
|
13
13
|
onIntersect: () => void,
|
|
14
|
-
rootMargin =
|
|
14
|
+
rootMargin = '200px',
|
|
15
15
|
) {
|
|
16
16
|
onMount(() => {
|
|
17
17
|
const el = getElement()
|
package/src/vite-plugin.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import type { Plugin } from
|
|
2
|
-
import { generateApiRouteModule } from
|
|
3
|
-
import { resolveConfig } from
|
|
4
|
-
import { renderErrorOverlay } from
|
|
5
|
-
import { generateMiddlewareModule, generateRouteModule, scanRouteFiles } from
|
|
6
|
-
import type { ZeroConfig } from
|
|
7
|
-
|
|
8
|
-
const VIRTUAL_ROUTES_ID =
|
|
1
|
+
import type { Plugin } from 'vite'
|
|
2
|
+
import { generateApiRouteModule } from './api-routes'
|
|
3
|
+
import { resolveConfig } from './config'
|
|
4
|
+
import { renderErrorOverlay } from './error-overlay'
|
|
5
|
+
import { generateMiddlewareModule, generateRouteModule, scanRouteFiles } from './fs-router'
|
|
6
|
+
import type { ZeroConfig } from './types'
|
|
7
|
+
|
|
8
|
+
const VIRTUAL_ROUTES_ID = 'virtual:zero/routes'
|
|
9
9
|
const RESOLVED_VIRTUAL_ROUTES_ID = `\0${VIRTUAL_ROUTES_ID}`
|
|
10
10
|
|
|
11
|
-
const VIRTUAL_MIDDLEWARE_ID =
|
|
11
|
+
const VIRTUAL_MIDDLEWARE_ID = 'virtual:zero/route-middleware'
|
|
12
12
|
const RESOLVED_VIRTUAL_MIDDLEWARE_ID = `\0${VIRTUAL_MIDDLEWARE_ID}`
|
|
13
13
|
|
|
14
|
-
const VIRTUAL_API_ROUTES_ID =
|
|
14
|
+
const VIRTUAL_API_ROUTES_ID = 'virtual:zero/api-routes'
|
|
15
15
|
const RESOLVED_VIRTUAL_API_ROUTES_ID = `\0${VIRTUAL_API_ROUTES_ID}`
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -33,8 +33,8 @@ export function zeroPlugin(userConfig: ZeroConfig = {}): Plugin {
|
|
|
33
33
|
let root: string
|
|
34
34
|
|
|
35
35
|
const plugin: Plugin & { _zeroConfig: ZeroConfig } = {
|
|
36
|
-
name:
|
|
37
|
-
enforce:
|
|
36
|
+
name: 'pyreon-zero',
|
|
37
|
+
enforce: 'pre',
|
|
38
38
|
_zeroConfig: userConfig,
|
|
39
39
|
|
|
40
40
|
configResolved(resolvedConfig) {
|
|
@@ -82,8 +82,8 @@ export function zeroPlugin(userConfig: ZeroConfig = {}): Plugin {
|
|
|
82
82
|
// This runs as a late middleware (return function) so it wraps
|
|
83
83
|
// Vite's own SSR handling and catches rendering failures.
|
|
84
84
|
server.middlewares.use((req, res, next) => {
|
|
85
|
-
const accept = req.headers.accept ??
|
|
86
|
-
if (!accept.includes(
|
|
85
|
+
const accept = req.headers.accept ?? ''
|
|
86
|
+
if (!accept.includes('text/html')) return next()
|
|
87
87
|
|
|
88
88
|
// Monkey-patch res.end to catch errors from SSR rendering
|
|
89
89
|
const originalEnd = res.end.bind(res)
|
|
@@ -96,12 +96,12 @@ export function zeroPlugin(userConfig: ZeroConfig = {}): Plugin {
|
|
|
96
96
|
server.ssrFixStacktrace(error)
|
|
97
97
|
const html = renderErrorOverlay(error)
|
|
98
98
|
res.statusCode = 500
|
|
99
|
-
res.setHeader(
|
|
100
|
-
res.setHeader(
|
|
99
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
|
100
|
+
res.setHeader('Content-Length', Buffer.byteLength(html))
|
|
101
101
|
originalEnd(html)
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
res.on(
|
|
104
|
+
res.on('error', handleError)
|
|
105
105
|
|
|
106
106
|
// Wrap next() in try/catch to handle synchronous errors
|
|
107
107
|
try {
|
|
@@ -115,8 +115,8 @@ export function zeroPlugin(userConfig: ZeroConfig = {}): Plugin {
|
|
|
115
115
|
server.watcher.add(`${routesDir}/**/*.{tsx,jsx,ts,js}`)
|
|
116
116
|
|
|
117
117
|
// Invalidate virtual modules when route files change
|
|
118
|
-
server.watcher.on(
|
|
119
|
-
if (path.startsWith(routesDir) && (event ===
|
|
118
|
+
server.watcher.on('all', (event, path) => {
|
|
119
|
+
if (path.startsWith(routesDir) && (event === 'add' || event === 'unlink')) {
|
|
120
120
|
for (const resolvedId of [
|
|
121
121
|
RESOLVED_VIRTUAL_ROUTES_ID,
|
|
122
122
|
RESOLVED_VIRTUAL_MIDDLEWARE_ID,
|
|
@@ -125,7 +125,7 @@ export function zeroPlugin(userConfig: ZeroConfig = {}): Plugin {
|
|
|
125
125
|
const mod = server.moduleGraph.getModuleById(resolvedId)
|
|
126
126
|
if (mod) server.moduleGraph.invalidateModule(mod)
|
|
127
127
|
}
|
|
128
|
-
server.ws.send({ type:
|
|
128
|
+
server.ws.send({ type: 'full-reload' })
|
|
129
129
|
}
|
|
130
130
|
})
|
|
131
131
|
},
|
|
@@ -133,7 +133,7 @@ export function zeroPlugin(userConfig: ZeroConfig = {}): Plugin {
|
|
|
133
133
|
config() {
|
|
134
134
|
return {
|
|
135
135
|
resolve: {
|
|
136
|
-
conditions: [
|
|
136
|
+
conditions: ['bun'],
|
|
137
137
|
},
|
|
138
138
|
server: {
|
|
139
139
|
port: config.port,
|