diff options
Diffstat (limited to 'packages/integrations/mdx')
10 files changed, 129 insertions, 4 deletions
diff --git a/packages/integrations/mdx/src/index.ts b/packages/integrations/mdx/src/index.ts index c27abb4d1..98390b1be 100644 --- a/packages/integrations/mdx/src/index.ts +++ b/packages/integrations/mdx/src/index.ts @@ -14,6 +14,7 @@ import type { Plugin as VitePlugin } from 'vite'; import { getRehypePlugins, getRemarkPlugins, recmaInjectImportMetaEnvPlugin } from './plugins.js'; import type { OptimizeOptions } from './rehype-optimize-static.js'; import { getFileInfo, ignoreStringPlugins, parseFrontmatter } from './utils.js'; +import { ASTRO_IMAGE_ELEMENT, ASTRO_IMAGE_IMPORT, USES_ASTRO_IMAGE_FLAG } from './remark-images-to-component.js'; export type MdxOptions = Omit<typeof markdownConfigDefaults, 'remarkPlugins' | 'rehypePlugins'> & { extendMarkdownConfig: boolean; @@ -194,12 +195,19 @@ export default function mdx(partialMdxOptions: Partial<MdxOptions> = {}): AstroI if (!moduleExports.find(({ n }) => n === 'Content')) { // If have `export const components`, pass that as props to `Content` as fallback const hasComponents = moduleExports.find(({ n }) => n === 'components'); + const usesAstroImage = moduleExports.find(({n}) => n === USES_ASTRO_IMAGE_FLAG); + + let componentsCode = `{ Fragment${hasComponents ? ', ...components' : ''}, ...props.components,` + if (usesAstroImage) { + componentsCode += ` ${JSON.stringify(ASTRO_IMAGE_ELEMENT)}: ${hasComponents ? 'components.img ?? ' : ''} props.components?.img ?? ${ASTRO_IMAGE_IMPORT}`; + } + componentsCode += ' }'; // Make `Content` the default export so we can wrap `MDXContent` and pass in `Fragment` code = code.replace('export default MDXContent;', ''); code += `\nexport const Content = (props = {}) => MDXContent({ ...props, - components: { Fragment${hasComponents ? ', ...components' : ''}, ...props.components }, + components: ${componentsCode}, }); export default Content;`; } diff --git a/packages/integrations/mdx/src/remark-images-to-component.ts b/packages/integrations/mdx/src/remark-images-to-component.ts index bb9657f42..40d414b5c 100644 --- a/packages/integrations/mdx/src/remark-images-to-component.ts +++ b/packages/integrations/mdx/src/remark-images-to-component.ts @@ -4,6 +4,10 @@ import type { MdxJsxFlowElement, MdxjsEsm } from 'mdast-util-mdx'; import { visit } from 'unist-util-visit'; import { jsToTreeNode } from './utils.js'; +export const ASTRO_IMAGE_ELEMENT = 'astro-image'; +export const ASTRO_IMAGE_IMPORT = '__AstroImage__'; +export const USES_ASTRO_IMAGE_FLAG = '__usesAstroImage'; + export function remarkImageToComponent() { return function (tree: any, file: MarkdownVFile) { if (!file.data.imagePaths) return; @@ -48,7 +52,7 @@ export function remarkImageToComponent() { // Build a component that's equivalent to <Image src={importName} alt={node.alt} title={node.title} /> const componentElement: MdxJsxFlowElement = { - name: '__AstroImage__', + name: ASTRO_IMAGE_ELEMENT, type: 'mdxJsxFlowElement', attributes: [ { @@ -92,7 +96,9 @@ export function remarkImageToComponent() { // Add all the import statements to the top of the file for the images tree.children.unshift(...importsStatements); - // Add an import statement for the Astro Image component, we rename it to avoid conflicts - tree.children.unshift(jsToTreeNode(`import { Image as __AstroImage__ } from "astro:assets";`)); + tree.children.unshift(jsToTreeNode(`import { Image as ${ASTRO_IMAGE_IMPORT} } from "astro:assets";`)); + // Export `__usesAstroImage` to pick up `astro:assets` usage in the module graph. + // @see the '@astrojs/mdx-postprocess' plugin + tree.children.push(jsToTreeNode(`export const ${USES_ASTRO_IMAGE_FLAG} = true`)); }; } diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/public/favicon.svg b/packages/integrations/mdx/test/fixtures/mdx-images/public/favicon.svg new file mode 100644 index 000000000..f157bd1c5 --- /dev/null +++ b/packages/integrations/mdx/test/fixtures/mdx-images/public/favicon.svg @@ -0,0 +1,9 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128"> + <path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" /> + <style> + path { fill: #000; } + @media (prefers-color-scheme: dark) { + path { fill: #FFF; } + } + </style> +</svg> diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/src/components/Component.mdx b/packages/integrations/mdx/test/fixtures/mdx-images/src/components/Component.mdx new file mode 100644 index 000000000..7463939ba --- /dev/null +++ b/packages/integrations/mdx/test/fixtures/mdx-images/src/components/Component.mdx @@ -0,0 +1,5 @@ +Optimized image: + + +Public image: + diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/src/components/MyImage.astro b/packages/integrations/mdx/test/fixtures/mdx-images/src/components/MyImage.astro new file mode 100644 index 000000000..e3541867c --- /dev/null +++ b/packages/integrations/mdx/test/fixtures/mdx-images/src/components/MyImage.astro @@ -0,0 +1,25 @@ +--- +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 data-my-image src={src} alt={alt} /> + ) : ( + <Image data-my-image {src} {alt} /> + ) +} + +<style> + [data-my-image] { + border: 1px solid red; + } +</style> diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/src/content/blog/entry.mdx b/packages/integrations/mdx/test/fixtures/mdx-images/src/content/blog/entry.mdx new file mode 100644 index 000000000..58aebcf54 --- /dev/null +++ b/packages/integrations/mdx/test/fixtures/mdx-images/src/content/blog/entry.mdx @@ -0,0 +1,5 @@ +Optimized image: + + +Public image: + diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/content-collection.astro b/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/content-collection.astro new file mode 100644 index 000000000..63d068b5c --- /dev/null +++ b/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/content-collection.astro @@ -0,0 +1,19 @@ +--- +import { getEntry } from 'astro:content'; +import MyImage from 'src/components/MyImage.astro'; + +const entry = await getEntry('blog', 'entry'); +const { Content } = await entry.render(); +--- + +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Renderer</title> +</head> +<body> + <Content components={{ img: MyImage }} /> +</body> +</html> diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/esm-import.astro b/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/esm-import.astro new file mode 100644 index 000000000..e5f7a61d9 --- /dev/null +++ b/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/esm-import.astro @@ -0,0 +1,16 @@ +--- +import MDX from '../components/Component.mdx'; +import MyImage from 'src/components/MyImage.astro'; +--- + +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Renderer</title> +</head> +<body> + <MDX components={{ img: MyImage }} /> +</body> +</html> diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/with-components.mdx b/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/with-components.mdx new file mode 100644 index 000000000..763256b1c --- /dev/null +++ b/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/with-components.mdx @@ -0,0 +1,9 @@ +import MyImage from '../components/MyImage.astro'; + +export const components = { img: MyImage }; + +Optimized image: + + +Public image: + diff --git a/packages/integrations/mdx/test/mdx-images.test.js b/packages/integrations/mdx/test/mdx-images.test.js index c9c8e1f7c..128a2fcb0 100644 --- a/packages/integrations/mdx/test/mdx-images.test.js +++ b/packages/integrations/mdx/test/mdx-images.test.js @@ -2,6 +2,8 @@ import { expect } from 'chai'; import { parseHTML } from 'linkedom'; import { loadFixture } from '../../../astro/test/test-utils.js'; +const imageTestRoutes = ['with-components', 'esm-import', 'content-collection'] + describe('MDX Page', () => { let devServer; let fixture; @@ -36,5 +38,26 @@ describe('MDX Page', () => { // Image with spaces in the path expect(imgs.item(3).src.startsWith('/_image')).to.be.true; }); + + for (const route of imageTestRoutes) { + it(`supports img component - ${route}`, async () => { + const res = await fixture.fetch(`/${route}`); + expect(res.status).to.equal(200); + + const html = await res.text(); + const { document } = parseHTML(html); + + const imgs = document.getElementsByTagName('img'); + expect(imgs.length).to.equal(2); + + const assetsImg = imgs.item(0); + expect(assetsImg.src.startsWith('/_image')).to.be.true; + expect(assetsImg.hasAttribute('data-my-image')).to.be.true; + + const publicImg = imgs.item(1); + expect(publicImg.src).to.equal('/favicon.svg'); + expect(publicImg.hasAttribute('data-my-image')).to.be.true; + }); + } }); }); |