ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Victory 文档站自定义 Prism 主题:为 Docusaurus 代码块实现 diff / diff-ts 语法高亮

Victory 文档站自定义 Prism 主题:为 Docusaurus 代码块实现 diff / diff-ts 语法高亮 数据可视化UI组件【免费下载链接】victoryA collection of composable React components for building interactive data visualizations项目地址https://gitcode.com/gh_mirrors/vi/victory点击查看免费下载Victory 是一套用于构建交互式数据可视化的可组合 React 组件库其官方文档站基于 Docusaurus 构建源码位于本仓库 website 目录。为了让文档中的代码示例能清晰展示新增了什么、删除了什么、改动了哪里文档站在theme目录中定制了一套 Prism 语法高亮方案专门支持diff与diff-ts两种语言标记。本篇文章以 website/src/theme/README.md 为核心结合仓库内完整的源码实现与配置讲解这套自定义高亮机制的原理、接入方式与扩展方法读完你可以掌握如何在 Docusaurus 站点中让diff-ts、diff-js等语言获得语言内语法着色 增删高亮的双重效果。一、为什么需要自定义 Prism 主题配置原文档明确说明了这一自定义目录存在的目的We use this custom prism theme configuration in order to supportdiffanddiff-tsin prismjs syntax highlighting for code blocks.默认情况下Docusaurus 内置的 Prism 支持diff语言但其高亮效果仅限于给/-前缀行做简单的增删标记被修改的代码本体例如 TypeScript 的关键字、字符串、类型注解仍然是纯文本没有做语言层面的语法着色。对于 Victory 这种以 React TypeScript 为主要技术栈、文档中大量出现在现有代码中插入/修改一段 TSX 代码场景的项目来说这种效果既不好看也不利于读者快速定位代码变化点。Victory 文档站的解法是在 Docusaurus 的theme目录中放置自定义 Prism 配置让 Prism 在完成diff解析之后再对每一行代码体按底层语言如 TypeScript重新做一次 tokenize实现diff 语义 语言语法的叠加高亮。这就是diff-ts这类语言别名的含义diff-ts diff 语义 TypeScript 语法。二、自定义目录的整体结构theme目录下的文件分工如下README.md —— 自定义主题的说明文档prism-include-languages.ts —— 语言加载入口注册diff-xxxx别名并挂载 diff 高亮插件prism-diff-highlight.ts —— 核心插件通过 Prism 钩子在 tokenize 后对 diff 行做二次语言解析prism-diff-highlight.css —— 为删除行、插入行、坐标行提供背景色与加粗样式DocItem/index.tsx、DocSidebar/index.tsx、Playground/index.tsx、MDXComponents.ts 等 —— 站点布局、侧边栏、在线 Playground 与 MDX 组件的定制与本文的高亮主题无直接关系可按需查看。其中真正支撑diff/diff-ts高亮的是前四个文件下文逐一深入。三、语言注册入口prism-include-languages.tsDocusaurus 允许通过themeConfig.prism.additionalLanguages声明额外需要加载的 Prism 语言见 docusaurus.config.ts 中的additionalLanguages: [diff, diff-ts]。Victory 文档站并不使用 Docusaurus 默认的加载逻辑而是用自定义的 prism-include-languages.ts 完全接管语言加载核心逻辑如下。3.1 双 Prism 实例问题的处理源码中有一段关键注释// 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.Prism 组件语言定义、插件钩子依赖全局window.Prism而prism-react-rendererDocusaurus 实际用于渲染高亮代码的库维护的是自己独立的 Prism 实例。两者的不一致会导致加载了语言却不起作用。因此该函数先将传入的PrismObject临时挂到globalThis.Prism上globalThis.Prism PrismObject;完成所有语言的注册和插件挂载后再将其删除避免污染全局命名空间delete (globalThis as Optionaltypeof globalThis, Prism).Prism;3.2 为 diff-xxxx 语言创建别名const DIFF_LANGUAGE_REGEX /^diff-([\w-])/i; additionalLanguages.forEach((lang) { const langMatch DIFF_LANGUAGE_REGEX.exec(lang); if (langMatch) { // eslint-disable-next-line global-require if (!PrismObject.languages.diff) { console.error( prism-include-languages:, You need to import diff language first to use diff-xxxx languages, ); } PrismObject.languages[lang] PrismObject.languages.diff; } else { require(prismjs/components/prism-${lang}); } });逻辑分为两支命中diff-([\w-])模式如diff-ts、diff-js、diff-jsx把该名字注册为diff语言的别名即PrismObject.languages[diff-ts] PrismObject.languages.diff这样diff-ts代码块会先按 diff 语法解析出增删行与坐标行未命中该模式如普通语言名diff本身走require(\prismjs/components/prism-${lang}) 加载对应语言组件。同时代码对先有diff再有diff-xxxx做了前置校验如果注册diff-ts时PrismObject.languages.diff尚不存在会输出一条明确的错误提示要求先把diff语言加载进来。这也是 docusaurus.config.ts 中必须同时列出diff与diff-ts的原因——顺序上diff在前确保别名注册时基础语言已就绪。在遍历完additionalLanguages后紧接着调用diffHighlight(PrismObject)注册核心插件再执行清理。完整的入口文件见 prism-include-languages.ts。四、核心插件prism-diff-highlight.ts 的实现原理prism-diff-highlight.ts 是整个方案的核心。它利用 Prism 的after-tokenize钩子在 diff 语法完成分词之后对代码内容做第二次语言级解析。4.1 语言识别与守卫条件const LANGUAGE_REGEX /^diff-([\w-])/i; export function diffHighlight(Prism: PrismLib) { Prism.hooks.add(after-tokenize, function (env: EnvConfig) { let diffLanguage; let diffGrammar; const language env.language; if (language ! diff) { const langMatch LANGUAGE_REGEX.exec(language); if (!langMatch) { return; // not a language specific diff } diffLanguage langMatch[1]; diffGrammar Prism.languages[diffLanguage]; if (!diffGrammar) { console.error( prism-diff-highlight:, You need to add language ${diffLanguage} to use ${language}, ); return; } } else return; ...钩子函数首先读取env.language即代码块的标注语言若是纯diff直接返回——纯 diff 无需二次处理若是diff-xxx形式则从正则中提取底层语言名如ts并取出对应的 Prism 语法定义diffGrammar如果该底层语言尚未加载输出You need to add language ${diffLanguage} to use ${language}的错误日志并跳过。这意味着使用diff-ts前ts语言本身必须已通过additionalLanguages或 Prism 组件加载。4.2 对 token 流的分类重处理拿到diffGrammar后代码遍历env.tokensdiff 解析产生的 token 数组对不同类型的 token 采取不同策略env.tokens.forEach((token) { if (typeof token string) { newTokens.push(...Prism.tokenize(token, diffGrammar)); } else if (token.type unchanged) { newTokens.push( ...Prism.tokenize(tokenStreamToString(token), diffGrammar), ); } else if ([deleted-sign, inserted-sign].includes(token.type)) { // 设置 alias 并保留 prefix对其余内容按底层语言二次 tokenize ... } else if (token.type coord) { newTokens.push(token); } });四种情况的处理分别对应 diff 语法中的四类行纯字符串 token直接对整个字符串按底层语言重新 tokenizeunchanged未改动行将 token 流还原为字符串后再按底层语言 tokenize从而给未改动代码也补上语法着色deleted-sign/inserted-sign删除行 / 插入行这是 diff 高亮的关键——先为其设置alias以区分删除与插入见下节再遍历其内容保留prefix类型的子 token即行首的-或符号不再重复解析对其余内容调用Prism.tokenize按底层语言重新解析最终把-前缀 带语言着色的删除代码组合回 tokentoken.alias [ token.type deleted-sign ? diff-highlight-deleted : diff-highlight-inserted, ]; // diff parser always return deleted and inserted lines with content of type array if (token.content.length 1) { const newTokenContent: Arraystring | Token []; (token.content as Arraystring | Token).forEach((subToken: Token) { if (subToken.type prefix) { newTokenContent.push(subToken); } else { newTokenContent.push( ...Prism.tokenize(tokenStreamToString(subToken), diffGrammar), ); } }); token.content newTokenContent; }注释 diff parser always return deleted and inserted lines with content of type array 说明了为何这里假定内容为数组并逐个子 token 判断而 preserve prefixes and dont parse them again 则解释了为何prefix需要原样保留——否则/-会被误当作底层语言的运算符再次着色破坏 diff 的可读性coord坐标行如 -1,3 1,4 原样保留。最后env.tokens newTokens写回钩子环境prism-react-renderer便会使用这份增强后的 token 流渲染。值得注意的是文件末尾保留了console.log(newTokens)调试输出在浏览器控制台可以观察到完整的二次解析结果方便排查高亮是否符合预期。4.3 工具函数tokenStreamToStringconst tokenStreamToString (tokenStream: TokenStream): string { const result: string[] []; const stack: TokenStream[] [tokenStream]; while (stack.length 0) { const item stack.pop(); if (typeof item string) { result.push(item); } else if (Array.isArray(item)) { for (let i item.length - 1; i 0; i--) { stack.push(item[i]); } } else { stack.push(item.content); } } return result.join(); };该函数用显式栈迭代地将任意嵌套的 token 树字符串、数组、Token对象扁平化为纯文本避免了深层递归用于把未改动行或增删行的代码体还原成字符串后再交给Prism.tokenize。五、配套样式prism-diff-highlight.cssprism-diff-highlight.css 为插件产出的两个自定义 token 别名提供视觉样式code .token.diff-highlight-deleted { background-color: rgba(255, 0, 0, .1); } code .token.diff-highlight-inserted { background-color: rgba(0, 255, 128, .1); } code .token.coord { font-weight: 700; }diff-highlight-deleted删除行代码体使用半透明的红色背景rgba(255, 0, 0, .1)与常见的 diff 工具删除语义一致diff-highlight-inserted插入行代码体使用半透明的绿色背景rgba(0, 255, 128, .1)coord坐标行hunk 头加粗显示font-weight: 700让 ... 行在视觉上与其他行区分。由于alias的取值是diff-highlight-deleted/diff-highlight-inserted渲染出的 DOM class 形如token diff-highlight-deleted正好与上述选择器匹配。背景色采用低透明度是为了在保留语言语法着色的同时不影响前景色可读性。六、站点级接入docusaurus.config.ts 中的配置自定义 Prism 主题需要与站点配置配合关键配置位于 docusaurus.config.tsprism: { theme: prismThemes.github, darkTheme: prismThemes.dracula, additionalLanguages: [diff, diff-ts], },要点如下theme与darkTheme分别指定亮/暗色主题GitHub 与 Dracula。注意该站点在 colorMode 中将disableSwitch设为true、默认 light即站点实际固定使用亮色主题additionalLanguages中diff必须排在diff-ts之前diff会通过prismjs/components/prism-diff正常加载而diff-ts会走prism-include-languages.ts中的别名注册分支且注册前提是PrismObject.languages.diff已存在对应源码中You need to import diff language first的校验逻辑只要配置了additionalLanguagesDocusaurus 就会调用自定义的prismIncludeLanguages导出函数Docusaurus 约定位于src/theme/prism-include-languages.ts从而自动执行注册别名 挂载 diffHighlight 插件 清理全局 Prism的完整流程文档作者无需在 MDX 中做任何额外引入。七、实际使用效果native.mdx 中的 diff 代码块仓库文档 website/docs/introduction/native.mdx 中有一处典型的diff代码块用法——指导读者在 React Native 应用的入口文件中加入LogBox.ignoreLogs来屏蔽 victory-native 的 require cycle 警告diff import { AppRegistry, LogBox } from react-native; import App from ./App; import { name as appName } from ./app.json; LogBox.ignoreLogs([Require cycle: node_modules/victory]); AppRegistry.registerComponent(appName, () App); 渲染效果中带的两行会以绿色背景呈现diff-highlight-inserted未改动的三行保持普通代码样式代码内容本身import、字符串字面量等仍按 JavaScript 语法着色。这是默认纯diff高亮做不到的。如果希望这里得到 TypeScript 级别的二次着色只需把语言标记改为diff-ts并在additionalLanguages中确保ts语言可用即可。diff-ts的语义是以 diff 方式显示改动 以 TypeScript 语法解析代码体。八、扩展如何支持更多的 diff-xxx 语言该方案具备良好的可扩展性。要支持diff-jsx、diff-json等其他语言只需两步在 docusaurus.config.ts 的prism.additionalLanguages数组中追加对应条目如diff-jsx、diff-json并保证其底层语言jsx、json已通过prismjs/components/prism-xxx或additionalLanguages加载在文档中使用diff-jsx之类的代码块标记即可。无需修改 prism-include-languages.ts 与 prism-diff-highlight.ts 的源码——别名注册与二次解析逻辑都是通用的LANGUAGE_REGEX与DIFF_LANGUAGE_REGEX已覆盖diff-后跟任意[\w-]语言名的场景。九、常见问题与排查要点结合源码逻辑使用中可能遇到的问题及排查方向如下现象原因排查方向diff-ts代码块完全没有高亮diff语言未先加载或ts语法未注册确认additionalLanguages中diff排在diff-ts之前确认prismjs/components/prism-typescript可用查看控制台prism-include-languages:前缀的错误日志控制台报You need to add language ts to use diff-ts底层语言缺失在additionalLanguages中加入底层语言或直接使用diff行首的/-被当作代码着色prefix 未被保留检查是否改动过deleted-sign/inserted-sign分支中prefix原样保留的逻辑见 prism-diff-highlight.ts增删行背景色不生效alias 对应的 CSS 缺失确认 prism-diff-highlight.css 被 prism-include-languages.ts 顶部import引入另外prism-diff-highlight.ts 中保留了console.log(newTokens)调试输出渲染时可打开浏览器控制台观察二次 tokenize 的结果这对定位哪些行没有被正确解析非常有帮助。十、总结Victory 文档站在theme目录中实现了一套完整且可复用的 Prism diff 高亮方案prism-include-languages.ts负责语言别名注册与插件挂载并妥善处理了双 Prism 实例问题prism-diff-highlight.ts通过after-tokenize钩子对diff-xxx代码块做diff 语义 底层语言语法的叠加着色prism-diff-highlight.css提供删除/插入/坐标行的视觉样式最终由docusaurus.config.ts中的additionalLanguages: [diff, diff-ts]一键接入。整套实现既有清晰的工程边界也保留了良好的扩展空间——任何diff-language组合都可以按同样的模式接入让代码示例中的改动点一目了然。对 Victory 文档站中涉及在既有代码上增改的教程类内容如 native.mdx 中的示例而言这是一项切实提升阅读与检索体验的基础设施。赞分享数据可视化UI组件【免费下载链接】victoryA collection of composable React components for building interactive data visualizations项目地址https://gitcode.com/gh_mirrors/vi/victory点击查看免费下载相关推荐Prism Diff Highlight 插件实战让 diff 代码块同时获得 Diff 与目标语言双重高亮Prism Diff Highlight 插件实战让 diff 代码块同时获得 Diff 与目标语言双重高亮 Prism 的 Diff Highlight 插前端UI组件Kz Blog代码高亮Prism语法高亮配置与自定义主题终极指南Kz Blog代码高亮Prism语法高亮配置与自定义主题终极指南 Kz Blog基于Docusaurus构建采用Prism作为代码语法高亮解决方案提供出色Zola代码块样式自定义语法高亮的外观主题Zola代码块样式自定义语法高亮的外观主题 在现代技术文档和博客中代码块的可读性至关重要。Zola作为一款强大的静态网站生成器提供了灵活的语法高亮功能让静态站点CLI开发工具上一篇Hyper终端与Bash的完美集成传统shell的现代革命性体验下一篇如何快速掌握5G应用开发从网络测速到低延迟数据传输的全场景项目指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表