0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-01-13 22:11:20 -05:00

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 <bjornlu.dev@gmail.com>

* fix build

* Fix bug

* Apply suggestions from code review

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* Update .changeset/dirty-socks-sip.md

---------

Co-authored-by: Bjorn Lu <bjornlu.dev@gmail.com>
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
This commit is contained in:
Emanuele Stoppa 2024-10-09 10:51:38 +01:00 committed by GitHub
parent 3ac2263ff6
commit 710a1a11f4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 78 additions and 4 deletions

View file

@ -0,0 +1,34 @@
---
'@astrojs/markdown-remark': minor
'astro': minor
---
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.
The following example configures Shiki to highlight `cjs` code blocks using the `javascript` syntax highlighter:
```js
import { defineConfig } from 'astro/config';
export default defineConfig({
markdown: {
shikiConfig: {
langAlias: {
cjs: 'javascript',
},
},
},
});
```
Then in your Markdown, you can use the alias as the language for a code block for syntax highlighting:
````md
```cjs
'use strict';
function commonJs() {
return 'I am a commonjs file';
}
```
````

View file

@ -313,6 +313,10 @@ export const AstroConfigSchema = z.object({
return langs; return langs;
}) })
.default([]), .default([]),
langAlias: z
.record(z.string(), z.string())
.optional()
.default(ASTRO_CONFIG_DEFAULTS.markdown.shikiConfig.langAlias!),
theme: z theme: z
.enum(Object.keys(bundledThemes) as [BuiltinTheme, ...BuiltinTheme[]]) .enum(Object.keys(bundledThemes) as [BuiltinTheme, ...BuiltinTheme[]])
.or(z.custom<ShikiTheme>()) .or(z.custom<ShikiTheme>())

View file

@ -37,6 +37,7 @@ export const markdownConfigDefaults: Required<AstroMarkdownOptions> = {
themes: {}, themes: {},
wrap: false, wrap: false,
transformers: [], transformers: [],
langAlias: {},
}, },
remarkPlugins: [], remarkPlugins: [],
rehypePlugins: [], rehypePlugins: [],

View file

@ -45,24 +45,29 @@ export async function createShikiHighlighter({
defaultColor, defaultColor,
wrap = false, wrap = false,
transformers = [], transformers = [],
langAlias = {},
}: ShikiConfig = {}): Promise<ShikiHighlighter> { }: ShikiConfig = {}): Promise<ShikiHighlighter> {
theme = theme === 'css-variables' ? cssVariablesTheme() : theme; theme = theme === 'css-variables' ? cssVariablesTheme() : theme;
const highlighter = await getHighlighter({ const highlighter = await getHighlighter({
langs: ['plaintext', ...langs], langs: ['plaintext', ...langs],
langAlias,
themes: Object.values(themes).length ? Object.values(themes) : [theme], themes: Object.values(themes).length ? Object.values(themes) : [theme],
}); });
return { return {
async highlight(code, lang = 'plaintext', options) { async highlight(code, lang = 'plaintext', options) {
const resolvedLang = langAlias[lang] ?? lang;
const loadedLanguages = highlighter.getLoadedLanguages(); const loadedLanguages = highlighter.getLoadedLanguages();
if (!isSpecialLang(lang) && !loadedLanguages.includes(lang)) { if (!isSpecialLang(lang) && !loadedLanguages.includes(resolvedLang)) {
try { try {
await highlighter.loadLanguage(lang as BundledLanguage); await highlighter.loadLanguage(resolvedLang as BundledLanguage);
} catch (_err) { } catch (_err) {
const langStr =
lang === resolvedLang ? `"${lang}"` : `"${lang}" (aliased to "${resolvedLang}")`;
console.warn( console.warn(
`[Shiki] The language "${lang}" doesn't exist, falling back to "plaintext".`, `[Shiki] The language ${langStr} doesn't exist, falling back to "plaintext".`,
); );
lang = 'plaintext'; lang = 'plaintext';
} }
@ -120,7 +125,7 @@ export async function createShikiHighlighter({
// Add "user-select: none;" for "+"/"-" diff symbols. // Add "user-select: none;" for "+"/"-" diff symbols.
// Transform `<span class="line"><span style="...">+ something</span></span> // Transform `<span class="line"><span style="...">+ something</span></span>
// into `<span class="line"><span style="..."><span style="user-select: none;">+</span> something</span></span>` // into `<span class="line"><span style="..."><span style="user-select: none;">+</span> something</span></span>`
if (lang === 'diff') { if (resolvedLang === 'diff') {
const innerSpanNode = node.children[0]; const innerSpanNode = node.children[0];
const innerSpanTextNode = const innerSpanTextNode =
innerSpanNode?.type === 'element' && innerSpanNode.children?.[0]; innerSpanNode?.type === 'element' && innerSpanNode.children?.[0];

View file

@ -3,6 +3,7 @@ import type * as mdast from 'mdast';
import type { Options as RemarkRehypeOptions } from 'remark-rehype'; import type { Options as RemarkRehypeOptions } from 'remark-rehype';
import type { import type {
BuiltinTheme, BuiltinTheme,
HighlighterCoreOptions,
LanguageRegistration, LanguageRegistration,
ShikiTransformer, ShikiTransformer,
ThemeRegistration, ThemeRegistration,
@ -37,6 +38,7 @@ export type ThemePresets = BuiltinTheme | 'css-variables';
export interface ShikiConfig { export interface ShikiConfig {
langs?: LanguageRegistration[]; langs?: LanguageRegistration[];
langAlias?: HighlighterCoreOptions['langAlias'];
theme?: ThemePresets | ThemeRegistration | ThemeRegistrationRaw; theme?: ThemePresets | ThemeRegistration | ThemeRegistrationRaw;
themes?: Record<string, ThemePresets | ThemeRegistration | ThemeRegistrationRaw>; themes?: Record<string, ThemePresets | ThemeRegistration | ThemeRegistrationRaw>;
defaultColor?: 'light' | 'dark' | string | false; defaultColor?: 'light' | 'dark' | string | false;

View file

@ -101,4 +101,32 @@ describe('shiki syntax highlighting', () => {
// Doesn't have `color` or `background-color` properties. // Doesn't have `color` or `background-color` properties.
assert.doesNotMatch(code, /color:/); assert.doesNotMatch(code, /color:/);
}); });
it('the highlighter supports lang alias', async () => {
const highlighter = await createShikiHighlighter({
langAlias: {
cjs: 'javascript',
},
});
const html = await highlighter.highlight(`let test = "some string"`, 'cjs', {
attributes: { 'data-foo': 'bar', autofocus: true },
});
assert.match(html, /data-language="cjs"/);
});
it('the markdown processsor support lang alias', async () => {
const processor = await createMarkdownProcessor({
shikiConfig: {
langAlias: {
cjs: 'javascript',
},
},
});
const { code } = await processor.render('```cjs\nlet foo = "bar"\n```');
assert.match(code, /data-language="cjs"/);
});
}); });