@vesk/adapter 0.2.66 → 0.2.67

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.
@@ -1,3 +1,4 @@
1
- import type { ImageResult } from '@vesk/adapter/src/types';
1
+ import type { ImageRef, ImageResult } from '@vesk/adapter/src/types';
2
+ export declare function collectImageRefs(appDir: string): ImageRef[];
2
3
  export declare function optimizeImages(appDir: string, outDir: string): Promise<ImageResult[]>;
3
4
  //# sourceMappingURL=image-pipeline.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"image-pipeline.d.ts","sourceRoot":"","sources":["../src/image-pipeline.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAY,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAyKrE,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAwD3F"}
1
+ {"version":3,"file":"image-pipeline.d.ts","sourceRoot":"","sources":["../src/image-pipeline.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAoKrE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,EAAE,CA6B3D;AAED,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAoE3F"}
@@ -99,22 +99,42 @@ async function ensureSharp() {
99
99
  async function processImage(srcPath, outDir, baseName) {
100
100
  const image = sharpFn ? sharpFn(srcPath) : null;
101
101
  if (!image) {
102
+ // Copy-only fallback. The emitted file MUST keep the source extension:
103
+ // `<Image>`'s srcset is `${base}-${w}w${ext}`, so writing `hero-640w` with
104
+ // no extension left every copy-only URL 404ing.
102
105
  const original = readFileSync(srcPath);
106
+ const ext = extname(srcPath);
107
+ const written = [];
103
108
  for (const w of OUTPUT_WIDTHS) {
104
- const outputPath = resolve(outDir, `${baseName}-${w}w`);
109
+ const outputPath = resolve(outDir, `${baseName}-${w}w${ext}`);
105
110
  mkdirSync(dirname(outputPath), { recursive: true });
106
111
  writeFileSync(outputPath, original);
112
+ written.push(outputPath);
107
113
  }
108
- return [];
114
+ return written;
109
115
  }
110
116
  const meta = await image.metadata();
111
117
  const originalWidth = meta.width || 1920;
112
118
  const generated = [];
119
+ // `<Image>` emits a srcset entry for EVERY configured width, whatever the
120
+ // source's real size — it has no way to know what was generated. Skipping
121
+ // widths above the source's width therefore left real URLs 404ing (a 400px
122
+ // photo produced none of the 640w+ entries the browser would pick).
123
+ //
124
+ // So emit every configured width, and for any width at or above the
125
+ // source's own, emit the ORIGINAL bytes rather than enlarging: the URL
126
+ // resolves, and the browser gets a smaller image than it asked for instead
127
+ // of a 404.
113
128
  for (const w of OUTPUT_WIDTHS) {
114
- if (w > originalWidth)
129
+ const base = `${baseName}-${w}w`;
130
+ if (w >= originalWidth) {
131
+ const copyPath = resolve(outDir, `${base}${extname(srcPath)}`);
132
+ mkdirSync(dirname(copyPath), { recursive: true });
133
+ writeFileSync(copyPath, readFileSync(srcPath));
134
+ generated.push(copyPath);
115
135
  continue;
136
+ }
116
137
  const resized = image.clone().resize({ width: w, withoutEnlargement: true });
117
- const base = `${baseName}-${w}w`;
118
138
  const jpgPath = resolve(outDir, `${base}${extname(srcPath)}`);
119
139
  mkdirSync(dirname(jpgPath), { recursive: true });
120
140
  await resized.toFile(jpgPath);
@@ -127,7 +147,7 @@ async function processImage(srcPath, outDir, baseName) {
127
147
  }
128
148
  return generated;
129
149
  }
130
- function collectImageRefs(appDir) {
150
+ export function collectImageRefs(appDir) {
131
151
  const refs = [];
132
152
  function walk(dir) {
133
153
  let entries;
@@ -145,9 +165,13 @@ function collectImageRefs(appDir) {
145
165
  continue;
146
166
  walk(full);
147
167
  }
148
- else if (entry === 'page.vsk') {
168
+ else if (entry.endsWith('.vsk')) {
169
+ // Scan EVERY .vsk file, not just page.vsk. An `<Image>` living in a
170
+ // shared component or a layout is just as real a reference, and
171
+ // restricting the walk to page.vsk meant those images were never
172
+ // processed — their srcsets 404'd with no build-time complaint.
149
173
  const src = readFileSync(full, 'utf-8');
150
- const imgRegex = /<Image\s+src=["']([^"']+)["']/g;
174
+ const imgRegex = /<Image\s[^>]*src=["']([^"']+)["']/g;
151
175
  let m;
152
176
  while ((m = imgRegex.exec(src)) !== null) {
153
177
  refs.push({ source: full, src: m[1] });
@@ -159,8 +183,15 @@ function collectImageRefs(appDir) {
159
183
  return refs;
160
184
  }
161
185
  export async function optimizeImages(appDir, outDir) {
162
- const imageOutDir = resolve(outDir, 'static', 'images');
163
- mkdirSync(imageOutDir, { recursive: true });
186
+ // Variants must land where the emitted URLs actually resolve.
187
+ //
188
+ // `<Image src="/hero.png" />` renders `srcset="/hero-640w.png 640w, …"` —
189
+ // site-root-relative, with no directory prefix. The prod server serves the
190
+ // site root from `<outDir>/static/public`, so that is exactly where each
191
+ // variant belongs. They used to be written to `<outDir>/static/images`,
192
+ // which nothing serves: every `<Image>` srcset URL 404'd in a real build
193
+ // while the build itself reported success.
194
+ mkdirSync(resolve(outDir, 'static', 'public'), { recursive: true });
164
195
  const refs = collectImageRefs(appDir);
165
196
  if (refs.length === 0) {
166
197
  console.error('vesk images: no <Image> refs found');
@@ -198,6 +229,11 @@ export async function optimizeImages(appDir, outDir) {
198
229
  continue;
199
230
  }
200
231
  const baseName = ref.src.replace(/^\//, '').replace(extname(ref.src), '');
232
+ // `baseName` already carries the source's subdirectory (`/photos/hero.png`
233
+ // → `photos/hero`), and processImage mkdir's the parent of each output —
234
+ // so the destination stays the site root and the nesting is not doubled.
235
+ const imageOutDir = resolve(outDir, 'static', 'public');
236
+ mkdirSync(imageOutDir, { recursive: true });
201
237
  const files = await processImage(srcPath, imageOutDir, baseName);
202
238
  results.push({ src: ref.src, baseName, files, widths: OUTPUT_WIDTHS });
203
239
  console.error(`vesk images: ${ref.src} → ${files.length} variants`);
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Test helper exposing the srcset URL list that `collectImageRefs` /
3
+ * `optimizeImages` must make reachable.
4
+ *
5
+ * `<Image>`'s `generateSrcset` lives inside the runtime package's private
6
+ * scope, so the pipeline tests derive the same URLs here rather than
7
+ * duplicating the rule by hand in every assertion.
8
+ */
9
+ export declare function generateSrcsetForTest(src: string, widths: number[]): Array<{
10
+ url: string;
11
+ descriptor: string;
12
+ }>;
13
+ //# sourceMappingURL=image-pipeline.test-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-pipeline.test-helpers.d.ts","sourceRoot":"","sources":["../src/image-pipeline.test-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACpC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EAAE,GACd,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAK5C"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Test helper exposing the srcset URL list that `collectImageRefs` /
3
+ * `optimizeImages` must make reachable.
4
+ *
5
+ * `<Image>`'s `generateSrcset` lives inside the runtime package's private
6
+ * scope, so the pipeline tests derive the same URLs here rather than
7
+ * duplicating the rule by hand in every assertion.
8
+ */
9
+ export function generateSrcsetForTest(src, widths) {
10
+ if (!src || widths.length === 0)
11
+ return [];
12
+ const ext = src.lastIndexOf('.') > src.lastIndexOf('/') ? src.slice(src.lastIndexOf('.')) : '';
13
+ const base = ext ? src.slice(0, -ext.length) : src;
14
+ return widths.map(w => ({ url: `${base}-${w}w${ext}`, descriptor: `${w}w` }));
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vesk/adapter",
3
- "version": "0.2.66",
3
+ "version": "0.2.67",
4
4
  "type": "module",
5
5
  "description": "Vesk adapter — builds deployable .vesk/ output for Deno and Node serverless",
6
6
  "main": "./dist/index.js",
@@ -23,9 +23,9 @@
23
23
  "sharp": "^0.35.3"
24
24
  },
25
25
  "dependencies": {
26
- "@vesk/compiler": "^0.2.66",
27
- "@vesk/runtime": "^0.2.66",
28
- "@vesk/types": "^0.2.66"
26
+ "@vesk/compiler": "^0.2.67",
27
+ "@vesk/runtime": "^0.2.67",
28
+ "@vesk/types": "^0.2.67"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/ws": "^8.18.1",