astro/packages/integrations/image/src/loaders/sharp.ts
Tony Sullivan 00c605ce35
Image integration refactor and cleanup (#4482)
* WIP: simplifying the use of `fs` vs. the vite plugin

* removing a few node deps (etag and node:path)

* adding ts defs for sharp

* using the same mime package as astro's core App

* fixing file URL support in windows

* using file URLs when loading local image metadata

* fixing a bug in the etag helper

* Windows compat

* splitting out dev & build tests

* why do these suites fail in parallel?

* one last windows compat case

* Adding tests for treating /public images the same as remote URLs

* a couple fixes for Astro's `base` config

* adding base path tests for SSR

* fixing a bad merge, lost the kleur dependency

* adding a test suite for images + MDX

* chore: add changeset

* simplifying the with-mdx tests

* bugfix: don't duplicate the period when using existing file extensions

* let Vite cache the image loader service

* adding some docs for using /public images

* fixing changeset

* Update packages/integrations/image/README.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* Update packages/integrations/image/README.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* nit: minor README syntax tweaks

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
2022-08-30 21:09:44 +00:00

105 lines
2.5 KiB
TypeScript

import sharp from 'sharp';
import { isAspectRatioString, isOutputFormat } from '../loaders/index.js';
import type { OutputFormat, SSRImageService, TransformOptions } from './index.js';
class SharpService implements SSRImageService {
async getImageAttributes(transform: TransformOptions) {
// strip off the known attributes
const { width, height, src, format, quality, aspectRatio, ...rest } = transform;
return {
...rest,
width: width,
height: height,
};
}
serializeTransform(transform: TransformOptions) {
const searchParams = new URLSearchParams();
if (transform.quality) {
searchParams.append('q', transform.quality.toString());
}
if (transform.format) {
searchParams.append('f', transform.format);
}
if (transform.width) {
searchParams.append('w', transform.width.toString());
}
if (transform.height) {
searchParams.append('h', transform.height.toString());
}
if (transform.aspectRatio) {
searchParams.append('ar', transform.aspectRatio.toString());
}
return { searchParams };
}
parseTransform(searchParams: URLSearchParams) {
let transform: TransformOptions = { src: searchParams.get('href')! };
if (searchParams.has('q')) {
transform.quality = parseInt(searchParams.get('q')!);
}
if (searchParams.has('f')) {
const format = searchParams.get('f')!;
if (isOutputFormat(format)) {
transform.format = format;
}
}
if (searchParams.has('w')) {
transform.width = parseInt(searchParams.get('w')!);
}
if (searchParams.has('h')) {
transform.height = parseInt(searchParams.get('h')!);
}
if (searchParams.has('ar')) {
const ratio = searchParams.get('ar')!;
if (isAspectRatioString(ratio)) {
transform.aspectRatio = ratio;
} else {
transform.aspectRatio = parseFloat(ratio);
}
}
return transform;
}
async transform(inputBuffer: Buffer, transform: TransformOptions) {
const sharpImage = sharp(inputBuffer, { failOnError: false, pages: -1 });
// always call rotate to adjust for EXIF data orientation
sharpImage.rotate();
if (transform.width || transform.height) {
const width = transform.width && Math.round(transform.width);
const height = transform.height && Math.round(transform.height);
sharpImage.resize(width, height);
}
if (transform.format) {
sharpImage.toFormat(transform.format, { quality: transform.quality });
}
const { data, info } = await sharpImage.toBuffer({ resolveWithObject: true });
return {
data,
format: info.format as OutputFormat,
};
}
}
const service = new SharpService();
export default service;