From cb585f706c0ece27d91197d51b27e1e651eefc87 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 6 Nov 2025 16:48:19 -0800 Subject: [PATCH] feat(vue): add code highlighting --- src/theme/prism-include-languages.ts | 49 ++++++++++++++++++++++++++ src/theme/prism-languages/prism-vue.ts | 44 +++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 src/theme/prism-include-languages.ts create mode 100644 src/theme/prism-languages/prism-vue.ts diff --git a/src/theme/prism-include-languages.ts b/src/theme/prism-include-languages.ts new file mode 100644 index 0000000000..032dfd597e --- /dev/null +++ b/src/theme/prism-include-languages.ts @@ -0,0 +1,49 @@ +/** + * Prism is used to syntax highlight code blocks in markdown files. + * + * Original source: + * @link https://github.com/facebook/docusaurus/blob/main/packages/docusaurus-theme-classic/src/theme/prism-include-languages.ts + * + * Reason for overriding: + * - Add Vue language support since it's not included in Prism + */ + +import siteConfig from '@generated/docusaurus.config'; +import type * as PrismNamespace from 'prismjs'; +import type { Optional } from 'utility-types'; + +export default function prismIncludeLanguages(PrismObject: typeof PrismNamespace): void { + const { + themeConfig: { prism }, + } = siteConfig; + const { additionalLanguages } = prism as { additionalLanguages: string[] }; + + // Prism components work on the Prism instance on the window, while prism- + // react-renderer uses its own Prism instance. We temporarily mount the + // instance onto window, import components to enhance it, then remove it to + // avoid polluting global namespace. + // You can mutate PrismObject: registering plugins, deleting languages... As + // long as you don't re-assign it + + const PrismBefore = globalThis.Prism; + globalThis.Prism = PrismObject; + + additionalLanguages.forEach((lang) => { + if (lang === 'php') { + // eslint-disable-next-line global-require + require('prismjs/components/prism-markup-templating.js'); + } + // eslint-disable-next-line global-require, import/no-dynamic-require + require(`prismjs/components/prism-${lang}`); + }); + + // CUSTOM CODE + require('../theme/prism-languages/prism-vue'); + // CUSTOM CODE END + + // Clean up and eventually restore former globalThis.Prism object (if any) + delete (globalThis as Optional).Prism; + if (typeof PrismBefore !== 'undefined') { + globalThis.Prism = PrismObject; + } +} diff --git a/src/theme/prism-languages/prism-vue.ts b/src/theme/prism-languages/prism-vue.ts new file mode 100644 index 0000000000..a63b281220 --- /dev/null +++ b/src/theme/prism-languages/prism-vue.ts @@ -0,0 +1,44 @@ +(function (Prism) { + Prism.languages.vue = Prism.languages.extend('markup', { + // Add Vue template interpolation (e.g., {{ expression }}) + interpolation: { + pattern: /\{\{[^}]+\}\}/, + inside: { + punctuation: /\{\{|\}\}/, + expression: { + pattern: /[\s\S]+/, + inside: Prism.languages.javascript, + }, + }, + }, + + // Add Vue-specific attributes (v-bind, @click, :prop) + attribute: { + pattern: /(^|["'\s])(?:v-|:|\@|#)[\w-]+(?:\.[\w-]+)*(?=[^\w-])(?=[^=>]*=)/, + lookbehind: true, + alias: 'keyword', + }, + }); + + Prism.languages.insertBefore('vue', 'comment', { + script: { + pattern: /[\s\S]*?<\/script>/i, + inside: { + rest: Prism.languages.javascript, + }, + alias: 'language-javascript', + }, + style: { + pattern: /[\s\S]*?<\/style>/i, + inside: { + rest: Prism.languages.css, + }, + alias: 'language-css', + }, + }); + + Prism.languages.html = Prism.languages.vue; + Prism.languages.markup = Prism.languages.vue; + + Prism.languages.v = Prism.languages.vue; +})(Prism);