@pyreon/zero 0.19.0 → 0.21.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.
@@ -23,7 +23,13 @@ function warnSharpMissing() {
23
23
  //
24
24
  // Usage in code:
25
25
  // import heroImg from "./hero.jpg?optimize"
26
- // // → { src, srcset, width, height, placeholder }
26
+ // // → ProcessedImage { src, srcset, width, height, placeholder }
27
+ //
28
+ // Type the `?optimize` / `?component` / `?raw` imports out of the box —
29
+ // add ONE line to a tsconfig-covered `.d.ts` (e.g. `src/env.d.ts`):
30
+ // /// <reference types="@pyreon/zero/image-types" />
31
+ // (ships the ambient `declare module "*?optimize"` etc. — reuses this
32
+ // module's own `ProcessedImage`, so it never drifts.)
27
33
  //
28
34
  // Or use the component helper:
29
35
  // import { Image } from "@pyreon/zero/image"
@@ -229,23 +235,45 @@ export function imagePlugin(config: ImagePluginConfig = {}): Plugin {
229
235
  isBuild = resolvedConfig.command === 'build'
230
236
  },
231
237
 
232
- async resolveId(id) {
233
- // SVG as component: import Logo from './logo.svg?component'
234
- if (svgOpts && id.includes('?component') && id.split('?')[0]!.endsWith('.svg')) {
235
- return `\0virtual:zero-svg:${id}`
236
- }
237
- // Handle ?optimize query on image imports
238
- if (id.includes('?optimize') && include.test(id.split('?')[0]!)) {
239
- return `\0virtual:zero-image:${id}`
240
- }
241
- return null
238
+ async resolveId(id, importer) {
239
+ const isSvgComponent =
240
+ svgOpts && id.includes('?component') && id.split('?')[0]!.endsWith('.svg')
241
+ const isOptimize =
242
+ id.includes('?optimize') && include.test(id.split('?')[0]!)
243
+ if (!isSvgComponent && !isOptimize) return null
244
+
245
+ // Resolve the bare specifier to an ABSOLUTE fs path the way Vite
246
+ // resolves `?url` — importer-relative + alias-aware. The old code
247
+ // embedded the raw unresolved id, so `load()` had to guess: a
248
+ // relative `./img.png?optimize` resolved against cwd (≠ the
249
+ // importer's dir → ENOENT), and an aliased `~/x.png?optimize`
250
+ // arrived already-absolute and got `join(root,'public',…)`-doubled.
251
+ // `this.resolve` (skipSelf so we don't recurse into our own
252
+ // resolveId) handles relative + aliases + extensions. A public-dir
253
+ // web path (`/foo.png?optimize`) doesn't resolve to a module →
254
+ // null → keep the original id so load()'s public/ fallback applies.
255
+ const qIdx = id.indexOf('?')
256
+ const bare = qIdx === -1 ? id : id.slice(0, qIdx)
257
+ const query = qIdx === -1 ? '' : id.slice(qIdx)
258
+ const resolved = await this.resolve(bare, importer, { skipSelf: true })
259
+ const carried = resolved ? `${resolved.id}${query}` : id
260
+
261
+ if (isSvgComponent) return `\0virtual:zero-svg:${carried}`
262
+ return `\0virtual:zero-image:${carried}`
242
263
  },
243
264
 
244
265
  async load(id) {
245
266
  // SVG component loading
246
267
  if (id.startsWith('\0virtual:zero-svg:')) {
247
268
  const rawPath = id.replace('\0virtual:zero-svg:', '').split('?')[0] ?? id
248
- const absPath = rawPath.startsWith('/') ? join(root, rawPath) : rawPath
269
+ // resolveId now carries an absolute fs path for relative/aliased
270
+ // imports → trust it if it exists. Only a public-dir web path
271
+ // (`/logo.svg`, unresolved) falls back to root-join.
272
+ const absPath = existsSync(rawPath)
273
+ ? rawPath
274
+ : rawPath.startsWith('/')
275
+ ? join(root, rawPath)
276
+ : rawPath
249
277
  if (!existsSync(absPath)) return null
250
278
 
251
279
  let svg = await readFile(absPath, 'utf-8')
@@ -287,7 +315,16 @@ export default function SvgComponent(props) {
287
315
  if (!id.startsWith('\0virtual:zero-image:')) return null
288
316
 
289
317
  const rawPath = id.replace('\0virtual:zero-image:', '').split('?')[0] ?? id
290
- const absPath = rawPath.startsWith('/') ? join(root, 'public', rawPath) : rawPath
318
+ // resolveId now carries an absolute fs path for relative/aliased
319
+ // imports (the `./img.png?optimize` and `~/img.png?optimize` cases
320
+ // that used to ENOENT / double-`public`). Trust an existing
321
+ // absolute path; only an unresolved public-dir web path
322
+ // (`/foo.png?optimize`) falls back to `<root>/public/…`.
323
+ const absPath = existsSync(rawPath)
324
+ ? rawPath
325
+ : rawPath.startsWith('/')
326
+ ? join(root, 'public', rawPath)
327
+ : rawPath
291
328
 
292
329
  // CDN mode — rewrite URLs, no local processing
293
330
  if (cdn) {
@@ -358,7 +395,15 @@ async function loadDevImage(
358
395
  placeholderSize: number,
359
396
  ): Promise<ProcessedImage> {
360
397
  const metadata = await getImageMetadata(absPath)
361
- const publicPath = rawPath.startsWith('/') ? rawPath : `/@fs/${absPath}`
398
+ // `rawPath` is a public-dir web path (e.g. `/logo.png`, served from
399
+ // `public/` at the web root) ONLY when it does NOT resolve to a real
400
+ // file on disk — the same discriminator the `absPath` derivation uses
401
+ // above. `resolveId` now hands absolute fs paths for relative/aliased
402
+ // imports (`/Users/…/img.png`); those ARE real files and must be
403
+ // served through Vite's `/@fs/` prefix, not as a literal `/Users/…`
404
+ // URL (which 404s in dev — build mode was unaffected).
405
+ const isPublicWebPath = rawPath.startsWith('/') && !existsSync(rawPath)
406
+ const publicPath = isPublicWebPath ? rawPath : `/@fs/${absPath}`
362
407
 
363
408
  return {
364
409
  src: publicPath,
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Ambient type declarations for the custom image-import queries that
3
+ * `@pyreon/zero`'s `imagePlugin` introduces (`?optimize` / `?component`
4
+ * / `?raw`). Shipped + exported so the documented usage type-checks out
5
+ * of the box — no consumer hand-authoring required.
6
+ *
7
+ * Add ONE line to any tsconfig-covered `.d.ts` (e.g. `src/env.d.ts`):
8
+ * /// <reference types="@pyreon/zero/image-types" />
9
+ *
10
+ * Or via tsconfig.json:
11
+ * "types": ["@pyreon/zero/image-types"]
12
+ *
13
+ * This is an ambient-only **script** (no top-level import/export) so
14
+ * every `declare module` below is a global module augmentation. The
15
+ * `ProcessedImage` shape is referenced via the package self-ref
16
+ * `import('@pyreon/zero/image-plugin')` (resolution-stable in the
17
+ * published layout, and re-uses the plugin's own type so it can never
18
+ * drift out of sync).
19
+ */
20
+
21
+ declare module '*.jpg?optimize' {
22
+ const image: import('@pyreon/zero/image-plugin').ProcessedImage
23
+ export default image
24
+ }
25
+
26
+ declare module '*.jpeg?optimize' {
27
+ const image: import('@pyreon/zero/image-plugin').ProcessedImage
28
+ export default image
29
+ }
30
+
31
+ declare module '*.png?optimize' {
32
+ const image: import('@pyreon/zero/image-plugin').ProcessedImage
33
+ export default image
34
+ }
35
+
36
+ declare module '*.webp?optimize' {
37
+ const image: import('@pyreon/zero/image-plugin').ProcessedImage
38
+ export default image
39
+ }
40
+
41
+ declare module '*.avif?optimize' {
42
+ const image: import('@pyreon/zero/image-plugin').ProcessedImage
43
+ export default image
44
+ }
45
+
46
+ declare module '*.svg?component' {
47
+ const component: import('@pyreon/core').ComponentFn<{
48
+ width?: number
49
+ height?: number
50
+ class?: string
51
+ style?: string
52
+ [key: string]: unknown
53
+ }>
54
+ export default component
55
+ }
56
+
57
+ declare module '*.svg?raw' {
58
+ const svg: string
59
+ export default svg
60
+ }