From a397b981f5f46dee85e6e00aa39633d018d4b9a2 Mon Sep 17 00:00:00 2001 From: Tony Sullivan Date: Wed, 27 Jul 2022 15:39:05 +0000 Subject: [PATCH] Fixes type definitions `@astrojs/image` and adds more documentation to the README (#4045) * WIP: moving to a static .d.ts types file * fixing named exports for getImage and getPicture * removing the exports.astro map for now * WIP: adding readme docs for component attributes * Adding docs for getImage and getPicture * leaning fully on TSC to build .d.ts files * finally found the solution for proper ESM import types * adding a note to the README for tsconfig updates * chore: add changesets * typo * docs: removing the "Images in Markdown" example * removing the need for publishing src to NPM * fix: make type re-export explicit * updating image module defs to match InputFormat * using astro syntax highlighting for README code blocks * nit: missing backtick in README * make sure Astro component directives aren't recommended twice --- .changeset/fifty-apricots-clap.md | 5 + .changeset/five-singers-notice.md | 5 + packages/astro/client-base.d.ts | 173 ++++++++++++++ packages/astro/client.d.ts | 174 +------------- packages/astro/package.json | 2 + packages/integrations/image/README.md | 224 ++++++++++++++++-- packages/integrations/image/client.d.ts | 55 +++++ .../integrations/image/components/Image.astro | 9 +- .../image/components/Picture.astro | 13 +- .../integrations/image/components/index.ts | 5 + packages/integrations/image/package.json | 13 +- packages/integrations/image/src/build/ssg.ts | 2 +- packages/integrations/image/src/index.ts | 119 +++++++++- .../integrations/image/src/integration.ts | 93 -------- .../integrations/image/src/lib/get-image.ts | 8 +- .../integrations/image/src/lib/get-picture.ts | 6 +- .../image/src/{types.ts => loaders/index.ts} | 44 +--- .../integrations/image/src/loaders/sharp.ts | 2 +- .../integrations/image/src/utils/images.ts | 2 +- .../integrations/image/src/utils/metadata.ts | 3 +- .../integrations/image/src/utils/paths.ts | 2 +- .../image/src/vite-plugin-astro-image.ts | 10 +- 22 files changed, 606 insertions(+), 363 deletions(-) create mode 100644 .changeset/fifty-apricots-clap.md create mode 100644 .changeset/five-singers-notice.md create mode 100644 packages/astro/client-base.d.ts create mode 100644 packages/integrations/image/client.d.ts delete mode 100644 packages/integrations/image/src/integration.ts rename packages/integrations/image/src/{types.ts => loaders/index.ts} (72%) diff --git a/.changeset/fifty-apricots-clap.md b/.changeset/fifty-apricots-clap.md new file mode 100644 index 000000000..2fcf04fe7 --- /dev/null +++ b/.changeset/fifty-apricots-clap.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Adding support for custom "astro/client" type definitions in `@astrojs/image` diff --git a/.changeset/five-singers-notice.md b/.changeset/five-singers-notice.md new file mode 100644 index 000000000..2e1a930ee --- /dev/null +++ b/.changeset/five-singers-notice.md @@ -0,0 +1,5 @@ +--- +'@astrojs/image': minor +--- + +Big improvements to the TypeScript and Language Tools support for `@astrojs/image` :tada: diff --git a/packages/astro/client-base.d.ts b/packages/astro/client-base.d.ts new file mode 100644 index 000000000..1e65d7f71 --- /dev/null +++ b/packages/astro/client-base.d.ts @@ -0,0 +1,173 @@ +/// + +// CSS modules +type CSSModuleClasses = { readonly [key: string]: string }; + +declare module '*.module.css' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.scss' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.sass' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.less' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.styl' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.stylus' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.pcss' { + const classes: CSSModuleClasses; + export default classes; +} + +// CSS +declare module '*.css' { + const css: string; + export default css; +} +declare module '*.scss' { + const css: string; + export default css; +} +declare module '*.sass' { + const css: string; + export default css; +} +declare module '*.less' { + const css: string; + export default css; +} +declare module '*.styl' { + const css: string; + export default css; +} +declare module '*.stylus' { + const css: string; + export default css; +} +declare module '*.pcss' { + const css: string; + export default css; +} + +// Built-in asset types +// see `src/constants.ts` + +// media +declare module '*.mp4' { + const src: string; + export default src; +} +declare module '*.webm' { + const src: string; + export default src; +} +declare module '*.ogg' { + const src: string; + export default src; +} +declare module '*.mp3' { + const src: string; + export default src; +} +declare module '*.wav' { + const src: string; + export default src; +} +declare module '*.flac' { + const src: string; + export default src; +} +declare module '*.aac' { + const src: string; + export default src; +} + +// fonts +declare module '*.woff' { + const src: string; + export default src; +} +declare module '*.woff2' { + const src: string; + export default src; +} +declare module '*.eot' { + const src: string; + export default src; +} +declare module '*.ttf' { + const src: string; + export default src; +} +declare module '*.otf' { + const src: string; + export default src; +} + +// other +declare module '*.wasm' { + const initWasm: (options: WebAssembly.Imports) => Promise; + export default initWasm; +} +declare module '*.webmanifest' { + const src: string; + export default src; +} +declare module '*.pdf' { + const src: string; + export default src; +} +declare module '*.txt' { + const src: string; + export default src; +} + +// web worker +declare module '*?worker' { + const workerConstructor: { + new (): Worker; + }; + export default workerConstructor; +} + +declare module '*?worker&inline' { + const workerConstructor: { + new (): Worker; + }; + export default workerConstructor; +} + +declare module '*?sharedworker' { + const sharedWorkerConstructor: { + new (): SharedWorker; + }; + export default sharedWorkerConstructor; +} + +declare module '*?raw' { + const src: string; + export default src; +} + +declare module '*?url' { + const src: string; + export default src; +} + +declare module '*?inline' { + const src: string; + export default src; +} diff --git a/packages/astro/client.d.ts b/packages/astro/client.d.ts index cfbb65ef0..44da8654b 100644 --- a/packages/astro/client.d.ts +++ b/packages/astro/client.d.ts @@ -1,69 +1,4 @@ -/// - -// CSS modules -type CSSModuleClasses = { readonly [key: string]: string }; - -declare module '*.module.css' { - const classes: CSSModuleClasses; - export default classes; -} -declare module '*.module.scss' { - const classes: CSSModuleClasses; - export default classes; -} -declare module '*.module.sass' { - const classes: CSSModuleClasses; - export default classes; -} -declare module '*.module.less' { - const classes: CSSModuleClasses; - export default classes; -} -declare module '*.module.styl' { - const classes: CSSModuleClasses; - export default classes; -} -declare module '*.module.stylus' { - const classes: CSSModuleClasses; - export default classes; -} -declare module '*.module.pcss' { - const classes: CSSModuleClasses; - export default classes; -} - -// CSS -declare module '*.css' { - const css: string; - export default css; -} -declare module '*.scss' { - const css: string; - export default css; -} -declare module '*.sass' { - const css: string; - export default css; -} -declare module '*.less' { - const css: string; - export default css; -} -declare module '*.styl' { - const css: string; - export default css; -} -declare module '*.stylus' { - const css: string; - export default css; -} -declare module '*.pcss' { - const css: string; - export default css; -} - -// Built-in asset types -// see `src/constants.ts` +/// // images declare module '*.jpg' { @@ -98,110 +33,3 @@ declare module '*.avif' { const src: string; export default src; } - -// media -declare module '*.mp4' { - const src: string; - export default src; -} -declare module '*.webm' { - const src: string; - export default src; -} -declare module '*.ogg' { - const src: string; - export default src; -} -declare module '*.mp3' { - const src: string; - export default src; -} -declare module '*.wav' { - const src: string; - export default src; -} -declare module '*.flac' { - const src: string; - export default src; -} -declare module '*.aac' { - const src: string; - export default src; -} - -// fonts -declare module '*.woff' { - const src: string; - export default src; -} -declare module '*.woff2' { - const src: string; - export default src; -} -declare module '*.eot' { - const src: string; - export default src; -} -declare module '*.ttf' { - const src: string; - export default src; -} -declare module '*.otf' { - const src: string; - export default src; -} - -// other -declare module '*.wasm' { - const initWasm: (options: WebAssembly.Imports) => Promise; - export default initWasm; -} -declare module '*.webmanifest' { - const src: string; - export default src; -} -declare module '*.pdf' { - const src: string; - export default src; -} -declare module '*.txt' { - const src: string; - export default src; -} - -// web worker -declare module '*?worker' { - const workerConstructor: { - new (): Worker; - }; - export default workerConstructor; -} - -declare module '*?worker&inline' { - const workerConstructor: { - new (): Worker; - }; - export default workerConstructor; -} - -declare module '*?sharedworker' { - const sharedWorkerConstructor: { - new (): SharedWorker; - }; - export default sharedWorkerConstructor; -} - -declare module '*?raw' { - const src: string; - export default src; -} - -declare module '*?url' { - const src: string; - export default src; -} - -declare module '*?inline' { - const src: string; - export default src; -} diff --git a/packages/astro/package.json b/packages/astro/package.json index 8b56e1500..19943f0c5 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -27,6 +27,7 @@ ".": "./astro.js", "./env": "./env.d.ts", "./client": "./client.d.ts", + "./client-base": "./client-base.d.ts", "./astro-jsx": "./astro-jsx.d.ts", "./jsx/*": "./dist/jsx/*", "./jsx-runtime": "./dist/jsx-runtime/index.js", @@ -65,6 +66,7 @@ "config.mjs", "env.d.ts", "client.d.ts", + "client-base.d.ts", "astro-jsx.d.ts", "README.md", "vendor" diff --git a/packages/integrations/image/README.md b/packages/integrations/image/README.md index c24a84e17..a5a4438c1 100644 --- a/packages/integrations/image/README.md +++ b/packages/integrations/image/README.md @@ -63,11 +63,205 @@ export default { Then, restart the dev server. +### Update `tsconfig.json` + +For the best development experience, add the integrations type definitions to your project's `tsconfig.json` file. + +```json +{ + "compilerOptions": { + // Replace `astro/client` with `@astrojs/image/client` + "types": ["@astrojs/image/client"] + } +} +``` + ## Usage +The included `sharp` transformer supports resizing images and encoding them to different image formats. Third-party image services will be able to add support for custom transformations as well (ex: `blur`, `filter`, `rotate`, etc). + +### `` + The built-in `` component is used to create an optimized `` for both remote images hosted on other domains as well as local images imported from your project's `src` directory. -The included `sharp` transformer supports resizing images and encoding them to different image formats. Third-party image services will be able to add support for custom transformations as well (ex: `blur`, `filter`, `rotate`, etc). +In addition to the component-specific properties, any valid HTML attribute for the `` included in the `` component will be included in the built ``. + +#### src + +

+ +**Type:** `string` | `ImageMetadata` | `Promise`
+**Required:** `true` +

+ +Source for the original image file. + +For images in your project's repository, use the `src` relative to the `public` directory. For remote images, provide the full URL. + +#### format + +

+ +**Type:** 'avif' | 'jpeg' | 'png' | 'webp'
+**Default:** `undefined` +

+ +The output format to be used in the optimized image. The original image format will be used if `format` is not provided. + +#### quality + +

+ +**Type:** `number`
+**Default:** `undefined` +

+ +The compression quality used during optimization. The image service will use a default quality if not provided. + +#### width + +

+ +**Type:** `number`
+**Default:** `undefined` +

+ +The desired width of the output image. Combine with `height` to crop the image to an exact size, or `aspectRatio` to automatically calculate and crop the height. + +Dimensions are optional for local images, the original image size will be used if not provided. + +For remote images, the integration needs to be able to calculate dimensions for the optimized image. This can be done by providing `width` and `height` or by providing one dimension and an `aspectRatio`. + +#### height + +

+ +**Type:** `number`
+**Default:** `undefined` +

+ +The desired height of the output image. Combine with `width` to crop the image to an exact size, or `aspectRatio` to automatically calculate and crop the width. + +Dimensions are optional for local images, the original image size will be used if not provided. + +For remote images, the integration needs to be able to calculate dimensions for the optimized image. This can be done by providing `width` and `height` or by providing one dimension and an `aspectRatio`. + +#### aspectRatio + +

+ +**Type:** `number` | `string`
+**Default:** `undefined` +

+ +The desired aspect ratio of the output image. Combine with either `width` or `height` to automatically calculate and crop the other dimension. + +A `string` can be provided in the form of `{width}:{height}`, ex: `16:9` or `3:4`. + +A `number` can also be provided, useful when the aspect ratio is calculated at build time. This can be an inline number such as `1.777` or inlined as a JSX expression like `aspectRatio={16/9}`. + +### ` + +#### src + +

+ +**Type:** `string` | `ImageMetadata` | `Promise`
+**Required:** `true` +

+ +Source for the original image file. + +For images in your project's repository, use the `src` relative to the `public` directory. For remote images, provide the full URL. + +#### alt + +

+ +**Type:** `string`
+**Default:** `undefined` +

+ +If provided, the `alt` string will be included on the built `` element. + +#### sizes + +

+ +**Type:** `string`
+**Required:** `true` +

+ +The HTMLImageElement property `sizes` allows you to specify the layout width of the image for each of a list of media conditions. + +See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/sizes) for more details. + +#### widths + +

+ +**Type:** `number[]`
+**Reuqired:** `true` +

+ +The list of sizes that should be built for responsive images. This is combined with `aspectRatio` to calculate the final dimensions of each built image. + +```astro +// Builds three images: 400x400, 800x800, and 1200x1200 + +``` + +#### aspectRatio + +

+ +**Type:** `number` | `string`
+**Required:** `true` +

+ +The desired aspect ratio of the output image. This is combined with `widhts` to calculate the final dimensions of each built image. + +A `string` can be provided in the form of `{width}:{height}`, ex: `16:9` or `3:4`. + +A `number` can also be provided, useful when the aspect ratio is calculated at build time. This can be an inline number such as `1.777` or inlined as a JSX expression like `aspectRatio={16/9}`. + +#### formats + +

+ +**Type:** Array<'avif' | 'jpeg' | 'png' | 'webp'>
+**Default:** `undefined` +

+ +The output formats to be used in the optimized image. If not provided, `webp` and `avif` will be used in addition to the original image format. + +### `getImage` + +This is the helper function used by the `` component to build `` attributes for the transformed image. This helper can be used directly for more complex use cases that aren't currently supported by the `` component. + +This helper takes in an object with the same properties as the `` component and returns an object with attributes that should be included on the final `` element. + +This can helpful if you need to add preload links to a page's ``. + +```astro +--- +import { getImage } from '@astrojs/image'; + +const { src } = await getImage('../assets/hero.png'); +--- + + + + + + +``` + +### `getPicture` + +This is the helper function used by the `` component to build multiple sizes and formats for responsive images. This helper can be used directly for more complex use cases that aren't currently supported by the `` component. + +This helper takes in an object with the same properties as the `` component and returns an object attributes that should be included on the final `` element **and** a list of sources that should be used to render all ``s for the `` element. ## Configuration @@ -102,7 +296,7 @@ export default { Image files in your project's `src` directory can be imported in frontmatter and passed directly to the `` component. All other properties are optional and will default to the original image file's properties if not provided. -```html +```astro --- import { Image } from '@astrojs/image/components'; import heroImage from '../assets/hero.png'; @@ -130,7 +324,7 @@ import heroImage from '../assets/hero.png'; Remote images can be transformed with the `` component. The `` component needs to know the final dimensions for the `` element to avoid content layout shifts. For remote images, this means you must either provide `width` and `height`, or one of the dimensions plus the required `aspectRatio`. -```html +```astro --- import { Image } from '@astrojs/image/components'; @@ -148,28 +342,6 @@ const imageUrl = 'https://www.google.com/images/branding/googlelogo/2x/googlelog ``` -
-Images in markdown - - The `` component can also be used to optimize images in markdown pages. For local images imported from your project's `src` directory, use Astro's the `setup` frontmatter to import the image file. - -```html ---- -setup: | - import { Image } from '@astrojs/image/components' - import hero from '../../assets/blog/introducing-astro.jpg' -title: Hello world! -publishDate: 12 Sep 2021 -name: Nate Moore -value: 128 -description: Just a Hello World Post! ---- - - - -``` -
-
Responsive pictures @@ -179,7 +351,7 @@ description: Just a Hello World Post! For remote images, an `aspectRatio` is required to ensure the correct `height` can be calculated at build time. -```html +```astro --- import { Picture } from '@astrojs/image/components'; import hero from '../assets/hero.png'; diff --git a/packages/integrations/image/client.d.ts b/packages/integrations/image/client.d.ts new file mode 100644 index 000000000..c736d2f44 --- /dev/null +++ b/packages/integrations/image/client.d.ts @@ -0,0 +1,55 @@ +type InputFormat = + | 'avif' + | 'gif' + | 'heic' + | 'heif' + | 'jpeg' + | 'jpg' + | 'png' + | 'tiff' + | 'webp'; + +interface ImageMetadata { + src: string; + width: number; + height: number; + format: InputFormat; +} + +// images +declare module '*.avif' { + const metadata: ImageMetadata; + export default metadata; +} +declare module '*.gif' { + const metadata: ImageMetadata; + export default metadata; +} +declare module '*.heic' { + const metadata: ImageMetadata; + export default metadata; +} +declare module '*.heif' { + const metadata: ImageMetadata; + export default metadata; +} +declare module '*.jpeg' { + const metadata: ImageMetadata; + export default metadata; +} +declare module '*.jpg' { + const metadata: ImageMetadata; + export default metadata; +} +declare module '*.png' { + const metadata: ImageMetadata; + export default metadata; +} +declare module '*.tiff' { + const metadata: ImageMetadata; + export default metadata; +} +declare module '*.webp' { + const metadata: ImageMetadata; + export default metadata; +} diff --git a/packages/integrations/image/components/Image.astro b/packages/integrations/image/components/Image.astro index 18e35d1a6..c38814c9e 100644 --- a/packages/integrations/image/components/Image.astro +++ b/packages/integrations/image/components/Image.astro @@ -1,13 +1,16 @@ --- // @ts-ignore import { getImage } from '../dist/index.js'; -import type { ImageAttributes, ImageMetadata, TransformOptions, OutputFormat } from '../dist/types'; +import type { ImgHTMLAttributes } from './index.js'; +import type { ImageMetadata, TransformOptions, OutputFormat } from '../dist/index.js'; -export interface LocalImageProps extends Omit, Omit { +interface LocalImageProps extends + Omit, + Omit { src: ImageMetadata | Promise<{ default: ImageMetadata }>; } -export interface RemoteImageProps extends TransformOptions, ImageAttributes { +interface RemoteImageProps extends TransformOptions, astroHTML.JSX.ImgHTMLAttributes { src: string; format: OutputFormat; width: number; diff --git a/packages/integrations/image/components/Picture.astro b/packages/integrations/image/components/Picture.astro index badfc7f46..372fcbb1b 100644 --- a/packages/integrations/image/components/Picture.astro +++ b/packages/integrations/image/components/Picture.astro @@ -1,8 +1,12 @@ --- import { getPicture } from '../dist/index.js'; -import type { ImageAttributes, ImageMetadata, OutputFormat, PictureAttributes, TransformOptions } from '../dist/types'; +import type { ImgHTMLAttributes, HTMLAttributes } from './index.js'; +import type { ImageMetadata, OutputFormat, TransformOptions } from '../dist/index.js'; -export interface LocalImageProps extends Omit, Omit, Pick { +interface LocalImageProps extends + Omit, + Omit, + Pick { src: ImageMetadata | Promise<{ default: ImageMetadata }>; alt?: string; sizes: HTMLImageElement['sizes']; @@ -10,7 +14,10 @@ export interface LocalImageProps extends Omit, TransformOptions, Pick { +interface RemoteImageProps extends + Omit, + TransformOptions, + Pick { src: string; alt?: string; sizes: HTMLImageElement['sizes']; diff --git a/packages/integrations/image/components/index.ts b/packages/integrations/image/components/index.ts index be0e10130..088278fc2 100644 --- a/packages/integrations/image/components/index.ts +++ b/packages/integrations/image/components/index.ts @@ -1,2 +1,7 @@ +/// export { default as Image } from './Image.astro'; export { default as Picture } from './Picture.astro'; + +// TODO: should these directives be removed from astroHTML.JSX? +export type ImgHTMLAttributes = Omit; +export type HTMLAttributes = Omit; diff --git a/packages/integrations/image/package.json b/packages/integrations/image/package.json index e1dfeed8e..fb8635eb1 100644 --- a/packages/integrations/image/package.json +++ b/packages/integrations/image/package.json @@ -3,7 +3,7 @@ "description": "Load and transform images in your Astro site.", "version": "0.2.0", "type": "module", - "types": "./dist/types.d.ts", + "types": "./dist/index.d.ts", "author": "withastro", "license": "MIT", "repository": { @@ -20,21 +20,18 @@ "bugs": "https://github.com/withastro/astro/issues", "homepage": "https://docs.astro.build/en/guides/integrations-guide/image/", "exports": { - ".": { - "astro": "./components/index.js", - "import": "./dist/index.js" - }, + ".": "./dist/index.js", "./sharp": "./dist/loaders/sharp.js", "./endpoints/dev": "./dist/endpoints/dev.js", "./endpoints/prod": "./dist/endpoints/prod.js", "./components": "./components/index.js", - "./package.json": "./package.json" + "./package.json": "./package.json", + "./client": "./client.d.ts" }, "files": [ "components", "dist", - "src", - "types" + "client.d.ts" ], "scripts": { "build": "astro-scripts build \"src/**/*.ts\" && tsc", diff --git a/packages/integrations/image/src/build/ssg.ts b/packages/integrations/image/src/build/ssg.ts index 78510889c..496905f92 100644 --- a/packages/integrations/image/src/build/ssg.ts +++ b/packages/integrations/image/src/build/ssg.ts @@ -2,7 +2,7 @@ import fs from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { OUTPUT_DIR } from '../constants.js'; -import type { SSRImageService, TransformOptions } from '../types.js'; +import type { SSRImageService, TransformOptions } from '../loaders/index.js'; import { isRemoteImage, loadLocalImage, loadRemoteImage } from '../utils/images.js'; import { ensureDir } from '../utils/paths.js'; diff --git a/packages/integrations/image/src/index.ts b/packages/integrations/image/src/index.ts index 81ef8c6b9..234b1646d 100644 --- a/packages/integrations/image/src/index.ts +++ b/packages/integrations/image/src/index.ts @@ -1,5 +1,116 @@ -import integration from './integration.js'; -export * from './lib/get-image.js'; -export * from './lib/get-picture.js'; +import type { AstroConfig, AstroIntegration } from 'astro'; +import { ssgBuild } from './build/ssg.js'; +import { ssrBuild } from './build/ssr.js'; +import { PKG_NAME, ROUTE_PATTERN } from './constants.js'; +import { ImageService, TransformOptions } from './loaders/index.js'; +import { filenameFormat, propsToFilename } from './utils/paths.js'; +import { createPlugin } from './vite-plugin-astro-image.js'; -export default integration; +export { getImage } from './lib/get-image.js'; +export { getPicture } from './lib/get-picture.js'; +export * from './loaders/index.js'; +export type { ImageMetadata} from './vite-plugin-astro-image.js'; + +interface ImageIntegration { + loader?: ImageService; + addStaticImage?: (transform: TransformOptions) => void; + filenameFormat?: (transform: TransformOptions, searchParams: URLSearchParams) => string; +} + +declare global { + // eslint-disable-next-line no-var + var astroImage: ImageIntegration | undefined; +} + +export interface IntegrationOptions { + /** + * Entry point for the @type {HostedImageService} or @type {LocalImageService} to be used. + */ + serviceEntryPoint?: string; +} + +export default function integration(options: IntegrationOptions = {}): AstroIntegration { + const resolvedOptions = { + serviceEntryPoint: '@astrojs/image/sharp', + ...options, + }; + + // During SSG builds, this is used to track all transformed images required. + const staticImages = new Map>(); + + let _config: AstroConfig; + let output: 'server' | 'static'; + + function getViteConfiguration() { + return { + plugins: [createPlugin(_config, resolvedOptions)], + optimizeDeps: { + include: ['image-size', 'sharp'], + }, + ssr: { + noExternal: ['@astrojs/image', resolvedOptions.serviceEntryPoint], + }, + }; + } + + return { + name: PKG_NAME, + hooks: { + 'astro:config:setup': ({ command, config, injectRoute, updateConfig }) => { + _config = config; + + // Always treat `astro dev` as SSR mode, even without an adapter + output = command === 'dev' ? 'server' : config.output; + + updateConfig({ vite: getViteConfiguration() }); + + if (output === 'server') { + injectRoute({ + pattern: ROUTE_PATTERN, + entryPoint: + command === 'dev' ? '@astrojs/image/endpoints/dev' : '@astrojs/image/endpoints/prod', + }); + } + }, + 'astro:server:setup': async () => { + globalThis.astroImage = {}; + }, + 'astro:build:setup': () => { + // Used to cache all images rendered to HTML + // Added to globalThis to share the same map in Node and Vite + function addStaticImage(transform: TransformOptions) { + const srcTranforms = staticImages.has(transform.src) + ? staticImages.get(transform.src)! + : new Map(); + + srcTranforms.set(propsToFilename(transform), transform); + + staticImages.set(transform.src, srcTranforms); + } + + // Helpers for building static images should only be available for SSG + globalThis.astroImage = + output === 'static' + ? { + addStaticImage, + filenameFormat, + } + : {}; + }, + 'astro:build:done': async ({ dir }) => { + if (output === 'server') { + // for SSR builds, copy all image files from src to dist + // to make sure they are available for use in production + await ssrBuild({ srcDir: _config.srcDir, outDir: dir }); + } else { + // for SSG builds, build all requested image transforms to dist + const loader = globalThis?.astroImage?.loader; + + if (loader && 'transform' in loader && staticImages.size > 0) { + await ssgBuild({ loader, staticImages, srcDir: _config.srcDir, outDir: dir }); + } + } + }, + }, + }; +} diff --git a/packages/integrations/image/src/integration.ts b/packages/integrations/image/src/integration.ts deleted file mode 100644 index 725276d03..000000000 --- a/packages/integrations/image/src/integration.ts +++ /dev/null @@ -1,93 +0,0 @@ -import type { AstroConfig, AstroIntegration } from 'astro'; -import { ssgBuild } from './build/ssg.js'; -import { ssrBuild } from './build/ssr.js'; -import { PKG_NAME, ROUTE_PATTERN } from './constants.js'; -import { IntegrationOptions, TransformOptions } from './types.js'; -import { filenameFormat, propsToFilename } from './utils/paths.js'; -import { createPlugin } from './vite-plugin-astro-image.js'; - -export default function integration(options: IntegrationOptions = {}): AstroIntegration { - const resolvedOptions = { - serviceEntryPoint: '@astrojs/image/sharp', - ...options, - }; - - // During SSG builds, this is used to track all transformed images required. - const staticImages = new Map>(); - - let _config: AstroConfig; - let output: 'server' | 'static'; - - function getViteConfiguration() { - return { - plugins: [createPlugin(_config, resolvedOptions)], - optimizeDeps: { - include: ['image-size', 'sharp'], - }, - ssr: { - noExternal: ['@astrojs/image', resolvedOptions.serviceEntryPoint], - }, - }; - } - - return { - name: PKG_NAME, - hooks: { - 'astro:config:setup': ({ command, config, injectRoute, updateConfig }) => { - _config = config; - - // Always treat `astro dev` as SSR mode, even without an adapter - output = command === 'dev' ? 'server' : config.output; - - updateConfig({ vite: getViteConfiguration() }); - - if (output === 'server') { - injectRoute({ - pattern: ROUTE_PATTERN, - entryPoint: - command === 'dev' ? '@astrojs/image/endpoints/dev' : '@astrojs/image/endpoints/prod', - }); - } - }, - 'astro:server:setup': async () => { - globalThis.astroImage = {}; - }, - 'astro:build:setup': () => { - // Used to cache all images rendered to HTML - // Added to globalThis to share the same map in Node and Vite - function addStaticImage(transform: TransformOptions) { - const srcTranforms = staticImages.has(transform.src) - ? staticImages.get(transform.src)! - : new Map(); - - srcTranforms.set(propsToFilename(transform), transform); - - staticImages.set(transform.src, srcTranforms); - } - - // Helpers for building static images should only be available for SSG - globalThis.astroImage = - output === 'static' - ? { - addStaticImage, - filenameFormat, - } - : {}; - }, - 'astro:build:done': async ({ dir }) => { - if (output === 'server') { - // for SSR builds, copy all image files from src to dist - // to make sure they are available for use in production - await ssrBuild({ srcDir: _config.srcDir, outDir: dir }); - } else { - // for SSG builds, build all requested image transforms to dist - const loader = globalThis?.astroImage?.loader; - - if (loader && 'transform' in loader && staticImages.size > 0) { - await ssgBuild({ loader, staticImages, srcDir: _config.srcDir, outDir: dir }); - } - } - }, - }, - }; -} diff --git a/packages/integrations/image/src/lib/get-image.ts b/packages/integrations/image/src/lib/get-image.ts index e0f57e873..0a28ff3ac 100644 --- a/packages/integrations/image/src/lib/get-image.ts +++ b/packages/integrations/image/src/lib/get-image.ts @@ -1,15 +1,15 @@ +/// import slash from 'slash'; import { ROUTE_PATTERN } from '../constants.js'; import sharp from '../loaders/sharp.js'; import { - ImageAttributes, - ImageMetadata, ImageService, isSSRService, OutputFormat, TransformOptions, -} from '../types.js'; +} from '../loaders/index.js'; import { isRemoteImage, parseAspectRatio } from '../utils/images.js'; +import { ImageMetadata } from '../vite-plugin-astro-image.js'; export interface GetImageTransform extends Omit { src: string | ImageMetadata | Promise<{ default: ImageMetadata }>; @@ -101,7 +101,7 @@ async function resolveTransform(input: GetImageTransform): Promise` element. */ -export async function getImage(transform: GetImageTransform): Promise { +export async function getImage(transform: GetImageTransform): Promise { if (!transform.src) { throw new Error('[@astrojs/image] `src` is required'); } diff --git a/packages/integrations/image/src/lib/get-picture.ts b/packages/integrations/image/src/lib/get-picture.ts index 82bf9f7c5..0b9521853 100644 --- a/packages/integrations/image/src/lib/get-picture.ts +++ b/packages/integrations/image/src/lib/get-picture.ts @@ -1,7 +1,9 @@ +/// import { lookup } from 'mrmime'; import { extname } from 'node:path'; -import { ImageAttributes, ImageMetadata, OutputFormat, TransformOptions } from '../types.js'; +import { OutputFormat, TransformOptions } from '../loaders/index.js'; import { parseAspectRatio } from '../utils/images.js'; +import { ImageMetadata } from '../vite-plugin-astro-image.js'; import { getImage } from './get-image.js'; export interface GetPictureParams { @@ -12,7 +14,7 @@ export interface GetPictureParams { } export interface GetPictureResult { - image: ImageAttributes; + image: astroHTML.JSX.HTMLAttributes; sources: { type: string; srcset: string }[]; } diff --git a/packages/integrations/image/src/types.ts b/packages/integrations/image/src/loaders/index.ts similarity index 72% rename from packages/integrations/image/src/types.ts rename to packages/integrations/image/src/loaders/index.ts index f7e0c0e5f..7681f25d4 100644 --- a/packages/integrations/image/src/types.ts +++ b/packages/integrations/image/src/loaders/index.ts @@ -1,17 +1,4 @@ /// -export * from './index.js'; - -interface ImageIntegration { - loader?: ImageService; - addStaticImage?: (transform: TransformOptions) => void; - filenameFormat?: (transform: TransformOptions, searchParams: URLSearchParams) => string; -} - -declare global { - // eslint-disable-next-line no-var - var astroImage: ImageIntegration | undefined; -} - export type InputFormat = | 'heic' | 'heif' @@ -25,21 +12,6 @@ export type InputFormat = export type OutputFormat = 'avif' | 'jpeg' | 'png' | 'webp'; -/** - * Converts a set of image transforms to the filename to use when building for static. - * - * This is only used for static production builds and ignored when an SSR adapter is used, - * or in `astro dev` for static builds. - */ -export type FilenameFormatter = (transform: TransformOptions) => string; - -export interface IntegrationOptions { - /** - * Entry point for the @type {HostedImageService} or @type {LocalImageService} to be used. - */ - serviceEntryPoint?: string; -} - /** * Defines the original image and transforms that need to be applied to it. */ @@ -83,22 +55,19 @@ export interface TransformOptions { aspectRatio?: number | `${number}:${number}`; } -export type ImageAttributes = astroHTML.JSX.ImgHTMLAttributes; -export type PictureAttributes = astroHTML.JSX.HTMLAttributes; - export interface HostedImageService { /** * Gets the HTML attributes needed for the server rendered `` element. */ - getImageAttributes(transform: T): Promise; + getImageAttributes(transform: T): Promise; } export interface SSRImageService extends HostedImageService { /** - * Gets tthe HTML attributes needed for the server rendered `` element. + * Gets the HTML attributes needed for the server rendered `` element. */ - getImageAttributes(transform: T): Promise>; + getImageAttributes(transform: T): Promise>; /** * Serializes image transformation properties to URLSearchParams, used to build * the final `src` that points to the self-hosted SSR endpoint. @@ -134,10 +103,3 @@ export function isHostedService(service: ImageService): service is ImageService export function isSSRService(service: ImageService): service is SSRImageService { return 'transform' in service; } - -export interface ImageMetadata { - src: string; - width: number; - height: number; - format: InputFormat; -} diff --git a/packages/integrations/image/src/loaders/sharp.ts b/packages/integrations/image/src/loaders/sharp.ts index b4c5e18fd..f76a16d39 100644 --- a/packages/integrations/image/src/loaders/sharp.ts +++ b/packages/integrations/image/src/loaders/sharp.ts @@ -1,5 +1,5 @@ import sharp from 'sharp'; -import type { OutputFormat, SSRImageService, TransformOptions } from '../types.js'; +import type { OutputFormat, SSRImageService, TransformOptions } from './index.js'; import { isAspectRatioString, isOutputFormat } from '../utils/images.js'; class SharpService implements SSRImageService { diff --git a/packages/integrations/image/src/utils/images.ts b/packages/integrations/image/src/utils/images.ts index d36c64fc0..f3c519657 100644 --- a/packages/integrations/image/src/utils/images.ts +++ b/packages/integrations/image/src/utils/images.ts @@ -1,5 +1,5 @@ import fs from 'node:fs/promises'; -import type { OutputFormat, TransformOptions } from '../types.js'; +import type { OutputFormat, TransformOptions } from '../loaders/index.js'; export function isOutputFormat(value: string): value is OutputFormat { return ['avif', 'jpeg', 'png', 'webp'].includes(value); diff --git a/packages/integrations/image/src/utils/metadata.ts b/packages/integrations/image/src/utils/metadata.ts index fa60281b6..349a37535 100644 --- a/packages/integrations/image/src/utils/metadata.ts +++ b/packages/integrations/image/src/utils/metadata.ts @@ -1,6 +1,7 @@ import sizeOf from 'image-size'; import fs from 'node:fs/promises'; -import { ImageMetadata, InputFormat } from '../types.js'; +import { InputFormat } from '../loaders/index.js'; +import { ImageMetadata } from '../vite-plugin-astro-image.js'; export async function metadata(src: string): Promise { const file = await fs.readFile(src); diff --git a/packages/integrations/image/src/utils/paths.ts b/packages/integrations/image/src/utils/paths.ts index a958d4911..6258d392d 100644 --- a/packages/integrations/image/src/utils/paths.ts +++ b/packages/integrations/image/src/utils/paths.ts @@ -1,7 +1,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { OUTPUT_DIR } from '../constants.js'; -import type { TransformOptions } from '../types.js'; +import type { TransformOptions } from '../loaders/index.js'; import { isRemoteImage } from './images.js'; import { shorthash } from './shorthash.js'; diff --git a/packages/integrations/image/src/vite-plugin-astro-image.ts b/packages/integrations/image/src/vite-plugin-astro-image.ts index 81593c142..aefc910bb 100644 --- a/packages/integrations/image/src/vite-plugin-astro-image.ts +++ b/packages/integrations/image/src/vite-plugin-astro-image.ts @@ -3,9 +3,17 @@ import { pathToFileURL } from 'node:url'; import type { PluginContext } from 'rollup'; import slash from 'slash'; import type { Plugin, ResolvedConfig } from 'vite'; -import type { IntegrationOptions } from './types.js'; +import type { IntegrationOptions } from './index.js'; +import type { InputFormat } from './loaders/index.js'; import { metadata } from './utils/metadata.js'; +export interface ImageMetadata { + src: string; + width: number; + height: number; + format: InputFormat; +} + export function createPlugin(config: AstroConfig, options: Required): Plugin { const filter = (id: string) => /^(?!\/_image?).*.(heic|heif|avif|jpeg|jpg|png|tiff|webp|gif)$/.test(id);