0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-01-27 22:19:04 -05:00
astro/packages/integrations/mdx/src/remark-images-to-component.ts
Oliver Speir df37366556
MDX remark image props (#9753)
* rearrange plugins and add props to Image component

* add tests and update lockfile

* add changeset

* re-rearrange plugin order, gfm/smartypants then user defined then image related then shiki/prism

* make more generic

* add more/better tests

* remove unused logger

---------

Co-authored-by: Erika <3019731+Princesseuh@users.noreply.github.com>
2024-01-23 18:18:09 -05:00

153 lines
4.5 KiB
TypeScript

import type { MarkdownVFile } from '@astrojs/markdown-remark';
import type { Image, Parent } from 'mdast';
import type { MdxJsxFlowElement, MdxjsEsm, MdxJsxAttribute } 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;
const importsStatements: MdxjsEsm[] = [];
const importedImages = new Map<string, string>();
visit(tree, 'image', (node: Image, index: number | undefined, parent: Parent | null) => {
// Use the imagePaths set from the remark-collect-images so we don't have to duplicate the logic for
// checking if an image should be imported or not
if (file.data.imagePaths?.has(node.url)) {
let importName = importedImages.get(node.url);
// If we haven't already imported this image, add an import statement
if (!importName) {
importName = `__${importedImages.size}_${node.url.replace(/\W/g, '_')}__`;
importsStatements.push({
type: 'mdxjsEsm',
value: '',
data: {
estree: {
type: 'Program',
sourceType: 'module',
body: [
{
type: 'ImportDeclaration',
source: { type: 'Literal', value: node.url, raw: JSON.stringify(node.url) },
specifiers: [
{
type: 'ImportDefaultSpecifier',
local: { type: 'Identifier', name: importName },
},
],
},
],
},
},
});
importedImages.set(node.url, importName);
}
// Build a component that's equivalent to <Image src={importName} alt={node.alt} title={node.title} />
const componentElement: MdxJsxFlowElement = {
name: ASTRO_IMAGE_ELEMENT,
type: 'mdxJsxFlowElement',
attributes: [
{
name: 'src',
type: 'mdxJsxAttribute',
value: {
type: 'mdxJsxAttributeValueExpression',
value: importName,
data: {
estree: {
type: 'Program',
sourceType: 'module',
comments: [],
body: [
{
type: 'ExpressionStatement',
expression: { type: 'Identifier', name: importName },
},
],
},
},
},
},
{ name: 'alt', type: 'mdxJsxAttribute', value: node.alt || '' },
],
children: [],
};
if (node.title) {
componentElement.attributes.push({
type: 'mdxJsxAttribute',
name: 'title',
value: node.title,
});
}
if (node.data && node.data.hProperties) {
const createArrayAttribute = (name: string, values: string[]): MdxJsxAttribute => {
return {
type: 'mdxJsxAttribute',
name: name,
value: {
type: 'mdxJsxAttributeValueExpression',
value: name,
data: {
estree: {
type: 'Program',
body: [
{
type: 'ExpressionStatement',
expression: {
type: 'ArrayExpression',
elements: values.map((value) => ({
type: 'Literal',
value: value,
raw: String(value),
})),
},
},
],
sourceType: 'module',
comments: [],
},
},
},
};
};
// Go through every hProperty and add it as an attribute of the <Image>
Object.entries(node.data.hProperties as Record<string, string | string[]>).forEach(
([key, value]) => {
if (Array.isArray(value)) {
componentElement.attributes.push(createArrayAttribute(key, value));
} else {
componentElement.attributes.push({
name: key,
type: 'mdxJsxAttribute',
value: String(value),
});
}
}
);
}
parent!.children.splice(index!, 1, componentElement);
}
});
// Add all the import statements to the top of the file for the images
tree.children.unshift(...importsStatements);
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`));
};
}