Repository: shikijs/shiki
Pull request #902 (2025-01-21 13:30:33)

feat(markdown-it): support `markdown-it-async` integration
Integrate with https://github.com/antfu/markdown-it-async

netlify[bot]: ### <span aria-hidden="true">👷</span> Deploy Preview for *shiki-matsu* processing.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 80128106441cebbdef61201045de22c2a3507e76 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/shiki-matsu/deploys/678fa17ca619c20008aa7f79 |
netlify[bot]: ### <span aria-hidden="true">👷</span> Deploy Preview for *shiki-next* processing.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 80128106441cebbdef61201045de22c2a3507e76 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/shiki-next/deploys/678fa17dc510180008a54d1d |
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/shikijs/shiki/pull/902?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs) Report
Attention: Patch coverage is `96.42857%` with `2 lines` in your changes missing coverage. Please review.
> Project coverage is 94.27%. Comparing base [(`efce837`)](https://app.codecov.io/gh/shikijs/shiki/commit/efce837449eabf8407f1cd3d528857b6ee5abf7f?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs) to head [(`8012810`)](https://app.codecov.io/gh/shikijs/shiki/commit/80128106441cebbdef61201045de22c2a3507e76?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs).
> Report is 1 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/shikijs/shiki/pull/902?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs) | Patch % | Lines |
|---|---|---|
| [packages/markdown-it/src/async.ts](https://app.codecov.io/gh/shikijs/shiki/pull/902?src=pr&el=tree&filepath=packages%2Fmarkdown-it%2Fsrc%2Fasync.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs#diff-cGFja2FnZXMvbWFya2Rvd24taXQvc3JjL2FzeW5jLnRz) | 96.36% | [2 Missing :warning: ](https://app.codecov.io/gh/shikijs/shiki/pull/902?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #902      +/-   ##
==========================================
+ Coverage   94.25%   94.27%   +0.01%     
==========================================
  Files          89       91       +2     
  Lines        7415     7471      +56     
  Branches     1528     1536       +8     
==========================================
+ Hits         6989     7043      +54     
- Misses        417      419       +2     
  Partials        9        9              
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/shikijs/shiki/pull/902?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shikijs).


Patch:
diff --git a/docs/guide/regex-engines.md b/docs/guide/regex-engines.md
index 188e4684..3888398c 100644
--- a/docs/guide/regex-engines.md
+++ b/docs/guide/regex-engines.md
@@ -91,7 +91,7 @@ Instead of compiling regular expressions on-the-fly, we also provide pre-compile
 Pre-compiled languages require support for RegExp UnicodeSets (the `v` flag), which requires **ES2024** or Node.js 20+, and may not work in older environments. [Can I use](https://caniuse.com/mdn-javascript_builtins_regexp_unicodesets).
 :::
 
-You can install them with `@shikijs/langs-precompiled`, and then change your `@shikijs/langs` imports to `@shikijs/langs-precompiled`:
+You can install them with `@shikijs/langs-precompiled`, and change your `@shikijs/langs` imports to `@shikijs/langs-precompiled`:
 
 ```ts
 import { createHighlighterCore } from 'shiki/core'
diff --git a/docs/packages/markdown-it.md b/docs/packages/markdown-it.md
index 9b70b14d..6ea9b223 100644
--- a/docs/packages/markdown-it.md
+++ b/docs/packages/markdown-it.md
@@ -50,3 +50,34 @@ const md = MarkdownIt()
 
 md.use(fromHighlighter(highlighter, { /* options */ }))
 ```
+
+## With Shorthands
+
+Shiki's [shorthands](/guide/shorthands) provides on-demand loading of themes and languages, but also makes the highlighting process asynchronous. Unfortunately, `markdown-it` itself [does NOT support async highlighting](https://github.com/markdown-it/markdown-it/blob/master/docs/development.md#i-need-async-rule-how-to-do-it) out of the box.
+
+To workaround this, you can use [`markdown-it-async`](https://github.com/antfu/markdown-it-async) by [Anthony Fu](https://github.com/antfu). Where Shiki also provides an integration with it, you can import `fromAsyncCodeToHtml` from `@shikijs/markdown-it/async`.
+
+````ts twoslash
+import { fromAsyncCodeToHtml } from '@shikijs/markdown-it/async'
+import MarkdownItAsync from 'markdown-it-async'
+import { codeToHtml } from 'shiki' // Or your custom shorthand bundle
+
+// Initialize MarkdownIt instance with markdown-it-async
+const md = MarkdownItAsync()
+
+md.use(
+  fromAsyncCodeToHtml(
+    // Pass the codeToHtml function
+    codeToHtml,
+    {
+      themes: {
+        light: 'vitesse-light',
+        dark: 'vitesse-dark',
+      }
+    }
+  )
+)
+
+// Use `md.renderAsync` instead of `md.render`
+const html = await md.renderAsync('# Title\n```ts\nconsole.log("Hello, World!")\n```')
+````
diff --git a/docs/references/engine-js-compat.md b/docs/references/engine-js-compat.md
index af895568..49887c9d 100644
--- a/docs/references/engine-js-compat.md
+++ b/docs/references/engine-js-compat.md
@@ -2,9 +2,9 @@
 
 Compatibility reference of all built-in grammars with the [JavaScript RegExp engine](/guide/regex-engines#javascript-regexp-engine).
 
-> Generated on Monday, January 20, 2025
+> Generated on Tuesday, January 21, 2025
 >
-> Version `1.27.2`
+> Version `2.0.3`
 >
 > Runtime: Node.js v22.11.0
 
@@ -13,9 +13,9 @@ Compatibility reference of all built-in grammars with the [JavaScript RegExp eng
 |                 |                       Count |
 | :-------------- | --------------------------: |
 | Total Languages |                         219 |
-| Supported       | [213](#supported-languages) |
+| Supported       | [214](#supported-languages) |
 | Mismatched      |  [0](#mismatched-languages) |
-| Unsupported     | [6](#unsupported-languages) |
+| Unsupported     | [5](#unsupported-languages) |
 
 ## Supported Languages
 
@@ -54,7 +54,7 @@ In some edge cases, it's not guaranteed that the highlighting will be 100% the s
 | clj                | ✅ OK           |                38 |               - |      |
 | clojure            | ✅ OK           |                38 |               - |      |
 | cmake              | ✅ OK           |                23 |               - |      |
-| cobol              | ✅ OK           |               867 |               - |      |
+| cobol              | ✅ OK           |               868 |               - |      |
 | codeowners         | ✅ OK           |                 4 |               - |      |
 | coffee             | ✅ OK           |               471 |               - |      |
 | common-lisp        | ✅ OK           |                60 |               - |      |
@@ -183,7 +183,8 @@ In some edge cases, it's not guaranteed that the highlighting will be 100% the s
 | ruby               | ✅ OK           |              1787 |               - |      |
 | rust               | ✅ OK           |                89 |               - |      |
 | sas                | ✅ OK           |               101 |               - |      |
-| scala              | ✅ OK           |               117 |               - |      |
+| sass               | ✅ OK           |                69 |               - |      |
+| scala              | ✅ OK           |               118 |               - |      |
 | scheme             | ✅ OK           |                34 |               - |      |
 | scss               | ✅ OK           |               234 |               - |      |
 | sdbl               | ✅ OK           |                23 |               - |      |
@@ -259,8 +260,7 @@ Languages that throw with the JavaScript RegExp engine, either because they cont
 | Language   | Highlight Match | Patterns Parsable | Patterns Failed | Diff |
 | ---------- | :-------------- | ----------------: | --------------: | ---: |
 | codeql     | ✅ OK           |               150 |               1 |      |
-| sass       | ✅ OK           |                67 |               2 |      |
+| csharp     | ❌ Error        |               312 |               1 |  137 |
 | purescript | ❌ Error        |                72 |               1 |      |
-| csharp     | ❌ Error        |               310 |               3 |  137 |
-| razor      | ❌ Error        |               959 |               3 |      |
-| swift      | ❌ Error        |               326 |               3 |      |
+| razor      | ❌ Error        |               961 |               1 |      |
+| swift      | ❌ Error        |               330 |               1 |      |
diff --git a/packages/markdown-it/build.config.ts b/packages/markdown-it/build.config.ts
index e9c3a90f..8e4ccf49 100644
--- a/packages/markdown-it/build.config.ts
+++ b/packages/markdown-it/build.config.ts
@@ -4,6 +4,7 @@ export default defineBuildConfig({
   entries: [
     'src/index.ts',
     'src/core.ts',
+    'src/async.ts',
   ],
   declaration: true,
   rollup: {
diff --git a/packages/markdown-it/package.json b/packages/markdown-it/package.json
index 842da5a4..60c72b19 100644
--- a/packages/markdown-it/package.json
+++ b/packages/markdown-it/package.json
@@ -25,6 +25,10 @@
     "./core": {
       "types": "./dist/core.d.mts",
       "default": "./dist/core.mjs"
+    },
+    "./async": {
+      "types": "./dist/async.d.mts",
+      "default": "./dist/async.mjs"
     }
   },
   "main": "./dist/index.mjs",
@@ -35,6 +39,9 @@
       "core": [
         "./dist/core.d.mts"
       ],
+      "async": [
+        "./dist/async.d.mts"
+      ],
       "*": [
         "./dist/*",
         "./*"
@@ -49,12 +56,21 @@
     "dev": "unbuild --stub",
     "prepublishOnly": "nr build"
   },
+  "peerDependencies": {
+    "markdown-it-async": "catalog:"
+  },
+  "peerDependenciesMeta": {
+    "markdown-it-async": {
+      "optional": true
+    }
+  },
   "dependencies": {
     "markdown-it": "catalog:",
     "shiki": "workspace:*"
   },
   "devDependencies": {
     "@shikijs/transformers": "workspace:*",
-    "@types/markdown-it": "catalog:"
+    "@types/markdown-it": "catalog:",
+    "markdown-it-async": "catalog:"
   }
 }
diff --git a/packages/markdown-it/src/async.ts b/packages/markdown-it/src/async.ts
new file mode 100644
index 00000000..ebc26825
--- /dev/null
+++ b/packages/markdown-it/src/async.ts
@@ -0,0 +1,72 @@
+import type { MarkdownItAsync } from 'markdown-it-async'
+import type { CodeToHastOptions, ShikiTransformer } from 'shiki'
+import type { MarkdownItShikiSetupOptions } from './core'
+
+export type { MarkdownItShikiExtraOptions, MarkdownItShikiSetupOptions } from './common'
+
+export function setupMarkdownWithCodeToHtml(
+  markdownit: MarkdownItAsync,
+  codeToHtml: (code: string, options: CodeToHastOptions<any, any>) => Promise<string>,
+  options: MarkdownItShikiSetupOptions,
+): void {
+  const {
+    parseMetaString,
+    trimEndingNewline = true,
+    defaultLanguage = 'text',
+  } = options
+
+  markdownit.options.highlight = async (code, lang = 'text', attrs) => {
+    if (lang === '') {
+      lang = defaultLanguage as string
+    }
+    const meta = parseMetaString?.(attrs, code, lang) || {}
+    const codeOptions: CodeToHastOptions = {
+      ...options,
+      lang,
+      meta: {
+        ...options.meta,
+        ...meta,
+        __raw: attrs,
+      },
+    }
+
+    const builtInTransformer: ShikiTransformer[] = []
+
+    builtInTransformer.push({
+      name: '@shikijs/markdown-it:block-class',
+      code(node) {
+        node.properties.class = `language-${lang}`
+      },
+    })
+
+    if (trimEndingNewline) {
+      if (code.endsWith('\n'))
+        code = code.slice(0, -1)
+    }
+
+    return await codeToHtml(
+      code,
+      {
+        ...codeOptions,
+        transformers: [
+          ...builtInTransformer,
+          ...codeOptions.transformers || [],
+        ],
+      },
+    )
+  }
+}
+
+/**
+ * Create a markdown-it-async plugin from a codeToHtml function.
+ *
+ * This plugin requires to be installed against a markdown-it-async instance.
+ */
+export function fromAsyncCodeToHtml(
+  codeToHtml: (code: string, options: CodeToHastOptions<any, any>) => Promise<string>,
+  options: MarkdownItShikiSetupOptions,
+) {
+  return async function (markdownit: MarkdownItAsync) {
+    return setupMarkdownWithCodeToHtml(markdownit, codeToHtml, options)
+  }
+}
diff --git a/packages/markdown-it/src/common.ts b/packages/markdown-it/src/common.ts
new file mode 100644
index 00000000..ff6f6008
--- /dev/null
+++ b/packages/markdown-it/src/common.ts
@@ -0,0 +1,48 @@
+import type {
+  BuiltinLanguage,
+  BuiltinTheme,
+  CodeOptionsMeta,
+  CodeOptionsThemes,
+  LanguageInput,
+  TransformerOptions,
+} from 'shiki'
+
+export interface MarkdownItShikiExtraOptions {
+  /**
+   * Custom meta string parser
+   * Return an object to merge with `meta`
+   */
+  parseMetaString?: (
+    metaString: string,
+    code: string,
+    lang: string,
+  ) => Record<string, any> | undefined | null
+
+  /**
+   * markdown-it's highlight function will add a trailing newline to the code.
+   *
+   * This integration removes the trailing newline to the code by default,
+   * you can turn this off by passing false.
+   *
+   * @default true
+   */
+  trimEndingNewline?: boolean
+
+  /**
+   * When lang of code block is empty string, it will work.
+   *
+   * @default 'text'
+   */
+  defaultLanguage?: LanguageInput | BuiltinLanguage
+
+  /**
+   * When lang of code block is not included in langs of options, it will be as a fallback lang.
+   */
+  fallbackLanguage?: LanguageInput | BuiltinLanguage
+}
+
+export type MarkdownItShikiSetupOptions =
+  & CodeOptionsThemes<BuiltinTheme>
+  & TransformerOptions
+  & CodeOptionsMeta
+  & MarkdownItShikiExtraOptions
diff --git a/packages/markdown-it/src/core.ts b/packages/markdown-it/src/core.ts
index ced8cca9..f8311bee 100644
--- a/packages/markdown-it/src/core.ts
+++ b/packages/markdown-it/src/core.ts
@@ -1,55 +1,12 @@
 import type MarkdownIt from 'markdown-it'
 import type {
-  BuiltinLanguage,
-  BuiltinTheme,
-  CodeOptionsMeta,
-  CodeOptionsThemes,
   CodeToHastOptions,
   HighlighterGeneric,
-  LanguageInput,
   ShikiTransformer,
-  TransformerOptions,
 } from 'shiki'
+import type { MarkdownItShikiSetupOptions } from './common'
 
-export interface MarkdownItShikiExtraOptions {
-  /**
-   * Custom meta string parser
-   * Return an object to merge with `meta`
-   */
-  parseMetaString?: (
-    metaString: string,
-    code: string,
-    lang: string,
-  ) => Record<string, any> | undefined | null
-
-  /**
-   * markdown-it's highlight function will add a trailing newline to the code.
-   *
-   * This integration removes the trailing newline to the code by default,
-   * you can turn this off by passing false.
-   *
-   * @default true
-   */
-  trimEndingNewline?: boolean
-
-  /**
-   * When lang of code block is empty string, it will work.
-   *
-   * @default 'text'
-   */
-  defaultLanguage?: LanguageInput | BuiltinLanguage
-
-  /**
-   * When lang of code block is not included in langs of options, it will be as a fallback lang.
-   */
-  fallbackLanguage?: LanguageInput | BuiltinLanguage
-}
-
-export type MarkdownItShikiSetupOptions =
-  & CodeOptionsThemes<BuiltinTheme>
-  & TransformerOptions
-  & CodeOptionsMeta
-  & MarkdownItShikiExtraOptions
+export type { MarkdownItShikiExtraOptions, MarkdownItShikiSetupOptions } from './common'
 
 export function setupMarkdownIt(
   markdownit: MarkdownIt,
diff --git a/packages/markdown-it/src/index.ts b/packages/markdown-it/src/index.ts
index fc1dcdcd..7dff40e1 100644
--- a/packages/markdown-it/src/index.ts
+++ b/packages/markdown-it/src/index.ts
@@ -1,6 +1,6 @@
 import type MarkdownIt from 'markdown-it'
 import type { BuiltinLanguage, BuiltinTheme, LanguageInput } from 'shiki'
-import type { MarkdownItShikiSetupOptions } from './core'
+import type { MarkdownItShikiSetupOptions } from './common'
 import { bundledLanguages, createHighlighter } from 'shiki'
 import { setupMarkdownIt } from './core'
 
@@ -19,6 +19,7 @@ export default async function markdownItShiki(options: MarkdownItShikiOptions) {
   const themeNames = ('themes' in options
     ? Object.values(options.themes)
     : [options.theme]).filter(Boolean) as BuiltinTheme[]
+
   const highlighter = await createHighlighter({
     themes: themeNames,
     langs: options.langs || Object.keys(bundledLanguages) as BuiltinLanguage[],
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index afcec3c5..ad8ae82f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -126,6 +126,9 @@ catalogs:
     markdown-it:
       specifier: ^14.1.0
       version: 14.1.0
+    markdown-it-async:
+      specifier: ^1.1.1
+      version: 1.1.1
     mdast-util-from-markdown:
       specifier: ^2.0.2
       version: 2.0.2
@@ -148,8 +151,8 @@ catalogs:
       specifier: ^1.1.4
       version: 1.1.4
     oniguruma-to-es:
-      specifier: ^2.2.0
-      version: 2.2.0
+      specifier: ^2.3.0
+      version: 2.3.0
     picocolors:
       specifier: ^1.1.1
       version: 1.1.1
@@ -196,8 +199,8 @@ catalogs:
       specifier: ^18.2.0
       version: 18.2.0
     tm-grammars:
-      specifier: ^1.22.8
-      version: 1.22.8
+      specifier: ^1.22.9
+      version: 1.22.9
     tm-themes:
       specifier: ^1.9.8
       version: 1.9.8
@@ -578,7 +581,7 @@ importers:
         version: 10.0.1
       oniguruma-to-es:
         specifier: 'catalog:'
-        version: 2.2.0
+        version: 2.3.0
 
   packages/engine-oniguruma:
     dependencies:
@@ -601,7 +604,7 @@ importers:
     devDependencies:
       tm-grammars:
         specifier: 'catalog:'
-        version: 1.22.8
+        version: 1.22.9
 
   packages/langs-precompiled:
     dependencies:
@@ -610,11 +613,11 @@ importers:
         version: link:../types
       oniguruma-to-es:
         specifier: 'catalog:'
-        version: 2.2.0
+        version: 2.3.0
     devDependencies:
       tm-grammars:
         specifier: 'catalog:'
-        version: 1.22.8
+        version: 1.22.9
 
   packages/markdown-it:
     dependencies:
@@ -631,6 +634,9 @@ importers:
       '@types/markdown-it':
         specifier: 'catalog:'
         version: 14.1.2
+      markdown-it-async:
+        specifier: 'catalog:'
+        version: 1.1.1
 
   packages/monaco:
     dependencies:
@@ -726,7 +732,7 @@ importers:
         version: 3.5.0
       tm-grammars:
         specifier: 'catalog:'
-        version: 1.22.8
+        version: 1.22.9
       tm-themes:
         specifier: 'catalog:'
         version: 1.9.8
@@ -3728,6 +3734,9 @@ packages:
   mark.js@8.11.1:
     resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==}
 
+  markdown-it-async@1.1.1:
+    resolution: {integrity: sha512-wTGXuxaDbZdKOg7cAJSWQcp/zyY/eUOD7Gbgp2hVWp7xv4GLmlK6kghsjKY4eKoiFFTfKa3nsyDvg1lS5zDAkA==}
+
   markdown-it@14.1.0:
     resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
     hasBin: true
@@ -4116,8 +4125,8 @@ packages:
     resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
     engines: {node: '>=18'}
 
-  oniguruma-to-es@2.2.0:
-    resolution: {integrity: sha512-EEsso27ri0sf+t4uRFEj5C5gvXQj0d0w1Y2qq06b+hDLBnvzO1rWTwEW4C7ytan6nhg4WPwE26eLoiPhHUbvKg==}
+  oniguruma-to-es@2.3.0:
+    resolution: {integrity: sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g==}
 
   optionator@0.9.4:
     resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
@@ -4866,8 +4875,8 @@ packages:
     resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==}
     engines: {node: '>=14.0.0'}
 
-  tm-grammars@1.22.8:
-    resolution: {integrity: sha512-htD1bhek5kDiwkjZcNxBTQea5I9zmXR7cWcqZ7jzMGlR4Wdgrba4RDFRIhf3pfpUiKjFOOeyqtd0W2CcWSG6cg==}
+  tm-grammars@1.22.9:
+    resolution: {integrity: sha512-PhznAxT+NBe22Gvwozs9KTWLYFlXFeOVmEpFYbcOgJQYJ1XNGZwDjsrPpIMjta+FRmCJNKLyJhzRZ9Ap6Ewchw==}
 
   tm-themes@1.9.8:
     resolution: {integrity: sha512-XbsuHkBCRBAt/MYXYFDHLfBH70SHa55TwJ50Qsw5UE6w9s12RDNNFyA3wkljkJw/LTG1F672QaO3BOMcIGBfJQ==}
@@ -8470,6 +8479,11 @@ snapshots:
 
   mark.js@8.11.1: {}
 
+  markdown-it-async@1.1.1:
+    dependencies:
+      '@types/markdown-it': 14.1.2
+      markdown-it: 14.1.0
+
   markdown-it@14.1.0:
     dependencies:
       argparse: 2.0.1
@@ -9140,7 +9154,7 @@ snapshots:
     dependencies:
       mimic-function: 5.0.1
 
-  oniguruma-to-es@2.2.0:
+  oniguruma-to-es@2.3.0:
     dependencies:
       emoji-regex-xs: 1.0.0
       regex: 5.1.1
@@ -9892,7 +9906,7 @@ snapshots:
 
   tinyspy@3.0.2: {}
 
-  tm-grammars@1.22.8: {}
+  tm-grammars@1.22.9: {}
 
   tm-themes@1.9.8: {}
 
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 3e2ebaad..2e2d4402 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -30,6 +30,7 @@ catalog:
   '@unocss/reset': ^65.4.2
   '@vitest/coverage-v8': ^3.0.2
   '@vueuse/core': ^12.4.0
+  markdown-it-async: ^1.1.1
   ansi-sequence-parser: ^1.1.1
   bumpp: ^9.10.1
   cac: ^6.7.14
@@ -56,7 +57,7 @@ catalog:
   monaco-editor-core: ^0.52.2
   ofetch: ^1.4.1
   ohash: ^1.1.4
-  oniguruma-to-es: ^2.2.0
+  oniguruma-to-es: ^2.3.0
   picocolors: ^1.1.1
   pinia: ^2.3.0
   pnpm: ^9.15.4
@@ -73,7 +74,7 @@ catalog:
   shiki-legacy: npm:shiki@^0.14.7
   simple-git-hooks: ^2.11.1
   taze: ^18.2.0
-  tm-grammars: ^1.22.8
+  tm-grammars: ^1.22.9
   tm-themes: ^1.9.8
   twoslash: ^0.2.12
   twoslash-vue: ^0.2.12


Test patch:
diff --git a/packages/markdown-it/test/async.test.ts b/packages/markdown-it/test/async.test.ts
new file mode 100644
index 00000000..7eff583b
--- /dev/null
+++ b/packages/markdown-it/test/async.test.ts
@@ -0,0 +1,19 @@
+import fs from 'node:fs/promises'
+import MarkdownItAsync from 'markdown-it-async'
+import { codeToHtml } from 'shiki'
+import { expect, it } from 'vitest'
+import { fromAsyncCodeToHtml } from '../src/async'
+
+it('async', { timeout: 10_000 }, async () => {
+  const md = MarkdownItAsync()
+  md.use(fromAsyncCodeToHtml(codeToHtml, {
+    themes: {
+      light: 'vitesse-light',
+      dark: 'vitesse-dark',
+    },
+  }))
+
+  const result = await md.renderAsync(await fs.readFile(new URL('./fixtures/a.md', import.meta.url), 'utf-8'))
+
+  await expect(result).toMatchFileSnapshot('./fixtures/a.async.out.html')
+})
diff --git a/packages/markdown-it/test/fixtures/a.async.out.html b/packages/markdown-it/test/fixtures/a.async.out.html
new file mode 100644
index 00000000..aef3e7c8
--- /dev/null
+++ b/packages/markdown-it/test/fixtures/a.async.out.html
@@ -0,0 +1,6 @@
+<h1>Hello</h1>
+<p>…world!</p>
+<pre class="shiki shiki-themes vitesse-light vitesse-dark" style="background-color:#ffffff;--shiki-dark-bg:#121212;color:#393a34;--shiki-dark:#dbd7caee" tabindex="0"><code class="language-js"><span class="line"><span style="color:#B07D48;--shiki-dark:#BD976A">console</span><span style="color:#999999;--shiki-dark:#666666">.</span><span style="color:#59873A;--shiki-dark:#80A665">log</span><span style="color:#999999;--shiki-dark:#666666">(</span><span style="color:#B5695977;--shiki-dark:#C98A7D77">'</span><span style="color:#B56959;--shiki-dark:#C98A7D">it works!</span><span style="color:#B5695977;--shiki-dark:#C98A7D77">'</span><span style="color:#999999;--shiki-dark:#666666">)</span></span>
+<span class="line"></span>
+<span class="line"><span style="color:#AB5959;--shiki-dark:#CB7676">const</span><span style="color:#B07D48;--shiki-dark:#BD976A"> a</span><span style="color:#999999;--shiki-dark:#666666"> =</span><span style="color:#2F798A;--shiki-dark:#4C9A91"> 1</span></span>
+<span class="line"><span style="color:#B07D48;--shiki-dark:#BD976A">console</span><span style="color:#999999;--shiki-dark:#666666">.</span><span style="color:#59873A;--shiki-dark:#80A665">log</span><span style="color:#999999;--shiki-dark:#666666">(</span><span style="color:#B07D48;--shiki-dark:#BD976A">a</span><span style="color:#999999;--shiki-dark:#666666">)</span></span></code></pre>