Skip to content

Commit

Permalink
MDX remark image props (#9753)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
OliverSpeir and Princesseuh committed Jan 23, 2024
1 parent 2e58904 commit df37366
Show file tree
Hide file tree
Showing 10 changed files with 157 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/quick-cars-kneel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@astrojs/mdx": minor
---

Allows remark plugins to pass options specifying how images in .mdx files will be optimized
4 changes: 2 additions & 2 deletions packages/integrations/mdx/src/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function createMdxProcessor(mdxOptions: MdxOptions, extraOptions: MdxProc
}

function getRemarkPlugins(mdxOptions: MdxOptions): PluggableList {
let remarkPlugins: PluggableList = [remarkCollectImages, remarkImageToComponent];
let remarkPlugins: PluggableList = [];

if (!isPerformanceBenchmark) {
if (mdxOptions.gfm) {
Expand All @@ -54,7 +54,7 @@ function getRemarkPlugins(mdxOptions: MdxOptions): PluggableList {
}
}

remarkPlugins = [...remarkPlugins, ...mdxOptions.remarkPlugins];
remarkPlugins = [...remarkPlugins, ...mdxOptions.remarkPlugins, remarkCollectImages, remarkImageToComponent];

if (!isPerformanceBenchmark) {
// Apply syntax highlighters after user plugins to match `markdown/remark` behavior
Expand Down
49 changes: 48 additions & 1 deletion packages/integrations/mdx/src/remark-images-to-component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { MarkdownVFile } from '@astrojs/markdown-remark';
import type { Image, Parent } from 'mdast';
import type { MdxJsxFlowElement, MdxjsEsm } from 'mdast-util-mdx';
import type { MdxJsxFlowElement, MdxjsEsm, MdxJsxAttribute } from 'mdast-util-mdx';
import { visit } from 'unist-util-visit';
import { jsToTreeNode } from './utils.js';

Expand Down Expand Up @@ -89,6 +89,53 @@ export function remarkImageToComponent() {
});
}

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);
}
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineConfig } from 'astro/config';
import mdx from '@astrojs/mdx';
import plugin from "./remarkPlugin"

// https://astro.build/config
export default defineConfig({
integrations: [mdx({remarkPlugins:[plugin]})],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "@test/image-remark-imgattr",
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/mdx": "workspace:*",
"astro": "workspace:*"
},
"scripts": {
"dev": "astro dev"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export default function plugin() {
return transformer;

function transformer(tree) {
function traverse(node) {
if (node.type === "image") {
node.data = node.data || {};
node.data.hProperties = node.data.hProperties || {};
node.data.hProperties.id = "test";
node.data.hProperties.width = "300";
node.data.hProperties.widths = [300,600];
node.data.hProperties.sizes = "(min-width: 600px) 600w, 300w";
}

if (node.children) {
node.children.forEach(traverse);
}
}

traverse(tree);
}
}
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
![alt](../assets/penguin2.jpg)
50 changes: 50 additions & 0 deletions packages/integrations/mdx/test/remark-imgattr.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { expect } from 'chai';
import * as cheerio from 'cheerio';
import { loadFixture } from '../../../astro/test/test-utils.js';

const FIXTURE_ROOT = new URL('./fixtures/image-remark-imgattr/', import.meta.url);

describe('Testing remark plugins for image processing', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;

describe('start dev server', () => {
/** @type {import('./test-utils').DevServer} */
let devServer;

before(async () => {
fixture = await loadFixture({
root: FIXTURE_ROOT,
});

devServer = await fixture.startDevServer();
});

after(async () => {
await devServer.stop();
});

describe('Test image attributes can be added by remark plugins', () => {
let $;
before(async () => {
let res = await fixture.fetch('/');
let html = await res.text();
$ = cheerio.load(html);
});

it('<img> has correct attributes', async () => {
let $img = $('img');
expect($img.attr('id')).to.equal('test');
expect($img.attr('sizes')).to.equal('(min-width: 600px) 600w, 300w');
expect($img.attr('srcset')).to.not.be.empty;
});

it('<img> was processed properly', async () => {
let $img = $('img');
expect(new URL($img.attr('src'), 'http://example.com').searchParams.get('w')).to.equal(
'300'
);
});
});
});
});
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit df37366

Please sign in to comment.