sveltejs / kit

web development, streamlined
https://kit.svelte.dev
MIT License
18.43k stars 1.89k forks source link

Image processing #241

Open Rich-Harris opened 3 years ago

Rich-Harris commented 3 years ago

Updated Aug 2023 by @benmccann - this thread has gotten quite long, so I've summarized some key points here. We have some docs on the site (https://kit.svelte.dev/docs/assets) that share some helpful basics. This issue discusses a potential future implementation within SvelteKit

Static optimization with an Image component

Vite's build pipeline will handle assets that you import: https://vitejs.dev/guide/assets.html

You can use vite-imagetools to transform those images as part of Vite's pipeline. E.g. the most common transform might be to generate avif and webp versions of an image. You can use the defaultDirectives option to set a project-wide default or you can add query parameters to handle images individually. You can also import a directory of images using Vite's import.meta.glob with its query option.

You could use something like bluwy/svelte-preprocess-import-assets to let users simply write img tags that get converted to import statements for Vite to process.

A discussion of how to set this up is included at https://github.com/sveltejs/kit/issues/241#issuecomment-1274046866 and further demonstrated in https://github.com/sveltejs/kit/issues/241#issuecomment-1286296389.

Static optimization powered by a preprocessor

A problem with using an Image component in Svelte is that it requires the usage of :global to style it and it's difficult to handle events on it. It's possible some of these issues could be addressed in Svelte itself (e.g. there was a community proposal regarding event forwarding https://github.com/sveltejs/rfcs/pull/60), but at the current time there are no concrete plans around this.

One solution to this would be to use a preprocessor to convert:

<img alt="delete" src="$lib/icons/trashcan.png" />

Into something like:

<script>
  import __image1 from '$lib/icons/trashcan.png';
  import __image1_avif from '$lib/icons/trashcan.png?format=avif';
  import __image1_webp from '$lib/icons/trashcan.png?format=webp';
</script>

<picture>
  <source type="image/avif" src={__image1_avif} />
  <source type="image/webp" src={__image1_webp} />
  <img alt="delete" src={__image1} width="24" height="24" />
</picture>

This actually scales very well since https://github.com/sveltejs/svelte/pull/8948.

However, this approach doesn't work as well for something like the SvelteKit showcase or import.meta.glob because it requires the presence of an img tag.

Dynamic optimization

You could also implement a function to alter a URL and output the CDN's URL (e.g. https://github.com/sveltejs/kit/pull/10323). Including this manually may become cumbersome, but as with the static optimization case, you could use something like bluwy/svelte-preprocess-import-assets to let users simply write img tags that get converted to use this function. The unpic library is an example of this approach. This approach works really well when users have a CDN available and some hosts like Vercel have CDNs as included offerings. For users that want to deploy to somewhere like GitHub pages the static approach might work better and so it may make sense to offer dynamic optimzation alongside one of the static optimization approaches.

benmccann commented 3 years ago

Snowpack has some image optimization plugins (https://www.snowpack.dev/plugins) and he said that one off them looked to fit his needs. Would plugins only be used in dev mode because Rollup is used in production mode?

Rich-Harris commented 3 years ago

Plugins are used in both cases. Rollup just bundles/analyses the resulting vanilla JS

benmccann commented 3 years ago

Ok, it sounds like this is already addressed then, so we could probably close this

Rich-Harris commented 3 years ago

Maybe — need to take a look and see if there's anything missing from this: https://twitter.com/rchrdnsh/status/1336386125552779264

pngwn commented 3 years ago

That description isn't very helpful, does the plugin transform the sourcecode as well as optimising the image? If it only does the image optimisation, it would be pretty limiting in terms of lazy-loading, generating srcsets, etc. Likewise some way to set the priority of the image (by adding preload tags to the head) would also be good. next/image does all of this.

Image components aren't always the best solution but they offer a lot of flexibility depending on your needs.

pngwn commented 3 years ago

I think the most comprehensive implementations will need to be framework specific, although I haven't thought about it a great deal.

antony commented 3 years ago

With regards to this, I think a rollup/vite plugin for https://www.npmjs.com/package/sharp would likely be able to fulfil these requirements.

rchrdnsh commented 3 years ago

Hi all, thank you for considering this feature...

Just found this new (2 months old, it seems) plugin for Vite...starting to mess around with it with no success yet...seems potentially promising, however :-)

https://github.com/JonasKruckenberg/vite-imagetools

alteredorange commented 3 years ago

Nextjs handles this pretty well, and since it's open source, you can see how they do it. Docs here and here, with the main source code here. Handles different sources, sizes, etc. It's not perfect, but it's more robust than most options.

yamiteru commented 3 years ago

Hi all, thank you for considering this feature...

Just found this new (2 months old, it seems) plugin for Vite...starting to mess around with it with no success yet...seems potentially promising, however :-)

https://github.com/JonasKruckenberg/vite-imagetools

I'm in a close contact with the author and he's rewriting the whole library into core library and different bundler adapters including vite.

You can easily just do this to generate 6 different images together with their width and height info:

import {width, height, src} from "./image.jpg?format=webp;jpg&width=480;1024;1920&metadata"
JonasKruckenberg commented 3 years ago

Hey guys! I'm a little late to the party, but whatever 👋🏻

With regards to this, I think a rollup/vite plugin for https://www.npmjs.com/package/sharp would likely be able to fulfil these requirements.

vite-imagetools (or the core library more specifically) is basically just a wrapper around sharp, that let's you "execute" sharps image function in the import statement.

The basic idea though is to provide a "common api" that lets you specify how you want your image to be transformed and all the rest (wether the transformation is done by sharp or libvips directly, what buildtools you use, etc.) are completely irrelevant to the developer as all information is encoded in the url and not some config file.

It's not super reliable in vite yet(because vite is sometimes a bit quirky with static assets) and the docs are horrible, but I'm working on improvements and other buildtool integrations so if you have any questions, bug, feature requests just let me know 👍🏻

cryptodeal commented 3 years ago

@JonasKruckenberg really appreciate your work on the vite-imagetools library. I was able to get vite-imagetools integrated with the current release of sveltekit and the node adapter!

  1. Install vite-imagetools@next as a devDependency
  2. In your svelte.config.cjs, ensure you have imagetools() set as a vite plugin:
    
    const imagetools = require('vite-imagetools')

vite: { plugins: [imagetools({force: true})] }

3. wherever you want to import the transformed image (example shown below is in src/routes/$layout.svelte) :
alt description
Edit: With srcset:
testattribute
babichjacob commented 3 years ago
   import Logo1 from '../../static/title.png?w=300;400;500&format=webp&srcset'

Is it a better idea to put your images like this in the src folder instead of importing from static which requires weird .. directory traversal?

cryptodeal commented 3 years ago

Is it a better idea to put your images like this in the src folder instead of importing from static which requires weird .. directory traversal?

Definitely; I will likely end up moving them to src/static and updating svelte.config.cjs to reflect the updated location of the static assets.

rchrdnsh commented 3 years ago

hmmmm...

might it be worth considering adding some sort of official Svelte/Kit support for some sort of assets folder in the src folder for images that will be processed by plugins such as these in the future?

babichjacob commented 3 years ago

hmmmm...

might it be worth considering adding some sort of official Svelte/Kit support for some sort of assets folder in the src folder for images that will be processed by plugins such as these in the future?

What more is SvelteKit supposed to do than is already possible per the comment above?

rchrdnsh commented 3 years ago

well, i don't have to make a lib folder myself, as it's already there, with a nice and ergonomic alias as well, which would be super awesome to have for an images folder...but maybe it's easy to make as alias, as I simply don't know how...but to be more specific, making image processing part of the default configuration out of the box, as it's such an important consideration for performant sites and apps...or maybe even a svelte-add for image processing in the future...

cryptodeal commented 3 years ago

Example integration of vite-imagetools and sveltekit with $static alias for src/static below:

  1. Install vite-imagetools@next as a devDependency
  2. Your svelte.config.cjs should be modified to include the following:
const node = require('@sveltejs/adapter-node');
const pkg = require('./package.json');
const imagetools = require('vite-imagetools')
const path = require('path')

/** @type {import('@sveltejs/kit').Config} */
module.exports = {
  kit: {
    files: {
      assets: 'src/static',
    },
    vite: {
      resolve: {
        alias: {
          $static: path.resolve('src/static')
        }
      },
      plugins: [imagetools({force: true})]
    }
  },
};
  1. Example importing png and converting to webp ($static alias to src/static for convenience)
<script>
   import Logo1 from '$static/title.png?webp&meta'
</script>
<img src={Logo1.src} alt="alt description">
  1. Example importing png in 3 sizes, convert to webp and render as srcset. ($static alias to src/static for convenience)
    <script>
    import Logo1 from '$static/title.png?w=300;400;500&format=webp&srcset'
    </script>
    <img srcset={Logo1} type="image/webp" alt="testattribute"/>
rchrdnsh commented 3 years ago

very nice :-) We should probably move this kind of stuff to discord, but thank you!

pzerelles commented 3 years ago

There is so much boilerplate to write, even more if you need backward compatibility with WebP in a picture tag. But since svelte is a compiler, can't we just have an image transformation attribute right on the img tag that can create whatever we want - including lazy loading or fade-in transitions and more?

tony-sull commented 3 years ago

Well I went down the rabbit hole of trying to use vite-imagetools with SvelteKit yesterday. I have a site using Netlify CMS with local JSON files and wanted to dynamically load and resize images at build time along the lines of

<script lang="ts" context="module">
    import page from "$data/pages/home.json";

    export async function load() {
        const { default: hero } = await import(`../static/${page.hero}.jpg?height=550&format=webp`)

        return {
            props: {
                hero
            }
        }
    }
</script>

It looks like it may be a limitation related to dynamic-import-vars, but when digging through that project I couldn't tell if the query parameters are expected to break. Based on the examples in vite-imagetools I would have assumed query params alone wouldn't break production builds, but who knows.

I made sure to follow the workarounds for known limitations of the rollup plugin, like having the import starts with ./ or ../ and the file extension is included for the glob serach. It works fine in dev and actually works in production without the query params, I was hoping the dynamic-import-vars logic would have been able to strip off the query params and find the original image file.

Repro

I made a barebones repro of this at https://github.com/tonyfsullivan/sveltekit-imagetools-repro. Not sure if my use case here is just an outlier, it works flawlessly when the image file names are basic strings rather than dynamic variables.

Does anyone know if this should have worked for me, or where the bug/feature request would land? At the moment I have no idea if this would be related to vite, vite-imagetools, @rollup/plugin-dynamic-import-vars, or if it's specific to SvelteKit somehow.

JonasKruckenberg commented 3 years ago

I don't know enough about svelte-kit to really debug this, but I can confirm that it is not an issue with vite-imagetools as the plugin never gets told about the image in build mode. Seems to be related to vite-imagetools#34 so a problem with with the resolution of the import glob pattern

pzerelles commented 3 years ago

I wrote a little preprocessor to make using vite-imagetools even easier. Modifiers can be directly appended to the img's src or srcset prop and the imports will be added by the preprocessor.

const svelte = require("svelte/compiler")

/**
 * @typedef Options
 * @property {{ [tag: string]: string[] }} tags
 */

/**
 * @typedef {import('svelte/types/compiler/interfaces').TemplateNode} TemplateNode
 */

/**
 * @param {Options} [options]
 * @returns {import('svelte/types/compiler/preprocess/types').PreprocessorGroup}
 */
function imagePreprocess(options = {}) {
  const imports = {}
  const tags = options.tags || {
    img: ["src", "srcset", "data-src", "data-srcset"],
    source: ["src", "srcset", "data-src", "data-srcset"],
  }
  return {
    markup: ({ content, filename }) => {
      const preparedContent = content
        .replace(/<style[^>]*>[\w\W]+<\/style>/g, (match) => " ".repeat(match.length))
        .replace(/<script[^>]*>[\w\W]+<\/script>/g, (match) => " ".repeat(match.length))
      let ast
      try {
        ast = svelte.parse(preparedContent)
      } catch (e) {
        console.error(e, "Error parsing content")
        return
      }

      /** @type {TemplateNode[]} */
      const matches = []
      svelte.walk(ast, {
        enter: (node) => {
          if (!["Element", "Fragment", "InlineComponent"].includes(node.type)) {
            return
          }

          if (tags[node.name]) {
            matches.push({ node, attributes: tags[node.name] })
          }
        },
      })

      const dependencies = []
      const code = matches.reduce(
        /**
         * @param {{content: string, offset: number}} processed
         * @param {{node: TemplateNode, attributes: string[]} match
         * @param {number} index
         */
        (processed, match, index) => {
          const attributes = (match.node.attributes || []).filter(
            (a) => a.type === "Attribute" && match.attributes.includes(a.name)
          )
          if (
            attributes.length === 0 ||
            (match.node.attributes || []).find((a) => a.name === "rel" && a.value[0].data === "external")
          ) {
            return processed
          }

          let { content, offset } = processed

          for (const attribute of attributes) {
            if (attribute.value[0]?.type === "Text") {
              const value = attribute.value[0]
              if (value.data.startsWith("http")) continue

              const symbol = `__IMAGE_${index}__`
              const replacement = `{${symbol}}`

              if (!imports[filename]) imports[filename] = {}
              imports[filename][symbol] = value.data

              dependencies.push(value.data)

              content = content.substring(0, value.start + offset) + replacement + content.substring(value.end + offset)

              offset += replacement.length - value.data.length
            }
          }
          return { content, offset }
        },
        { content, offset: 0 }
      ).content

      return { code, dependencies }
    },
    script: ({ content, attributes, filename }) => {
      if (!attributes.context) {
        const localImports = Object.entries(imports[filename] || {})
        if (localImports.length > 0) {
          const dependencies = localImports.map(([symbol, path]) => path)
          const code =
            localImports.map(([symbol, path]) => `import ${symbol} from "${path}"`).join("\n") + "\n" + content
          return { code, dependencies }
        }
      }
    },
  }
}

module.exports = imagePreprocess
michaeloliverx commented 2 years ago

I am interested in creating an image component for svelte but I am not sure how to approach it. We have build time approaches like:

A know limitation (https://github.com/matyunya/svelte-image/issues/31, https://github.com/JonasKruckenberg/imagetools/issues/5) of these approaches is that the images cannot be dynamic i.e. inside variables, instead they have to be referenced as string literals for preprocessing to work.

I have a few questions for what would a runtime implementation would look like?

Some discussions on https://github.com/snowpackjs/astro/issues/492 that might be related.

leimantas commented 2 years ago

I am interested in creating an image component for svelte but I am not sure how to approach it. We have build time approaches like:

A know limitation (matyunya/svelte-image#31, JonasKruckenberg/imagetools#5) of these approaches is that the images cannot be dynamic i.e. inside variables, instead they have to be referenced as string literals for preprocessing to work.

I have a few questions for what would a runtime implementation would look like?

  • Where would this code live? In an endpoint?
  • Where should generated assets be stored?
  • Are there any hooks in svelte kit that we can use to emit generated assets?
  • Do we need additional hooks like NextJS getStaticProps/getServerSideProps etc?

Some discussions on snowpackjs/astro#492 that might be related.

hey, I played a bit with dynamic on demand optimization.

https://github.com/leimantas/svelte-image-caravaggio/blob/main/src/lib/components/Image.svelte

myisaak commented 2 years ago

<Image/> components are nice to add loading transitions which CSS can't yet do. But wrapping each image in such a stateful component harms loading performance. Perhaps such a task is better for a Svelte transition that hooks on image load event (doesn't exist yet, just an idea):

<img transition:fade src="image.webp" alt="fade in when image loads">

Image processing on localhost becomes a huge build bottleneck when having to process multiple images each time per commit. It's not scalable. The job is better suited from a CMS or serverless function that provides an API similar to imagetools, and most importantly caches the results:

<img src="image.jpg?w=300" srcset="image.jpg?w=300,image_2x.jpg?w=600 2x" alt="responsive widths from api">

On the other hand, network performance can be improved when one downloads all remotely sourced images and serves them from a single host, because we create fewer new connections. A config option downloadRemoteImages in svelte.config.js would suffice, or?

Another room for processing would be "width" and "height" parameters on images. Could also be an option on SvelteKit's config.

I've been working with SvelteKit and have tacked this issue with custom scripts wrapped around the framework. Would be nice to see this stuff get integrated. Perhaps I can upstream my changes if thats okay?

paulkre commented 2 years ago

It would be nice for image processing to be available in SSG. I'm trying to switch from Gatsby to SvelteKit and the lack of a working <Image /> tag is the main thing that prevents me from doing so.

jessebenjamin1 commented 2 years ago

@paulkre I also think it would be great to see an image component similar to Gatsby! Here's an attempt at one which uses Vite Imagetools for processing. It's not perfect, and importing image data in pages is definitely verbose, but I think it emulates the Gatsby Image effect relatively well.

I've also put together a starter blog that uses this component and the metadata object in MDSvex to have optimised post thumbnails using the Image Component like you might find in a Gatsby blog.

Thank you everyone for the incredible work on Svelte & SvelteKit!

mcmxcdev commented 2 years ago

For anyone looking for a good library until this issue gets resolved: I just discovered https://github.com/xiphux/svimg which is very similar to https://github.com/matyunya/svelte-image but is maintained well and up-to-date :)

lietu commented 2 years ago

Anyone got a working configuration for svimg + Svelte Kit? I tried to set it up, but it doesn't seem to be doing much. It just creates empty folders for the processed image paths, but I see no errors, or really no other clues for what could be going wrong.

rchrdnsh commented 1 year ago

svimg does not currently support dynamic images, so maybe you are running into that issue...it seems like image processing is something that kit should probably try to handle, as none of the solutions so far seem to be able to handle dynamic images at all...that might be something that only kit can end up doing...but I'm not sure about that...starting down the path to try and figure that out...

benmccann commented 1 year ago

I've looked at all the various image processing solutions and feel that https://github.com/JonasKruckenberg/vite-imagetools is probably the best and have been slowly working to make support for it a bit more official. You can dynamically choose from a list of images with it. But if you want something even more dynamic like handling an image pulled out of a database, that's going to be outside of what would make sense to build into SvelteKit.

rchrdnsh commented 1 year ago

Hi ben! XD

Caveat to everything I write...I am an amateur dev with little to no knowledge of build pipelines or how any of vite or kit actually work...no team either...just me...stumbling alone...in the dark :-)

So, I think that maybe this issue is almost 100% of my image use cases, which I am currently using vite-imagetools for...

but it's...

not ideal...

Let's say I define images in my frontmatter for mdsvex files, like so:

---
title: Notes
subtitle: Every journey begins with a single sound.
portrait_image: /words/notes/portrait_notes.jpg
landscape_image: /words/notes/landscape_notes.jpg
alt: Making notes with a fountain pen.
---

<script context="module">

  import NotesPortraitAVIF from '$images/words/notes.jpg?width=240;280;360;480;720;960;1920&format=avif&srcset'
  import NotesLandscapeAVIF from '$images/words/notes.jpg?width=480;960;1280;1920;2560;3840&format=avif&srcset'

  metadata.srcset_portrait_avif = NotesPortraitAVIF;
  metadata.srcset_landscape_avif = NotesLandscapeAVIF;

  import NotesPortraitJPG from '$images/words/notes.jpg?width=240;280;360;480;720;960;1920&format=jpg&srcset'
  import NotesLandscapeJPG from '$images/words/notes.jpg?width=480;960;1280;1920;2560;3840&format=jpg&srcset'

  metadata.srcset_portrait_jpg = NotesPortraitJPG;
  metadata.srcset_landscape_jpg = NotesLandscapeJPG;

</script>

...I have to define all of the srcsets for different image types and crops manually for EVERY mdsvex file, and add them to the metadata in a module script, as you can see from the above code.

...so this sort of works...although I cannot ACTUALLY use AVIF images at all, because netlify will not build the site, because AVIF images take too much processing power and time??? to make...I think...still hazy on that one, tbh...talking to the netlify folks about it more in the near future...

Then this kind of frontmatter would be used in the following way on a svelte page in an [#each} block:

{#each sortedWords as {
  slug,
  title,
  subtitle,
  portrait_image,
  landscape_image,
  srcset_portrait_avif,
  srcset_landscape_avif,
  srcset_portrait_jpg,
  srcset_landscape_jpg
} }
  <Card
    {slug}
    {title}
    {subtitle}
    {portrait_image}
    {landscape_image}
    {srcset_portrait_avif}
    {srcset_landscape_avif}
    {srcset_portrait_jpg}
    {srcset_landscape_jpg}
  />
{/each}

...where the Card component uses the <picture> element to use the correct srcset, among other things.

So, as you can see, I currently have to manually add all this information to the frontmatter of every article, and I can't even use AVIF images at all because they simply stop the build entirely, it would seem.

So ideally I would want to be able to avoid all of this and only define the images in the frontmatter:

---
title: Notes
subtitle: Every journey begins with a single sound.
portrait_image: /words/notes/portrait_notes.jpg
landscape_image: /words/notes/landscape_notes.jpg
alt: Making notes with a fountain pen.
---

...then all this stuff:

'$images/words/notes.jpg?width=480;960;1280;1920;2560;3840&format=jpg&srcset'

for every possible image type and crop, etc...is defined in a config file for images or something...dunno...

Then transforming the images would happen locally and cached when dev happens so no images would be transformed by the server. Image transforms should also not happen every time a dev or build is run, either, as that would take forever and be very wasteful, especially with AVIF image transforms. Only NEW images would be transformed, and ideally only once, or whenever a change is made...some type of diffing system maybe?

The reason I say kit should handle it because only kit and vite know about what would be passed into a prop in a component in an {#each} block at dev or build time...I think...???

BUT...I have no idea how kit or vite work at all, so I have no idea if that is the best idea or even possible, etc...

So, this is what I would like to be able to do...dunno how to go about it or what the best approach might be...maybe kit should NOT do it, as you mentioned, but if so, do you have any thoughts as to how it might be done? I'm just starting to learn about node and npm scripts, so maybe that could be a possibility as well...

Anyhoo, I hope this makes sense, and maybe there is a way to solve these issues in some capacity XD

benmccann commented 1 year ago

Hey @rchrdnsh, this can be made much nicer with a couple of options. First I suggest using the defaultDirectives option so that you don't have to specify query parameters for the most part. Here's the vite.config.js I use in my project:

import * as path from 'node:path';
import { sveltekit } from '@sveltejs/kit/vite';
import { imagetools } from 'vite-imagetools';

const fallback = {
    '.heic': 'jpg',
    '.heif': 'jpg',
    '.avif': 'png',
    '.jpeg': 'jpg',
    '.jpg':  'jpg',
    '.png':  'png',
    '.tiff': 'jpg',
    '.webp': 'png',
    '.gif':  'gif'
};

/** @type {import('vite').UserConfig} */
const config = {
    plugins: [
        imagetools({
            defaultDirectives: (url) => {
                const ext = path.extname(url.pathname);
                return new URLSearchParams(`format=avif;webp;${fallback[ext]}&as=picture`);
            }
        }),
        sveltekit()
    ]
};

export default config;

I haven't tried putting images in frontmatter yet, but for my .svelte files I'm using svelte-preprocess-import-assets to automatically extract and import image URLs from code like <Image src="./_images/venostent.svg" alt="VenoStent" />. Then imagetools will automatically create webp and avif versions of the file in this example. Here's an example of setting up the preprocessor:

import sveltePreprocess from 'svelte-preprocess';
import { vitePreprocess } from '@sveltejs/kit/vite';
import { importAssets } from 'svelte-preprocess-import-assets';

/** @type {import('@sveltejs/kit').Config} */
export default {
    preprocess: [
        importAssets({
            sources: (defaultSources) => {
                return [
                    ...defaultSources,
                    {
                        tag: 'Image',
                        srcAttributes: ['src']
                    }
                ]
            }
        }),
        vitePreprocess()
    ]
...

My Image component roughly looks like:

<script>
    /** @type {string | import('vite-imagetools').Picture} */
    export let src;

    /** @type {string} */
    export let alt;
</script>

{#if typeof src === 'string'}
    <img {src} {alt} {...$$restProps} />
{:else}
    <picture>
        {#each Object.entries(src.sources) as [format, images]}
            <source srcset={images.map((i) => `${i.src} ${i.w}w`).join(', ')} type={'image/' + format} />
        {/each}
        <img src={src.img.src} width={src.img.w} height={src.img.h} {alt} {...$$restProps} />
    </picture>
{/if}

I realize of course this is a fair bit of configuration required at the moment. We've just done new releases of both imagetools and svelte-preprocess-import-assets today to make this all work pretty well. The next step will be packaging it all up together to be easier to setup.

rchrdnsh commented 1 year ago

Thank you for the reply @benmccann XD

I must admit, however, that i do not really understand any of it ATM...but I will try! Can't find anything about defaultDirectives in the imagetools docs, tho...

But thank you for helping and putting time and effort into this issue :-)

benmccann commented 1 year ago

Can't find anything about defaultDirectives in the imagetools docs, tho...

See https://github.com/JonasKruckenberg/imagetools/blob/main/packages/vite/README.md#defaultdirectives

rchrdnsh commented 1 year ago

thank you!

trying this all out in a kit project and i'm getting the following error:

supportedExtensions is not defined
ReferenceError: supportedExtensions is not defined
    at Object.defaultDirectives (file:///Users/rchrdnsh/Code/Svelte/RYKR-kit/vite.config.js.timestamp-1665513195017.mjs:21:11)
    at Context.load (file:///Users/rchrdnsh/Code/Svelte/RYKR-kit/node_modules/vite-imagetools/dist/index.mjs:36:68)
    at Object.load (file:///Users/rchrdnsh/Code/Svelte/RYKR-kit/node_modules/vite/dist/node/chunks/dep-6b3a5aff.js:41076:46)
    at async loadAndTransform (file:///Users/rchrdnsh/Code/Svelte/RYKR-kit/node_modules/vite/dist/node/chunks/dep-6b3a5aff.js:37304:24)

...not sure what that is or where it should go or where it is coming from...I guess...

rchrdnsh commented 1 year ago

this is what my vite config looks like at the moment...I suppose I need to make a supportedExtensions?

import { sveltekit } from '@sveltejs/kit/vite';
// import { defineConfig } from 'vite';
import { imagetools } from 'vite-imagetools';
import mkcert from 'vite-plugin-mkcert';

/** @type {import('vite').UserConfig} */

const config = {
  // server: {
  //   hmr: false
  // },
  ssr: {
    noExternal: [
      'svelte-stripe-js',
      'style-value-types',
      'popmotion',
      'framesync'
    ]
  },
  plugins: [
    sveltekit(),
    // imagetools(),
    imagetools(
      {
        defaultDirectives: (url) => {
          const extension = url.pathname.substring(url.pathname.lastIndexOf('.') + 1);
          if (supportedExtensions.includes(extension)) {
            return new URLSearchParams({
              format: 'avif;webp;' + extension,
              picture: true
            });
          }
          return new URLSearchParams();
        }
      }
    ),
    mkcert({hosts: ['localhost', 'app.local']})
  ]
};

export default config;
benmccann commented 1 year ago

@rchrdnsh I updated my post to include that line. Hope it helps

rdela commented 1 year ago

I am also interested in the putting images in frontmatter use case and am very pleased by the progress. Thank you Ben and others involved in moving this forward.

EDIT Re:

I realize of course this is a fair bit of configuration required at the moment. We've just done new releases of both imagetools and svelte-preprocess-import-assets today to make this all work pretty well. The next step will be packaging it all up together to be easier to setup.

…Is it worth making a demo of vite-imagetools all hooked up? Or will it get easier quickly enough that it's worth waiting instead of turning Ben's comment into a repo? 🤔💭

rchrdnsh commented 1 year ago

oh geez! i must have missed the extensions, my bad @benmccann 😬

const supportedExtensions = ['png', 'jpg', 'jpeg'];

...seems to work now...or, at least...I'm apparently missing images, lol...gonna sort them all out now 😁

rchrdnsh commented 1 year ago

ok, was confused about the hidden messages...i get that you added it now...

rchrdnsh commented 1 year ago

ok, so, I'm trying to apply this technique to markdown frontmatter image and I am getting the following error, after it worked ok on the markdown inline images:

Cannot convert undefined or null to object
TypeError: Cannot convert undefined or null to object
    at Function.entries (<anonymous>)
    at eval (/src/library/images/Image.svelte:21:137)
    at Object.$$render (/node_modules/svelte/internal/index.mjs:1771:22)
    at eval (/src/routes/words/+page.svelte:48:92)
    at Module.each (/node_modules/svelte/internal/index.mjs:1737:16)
    at Object.default (/src/routes/words/+page.svelte:46:29)
    at eval (/src/library/layout/Grid.svelte:19:74)
    at Object.$$render (/node_modules/svelte/internal/index.mjs:1771:22)
    at Object.default (/src/routes/words/+page.svelte:43:94)
    at eval (/src/library/layout/Container.svelte:41:54)

...dunno what's going on, but maybe because the image file path has not been determined yet at the time the code runs its undefined in the Image component?

rdela commented 1 year ago

Made an example repo with Imagetools all wired up, no frontmatter example yet, but it's a start and PRs + input very welcome. Uses a NASA pic, hooray public domain.

The vercel branch is the production branch on Vercel, it specifies the Vercel adapter and passes the edge: true option to the adapter for Edge Functions. Rich covered this at Vite Conf.

Again, any advice or suggestions appreciated.

UPDATE 1, 20 Oct: Now available on Netlify as well, building off the netlify branch. Had to fight with sveltejs/adapter-netlify a bit, ended up downgrading 1.0.0-next.81 to 1.0.0-next.78 to get build and static file serving working in unison (should I file a bug?).

UPDATE 2, 21 Oct: Appears though iOS 16 adds support for AVIF, AVIF images do not currently render in iOS Safari if your phone is in Lockdown Mode. Lockdown Mode can be disabled on a per website basis.

eur2 commented 1 year ago

@rdela Thank you for the example repo! Any hope to see a solution to optimize images in markdown (body and frontmatter)? It's something that Gatsby is doing well and it's missing a bit in SvelteKit…

elibenton commented 1 year ago

Is there a way you could import a directory of static images and have svelte/vite batch process them? So rather than having to specific a specific file path in vite-imagetools, you could just specific a folder, or maybe a blob? And then vite-imagetools processes all of them, waits for all the promises to resolve, and returns an array of images?

sawyerclick commented 1 year ago

@elibenton — I've had luck batch importing/glob'ing images with this component. Note that all targeted images live in $lib/assets/picture/*.

Ideally I could find a way to dynamically import images, though I'm interested in trying Ben's approach above first. Until then I have this component.

<script context="module">
    const pictures = import.meta.glob(
        '/src/lib/assets/picture/*.{heic,heif,avif,jpg,jpeg,png,tiff,webp,gif}',
        {
            query: {
                format: 'webp;avif;jpg',
                width: '300;600;1200',
                picture: '',
                flatten: '',
                background: '#ffffff'
            },
            import: 'default',
            eager: true
        }
    );
</script>

<script>
    /** REQUIRED */

    /** @type {String} required */
    export let src = undefined;

    /** @type {String} required */
    export let alt = undefined;

    /** OPTIONAL */

    /** @type {Boolean} */
    export let draggable = false;

    /** @type {('sync' | 'async' | 'auto')} */
    export let decoding = 'async';

    /** @type {('lazy' | 'eager' | 'auto')} */
    export let loading = 'lazy';

    /** @type {String} */
    let classes = '';
    export { classes as class };

    /** @type {String|{{ fallback: { w: Number, h: Number, src: String }, sources?: Object }}} the selected image */
    $: picture = pictures[src];
</script>

<picture>
    {#each Object.entries(picture.sources) as [format, images]}
        <source srcset={images.map((img) => `${img.src} ${img.w}w`).join(', ')} type="image/{format}" />
    {/each}

    <img
        class={classes}
        {decoding}
        {loading}
        {draggable}
        src={picture.fallback.src}
        {alt}
        width={picture.fallback.w}
        height={picture.fallback.h}
    />
</picture>
brev commented 1 year ago

Hi all, if none of the available options are working for you, and you are looking to:

I have written a new tool: web-image-gen.

It does not use the vite pipeline, but plays along nicely with it.

eur2 commented 1 year ago

@brev thank you for the ressource! It doesn't seems to parse and optimize markdown images, right?

brev commented 1 year ago

@eur2 no, sorry, nothing automatic with markdown. If using mdsvex you could import the manifest at top, and call the svelte component from within, but that's not very markdown-like.