From a9a6ae29812339ea00f3b9afd3de09bd9d3733a9 Mon Sep 17 00:00:00 2001 From: Erika <3019731+Princesseuh@users.noreply.github.com> Date: Thu, 9 Mar 2023 20:43:58 +0100 Subject: [PATCH 1/5] Fix using images in content collections (#6483) * fix(images): Fix not being able to refer to images in content collections * fix(images): Normalize path * fix(images): Do it properly * chore: changeset --- .changeset/five-coats-tie.md | 5 +++ packages/astro/src/assets/internal.ts | 42 ++++++++++++++++++ .../astro/src/assets/vite-plugin-assets.ts | 35 ++------------- packages/astro/src/content/internal.ts | 2 +- packages/astro/src/content/utils.ts | 37 ++++++++++------ .../content/vite-plugin-content-imports.ts | 13 ++---- packages/astro/test/core-image.test.js | 44 ++++++++++++++++++- .../core-image-ssg/src/content/blog/one.md | 10 +++++ .../core-image-ssg/src/content/config.ts | 15 +++++++ .../src/pages/blog/[...slug].astro | 33 ++++++++++++++ .../core-image/src/content/blog/one.md | 2 + .../fixtures/core-image/src/content/config.ts | 5 ++- .../core-image/src/pages/blog/[...slug].astro | 18 +++++++- 13 files changed, 199 insertions(+), 62 deletions(-) create mode 100644 .changeset/five-coats-tie.md create mode 100644 packages/astro/test/fixtures/core-image-ssg/src/content/blog/one.md create mode 100644 packages/astro/test/fixtures/core-image-ssg/src/content/config.ts create mode 100644 packages/astro/test/fixtures/core-image-ssg/src/pages/blog/[...slug].astro diff --git a/.changeset/five-coats-tie.md b/.changeset/five-coats-tie.md new file mode 100644 index 0000000000000..b358aa22c61b8 --- /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 aa69f71832606..0202e2359ddcf 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 1e84b22718036..9af9c6073b366 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 2fdfe74545ab6..bdc99bbc7414c 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 011ade19f89f4..22b71e0afe6d8 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[] { - function findAssets(potentialAssets: Record): 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, + 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 7c335e112956b..a81d1febaa764 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 81a793ffc1f29..e0af4f1aab97a 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 tag', () => { + it('Adds the 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 0000000000000..88a210b755047 --- /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 0000000000000..b38ad070e1003 --- /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 0000000000000..dc25493e854a6 --- /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); +--- + + + Testing + + +

Testing

+ +
+ +
+ +
+ +
+ + + + 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 00e43a6a214d9..88a210b755047 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 bd101a4aa383b..b38ad070e1003 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 4f6e5003c1145..b9667968835ff 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);

Testing

- +
+ +
+ +
+ +
+ +
+ +
+ +
+ A penguin! +
From d637d1ea5b347b9c724adc895c9006c696ac8fc8 Mon Sep 17 00:00:00 2001 From: Bjorn Lu Date: Fri, 10 Mar 2023 03:44:14 +0800 Subject: [PATCH 2/5] Fix `@astrojs/prism` edgecase with pnpm (#6485) --- .changeset/tall-taxis-exercise.md | 5 +++++ packages/astro/src/core/create-vite.ts | 15 +++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 .changeset/tall-taxis-exercise.md diff --git a/.changeset/tall-taxis-exercise.md b/.changeset/tall-taxis-exercise.md new file mode 100644 index 0000000000000..18f2b6111b506 --- /dev/null +++ b/.changeset/tall-taxis-exercise.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fix `@astrojs/prism` edgecase with strict package managers diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index 7308a56dff1f4..f4316bc2c110d 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -49,6 +49,16 @@ const ALWAYS_NOEXTERNAL = [ '@fontsource/*', ]; +// These specifiers are usually dependencies written in CJS, but loaded through Vite's transform +// pipeline, which Vite doesn't support in development time. This hardcoded list temporarily +// fixes things until Vite can properly handle them, or when they support ESM. +const ONLY_DEV_EXTERNAL = [ + // Imported by `` which is processed by Vite + 'shiki', + // Imported by `@astrojs/prism` which exposes `` that is processed by Vite + 'prismjs/components/index.js', +]; + /** Return a common starting point for all Vite actions */ export async function createVite( commandConfig: vite.InlineConfig, @@ -162,10 +172,7 @@ export async function createVite( }, ssr: { noExternal: [...ALWAYS_NOEXTERNAL, ...astroPkgsConfig.ssr.noExternal], - // shiki is imported by Code.astro, which is no-externalized (processed by Vite). - // However, shiki's deps are in CJS and trips up Vite's dev SSR transform, externalize - // shiki to load it with node instead. - external: [...(mode === 'dev' ? ['shiki'] : []), ...astroPkgsConfig.ssr.external], + external: [...(mode === 'dev' ? ONLY_DEV_EXTERNAL : []), ...astroPkgsConfig.ssr.external], }, }; From dbd61c111dc1ecf47f3b1f747827221ebac5d2be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elian=20=E2=98=95=EF=B8=8F?= Date: Thu, 9 Mar 2023 21:02:33 +0100 Subject: [PATCH 3/5] add browser to bug reports (#6474) --- .github/ISSUE_TEMPLATE/---01-bug-report.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/---01-bug-report.yml b/.github/ISSUE_TEMPLATE/---01-bug-report.yml index 9da01a3346e8a..d42305e9224e6 100644 --- a/.github/ISSUE_TEMPLATE/---01-bug-report.yml +++ b/.github/ISSUE_TEMPLATE/---01-bug-report.yml @@ -39,6 +39,13 @@ body: placeholder: Mac, Windows, Linux validations: required: true + - type: input + id: browser + attributes: + label: What browser are you using? + placeholder: Chrome, Firefox, Safari + validations: + required: true - type: textarea id: bug-description attributes: From bfd67ea749dbc6ffa7c9a671fcc48bea6c04a075 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Thu, 9 Mar 2023 16:08:47 -0500 Subject: [PATCH 4/5] Remove usage of createRequire in core image (#6488) * Add fallback for hosts without import.meta.url * Try using an import * Add image-size as external * Make image-size external * Apply suggestions from code review * leave image-size alone --- .changeset/lucky-shoes-mate.md | 5 +++++ packages/astro/src/assets/utils/metadata.ts | 4 +--- packages/astro/src/core/sync/index.ts | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 .changeset/lucky-shoes-mate.md diff --git a/.changeset/lucky-shoes-mate.md b/.changeset/lucky-shoes-mate.md new file mode 100644 index 0000000000000..32ddde78d8af6 --- /dev/null +++ b/.changeset/lucky-shoes-mate.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Remove use of createRequire breaking non-Node hosts. diff --git a/packages/astro/src/assets/utils/metadata.ts b/packages/astro/src/assets/utils/metadata.ts index d1bc37bad2051..1e96cbe97ec01 100644 --- a/packages/astro/src/assets/utils/metadata.ts +++ b/packages/astro/src/assets/utils/metadata.ts @@ -1,9 +1,7 @@ -import { createRequire } from 'module'; import fs from 'node:fs/promises'; import { fileURLToPath } from 'node:url'; import { ImageMetadata, InputFormat } from '../types.js'; -const require = createRequire(import.meta.url); -const sizeOf = require('image-size'); +import sizeOf from 'image-size'; export interface Metadata extends ImageMetadata { orientation?: number; diff --git a/packages/astro/src/core/sync/index.ts b/packages/astro/src/core/sync/index.ts index 914783331c5a6..82575f0837a62 100644 --- a/packages/astro/src/core/sync/index.ts +++ b/packages/astro/src/core/sync/index.ts @@ -67,6 +67,7 @@ export async function sync( { server: { middlewareMode: true, hmr: false }, optimizeDeps: { entries: [] }, + ssr: { external: ['image-size'] }, logLevel: 'silent', }, { settings, logging, mode: 'build', command: 'build', fs } From 3f6a0888b024325c687cb2da3ee836e6eb93ef64 Mon Sep 17 00:00:00 2001 From: matthewp Date: Thu, 9 Mar 2023 21:11:07 +0000 Subject: [PATCH 5/5] [ci] format --- packages/astro/src/assets/utils/metadata.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/astro/src/assets/utils/metadata.ts b/packages/astro/src/assets/utils/metadata.ts index 1e96cbe97ec01..8cf0cc4708920 100644 --- a/packages/astro/src/assets/utils/metadata.ts +++ b/packages/astro/src/assets/utils/metadata.ts @@ -1,7 +1,7 @@ +import sizeOf from 'image-size'; import fs from 'node:fs/promises'; import { fileURLToPath } from 'node:url'; import { ImageMetadata, InputFormat } from '../types.js'; -import sizeOf from 'image-size'; export interface Metadata extends ImageMetadata { orientation?: number;