diff --git a/.changeset/lazy-rehype-shiki-highlighter.md b/.changeset/lazy-rehype-shiki-highlighter.md new file mode 100644 index 0000000000000..42d4e5da2c7d8 --- /dev/null +++ b/.changeset/lazy-rehype-shiki-highlighter.md @@ -0,0 +1,5 @@ +--- +'@node-core/rehype-shiki': patch +--- + +Only import every bundled grammar when the plugin isn't given a highlighter diff --git a/packages/rehype-shiki/src/__tests__/plugin.test.mjs b/packages/rehype-shiki/src/__tests__/plugin.test.mjs index c6e5b6d6f5713..b120ecb86d4f7 100644 --- a/packages/rehype-shiki/src/__tests__/plugin.test.mjs +++ b/packages/rehype-shiki/src/__tests__/plugin.test.mjs @@ -1,6 +1,21 @@ import assert from 'node:assert/strict'; +import { registerHooks } from 'node:module'; import { describe, it, mock } from 'node:test'; +// The modules loaded from here on, to tell whether the default highlighter, +// which imports every grammar Shiki bundles, was loaded +const loaded = new Set(); + +registerHooks({ + load(url, context, nextLoad) { + loaded.add(new URL(url).pathname); + + return nextLoad(url, context); + }, +}); + +const defaultHighlighter = new URL('../index.mjs', import.meta.url).pathname; + // Simplified mocks - only mock what's actually needed mock.module('../index.mjs', { defaultExport: () => ({ highlightToHast: mock.fn(() => ({ children: [] })) }), @@ -23,6 +38,15 @@ describe('rehypeShikiji', async () => { const { default: rehypeShikiji } = await import('../plugin.mjs'); const mockTree = { type: 'root', children: [] }; + // Runs first, as the cases after it load the default highlighter + it('only loads the default highlighter without a highlighter', async () => { + await rehypeShikiji({ highlighter: { highlightToHast: mock.fn() } }); + assert.ok(!loaded.has(defaultHighlighter)); + + await rehypeShikiji(); + assert.ok(loaded.has(defaultHighlighter)); + }); + it('calls visit twice', async () => { mockVisit.mock.resetCalls(); (await rehypeShikiji())(mockTree); diff --git a/packages/rehype-shiki/src/plugin.mjs b/packages/rehype-shiki/src/plugin.mjs index 384ecbfb07b0f..1309d73089c06 100644 --- a/packages/rehype-shiki/src/plugin.mjs +++ b/packages/rehype-shiki/src/plugin.mjs @@ -4,8 +4,6 @@ import classNames from 'classnames'; import { toString } from 'hast-util-to-string'; import { SKIP, visit } from 'unist-util-visit'; -import createHighlighter from '#rs/index.mjs'; - // This is what Remark will use as prefix within a
 className
 // to attribute the current language of the 
 element
 const languagePrefix = 'language-';
@@ -57,8 +55,15 @@ function isCodeBlock(node) {
  * @param {import('#rs/index.mjs').HighlighterOptions & { highlighter: import('#rs/highlighter.mjs').SyntaxHighlighter }} options
  */
 export default async function rehypeShikiji(options) {
-  const highlighter =
-    options?.highlighter ?? (await createHighlighter(options));
+  let highlighter = options?.highlighter;
+
+  // Loaded only when no highlighter is given, as it imports every grammar
+  // Shiki bundles as soon as it's loaded
+  if (!highlighter) {
+    const { default: createHighlighter } = await import('#rs/index.mjs');
+
+    highlighter = await createHighlighter(options);
+  }
 
   return function (tree) {
     visit(tree, 'element', (_, index, parent) => {