fix(markdown): Fix Markdown images breaking the build (#8598)
* fix(markdown): Fix Markdown images breaking the build * chore: changeset * Update packages/astro/src/core/errors/errors-data.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Fix tla chunking * One directory up * Down we go --------- Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> Co-authored-by: bluwy <bjornlu.dev@gmail.com>
This commit is contained in:
parent
5a988eaf60
commit
bdd267d089
7 changed files with 96 additions and 43 deletions
5
.changeset/honest-snakes-peel.md
Normal file
5
.changeset/honest-snakes-peel.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Fix relative images in Markdown breaking the build process in certain circumstances
|
|
@ -29,8 +29,8 @@ const qualityTable: Record<
|
|||
// Squoosh's PNG encoder does not support a quality setting, so we can skip that here
|
||||
};
|
||||
|
||||
async function getRotationForEXIF(inputBuffer: Buffer): Promise<Operation | undefined> {
|
||||
const meta = await imageMetadata(inputBuffer);
|
||||
async function getRotationForEXIF(inputBuffer: Buffer, src?: string): Promise<Operation | undefined> {
|
||||
const meta = await imageMetadata(inputBuffer, src);
|
||||
if (!meta) return undefined;
|
||||
|
||||
// EXIF orientations are a bit hard to read, but the numbers are actually standard. See https://exiftool.org/TagNames/EXIF.html for a list.
|
||||
|
@ -63,7 +63,7 @@ const service: LocalImageService = {
|
|||
|
||||
const operations: Operation[] = [];
|
||||
|
||||
const rotation = await getRotationForEXIF(inputBuffer);
|
||||
const rotation = await getRotationForEXIF(inputBuffer, transform.src);
|
||||
|
||||
if (rotation) {
|
||||
operations.push(rotation);
|
||||
|
|
|
@ -22,11 +22,7 @@ export async function emitESMImage(
|
|||
return undefined;
|
||||
}
|
||||
|
||||
const fileMetadata = await imageMetadata(fileData);
|
||||
|
||||
if (!fileMetadata) {
|
||||
return undefined;
|
||||
}
|
||||
const fileMetadata = await imageMetadata(fileData, id);
|
||||
|
||||
const emittedImage: ImageMetadata = {
|
||||
src: '',
|
||||
|
|
|
@ -1,19 +1,23 @@
|
|||
import probe from 'probe-image-size';
|
||||
import { AstroError, AstroErrorData } from '../../core/errors/index.js';
|
||||
import type { ImageInputFormat, ImageMetadata } from '../types.js';
|
||||
|
||||
export async function imageMetadata(data: Buffer): Promise<Omit<ImageMetadata, 'src'> | undefined> {
|
||||
export async function imageMetadata(
|
||||
data: Buffer,
|
||||
src?: string
|
||||
): Promise<Omit<ImageMetadata, 'src'>> {
|
||||
const result = probe.sync(data);
|
||||
|
||||
if (result === null) {
|
||||
throw new Error('Failed to probe image size.');
|
||||
throw new AstroError({
|
||||
...AstroErrorData.NoImageMetadata,
|
||||
message: AstroErrorData.NoImageMetadata.message(src),
|
||||
});
|
||||
}
|
||||
|
||||
const { width, height, type, orientation } = result;
|
||||
const isPortrait = (orientation || 0) >= 5;
|
||||
|
||||
if (!width || !height || !type) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
width: isPortrait ? height : width,
|
||||
height: isPortrait ? width : height,
|
||||
|
|
|
@ -2,6 +2,8 @@ import MagicString from 'magic-string';
|
|||
import type * as vite from 'vite';
|
||||
import { normalizePath } from 'vite';
|
||||
import type { AstroPluginOptions, ImageTransform } from '../@types/astro.js';
|
||||
import { extendManualChunks } from '../core/build/plugins/util.js';
|
||||
import { AstroError, AstroErrorData } from '../core/errors/index.js';
|
||||
import {
|
||||
appendForwardSlash,
|
||||
joinPaths,
|
||||
|
@ -28,6 +30,18 @@ export default function assets({
|
|||
// Expose the components and different utilities from `astro:assets` and handle serving images from `/_image` in dev
|
||||
{
|
||||
name: 'astro:assets',
|
||||
outputOptions(outputOptions) {
|
||||
// Specifically split out chunk for asset files to prevent TLA deadlock
|
||||
// caused by `getImage()` for markdown components.
|
||||
// https://github.com/rollup/rollup/issues/4708
|
||||
extendManualChunks(outputOptions, {
|
||||
after(id) {
|
||||
if (id.includes('astro/dist/assets/services/')) {
|
||||
return `astro-assets-services`;
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
async resolveId(id) {
|
||||
if (id === VIRTUAL_SERVICE_ID) {
|
||||
return await this.resolve(settings.config.image.service.entrypoint);
|
||||
|
@ -125,6 +139,14 @@ export default function assets({
|
|||
}
|
||||
if (assetRegex.test(id)) {
|
||||
const meta = await emitESMImage(id, this.meta.watchMode, this.emitFile);
|
||||
|
||||
if (!meta) {
|
||||
throw new AstroError({
|
||||
...AstroErrorData.ImageNotFound,
|
||||
message: AstroErrorData.ImageNotFound.message(id),
|
||||
});
|
||||
}
|
||||
|
||||
return `export default ${JSON.stringify(meta)}`;
|
||||
}
|
||||
},
|
||||
|
|
|
@ -620,8 +620,42 @@ export const ExpectedImageOptions = {
|
|||
message: (options: string) =>
|
||||
`Expected getImage() parameter to be an object. Received \`${options}\`.`,
|
||||
} satisfies ErrorData;
|
||||
|
||||
/**
|
||||
* @docs
|
||||
* @see
|
||||
* - [Images](https://docs.astro.build/en/guides/images/)
|
||||
* @description
|
||||
* Astro could not find an image you imported. Often, this is simply caused by a typo in the path.
|
||||
*
|
||||
* Images in Markdown are relative to the current file. To refer to an image that is located in the same folder as the `.md` file, the path should start with `./`
|
||||
*/
|
||||
export const ImageNotFound = {
|
||||
name: 'ImageNotFound',
|
||||
title: 'Image not found.',
|
||||
message: (imagePath: string) => `Could not find requested image \`${imagePath}\`. Does it exist?`,
|
||||
hint: 'This is often caused by a typo in the image path. Please make sure the file exists, and is spelled correctly.',
|
||||
} satisfies ErrorData;
|
||||
|
||||
/**
|
||||
* @docs
|
||||
* @message Could not process image metadata for `IMAGE_PATH`.
|
||||
* @see
|
||||
* - [Images](https://docs.astro.build/en/guides/images/)
|
||||
* @description
|
||||
* Astro could not process the metadata of an image you imported. This is often caused by a corrupted or malformed image and re-exporting the image from your image editor may fix this issue.
|
||||
*/
|
||||
export const NoImageMetadata = {
|
||||
name: 'NoImageMetadata',
|
||||
title: 'Could not process image metadata.',
|
||||
message: (imagePath: string | undefined) =>
|
||||
`Could not process image metadata${imagePath ? ' for `${imagePath}`' : ''}.`,
|
||||
hint: 'This is often caused by a corrupted or malformed image. Re-exporting the image from your image editor may fix this issue.',
|
||||
} satisfies ErrorData;
|
||||
|
||||
/**
|
||||
* @docs
|
||||
* @deprecated This error is no longer Markdown specific and as such, as been replaced by `ImageNotFound`
|
||||
* @message
|
||||
* Could not find requested image `IMAGE_PATH` at `FULL_IMAGE_PATH`.
|
||||
* @see
|
||||
|
@ -640,6 +674,7 @@ export const MarkdownImageNotFound = {
|
|||
}`,
|
||||
hint: 'This is often caused by a typo in the image path. Please make sure the file exists, and is spelled correctly.',
|
||||
} satisfies ErrorData;
|
||||
|
||||
/**
|
||||
* @docs
|
||||
* @description
|
||||
|
|
|
@ -12,7 +12,8 @@ import { normalizePath } from 'vite';
|
|||
import type { AstroSettings } from '../@types/astro.js';
|
||||
import { AstroError, AstroErrorData, MarkdownError } from '../core/errors/index.js';
|
||||
import type { Logger } from '../core/logger/core.js';
|
||||
import { isMarkdownFile, rootRelativePath } from '../core/util.js';
|
||||
import { isMarkdownFile } from '../core/util.js';
|
||||
import { shorthash } from '../runtime/server/shorthash.js';
|
||||
import type { PluginMetadata } from '../vite-plugin-astro/types.js';
|
||||
import { escapeViteEnvReferences, getFileInfo } from '../vite-plugin-utils/index.js';
|
||||
|
||||
|
@ -92,12 +93,13 @@ export default function markdown({ settings, logger }: AstroPluginOptions): Plug
|
|||
const { headings, imagePaths: rawImagePaths, frontmatter } = renderResult.metadata;
|
||||
|
||||
// Resolve all the extracted images from the content
|
||||
const imagePaths: { raw: string; resolved: string }[] = [];
|
||||
const imagePaths: { raw: string; resolved: string; safeName: string }[] = [];
|
||||
for (const imagePath of rawImagePaths.values()) {
|
||||
imagePaths.push({
|
||||
raw: imagePath,
|
||||
resolved:
|
||||
(await this.resolve(imagePath, id))?.id ?? path.join(path.dirname(id), imagePath),
|
||||
safeName: shorthash(imagePath),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -118,39 +120,28 @@ export default function markdown({ settings, logger }: AstroPluginOptions): Plug
|
|||
|
||||
${layout ? `import Layout from ${JSON.stringify(layout)};` : ''}
|
||||
import { getImage } from "astro:assets";
|
||||
${imagePaths.map((entry) => `import Astro__${entry.safeName} from ${JSON.stringify(entry.raw)};`)}
|
||||
|
||||
export const images = {
|
||||
${imagePaths.map(
|
||||
(entry) =>
|
||||
`'${entry.raw}': await getImageSafely((await import("${entry.raw}")).default, "${
|
||||
entry.raw
|
||||
}", "${rootRelativePath(settings.config.root, entry.resolved)}")`
|
||||
)}
|
||||
}
|
||||
|
||||
async function getImageSafely(imageSrc, imagePath, resolvedImagePath) {
|
||||
if (!imageSrc) {
|
||||
throw new AstroError({
|
||||
...AstroErrorData.MarkdownImageNotFound,
|
||||
message: AstroErrorData.MarkdownImageNotFound.message(
|
||||
imagePath,
|
||||
resolvedImagePath
|
||||
),
|
||||
location: { file: "${id}" },
|
||||
});
|
||||
const images = async function() {
|
||||
return {
|
||||
${imagePaths
|
||||
.map((entry) => `"${entry.raw}": await getImage({src: Astro__${entry.safeName}})`)
|
||||
.join('\n')}
|
||||
}
|
||||
|
||||
return await getImage({src: imageSrc})
|
||||
}
|
||||
|
||||
function updateImageReferences(html) {
|
||||
return html.replaceAll(
|
||||
/__ASTRO_IMAGE_="([^"]+)"/gm,
|
||||
(full, imagePath) => spreadAttributes({src: images[imagePath].src, ...images[imagePath].attributes})
|
||||
);
|
||||
async function updateImageReferences(html) {
|
||||
return images().then((images) => {
|
||||
return html.replaceAll(/__ASTRO_IMAGE_="([^"]+)"/gm, (full, imagePath) =>
|
||||
spreadAttributes({
|
||||
src: images[imagePath].src,
|
||||
...images[imagePath].attributes,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const html = updateImageReferences(${JSON.stringify(html)});
|
||||
const html = await updateImageReferences(${JSON.stringify(html)});
|
||||
|
||||
export const frontmatter = ${JSON.stringify(frontmatter)};
|
||||
export const file = ${JSON.stringify(fileId)};
|
||||
|
|
Loading…
Reference in a new issue