summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/shaggy-spies-sit.md41
-rw-r--r--packages/integrations/markdoc/src/content-entry-type.ts49
-rw-r--r--packages/integrations/markdoc/test/fixtures/image-assets/markdoc.config.mjs10
-rw-r--r--packages/integrations/markdoc/test/fixtures/image-assets/src/components/Image.astro22
-rw-r--r--packages/integrations/markdoc/test/fixtures/image-assets/src/content/docs/intro.mdoc2
-rw-r--r--packages/integrations/markdoc/test/image-assets.test.js14
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 @@
![Oar](../../assets/relative/oar.jpg) {% #relative %}
![Gray cityscape arial view](~/assets/alias/cityscape.jpg) {% #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$/);
+ });
});
});