#[Solved] How to pass data from `remark` to `rehype` (code metastrings)?

6 messages · Page 1 of 1 (latest)

glossy oyster
#

Hello. While working with MDX, I discovered that Astro's built-in remark/rehype plugins do not pass code block metastrings (e.g. title="file.js") as properties down to final html components. I've tried to create remark/rehype plugins, but once rehype plugin is executed, metastring data is lost. Is there a proper/convenient way to preserve such data to pass it as properties?

The only way I can think of to do it is very hacky:

let metaIdx = 0;
let metastrings: string[] = [];

export default defineConfig({
  markdown: {
    remarkPlugins: [() => (root) => {
      metaIdx = 0;
      metastrings = [];
      visit(root, 'code', (node) => {
        metastrings.push(node.meta);
      });
    }],
    rehypePlugins: [() => (root) => visit(root, 'pre', (node) => {
      const idx = metaIdx++;
      node.properties.metastring = metastrings[idx];
    })],
  },
});
cinder flickerBOT
#
Still waiting for an answer?

It looks like no-one has responded to your question yet. People might not be available right now or don’t know how to answer your question. Want an answer while you wait? Try asking our experimental bot in #1095492539085230272.

glossy oyster
#

I've managed to come up with a less (but not completely non-) hacky solution:

export default defineConfig({
  markdown: {
    remarkPlugins: [
      () => (root, file) => {
        file.data.meta = [];

        visit(root, 'code', (node) => {
          const entry = { lang: node.lang };
          const metastrings = node.meta?.split(' ') ?? [];

          for (const metastring of metastrings) {
            const [key, value] = metastring.split('=');
            entry[key] = value.slice(1, -1);
          }

          file.data.meta.push(entry);
        });
      },
    ],
    rehypePlugins: [
      () => (root, file) => {
        let idx = 0;
        const meta = file.data.meta;

        visit(root, { tagName: 'pre' }, (node) => {
          if (!node.properties) node.properties = {};
          Object.assign(node.properties, meta[idx++]);
        });
      },
    ],
  },
});

But this search for a solution made me wonder whether Astro's internal plugins behave correctly: shouldn't remark's node data field be carries over to rehype's?

#

[Partially Solved] How to pass data from remark to rehype (code metastrings)?

glossy oyster
#

I ended up creating my own remark plugin for shiki following Astro's implementation, as well as creating custom renderer for shiki itself (shiki exposes API to get tokens alone) for some extra stuff I wanted. Here's rough sketch of what I do, if anybody is interested:

import type { Root, Html } from 'mdast';
import { getHighlighter } from 'shiki';
import { visit } from 'unist-util-visit';

import { theme } from './theme';
import { renderToHtml } from './renderer';

export const remarkShiki = async () => {
  const highlighter = await getHighlighter({ theme });

  return () => (root: Root) => {
    visit(root, 'code', (node) => {
      const lang = typeof node.lang !== 'string'
        ? 'plaintext'
        : highlighter.getLoadedLanguages().includes(node.lang as any)
          ? node.lang
          : 'plaintext';

      const code = renderToHtml(highlighter.codeToThemedTokens(node.value, lang), lang);
      const pre = `<pre${!node.meta ? '' : ` ${node.meta}`}>${code}</pre>`;

      (node as any as Html).type = 'html';
      (node as any as Html).value = pre;
    });
  };
};

This probably has many pitfalls, but it works for now.

#

[Solved] How to pass data from remark to rehype (code metastrings)?