All files / src/lib rehype-image-dimensions.mjs

0% Statements 0/0
0% Branches 0/0
0% Functions 0/0
0% Lines 0/0

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84                                                                                                                                                                       
import { visit } from 'unist-util-visit';
import sharp from 'sharp';
import path from 'node:path';
import { existsSync } from 'node:fs';
 
/** In-memory cache: imagePath -> { width, height } */
const dimensionCache = new Map();
 
/**
 * Resolve a local src (e.g. `/images/blog/foo.png`) to an absolute file path.
 */
function resolveImagePath(src) {
  return path.join(process.cwd(), 'static', src);
}
 
/**
 * Get image dimensions via sharp, with caching.
 * Returns { width, height } or null on failure.
 */
async function getImageDimensions(filePath) {
  if (dimensionCache.has(filePath)) {
    return dimensionCache.get(filePath);
  }
 
  try {
    const metadata = await sharp(filePath).metadata();
    const dims = { width: metadata.width, height: metadata.height };
    dimensionCache.set(filePath, dims);
    return dims;
  } catch (err) {
    // Cache the failure too so we don't retry
    dimensionCache.set(filePath, null);
    return null;
  }
}
 
export default function rehypeImageDimensions() {
  return async (tree) => {
    const imageNodes = [];
 
    visit(tree, 'element', (node) => {
      if (node.tagName !== 'img') return;
 
      const src = node.properties?.src;
      if (!src) return;
 
      // Only process local images starting with /images/
      if (!src.startsWith('/images/')) return;
 
      // Skip if width and height are already set
      if (node.properties.width && node.properties.height) return;
 
      imageNodes.push(node);
    });
 
    // Process all images in parallel
    await Promise.all(
      imageNodes.map(async (node) => {
        const src = node.properties.src;
        const filePath = resolveImagePath(src);
 
        if (!existsSync(filePath)) {
          console.warn(
            `[rehype-image-dimensions] File not found, skipping: ${filePath}`
          );
          return;
        }
 
        const dims = await getImageDimensions(filePath);
 
        if (!dims || !dims.width || !dims.height) {
          console.warn(
            `[rehype-image-dimensions] Could not read dimensions, skipping: ${filePath}`
          );
          return;
        }
 
        node.properties.width = String(dims.width);
        node.properties.height = String(dims.height);
      })
    );
  };
}