Skip to content

Commit 710a1a1

Browse files
ematipicobluwysarah11918
authored
feat(markdown): add support for shiki option langAlias (#12039)
* feat(shiki): add support for `langAlias` * chore: apply feedback * Update packages/markdown/remark/src/types.ts Co-authored-by: Bjorn Lu <[email protected]> * fix build * Fix bug * Apply suggestions from code review Co-authored-by: Sarah Rainsberger <[email protected]> * Update .changeset/dirty-socks-sip.md --------- Co-authored-by: Bjorn Lu <[email protected]> Co-authored-by: Sarah Rainsberger <[email protected]>
1 parent 3ac2263 commit 710a1a1

File tree

6 files changed

+78
-4
lines changed

6 files changed

+78
-4
lines changed

.changeset/dirty-socks-sip.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
'@astrojs/markdown-remark': minor
3+
'astro': minor
4+
---
5+
6+
Adds a `markdown.shikiConfig.langAlias` option that allows [aliasing a non-supported code language to a known language](https://shiki.style/guide/load-lang#custom-language-aliases). This is useful when the language of your code samples is not [a built-in Shiki language](https://shiki.style/languages), but you want your Markdown source to contain an accurate language while also displaying syntax highlighting.
7+
8+
The following example configures Shiki to highlight `cjs` code blocks using the `javascript` syntax highlighter:
9+
10+
```js
11+
import { defineConfig } from 'astro/config';
12+
13+
export default defineConfig({
14+
markdown: {
15+
shikiConfig: {
16+
langAlias: {
17+
cjs: 'javascript',
18+
},
19+
},
20+
},
21+
});
22+
```
23+
24+
Then in your Markdown, you can use the alias as the language for a code block for syntax highlighting:
25+
26+
````md
27+
```cjs
28+
'use strict';
29+
30+
function commonJs() {
31+
return 'I am a commonjs file';
32+
}
33+
```
34+
````

packages/astro/src/core/config/schema.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,10 @@ export const AstroConfigSchema = z.object({
313313
return langs;
314314
})
315315
.default([]),
316+
langAlias: z
317+
.record(z.string(), z.string())
318+
.optional()
319+
.default(ASTRO_CONFIG_DEFAULTS.markdown.shikiConfig.langAlias!),
316320
theme: z
317321
.enum(Object.keys(bundledThemes) as [BuiltinTheme, ...BuiltinTheme[]])
318322
.or(z.custom<ShikiTheme>())

packages/markdown/remark/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export const markdownConfigDefaults: Required<AstroMarkdownOptions> = {
3737
themes: {},
3838
wrap: false,
3939
transformers: [],
40+
langAlias: {},
4041
},
4142
remarkPlugins: [],
4243
rehypePlugins: [],

packages/markdown/remark/src/shiki.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,24 +45,29 @@ export async function createShikiHighlighter({
4545
defaultColor,
4646
wrap = false,
4747
transformers = [],
48+
langAlias = {},
4849
}: ShikiConfig = {}): Promise<ShikiHighlighter> {
4950
theme = theme === 'css-variables' ? cssVariablesTheme() : theme;
5051

5152
const highlighter = await getHighlighter({
5253
langs: ['plaintext', ...langs],
54+
langAlias,
5355
themes: Object.values(themes).length ? Object.values(themes) : [theme],
5456
});
5557

5658
return {
5759
async highlight(code, lang = 'plaintext', options) {
60+
const resolvedLang = langAlias[lang] ?? lang;
5861
const loadedLanguages = highlighter.getLoadedLanguages();
5962

60-
if (!isSpecialLang(lang) && !loadedLanguages.includes(lang)) {
63+
if (!isSpecialLang(lang) && !loadedLanguages.includes(resolvedLang)) {
6164
try {
62-
await highlighter.loadLanguage(lang as BundledLanguage);
65+
await highlighter.loadLanguage(resolvedLang as BundledLanguage);
6366
} catch (_err) {
67+
const langStr =
68+
lang === resolvedLang ? `"${lang}"` : `"${lang}" (aliased to "${resolvedLang}")`;
6469
console.warn(
65-
`[Shiki] The language "${lang}" doesn't exist, falling back to "plaintext".`,
70+
`[Shiki] The language ${langStr} doesn't exist, falling back to "plaintext".`,
6671
);
6772
lang = 'plaintext';
6873
}
@@ -120,7 +125,7 @@ export async function createShikiHighlighter({
120125
// Add "user-select: none;" for "+"/"-" diff symbols.
121126
// Transform `<span class="line"><span style="...">+ something</span></span>
122127
// into `<span class="line"><span style="..."><span style="user-select: none;">+</span> something</span></span>`
123-
if (lang === 'diff') {
128+
if (resolvedLang === 'diff') {
124129
const innerSpanNode = node.children[0];
125130
const innerSpanTextNode =
126131
innerSpanNode?.type === 'element' && innerSpanNode.children?.[0];

packages/markdown/remark/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type * as mdast from 'mdast';
33
import type { Options as RemarkRehypeOptions } from 'remark-rehype';
44
import type {
55
BuiltinTheme,
6+
HighlighterCoreOptions,
67
LanguageRegistration,
78
ShikiTransformer,
89
ThemeRegistration,
@@ -37,6 +38,7 @@ export type ThemePresets = BuiltinTheme | 'css-variables';
3738

3839
export interface ShikiConfig {
3940
langs?: LanguageRegistration[];
41+
langAlias?: HighlighterCoreOptions['langAlias'];
4042
theme?: ThemePresets | ThemeRegistration | ThemeRegistrationRaw;
4143
themes?: Record<string, ThemePresets | ThemeRegistration | ThemeRegistrationRaw>;
4244
defaultColor?: 'light' | 'dark' | string | false;

packages/markdown/remark/test/shiki.test.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,32 @@ describe('shiki syntax highlighting', () => {
101101
// Doesn't have `color` or `background-color` properties.
102102
assert.doesNotMatch(code, /color:/);
103103
});
104+
105+
it('the highlighter supports lang alias', async () => {
106+
const highlighter = await createShikiHighlighter({
107+
langAlias: {
108+
cjs: 'javascript',
109+
},
110+
});
111+
112+
const html = await highlighter.highlight(`let test = "some string"`, 'cjs', {
113+
attributes: { 'data-foo': 'bar', autofocus: true },
114+
});
115+
116+
assert.match(html, /data-language="cjs"/);
117+
});
118+
119+
it('the markdown processsor support lang alias', async () => {
120+
const processor = await createMarkdownProcessor({
121+
shikiConfig: {
122+
langAlias: {
123+
cjs: 'javascript',
124+
},
125+
},
126+
});
127+
128+
const { code } = await processor.render('```cjs\nlet foo = "bar"\n```');
129+
130+
assert.match(code, /data-language="cjs"/);
131+
});
104132
});

0 commit comments

Comments
 (0)