尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

网页代码高亮方案对比:Google Code Prettify、highlight.js与Prism实战解析

网页代码高亮方案对比:Google Code Prettify、highlight.js与Prism实战解析 简介面向网页开发者的Prettify代码高亮资源包基于原生HTML/CSS/JavaScript即可集成无需依赖框架适合在个人博客、技术文档或CMS页面中展示源代码。压缩包共3个文件包含两个JavaScript脚本与一份CSS样式表prettify.css负责定义关键字、字符串、注释等语法元素的配色与字体prettify.js提供语法解析与高亮逻辑支持行号显示与内联错误提示run_prettify.min.js是压缩优化版加载更快三个文件配合即可自动完成多语言代码块的格式化与着色。整包仅14KB几乎不增加页面负担。目前已有841人学习下载适用于需要快速为网页增添专业代码展示能力的前端工程师、博主及技术文档维护者。引入样式表与脚本后只需给代码块添加prettyprint类名即可让HTML、CSS、JavaScript、Python、Java、C等常见语言呈现清晰的高亮效果显著提升技术内容的阅读体验。 一直有朋友问我网页上贴代码到底用什么方案稳妥。这几年 highlight.js、Prism 满天飞但我电脑里还压着一套老古董——Google Code Prettify就是那个解压出来只有 prettify.css、prettify.js、run_prettify.min.js 三个文件的代码高亮库。别笑这套东西虽然十几年没更新了但在静态博客、README、内部 Wiki 这类场景里它依然是我敢拍胸脯说最省事的选择之一。这篇文章我就把这套东西彻底拆开把三个文件各自干什么讲透再聊聊它和你在 VSCode、Cursor 里看到的代码高亮到底是不是一回事。1. 为什么一个十几年没维护的方案还能打prettify 的定位与适用场景1.1 它是 Google Code 时代的产物但设计思路不过时很多年轻前端没听说过 prettify因为它火的时候还是 Google Code 托管平台的时代。它的全名叫 Google Code Prettify设计目标非常朴素写一个pre classprettyprint页面加载后代码就自动有颜色了。不需要配置构建工具不需要包管理器甚至不需要你告诉它代码是 JavaScript 还是 Python它会自己嗅探语言类型。这套零配置的设计放到今天依然很有吸引力。你的博客如果是用纯 HTML 或简单模板写的不想为了贴代码专门引入一套 Node 构建链那 prettify 就是那种下载下来、三个文件放进 js 和 css 目录、引用两行就能开干的东西。1.2 它适合什么场景又不适合什么场景以我实际使用的经验prettify 最适合三类地方静态 HTML 页面比如公司内部文档、个人知识库、纯静态的教程页面Markdown 渲染器的自定义高亮层把渲染出来的pre统一加上 prettyprint 类再调一次prettyPrint()不需要复杂交互的代码展示比如 README 里嵌入示例、论坛帖子里贴代码段不适合的场景也很明显如果你的站点是现代前端框架Vue、React单页应用大量数据通过接口动态渲染那 prettify 这种页面加载时一次性扫描 DOM的模型就很别扭你得手动管理每次更新的高亮时机。这种情况我更推荐 Prism它的 API 设计和模块化更贴合组件化开发。1.3 它到底解决了什么问题本质上prettify 解决的是浏览器不认识代码语法这个问题。浏览器只认识 HTML、CSS、JavaScript它不会因为你写了个pre就自动把里面的function染成蓝色。prettify 做的事就是读取代码文本按词法规则拆成 token关键字、字符串、注释、数字、标识符然后往每个 token 外包一层span classstr、span classkwd之类的标签最后由 CSS 决定这些 class 是什么颜色。所以理解 prettify 一定要抓住这条主线prettify.css 管颜色prettify.js 管分词run_prettify.min.js 管自动跑起来。搞清楚了这一点后面所有问题都能顺着推导出来。2. 拆开压缩包三个文件各自的职责一个都不能少2.1 prettify.css主题皮肤与行号样式都靠它打开 prettify.css 你会发现它不像常规 CSS 那样是给页面做排版的而是定义了一套语法 token 的颜色字典。.str { color: #080; } /* 字符串 */ .kwd { color: #008; } /* 关键字 */ .com { color: #800; } /* 注释 */ .typ { color: #606; } /* 类型名 */ .lit { color: #066; } /* 字面量 */ .pun { color: #660; } /* 标点 */ .pln { color: #000; } /* 纯文本 */这套 class 命名是 prettify 的固定约定没有参数可以改。你要是想换主题就是换一套 CSS 把这些 class 重新映射成别的颜色。官方设计里还有一个隐藏细节prettify.css 末尾包含了.linenums相关的样式就是给ol.linenums设置列表样式、给每行代码做底色的那部分。很多人只引自定义代码高亮主题忘了把 linenums 样式带上结果行号出来了但对不齐。这点后面避坑章节再细说。2.2 prettify.js真正干活的词法分析引擎prettify.js 是核心文件它内部实现了一个不依赖任何框架的语法分析器。它并没有维护一份所有语言的关键字大全而是采用了一种聪明的策略先识别注释、字符串、正则字面量这些结构性的 token再把剩下的文本按语言特征分成代码和普通文本。不同语言通过注册 handler 来增强识别能力比如lang-css.js、lang-sql.js就是额外的语言扩展。这个文件暴露了很关键的全局 API// 手动触发高亮不传参数默认处理整个页面 window.prettyPrint(); // 更底层的调用只处理指定容器 window.PR.prettyPrint(undefined, document.getElementById(codeArea));其中PR对象是 prettify 的命名空间prettyPrint是它暴露的便捷函数。如果你只用prettify.js而不引入 run 版本那页面不会自动高亮你必须自己在合适的时机调用prettyPrint()。这给了开发者绝对的控制权但也带来一个新手经常踩的坑——以为引了 prettify.js 就有高亮效果了结果页面出来全是黑白代码。2.3 run_prettify.min.js把找到代码块→调用高亮自动化run_prettify.min.js 是三者里最容易让人困惑的文件。它看起来像另一个脚本其实它是prettify.js 的压缩核心 自动启动代码 扩展加载器打出来的一个自执行包。你只引入它不引入 prettify.js页面照样能高亮因为核心已经揉在里面了。它做的事情可以拆成三步在页面load事件触发后扫描所有带classprettyprint的pre或code元素根据代码块内容自动猜测语言或读取你标注的lang-xxclass 确定语言规则调用核心的prettyPrint()把这些元素替换成带高亮 span 的新 HTML所以正常接入时你只需要两个引用link relstylesheet hrefprettify.css script srcrun_prettify.min.js/scriptprettify.js 只有在你想手动控制高亮时机的时候才需要单独引出来。3. 实践三种接入 prettify 的方式与配置细节3.1 最省事的自动模式两个引用搞定拿一个普通 HTML 页面举例完整接入代码是这样!DOCTYPE html html head meta charsetutf-8 link relstylesheet hrefprettify.css /head body pre classprettyprint linenums code function hello(name) { // 这是一个注释 return Hello, name; } /code /pre script srcrun_prettify.min.js/script /body /html注意两个细节第一run_prettify.min.js放在body末尾避免脚本阻塞渲染第二pre里的、、这些字符如果代码里有必须写成lt;、gt;、amp;否则浏览器会当成标签解析整个代码块显示都会乱套。我见过不少人栽在这里以为是 prettify 的 bug其实 HTML 转义问题。3.2 手动控制适合动态页面和单页应用如果你的代码块是后渲染的比如fetch回来一段代码再插入到页面里那自动模式就失效了。这时候要改用 prettify.jsscript srcprettify.js/script然后在数据插入模版之后手动调一次const codeContainer document.getElementById(codeBox); // 先把代码文本填进去 codeContainer.textContent fetchedCode; codeContainer.className prettyprint lang-js; // 再手动高亮 prettyPrint(undefined, codeContainer);这里prettyPrint的第二个参数传入容器元素就能只处理这一个节点避免全页面重新扫描。如果你用 Vue 或 React可以在这个逻辑外面包一层比如 Vue 的自定义指令或者 React 的useEffect效果是一样的。3.3 语言标注、行号和皮肤参数prettify 对语言的识别有两种方式。一种是自动嗅探它通过代码特征猜命中率在常见语言里还算体面另一种是你主动声明在 prettyprint 后面加lang-语言名pre classprettyprint lang-python ... /pre需要语言扩展的时候比如要识别 SQL 的特定关键字通过 run 版本 script 标签的参数加载script srcrun_prettify.min.js?langsqllangcss/script皮肤也是通过参数指定script srcrun_prettify.min.js?skinsunburst/script脚本会解析当前 script 标签的 src动态加载对应的语言扩展 JS 和皮肤 CSS。这个设计在当时很讨巧但现在看它有个反直觉的地方语言和皮肤的触发依赖 script 标签的 url如果你用 webpack 打包合并脚本这个参数机制就会失效所以新时代项目里大家更倾向于手动引扩展文件。4. run_prettify.min.js 的自动化逻辑以及它和手写加载的差别4.1 它凭什么能做到引一个文件就自动高亮如果你好奇 run_prettify.min.js 内部到底怎么自动化的我直接说源码层面的逻辑。它实际是打包了核心代码后又在末尾追加了一段自执行逻辑监听window.load事件为什么不用DOMContentLoaded我估计是早期浏览器兼容性考虑window.load能确保所有脚本、样式、资源都就位此时扫描 DOM 最稳妥遍历document.querySelectorAll(pre.prettyprint, code.prettyprint)对每个元素读取它的className中是否有lang-xx标记调用核心prettyPrint()处理完成后把结果写回 DOM所以如果你在页面里又单独引了 prettify.js再引 run_prettify.min.js其实没任何冲突——run 版本内部已经有核心了你引的 prettify.js 会重复挂载 API 而已实际以它的为准。但完全没有必要两个都引。4.2 一个容易忽略的机制script 标签 url 参数会被解析run_prettify.min.js 不是简单地把 prettify.js 压缩了事它里面有一段读取自己的src字符串的逻辑。正则取出skin和lang参数后有skin就动态插入一个link relstylesheet指向对应主题 CSS有lang就动态插入script加载对应的语言扩展文件这解释了为什么很多教程里写直接用 CDN 的 run_prettify.min.js 一行接入。你想让 Python 代码高亮正确就得在 script src 后面跟参数script srchttps://cdn.jsdelivr.net/gh/google/code-prettifymaster/loader/run_prettify.min.js?langpython/script但要注意如果你用的是本地文件路径脚本同样能解析参数可如果你把脚本用构建工具打包、或者用了 defer/async 外部加载它解析 src 参数的能力就可能失效。所以我在项目里通常会绕开这个机制老老实实手动引lang-xxx.js文件行为更可控。4.3 动态内容、懒加载页面下的重启高亮预加载的静态代码块没问题但像 tabs 切换、弹窗里塞代码这种场景新进入 DOM 的pre不会被自动处理。我常用的方案是定义一个函数function reHighlight(container) { var elements container.querySelectorAll(pre.prettyprint); elements.forEach(function(el) { // 先清掉可能被重复处理的标记 el.classList.remove(prettyprinted); prettyPrint(undefined, el); }); }prettify 有一个内部机制处理过的元素会加上prettyprintedclass防止重复高亮。所以手动重新高亮前最好把这个 class 去掉否则你调多少次都没效果。这个细节是调试时最容易卡住的地方。4.4 性能边界它为什么不适合超大代码块prettify 的处理是同步的并且一次性把整个代码块的 token 全部替换成 span。代码量小没关系但如果有人把整个项目源码 paste 进来一个pre里几万行代码浏览器大概率会卡一下。我的经验是单块代码超过 500 行就要警惕超过 1000 行直接换方案。真要展示大文件分段渲染、或者用 web worker 实现后台高亮是更合理的选择。5. 网页端高亮和编辑器端高亮是两套逻辑prettify、highlight.js、Prism 与 VSCode/Cursor5.1 网页端三巨头横评很多人一搜代码高亮就看到 prettify、highlight.js、Prism 三个名字不知道怎么选。我从实际项目角度列个表维度prettifyhighlight.jsPrism维护状态长期停更社区有补丁活跃维护活跃维护接入成本两个标签零配置一个脚本 一个 CSS可自动识别需要注册语言、调 API包体积核心约 20KB含扩展更大核心约 30KB语言按需加载Core 约 8KB语言按需加载语言检测内置自动嗅探内置自动嗅探支持的语种多不做自动检测必须声明语言主题定制CSS 改 class 颜色多套主题自带换主题简单主题丰富社区生态好现代框架适配差要手动管时机社区有 Vue/React 封装提供Prism.highlightElementAPI组件化友好选型我个人的判断如果只是静态页里展示少量代码prettify 和 highlight.js 差不多谁简单用谁如果做文档站、代码要精确到每个语言细节Prism 的模块化更可控如果是在构建工具链里直接走 highlight.js 或 Prism 的 npm 包最省心因为 prettify 的脚本参数机制在 webpack 打包后很难用。5.2 VSCode 和 Cursor 里的代码高亮为什么不一样注意一个概念陷阱VSCode、Cursor 里的高亮和网页端的 prettify 完全不是一码事。编辑器用的是 TextMate 语法规则 语义化标记Semantic Tokens配合配色主题Color Theme来上色。它的工作链路是扩展如 Vue 的 Volar、Python 的 Pylance提供语法定义文件编辑器把源码拆成作用域片段配色主题给每个作用域分配颜色所以你在 VSCode 里能把 JSON 文件染出花花绿绿是因为有扩展在背后做语法解析。网上搜cursor 如何让 vue 文件代码高亮大概率是新用户刚装完 Cursor发现 .vue 文件没有颜色原因无非两个一是没装 Volar 或 Vetur 扩展二是打开文件时右下角语言模式被切成了 Plain Text。这个是编辑器扩展问题和网页代码高亮库八竿子打不着。5.3 高亮选中代码这个热搜词的内幕还有人搜vscode 高亮选中代码。这里其实有两个完全不同的功能同一变量出现位置的高亮VSCode 默认开启叫editor.selectionHighlight选中变量时所有同名字符串都会浅色高亮你买了某种高亮插件希望选中某段代码后整块画背景色那是editor.occurrencesHighlight或类似扩展提供的理解了这两点你就明白热搜词高亮选中代码不是浏览器代码高亮的问题而是编辑器交互配置的问题。所以我在回答同事问题时经常强调先分清你是在网页上展示代码还是在编辑器里看代码。两者的技术栈、解决方案、排查路径完全不同。6. 我在实际项目里踩过的 prettify 的坑中文注释、行号、定制与动态渲染6.1 中文注释和符号转义不是 prettify 的锅早期我用 prettify 渲染的代码块中文注释偶尔显示乱码。排查到最后发现是页面没有声明 UTF-8 编码或者服务器返回的 Content-Type 里缺了 charset。prettify 本身对文本处理是 UTF-8 安全的它不会主动破坏中文。真正容易出问题的是 HTML 转义。代码里出现div如果直接在pre里写原文浏览器会试图解析成一个 div 标签导致整个代码块结构错乱。正确做法是在服务端或 Markdown 渲染阶段就把代码文本转义成实体。如果你用 hexo、jekyll 这类静态站Markdown 解析器通常已经帮你转好了不用额外操心但如果手写 HTML 模板一定要记得转义。6.2 linenums 行号错位先查自己有没有覆盖样式自动模式下给pre加linenumsclassprettify 会把内容渲染成一个有序列表。很多人的自定义 CSS 会把ol的默认样式清掉比如ol { list-style: none; padding: 0; }这一下就把 prettify 的行号弄没了或者行号和代码错位。解决方法是给.linenums单独恢复样式或者在定制 CSS 时给范围限定清楚pre.prettyprint ol.linenums { padding-left: 40px; } pre.prettyprint li { list-style: decimal; }另一个更隐蔽的问题是复制代码带行号的ol结构复制到别处会带上一堆数字。如果代码展示区需要一键复制功能我的经验是复制逻辑不要直接取 innerText而是先临时去掉.linenums的列表样式或者用 data 属性存一份原始代码文本。6.3 皮肤定制从改色到做主题想换主题色最简单的是直接用社区皮肤。如果你用的本地包去下载或者参考prettify.css内部结构把.str、.kwd、.com这些 class 的颜色改掉即可。比如做一个暗色主题核心就是这些颜色的重定义pre.prettyprint { background: #2d2d2d; padding: 12px; border-radius: 6px; } .pln { color: #ccc; } .str { color: #7ec699; } .kwd { color: #cc99cd; } .com { color: #999; } .typ { color: #f08d49; } .lit { color: #f2777a; }注意如果通过?skin参数加载皮肤脚本插入的 CSS 顺序会在 prettify.css 之后所以你能通过覆盖的方式生效。但如果你把 prettify.css 和自定义主题都用link引入就一定要把主题放在 prettify.css 后面否则优先级会被前者压住。6.4 动态渲染场景的最后一次调试记录有一次在内部工具里用 prettify数据是通过 WebSocket 推过来的每个新消息都有代码块。我写了函数在高亮前把转义、高亮后添加 prettyprinted class结果第二次推送时页面里所有代码块都是纯文本。查了很久发现是我在渲染时把服务端已经转义过一次的字符串又转了一次导致代码里出现amp;lt;。这个问题给到我的教训是终端里能看见的代码文本和 HTML 页面里的代码文本差着一层转义。调试时先确认这一层。prettify 本身没什么好调大部分所谓bug都是数据链路上的转义不一致。6.5 我的最终用法建议如果今天让我给一个纯静态页面选代码高亮方案我仍然会优先上 prettify而且只用三件套里最简单的方式引 prettify.css 和 run_prettify.min.js在pre上加 prettyprint 和 lang-xx够用了。十几年前的库放到现在还能轻轻松松跑在主流浏览器里这本身就说明词法高亮这个问题的解法已经足够成熟不需要每次都用重型武器。不过也提醒一句它的安全性和现代浏览器的兼容性没有经过大规模维护如果你要处理的是不可信的第三方代码最好在展示前做一次文本转义和脱敏别让一段高亮代码成为 XSS 的入口。工具是老的但安全意识得是新的。本文还有配套的精品资源点击获取
返回列表