Body
- Accepted Date: 2023-02-08
- Reference Issues/Discussions: #468
- Authors: @Princesseuh
- Implementation PR:
Summary
This proposal aims to outline a plan for a core story for images in Astro, doing so by:
- Making the image component easier to use through reducing the amount of necessary props in most cases, more intuitive properties, behaviour that is closer to similar tools in the ecosystem and an overall enhanced developer experience (better error messages, better types etc)
- Adding a new path to import images assets from in a consistent way (inspired by the current
src/content
)
- Make things nice and easy to use both in Astro files and in Markdown
- All of it in core, no more integration to install!
Background & Motivation
src/assets
folder
Using images in Astro is currently a bit confusing. Should my images go in public
or src
? How do I refer to them?
For this, we'd like to introduce a src/assets
folder. This folder would be used for your source assets, as in, your not optimized, raw assets. Usage of this folder would be recommended, but not forced.
To make it easier to use this folder from anywhere in the project, an alias will be provided so it is possible to write ~/assets
. Due to necessitating tsconfig.json
changes, this will only affect new projects, and is completely optional.
Content Collection integration
It is fairly common for one of the property of a Markdown piece of content to need to be a reference to an asset (think, cover image for an article, picture for an author etc).
In tandem with the src/assets
folder, we'd like to introduce a way for users to specify that a specific property needs to refer to a valid asset from the src/assets
folder. This would allow us to provide validation of the asset, think cover needs to be a png of size 240x240
, including validating that the file actually exists.
Facts
- ESM importing images from other folders than
src/assets
is supported and they would be optimized, like before.
- In Markdown, referring relatively to assets from other folders than
src/assets
is also supported (ex: ![...](./image.png)
) in addition to the src/assets
folder (ex: ![...](~/assets/image.png)
)
- Images in the
public
folder can still be referred to, however, they won't be optimized / transformed. We would like for this folder to be kept for its original purpose, for assets that will be copied as-is.
- Content collection-powered validation only works for assets inside the
src/assets
folder. Remote images are currently out of scope and still need to be referred to using a z.string
.
- TL;DR:
- In Astro files, refer to local assets by ESM import anywhere in
src
and they'll be optimized.
- In Markdown, use the
![]
syntax and refer to images in src/assets
or relative to the file and they'll be optimized.
- Need an asset in the frontmatter and want to valid it through content collection? Refer to one from
src/assets
.
Image component
(see #447 for an earlier version of this proposal)
The current Image
component can be confusing to use at time. Why do I need to set an aspectRatio
? (what even is the aspect ratio of my image?) Why is my image getting cropped? What format should I use? What does quality
means?
width, height and aspectRatio
We'd like to make it so less properties are needed in general. In most cases, the width
and height
would be automatically inferred from the source file and not passing them would just result in the same dimensions, but a smaller weight.
For cases where they're needed, we'd like to remove the aspectRatio
property, instead preferring that users manually set both width
and height
. Combined with [[#Better behaviour for resizing images]], we believe that this will lead to a more consistant and easier to resonate about experience.
format
format
would be set to webp
by default, removing the need for it to be passed completely. We believe WebP to be a sensible default for most images, providing with a smaller file size while not sacrificing on compatibility and support for transparency.
quality
For quality
, we'd like to make it easier for people to use without needing to know the in-and-out of the loader's algorithm for quality. We propose to fix this by introducing different presets that users can choose from, for example: quality: 'low'
would automatically set an appropriate, approximative quality that is considered 'low' (specific implementation is left to the loaders to decide.)
Better behaviour for resizing images
Despite the tremendous power it offered, users were often confused by how @astrojs/image
would crop their images to fit the desired aspect ratio. This was especially confusing for users coming from other popular frameworks that don't offer this feature.
As such, this feature would be removed of the base toolkit. Resizing would now only operate in ways that follow the original image aspect ratio (based on width
). To control how an image is fitted inside its container, the object-fit
and object-position
CSS properties can be used.
For users used to other frameworks, this is a similar behaviour to the one offered by NextJS's next/image
and Eleventy's eleventy-img
.
Facts
- For ESM images, the only required property would be
src
. Remote images (ex: http://example.com/image.png
, /image.png
or ${import.meta.env.BASE_URL}/image.png
) would however require width
and height
to be set manually.
- Remote images would not be optimized or resized. We understand the need for this, but would like to orient our users towards self-hosing their images as much as much as possible, at least for now.
src
can be a dynamic import, but be aware that it must respect Vite's limitations on dynamic imports
format
would be set to webp
by default, with the possibility to override as needed. If you desire to have the same format as the original, for remote images it needs to be manually provided whereas for local images, it is possible to do format={myImage.format}
.
quality
now accepts two types of values, either a preset (low | mid | high | max
) or a specific number (0 - 100
).
alt
is a required property in all cases. It can be explicitly set to ""
, for cases where an alt-text is not required.
- By default, images are
loading="lazy"
and decoding="async"
, with possibility to override as needed.
- TL;DR: The complete list of property would be
src
, width
, height
, quality
, and all the properties natively available on the img
tag (alt
, loading
, decoding
, style
etc)
Shape of ESM imports
Currently, importing images in Astro returns a simple string
with the path of the image. The @astrojs/image
integration enhance this by instead returning the following shape: {src: string, width: number, height: number, format: string}
, allowing users to easily construct img
tags with no CLS:
---
import image from "../my_image.png"
---
<img src={image.src} width={image.width} height={image.height} />
This shape should also give most of the information an user could need to build their own image integrations based on ESM imports.
Since this would be directly in core, there would be no more configuration changes needed to get the proper types and as such, editor integration should be painless (completions, type checking etc) and address user confusion around this.
In order to avoid any breakage, this shape would be under an experimental flag until the next major release of Astro. Similarly to content collections, we would automatically update your env.d.ts
with the type changes needed for this.
Goals
- A consistant, robust and friendly way to include images into your page
- No CLS.
- Enforced best practices (
alt
, loading
, decoding
)
- Optimized images with good defaults, but still flexible
- With the all the usual good DX you expect from Astro (good error messages, good types etc)
Non-goals of this proposal
- Advanced usage in Markdown (ability to set width, height, quality etc)
- Using optimized images inside framework components
- Automatic generation of
srcset
- Placeholders generation
- Background generation
- Picture component
- Optimizing remote images
.svg
support
We realize that many of those features not being goals are considered to be downgrades compared to @astrojs/image
. This is on purpose as we'd like to approach this project with a "small MVP, add features over time" mentality.
Creating a perfect Image
component for core is a way more complex project than it might seems, the balance between "This has too much options, it's too confusing to use" and "This doesn't have enough options, it's unusable for anything even a little bit complex" is incredibly hard to hit.
We believe that, much like we did with other features in Astro, by building a good core and providing ways build around it, we'll ultimately achieve the best result possible.
Example
All the examples below show the result path in the built website
Basic image from src/assets
Source
---
import { Image } from "astro:image";
import myImage from "~/assets/my_image.png"; // Image is 1600x900
// Relative paths are also supported:
// import myImage from "../../assets/my_image.png"
---
<Image src={myImage} alt="Image showing Elsa from the movie Frozen, she has blonde hair and is wearing a long blue dress" />
Result
<img src="/_astro/my_image.hash.webp" width="1600" height="900" decoding="async" loading="lazy" alt="Image showing Elsa from the movie Frozen, she has blonde hair and is wearing a long blue dress" />
Resized image from src/assets
Source
---
import { Image } from "astro:image";
import myImage from "~/assets/my_image.png"; // Image is 1600x900
---
<Image src={myImage} width={800} alt="..." />
Result
<!-- Result image is a webp of 800x450 -->
<img src="..." width="800" height="450" decoding="async" loading="lazy" alt="..." />
Source
---
import { Image } from "astro:image";
import myImage from "~/assets/my_image.png"; // Image is 1600x900
---
<Image src={myImage} width={800} height={900} alt="..." />
Result
<!-- Result image is a webp of 800x450 -->
<img src="..." width="800" height="900" decoding="async" loading="lazy" alt="..." />
Basic remote image
Source
---
import { Image } from "astro:image";
// Image is 1920x1080
---
<Image src="https://example.com/image.png" alt="..." />
<!-- ERROR! `width` and `height` are required -->
<Image src="https://example.com/image.png" width={1280} alt="..." />
<!-- ERROR! `height` is required -->
<Image src="https://example.com/image.png" width={1280} height={720} alt="..." />
Result (3rd example)
<!-- Remote images are not optimized or resized.
<img src="https://example.com/image.png" decoding="async" loading="lazy" width="1280" height="720" alt="...">
Markdown
Source
Source image is a .png of 640x960
---
cover: ~/assets/cover.png
---
Result
Result is a .webp of 640x960
{
cover: {
src: "/_astro/cover.hash.webp",
width: 640,
height: 960
}
}
Source
My super image of 640x480:
![...](~/assets/my_image.png)
OR
![...](./my_image.png)
OR
![...](../../assets/my_image.png)
Result
<img src="/_astro/my_image.hash.webp" width="640" height="480" loading="lazy" decoding="async" alt="...">