diff --git a/apps/site/mdx/plugins.mjs b/apps/site/mdx/plugins.mjs index f6b33cd5416ae..02166643aaf26 100644 --- a/apps/site/mdx/plugins.mjs +++ b/apps/site/mdx/plugins.mjs @@ -15,6 +15,20 @@ import remarkTableTitles from '../util/table'; // Reference: https://github.com/nodejs/nodejs.org/pull/7896#issuecomment-3009480615 const OPEN_NEXT_CLOUDFLARE = 'Cloudflare' in global; +// Shiki is created out here to avoid an async rehype plugin +const singletonShiki = await rehypeShikiji({ + // We use the faster WASM engine on the server instead of the web-optimized version. + // + // Currently we fall back to the JavaScript RegEx engine + // on Cloudflare workers because `shiki/wasm` requires loading via + // `WebAssembly.instantiate` with custom imports, which Cloudflare doesn't support + // for security reasons. + wasm: !OPEN_NEXT_CLOUDFLARE, + + // TODO(@avivkeller): Find a way to enable Twoslash w/ a VFS on Cloudflare + twoslash: !OPEN_NEXT_CLOUDFLARE, +}); + /** * Provides all our Rehype Plugins that are used within MDX */ @@ -25,21 +39,7 @@ export const rehypePlugins = [ [rehypeAutolinkHeadings, { behavior: 'wrap' }], // Transforms sequential code elements into code tabs and // adds our syntax highlighter (Shikiji) to Codeboxes - [ - rehypeShikiji, - { - // We use the faster WASM engine on the server instead of the web-optimized version. - // - // Currently we fall back to the JavaScript RegEx engine - // on Cloudflare workers because `shiki/wasm` requires loading via - // `WebAssembly.instantiate` with custom imports, which Cloudflare doesn't support - // for security reasons. - wasm: !OPEN_NEXT_CLOUDFLARE, - - // TODO(@avivkeller): Find a way to enable Twoslash w/ a VFS on Cloudflare - twoslash: !OPEN_NEXT_CLOUDFLARE, - }, - ], + () => singletonShiki, ]; /** diff --git a/packages/rehype-shiki/package.json b/packages/rehype-shiki/package.json index 2355532fc4e07..a9be231d9e108 100644 --- a/packages/rehype-shiki/package.json +++ b/packages/rehype-shiki/package.json @@ -1,6 +1,6 @@ { "name": "@node-core/rehype-shiki", - "version": "1.2.0", + "version": "1.3.0", "type": "module", "exports": { ".": "./src/index.mjs", diff --git a/packages/rehype-shiki/src/__tests__/plugin.test.mjs b/packages/rehype-shiki/src/__tests__/plugin.test.mjs index 17fe2def9e6a3..c6e5b6d6f5713 100644 --- a/packages/rehype-shiki/src/__tests__/plugin.test.mjs +++ b/packages/rehype-shiki/src/__tests__/plugin.test.mjs @@ -25,7 +25,7 @@ describe('rehypeShikiji', async () => { it('calls visit twice', async () => { mockVisit.mock.resetCalls(); - await rehypeShikiji()(mockTree); + (await rehypeShikiji())(mockTree); assert.strictEqual(mockVisit.mock.calls.length, 2); }); @@ -61,7 +61,7 @@ describe('rehypeShikiji', async () => { } }); - await rehypeShikiji()(mockTree); + (await rehypeShikiji())(mockTree); assert.ok(parent.children.some(child => child.tagName === 'CodeTabs')); }); }); diff --git a/packages/rehype-shiki/src/highlighter.mjs b/packages/rehype-shiki/src/highlighter.mjs index b8159c8501c50..15b57f00fc5e7 100644 --- a/packages/rehype-shiki/src/highlighter.mjs +++ b/packages/rehype-shiki/src/highlighter.mjs @@ -18,12 +18,20 @@ export const getLanguageByName = (language, langs) => { ); }; +/** + * @typedef {Object} SyntaxHighlighter + * @property {import('@shikijs/core').HighlighterCoreSync} shiki - The underlying shiki core instance. + * @property {(code: string, lang: string, meta?: Record) => string} highlightToHtml - Highlights code and returns inner HTML of the tag. + * @property {(code: string, lang: string, meta?: Record) => any} highlightToHast - Highlights code and returns a HAST tree. + */ + /** * Factory function to create a syntax highlighter instance with utility methods. * * @param {Object} params - Parameters for highlighter creation. * @param {import('@shikijs/core').HighlighterCoreOptions} [params.coreOptions] - Core options for the highlighter. * @param {import('@shikijs/core').CodeToHastOptions} [params.highlighterOptions] - Additional options for highlighting. + * @returns {SyntaxHighlighter} */ const createHighlighter = ({ coreOptions = {}, highlighterOptions = {} }) => { const options = { diff --git a/packages/rehype-shiki/src/index.mjs b/packages/rehype-shiki/src/index.mjs index 1a5e1cfcdd131..3b0a951c3d542 100644 --- a/packages/rehype-shiki/src/index.mjs +++ b/packages/rehype-shiki/src/index.mjs @@ -21,8 +21,8 @@ import createHighlighter, { getLanguageByName } from './highlighter.mjs'; * @property {boolean} [wasm=false] - Enable WebAssembly for the regex engine * @property {boolean} [twoslash=false] - Enable twoslash * @property {import('@shikijs/twoslash').TransformerTwoslashIndexOptions} [twoslashOptions] - Twoslash configuration options - * @param {import('@shikijs/core').HighlighterCoreOptions} [coreOptions] - Core options for the highlighter. - * @param {import('@shikijs/core').CodeToHastOptions} [highlighterOptions] - Additional options for highlighting. + * @property {import('@shikijs/core').HighlighterCoreOptions} [coreOptions] - Core options for the highlighter. + * @property {import('@shikijs/core').CodeToHastOptions} [highlighterOptions] - Additional options for highlighting. */ /** diff --git a/packages/rehype-shiki/src/plugin.mjs b/packages/rehype-shiki/src/plugin.mjs index 2bc93a327153b..b0b5a741a83d4 100644 --- a/packages/rehype-shiki/src/plugin.mjs +++ b/packages/rehype-shiki/src/plugin.mjs @@ -54,14 +54,13 @@ function isCodeBlock(node) { } /** - * @param {import('./index.mjs').HighlighterOptions} options + * @param {import('./index.mjs').HighlighterOptions & { highlighter: import('./highlighter.mjs').SyntaxHighlighter }} options */ -export default function rehypeShikiji(options) { - let highlighter; - - return async function (tree) { - highlighter ??= await createHighlighter(options); +export default async function rehypeShikiji(options) { + const highlighter = + options?.highlighter ?? (await createHighlighter(options)); + return function (tree) { visit(tree, 'element', (_, index, parent) => { const languages = []; const displayNames = [];