diff options
6 files changed, 117 insertions, 21 deletions
diff --git a/.changeset/shaggy-spies-sit.md b/.changeset/shaggy-spies-sit.md new file mode 100644 index 000000000..70776cb1e --- /dev/null +++ b/.changeset/shaggy-spies-sit.md @@ -0,0 +1,41 @@ +--- +"@astrojs/markdoc": minor +--- + +Adds support for using a custom tag (component) for optimized images + +Starting from this version, when a tag called `image` is used, its `src` attribute will automatically be resolved if it's a local image. Astro will pass the result `ImageMetadata` object to the underlying component as the `src` prop. For non-local images (i.e. images using URLs or absolute paths), Astro will continue to pass the `src` as a string. + +```ts +// markdoc.config.mjs +import { component, defineMarkdocConfig, nodes } from '@astrojs/markdoc/config'; + +export default defineMarkdocConfig({ + tags: { + image: { + attributes: nodes.image.attributes, + render: component('./src/components/MarkdocImage.astro'), + }, + }, +}); +``` +```astro +--- +// src/components/MarkdocImage.astro +import { Image } from "astro:assets"; + +interface Props { + src: ImageMetadata | string; + alt: string; + width: number; + height: number; +} + +const { src, alt, width, height } = Astro.props; +--- + +<Image {src} {alt} {width} {height} /> +``` +```mdoc +{% image src="./astro-logo.png" alt="Astro Logo" width="100" height="100" %} +`````` diff --git a/packages/integrations/markdoc/src/content-entry-type.ts b/packages/integrations/markdoc/src/content-entry-type.ts index 384dec944..eea580f72 100644 --- a/packages/integrations/markdoc/src/content-entry-type.ts +++ b/packages/integrations/markdoc/src/content-entry-type.ts @@ -179,28 +179,35 @@ async function emitOptimizedImages( } ) { for (const node of nodeChildren) { - if ( - node.type === 'image' && - typeof node.attributes.src === 'string' && - shouldOptimizeImage(node.attributes.src) - ) { - // Attempt to resolve source with Vite. - // This handles relative paths and configured aliases - const resolved = await ctx.pluginContext.resolve(node.attributes.src, ctx.filePath); + let isComponent = node.type === 'tag' && node.tag === 'image'; + // Support either a ![]() or {% image %} syntax, and handle the `src` attribute accordingly. + if ((node.type === 'image' || isComponent) && typeof node.attributes.src === 'string') { + let attributeName = isComponent ? 'src' : '__optimizedSrc'; - if (resolved?.id && fs.existsSync(new URL(prependForwardSlash(resolved.id), 'file://'))) { - const src = await emitESMImage( - resolved.id, - ctx.pluginContext.meta.watchMode, - ctx.pluginContext.emitFile - ); - node.attributes.__optimizedSrc = src; - } else { - throw new MarkdocError({ - message: `Could not resolve image ${JSON.stringify( - node.attributes.src - )} from ${JSON.stringify(ctx.filePath)}. Does the file exist?`, - }); + // If the image isn't an URL or a link to public, try to resolve it. + if (shouldOptimizeImage(node.attributes.src)) { + // Attempt to resolve source with Vite. + // This handles relative paths and configured aliases + const resolved = await ctx.pluginContext.resolve(node.attributes.src, ctx.filePath); + + if (resolved?.id && fs.existsSync(new URL(prependForwardSlash(resolved.id), 'file://'))) { + const src = await emitESMImage( + resolved.id, + ctx.pluginContext.meta.watchMode, + ctx.pluginContext.emitFile + ); + node.attributes[attributeName] = src; + } else { + throw new MarkdocError({ + message: `Could not resolve image ${JSON.stringify( + node.attributes.src + )} from ${JSON.stringify(ctx.filePath)}. Does the file exist?`, + }); + } + } else if (isComponent) { + // If the user is using the {% image %} tag, always pass the `src` attribute as `__optimizedSrc`, even if it's an external URL or absolute path. + // That way, the component can decide whether to optimize it or not. + node.attributes[attributeName] = node.attributes.src; } } await emitOptimizedImages(node.children, ctx); diff --git a/packages/integrations/markdoc/test/fixtures/image-assets/markdoc.config.mjs b/packages/integrations/markdoc/test/fixtures/image-assets/markdoc.config.mjs new file mode 100644 index 000000000..04fe75244 --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/image-assets/markdoc.config.mjs @@ -0,0 +1,10 @@ +import { component, defineMarkdocConfig, nodes } from '@astrojs/markdoc/config'; + +export default defineMarkdocConfig({ + tags: { + image: { + attributes: nodes.image.attributes, + render: component('./src/components/Image.astro'), + }, + }, +}); diff --git a/packages/integrations/markdoc/test/fixtures/image-assets/src/components/Image.astro b/packages/integrations/markdoc/test/fixtures/image-assets/src/components/Image.astro new file mode 100644 index 000000000..aac129452 --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/image-assets/src/components/Image.astro @@ -0,0 +1,22 @@ +--- +// src/components/MyImage.astro +import type { ImageMetadata } from 'astro'; +import { Image } from 'astro:assets'; +type Props = { + src: string | ImageMetadata; + alt: string; +}; +const { src, alt } = Astro.props; +--- +{ + typeof src === 'string' ? ( + <img class="custom-styles" src={src} alt={alt} /> + ) : ( + <Image class="custom-styles" {src} {alt} /> + ) +} +<style> + .custom-styles { + border: 1px solid red; + } +</style> diff --git a/packages/integrations/markdoc/test/fixtures/image-assets/src/content/docs/intro.mdoc b/packages/integrations/markdoc/test/fixtures/image-assets/src/content/docs/intro.mdoc index ae5fced49..f5ba3950f 100644 --- a/packages/integrations/markdoc/test/fixtures/image-assets/src/content/docs/intro.mdoc +++ b/packages/integrations/markdoc/test/fixtures/image-assets/src/content/docs/intro.mdoc @@ -5,3 +5,5 @@  {% #relative %}  {% #alias %} + +{% image src="../../assets/relative/oar.jpg" alt="oar" /%} {% #component %} diff --git a/packages/integrations/markdoc/test/image-assets.test.js b/packages/integrations/markdoc/test/image-assets.test.js index ae576b5cf..880ee9b26 100644 --- a/packages/integrations/markdoc/test/image-assets.test.js +++ b/packages/integrations/markdoc/test/image-assets.test.js @@ -51,6 +51,13 @@ describe('Markdoc - Image assets', () => { /\/_image\?href=.*%2Fsrc%2Fassets%2Falias%2Fcityscape.jpg%3ForigWidth%3D420%26origHeight%3D280%26origFormat%3Djpg&f=webp/ ); }); + + it('passes images inside image tags to configured image component', async () => { + const res = await baseFixture.fetch('/'); + const html = await res.text(); + const { document } = parseHTML(html); + assert.equal(document.querySelector('#component > img')?.className, 'custom-styles'); + }); }); describe('build', () => { @@ -75,5 +82,12 @@ describe('Markdoc - Image assets', () => { const { document } = parseHTML(html); assert.match(document.querySelector('#alias > img')?.src, /^\/_astro\/cityscape.*\.webp$/); }); + + it('passes images inside image tags to configured image component', async () => { + const html = await baseFixture.readFile('/index.html'); + const { document } = parseHTML(html); + assert.equal(document.querySelector('#component > img')?.className, 'custom-styles'); + assert.match(document.querySelector('#component > img')?.src, /^\/_astro\/oar.*\.webp$/); + }); }); }); |