summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Gérald Gounot <gerald@gounot.eu> 2023-02-15 09:21:41 +0100
committerGravatar GitHub <noreply@github.com> 2023-02-15 09:21:41 +0100
commitac3649bb589a3ee1deab4ba73c06a36a45e2cee5 (patch)
tree1554b164878becfabd58c217688a206531959cca
parent87e0c10365528bf6b328e88d2d56d7ac15c14a2a (diff)
downloadastro-ac3649bb589a3ee1deab4ba73c06a36a45e2cee5.tar.gz
astro-ac3649bb589a3ee1deab4ba73c06a36a45e2cee5.tar.zst
astro-ac3649bb589a3ee1deab4ba73c06a36a45e2cee5.zip
Add support for SVG images to  `@astrojs/image` (#6118)
* @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
-rw-r--r--.changeset/little-carrots-add.md5
-rw-r--r--packages/integrations/image/README.md5
-rw-r--r--packages/integrations/image/client.d.ts16
-rw-r--r--packages/integrations/image/src/loaders/index.ts7
-rw-r--r--packages/integrations/image/src/loaders/sharp.ts8
-rw-r--r--packages/integrations/image/src/loaders/squoosh.ts8
-rw-r--r--packages/integrations/image/src/vite-plugin-astro-image.ts5
-rw-r--r--packages/integrations/image/test/fixtures/basic-image/src/assets/logo.svg22
-rw-r--r--packages/integrations/image/test/fixtures/basic-image/src/pages/index.astro3
-rw-r--r--packages/integrations/image/test/image-ssg.test.js24
-rw-r--r--packages/integrations/image/test/image-ssr-build.test.js12
-rw-r--r--packages/integrations/image/test/image-ssr-dev.test.js14
12 files changed, 122 insertions, 7 deletions
diff --git a/.changeset/little-carrots-add.md b/.changeset/little-carrots-add.md
new file mode 100644
index 000000000..433635158
--- /dev/null
+++ b/.changeset/little-carrots-add.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/image': minor
+---
+
+Add support for SVG images
diff --git a/packages/integrations/image/README.md b/packages/integrations/image/README.md
index 1cb0d5d4c..d1cc22f2c 100644
--- a/packages/integrations/image/README.md
+++ b/packages/integrations/image/README.md
@@ -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>
diff --git a/packages/integrations/image/client.d.ts b/packages/integrations/image/client.d.ts
index cafec4184..71842742a 100644
--- a/packages/integrations/image/client.d.ts
+++ b/packages/integrations/image/client.d.ts
@@ -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;
@@ -46,3 +56,7 @@ declare module '*.webp' {
const metadata: ImageMetadata;
export default metadata;
}
+declare module '*.svg' {
+ const metadata: ImageMetadata;
+ export default metadata;
+}
diff --git a/packages/integrations/image/src/loaders/index.ts b/packages/integrations/image/src/loaders/index.ts
index 280fb37c2..225e98cee 100644
--- a/packages/integrations/image/src/loaders/index.ts
+++ b/packages/integrations/image/src/loaders/index.ts
@@ -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
@@ -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 {
diff --git a/packages/integrations/image/src/loaders/sharp.ts b/packages/integrations/image/src/loaders/sharp.ts
index 55ea28645..517224289 100644
--- a/packages/integrations/image/src/loaders/sharp.ts
+++ b/packages/integrations/image/src/loaders/sharp.ts
@@ -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
diff --git a/packages/integrations/image/src/loaders/squoosh.ts b/packages/integrations/image/src/loaders/squoosh.ts
index 5d71cdb7f..a5be16adb 100644
--- a/packages/integrations/image/src/loaders/squoosh.ts
+++ b/packages/integrations/image/src/loaders/squoosh.ts
@@ -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)) {
diff --git a/packages/integrations/image/src/vite-plugin-astro-image.ts b/packages/integrations/image/src/vite-plugin-astro-image.ts
index 3eee310e8..b721578a5 100644
--- a/packages/integrations/image/src/vite-plugin-astro-image.ts
+++ b/packages/integrations/image/src/vite-plugin-astro-image.ts
@@ -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';
@@ -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';
@@ -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);
diff --git a/packages/integrations/image/test/fixtures/basic-image/src/assets/logo.svg b/packages/integrations/image/test/fixtures/basic-image/src/assets/logo.svg
new file mode 100644
index 000000000..a1388d931
--- /dev/null
+++ b/packages/integrations/image/test/fixtures/basic-image/src/assets/logo.svg
@@ -0,0 +1,22 @@
+<svg width="192" height="256" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path fill-rule="evenodd" clip-rule="evenodd"
+ d="M131.008 18.929c1.944 2.413 2.935 5.67 4.917 12.181l43.309 142.27a180.277 180.277 0 00-51.778-17.53L99.258 60.56a3.67 3.67 0 00-7.042.01l-27.857 95.232a180.225 180.225 0 00-52.01 17.557l43.52-142.281c1.99-6.502 2.983-9.752 4.927-12.16a15.999 15.999 0 016.484-4.798c2.872-1.154 6.271-1.154 13.07-1.154h31.085c6.807 0 10.211 0 13.086 1.157a16.004 16.004 0 016.487 4.806z"
+ fill="url(#paint0_linear)" />
+ <path fill-rule="evenodd" clip-rule="evenodd"
+ d="M136.19 180.151c-7.139 6.105-21.39 10.268-37.804 10.268-20.147 0-37.033-6.272-41.513-14.707-1.602 4.835-1.961 10.367-1.961 13.902 0 0-1.056 17.355 11.015 29.426 0-6.268 5.081-11.349 11.35-11.349 10.742 0 10.73 9.373 10.72 16.977v.679c0 11.542 7.054 21.436 17.086 25.606a23.27 23.27 0 01-2.339-10.2c0-11.008 6.463-15.107 13.974-19.87 5.976-3.79 12.616-8.001 17.192-16.449a31.024 31.024 0 003.743-14.82c0-3.299-.513-6.479-1.463-9.463z"
+ fill="#FF5D01" />
+ <path fill-rule="evenodd" clip-rule="evenodd"
+ d="M136.19 180.151c-7.139 6.105-21.39 10.268-37.804 10.268-20.147 0-37.033-6.272-41.513-14.707-1.602 4.835-1.961 10.367-1.961 13.902 0 0-1.056 17.355 11.015 29.426 0-6.268 5.081-11.349 11.35-11.349 10.742 0 10.73 9.373 10.72 16.977v.679c0 11.542 7.054 21.436 17.086 25.606a23.27 23.27 0 01-2.339-10.2c0-11.008 6.463-15.107 13.974-19.87 5.976-3.79 12.616-8.001 17.192-16.449a31.024 31.024 0 003.743-14.82c0-3.299-.513-6.479-1.463-9.463z"
+ fill="url(#paint1_linear)" />
+ <defs>
+ <linearGradient id="paint0_linear" x1="144.599" y1="5.423" x2="95.791" y2="173.38" gradientUnits="userSpaceOnUse">
+ <stop stop-color="#000014" />
+ <stop offset="1" stop-color="#150426" />
+ </linearGradient>
+ <linearGradient id="paint1_linear" x1="168.336" y1="130.49" x2="126.065" y2="218.982"
+ gradientUnits="userSpaceOnUse">
+ <stop stop-color="#FF1639" />
+ <stop offset="1" stop-color="#FF1639" stop-opacity="0" />
+ </linearGradient>
+ </defs>
+</svg>
diff --git a/packages/integrations/image/test/fixtures/basic-image/src/pages/index.astro b/packages/integrations/image/test/fixtures/basic-image/src/pages/index.astro
index ba492576c..ed1d79db6 100644
--- a/packages/integrations/image/test/fixtures/basic-image/src/pages/index.astro
+++ b/packages/integrations/image/test/fixtures/basic-image/src/pages/index.astro
@@ -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';
@@ -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" />
diff --git a/packages/integrations/image/test/image-ssg.test.js b/packages/integrations/image/test/image-ssg.test.js
index 5bc1c1e0d..12b3ffea9 100644
--- a/packages/integrations/image/test/image-ssg.test.js
+++ b/packages/integrations/image/test/image-ssg.test.js
@@ -53,6 +53,12 @@ describe('SSG images - dev', function () {
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',
url: toAstroImage('src/assets/social.jpg'),
@@ -158,6 +164,12 @@ describe('SSG images with subpath - dev', function () {
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',
url: toAstroImage('src/assets/social.jpg'),
@@ -264,6 +276,12 @@ describe('SSG images - build', function () {
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',
regex: /^\/_astro\/social.\w{8}_\w{4,10}.jpg/,
@@ -352,6 +370,12 @@ describe('SSG images with subpath - build', function () {
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',
regex: /^\/docs\/_astro\/social.\w{8}_\w{4,10}.jpg/,
diff --git a/packages/integrations/image/test/image-ssr-build.test.js b/packages/integrations/image/test/image-ssr-build.test.js
index 4b985c0ad..f85373c27 100644
--- a/packages/integrations/image/test/image-ssr-build.test.js
+++ b/packages/integrations/image/test/image-ssr-build.test.js
@@ -29,6 +29,12 @@ describe('SSR images - build', async function () {
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',
url: '/_image',
@@ -145,6 +151,12 @@ describe('SSR images with subpath - build', function () {
},
},
{
+ 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',
url: '/_image',
diff --git a/packages/integrations/image/test/image-ssr-dev.test.js b/packages/integrations/image/test/image-ssr-dev.test.js
index fbaa6f965..186100b12 100644
--- a/packages/integrations/image/test/image-ssr-dev.test.js
+++ b/packages/integrations/image/test/image-ssr-dev.test.js
@@ -60,6 +60,13 @@ describe('SSR images - dev', function () {
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',
url: toAstroImage('src/assets/social.jpg'),
@@ -182,6 +189,13 @@ describe('SSR images with subpath - dev', function () {
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',
url: toAstroImage('src/assets/social.jpg'),