summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/five-coats-tie.md5
-rw-r--r--packages/astro/src/assets/internal.ts42
-rw-r--r--packages/astro/src/assets/vite-plugin-assets.ts35
-rw-r--r--packages/astro/src/content/internal.ts2
-rw-r--r--packages/astro/src/content/utils.ts37
-rw-r--r--packages/astro/src/content/vite-plugin-content-imports.ts13
-rw-r--r--packages/astro/test/core-image.test.js44
-rw-r--r--packages/astro/test/fixtures/core-image-ssg/src/content/blog/one.md10
-rw-r--r--packages/astro/test/fixtures/core-image-ssg/src/content/config.ts15
-rw-r--r--packages/astro/test/fixtures/core-image-ssg/src/pages/blog/[...slug].astro33
-rw-r--r--packages/astro/test/fixtures/core-image/src/content/blog/one.md2
-rw-r--r--packages/astro/test/fixtures/core-image/src/content/config.ts5
-rw-r--r--packages/astro/test/fixtures/core-image/src/pages/blog/[...slug].astro18
13 files changed, 199 insertions, 62 deletions
diff --git a/.changeset/five-coats-tie.md b/.changeset/five-coats-tie.md
new file mode 100644
index 000000000..b358aa22c
--- /dev/null
+++ b/.changeset/five-coats-tie.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Fix images defined in content collections schemas not working
diff --git a/packages/astro/src/assets/internal.ts b/packages/astro/src/assets/internal.ts
index aa69f7183..0202e2359 100644
--- a/packages/astro/src/assets/internal.ts
+++ b/packages/astro/src/assets/internal.ts
@@ -1,8 +1,13 @@
import fs from 'node:fs';
+import path from 'node:path';
+import { pathToFileURL } from 'node:url';
+import { AstroSettings } from '../@types/astro.js';
import { StaticBuildOptions } from '../core/build/types.js';
import { AstroError, AstroErrorData } from '../core/errors/index.js';
+import { rootRelativePath } from '../core/util.js';
import { ImageService, isLocalService, LocalImageService } from './services/service.js';
import type { ImageMetadata, ImageTransform } from './types.js';
+import { imageMetadata } from './utils/metadata.js';
export function isESMImportedImage(src: ImageMetadata | string): src is ImageMetadata {
return typeof src === 'object';
@@ -115,3 +120,40 @@ export async function generateImage(
},
};
}
+
+export async function emitESMImage(
+ id: string,
+ watchMode: boolean,
+ fileEmitter: any,
+ settings: AstroSettings
+) {
+ const url = pathToFileURL(id);
+ const meta = await imageMetadata(url);
+
+ if (!meta) {
+ return;
+ }
+
+ // Build
+ if (!watchMode) {
+ const pathname = decodeURI(url.pathname);
+ const filename = path.basename(pathname, path.extname(pathname) + `.${meta.format}`);
+
+ const handle = fileEmitter({
+ name: filename,
+ source: await fs.promises.readFile(url),
+ type: 'asset',
+ });
+
+ meta.src = `__ASTRO_ASSET_IMAGE__${handle}__`;
+ } else {
+ // Pass the original file information through query params so we don't have to load the file twice
+ url.searchParams.append('origWidth', meta.width.toString());
+ url.searchParams.append('origHeight', meta.height.toString());
+ url.searchParams.append('origFormat', meta.format);
+
+ meta.src = rootRelativePath(settings.config, url);
+ }
+
+ return meta;
+}
diff --git a/packages/astro/src/assets/vite-plugin-assets.ts b/packages/astro/src/assets/vite-plugin-assets.ts
index 1e84b2271..9af9c6073 100644
--- a/packages/astro/src/assets/vite-plugin-assets.ts
+++ b/packages/astro/src/assets/vite-plugin-assets.ts
@@ -1,17 +1,15 @@
import MagicString from 'magic-string';
import mime from 'mime';
import fs from 'node:fs/promises';
-import path from 'node:path';
import { Readable } from 'node:stream';
-import { fileURLToPath, pathToFileURL } from 'node:url';
+import { fileURLToPath } from 'node:url';
import type * as vite from 'vite';
import { normalizePath } from 'vite';
import { AstroPluginOptions, ImageTransform } from '../@types/astro';
import { error } from '../core/logger/core.js';
import { joinPaths, prependForwardSlash } from '../core/path.js';
-import { rootRelativePath } from '../core/util.js';
import { VIRTUAL_MODULE_ID, VIRTUAL_SERVICE_ID } from './consts.js';
-import { isESMImportedImage } from './internal.js';
+import { emitESMImage, isESMImportedImage } from './internal.js';
import { isLocalService } from './services/service.js';
import { copyWasmFiles } from './services/vendor/squoosh/copy-wasm.js';
import { imageMetadata } from './utils/metadata.js';
@@ -202,34 +200,7 @@ export default function assets({
},
async load(id) {
if (/\.(jpeg|jpg|png|tiff|webp|gif|svg)$/.test(id)) {
- const url = pathToFileURL(id);
- const meta = await imageMetadata(url);
-
- if (!meta) {
- return;
- }
-
- // Build
- if (!this.meta.watchMode) {
- const pathname = decodeURI(url.pathname);
- const filename = path.basename(pathname, path.extname(pathname) + `.${meta.format}`);
-
- const handle = this.emitFile({
- name: filename,
- source: await fs.readFile(url),
- type: 'asset',
- });
-
- meta.src = `__ASTRO_ASSET_IMAGE__${handle}__`;
- } else {
- // Pass the original file information through query params so we don't have to load the file twice
- url.searchParams.append('origWidth', meta.width.toString());
- url.searchParams.append('origHeight', meta.height.toString());
- url.searchParams.append('origFormat', meta.format);
-
- meta.src = rootRelativePath(settings.config, url);
- }
-
+ const meta = await emitESMImage(id, this.meta.watchMode, this.emitFile, settings);
return `export default ${JSON.stringify(meta)}`;
}
},
diff --git a/packages/astro/src/content/internal.ts b/packages/astro/src/content/internal.ts
index 2fdfe7454..bdc99bbc7 100644
--- a/packages/astro/src/content/internal.ts
+++ b/packages/astro/src/content/internal.ts
@@ -191,7 +191,7 @@ async function render({
};
}
-export function createImage(options: { assetsDir: string }) {
+export function createImage(options: { assetsDir: string; relAssetsDir: string }) {
return () => {
if (options.assetsDir === 'undefined') {
throw new Error('Enable `experimental.assets` in your Astro config to use image()');
diff --git a/packages/astro/src/content/utils.ts b/packages/astro/src/content/utils.ts
index 011ade19f..22b71e0af 100644
--- a/packages/astro/src/content/utils.ts
+++ b/packages/astro/src/content/utils.ts
@@ -3,10 +3,11 @@ import matter from 'gray-matter';
import fsMod from 'node:fs';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
+import type { EmitFile } from 'rollup';
import { ErrorPayload as ViteErrorPayload, normalizePath, ViteDevServer } from 'vite';
import { z } from 'zod';
import { AstroConfig, AstroSettings } from '../@types/astro.js';
-import type { ImageMetadata } from '../assets/types.js';
+import { emitESMImage } from '../assets/internal.js';
import { AstroError, AstroErrorData } from '../core/errors/index.js';
import { CONTENT_TYPES_FILE } from './consts.js';
@@ -43,21 +44,29 @@ export const msg = {
`${collection} does not have a config. We suggest adding one for type safety!`,
};
-export function extractFrontmatterAssets(data: Record<string, any>): string[] {
- function findAssets(potentialAssets: Record<string, any>): ImageMetadata[] {
- return Object.values(potentialAssets).reduce((acc, curr) => {
- if (typeof curr === 'object') {
- if (curr.__astro === true) {
- acc.push(curr);
- } else {
- acc.push(...findAssets(curr));
- }
+/**
+ * Mutate (arf) the entryData to reroute assets to their final paths
+ */
+export async function patchAssets(
+ frontmatterEntry: Record<string, any>,
+ watchMode: boolean,
+ fileEmitter: EmitFile,
+ astroSettings: AstroSettings
+) {
+ for (const key of Object.keys(frontmatterEntry)) {
+ if (typeof frontmatterEntry[key] === 'object' && frontmatterEntry[key] !== null) {
+ if (frontmatterEntry[key]['__astro_asset']) {
+ frontmatterEntry[key] = await emitESMImage(
+ frontmatterEntry[key].src,
+ watchMode,
+ fileEmitter,
+ astroSettings
+ );
+ } else {
+ await patchAssets(frontmatterEntry[key], watchMode, fileEmitter, astroSettings);
}
- return acc;
- }, []);
+ }
}
-
- return findAssets(data).map((asset) => asset.src);
}
export function getEntrySlug({
diff --git a/packages/astro/src/content/vite-plugin-content-imports.ts b/packages/astro/src/content/vite-plugin-content-imports.ts
index 7c335e112..a81d1feba 100644
--- a/packages/astro/src/content/vite-plugin-content-imports.ts
+++ b/packages/astro/src/content/vite-plugin-content-imports.ts
@@ -3,7 +3,6 @@ import type fsMod from 'node:fs';
import { extname } from 'node:path';
import { pathToFileURL } from 'url';
import type { Plugin } from 'vite';
-import { normalizePath } from 'vite';
import { AstroSettings, ContentEntryType } from '../@types/astro.js';
import { AstroErrorData } from '../core/errors/errors-data.js';
import { AstroError } from '../core/errors/errors.js';
@@ -11,7 +10,6 @@ import { escapeViteEnvReferences, getFileInfo } from '../vite-plugin-utils/index
import { CONTENT_FLAG } from './consts.js';
import {
ContentConfig,
- extractFrontmatterAssets,
getContentEntryExts,
getContentPaths,
getEntryData,
@@ -19,8 +17,8 @@ import {
getEntrySlug,
getEntryType,
globalContentConfigObserver,
+ patchAssets,
} from './utils.js';
-
function isContentFlagImport(viteId: string, contentEntryExts: string[]) {
const { searchParams, pathname } = new URL(viteId, 'file://');
return searchParams.has(CONTENT_FLAG) && contentEntryExts.some((ext) => pathname.endsWith(ext));
@@ -106,25 +104,20 @@ export function astroContentImportPlugin({
const slug = getEntrySlug({ ...generatedInfo, unvalidatedSlug: info.slug });
const collectionConfig = contentConfig?.collections[generatedInfo.collection];
- const data = collectionConfig
+ let data = collectionConfig
? await getEntryData(
{ ...generatedInfo, _internal, unvalidatedData: info.data },
collectionConfig
)
: info.data;
- const images = extractFrontmatterAssets(data).map(
- (image) => `'${image}': await import('${normalizePath(image)}'),`
- );
+ await patchAssets(data, this.meta.watchMode, this.emitFile, settings);
const code = escapeViteEnvReferences(`
export const id = ${JSON.stringify(generatedInfo.id)};
export const collection = ${JSON.stringify(generatedInfo.collection)};
export const slug = ${JSON.stringify(slug)};
export const body = ${JSON.stringify(info.body)};
-const frontmatterImages = {
- ${images.join('\n')}
-}
export const data = ${devalue.uneval(data) /* TODO: reuse astro props serializer */};
export const _internal = {
filePath: ${JSON.stringify(_internal.filePath)},
diff --git a/packages/astro/test/core-image.test.js b/packages/astro/test/core-image.test.js
index 81a793ffc..e0af4f1aa 100644
--- a/packages/astro/test/core-image.test.js
+++ b/packages/astro/test/core-image.test.js
@@ -213,10 +213,34 @@ describe('astro:image', () => {
$ = cheerio.load(html);
});
- it('Adds the <img> tag', () => {
+ it('Adds the <img> tags', () => {
let $img = $('img');
- expect($img).to.have.a.lengthOf(1);
+ expect($img).to.have.a.lengthOf(4);
+ });
+
+ it('has proper source for directly used image', () => {
+ let $img = $('#direct-image img');
+ expect($img.attr('src').startsWith('/src/')).to.equal(true);
+ });
+
+ it('has proper attributes for optimized image through getImage', () => {
+ let $img = $('#optimized-image-get-image img');
+ expect($img.attr('src').startsWith('/_image')).to.equal(true);
+ expect($img.attr('width')).to.equal('207');
+ expect($img.attr('height')).to.equal('243');
+ });
+
+ it('has proper attributes for optimized image through Image component', () => {
+ let $img = $('#optimized-image-component img');
expect($img.attr('src').startsWith('/_image')).to.equal(true);
+ expect($img.attr('width')).to.equal('207');
+ expect($img.attr('height')).to.equal('243');
+ expect($img.attr('alt')).to.equal('A penguin!');
+ });
+
+ it('properly handles nested images', () => {
+ let $img = $('#nested-image img');
+ expect($img.attr('src').startsWith('/src/')).to.equal(true);
});
});
@@ -306,6 +330,22 @@ describe('astro:image', () => {
expect(data).to.be.an.instanceOf(Buffer);
});
+ it('output files for content collections images', async () => {
+ const html = await fixture.readFile('/blog/one/index.html');
+
+ const $ = cheerio.load(html);
+ let $img = $('img');
+ expect($img).to.have.a.lengthOf(2);
+
+ const srcdirect = $('#direct-image img').attr('src');
+ const datadirect = await fixture.readFile(srcdirect, null);
+ expect(datadirect).to.be.an.instanceOf(Buffer);
+
+ const srcnested = $('#nested-image img').attr('src');
+ const datanested = await fixture.readFile(srcnested, null);
+ expect(datanested).to.be.an.instanceOf(Buffer);
+ });
+
it('quality attribute produces a different file', async () => {
const html = await fixture.readFile('/quality/index.html');
const $ = cheerio.load(html);
diff --git a/packages/astro/test/fixtures/core-image-ssg/src/content/blog/one.md b/packages/astro/test/fixtures/core-image-ssg/src/content/blog/one.md
new file mode 100644
index 000000000..88a210b75
--- /dev/null
+++ b/packages/astro/test/fixtures/core-image-ssg/src/content/blog/one.md
@@ -0,0 +1,10 @@
+---
+title: One
+image: penguin2.jpg
+cover:
+ image: penguin1.jpg
+---
+
+# A post
+
+text here
diff --git a/packages/astro/test/fixtures/core-image-ssg/src/content/config.ts b/packages/astro/test/fixtures/core-image-ssg/src/content/config.ts
new file mode 100644
index 000000000..b38ad070e
--- /dev/null
+++ b/packages/astro/test/fixtures/core-image-ssg/src/content/config.ts
@@ -0,0 +1,15 @@
+import { defineCollection, image, z } from "astro:content";
+
+const blogCollection = defineCollection({
+ schema: z.object({
+ title: z.string(),
+ image: image(),
+ cover: z.object({
+ image: image()
+ })
+ }),
+});
+
+export const collections = {
+ blog: blogCollection
+};
diff --git a/packages/astro/test/fixtures/core-image-ssg/src/pages/blog/[...slug].astro b/packages/astro/test/fixtures/core-image-ssg/src/pages/blog/[...slug].astro
new file mode 100644
index 000000000..dc25493e8
--- /dev/null
+++ b/packages/astro/test/fixtures/core-image-ssg/src/pages/blog/[...slug].astro
@@ -0,0 +1,33 @@
+---
+import { getImage } from 'astro:assets';
+import { getCollection } from 'astro:content';
+
+export async function getStaticPaths() {
+ const blogEntries = await getCollection('blog');
+ return blogEntries.map(entry => ({
+ params: { slug: entry.slug }, props: { entry },
+ }));
+}
+
+const { entry } = Astro.props;
+const { Content } = await entry.render();
+const myImage = await getImage(entry.data.image);
+---
+<html>
+ <head>
+ <title>Testing</title>
+ </head>
+ <body>
+ <h1>Testing</h1>
+
+ <div id="direct-image">
+ <img src={entry.data.image.src} width={entry.data.image.width} height={entry.data.image.height} />
+ </div>
+
+ <div id="nested-image">
+ <img src={entry.data.cover.image.src} width={entry.data.cover.image.width} height={entry.data.cover.image.height} />
+ </div>
+
+ <Content />
+ </body>
+</html>
diff --git a/packages/astro/test/fixtures/core-image/src/content/blog/one.md b/packages/astro/test/fixtures/core-image/src/content/blog/one.md
index 00e43a6a2..88a210b75 100644
--- a/packages/astro/test/fixtures/core-image/src/content/blog/one.md
+++ b/packages/astro/test/fixtures/core-image/src/content/blog/one.md
@@ -1,6 +1,8 @@
---
title: One
image: penguin2.jpg
+cover:
+ image: penguin1.jpg
---
# A post
diff --git a/packages/astro/test/fixtures/core-image/src/content/config.ts b/packages/astro/test/fixtures/core-image/src/content/config.ts
index bd101a4aa..b38ad070e 100644
--- a/packages/astro/test/fixtures/core-image/src/content/config.ts
+++ b/packages/astro/test/fixtures/core-image/src/content/config.ts
@@ -1,9 +1,12 @@
-import { image, defineCollection, z } from "astro:content";
+import { defineCollection, image, z } from "astro:content";
const blogCollection = defineCollection({
schema: z.object({
title: z.string(),
image: image(),
+ cover: z.object({
+ image: image()
+ })
}),
});
diff --git a/packages/astro/test/fixtures/core-image/src/pages/blog/[...slug].astro b/packages/astro/test/fixtures/core-image/src/pages/blog/[...slug].astro
index 4f6e5003c..b96679688 100644
--- a/packages/astro/test/fixtures/core-image/src/pages/blog/[...slug].astro
+++ b/packages/astro/test/fixtures/core-image/src/pages/blog/[...slug].astro
@@ -1,6 +1,6 @@
---
+import { getImage,Image } from 'astro:assets';
import { getCollection } from 'astro:content';
-import { getImage } from 'astro:assets';
export async function getStaticPaths() {
const blogEntries = await getCollection('blog');
@@ -20,7 +20,21 @@ const myImage = await getImage(entry.data.image);
<body>
<h1>Testing</h1>
- <img src={myImage.src} {...myImage.attributes} />
+ <div id="direct-image">
+ <img src={entry.data.image.src} width={entry.data.image.width} height={entry.data.image.height} />
+ </div>
+
+ <div id="nested-image">
+ <img src={entry.data.cover.image.src} width={entry.data.cover.image.width} height={entry.data.cover.image.height} />
+ </div>
+
+ <div id="optimized-image-get-image">
+ <img src={myImage.src} {...myImage.attributes} />
+ </div>
+
+ <div id="optimized-image-component">
+ <Image src={entry.data.image} alt="A penguin!" />
+ </div>
<Content />
</body>