mirror of
https://github.com/withastro/astro.git
synced 2024-12-16 21:46:22 -05:00
Pass meta to shiki transformers (#10494)
This commit is contained in:
parent
17b4991cff
commit
19e42c3681
5 changed files with 55 additions and 3 deletions
5
.changeset/stale-jeans-yawn.md
Normal file
5
.changeset/stale-jeans-yawn.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"@astrojs/markdown-remark": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fixes support for Shiki transformers that access the `meta` to conditionally perform transformations
|
|
@ -12,6 +12,9 @@ export default async function shiki(config?: ShikiConfig): Promise<AstroMarkdocC
|
||||||
fence: {
|
fence: {
|
||||||
attributes: Markdoc.nodes.fence.attributes!,
|
attributes: Markdoc.nodes.fence.attributes!,
|
||||||
transform({ attributes }) {
|
transform({ attributes }) {
|
||||||
|
// NOTE: The `meta` from fence code, e.g. ```js {1,3-4}, isn't quite supported by Markdoc.
|
||||||
|
// Only the `js` part is parsed as `attributes.language` and the rest is ignored. This means
|
||||||
|
// some Shiki transformers may not work correctly as it relies on the `meta`.
|
||||||
const lang = typeof attributes.language === 'string' ? attributes.language : 'plaintext';
|
const lang = typeof attributes.language === 'string' ? attributes.language : 'plaintext';
|
||||||
const html = highlighter.highlight(attributes.content, lang);
|
const html = highlighter.highlight(attributes.content, lang);
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { toText } from 'hast-util-to-text';
|
||||||
import { removePosition } from 'unist-util-remove-position';
|
import { removePosition } from 'unist-util-remove-position';
|
||||||
import { visitParents } from 'unist-util-visit-parents';
|
import { visitParents } from 'unist-util-visit-parents';
|
||||||
|
|
||||||
type Highlighter = (code: string, language: string) => string;
|
type Highlighter = (code: string, language: string, options?: { meta?: string }) => string;
|
||||||
|
|
||||||
const languagePattern = /\blanguage-(\S+)\b/;
|
const languagePattern = /\blanguage-(\S+)\b/;
|
||||||
|
|
||||||
|
@ -55,8 +55,9 @@ export function highlightCodeBlocks(tree: Root, highlighter: Highlighter) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const meta = (node.data as any)?.meta ?? node.properties.metastring ?? undefined;
|
||||||
const code = toText(node, { whitespace: 'pre' });
|
const code = toText(node, { whitespace: 'pre' });
|
||||||
const html = highlighter(code, languageMatch?.[1] || 'plaintext');
|
const html = highlighter(code, languageMatch?.[1] || 'plaintext', { meta });
|
||||||
// The replacement returns a root node with 1 child, the `<pr>` element replacement.
|
// The replacement returns a root node with 1 child, the `<pr>` element replacement.
|
||||||
const replacement = fromHtml(html, { fragment: true }).children[0] as Element;
|
const replacement = fromHtml(html, { fragment: true }).children[0] as Element;
|
||||||
// We just generated this node, so any positional information is invalid.
|
// We just generated this node, so any positional information is invalid.
|
||||||
|
|
|
@ -7,7 +7,14 @@ export interface ShikiHighlighter {
|
||||||
highlight(
|
highlight(
|
||||||
code: string,
|
code: string,
|
||||||
lang?: string,
|
lang?: string,
|
||||||
options?: { inline?: boolean; attributes?: Record<string, string> }
|
options?: {
|
||||||
|
inline?: boolean;
|
||||||
|
attributes?: Record<string, string>;
|
||||||
|
/**
|
||||||
|
* Raw `meta` information to be used by Shiki transformers
|
||||||
|
*/
|
||||||
|
meta?: string;
|
||||||
|
}
|
||||||
): string;
|
): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +63,10 @@ export async function createShikiHighlighter({
|
||||||
return highlighter.codeToHtml(code, {
|
return highlighter.codeToHtml(code, {
|
||||||
...themeOptions,
|
...themeOptions,
|
||||||
lang,
|
lang,
|
||||||
|
// NOTE: while we can spread `options.attributes` here so that Shiki can auto-serialize this as rendered
|
||||||
|
// attributes on the top-level tag, it's not clear whether it is fine to pass all attributes as meta, as
|
||||||
|
// they're technically not meta, nor parsed from Shiki's `parseMetaString` API.
|
||||||
|
meta: options?.meta ? { __raw: options?.meta } : undefined,
|
||||||
transformers: [
|
transformers: [
|
||||||
{
|
{
|
||||||
pre(node) {
|
pre(node) {
|
||||||
|
|
|
@ -53,4 +53,36 @@ describe('shiki syntax highlighting', () => {
|
||||||
assert.match(html, />-<\/span>/);
|
assert.match(html, />-<\/span>/);
|
||||||
assert.match(html, />+<\/span>/);
|
assert.match(html, />+<\/span>/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders attributes', async () => {
|
||||||
|
const highlighter = await createShikiHighlighter();
|
||||||
|
|
||||||
|
const html = highlighter.highlight(`foo`, 'js', {
|
||||||
|
attributes: { 'data-foo': 'bar', autofocus: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.match(html, /data-foo="bar"/);
|
||||||
|
assert.match(html, /autofocus(?!=)/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports transformers that reads meta', async () => {
|
||||||
|
const highlighter = await createShikiHighlighter({
|
||||||
|
transformers: [
|
||||||
|
{
|
||||||
|
pre(node) {
|
||||||
|
const meta = this.options.meta?.__raw;
|
||||||
|
if (meta) {
|
||||||
|
node.properties['data-test'] = meta;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const html = highlighter.highlight(`foo`, 'js', {
|
||||||
|
meta: '{1,3-4}',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.match(html, /data-test="\{1,3-4\}"/);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue