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
This commit is contained in:
Tony Sullivan 2022-07-27 15:39:05 +00:00 committed by GitHub
parent 55c8aced44
commit a397b981f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 606 additions and 363 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Adding support for custom "astro/client" type definitions in `@astrojs/image`

View file

@ -0,0 +1,5 @@
---
'@astrojs/image': minor
---
Big improvements to the TypeScript and Language Tools support for `@astrojs/image` :tada:

173
packages/astro/client-base.d.ts vendored Normal file
View file

@ -0,0 +1,173 @@
/// <reference types="vite/types/importMeta" />
// 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<WebAssembly.Exports>;
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;
}

View file

@ -1,69 +1,4 @@
/// <reference types="vite/types/importMeta" />
// 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`
/// <reference path="./client-base.d.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<WebAssembly.Exports>;
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;
}

View file

@ -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"

View file

@ -63,11 +63,205 @@ export default {
Then, restart the dev server.
</details>
### 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).
### `<Image />`
The built-in `<Image />` component is used to create an optimized `<img />` 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 `<img />` included in the `<Image />` component will be included in the built `<img />`.
#### src
<p>
**Type:** `string` | `ImageMetadata` | `Promise<ImageMetadata>`<br>
**Required:** `true`
</p>
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
<p>
**Type:** 'avif' | 'jpeg' | 'png' | 'webp'<br>
**Default:** `undefined`
</p>
The output format to be used in the optimized image. The original image format will be used if `format` is not provided.
#### quality
<p>
**Type:** `number`<br>
**Default:** `undefined`
</p>
The compression quality used during optimization. The image service will use a default quality if not provided.
#### width
<p>
**Type:** `number`<br>
**Default:** `undefined`
</p>
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
<p>
**Type:** `number`<br>
**Default:** `undefined`
</p>
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
<p>
**Type:** `number` | `string`<br>
**Default:** `undefined`
</p>
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}`.
### `<Picture /`>
#### src
<p>
**Type:** `string` | `ImageMetadata` | `Promise<ImageMetadata>`<br>
**Required:** `true`
</p>
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
<p>
**Type:** `string`<br>
**Default:** `undefined`
</p>
If provided, the `alt` string will be included on the built `<img />` element.
#### sizes
<p>
**Type:** `string`<br>
**Required:** `true`
</p>
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
<p>
**Type:** `number[]`<br>
**Reuqired:** `true`
</p>
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
<Picture src={...} widths={[400, 800, 1200]} aspectRatio="1:1" />
```
#### aspectRatio
<p>
**Type:** `number` | `string`<br>
**Required:** `true`
</p>
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
<p>
**Type:** Array<'avif' | 'jpeg' | 'png' | 'webp'><br>
**Default:** `undefined`
</p>
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 `<Image />` component to build `<img />` attributes for the transformed image. This helper can be used directly for more complex use cases that aren't currently supported by the `<Image />` component.
This helper takes in an object with the same properties as the `<Image />` component and returns an object with attributes that should be included on the final `<img />` element.
This can helpful if you need to add preload links to a page's `<head>`.
```astro
---
import { getImage } from '@astrojs/image';
const { src } = await getImage('../assets/hero.png');
---
<html>
<head>
<link rel="preload" as="image" href={src}>
</head>
</html>
```
### `getPicture`
This is the helper function used by the `<Picture />` 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 `<Picture />` component.
This helper takes in an object with the same properties as the `<Picture />` component and returns an object attributes that should be included on the final `<img />` element **and** a list of sources that should be used to render all `<source>`s for the `<picture>` 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 `<Image />` 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 `<Image />` component. The `<Image />` component needs to know the final dimensions for the `<img />` 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
```
</details>
<details>
<summary><strong>Images in markdown</strong></summary>
The `<Image />` 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!
---
<Image src={hero} width={640} />
<Image src="https://example.com/image.jpg" width={640} aspectRatio="16:9" />
```
</details>
<details>
<summary><strong>Responsive pictures</strong></summary>
@ -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';

55
packages/integrations/image/client.d.ts vendored Normal file
View file

@ -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;
}

View file

@ -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<TransformOptions, 'src'>, Omit<ImageAttributes, 'src' | 'width' | 'height'> {
interface LocalImageProps extends
Omit<TransformOptions, 'src'>,
Omit<ImgHTMLAttributes, | 'src' | 'width' | 'height'> {
src: ImageMetadata | Promise<{ default: ImageMetadata }>;
}
export interface RemoteImageProps extends TransformOptions, ImageAttributes {
interface RemoteImageProps extends TransformOptions, astroHTML.JSX.ImgHTMLAttributes {
src: string;
format: OutputFormat;
width: number;

View file

@ -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<PictureAttributes, 'src' | 'width' | 'height'>, Omit<TransformOptions, 'src'>, Pick<ImageAttributes, 'loading' | 'decoding'> {
interface LocalImageProps extends
Omit<HTMLAttributes, 'src' | 'width' | 'height'>,
Omit<TransformOptions, 'src'>,
Pick<astroHTML.JSX.ImgHTMLAttributes, 'loading' | 'decoding'> {
src: ImageMetadata | Promise<{ default: ImageMetadata }>;
alt?: string;
sizes: HTMLImageElement['sizes'];
@ -10,7 +14,10 @@ export interface LocalImageProps extends Omit<PictureAttributes, 'src' | 'width'
formats?: OutputFormat[];
}
export interface RemoteImageProps extends Omit<PictureAttributes, 'src' | 'width' | 'height'>, TransformOptions, Pick<ImageAttributes, 'loading' | 'decoding'> {
interface RemoteImageProps extends
Omit<HTMLAttributes, 'src' | 'width' | 'height'>,
TransformOptions,
Pick<ImgHTMLAttributes, 'loading' | 'decoding'> {
src: string;
alt?: string;
sizes: HTMLImageElement['sizes'];

View file

@ -1,2 +1,7 @@
/// <reference types="astro/astro-jsx" />
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<astroHTML.JSX.ImgHTMLAttributes, 'client:list' | 'set:text' | 'set:html' | 'is:raw'>;
export type HTMLAttributes = Omit<astroHTML.JSX.HTMLAttributes, 'client:list' | 'set:text' | 'set:html' | 'is:raw'>;

View file

@ -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",

View file

@ -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';

View file

@ -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<string, Map<string, TransformOptions>>();
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<string, TransformOptions>();
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 });
}
}
},
},
};
}

View file

@ -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<string, Map<string, TransformOptions>>();
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<string, TransformOptions>();
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 });
}
}
},
},
};
}

View file

@ -1,15 +1,15 @@
/// <reference types="astro/astro-jsx" />
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<TransformOptions, 'src'> {
src: string | ImageMetadata | Promise<{ default: ImageMetadata }>;
@ -101,7 +101,7 @@ async function resolveTransform(input: GetImageTransform): Promise<TransformOpti
* @param transform @type {TransformOptions} The transformations requested for the optimized image.
* @returns @type {ImageAttributes} The HTML attributes to be included on the built `<img />` element.
*/
export async function getImage(transform: GetImageTransform): Promise<ImageAttributes> {
export async function getImage(transform: GetImageTransform): Promise<astroHTML.JSX.ImgHTMLAttributes> {
if (!transform.src) {
throw new Error('[@astrojs/image] `src` is required');
}

View file

@ -1,7 +1,9 @@
/// <reference types="astro/astro-jsx" />
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 }[];
}

View file

@ -1,17 +1,4 @@
/// <reference types="astro/astro-jsx" />
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<T extends TransformOptions = TransformOptions> {
/**
* Gets the HTML attributes needed for the server rendered `<img />` element.
*/
getImageAttributes(transform: T): Promise<ImageAttributes>;
getImageAttributes(transform: T): Promise<astroHTML.JSX.ImgHTMLAttributes>;
}
export interface SSRImageService<T extends TransformOptions = TransformOptions>
extends HostedImageService<T> {
/**
* Gets tthe HTML attributes needed for the server rendered `<img />` element.
* Gets the HTML attributes needed for the server rendered `<img />` element.
*/
getImageAttributes(transform: T): Promise<Exclude<ImageAttributes, 'src'>>;
getImageAttributes(transform: T): Promise<Exclude<astroHTML.JSX.ImgHTMLAttributes, 'src'>>;
/**
* 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;
}

View file

@ -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 {

View file

@ -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);

View file

@ -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<ImageMetadata | undefined> {
const file = await fs.readFile(src);

View file

@ -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';

View file

@ -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<IntegrationOptions>): Plugin {
const filter = (id: string) =>
/^(?!\/_image?).*.(heic|heif|avif|jpeg|jpg|png|tiff|webp|gif)$/.test(id);