astro/packages/integrations/mdx/src/remark-images-to-component.ts
2023-09-13 16:29:09 +00:00

106 lines
3.2 KiB
TypeScript

import type { MarkdownVFile } from '@astrojs/markdown-remark';
import type { Image, Parent } from 'mdast';
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;
const importsStatements: MdxjsEsm[] = [];
const importedImages = new Map<string, string>();
visit(tree, 'image', (node: Image, index: number | null, 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,
});
}
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`));
};
}