-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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: support for Vercel Images in enhanced-img #11979
Conversation
|
I think |
I'm afraid I have to agree with Simon. Something like |
@benmccann Except they ship components which is bloated compared to a vite plugin. |
Maybe better as a third-party preprocessor then. The idea of As far as the weight of |
That's why I originally had this titled "Example..." not "feat". How would you go about sharing the sizes array between Best I could come up with was importing a file like export default [480, 1024, 1920, 2560] Then passing that along. I don't think using components will outperform the standard |
Perhaps it's a bit unclear what the intention with this PR was. It was editing
I don't think adapter options get passed to Vite plugins
Yeah, that's probably the best solution for now |
@benmccann Note that Vercel's API doesn't have any support for cropping, only resize, and that I was thinking perhaps enhanced-img could accept some kind of callback interface since the bulk of the code here is quite complex, where we can plug into enhanced-img to transform the result outside of a |
Hmmm... So trying to get this to work in reality (production) is not easy. This function in async function process(resolved_id, opts) {
if (!opts.imagetools_plugin.load) {
throw new Error('Invalid instance of vite-imagetools. Could not find load method.');
}
const hook = opts.imagetools_plugin.load;
const handler = typeof hook === 'object' ? hook.handler : hook;
const module_info = await handler.call(opts.plugin_context, resolved_id);
// ...
} Returns We can try and hijack it at this point with something like this in /**
* @param {{ vercel_sizes: number[] }} options
* @returns {Promise<import('vite').Plugin[]>}
*/
export async function enhancedImages(options) {
const imagetools_instance = await imagetools_plugin();
const image_plugin_instance = image_plugin(imagetools_instance, options);
return !process.versions.webcontainer
? [image_plugin_instance, imagetools_instance, vercel_encodeURIComponent_plugin()]
: [];
}
/**
* @returns {import('vite').Plugin}
*/
function vercel_encodeURIComponent_plugin() {
return {
name: 'vite-plugin-vercel-encodeuricomponent',
enforce: 'post',
async generateBundle(_, output_bundle) {
Object.values(output_bundle).filter((element) => {
return (element.fileName.endsWith('.svelte.js'));
}).map((obj) => {
// @ts-ignore
const ms = new MagicString(output_bundle[obj.fileName].code);
ms.replaceAll(new RegExp(/(\/_vercel\/image\?url=)(.+?)(&w=)/, 'g'), (_, $1, $2, $3) => {
return $1 + encodeURIComponent($2) + $3;
});
if (ms.hasChanged()) {
// @ts-ignore
output_bundle[obj.fileName].code = ms.toString();
}
})
},
}
} Which gets us the following unhydrated HTML sent to the browser: <img alt="" fetchpriority=high height=1334 src=/_app/immutable/assets/14.3BN3nevY.jpg width=2000 sizes="
(min-width: 50rem) 27rem,
(min-width: 43.875rem) calc(100vw - 7.5rem),
calc(100vw - 4rem)" srcset="/_vercel/image?url=%2F_app%2Fimmutable%2Fassets%2F14.3BN3nevY.jpg&w=80&q=99 80w, /_vercel/image?url=%2F_app%2Fimmutable%2Fassets%2F14.3BN3nevY.jpg&w=240&q=99 240w, /_vercel/image?url=%2F_app%2Fimmutable%2Fassets%2F14.3BN3nevY.jpg&w=300&q=99 300w, /_vercel/image?url=%2F_app%2Fimmutable%2Fassets%2F14.3BN3nevY.jpg&w=328&q=99 328w, /_vercel/image?url=%2F_app%2Fimmutable%2Fassets%2F14.3BN3nevY.jpg&w=540&q=99 540w, /_vercel/image?url=%2F_app%2Fimmutable%2Fassets%2F14.3BN3nevY.jpg&w=626&q=99 626w, /_vercel/image?url=%2F_app%2Fimmutable%2Fassets%2F14.3BN3nevY.jpg&w=640&q=99 640w, /_vercel/image?url=%2F_app%2Fimmutable%2Fassets%2F14.3BN3nevY.jpg&w=780&q=99 780w, /_vercel/image?url=%2F_app%2Fimmutable%2Fassets%2F14.3BN3nevY.jpg&w=860&q=99 860w, /_vercel/image?url=%2F_app%2Fimmutable%2Fassets%2F14.3BN3nevY.jpg&w=1000&q=99 1000w, /_vercel/image?url=%2F_app%2Fimmutable%2Fassets%2F14.3BN3nevY.jpg&w=1200&q=99 1200w, /_vercel/image?url=%2F_app%2Fimmutable%2Fassets%2F14.3BN3nevY.jpg&w=1400&q=99 1400w, /_vercel/image?url=%2F_app%2Fimmutable%2Fassets%2F14.3BN3nevY.jpg&w=1600&q=99 1600w, /_vercel/image?url=%2F_app%2Fimmutable%2Fassets%2F14.3BN3nevY.jpg&w=1800&q=99 1800w, /_vercel/image?url=%2F_app%2Fimmutable%2Fassets%2F14.3BN3nevY.jpg&w=2000&q=99 2000w"> Copying and pasting these However, since Vite was using that h() {
S(e, o = "/_vercel/image?url=" + new URL("../assets/14.3BN3nevY.jpg",import.meta.url).href + "&w=80&q=99 80w, /_vercel/image?url=" + new URL("../assets/14.3BN3nevY.jpg",import.meta.url).href + "&w=240&q=99 240w, /_vercel/image?url=" + new URL("../assets/14.3BN3nevY.jpg",import.meta.url).href + "&w=300&q=99 300w, /_vercel/image?url=" + new URL("../assets/14.3BN3nevY.jpg",import.meta.url).href + "&w=328&q=99 328w, /_vercel/image?url=" + new URL("../assets/14.3BN3nevY.jpg",import.meta.url).href + "&w=540&q=99 540w, /_vercel/image?url=" + new URL("../assets/14.3BN3nevY.jpg",import.meta.url).href + "&w=626&q=99 626w, /_vercel/image?url=" + new URL("../assets/14.3BN3nevY.jpg",import.meta.url).href + "&w=640&q=99 640w, /_vercel/image?url=" + new URL("../assets/14.3BN3nevY.jpg",import.meta.url).href + "&w=780&q=99 780w, /_vercel/image?url=" + new URL("../assets/14.3BN3nevY.jpg",import.meta.url).href + "&w=860&q=99 860w, /_vercel/image?url=" + new URL("../assets/14.3BN3nevY.jpg",import.meta.url).href + "&w=1000&q=99 1000w, /_vercel/image?url=" + new URL("../assets/14.3BN3nevY.jpg",import.meta.url).href + "&w=1200&q=99 1200w, /_vercel/image?url=" + new URL("../assets/14.3BN3nevY.jpg",import.meta.url).href + "&w=1400&q=99 1400w, /_vercel/image?url=" + new URL("../assets/14.3BN3nevY.jpg",import.meta.url).href + "&w=1600&q=99 1600w, /_vercel/image?url=" + new URL("../assets/14.3BN3nevY.jpg",import.meta.url).href + "&w=1800&q=99 1800w, /_vercel/image?url=" + new URL("../assets/14.3BN3nevY.jpg",import.meta.url).href + "&w=2000&q=99 2000w") || r(e, "srcset", o),
r(e, "sizes", u[0]),
r(e, "fetchpriority", "high"),
r(e, "alt", G()),
I(e.src, s = "" + new URL("../assets/14.3BN3nevY.jpg",import.meta.url).href) || r(e, "src", s),
r(e, "width", "2000"),
r(e, "height", "1334")
}, The previously relative and
Now, while I would prefer to have the URLs be relative and explicitly URI encoded, it looks like the swapped out URLs can be made to work by specifying /** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter({
images: {
minimumCacheTTL: 300,
formats: ['image/avif', 'image/webp'],
sizes: allowedSizes,
domains: ['the-origin-domain.vercel.app']
}
}), Now that works: <img alt="Property Photo" fetchpriority="high" height="1334" src="/_app/immutable/assets/14.3BN3nevY.jpg" width="2000"
sizes="
(min-width: 50rem) 27rem,
(min-width: 43.875rem) calc(100vw - 7.5rem),
calc(100vw - 4rem)"
srcset="/_vercel/image?url=https://the-origin-domain.vercel.app/_app/immutable/assets/14.3BN3nevY.jpg&w=80&q=99 80w, /_vercel/image?url=https://the-origin-domain.vercel.app/_app/immutable/assets/14.3BN3nevY.jpg&w=240&q=99 240w, /_vercel/image?url=https://the-origin-domain.vercel.app/_app/immutable/assets/14.3BN3nevY.jpg&w=300&q=99 300w, /_vercel/image?url=https://the-origin-domain.vercel.app/_app/immutable/assets/14.3BN3nevY.jpg&w=328&q=99 328w, /_vercel/image?url=https://the-origin-domain.vercel.app/_app/immutable/assets/14.3BN3nevY.jpg&w=540&q=99 540w, /_vercel/image?url=https://the-origin-domain.vercel.app/_app/immutable/assets/14.3BN3nevY.jpg&w=626&q=99 626w, /_vercel/image?url=https://the-origin-domain.vercel.app/_app/immutable/assets/14.3BN3nevY.jpg&w=640&q=99 640w, /_vercel/image?url=https://the-origin-domain.vercel.app/_app/immutable/assets/14.3BN3nevY.jpg&w=780&q=99 780w, /_vercel/image?url=https://the-origin-domain.vercel.app/_app/immutable/assets/14.3BN3nevY.jpg&w=860&q=99 860w, /_vercel/image?url=https://the-origin-domain.vercel.app/_app/immutable/assets/14.3BN3nevY.jpg&w=1000&q=99 1000w, /_vercel/image?url=https://the-origin-domain.vercel.app/_app/immutable/assets/14.3BN3nevY.jpg&w=1200&q=99 1200w, /_vercel/image?url=https://the-origin-domain.vercel.app/_app/immutable/assets/14.3BN3nevY.jpg&w=1400&q=99 1400w, /_vercel/image?url=https://the-origin-domain.vercel.app/_app/immutable/assets/14.3BN3nevY.jpg&w=1600&q=99 1600w, /_vercel/image?url=https://the-origin-domain.vercel.app/_app/immutable/assets/14.3BN3nevY.jpg&w=1800&q=99 1800w, /_vercel/image?url=https://the-origin-domain.vercel.app/_app/immutable/assets/14.3BN3nevY.jpg&w=2000&q=99 2000w"> But I think the browser is helping us out here as those should technically be URI encoded. It's almost like we need partial hydration here. Although really the problem starts with when imagetools returns a asset identifier. I'm only just learning about vite and plugins so it is not clear to me if that asset identifier can be resolved early so we can use it as an URI encoded query param, and stop SvelteKit from hydrating it? Any other ideas? |
I don't think there's any real value in using an image CDN for static images. Just process them at build time and serve them with the rest of your assets. Otherwise you're going to have to pay for them! For dynamic images, I've used this approach to good effect in the past: import { dev } from '$app/environment';
export function optimize(src: string, widths = [640, 960, 1280], quality = 90) {
if (dev) return src;
return widths
.slice()
.sort((a, b) => a - b)
.map((width, i) => {
const url = `/_vercel/image?url=${encodeURIComponent(src)}&w=${width}&q=${quality}`;
const descriptor = i < widths.length - 1 ? ` ${width}w` : '';
return url + descriptor;
})
.join(', ');
} Usage: <img alt={photo.description} srcset={optimize(photo.url)} /> Ways to improve it:
As for keeping sizes in sync between adapter config and wherever you configure the |
I appreciate the effort on this, but I'm going to go ahead and close it given the comments here |
@Rich-Harris It sounds like you might have a more behind-the-scenes understanding of how Vercel caches images on the edge... Are you saying images pumped through https://vercel.com/docs/image-optimization are cached and served exactly the same as if they were just part of the initial deploy? F.ex is there really no difference between Regarding my original inquiry about how can we resolve the identifier returned by imagetools, to answer my own question it looks like we may need to wait until Vite exports the following function for external plugins to use:
I may try making a fork of Vite to do this now, as I do have the use case for wanting to do this outside of the potential(?) caching and serving benefits (hint: to support doing things like Am I wrong in thinking creating a totally static |
|
Usage:
kit/sites/kit.svelte.dev/svelte.config.js
Line 11 in 7cf6703
kit/sites/kit.svelte.dev/vite.config.js
Line 23 in 7cf6703
Notice the
cdn=vercel&tint=ffaa22
.Output (after
npm run dev
):The scope of adding this functionality is kind of large given the different CDN providers.
Not sure how you all would want to go about it, but this is something I wanted for use with Vercel instead of having to use a component.
Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
Tests
pnpm test
and lint the project withpnpm lint
andpnpm check
Changesets
pnpm changeset
and following the prompts. Changesets that add features should beminor
and those that fix bugs should bepatch
. Please prefix changeset messages withfeat:
,fix:
, orchore:
.Edits