Skip to content

Commit

Permalink
Add support for SVG images to  @astrojs/image (#6118)
Browse files Browse the repository at this point in the history
* @astrojs/image: add support for SVG images

* @astrojs/image: add tests for SVG images

* @astrojs/image: update README.md with SVG format info

* Add minor changeset for @astrojs/image
  • Loading branch information
ggounot authored Feb 15, 2023
1 parent 87e0c10 commit ac3649b
Show file tree
Hide file tree
Showing 12 changed files with 122 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/little-carrots-add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/image': minor
---

Add support for SVG images
5 changes: 4 additions & 1 deletion packages/integrations/image/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,16 @@ Set to an empty string (`alt=""`) if the image is not a key part of the content

<p>

**Type:** `'avif' | 'jpeg' | 'png' | 'webp'`<br>
**Type:** `'avif' | 'jpeg' | 'jpg' | 'png' | 'svg' | 'webp'`<br>
**Default:** `undefined`
</p>

The output format to be used in the optimized image. The original image format will be used if `format` is not provided.

This property is required for remote images when using the default image transformer Squoosh, this is because the original format cannot be inferred.

> When using the `svg` format, the original image must be in SVG format already (raster images cannot be converted to vector images). The SVG image itself won't be transformed but the final `<img />` element will get the optimization attributes.
#### quality

<p>
Expand Down
16 changes: 15 additions & 1 deletion packages/integrations/image/client.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
/// <reference types="astro/client-base" />

type InputFormat = 'avif' | 'gif' | 'heic' | 'heif' | 'jpeg' | 'jpg' | 'png' | 'tiff' | 'webp';
type InputFormat =
| 'avif'
| 'gif'
| 'heic'
| 'heif'
| 'jpeg'
| 'jpg'
| 'png'
| 'tiff'
| 'webp'
| 'svg';

interface ImageMetadata {
src: string;
Expand Down Expand Up @@ -46,3 +56,7 @@ declare module '*.webp' {
const metadata: ImageMetadata;
export default metadata;
}
declare module '*.svg' {
const metadata: ImageMetadata;
export default metadata;
}
7 changes: 4 additions & 3 deletions packages/integrations/image/src/loaders/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ export type InputFormat =
| 'png'
| 'tiff'
| 'webp'
| 'gif';
| 'gif'
| 'svg';

export type OutputFormatSupportsAlpha = 'avif' | 'png' | 'webp';
export type OutputFormat = OutputFormatSupportsAlpha | 'jpeg' | 'jpg';
export type OutputFormat = OutputFormatSupportsAlpha | 'jpeg' | 'jpg' | 'svg';

export type ColorDefinition =
| NamedColor
Expand Down Expand Up @@ -49,7 +50,7 @@ export type CropPosition =
| 'attention';

export function isOutputFormat(value: string): value is OutputFormat {
return ['avif', 'jpeg', 'jpg', 'png', 'webp'].includes(value);
return ['avif', 'jpeg', 'jpg', 'png', 'webp', 'svg'].includes(value);
}

export function isOutputFormatSupportsAlpha(value: string): value is OutputFormatSupportsAlpha {
Expand Down
8 changes: 8 additions & 0 deletions packages/integrations/image/src/loaders/sharp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ import type { OutputFormat, TransformOptions } from './index.js';

class SharpService extends BaseSSRService {
async transform(inputBuffer: Buffer, transform: TransformOptions) {
if (transform.format === 'svg') {
// sharp can't output SVG so we return the input image
return {
data: inputBuffer,
format: transform.format,
};
}

const sharpImage = sharp(inputBuffer, { failOnError: false, pages: -1 });

// always call rotate to adjust for EXIF data orientation
Expand Down
8 changes: 8 additions & 0 deletions packages/integrations/image/src/loaders/squoosh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ class SquooshService extends BaseSSRService {
}

async transform(inputBuffer: Buffer, transform: TransformOptions) {
if (transform.format === 'svg') {
// squoosh can't output SVG so we return the input image
return {
data: inputBuffer,
format: transform.format,
};
}

const operations: Operation[] = [];

if (!isRemoteImage(transform.src)) {
Expand Down
5 changes: 3 additions & 2 deletions packages/integrations/image/src/vite-plugin-astro-image.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { AstroConfig } from 'astro';
import MagicString from 'magic-string';
import mime from 'mime';
import fs from 'node:fs/promises';
import { basename, extname } from 'node:path';
import { Readable } from 'node:stream';
Expand All @@ -18,7 +19,7 @@ export interface ImageMetadata {

export function createPlugin(config: AstroConfig, options: Required<IntegrationOptions>): Plugin {
const filter = (id: string) =>
/^(?!\/_image?).*.(heic|heif|avif|jpeg|jpg|png|tiff|webp|gif)$/.test(id);
/^(?!\/_image?).*.(heic|heif|avif|jpeg|jpg|png|tiff|webp|gif|svg)$/.test(id);

const virtualModuleId = 'virtual:image-loader';

Expand Down Expand Up @@ -97,7 +98,7 @@ export function createPlugin(config: AstroConfig, options: Required<IntegrationO
format = result.format;
}

res.setHeader('Content-Type', `image/${format}`);
res.setHeader('Content-Type', mime.getType(format) || '');
res.setHeader('Cache-Control', 'max-age=360000');

const stream = Readable.from(data);
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
import socialJpg from '../assets/social.jpg';
import logoSvg from '../assets/logo.svg';
import introJpg from '../assets/blog/introducing astro.jpg';
import outsideSrc from '../../social.png';
import { Image } from '@astrojs/image/components';
Expand All @@ -21,6 +22,8 @@ const publicImage = new URL('./hero.jpg', Astro.url);
<br />
<Image id="outside-src" src={outsideSrc} alt="outside-src" />
<br />
<Image id="logo-svg" src={logoSvg} alt="logo-svg" />
<br />
<Image id="google" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" width={544} height={184} format="webp" alt="Google" />
<br />
<Image id="inline" src={import('../assets/social.jpg')} width={506} alt="inline" />
Expand Down
24 changes: 24 additions & 0 deletions packages/integrations/image/test/image-ssg.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ describe('SSG images - dev', function () {
query: { f: 'png', w: '2024', h: '1012' },
contentType: 'image/png',
},
{
title: 'SVG image',
id: '#logo-svg',
url: toAstroImage('src/assets/logo.svg'),
query: { f: 'svg', w: '192', h: '256' },
},
{
title: 'Inline imports',
id: '#inline',
Expand Down Expand Up @@ -157,6 +163,12 @@ describe('SSG images with subpath - dev', function () {
query: { f: 'png', w: '2024', h: '1012' },
contentType: 'image/png',
},
{
title: 'SVG image',
id: '#logo-svg',
url: toAstroImage('src/assets/logo.svg'),
query: { f: 'svg', w: '192', h: '256' },
},
{
title: 'Inline imports',
id: '#inline',
Expand Down Expand Up @@ -263,6 +275,12 @@ describe('SSG images - build', function () {
regex: /^\/_astro\/social.\w{8}_\w{4,10}.png/,
size: { type: 'png', width: 2024, height: 1012 },
},
{
title: 'SVG image',
id: '#logo-svg',
regex: /^\/_astro\/logo.\w{8}_\w{4,10}.svg/,
size: { width: 192, height: 256, type: 'svg' },
},
{
title: 'Inline imports',
id: '#inline',
Expand Down Expand Up @@ -351,6 +369,12 @@ describe('SSG images with subpath - build', function () {
regex: /^\/docs\/_astro\/social.\w{8}_\w{4,10}.png/,
size: { type: 'png', width: 2024, height: 1012 },
},
{
title: 'SVG image',
id: '#logo-svg',
regex: /^\/docs\/_astro\/logo.\w{8}_\w{4,10}.svg/,
size: { width: 192, height: 256, type: 'svg' },
},
{
title: 'Inline imports',
id: '#inline',
Expand Down
12 changes: 12 additions & 0 deletions packages/integrations/image/test/image-ssr-build.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ describe('SSR images - build', async function () {
url: '/_image',
query: { f: 'webp', w: '768', h: '414', href: /^\/_astro\/introducing astro.\w{8}.jpg/ },
},
{
title: 'SVG image',
id: '#logo-svg',
url: '/_image',
query: { f: 'svg', w: '192', h: '256', href: /^\/_astro\/logo.\w{8}.svg/ },
},
{
title: 'Inline imports',
id: '#inline',
Expand Down Expand Up @@ -144,6 +150,12 @@ describe('SSR images with subpath - build', function () {
href: /^\/docs\/_astro\/introducing astro.\w{8}.jpg/,
},
},
{
title: 'SVG image',
id: '#logo-svg',
url: '/_image',
query: { f: 'svg', w: '192', h: '256', href: /^\/docs\/_astro\/logo.\w{8}.svg/ },
},
{
title: 'Inline imports',
id: '#inline',
Expand Down
14 changes: 14 additions & 0 deletions packages/integrations/image/test/image-ssr-dev.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ describe('SSR images - dev', function () {
query: { f: 'png', w: '2024', h: '1012' },
contentType: 'image/png',
},
{
title: 'SVG image',
id: '#logo-svg',
url: toAstroImage('src/assets/logo.svg'),
query: { f: 'svg', w: '192', h: '256' },
contentType: 'image/svg+xml',
},
{
title: 'Inline imports',
id: '#inline',
Expand Down Expand Up @@ -181,6 +188,13 @@ describe('SSR images with subpath - dev', function () {
query: { f: 'png', w: '2024', h: '1012' },
contentType: 'image/png',
},
{
title: 'SVG image',
id: '#logo-svg',
url: toAstroImage('src/assets/logo.svg'),
query: { f: 'svg', w: '192', h: '256' },
contentType: 'image/svg+xml',
},
{
title: 'Inline imports',
id: '#inline',
Expand Down

0 comments on commit ac3649b

Please sign in to comment.