summaryrefslogtreecommitdiff
path: root/packages/integrations/image/src/index.ts
blob: 28df1ec387a8ef2faf473e9a091ab15191cbec64 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
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 type { LoggerLevel } from './utils/logger.js';
import { filenameFormat, propsToFilename } from './utils/paths.js';
import { createPlugin } from './vite-plugin-astro-image.js';

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;
	logLevel?: LoggerLevel;
}

export default function integration(options: IntegrationOptions = {}): AstroIntegration {
	const resolvedOptions = {
		serviceEntryPoint: '@astrojs/image/sharp',
		logLevel: 'info' as LoggerLevel,
		...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 ({ server }) => {
				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,
							logLevel: resolvedOptions.logLevel,
						});
					}
				}
			},
		},
	};
}