diff --git a/.changeset/little-cameras-guess.md b/.changeset/little-cameras-guess.md new file mode 100644 index 000000000000..a3aae021301b --- /dev/null +++ b/.changeset/little-cameras-guess.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/image': minor +--- + +feat: add experimental `@sveltejs/image` package diff --git a/packages/adapter-vercel/index.d.ts b/packages/adapter-vercel/index.d.ts index 634f90ed8db9..b02681f1f0fa 100644 --- a/packages/adapter-vercel/index.d.ts +++ b/packages/adapter-vercel/index.d.ts @@ -1,7 +1,16 @@ import { Adapter } from '@sveltejs/kit'; import './ambient.js'; -export default function plugin(config?: Config): Adapter; +export default function plugin( + config?: Config & { + /** + * Enable or disable Vercel's image optimization + * https://vercel.com/docs/concepts/image-optimization + * @default false + */ + images?: boolean | { domains?: string[] }; + } +): Adapter; export interface ServerlessConfig { /** diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js index a46a953dd32f..7c6609a8ec96 100644 --- a/packages/adapter-vercel/index.js +++ b/packages/adapter-vercel/index.js @@ -6,7 +6,6 @@ import esbuild from 'esbuild'; import { get_pathname } from './utils.js'; const VALID_RUNTIMES = ['edge', 'nodejs16.x', 'nodejs18.x']; - const DEFAULT_FUNCTION_NAME = 'fn'; const get_default_runtime = () => { @@ -55,7 +54,7 @@ const plugin = function (defaults = {}) { functions: `${dir}/functions` }; - const static_config = static_vercel_config(builder); + const static_config = await static_vercel_config(builder, defaults); builder.log.minor('Generating serverless function...'); @@ -368,8 +367,11 @@ function write(file, data) { } // This function is duplicated in adapter-static -/** @param {import('@sveltejs/kit').Builder} builder */ -function static_vercel_config(builder) { +/** + * @param {import('@sveltejs/kit').Builder} builder + * @param {Parameters[0]} config + */ +async function static_vercel_config(builder, config) { /** @type {any[]} */ const prerendered_redirects = []; @@ -407,8 +409,22 @@ function static_vercel_config(builder) { overrides[page.file] = { path: overrides_path }; } + /** @type {Record | undefined} */ + let images = undefined; + if (config.images) { + images = { + // TODO this is duplicated in @sveltejs/image -> figure out a good way to keep them in sync / make them configurable + sizes: [64, 96, 128, 256, 384, 640, 750, 828, 1080, 1200, 1920, 2048, 3840], + domains: Array.isArray(config.images?.domains) ? config.images.domains : [], + // TODO should we expose the following and some other optional options through the adapter? + formats: ['image/avif', 'image/webp'], + minimumCacheTTL: 300 + }; + } + return { version: 3, + images, routes: [ ...prerendered_redirects, { diff --git a/packages/image/CHANGELOG.md b/packages/image/CHANGELOG.md new file mode 100644 index 000000000000..35fd28070993 --- /dev/null +++ b/packages/image/CHANGELOG.md @@ -0,0 +1 @@ +# @sveltejs/image diff --git a/packages/image/README.md b/packages/image/README.md new file mode 100644 index 000000000000..c1a03a4d2f5d --- /dev/null +++ b/packages/image/README.md @@ -0,0 +1,140 @@ +# `@sveltejs/image` + +**WARNING**: This package is experimental. It uses pre-1.0 versioning and may introduce breaking changes with every minor version release. + +This package aims to bring a plug and play image component to SvelteKit that is opinionated enough so you don't have to worry about the details, yet flexible enough for more advanced use cases or tweaks. It serves a smaller file format like `avif` or `webp` and uses the `srcset` and `sizes` attributes of the `img` tag to provide resized images suitable for various device sizes, which for example results in smaller images downloaded for mobile. + +## Setup + +Install: + +```bash +npm install --save @sveltejs/image +``` + +Adjust `vite.config.js`: + +```diff ++import { images } from '@sveltejs/image/vite'; +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [ ++ images({ + runtime: { + providers: { + default: '@sveltejs/image/providers/' + } + } + }), + sveltekit() + ] +}); +``` + +> `` refers to choosing one of the ready-to-use providers. We plan to add more providers over time. You can create your own by creating a JavaScript file with a `export function getURL({ src, width, height }): string` function inside and then pointing to that file in the config. + +In case of Vercel, also adjust your `svelte.config.js`: + +```diff +import adapter from '@sveltejs/adapter-vercel'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + kit: { +- adapter: adapter() ++ adapter: adapter({ images: true }) + } +}; + +export default config; +``` + +## Usage + +### Dynamic runtime optimization using one of the providers, i.e. an image CDN: + +```svelte + + +An alt text +``` + +`width` and `height` should be the intrinsic width/height of the referenced image - i.e. the dimensions of the image before styling. `alt` should describe the image. All are required. The `src` is transformed by calling `getURL` of the `default` provider provided in the `vite.config.js`. + +If you want to optimize an image from an external URL, add its domain to the `domains` config of the plugin. Note that you may also add a corresponding config to the image cdn (how exactly depends on the provider). + +### Static build time optimization: + +Static build time optimization uses `vite-imagetools`, which comes as an optional peer dependency, so you first need to install it: + +```bash +npm install --save-dev vite-imagetools +``` + +> If you have existing image imports like `import SomeImage from './some/image.jpg';` they will be treated differently now. If you want to get back the previous behavior of this import returning a URL string, add `?url` to the end of the import. + +Use in your `.svelte` components: + +```svelte + + +An alt text +``` + +This optimizes the image at build time using `vite-imagetools`. `width` and `height` are optional as they can be inferred from the source image. + +Note that the `src` prop must start with a `.`. `@sveltejs/image` includes a preprocessor that will convert it to the following: + +```svelte + + +An alt text +``` + +You can also write it yourself like this, if you want to. + +Note that the generated code is a `picture` tag (not an `img` tag as in the case of dynamic providers) wrapping one `source` tag per image type. + +### Choosing static vs dynamic + +Using the static provider generates the images at build time, so build time may take longer the more images you transform. + +Using an image CDN provides more flexibility with regards to sizes and you can pass image sources not known at build time, but it comes with potentially a bit of setup overhead (configuring the image CDN) and possibly usage cost. + +You can mix and match both solutions in one project. + +### `Image` component props + +There are a few things you can customize: + +- `priority`: set this to `true` for the most important/largest image on the page so it loads faster +- `sizes`: If your image is less than full width on one or more screen sizes, add this info here. When using dynamic providers the widths can be adjusted accordingly to produce a more optimal `srcset`. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/sizes) for more info on the attribute +- `style`: to style the image +- `class`: to style the image. Be aware that you need to pass classes that are global (i.e. wrapped in `:global()` when coming from a `