Skip to content

Commit d0c50c8

Browse files
committed
fix(docs): find image sizes for query URLs, paths with spaces and read errors
Three gaps in the image sizes from #443, found in its review: - A src with a query or fragment (`/a.png?v=2`) missed its size; the lookup now uses the path. - The public directory came from `URL.pathname`, which keeps `%20` for a space, so a checkout path with a space found no images. It now comes from `import.meta.dirname`. - Any error while reading the images was swallowed, so the build could emit images without sizes. Only a missing directory is skipped now.
1 parent 678b965 commit d0c50c8

5 files changed

Lines changed: 34 additions & 12 deletions

File tree

‎apps/docs/image-sizes.spec.ts‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {mkdirSync, mkdtempSync, writeFileSync} from 'node:fs';
1+
import {mkdirSync, mkdtempSync, symlinkSync, writeFileSync} from 'node:fs';
22
import {tmpdir} from 'node:os';
33
import {join} from 'node:path';
44
import {imageSize, readImageSizes} from './image-sizes';
@@ -42,6 +42,22 @@ describe('imageSize', () => {
4242
});
4343

4444
describe('readImageSizes', () => {
45+
it('reads a directory whose path has a space', () => {
46+
const dir = mkdtempSync(join(tmpdir(), 'image sizes '));
47+
writeFileSync(join(dir, 'a.png'), png(2, 1));
48+
expect(readImageSizes(dir)).toEqual({'/a.png': [2, 1]});
49+
});
50+
51+
it('returns nothing when there is no public directory', () => {
52+
expect(readImageSizes(join(tmpdir(), 'image-sizes-missing-dir'))).toEqual({});
53+
});
54+
55+
it('fails when an image cannot be read, instead of leaving it out', () => {
56+
const dir = mkdtempSync(join(tmpdir(), 'image-sizes-'));
57+
symlinkSync(join(dir, 'gone.png'), join(dir, 'broken.png'));
58+
expect(() => readImageSizes(dir)).toThrow(/ENOENT/);
59+
});
60+
4561
it('keys the sizes by URL path', () => {
4662
const dir = mkdtempSync(join(tmpdir(), 'image-sizes-'));
4763
mkdirSync(join(dir, 'images/panel'), {recursive: true});

‎apps/docs/image-sizes.ts‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {readFileSync, readdirSync, statSync} from 'node:fs';
1+
import {existsSync, readFileSync, readdirSync, statSync} from 'node:fs';
22
import {join, relative, sep} from 'node:path';
33

44
/**
@@ -9,7 +9,11 @@ import {join, relative, sep} from 'node:path';
99
*/
1010
export type ImageSizes = Record<string, [number, number]>;
1111

12-
/** The sizes of the PNG and JPEG files under `dir`, keyed by their URL path. */
12+
/**
13+
* The sizes of the PNG and JPEG files under `dir`, keyed by their URL path.
14+
* A missing `dir` gives none; a file that cannot be read fails the build
15+
* rather than leaving its image without a size.
16+
*/
1317
export function readImageSizes(dir: string): ImageSizes {
1418
const sizes: ImageSizes = {};
1519
const walk = (current: string) => {
@@ -22,11 +26,7 @@ export function readImageSizes(dir: string): ImageSizes {
2226
}
2327
}
2428
};
25-
try {
26-
walk(dir);
27-
} catch {
28-
// no public/ directory
29-
}
29+
if (existsSync(dir)) walk(dir);
3030
return sizes;
3131
}
3232

‎apps/docs/src/marked-extensions/index.ts‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,7 @@ export async function getBuildExtensions(): Promise<MarkedExtension[]> {
3737
]);
3838
return [
3939
// The pages are rendered here, so `<ngmd-image>` gets the sizes of the files in public/.
40-
...createNgmdRuntimeExtensions(
41-
readImageSizes(new URL('../../public', import.meta.url).pathname),
42-
),
40+
...createNgmdRuntimeExtensions(readImageSizes(`${import.meta.dirname}/../../public`)),
4341
ngmdCodeImportExtension,
4442
ngmdCodeGroupExtension,
4543
ngmdCodeHighlightExtension,

‎apps/docs/src/marked-extensions/marked-extensions.spec.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ describe('ngmd-image', () => {
2525
expect(html).toContain('<figcaption>Tom &amp; Jerry</figcaption>');
2626
});
2727

28+
it('finds the size of an image whose src has a query or fragment', () => {
29+
const html = new Marked({
30+
extensions: [createNgmdImageExtension({'/images/a.png': [1280, 800]})],
31+
}).parse('<ngmd-image src="/images/a.png?v=2#x" alt="A"></ngmd-image>') as string;
32+
expect(html).toContain('<img src="/images/a.png?v=2#x" alt="A" width="1280" height="800"');
33+
});
34+
2835
it('gives the image its width and height when its size is known', () => {
2936
const html = new Marked({
3037
extensions: [createNgmdImageExtension({'/images/a.png': [1280, 800]})],

‎apps/docs/src/marked-extensions/ngmd-image.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ export function createNgmdImageExtension(sizes: NgmdImageSizes = {}) {
5050
const width = token.width?.trim() ?? '';
5151
const maxWidth = /^\d+(\.\d+)?$/.test(width) ? `${width}px` : width;
5252
const style = maxWidth ? ` style="max-width: ${escapeHtml(maxWidth)}"` : '';
53-
const size = sizes[token.src];
53+
// A query or fragment (`/a.png?v=2`) still names the file `/a.png`.
54+
const size = sizes[token.src.split(/[?#]/)[0]];
5455
const dimensions = size ? ` width="${size[0]}" height="${size[1]}"` : '';
5556
const caption = token.caption ? `<figcaption>${escapeHtml(token.caption)}</figcaption>` : '';
5657
return `<figure class="ngmd-image"${style}><img src="${escapeHtml(token.src)}" alt="${escapeHtml(token.alt)}"${dimensions} loading="lazy" decoding="async">${caption}</figure>`;

0 commit comments

Comments
 (0)