如何给代码添加语法高亮
使用 Shiki 实现语法高亮,帮助您避免常见陷阱

概述
语法高亮是任何包含代码片段的技术博客的基本功能。研究表明,颜色编码的语法可以将代码理解能力提高多达 20%,使读者更容易理解和参与技术内容。它通过使代码视觉上更吸引人且更易读,清楚地区分关键字、字符串和注释等元素来增强参与度。在重建我的博客时,我花费了大量时间研究如何有效地实现语法高亮。
有几个成熟的解决方案可用,例如 Prism 因其基于正则表达式的效率而闻名,React Syntax Highlighter 因其与 React 的无缝集成而著称。但是,由于文档有限以及混合不同解决方案时的冲突(如样式不一致、依赖项重复或客户端和服务器端渲染之间的不兼容性),实现过程可能具有挑战性。在这里,我记录了我成功的方法以供将来参考。
背景
框架: Next.js
内容: MDX
托管: Cloudflare Pages
可用选项
React Syntax Highlighter:在 React 生态系统中广泛使用。
Prism:基于正则表达式的实现,支持服务器端和客户端使用。
Shiki:通常与 Unified 生态系统一起使用,在服务器端工作。
Rehype Highlight:Rehype 的插件,是 HTML 的 Unified 处理器的一部分。
服务器端还是客户端?
语法高亮解决方案可以分类为:
服务器端: 例如 Rehype Highlight。
客户端: 例如 React Syntax Highlighter。
对于服务器端解决方案,最常见的方法是使用 Unified 生态系统。Unified 是一个用于处理 Markdown 或 HTML 等内容的模块化框架,允许解析器、转换器和编译器的无缝集成。有关更多详细信息,请访问 Unified 文档。
Unified 架构
| ........................ process ........................... |
| .......... parse ... | ... run ... | ... stringify ..........|
+--------+ +----------+
Input ->- | Parser | ->- Syntax Tree ->- | Compiler | ->- Output
+--------+ | +----------+
X
|
+--------------+
| Transformers |
+--------------+无服务器注意事项
在像 Cloudflare Workers 或 Pages 这样的无服务器环境中,适用某些限制。例如,dangerouslySetInnerHTML 被禁止,因为它可能引入跨站脚本(XSS)等安全漏洞。在无服务器环境中,此限制确保了更高的安全性,但也限制了直接注入预编译 HTML 的能力,需要替代方法来渲染语法高亮的内容。
使用 Shiki 时,请注意 Cloudflare Workers 不支持从二进制数据初始化 WebAssembly。相反,您必须将 WebAssembly(WASM)文件作为资产上传并直接导入,如 Shiki 文档中所解释的。
选择的路径
我选择了 React Syntax Highlighter 和 Prism 作为底层高亮器。这个决定与我使用 Next.js 以及由于无服务器限制而需要客户端解决方案相一致。选择 Prism 是因为其速度和兼容性。
使用 React Syntax Highlighter
Prism.js 还是 Highlight.js?
Prism.js 和 Highlight.js 都是流行的语法高亮库,但它们在关键方面有所不同:
Prism.js 更现代、轻量级且可定制。它使用基于正则表达式的解析,支持插件,并需要手动指定语言,使其适用于性能关键的应用程序。
Highlight.js 以其自动语言检测而闻名,使其更适合初学者且在需要语言检测时更易于集成。
在这个项目中,我选择了 Highlight.js,因为它支持自动语言检测,在未指定语言的情况下简化了实现。但是,与 Prism.js 相比,Highlight.js 有一些缺点,例如可定制性较差且包大小更大,这可能会在资源受限的环境中影响性能。
样式选项
使用 style 属性
优点: 避免 CSS 冲突。
缺点: 无法重用主题的样式。
使用 className 属性
优点: 允许重用主题的样式。
缺点: 切换主题时可能出现 CSS 冲突。
管理主题特定的 CSS
当在明暗主题之间切换时,例如通过导入不同的 CSS 文件,CSS 文件会发生冲突,因为以前的文件没有被移除。有一个天才的想法来解决这个问题:
const { resolvedTheme } = useTheme();
React.useEffect(() => {
const styleId = 'prism-theme';
const existingStyle = document.getElementById(styleId);
if (existingStyle) {
existingStyle.remove();
}
const link = document.createElement('link');
link.id = styleId;
link.rel = 'stylesheet';
link.href = resolvedTheme === 'dark' ? '/prism-themes/prism-vsc-dark-plus.css' : '/prism-themes/prism-one-light.css';
document.head.appendChild(link);
return () => {
const style = document.getElementById(styleId);
if (style) {
style.remove();
}
};
}, [resolvedTheme]);这种方法的一个缺点是我们需要在项目中手动管理 CSS 文件:
cp node_modules/prism-themes/themes/prism-vsc-dark-plus.css public/prism-themes/
cp node_modules/prism-themes/themes/prism-one-light.css public/prism-themes/语法高亮示例
CPP
#include <iostream>
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}Python
def hello():
print("Hello, World!")JavaScript
const hello = () => {
console.log('Hello, World!');
};Bash
$ git clone https://github.com/username/repo.gitSQL
SELECT * FROM users;JSON
{
"name": "John Doe",
"age": 30,
"email": "john.doe@example.com"
}YAML
name: John Doe
age: 30
email: john.doe@example.comMarkdown
# Hello, World!
This is a test of Markdown syntax highlighting.HTML
<h1>Hello, World!</h1>CSS
body {
background-color: #f0f0f0;
}结论
语法高亮是创建引人入胜且易于访问的技术内容的关键功能。Prism.js 提供现代功能、轻量级性能和可定制性,使其非常适合对语法渲染进行精确控制。相比之下,Highlight.js 提供自动语言检测和易用性,但对于性能关键的应用程序可能效率较低。理解这些权衡有助于确保做出符合特定需求的明智选择。
虽然由于无服务器约束和兼容性问题,实现过程可能很复杂,但像 React Syntax Highlighter 和 Prism 这样的解决方案使其变得可行。通过仔细考虑您的框架、托管环境和定制需求,您可以选择一个既提供功能又具有视觉吸引力的博客策略。通过这次探索,我希望能够澄清这个过程并授权其他人创建更好的技术博客。
