Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow remark plugins to affect getImage call for .md files #9566

Merged
merged 18 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/calm-socks-shake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@astrojs/markdown-remark": minor
"astro": minor
---

Allow remark plugins to add hProperties to imgs that will be included in the getImage call if they are optimized.
OliverSpeir marked this conversation as resolved.
Show resolved Hide resolved
73 changes: 50 additions & 23 deletions packages/astro/src/vite-plugin-markdown/images.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,60 @@ export type MarkdownImagePath = { raw: string; resolved: string; safeName: strin

export function getMarkdownCodeForImages(imagePaths: MarkdownImagePath[], html: string) {
return `
import { getImage } from "astro:assets";
${imagePaths
.map((entry) => `import Astro__${entry.safeName} from ${JSON.stringify(entry.raw)};`)
.join('\n')}
import { getImage } from "astro:assets";
${imagePaths
.map((entry) => `import Astro__${entry.safeName} from ${JSON.stringify(entry.raw)};`)
.join('\n')}

const images = async function() {
return {
${imagePaths
.map((entry) => `"${entry.raw}": await getImage({src: Astro__${entry.safeName}})`)
.join(',\n')}
}
}
const images = async function(html) {
const imageSources = {};
${imagePaths
.map((entry) => {
const rawUrl = JSON.stringify(entry.raw);
return `{
const regex = new RegExp('__ASTRO_IMAGE_="([^"]*' + ${rawUrl} + '[^"]*)"', 'g');
let match;
let occurrenceCounter = 0;
while ((match = regex.exec(html)) !== null) {
const matchKey = ${rawUrl} + '_' + occurrenceCounter;
const imageProps = JSON.parse(match[1].replace(/"/g, '"'));
const { src, ...props } = imageProps;

imageSources[matchKey] = await getImage({src: Astro__${entry.safeName}, ...props});
occurrenceCounter++;
}
}`;
})
.join('\n')}
return imageSources;
};

async function updateImageReferences(html) {
return images().then((images) => {
return html.replaceAll(/__ASTRO_IMAGE_="([^"]+)"/gm, (full, imagePath) =>
spreadAttributes({
src: images[imagePath].src,
...images[imagePath].attributes,
})
);
return images(html).then((imageSources) => {
return html.replaceAll(/__ASTRO_IMAGE_="([^"]+)"/gm, (full, imagePath) => {
const decodedImagePath = JSON.parse(imagePath.replace(/"/g, '"'));

// Use the 'index' property for each image occurrence
const srcKey = decodedImagePath.src + '_' + decodedImagePath.index;

if (imageSources[srcKey].srcSet && imageSources[srcKey].srcSet.values.length > 0) {
imageSources[srcKey].attributes.srcset = imageSources[srcKey].srcSet.attribute;
}

const { index, ...attributesWithoutIndex } = imageSources[srcKey].attributes;

return spreadAttributes({
src: imageSources[srcKey].src,
...attributesWithoutIndex,
});
});
});
}
}


// NOTE: This causes a top-level await to appear in the user's code, which can break very easily due to a Rollup
// bug and certain adapters not supporting it correctly. See: https://github.com/rollup/rollup/issues/4708
// Tread carefully!
// NOTE: This causes a top-level await to appear in the user's code, which can break very easily due to a Rollup
// bug and certain adapters not supporting it correctly. See: https://github.com/rollup/rollup/issues/4708
// Tread carefully!
const html = await updateImageReferences(${JSON.stringify(html)});
`;
`;
}
35 changes: 23 additions & 12 deletions packages/markdown/remark/src/rehype-images.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,28 @@ import type { MarkdownVFile } from './types.js';

export function rehypeImages() {
return () =>
function (tree: any, file: MarkdownVFile) {
visit(tree, (node) => {
if (node.type !== 'element') return;
if (node.tagName !== 'img') return;
function (tree: any, file: MarkdownVFile) {
const imageOccurrenceMap = new Map();

if (node.properties?.src) {
if (file.data.imagePaths?.has(node.properties.src)) {
node.properties['__ASTRO_IMAGE_'] = node.properties.src;
delete node.properties.src;
}
}
});
};
visit(tree, (node) => {
if (node.type !== 'element') return;
if (node.tagName !== 'img') return;

if (node.properties?.src) {
if (file.data.imagePaths?.has(node.properties.src)) {
const { alt, ...otherProps } = node.properties;
OliverSpeir marked this conversation as resolved.
Show resolved Hide resolved

// Initialize or increment occurrence count for this image
const index = imageOccurrenceMap.get(node.properties.src) || 0;
imageOccurrenceMap.set(node.properties.src, index + 1);

node.properties['__ASTRO_IMAGE_'] = JSON.stringify({ ...otherProps, index });

Object.keys(otherProps).forEach((prop) => {
delete node.properties[prop];
});
OliverSpeir marked this conversation as resolved.
Show resolved Hide resolved
}
}
});
};
}
46 changes: 23 additions & 23 deletions packages/markdown/remark/test/remark-collect-images.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,32 @@ import { createMarkdownProcessor } from '../dist/index.js';
import chai from 'chai';

describe('collect images', async () => {
const processor = await createMarkdownProcessor();
const processor = await createMarkdownProcessor();

it('should collect inline image paths', async () => {
const {
code,
metadata: { imagePaths },
} = await processor.render(`Hello ![inline image url](./img.png)`, {
fileURL: 'file.md',
});
it('should collect inline image paths', async () => {
const {
code,
metadata: { imagePaths },
} = await processor.render(`Hello ![inline image url](./img.png)`, {
fileURL: 'file.md',
});

chai
.expect(code)
.to.equal('<p>Hello <img alt="inline image url" __ASTRO_IMAGE_="./img.png"></p>');
chai
.expect(code)
.to.equal('<p>Hello <img alt="inline image url" __ASTRO_IMAGE_="{&#x22;src&#x22;:&#x22;./img.png&#x22;,&#x22;index&#x22;:0}"></p>');

chai.expect(Array.from(imagePaths)).to.deep.equal(['./img.png']);
});
chai.expect(Array.from(imagePaths)).to.deep.equal(['./img.png']);
});

it('should add image paths from definition', async () => {
const {
code,
metadata: { imagePaths },
} = await processor.render(`Hello ![image ref][img-ref]\n\n[img-ref]: ./img.webp`, {
fileURL: 'file.md',
});
it('should add image paths from definition', async () => {
const {
code,
metadata: { imagePaths },
} = await processor.render(`Hello ![image ref][img-ref]\n\n[img-ref]: ./img.webp`, {
fileURL: 'file.md',
});

chai.expect(code).to.equal('<p>Hello <img alt="image ref" __ASTRO_IMAGE_="./img.webp"></p>');
chai.expect(Array.from(imagePaths)).to.deep.equal(['./img.webp']);
});
chai.expect(code).to.equal('<p>Hello <img alt="image ref" __ASTRO_IMAGE_="{&#x22;src&#x22;:&#x22;./img.webp&#x22;,&#x22;index&#x22;:0}"></p>');
chai.expect(Array.from(imagePaths)).to.deep.equal(['./img.webp']);
});
});