Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add dynamic image optimization #10323

Draft
wants to merge 26 commits into
base: main
Choose a base branch
from
Draft

Conversation

dummdidumm
Copy link
Member

@dummdidumm dummdidumm commented Jul 5, 2023

part of #241
closes #9787

This adds image optimization through a new $app/images import. It's deliberately low level: The only export is getImage which you pass an image src and it returns an object containing src and srcset (possibly more?) values which you spread on an img tag.

Example:

<script>
  import { getImage } from '$app/images';
</script>

<img {...getImage('/images/my-image.jpg')} />

In order to use this you need to define a path to a loader in kit.config.images. The loader takes the original img src and a width and returns a URL pointing to the optimized image. You can also modify the number of sizes and trusted domains.

Open questions:

  • singular or plural, i.e. "images" (like it's now) or "image"? e.g. $app/image and kit.image or $app/images and kit.images?
  • Loaders can be defined in adapters that have also have an image optimization service, such a Vercel or Cloudflare. Not sure yet where we should place independent loaders such as Cloudinary - or even if at all, since we could also point people to implementations of those.
  • What else besides srcset and src should getImage return?
  • Should there be a more barebones version of this with just getSrcset? (probably not; you can create that yourself from getImage)
  • Right now this does not prevent cumulative layout shift. Should we add some kind of dev time mutation observer to check for images without fixed sizes that will result in CLS?

Please don't delete this checklist! Before submitting the PR, please make sure you do the following:

  • It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.

Tests

  • Run the tests with pnpm test and lint the project with pnpm lint and pnpm check

Changesets

  • If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running pnpm changeset and following the prompts. Changesets that add features should be minor and those that fix bugs should be patch. Please prefix changeset messages with feat:, fix:, or chore:.

part of #241
closes #9787

This adds image optimization through a new $app/images import. It's deliberately low level: The only export is getImage which you pass an image src and it returns an object containing src and srcset (possibly more?) values which you spread on an img tag.

In order to use this you need to define a path to a loader in kit.config.images. The loader takes the original img src and a width and returns a URL pointing to the optimized image. You can also modify the number of sizes and trusted domains.
@changeset-bot
Copy link

changeset-bot bot commented Jul 5, 2023

⚠️ No Changeset found

Latest commit: eec30ae

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@PhearZero
Copy link

LGTM, singular makes sense. Even though it is multiple images they are representing the same image resource. The limited scope is a lot easier to integrate into existing components.

Copy link
Member

@benmccann benmccann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I like the idea of telling people to use this for images that are in your repository without offering a preprocessor. It's much harder for users to setup as they must update every img tag on their site and it also makes it very hard to switch between static and dynamic providers

@PhearZero

This comment was marked as off-topic.

@dummdidumm
Copy link
Member Author

I'm not sure I like this idea because it makes it very hard to switch between the static and dynamic providers

Yes, that's deliberate - the insight in #9787 was that they are very different things and therefore should be treated differently. Trying to shoehorn both into one would result in suboptimal solutions for both. This way, each use case can be optimized for. You can also abstract both away into one image component that fits your needs.

@benmccann
Copy link
Member

Perhaps under the covers. But I feel the user interface shouldn't involve them having to write out a function call for every image. We should still pair it with a preprocessor that takes care of it

@dummdidumm
Copy link
Member Author

dummdidumm commented Jul 7, 2023

Assuming we create another preprocessor for static image optimization, how would we then be able to tell when to use which preprocessor? How would the user know which one is used?

@benmccann
Copy link
Member

benmccann commented Jul 7, 2023

You could set a default via an option in the preprocessor or you could make them separate preprocessors. I doubt you'd want to mix and match. I think you'd just choose a provider and go with them. If there really is a need though we could offer comments to change the behavior like <!-- svelte-image-static -->.

I think the API here would make sense for things like rendering an image the user has uploaded where the details are saved in a database or one-off usage where you've just got a handful of images you want to handle differently. For images that are included in your project's repository, you probably want the preprocessor which could be backed by either the build-time implementation or a CDN implementation like Vercel.

@benmccann benmccann mentioned this pull request Aug 28, 2023
Comment on lines +1 to +4
// https://vercel.com/docs/concepts/image-optimization

/**
* @param {string} src
Copy link
Member

@benmccann benmccann Oct 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// https://vercel.com/docs/concepts/image-optimization
/**
* @param {string} src
/**
* https://vercel.com/docs/concepts/image-optimization
* @param {string} src

* @param {number} width
* @param {{ quality?: number }} [options]
*/
export default function loader(src, width, options) {
Copy link
Member

@benmccann benmccann Oct 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it'd probably make sense to either generate the types with dts-buddy or use the type defined in the .d.ts file to keep them from going out of sync with each other

*/
sizes?: number[];
/**
* Which external domains to trust when optimizing images
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the usecase for this? it sort of seems like an oddly-specific feature. I imagine the user could create their own wrapper around getImage if this was something they needed

Copy link
Member Author

@dummdidumm dummdidumm Oct 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's needed if you want to optimize other images than ones that reside on your domain - else Vercel will not optimize them: https://vercel.com/docs/build-output-api/v3/configuration#images . AFAIK other provides have some variant of this, too, so I added it the general config.

Copy link
Member

@benmccann benmccann Oct 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, makes sense. But could we move these options to the Vercel loader since they're somewhat Vercel-specific? It's a bit confusing to me that some options are here and some are in the adapter, so you need to check two places to see what the available options are. Maybe we should just put them all in the adapter?

*/
loader?: string;
/**
* Which srcset sizes to generate
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this description correct? I thought it was the screen sizes. I know you had a device_sizes variable in #10323 and these look like pretty large numbers. I think only some of these will get generated based on the device being used, which would be helpful to include in the documentation

Copy link
Member Author

@dummdidumm dummdidumm Oct 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have to tell Vercel (and some other image optimization providers, too) which image widths / sizes / call-it-whatever-you-want are allowed to be generated. 3840 may sound like a big number, but it really isn't when you think about a 4k resolution laptop screen.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main problem with this config and the domain config is that image optimization providers may have specific settings / requirements. The question now is - how do you make it so that you don't have to duplicate the config? Either you put a common denominator inside the Kit config, and adapters can read from that config, or you provide that config through the adapters, but then we need a way to get the config from the adapter into Kit.

Copy link
Member

@benmccann benmccann Oct 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd not have any common config. E.g. the domains config seems rather Vercel-specific. As an example, I just checked Cloudinary and they seem not to have such an option.

sizes is very Vercel-specific as well. E.g. I just checked Bunny, Cloudinary, and Contentful and it doesn't seem any have such a concept

I don't think it's bad if you end up having the same config in different implementations. And in fact it might be helpful because different implementations might have config with the same name that behaves slightly differently

@@ -0,0 +1,47 @@
import { DEV } from 'esm-env';
import { sizes, loader, domains } from '__sveltekit/images';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer not to tie it to SvelteKit, so I think it'd be nicer to create a singleton instance rather than using a virtual module.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by that? How else would you get the config into the app?

Copy link
Member

@benmccann benmccann Oct 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the app needs it. If you move domains and sizes into adapter-vercel and just use them there then SvelteKit doesn't need the config. adapter-vercel could be given the sizes directly as an adapter option and then it could use it to generate the srcset

*/
images?: {
/**
* Path to a a file that contains a loader that will be used to generate the an image URL out of the given source and width.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Path to a a file that contains a loader that will be used to generate the an image URL out of the given source and width.
* Path to a file that contains a loader that will be used to generate the an image URL out of the given source and width.

Copy link
Member

@benmccann benmccann Oct 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should say "module" rather than "file"? It could be good to mention that it would often be a module from an npm package being used here

@leoj3n
Copy link
Contributor

leoj3n commented Jan 16, 2024

Will it preprocess CSS as well so to inject f.ex. /_vercel/ optimized urls in the stylesheets?

Perhaps you would just suggest not using images in CSS like in that article suggesting using <img> and object-fit?

Would it be interesting to bake in some of those other suggestions to use loading="lazy", decoding="async", etc as an "image preset" option? Like preset = LCP | BTF | visible or preset = hero | fold | visible.

This is what I came up with for images in the stylesheet:

https://github.com/leoj3n/svelte-vercel-optimized-images/blob/main/README.md#using-public_build_vercel-in-css

From what I'm getting this will operate more like a preprocessing step and not one that will require the originally proposed getImage syntax?

}

// Order of attributes is important here as they are set in this order
// and having src before srcset would result in a flicker
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better safe than sorry, but I haven't noticed any flicker when src comes before srcset in an img tag. Just curious if you have any proof of this? Haven't seen it mentioned anywhere else.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request New feature or request pkg:adapter-vercel Pertaining to the Vercel adapter
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants