diff options
-rw-r--r-- | .changeset/hot-buckets-tie.md | 5 | ||||
-rw-r--r-- | packages/astro/src/assets/image-endpoint.ts | 2 | ||||
-rw-r--r-- | packages/astro/src/assets/internal.ts | 4 | ||||
-rw-r--r-- | packages/astro/src/assets/services/service.ts | 16 | ||||
-rw-r--r-- | packages/astro/src/assets/utils/index.ts | 3 | ||||
-rw-r--r-- | packages/astro/src/assets/vite-plugin-assets.ts | 70 | ||||
-rw-r--r-- | packages/astro/src/cli/load-settings.ts | 4 | ||||
-rw-r--r-- | packages/astro/src/config/index.ts | 2 | ||||
-rw-r--r-- | packages/astro/src/core/config/settings.ts | 14 | ||||
-rw-r--r-- | packages/astro/src/core/dev/restart.ts | 2 | ||||
-rw-r--r-- | packages/astro/test/test-utils.js | 14 | ||||
-rw-r--r-- | packages/astro/test/units/config/format.test.js | 4 | ||||
-rw-r--r-- | packages/astro/test/units/dev/collections-mixed-content-errors.test.js | 2 | ||||
-rw-r--r-- | packages/astro/test/units/dev/restart.test.js | 8 |
14 files changed, 50 insertions, 100 deletions
diff --git a/.changeset/hot-buckets-tie.md b/.changeset/hot-buckets-tie.md new file mode 100644 index 000000000..2870b31f5 --- /dev/null +++ b/.changeset/hot-buckets-tie.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fix SharedImageService's types not properly reflecting that image services hooks can be async diff --git a/packages/astro/src/assets/image-endpoint.ts b/packages/astro/src/assets/image-endpoint.ts index 49fce8f05..0553272c2 100644 --- a/packages/astro/src/assets/image-endpoint.ts +++ b/packages/astro/src/assets/image-endpoint.ts @@ -22,7 +22,7 @@ async function loadRemoteImage(src: URL) { } /** - * Endpoint used in SSR to serve optimized images + * Endpoint used in dev and SSR to serve optimized images by the base image services */ export const get: APIRoute = async ({ request }) => { try { diff --git a/packages/astro/src/assets/internal.ts b/packages/astro/src/assets/internal.ts index 2d4d18ea7..635d0a5e7 100644 --- a/packages/astro/src/assets/internal.ts +++ b/packages/astro/src/assets/internal.ts @@ -38,10 +38,10 @@ export async function getImage( const service = await getConfiguredImageService(); const validatedOptions = service.validateOptions - ? service.validateOptions(options, serviceConfig) + ? await service.validateOptions(options, serviceConfig) : options; - let imageURL = service.getURL(validatedOptions, serviceConfig); + let imageURL = await service.getURL(validatedOptions, serviceConfig); // In build and for local services, we need to collect the requested parameters so we can generate the final images if (isLocalService(service) && globalThis.astroAsset.addStaticImage) { diff --git a/packages/astro/src/assets/services/service.ts b/packages/astro/src/assets/services/service.ts index 8b7611db2..d3479c880 100644 --- a/packages/astro/src/assets/services/service.ts +++ b/packages/astro/src/assets/services/service.ts @@ -32,7 +32,7 @@ interface SharedServiceProps { * For external services, this should point to the URL your images are coming from, for instance, `/_vercel/image` * */ - getURL: (options: ImageTransform, serviceConfig: Record<string, any>) => string; + getURL: (options: ImageTransform, serviceConfig: Record<string, any>) => string | Promise<string>; /** * Return any additional HTML attributes separate from `src` that your service requires to show the image properly. * @@ -42,7 +42,7 @@ interface SharedServiceProps { getHTMLAttributes?: ( options: ImageTransform, serviceConfig: Record<string, any> - ) => Record<string, any>; + ) => Record<string, any> | Promise<Record<string, any>>; /** * Validate and return the options passed by the user. * @@ -51,7 +51,10 @@ interface SharedServiceProps { * * This method should returns options, and can be used to set defaults (ex: a default output format to be used if the user didn't specify one.) */ - validateOptions?: (options: ImageTransform, serviceConfig: Record<string, any>) => ImageTransform; + validateOptions?: ( + options: ImageTransform, + serviceConfig: Record<string, any> + ) => ImageTransform | Promise<ImageTransform>; } export type ExternalImageService = SharedServiceProps; @@ -63,11 +66,14 @@ export type LocalImageTransform = { export interface LocalImageService extends SharedServiceProps { /** - * Parse the requested parameters passed in the URL from `getURL` back into an object to be used later by `transform` + * Parse the requested parameters passed in the URL from `getURL` back into an object to be used later by `transform`. * * In most cases, this will get query parameters using, for example, `params.get('width')` and return those. */ - parseURL: (url: URL, serviceConfig: Record<string, any>) => LocalImageTransform | undefined; + parseURL: ( + url: URL, + serviceConfig: Record<string, any> + ) => LocalImageTransform | undefined | Promise<LocalImageTransform> | Promise<undefined>; /** * Performs the image transformations on the input image and returns both the binary data and * final image format of the optimized image. diff --git a/packages/astro/src/assets/utils/index.ts b/packages/astro/src/assets/utils/index.ts index d82c5a6e6..3e6246519 100644 --- a/packages/astro/src/assets/utils/index.ts +++ b/packages/astro/src/assets/utils/index.ts @@ -1 +1,4 @@ export { emitESMImage } from './emitAsset.js'; +export { imageMetadata } from './metadata.js'; +export { getOrigQueryParams } from './queryParams.js'; +export { hashTransform, propsToFilename } from './transformToPath.js'; diff --git a/packages/astro/src/assets/vite-plugin-assets.ts b/packages/astro/src/assets/vite-plugin-assets.ts index 4088c7ec5..6a29d02f0 100644 --- a/packages/astro/src/assets/vite-plugin-assets.ts +++ b/packages/astro/src/assets/vite-plugin-assets.ts @@ -1,8 +1,5 @@ import { bold } from 'kleur/colors'; import MagicString from 'magic-string'; -import mime from 'mime/lite.js'; -import fs from 'node:fs/promises'; -import { Readable } from 'node:stream'; import { fileURLToPath } from 'node:url'; import type * as vite from 'vite'; import { normalizePath } from 'vite'; @@ -16,10 +13,7 @@ import { } from '../core/path.js'; import { VIRTUAL_MODULE_ID, VIRTUAL_SERVICE_ID } from './consts.js'; import { isESMImportedImage } from './internal.js'; -import { isLocalService } from './services/service.js'; import { emitESMImage } from './utils/emitAsset.js'; -import { imageMetadata } from './utils/metadata.js'; -import { getOrigQueryParams } from './utils/queryParams.js'; import { hashTransform, propsToFilename } from './utils/transformToPath.js'; const resolvedVirtualModuleId = '\0' + VIRTUAL_MODULE_ID; @@ -96,70 +90,6 @@ export default function assets({ `; } }, - // Handle serving images during development - configureServer(server) { - server.middlewares.use(async (req, res, next) => { - if (req.url?.startsWith('/_image')) { - // If the currently configured service isn't a local service, we don't need to do anything here. - // TODO: Support setting a specific service through a prop on Image / a parameter in getImage - if (!isLocalService(globalThis.astroAsset.imageService)) { - return next(); - } - - const url = new URL(req.url, 'file:'); - if (!url.searchParams.has('href')) { - return next(); - } - - const filePath = url.searchParams.get('href')?.slice('/@fs'.length); - const filePathURL = new URL('.' + filePath, 'file:'); - const file = await fs.readFile(filePathURL); - - // Get the file's metadata from the URL - let meta = getOrigQueryParams(filePathURL.searchParams); - - // If we don't have them (ex: the image came from Markdown, let's calculate them again) - if (!meta) { - meta = await imageMetadata(filePathURL, file); - - if (!meta) { - return next(); - } - } - - const transform = await globalThis.astroAsset.imageService.parseURL( - url, - settings.config.image.service.config - ); - - if (transform === undefined) { - error(logging, 'image', `Failed to parse transform for ${url}`); - } - - // if no transforms were added, the original file will be returned as-is - let data = file; - let format: string = meta.format; - - if (transform) { - const result = await globalThis.astroAsset.imageService.transform( - file, - transform, - settings.config.image.service.config - ); - data = result.data; - format = result.format; - } - - res.setHeader('Content-Type', mime.getType(format) ?? `image/${format}`); - res.setHeader('Cache-Control', 'max-age=360000'); - - const stream = Readable.from(data); - return stream.pipe(res); - } - - return next(); - }); - }, buildStart() { if (mode != 'build') { return; diff --git a/packages/astro/src/cli/load-settings.ts b/packages/astro/src/cli/load-settings.ts index 9b8d90781..ce9094c65 100644 --- a/packages/astro/src/cli/load-settings.ts +++ b/packages/astro/src/cli/load-settings.ts @@ -26,9 +26,11 @@ export async function loadSettings({ cmd, flags, logging }: LoadSettingsOptions) await handleConfigError(e, { cmd, cwd: root, flags, logging }); return {} as any; }); + + const mode = cmd === 'build' ? 'build' : 'dev'; if (!initialAstroConfig) return; telemetry.record(event.eventCliSession(cmd, initialUserConfig, flags)); - return createSettings(initialAstroConfig, root); + return createSettings(initialAstroConfig, mode, root); } export async function handleConfigError( diff --git a/packages/astro/src/config/index.ts b/packages/astro/src/config/index.ts index a9e32186d..2536df8e7 100644 --- a/packages/astro/src/config/index.ts +++ b/packages/astro/src/config/index.ts @@ -35,7 +35,7 @@ export function getViteConfig(inlineConfig: UserConfig) { level: 'info', }; const { astroConfig: config } = await openConfig({ cmd }); - const settings = createSettings(config, inlineConfig.root); + const settings = createSettings(config, cmd, inlineConfig.root); await runHookConfigSetup({ settings, command: cmd, logging }); const viteConfig = await createVite( { diff --git a/packages/astro/src/core/config/settings.ts b/packages/astro/src/core/config/settings.ts index 983b4566b..222e6461b 100644 --- a/packages/astro/src/core/config/settings.ts +++ b/packages/astro/src/core/config/settings.ts @@ -14,7 +14,7 @@ import { createDefaultDevConfig } from './config.js'; import { AstroTimer } from './timer.js'; import { loadTSConfig } from './tsconfig.js'; -export function createBaseSettings(config: AstroConfig): AstroSettings { +export function createBaseSettings(config: AstroConfig, mode: 'build' | 'dev'): AstroSettings { const { contentDir } = getContentPaths(config); return { config, @@ -23,7 +23,7 @@ export function createBaseSettings(config: AstroConfig): AstroSettings { adapter: undefined, injectedRoutes: - config.experimental.assets && isServerLikeOutput(config) + config.experimental.assets && (isServerLikeOutput(config) || mode === 'dev') ? [{ pattern: '/_image', entryPoint: 'astro/assets/image-endpoint', prerender: false }] : [], pageExtensions: ['.astro', '.html', ...SUPPORTED_MARKDOWN_FILE_EXTENSIONS], @@ -108,9 +108,13 @@ export function createBaseSettings(config: AstroConfig): AstroSettings { }; } -export function createSettings(config: AstroConfig, cwd?: string): AstroSettings { +export function createSettings( + config: AstroConfig, + mode: 'build' | 'dev', + cwd?: string +): AstroSettings { const tsconfig = loadTSConfig(cwd); - const settings = createBaseSettings(config); + const settings = createBaseSettings(config, mode); const watchFiles = tsconfig?.exists ? [tsconfig.path, ...tsconfig.extendedPaths] : []; @@ -132,5 +136,5 @@ export async function createDefaultDevSettings( root = fileURLToPath(root); } const config = await createDefaultDevConfig(userConfig, root); - return createBaseSettings(config); + return createBaseSettings(config, 'dev'); } diff --git a/packages/astro/src/core/dev/restart.ts b/packages/astro/src/core/dev/restart.ts index d96cc0b50..887470fb8 100644 --- a/packages/astro/src/core/dev/restart.ts +++ b/packages/astro/src/core/dev/restart.ts @@ -92,7 +92,7 @@ export async function restartContainer({ }); info(logging, 'astro', logMsg + '\n'); let astroConfig = newConfig.astroConfig; - const settings = createSettings(astroConfig, resolvedRoot); + const settings = createSettings(astroConfig, 'dev', resolvedRoot); await close(); return { container: await createRestartedContainer(container, settings, needsStart), diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js index 0cf441ad3..7301040c5 100644 --- a/packages/astro/test/test-utils.js +++ b/packages/astro/test/test-utils.js @@ -138,8 +138,8 @@ export async function loadFixture(inlineConfig) { * the `AstroSettings`. This function helps to create a fresh settings object that is used by the * command functions below to prevent tests from polluting each other. */ - const getSettings = async () => { - let settings = createSettings(config, fileURLToPath(cwd)); + const getSettings = async (mode) => { + let settings = createSettings(config, mode, fileURLToPath(cwd)); if (config.integrations.find((integration) => integration.name === '@astrojs/mdx')) { // Enable default JSX integration. It needs to come first, so unshift rather than push! const { default: jsxRenderer } = await import('astro/jsx/renderer.js'); @@ -179,15 +179,15 @@ export async function loadFixture(inlineConfig) { return { build: async (opts = {}) => { process.env.NODE_ENV = 'production'; - return build(await getSettings(), { logging, ...opts }); + return build(await getSettings('build'), { logging, ...opts }); }, - sync: async (opts) => sync(await getSettings(), { logging, fs, ...opts }), + sync: async (opts) => sync(await getSettings('build'), { logging, fs, ...opts }), check: async (opts) => { - return await check(await getSettings(), { logging, ...opts }); + return await check(await getSettings('build'), { logging, ...opts }); }, startDevServer: async (opts = {}) => { process.env.NODE_ENV = 'development'; - devServer = await dev(await getSettings(), { logging, ...opts }); + devServer = await dev(await getSettings('dev'), { logging, ...opts }); config.server.host = parseAddressToHost(devServer.address.address); // update host config.server.port = devServer.address.port; // update port return devServer; @@ -209,7 +209,7 @@ export async function loadFixture(inlineConfig) { }, preview: async (opts = {}) => { process.env.NODE_ENV = 'production'; - const previewServer = await preview(await getSettings(), { logging, ...opts }); + const previewServer = await preview(await getSettings('build'), { logging, ...opts }); config.server.host = parseAddressToHost(previewServer.host); // update host config.server.port = previewServer.port; // update port return previewServer; diff --git a/packages/astro/test/units/config/format.test.js b/packages/astro/test/units/config/format.test.js index 5dce2da44..cfa614bfb 100644 --- a/packages/astro/test/units/config/format.test.js +++ b/packages/astro/test/units/config/format.test.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; +import { createSettings, openConfig } from '../../../dist/core/config/index.js'; import { runInContainer } from '../../../dist/core/dev/index.js'; -import { openConfig, createSettings } from '../../../dist/core/config/index.js'; import { createFs, defaultLogging } from '../test-utils.js'; const root = new URL('../../fixtures/tailwindcss-ts/', import.meta.url); @@ -27,7 +27,7 @@ describe('Astro config formats', () => { logging: defaultLogging, fsMod: fs, }); - const settings = createSettings(astroConfig); + const settings = createSettings(astroConfig, 'dev'); await runInContainer({ fs, root, settings }, () => { expect(true).to.equal( diff --git a/packages/astro/test/units/dev/collections-mixed-content-errors.test.js b/packages/astro/test/units/dev/collections-mixed-content-errors.test.js index 4ebf2b510..b7f571019 100644 --- a/packages/astro/test/units/dev/collections-mixed-content-errors.test.js +++ b/packages/astro/test/units/dev/collections-mixed-content-errors.test.js @@ -10,7 +10,7 @@ const logging = defaultLogging; async function sync({ fs, config = {} }) { const astroConfig = await validateConfig(config, fileURLToPath(root), 'prod'); - const settings = createSettings(astroConfig, fileURLToPath(root)); + const settings = createSettings(astroConfig, 'build', fileURLToPath(root)); return _sync(settings, { logging, fs }); } diff --git a/packages/astro/test/units/dev/restart.test.js b/packages/astro/test/units/dev/restart.test.js index bef7006f2..f5fa679bf 100644 --- a/packages/astro/test/units/dev/restart.test.js +++ b/packages/astro/test/units/dev/restart.test.js @@ -2,6 +2,7 @@ import { expect } from 'chai'; import * as cheerio from 'cheerio'; import { fileURLToPath } from 'node:url'; +import { createSettings, openConfig } from '../../../dist/core/config/index.js'; import { createContainerWithAutomaticRestart, isStarted, @@ -13,7 +14,6 @@ import { defaultLogging, triggerFSEvent, } from '../test-utils.js'; -import { createSettings, openConfig } from '../../../dist/core/config/index.js'; const root = new URL('../../fixtures/alias/', import.meta.url); @@ -133,7 +133,7 @@ describe('dev container restarts', () => { cmd: 'dev', logging: defaultLogging, }); - const settings = createSettings(astroConfig); + const settings = createSettings(astroConfig, 'dev'); let restart = await createContainerWithAutomaticRestart({ params: { fs, root, settings }, @@ -167,7 +167,7 @@ describe('dev container restarts', () => { cmd: 'dev', logging: defaultLogging, }); - const settings = createSettings(astroConfig, fileURLToPath(root)); + const settings = createSettings(astroConfig, 'dev', fileURLToPath(root)); let restart = await createContainerWithAutomaticRestart({ params: { fs, root, settings }, @@ -199,7 +199,7 @@ describe('dev container restarts', () => { cmd: 'dev', logging: defaultLogging, }); - const settings = createSettings(astroConfig, fileURLToPath(root)); + const settings = createSettings(astroConfig, 'dev', fileURLToPath(root)); let restart = await createContainerWithAutomaticRestart({ params: { fs, root, settings }, |