mirror of
https://github.com/withastro/astro.git
synced 2024-12-23 21:53:55 -05:00
@astrojs/image: add a background
option/prop to replace the alpha layer (#4642)
* Added `background` option and prop. This optional color specifies which background to use when removing the alpha channel if the output format doesn't support transparency. * Modified existing tests * Fixed wrong dimensions in tests * Fixing a few instances of jpeg vs jpg * Added color checking * working on the tests * tests are now passing * Adding tests * Added tests for background color * no need to test with subpath * Added fixture * Renamed test fixture for background-color * skipping test until fixed * Typo * Working on tests * tests are passing * Updated readme and added changeset * Updated lockfile * Updated lockfile * Updated lockfile Co-authored-by: Tony Sullivan <tony.f.sullivan@outlook.com>
This commit is contained in:
parent
93c3aee01c
commit
e4348a4eb4
28 changed files with 1623 additions and 1012 deletions
5
.changeset/eleven-baboons-try.md
Normal file
5
.changeset/eleven-baboons-try.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@astrojs/image': minor
|
||||
---
|
||||
|
||||
Added a `background` option to specify a background color to replace transparent pixels (alpha layer).
|
|
@ -24,9 +24,9 @@ This integration provides `<Image />` and `<Picture>` components as well as a ba
|
|||
|
||||
|
||||
### Quick Install
|
||||
|
||||
|
||||
The `astro add` command-line tool automates the installation for you. Run one of the following commands in a new terminal window. (If you aren't sure which package manager you're using, run the first command.) Then, follow the prompts, and type "y" in the terminal (meaning "yes") for each one.
|
||||
|
||||
|
||||
```sh
|
||||
# Using NPM
|
||||
npx astro add image
|
||||
|
@ -35,13 +35,13 @@ yarn astro add image
|
|||
# Using PNPM
|
||||
pnpm astro add image
|
||||
```
|
||||
|
||||
|
||||
Finally, in the terminal window running Astro, press `CTRL+C` and then restart the dev server.
|
||||
|
||||
If you run into any issues, [feel free to report them to us on GitHub](https://github.com/withastro/astro/issues) and try the manual installation steps below.
|
||||
|
||||
### Manual Install
|
||||
|
||||
|
||||
First, install the `@astrojs/image` package using your package manager. If you're using npm or aren't sure, run this in the terminal:
|
||||
```sh
|
||||
npm install @astrojs/image
|
||||
|
@ -57,7 +57,7 @@ export default {
|
|||
// ...
|
||||
integrations: [image()],
|
||||
}
|
||||
```
|
||||
```
|
||||
Then, restart the dev server.
|
||||
|
||||
### Update `env.d.ts`
|
||||
|
@ -190,6 +190,24 @@ A `string` can be provided in the form of `{width}:{height}`, ex: `16:9` or `3:4
|
|||
|
||||
A `number` can also be provided, useful when the aspect ratio is calculated at build time. This can be an inline number such as `1.777` or inlined as a JSX expression like `aspectRatio={16/9}`.
|
||||
|
||||
#### background
|
||||
|
||||
<p>
|
||||
|
||||
**Type:** `ColorDefinition`<br>
|
||||
**Default:** `undefined`
|
||||
</p>
|
||||
|
||||
The background color to use for replacing the alpha channel with `sharp`'s `flatten` method. In case the output format
|
||||
doesn't support transparency (i.e. `jpeg`), it's advisable to include a background color, otherwise black will be used
|
||||
as default replacement for transparent pixels.
|
||||
|
||||
The parameter accepts a `string` as value.
|
||||
|
||||
The parameter can be a [named HTML color](https://www.w3schools.com/tags/ref_colornames.asp), a hexadecimal
|
||||
color representation with 3 or 6 hexadecimal characters in the form `#123[abc]`, or an RGB definition in the form
|
||||
`rgb(100,100,100)`.
|
||||
|
||||
### `<Picture /`>
|
||||
|
||||
#### src
|
||||
|
@ -271,6 +289,24 @@ A `number` can also be provided, useful when the aspect ratio is calculated at b
|
|||
|
||||
The output formats to be used in the optimized image. If not provided, `webp` and `avif` will be used in addition to the original image format.
|
||||
|
||||
#### background
|
||||
|
||||
<p>
|
||||
|
||||
**Type:** `ColorDefinition`<br>
|
||||
**Default:** `undefined`
|
||||
</p>
|
||||
|
||||
The background color to use for replacing the alpha channel with `sharp`'s `flatten` method. In case the output format
|
||||
doesn't support transparency (i.e. `jpeg`), it's advisable to include a background color, otherwise black will be used
|
||||
as default replacement for transparent pixels.
|
||||
|
||||
The parameter accepts a `string` as value.
|
||||
|
||||
The parameter can be a [named HTML color](https://www.w3schools.com/tags/ref_colornames.asp), a hexadecimal
|
||||
color representation with 3 or 6 hexadecimal characters in the form `#123[abc]`, or an RGB definition in the form
|
||||
`rgb(100,100,100)`.
|
||||
|
||||
### `getImage`
|
||||
|
||||
This is the helper function used by the `<Image />` component to build `<img />` attributes for the transformed image. This helper can be used directly for more complex use cases that aren't currently supported by the `<Image />` component.
|
||||
|
@ -307,7 +343,7 @@ The integration can be configured to run with a different image service, either
|
|||
|
||||
|
||||
### config.serviceEntryPoint
|
||||
|
||||
|
||||
The `serviceEntryPoint` should resolve to the image service installed from NPM. The default entry point is `@astrojs/image/sharp`, which resolves to the entry point exported from this integration's `package.json`.
|
||||
|
||||
```js
|
||||
|
@ -342,7 +378,7 @@ export default {
|
|||
## Examples
|
||||
|
||||
### Local images
|
||||
|
||||
|
||||
Image files in your project's `src` directory can be imported in frontmatter and passed directly to the `<Image />` component. All other properties are optional and will default to the original image file's properties if not provided.
|
||||
|
||||
```astro
|
||||
|
@ -371,7 +407,7 @@ import heroImage from '../assets/hero.png';
|
|||
|
||||
Files in the `/public` directory are always served or copied as-is, with no processing. We recommend that local images are always kept in `src/` so that Astro can transform, optimize and bundle them. But if you absolutely must keep an image in `public/`, use its relative URL path as the image's `src=` attribute. It will be treated as a remote image, which requires an `aspectRatio` attribute.
|
||||
|
||||
Alternatively, you can import an image from your `public/` directory in your frontmatter and use a variable in your `src=` attribute. You cannot, however, import this directly inside the component as its `src` value.
|
||||
Alternatively, you can import an image from your `public/` directory in your frontmatter and use a variable in your `src=` attribute. You cannot, however, import this directly inside the component as its `src` value.
|
||||
|
||||
For example, use an image located at `public/social.png` in either static or SSR builds like so:
|
||||
|
||||
|
@ -386,7 +422,7 @@ import socialImage from '/social.png';
|
|||
```
|
||||
|
||||
### Remote images
|
||||
|
||||
|
||||
Remote images can be transformed with the `<Image />` component. The `<Image />` component needs to know the final dimensions for the `<img />` element to avoid content layout shifts. For remote images, this means you must either provide `width` and `height`, or one of the dimensions plus the required `aspectRatio`.
|
||||
|
||||
```astro
|
||||
|
|
|
@ -27,6 +27,7 @@ interface RemoteImageProps
|
|||
widths: number[];
|
||||
aspectRatio: TransformOptions['aspectRatio'];
|
||||
formats?: OutputFormat[];
|
||||
background: TransformOptions['background'];
|
||||
}
|
||||
|
||||
export type Props = LocalImageProps | RemoteImageProps;
|
||||
|
@ -37,6 +38,7 @@ const {
|
|||
sizes,
|
||||
widths,
|
||||
aspectRatio,
|
||||
background,
|
||||
formats = ['avif', 'webp'],
|
||||
loading = 'lazy',
|
||||
decoding = 'async',
|
||||
|
@ -47,7 +49,7 @@ if (alt === undefined || alt === null) {
|
|||
warnForMissingAlt();
|
||||
}
|
||||
|
||||
const { image, sources } = await getPicture({ src, widths, formats, aspectRatio });
|
||||
const { image, sources } = await getPicture({ src, widths, formats, aspectRatio, background });
|
||||
---
|
||||
|
||||
<picture {...attrs}>
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
/// <reference types="astro/astro-jsx" />
|
||||
import type { ImageService, OutputFormat, TransformOptions } from '../loaders/index.js';
|
||||
import type {
|
||||
ColorDefinition,
|
||||
ImageService,
|
||||
OutputFormat,
|
||||
TransformOptions,
|
||||
} from '../loaders/index.js';
|
||||
import { isSSRService, parseAspectRatio } from '../loaders/index.js';
|
||||
import sharp from '../loaders/sharp.js';
|
||||
import { isRemoteImage } from '../utils/paths.js';
|
||||
|
@ -63,7 +68,7 @@ async function resolveTransform(input: GetImageTransform): Promise<TransformOpti
|
|||
// resolve the metadata promise, usually when the ESM import is inlined
|
||||
const metadata = 'then' in input.src ? (await input.src).default : input.src;
|
||||
|
||||
let { width, height, aspectRatio, format = metadata.format, ...rest } = input;
|
||||
let { width, height, aspectRatio, background, format = metadata.format, ...rest } = input;
|
||||
|
||||
if (!width && !height) {
|
||||
// neither dimension was provided, use the file metadata
|
||||
|
@ -86,6 +91,7 @@ async function resolveTransform(input: GetImageTransform): Promise<TransformOpti
|
|||
height,
|
||||
aspectRatio,
|
||||
format: format as OutputFormat,
|
||||
background: background as ColorDefinition | undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ export interface GetPictureParams {
|
|||
widths: number[];
|
||||
formats: OutputFormat[];
|
||||
aspectRatio?: TransformOptions['aspectRatio'];
|
||||
background?: TransformOptions['background'];
|
||||
}
|
||||
|
||||
export interface GetPictureResult {
|
||||
|
@ -64,6 +65,7 @@ export async function getPicture(params: GetPictureParams): Promise<GetPictureRe
|
|||
format,
|
||||
width,
|
||||
height: Math.round(width / aspectRatio!),
|
||||
background: params.background,
|
||||
});
|
||||
return `${img.src} ${width}w`;
|
||||
})
|
||||
|
@ -83,6 +85,7 @@ export async function getPicture(params: GetPictureParams): Promise<GetPictureRe
|
|||
width: Math.max(...widths),
|
||||
aspectRatio,
|
||||
format: allFormats[allFormats.length - 1],
|
||||
background: params.background,
|
||||
});
|
||||
|
||||
const sources = await Promise.all(allFormats.map((format) => getSource(format)));
|
||||
|
|
290
packages/integrations/image/src/loaders/colornames.ts
Normal file
290
packages/integrations/image/src/loaders/colornames.ts
Normal file
|
@ -0,0 +1,290 @@
|
|||
export type NamedColor =
|
||||
| 'aliceblue'
|
||||
| 'antiquewhite'
|
||||
| 'aqua'
|
||||
| 'aquamarine'
|
||||
| 'azure'
|
||||
| 'beige'
|
||||
| 'bisque'
|
||||
| 'black'
|
||||
| 'blanchedalmond'
|
||||
| 'blue'
|
||||
| 'blueviolet'
|
||||
| 'brown'
|
||||
| 'burlywood'
|
||||
| 'cadetblue'
|
||||
| 'chartreuse'
|
||||
| 'chocolate'
|
||||
| 'coral'
|
||||
| 'cornflowerblue'
|
||||
| 'cornsilk'
|
||||
| 'crimson'
|
||||
| 'cyan'
|
||||
| 'darkblue'
|
||||
| 'darkcyan'
|
||||
| 'darkgoldenrod'
|
||||
| 'darkgray'
|
||||
| 'darkgreen'
|
||||
| 'darkkhaki'
|
||||
| 'darkmagenta'
|
||||
| 'darkolivegreen'
|
||||
| 'darkorange'
|
||||
| 'darkorchid'
|
||||
| 'darkred'
|
||||
| 'darksalmon'
|
||||
| 'darkseagreen'
|
||||
| 'darkslateblue'
|
||||
| 'darkslategray'
|
||||
| 'darkturquoise'
|
||||
| 'darkviolet'
|
||||
| 'deeppink'
|
||||
| 'deepskyblue'
|
||||
| 'dimgray'
|
||||
| 'dodgerblue'
|
||||
| 'firebrick'
|
||||
| 'floralwhite'
|
||||
| 'forestgreen'
|
||||
| 'fuchsia'
|
||||
| 'gainsboro'
|
||||
| 'ghostwhite'
|
||||
| 'gold'
|
||||
| 'goldenrod'
|
||||
| 'gray'
|
||||
| 'green'
|
||||
| 'greenyellow'
|
||||
| 'honeydew'
|
||||
| 'hotpink'
|
||||
| 'indianred'
|
||||
| 'indigo'
|
||||
| 'ivory'
|
||||
| 'khaki'
|
||||
| 'lavender'
|
||||
| 'lavenderblush'
|
||||
| 'lawngreen'
|
||||
| 'lemonchiffon'
|
||||
| 'lightblue'
|
||||
| 'lightcoral'
|
||||
| 'lightcyan'
|
||||
| 'lightgoldenrodyellow'
|
||||
| 'lightgray'
|
||||
| 'lightgreen'
|
||||
| 'lightpink'
|
||||
| 'lightsalmon'
|
||||
| 'lightsalmon'
|
||||
| 'lightseagreen'
|
||||
| 'lightskyblue'
|
||||
| 'lightslategray'
|
||||
| 'lightsteelblue'
|
||||
| 'lightyellow'
|
||||
| 'lime'
|
||||
| 'limegreen'
|
||||
| 'linen'
|
||||
| 'magenta'
|
||||
| 'maroon'
|
||||
| 'mediumaquamarine'
|
||||
| 'mediumblue'
|
||||
| 'mediumorchid'
|
||||
| 'mediumpurple'
|
||||
| 'mediumseagreen'
|
||||
| 'mediumslateblue'
|
||||
| 'mediumslateblue'
|
||||
| 'mediumspringgreen'
|
||||
| 'mediumturquoise'
|
||||
| 'mediumvioletred'
|
||||
| 'midnightblue'
|
||||
| 'mintcream'
|
||||
| 'mistyrose'
|
||||
| 'moccasin'
|
||||
| 'navajowhite'
|
||||
| 'navy'
|
||||
| 'oldlace'
|
||||
| 'olive'
|
||||
| 'olivedrab'
|
||||
| 'orange'
|
||||
| 'orangered'
|
||||
| 'orchid'
|
||||
| 'palegoldenrod'
|
||||
| 'palegreen'
|
||||
| 'paleturquoise'
|
||||
| 'palevioletred'
|
||||
| 'papayawhip'
|
||||
| 'peachpuff'
|
||||
| 'peru'
|
||||
| 'pink'
|
||||
| 'plum'
|
||||
| 'powderblue'
|
||||
| 'purple'
|
||||
| 'rebeccapurple'
|
||||
| 'red'
|
||||
| 'rosybrown'
|
||||
| 'royalblue'
|
||||
| 'saddlebrown'
|
||||
| 'salmon'
|
||||
| 'sandybrown'
|
||||
| 'seagreen'
|
||||
| 'seashell'
|
||||
| 'sienna'
|
||||
| 'silver'
|
||||
| 'skyblue'
|
||||
| 'slateblue'
|
||||
| 'slategray'
|
||||
| 'snow'
|
||||
| 'springgreen'
|
||||
| 'steelblue'
|
||||
| 'tan'
|
||||
| 'teal'
|
||||
| 'thistle'
|
||||
| 'tomato'
|
||||
| 'turquoise'
|
||||
| 'violet'
|
||||
| 'wheat'
|
||||
| 'white'
|
||||
| 'whitesmoke'
|
||||
| 'yellow'
|
||||
| 'yellowgreen';
|
||||
|
||||
export const htmlColorNames: NamedColor[] = [
|
||||
'aliceblue',
|
||||
'antiquewhite',
|
||||
'aqua',
|
||||
'aquamarine',
|
||||
'azure',
|
||||
'beige',
|
||||
'bisque',
|
||||
'black',
|
||||
'blanchedalmond',
|
||||
'blue',
|
||||
'blueviolet',
|
||||
'brown',
|
||||
'burlywood',
|
||||
'cadetblue',
|
||||
'chartreuse',
|
||||
'chocolate',
|
||||
'coral',
|
||||
'cornflowerblue',
|
||||
'cornsilk',
|
||||
'crimson',
|
||||
'cyan',
|
||||
'darkblue',
|
||||
'darkcyan',
|
||||
'darkgoldenrod',
|
||||
'darkgray',
|
||||
'darkgreen',
|
||||
'darkkhaki',
|
||||
'darkmagenta',
|
||||
'darkolivegreen',
|
||||
'darkorange',
|
||||
'darkorchid',
|
||||
'darkred',
|
||||
'darksalmon',
|
||||
'darkseagreen',
|
||||
'darkslateblue',
|
||||
'darkslategray',
|
||||
'darkturquoise',
|
||||
'darkviolet',
|
||||
'deeppink',
|
||||
'deepskyblue',
|
||||
'dimgray',
|
||||
'dodgerblue',
|
||||
'firebrick',
|
||||
'floralwhite',
|
||||
'forestgreen',
|
||||
'fuchsia',
|
||||
'gainsboro',
|
||||
'ghostwhite',
|
||||
'gold',
|
||||
'goldenrod',
|
||||
'gray',
|
||||
'green',
|
||||
'greenyellow',
|
||||
'honeydew',
|
||||
'hotpink',
|
||||
'indianred',
|
||||
'indigo',
|
||||
'ivory',
|
||||
'khaki',
|
||||
'lavender',
|
||||
'lavenderblush',
|
||||
'lawngreen',
|
||||
'lemonchiffon',
|
||||
'lightblue',
|
||||
'lightcoral',
|
||||
'lightcyan',
|
||||
'lightgoldenrodyellow',
|
||||
'lightgray',
|
||||
'lightgreen',
|
||||
'lightpink',
|
||||
'lightsalmon',
|
||||
'lightsalmon',
|
||||
'lightseagreen',
|
||||
'lightskyblue',
|
||||
'lightslategray',
|
||||
'lightsteelblue',
|
||||
'lightyellow',
|
||||
'lime',
|
||||
'limegreen',
|
||||
'linen',
|
||||
'magenta',
|
||||
'maroon',
|
||||
'mediumaquamarine',
|
||||
'mediumblue',
|
||||
'mediumorchid',
|
||||
'mediumpurple',
|
||||
'mediumseagreen',
|
||||
'mediumslateblue',
|
||||
'mediumslateblue',
|
||||
'mediumspringgreen',
|
||||
'mediumturquoise',
|
||||
'mediumvioletred',
|
||||
'midnightblue',
|
||||
'mintcream',
|
||||
'mistyrose',
|
||||
'moccasin',
|
||||
'navajowhite',
|
||||
'navy',
|
||||
'oldlace',
|
||||
'olive',
|
||||
'olivedrab',
|
||||
'orange',
|
||||
'orangered',
|
||||
'orchid',
|
||||
'palegoldenrod',
|
||||
'palegreen',
|
||||
'paleturquoise',
|
||||
'palevioletred',
|
||||
'papayawhip',
|
||||
'peachpuff',
|
||||
'peru',
|
||||
'pink',
|
||||
'plum',
|
||||
'powderblue',
|
||||
'purple',
|
||||
'rebeccapurple',
|
||||
'red',
|
||||
'rosybrown',
|
||||
'royalblue',
|
||||
'saddlebrown',
|
||||
'salmon',
|
||||
'sandybrown',
|
||||
'seagreen',
|
||||
'seashell',
|
||||
'sienna',
|
||||
'silver',
|
||||
'skyblue',
|
||||
'slateblue',
|
||||
'slategray',
|
||||
'snow',
|
||||
'springgreen',
|
||||
'steelblue',
|
||||
'tan',
|
||||
'teal',
|
||||
'thistle',
|
||||
'tomato',
|
||||
'turquoise',
|
||||
'violet',
|
||||
'wheat',
|
||||
'white',
|
||||
'whitesmoke',
|
||||
'yellow',
|
||||
'yellowgreen',
|
||||
];
|
|
@ -1,3 +1,5 @@
|
|||
import { type NamedColor, htmlColorNames } from './colornames.js';
|
||||
|
||||
/// <reference types="astro/astro-jsx" />
|
||||
export type InputFormat =
|
||||
| 'heic'
|
||||
|
@ -10,16 +12,35 @@ export type InputFormat =
|
|||
| 'webp'
|
||||
| 'gif';
|
||||
|
||||
export type OutputFormat = 'avif' | 'jpeg' | 'png' | 'webp';
|
||||
export type OutputFormatSupportsAlpha = 'avif' | 'png' | 'webp';
|
||||
export type OutputFormat = OutputFormatSupportsAlpha | 'jpeg';
|
||||
|
||||
export type ColorDefinition =
|
||||
| NamedColor
|
||||
| `#${string}`
|
||||
| `rgb(${number}, ${number}, ${number})`
|
||||
| `rgb(${number},${number},${number})`;
|
||||
|
||||
export function isOutputFormat(value: string): value is OutputFormat {
|
||||
return ['avif', 'jpeg', 'png', 'webp'].includes(value);
|
||||
}
|
||||
|
||||
export function isOutputFormatSupportsAlpha(value: string): value is OutputFormatSupportsAlpha {
|
||||
return ['avif', 'png', 'webp'].includes(value);
|
||||
}
|
||||
|
||||
export function isAspectRatioString(value: string): value is `${number}:${number}` {
|
||||
return /^\d*:\d*$/.test(value);
|
||||
}
|
||||
|
||||
export function isColor(value: string): value is ColorDefinition {
|
||||
return (
|
||||
(htmlColorNames as string[]).includes(value.toLowerCase()) ||
|
||||
/^#[0-9a-f]{3}([0-9a-f]{3})?$/i.test(value) ||
|
||||
/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/i.test(value)
|
||||
);
|
||||
}
|
||||
|
||||
export function parseAspectRatio(aspectRatio: TransformOptions['aspectRatio']) {
|
||||
if (!aspectRatio) {
|
||||
return undefined;
|
||||
|
@ -75,6 +96,15 @@ export interface TransformOptions {
|
|||
* @example "16:9" - strings can be used in the format of `{ratioWidth}:{ratioHeight}`.
|
||||
*/
|
||||
aspectRatio?: number | `${number}:${number}`;
|
||||
/**
|
||||
* The background color to use when converting from a transparent image format to a
|
||||
* non-transparent format. This is useful for converting PNGs to JPEGs.
|
||||
*
|
||||
* @example "white" - a named color
|
||||
* @example "#ffffff" - a hex color
|
||||
* @example "rgb(255, 255, 255)" - an rgb color
|
||||
*/
|
||||
background?: ColorDefinition;
|
||||
}
|
||||
|
||||
export interface HostedImageService<T extends TransformOptions = TransformOptions> {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import sharp from 'sharp';
|
||||
import { isAspectRatioString, isOutputFormat } from '../loaders/index.js';
|
||||
import { isAspectRatioString, isColor, 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;
|
||||
const { width, height, src, format, quality, aspectRatio, background, ...rest } = transform;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
|
@ -37,6 +37,10 @@ class SharpService implements SSRImageService {
|
|||
searchParams.append('ar', transform.aspectRatio.toString());
|
||||
}
|
||||
|
||||
if (transform.background) {
|
||||
searchParams.append('bg', transform.background);
|
||||
}
|
||||
|
||||
return { searchParams };
|
||||
}
|
||||
|
||||
|
@ -72,6 +76,13 @@ class SharpService implements SSRImageService {
|
|||
}
|
||||
}
|
||||
|
||||
if (searchParams.has('bg')) {
|
||||
const background = searchParams.get('bg')!;
|
||||
if (isColor(background)) {
|
||||
transform.background = background;
|
||||
}
|
||||
}
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
||||
|
@ -87,6 +98,11 @@ class SharpService implements SSRImageService {
|
|||
sharpImage.resize(width, height);
|
||||
}
|
||||
|
||||
// remove alpha channel and replace with background color if requested
|
||||
if (transform.background) {
|
||||
sharpImage.flatten({ background: transform.background });
|
||||
}
|
||||
|
||||
if (transform.format) {
|
||||
sharpImage.toFormat(transform.format, { quality: transform.quality });
|
||||
}
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
import { expect } from 'chai';
|
||||
import * as cheerio from 'cheerio';
|
||||
import sharp from 'sharp';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { loadFixture } from './test-utils.js';
|
||||
|
||||
describe('SSG image with background - dev', function () {
|
||||
let fixture;
|
||||
let devServer;
|
||||
let $;
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({ root: './fixtures/background-color-image/' });
|
||||
devServer = await fixture.startDevServer();
|
||||
const html = await fixture.fetch('/').then((res) => res.text());
|
||||
$ = cheerio.load(html);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await devServer.stop();
|
||||
});
|
||||
|
||||
[
|
||||
{
|
||||
title: 'Named color',
|
||||
id: '#named',
|
||||
bg: 'dimgray',
|
||||
},
|
||||
{
|
||||
title: 'Hex color',
|
||||
id: '#hex',
|
||||
bg: '#696969',
|
||||
},
|
||||
{
|
||||
title: 'Hex color short',
|
||||
id: '#hex-short',
|
||||
bg: '#666',
|
||||
},
|
||||
{
|
||||
title: 'RGB color',
|
||||
id: '#rgb',
|
||||
bg: 'rgb(105,105,105)',
|
||||
},
|
||||
{
|
||||
title: 'RGB color with spaces',
|
||||
id: '#rgb-spaced',
|
||||
bg: 'rgb(105, 105, 105)',
|
||||
},
|
||||
].forEach(({ title, id, bg }) => {
|
||||
it(title, async () => {
|
||||
const image = $(id);
|
||||
const src = image.attr('src');
|
||||
const [_, params] = src.split('?');
|
||||
const searchParams = new URLSearchParams(params);
|
||||
expect(searchParams.get('bg')).to.equal(bg);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('SSG image with background - build', function () {
|
||||
let fixture;
|
||||
let $;
|
||||
let html;
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({ root: './fixtures/background-color-image/' });
|
||||
await fixture.build();
|
||||
|
||||
html = await fixture.readFile('/index.html');
|
||||
$ = cheerio.load(html);
|
||||
});
|
||||
|
||||
async function verifyImage(pathname, expectedBg) {
|
||||
const url = new URL('./fixtures/background-color-image/dist/' + pathname, import.meta.url);
|
||||
const dist = fileURLToPath(url);
|
||||
const data = await sharp(dist).raw().toBuffer();
|
||||
// check that the first RGB pixel indeed has the requested background color
|
||||
expect(data[0]).to.equal(expectedBg[0]);
|
||||
expect(data[1]).to.equal(expectedBg[1]);
|
||||
expect(data[2]).to.equal(expectedBg[2]);
|
||||
}
|
||||
|
||||
[
|
||||
{
|
||||
title: 'Named color',
|
||||
id: '#named',
|
||||
bg: [105, 105, 105],
|
||||
},
|
||||
{
|
||||
title: 'Hex color',
|
||||
id: '#hex',
|
||||
bg: [105, 105, 105],
|
||||
},
|
||||
{
|
||||
title: 'Hex color short',
|
||||
id: '#hex-short',
|
||||
bg: [102, 102, 102],
|
||||
},
|
||||
{
|
||||
title: 'RGB color',
|
||||
id: '#rgb',
|
||||
bg: [105, 105, 105],
|
||||
},
|
||||
{
|
||||
title: 'RGB color with spaces',
|
||||
id: '#rgb-spaced',
|
||||
bg: [105, 105, 105],
|
||||
},
|
||||
].forEach(({ title, id, bg }) => {
|
||||
it(title, async () => {
|
||||
const image = $(id);
|
||||
const src = image.attr('src');
|
||||
await verifyImage(src, bg);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,98 @@
|
|||
import { expect } from 'chai';
|
||||
import * as cheerio from 'cheerio';
|
||||
import { loadFixture } from './test-utils.js';
|
||||
import testAdapter from '../../../astro/test/test-adapter.js';
|
||||
|
||||
let fixture;
|
||||
|
||||
describe('SSR image with background', function () {
|
||||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
root: './fixtures/background-color-image/',
|
||||
adapter: testAdapter({ streaming: false }),
|
||||
output: 'server',
|
||||
});
|
||||
await fixture.build();
|
||||
});
|
||||
|
||||
[
|
||||
{
|
||||
title: 'Named color',
|
||||
id: '#named',
|
||||
query: {
|
||||
f: 'jpeg',
|
||||
w: '256',
|
||||
h: '256',
|
||||
href: /^\/assets\/file-icon.\w{8}.png/,
|
||||
bg: 'dimgray',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Hex color',
|
||||
id: '#hex',
|
||||
query: {
|
||||
f: 'avif',
|
||||
w: '256',
|
||||
h: '256',
|
||||
href: /^\/assets\/file-icon.\w{8}.png/,
|
||||
bg: '#696969',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Hex color short',
|
||||
id: '#hex-short',
|
||||
query: {
|
||||
f: 'png',
|
||||
w: '256',
|
||||
h: '256',
|
||||
href: /^\/assets\/file-icon.\w{8}.png/,
|
||||
bg: '#666',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'RGB color',
|
||||
id: '#rgb',
|
||||
query: {
|
||||
f: 'webp',
|
||||
w: '256',
|
||||
h: '256',
|
||||
href: /^\/assets\/file-icon.\w{8}.png/,
|
||||
bg: 'rgb(105,105,105)',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'RGB color with spaces',
|
||||
id: '#rgb-spaced',
|
||||
query: {
|
||||
f: 'jpeg',
|
||||
w: '256',
|
||||
h: '256',
|
||||
href: /^\/assets\/file-icon.\w{8}.png/,
|
||||
bg: 'rgb(105, 105, 105)',
|
||||
},
|
||||
},
|
||||
].forEach(({ title, id, query }) => {
|
||||
it(title, async () => {
|
||||
const app = await fixture.loadTestAdapterApp();
|
||||
|
||||
const request = new Request('http://example.com/');
|
||||
const response = await app.render(request);
|
||||
const html = await response.text();
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
const image = $(id);
|
||||
const src = image.attr('src');
|
||||
const [_, params] = src.split('?');
|
||||
|
||||
const searchParams = new URLSearchParams(params);
|
||||
|
||||
for (const [key, value] of Object.entries(query)) {
|
||||
if (typeof value === 'string') {
|
||||
expect(searchParams.get(key)).to.equal(value);
|
||||
} else {
|
||||
expect(searchParams.get(key)).to.match(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
8
packages/integrations/image/test/fixtures/background-color-image/astro.config.mjs
vendored
Normal file
8
packages/integrations/image/test/fixtures/background-color-image/astro.config.mjs
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import image from '@astrojs/image';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
site: 'http://localhost:3000',
|
||||
integrations: [image({ logLevel: 'silent' })]
|
||||
});
|
10
packages/integrations/image/test/fixtures/background-color-image/package.json
vendored
Normal file
10
packages/integrations/image/test/fixtures/background-color-image/package.json
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "@test/background-color-image",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@astrojs/image": "workspace:*",
|
||||
"@astrojs/node": "workspace:*",
|
||||
"astro": "workspace:*"
|
||||
}
|
||||
}
|
BIN
packages/integrations/image/test/fixtures/background-color-image/public/favicon.ico
vendored
Normal file
BIN
packages/integrations/image/test/fixtures/background-color-image/public/favicon.ico
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
44
packages/integrations/image/test/fixtures/background-color-image/server/server.mjs
vendored
Normal file
44
packages/integrations/image/test/fixtures/background-color-image/server/server.mjs
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { createServer } from 'http';
|
||||
import fs from 'fs';
|
||||
import mime from 'mime';
|
||||
import { handler as ssrHandler } from '../dist/server/entry.mjs';
|
||||
|
||||
const clientRoot = new URL('../dist/client/', import.meta.url);
|
||||
|
||||
async function handle(req, res) {
|
||||
ssrHandler(req, res, async (err) => {
|
||||
if (err) {
|
||||
res.writeHead(500);
|
||||
res.end(err.stack);
|
||||
return;
|
||||
}
|
||||
|
||||
let local = new URL('.' + req.url, clientRoot);
|
||||
try {
|
||||
const data = await fs.promises.readFile(local);
|
||||
res.writeHead(200, {
|
||||
'Content-Type': mime.getType(req.url),
|
||||
});
|
||||
res.end(data);
|
||||
} catch {
|
||||
res.writeHead(404);
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const server = createServer((req, res) => {
|
||||
handle(req, res).catch((err) => {
|
||||
console.error(err);
|
||||
res.writeHead(500, {
|
||||
'Content-Type': 'text/plain',
|
||||
});
|
||||
res.end(err.toString());
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(8085);
|
||||
console.log('Serving at http://localhost:8085');
|
||||
|
||||
// Silence weird <time> warning
|
||||
console.error = () => {};
|
BIN
packages/integrations/image/test/fixtures/background-color-image/src/assets/file-icon.png
vendored
Normal file
BIN
packages/integrations/image/test/fixtures/background-color-image/src/assets/file-icon.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.6 KiB |
21
packages/integrations/image/test/fixtures/background-color-image/src/pages/index.astro
vendored
Normal file
21
packages/integrations/image/test/fixtures/background-color-image/src/pages/index.astro
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
import { Image } from '@astrojs/image/components';
|
||||
---
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<!-- Head Stuff -->
|
||||
</head>
|
||||
<body>
|
||||
<Image id="named" src={import('../assets/file-icon.png')} width={256} format="jpeg" background="dimgray" alt="named" />
|
||||
<br />
|
||||
<Image id="hex" src={import('../assets/file-icon.png')} width={256} format="avif" background="#696969" alt="hex" />
|
||||
<br />
|
||||
<Image id="hex-short" src={import('../assets/file-icon.png')} width={256} background="#666" alt="hex-short" />
|
||||
<br />
|
||||
<Image id="rgb" src={import('../assets/file-icon.png')} width={256} format="webp" background="rgb(105,105,105)" alt="rgb" />
|
||||
<br />
|
||||
<Image id="rgb-spaced" src={import('../assets/file-icon.png')} width={256} format="jpeg" background="rgb(105, 105, 105)" alt="rgb-spaced" />
|
||||
<br />
|
||||
</body>
|
||||
</html>
|
|
@ -21,6 +21,8 @@ import { Image } from '@astrojs/image/components';
|
|||
<br />
|
||||
<Image id="query" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png?token=abc" width={544} height={184} format="webp" alt="query" />
|
||||
<br />
|
||||
<Image id="bg-color" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" width={544} height={184} format="jpeg" alt="Google" background="#333333" />
|
||||
<br />
|
||||
<Image id="ipsum" src="https://picsum.photos/200/300" width={200} height={300} alt="ipsum" format="jpeg" />
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -16,6 +16,8 @@ import { Picture } from '@astrojs/image/components';
|
|||
<br />
|
||||
<Picture id='inline' src={import('../assets/social.jpg')} sizes="(min-width: 640px) 50vw, 100vw" widths={[253, 506]} alt="Inline social image" />
|
||||
<br />
|
||||
<Picture id="bg-color" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" sizes="(min-width: 640px) 50vw, 100vw" widths={[272, 544]} aspectRatio={544/184} alt="Google logo" background="rgb(51, 51, 51)" formats={['avif', 'jpeg']} />
|
||||
<br />
|
||||
<Picture id="ipsum" src="https://picsum.photos/200/300" sizes="100vw" widths={[100, 200]} aspectRatio={2/3} formats={["avif", "webp", "jpg"]} alt="ipsum" />
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -15,3 +15,5 @@ import { Image } from '@astrojs/image/components';
|
|||
<Image id="inline" src={import('../assets/social.jpg')} width={506} alt="inline" />
|
||||
<br />
|
||||
<Image id="query" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png?token=abc" width={544} height={184} format="webp" alt="query" />
|
||||
<br />
|
||||
<Image id="bg-color" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" width={544} height={184} format="jpeg" alt="Google" background="#333333" />
|
||||
|
|
|
@ -66,6 +66,18 @@ describe('SSG images - dev', function () {
|
|||
url: '/_image',
|
||||
query: { f: 'webp', w: '768', h: '414', href: '/hero.jpg' },
|
||||
},
|
||||
{
|
||||
title: 'Background color',
|
||||
id: '#bg-color',
|
||||
url: '/_image',
|
||||
query: {
|
||||
f: 'jpeg',
|
||||
w: '544',
|
||||
h: '184',
|
||||
bg: '#333333',
|
||||
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
|
||||
},
|
||||
},
|
||||
].forEach(({ title, id, url, query }) => {
|
||||
it(title, () => {
|
||||
const image = $(id);
|
||||
|
@ -147,6 +159,18 @@ describe('SSG images with subpath - dev', function () {
|
|||
url: '/_image',
|
||||
query: { f: 'webp', w: '768', h: '414', href: '/hero.jpg' },
|
||||
},
|
||||
{
|
||||
title: 'Background color',
|
||||
id: '#bg-color',
|
||||
url: '/_image',
|
||||
query: {
|
||||
f: 'jpeg',
|
||||
w: '544',
|
||||
h: '184',
|
||||
bg: '#333333',
|
||||
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
|
||||
},
|
||||
},
|
||||
].forEach(({ title, id, url, query }) => {
|
||||
it(title, () => {
|
||||
const image = $(id);
|
||||
|
@ -222,6 +246,12 @@ describe('SSG images - build', function () {
|
|||
regex: /^\/hero_\w{4,10}.webp/,
|
||||
size: { width: 768, height: 414, type: 'webp' },
|
||||
},
|
||||
{
|
||||
title: 'Remote images',
|
||||
id: '#bg-color',
|
||||
regex: /^\/googlelogo_color_272x92dp_\w{4,10}.jpeg/,
|
||||
size: { width: 544, height: 184, type: 'jpg' },
|
||||
},
|
||||
].forEach(({ title, id, regex, size }) => {
|
||||
it(title, () => {
|
||||
const image = $(id);
|
||||
|
@ -292,6 +322,12 @@ describe('SSG images with subpath - build', function () {
|
|||
regex: /^\/docs\/hero_\w{4,10}.webp/,
|
||||
size: { width: 768, height: 414, type: 'webp' },
|
||||
},
|
||||
{
|
||||
title: 'Remote images',
|
||||
id: '#bg-color',
|
||||
regex: /^\/docs\/googlelogo_color_272x92dp_\w{4,10}.jpeg/,
|
||||
size: { width: 544, height: 184, type: 'jpg' },
|
||||
},
|
||||
].forEach(({ title, id, regex, size }) => {
|
||||
it(title, () => {
|
||||
const image = $(id);
|
||||
|
|
|
@ -72,6 +72,18 @@ describe('SSR images - build', async function () {
|
|||
url: '/_image',
|
||||
query: { f: 'webp', w: '768', h: '414', href: '/hero.jpg' },
|
||||
},
|
||||
{
|
||||
title: 'Background color',
|
||||
id: '#bg-color',
|
||||
url: '/_image',
|
||||
query: {
|
||||
f: 'jpeg',
|
||||
w: '544',
|
||||
h: '184',
|
||||
bg: '#333333',
|
||||
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
|
||||
},
|
||||
},
|
||||
].forEach(({ title, id, url, query }) => {
|
||||
it(title, async () => {
|
||||
const app = await fixture.loadTestAdapterApp();
|
||||
|
@ -176,6 +188,18 @@ describe('SSR images with subpath - build', function () {
|
|||
url: '/_image',
|
||||
query: { f: 'webp', w: '768', h: '414', href: '/hero.jpg' },
|
||||
},
|
||||
{
|
||||
title: 'Background color',
|
||||
id: '#bg-color',
|
||||
url: '/_image',
|
||||
query: {
|
||||
f: 'jpeg',
|
||||
w: '544',
|
||||
h: '184',
|
||||
bg: '#333333',
|
||||
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
|
||||
},
|
||||
},
|
||||
].forEach(({ title, id, url, query }) => {
|
||||
it(title, async () => {
|
||||
const app = await fixture.loadTestAdapterApp();
|
||||
|
|
|
@ -81,6 +81,19 @@ describe('SSR images - dev', function () {
|
|||
},
|
||||
contentType: 'image/webp',
|
||||
},
|
||||
{
|
||||
title: 'Background color',
|
||||
id: '#bg-color',
|
||||
url: '/_image',
|
||||
query: {
|
||||
f: 'jpeg',
|
||||
w: '544',
|
||||
h: '184',
|
||||
bg: '#333333',
|
||||
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
|
||||
},
|
||||
contentType: 'image/jpeg',
|
||||
},
|
||||
].forEach(({ title, id, url, query, contentType }) => {
|
||||
it(title, async () => {
|
||||
const image = $(id);
|
||||
|
@ -183,6 +196,19 @@ describe('SSR images with subpath - dev', function () {
|
|||
},
|
||||
contentType: 'image/webp',
|
||||
},
|
||||
{
|
||||
title: 'Background color',
|
||||
id: '#bg-color',
|
||||
url: '/_image',
|
||||
query: {
|
||||
f: 'jpeg',
|
||||
w: '544',
|
||||
h: '184',
|
||||
bg: '#333333',
|
||||
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
|
||||
},
|
||||
contentType: 'image/jpeg',
|
||||
},
|
||||
].forEach(({ title, id, url, query, contentType }) => {
|
||||
it(title, async () => {
|
||||
const image = $(id);
|
||||
|
|
|
@ -56,6 +56,19 @@ describe('SSG pictures - dev', function () {
|
|||
query: { f: 'jpg', w: '768', h: '414', href: '/hero.jpg' },
|
||||
alt: 'Hero image',
|
||||
},
|
||||
{
|
||||
title: 'Background color',
|
||||
id: '#bg-color',
|
||||
url: '/_image',
|
||||
query: {
|
||||
f: 'png',
|
||||
w: '544',
|
||||
h: '184',
|
||||
bg: 'rgb(51, 51, 51)',
|
||||
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
|
||||
},
|
||||
alt: 'Google logo',
|
||||
},
|
||||
].forEach(({ title, id, url, query, alt }) => {
|
||||
it(title, () => {
|
||||
const sources = $(`${id} source`);
|
||||
|
|
|
@ -60,6 +60,19 @@ describe('SSR pictures - build', function () {
|
|||
query: { f: 'jpg', w: '768', h: '414', href: '/hero.jpg' },
|
||||
alt: 'Hero image',
|
||||
},
|
||||
{
|
||||
title: 'Background color',
|
||||
id: '#bg-color',
|
||||
url: '/_image',
|
||||
query: {
|
||||
f: 'png',
|
||||
w: '544',
|
||||
h: '184',
|
||||
bg: 'rgb(51, 51, 51)',
|
||||
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
|
||||
},
|
||||
alt: 'Google logo',
|
||||
},
|
||||
].forEach(({ title, id, url, query }) => {
|
||||
it(title, async () => {
|
||||
const app = await fixture.loadTestAdapterApp();
|
||||
|
@ -151,6 +164,19 @@ describe('SSR pictures with subpath - build', function () {
|
|||
query: { f: 'jpg', w: '768', h: '414', href: '/hero.jpg' },
|
||||
alt: 'Hero image',
|
||||
},
|
||||
{
|
||||
title: 'Background color',
|
||||
id: '#bg-color',
|
||||
url: '/_image',
|
||||
query: {
|
||||
f: 'png',
|
||||
w: '544',
|
||||
h: '184',
|
||||
bg: 'rgb(51, 51, 51)',
|
||||
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
|
||||
},
|
||||
alt: 'Google logo',
|
||||
},
|
||||
].forEach(({ title, id, url, query }) => {
|
||||
it(title, async () => {
|
||||
const app = await fixture.loadTestAdapterApp();
|
||||
|
|
|
@ -80,6 +80,20 @@ describe('SSR pictures - dev', function () {
|
|||
contentType: 'image/jpeg',
|
||||
alt: 'Hero image',
|
||||
},
|
||||
{
|
||||
title: 'Background color',
|
||||
id: '#bg-color',
|
||||
url: '/_image',
|
||||
query: {
|
||||
f: 'png',
|
||||
w: '544',
|
||||
h: '184',
|
||||
bg: 'rgb(51, 51, 51)',
|
||||
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
|
||||
},
|
||||
contentType: 'image/png',
|
||||
alt: 'Google logo',
|
||||
},
|
||||
].forEach(({ title, id, url, query, alt, contentType }) => {
|
||||
it(title, async () => {
|
||||
const sources = $(`${id} source`);
|
||||
|
|
|
@ -14,6 +14,7 @@ describe('Sharp service', () => {
|
|||
['width & height', { src, height: 400, width: 200 }],
|
||||
['aspect ratio string', { src, aspectRatio: '16:9' }],
|
||||
['aspect ratio float', { src, aspectRatio: 1.7 }],
|
||||
['background color', { src, format: 'jpeg', background: '#333333' }],
|
||||
].forEach(([description, props]) => {
|
||||
it(description, async () => {
|
||||
const { searchParams } = await sharp.serializeTransform(props);
|
||||
|
@ -31,6 +32,7 @@ describe('Sharp service', () => {
|
|||
verifyProp(props.width, 'w');
|
||||
verifyProp(props.height, 'h');
|
||||
verifyProp(props.aspectRatio, 'ar');
|
||||
verifyProp(props.background, 'bg');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -48,6 +50,11 @@ describe('Sharp service', () => {
|
|||
['width & height', `w=200&h=400&href=${href}`, { src, height: 400, width: 200 }],
|
||||
['aspect ratio string', `ar=16:9&href=${href}`, { src, aspectRatio: '16:9' }],
|
||||
['aspect ratio float', `ar=1.7&href=${href}`, { src, aspectRatio: 1.7 }],
|
||||
[
|
||||
'background color',
|
||||
`f=jpeg&bg=%23333333&href=${href}`,
|
||||
{ src, format: 'jpeg', background: '#333333' },
|
||||
],
|
||||
].forEach(([description, params, expected]) => {
|
||||
it(description, async () => {
|
||||
const searchParams = new URLSearchParams(params);
|
||||
|
|
|
@ -49,6 +49,12 @@ describe('Images in MDX - build', function () {
|
|||
regex: /^\/hero_\w{4,10}.webp/,
|
||||
size: { width: 768, height: 414, type: 'webp' },
|
||||
},
|
||||
{
|
||||
title: 'Background color',
|
||||
id: '#bg-color',
|
||||
regex: /^\/googlelogo_color_272x92dp_\w{4,10}.jpeg/,
|
||||
size: { width: 544, height: 184, type: 'jpg' },
|
||||
},
|
||||
].forEach(({ title, id, regex, size }) => {
|
||||
it(title, () => {
|
||||
const image = $(id);
|
||||
|
|
1762
pnpm-lock.yaml
1762
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue