首页 / 资讯中心 / 文章详情

prettify代码高亮三件套:静态页面与动态内容的接入和避坑指南

prettify代码高亮三件套:静态页面与动态内容的接入和避坑指南 ★ FEATURED ARTICLE
简介面向网页开发者和技术博客作者的代码高亮工具包收录 Prettify 的核心三件套解决在页面中展示源代码时层次不清、阅读吃力的问题。压缩包内共 3 个文件包含 2 个 JavaScript 脚本和 1 个样式表prettify.css 负责定义关键字、字符串、注释等语法元素的配色与字体prettify.js 提供完整的语法识别与高亮逻辑run_prettify.min.js 为压缩优化版本在保持功能的同时减少带宽消耗、提升加载速度。目前已有 842 人学习下载。通过本包可快速为网站或博客接入专业代码高亮只需引用样式与脚本并给代码块添加 prettyprint 类名即可生效支持 HTML、CSS、JavaScript、Python、Java、C 等常见语言自动匹配语法规则无需手动配置。14KB 的轻量体积尤其适合对页面性能敏感的静态博客或文档站点让代码展示兼顾美观与效率。1. prettify 代码高亮三个文件就够用静态页面为何还离不开它在某公司内部文档站翻出十年前的老模板代码块全是纯色黑字想给它上色又不愿意动构建链。后来发现 prettify 这套三件套方案它不需要 Node 环境不需要打包器把三个文件放进静态目录写一行 class 就能让pre里的代码变得五颜六色。这个方案里的 prettify.css、prettify.js、run_prettify.min.js 分别管样式、逻辑和自动执行对于不想折腾的开发者这是性价比最高的接入方式。适合维护老项目、做静态页面、以及需要在不支持 npm 的服务器环境里解决问题的人。2. 三个文件各管一摊prettify.css、prettify.js、run_prettify.min.js 的分工与选型2.1 prettify.js核心引擎负责把代码切成带语义的 tokenprettify.js 是整个方案的心脏。它做的事情是读取pre classprettyprint元素里的纯文本用内置的词法分析器识别关键字、字符串、注释、类型、变量、标点等然后把每个 token 包到对应的span classxxx里。它并不是一个只能做“整段变颜色”的工具而是靠一组固定的 CSS 类名来标记 token具体显示成什么颜色就交给样式表。需要说明的是prettify.js 本身并不包含对每一种编程语言的完整支持。默认内置了对 JavaScript、C 系和一部分常见语言的基本支持其他语言需要额外的语言扩展文件也就是lang-*.js。比如你想突出显示 CSS就需要在页面里额外引入lang-css.js否则 CSS 的属性和选择器会被当成普通文本处理。这个设计让核心包体积保持很小但也意味着在接入之前必须先确认目标语言是否在默认支持范围内。我在实际接入时通常会先翻一下包内的 lang 文件夹看看有没有目标语言的扩展。如果有就直接通过 run_prettify 的参数去加载如果没有prettify 还会退回到“按普通文本”处理效果就是只有颜色背景没有语法着色不会报错但观感会差很多。这点尤其容易被新手忽略。2.2 prettify.css负责最终颜值默认主题与编辑器风格的差异prettify.css 里定义的是 token 的颜色方案。它把.str定义为字符串颜色.kwd定义为关键字颜色.com定义为注释颜色剩下的还有.typ类型、.lit字面量、.pun标点、.pln纯文本等。prettify 自己能做的只是给 token 打好标签颜色的决定权完全在这份样式表里。默认的 prettify.css 是一套白底黑字、红蓝绿配色的样式适合浅色页面。但如果你把run_prettify.min.js的皮肤参数改成skindesert或者skinsunburst就得额外引入对应的 CSS 文件而不是继续用 prettify.css。这里最容易踩的坑是只改 query 参数但没放对应 css页面会一直停留在没着色的状态。所以我一般建议在还不确定要不要换肤的时候直接用 prettify.css 跑通最小流程再考虑定制。很多老项目之所以选择 prettify就是因为它把“显示逻辑”和“皮肤”彻底分开了。换皮肤就是换 CSS完全不碰 JS。后续你要把代码块改成暗色背景只需要在自己的样式表里重新覆盖这几条颜色规则不需要重新下载任何文件这也是它能活这么多年的原因之一。2.3 run_prettify.min.js自动加载器一行脚本完成扫描、注入和着色run_prettify.min.js 从名字就能看出来它是一个压缩过的“自动运行”入口。引入它之后你不需要再手动调用任何函数它会在 DOM 加载完成后自动扫描页面中所有带prettyprint类名的代码块然后调用底层引擎执行高亮。这个文件内部其实已经打包了 prettify 的核心代码所以用了它通常就不必再单独引入 prettify.js。它额外提供了通过 script 标签 src 上的查询参数来做配置的方式。常见做法是这样script srcrun_prettify.min.js?langcssskinsunburst/scriptlangcss表示自动加载lang-css.js扩展skinsunburst表示加载 sunburst 主题样式。需要注意的是这些参数真正生效的前提是lang-css.js和sunburst.css必须和 run_prettify.min.js 放在同一个目录下。如果你把 run_prettify.min.js 放到了 CDN 上但扩展文件没有跟着放那这些参数就会被静默忽略。从工程角度讲run_prettify 适合那些代码块都在首屏、页面结构固定、不需要动态插入内容的场景。你只需要维护一行 script 标签就能覆盖整个站点的代码高亮。反过来说如果页面里大部分代码块是用户交互后动态生成的或者你希望自己掌控加载时机那么用 prettify.js 手动调用才更合适。2.4 选型依据全量自动 vs 手动控制按项目形态取舍回到标题里的三个文件很多初学者会困惑到底该引入哪个。我的判断标准很简单静态页面选 run_prettify动态页面选 prettify.js在两个都要用的时候主文档仍然可以用 run_prettify但动态内容插入之后不要再调用 run_prettify 内部已经执行过的逻辑而是用PR.prettyPrint()去处理增量部分。判断一个项目属于哪种形态可以看两个问题代码块是否在服务端输出时就存在代码块是否会因为用户点击、接口回调而新增如果两个都是“是”那你就不能指望一条 run_prettify 搞定全部必须设计手动调用的链路。另外体积和性能也是一个考量点。run_prettify.min.js 因为自带了自动执行逻辑会让首屏的 JS 多出一些开销如果你对首屏加载特别敏感可以把 prettify.js 单独放到页面底部再在window.load后再执行PR.prettyPrint()。这种做法把高亮对首屏渲染的影响降到最低代价是代码会晚几百毫秒上色对绝大多数场景来说完全可以接受。3. 从三个文件到完整页面最小接入命令与三种部署形态3.1 最简用法两个标签加一个脚本静态页面直接跑起来假设你已经把三个文件放到了站点的/static/prettify/目录那么一个能自动高亮的最小 HTML 长这样!DOCTYPE html html langzh-CN head meta charsetutf-8 titleprettify 最小接入/title link relstylesheet hrefstatic/prettify/prettify.css /head body pre classprettyprintcodefunction hello() { return world; }/code/pre script srcstatic/prettify/run_prettify.min.js/script /body /html这段代码里link负责加载默认皮肤script负责在页面加载完成后扫描并高亮。代码块里高亮的触发条件是 class 中包含prettyprint不是pre本身自带效果。如果你写了pre但没有加这个 classprettify 会把它当成普通块什么都不动。实际工作中我会把这段结构复制到项目里先验证网络路径。验证方法很简单浏览器打开页面右键检查代码块里的内容如果看到span classkwdfunction/span这样的结构说明链路已经通了。如果只有一行纯文本就先检查控制台的 404 信息十有八九是 CSS 或 JS 的路径写错了。3.2 动态内容用 prettify.js 手动调用覆盖接口返回的代码块服务端渲染的页面可以直接用 run_prettify但单页应用或者有局部刷新的页面就不行了。原因在于 run_prettify 自带的扫描只在初始 DOM 加载完成后执行一次之后通过 AJAX 插入的pre classprettyprint不会再被它看到。这个场景需要用 prettify.js 手动调用。// 动态代码块插入到页面后执行高亮 fetch(/api/code-snippet) .then(res res.text()) .then(codeText { const wrap document.getElementById(codeWrap); wrap.innerHTML pre classprettyprintcode escapeHtml(codeText) /code/pre; PR.prettyPrint(undefined, wrap); }); // 简单的 HTML 转义避免 被浏览器解析成标签 function escapeHtml(s) { return s.replace(//g, amp;) .replace(//g, lt;) .replace(//g, gt;); }这里需要解释两件事。第一PR.prettyPrint(undefined, wrap)的第二个参数是容器prettify 只处理这个容器内的代码块避免反复扫描整个文档。第二escapeHtml是必不可少的步骤因为接口返回的代码里很可能带着尖括号直接插进 innerHTML 会被浏览器当成 HTML 标签代码块就整个坏掉了。手动调用的优势是可控但代价是你必须自己在合适的时机触发。常见做法是在接口回调用执行或者放在requestAnimationFrame里避免在 DOM 尚未真正渲染时过早调用。多次对同一个容器调用PR.prettyPrint()不会报错但会在原有 token 的 span 上再包一层 span造成嵌套越来越深所以最好用布尔标记记录每个容器是否已经高亮过。3.3 博客与文档站集成把 run_prettify 放进公共模板绝大多数个人博客、文档站都是服务端渲染页面结构相对固定。这种项目最常见的做法是把 run_prettify.min.js 放到全站公共头部的 script 区域然后用 query 参数声明需要额外支持的语言。这样每篇文章里只要写pre classprettyprint linenums就能得到带行号的代码块。一个我在某个文档站上实际用过的模板片段如下script srcstatic/prettify/run_prettify.min.js?langcsslangjava/script这里langcss和langjava告诉加载器去加载两个语言扩展文件。需要注意多个lang参数是可以并列的这种写法是 prettify 加载器支持的而不是只能传一个。懒得写多个的话也可以直接写成?langcss, java吗这里要小心不同版本的 prettify 对参数解析的宽松程度不同最稳妥的做法还是每个语言单独一个参数。对于 Markdown 渲染器要保证最终输出的 HTML 中的代码块确实带有prettyprint类。有的渲染器默认给你的code加的语言类是language-python而不是prettyprint这种时候要么改渲染模板要么在外层包一个div classprettyprint。我一般建议直接改 Markdown 的渲染配置把 fenced code block 的输出 class 改成同时包含prettyprint和 language 类这样 prettify 负责高亮自己的 CSS 也能根据 language 类做额外修饰。3.4 行号、语言提示与自动加载的常见参数设置prettify 的行号用法是在 class 上追加linenums例如pre classprettyprint linenums。这个类会触发 prettify 把代码按行拆成li放进一个ol classlinenums里最终由 CSS 控制显示成数字列表。默认情况下行号从 1 开始但如果你只想从第 5 行开始显示需要自己用样式去偏移prettify 本身不提供起始行参数。另一个被经常问到的参数是“要不要自动高亮”。run_prettify.min.js 的机制是只要引入就会自动跑没有提供官方开关去关闭自动执行。如果你想完全控制执行时机唯一的办法是改用 prettify.js然后自己调 PR.prettyPrint。这也是我在一个混合项目里遇到的真实场景主框架引入了 run_prettify同时又想在弹窗里高亮一段代码结果发现无法阻止全局自动执行最后只能把弹窗的高亮也交给它在弹窗内容插入后手动调用一次 PR.prettyPrint虽然会重复处理主文档里的代码块但实际影响不大只要别每次都新建弹窗就行。参数这块的边界比较多我把常用的三种接入方式整理成了下表方便你按项目形态对号入座项目形态推荐文件触发方式优点代价纯静态页面run_prettify.min.js自动零配置无法精确控制触发时机动态单页prettify.js手动 PR.prettyPrint可控可增量要自己处理转义和时机混合页面run_prettify 手动调用自动手动全局省心局部兼容可能重复扫描注意去重4. 代码高亮避坑指南5 个血泪经验每条都让页面翻过车4.1 现象页面加载完代码块依然是一坨纯文本没有任何 span原因通常有三个第一pre上没有写prettyprint这个 class第二run_prettify.min.js 的路径写错控制台能看到 404第三浏览器在 DOM 加载完成前执行脚本导致扫描时代码块还不存在不过 run_prettify 内部一般会等待 DOMContentLoaded所以这种情况比较少见。解决方法是按顺序排查先确认 HTML 里的 class 是prettyprint而不是prettify一字之差完全失效再在控制台看网络面板有没有红色报错最后在 Console 里手动执行PR.prettyPrint()如果执行后高亮出现了说明是自动触发被浏览器策略拦了。如果用的是 prettify.js 而没引入 run_prettify一定记得手动调用。4.2 现象代码有意义但只有背景色和边框没有语法颜色这个现象大概率是 prettify.css 没加载成功或者被自己写的全局pre样式覆盖了。prettify 的高亮全部依赖 token 类选择器比如.prettyprint .kwd。如果全局 CSS 里写了pre { color: #333 }它的优先级可能高过 prettify.css 中的.pln导致所有 token 显示成同一个颜色。解决这个问题我一般先从开发者工具里找到代码块中的一个 span看它的 color 属性来自哪一条规则。如果是被全局 pre 覆盖就给自己主题里的.prettyprint加上更具体的选择器例如.article-content .prettyprint .kwd或者干脆把 prettify.css 放到最后一个被引入。另外要注意换肤参数skinsunburst配合的还是 prettify.css 时也会出现这种“半上色”状态因为 sunburst 的颜色规则在另一个 css 文件里。4.3 现象代码里的div变成了页面里的实际元素代码块整个没了这是最经典的转义问题。只要把包含尖括号的代码直接塞进 innerHTML浏览器就会优先把它们当成 HTML 标签。尤其常见于从接口动态拉取代码片段然后直接做字符串拼接的场景。比如某次我用 DOM 方式拼代码块忘了转义结果页面上出现了一堆隐藏的 div代码里的逻辑全被渲染成了真实的 DOM 结构。解决方法是强制转义。在服务端输出前用模板引擎的 escape在前端用上面 3.2 里的escapeHtml函数或者textContent赋值。这里有个比较隐蔽的细节textContent能安全显示代码但是 prettify 需要读取pre里的文本节点再生成 span如果你的pre是通过 textContent 塞的它也能正常工作。而 innerHTML 直接塞代码则必须先转义否则 prettify 拿到的已经不是原始代码根本没有办法正确切分 token。4.4 现象ajax 加载的代码块一直不上色刷新整页却正常root cause 是 run_prettify 只在页面初始加载时扫描了一次。动态插入的节点不在它的扫描队列里所以无论等了多久都不会自动处理。我之前在一个后台管理表格里遇到这个问题点击查看详情后弹窗里的代码永远是黑白的后来换成 prettify.js 手动调用才解决。解决思路是在动态内容插入后调用PR.prettyPrint(undefined, container)。这里要注意第二次调用同一个容器时prettify 会重新处理里面所有的 span可能导致样式嵌套所以最好记录一下状态。如果不想维护状态也可以给动态代码块的容器换成全新的元素不要复用已经高亮过的节点。4.5 现象行号显示出来了但复制代码时把行号也带进剪贴板行号不是 prettify 渲染出来的文本而是ol classlinenums里每个li前的数字这些数字由浏览器的列表样式生成。复制时能否带上取决于浏览器的默认行为。在多数浏览器里复制的范围是文本内容不会带上列表序号但在某些浏览器下选中代码块时会把左边的数字也算进去导致复制出来的代码开头多一串数字。解决方法是给.linenums li加上user-select: none让行号不可被选中。同时要注意.linenums li的list-style-position如果设置为 outside行号和代码之间的间距会变得不好控制我一般都会显式写出.prettyprint ol.linenums li { list-style-type: decimal; padding-left: 0.5em; -webkit-user-select: none; user-select: none; }注意除了这些还有一个常见的“玄学”问题代码块里某些字符显示成了乱码方框。这个通常和 prettify 无关而是页面没声明 UTF-8 编码建议先加meta charsetutf-8再回来排查 prettify 本身。5. 自定义主题让 prettify 完全融入你的站点配色体系5.1 读懂 token 类名才能对症下药prettify 把代码里的每一种语法成分都归到固定的几个类里理解了这些类名你就能在任何 CSS 中拦截它们。下面是最常用的几类类名含义常见误用.com注释以为是组件缩写.str字符串不只指双引号内容.kwd关键字不包含变量名.typ类型名泛型和类名.lit字面量数字、布尔值.pun标点括号分号逗号.pln普通文本未识别的纯内容虽然有些高亮库把字符串写成.stringprettify 用的是缩略名所以在写自定义样式前先确认这些类是对的。很多主题翻车其实是忘了.typ导致类型名和普通变量一个颜色看起来层次感不够。5.2 用自己站点的变量覆盖默认样式假设你的站点有一组 CSS 变量--code-bg、--code-text、--code-keyword。要让 prettify 跟着走只需把变量值赋给对应的 token 类如下.prettyprint { background: var(--code-bg, #f8f8f8); color: var(--code-text, #333); } .prettyprint .kwd { color: var(--code-keyword, #007020); } .prettyprint .str { color: var(--code-string, #a31515); } .prettyprint .com { color: var(--code-comment, #888888); } .prettyprint .lit { color: var(--code-literal, #c7254e); } .prettyprint .typ { color: var(--code-type, #008080); } .prettyprint .pun { color: var(--code-punct, #333); }这些规则要写在 prettify.css 之后因为后者本身也有.prettyprint .kwd这类规则。CSS 变量本身不参与优先级计算但两条规则同优先级时后面的会覆盖前面的。所以我的习惯是在自己的主题 css 里把这组规则放在文件末尾并且保留!important之外不用。如果你希望某个颜色绝对不被站内其他规则覆盖可以给 token 加更高优先级比如.article .prettyprint .kwd但我个人不太建议过度使用因为后续维护很难受。5.3 暗色模式适配一套 CSS 让代码块跟着系统主题切换现在很多站点会支持暗色模式prettify 默认的 prettify.css 是浅色主题直接用在暗色背景下会亮得刺眼。可以不改 HTML而是通过媒体查询覆盖。我常用的写法是media (prefers-color-scheme: dark) { .prettyprint { background: #1a1a1a; color: #d0d0d0; } .prettyprint .com { color: #7a7a7a; } .prettyprint .kwd { color: #cc7833; } .prettyprint .str { color: #a5c261; } .prettyprint .lit { color: #6c9cff; } }如果站点不是跟随系统而是自己提供“亮色/暗色”切换按钮那可以给html加一个>.prettyprint ol.linenums { margin: 0; padding-left: 2.5em; border-right: 1px solid #e1e1e1; } .prettyprint ol.linenums li { padding-left: 0.5em; line-height: 1.6; }这里有个细节ol.linenums的padding-left不能是 0因为列表自带的数字标记需要空间如果你发现行号不见了大概率就是padding-left被重置成了 0同时list-style-type被设成了 none。prettify 的行号并非靠::marker生成而是浏览器按普通列表绘制所以不要轻易把列表样式全部抹掉。另一个排版问题是代码换行。prettify 默认不会自动换行长代码会撑出横向滚动条。如果你希望代码自动折行可以加.prettyprint { white-space: pre-wrap; word-wrap: break-word; }但要注意white-space: pre-wrap会改变行号与代码的对齐逻辑因为当某一行断开成两行时行号仍然只出现一次看起来会有点错位。对接受横向滚动条的站点我更建议保留默认的pre只在移动端用媒体查询临时开启pre-wrap。6. 验证高亮与按需加载收尾前再检查这四件事最后一个环节是验证。我每接入一个 prettify 实例都会在浏览器控制台执行下面这段判断// 检查页面上是否有 prettify 生成的 token span document.querySelectorAll(.prettyprint span).length 0如果返回 false说明高亮没有真正执行回到第 4 章排查如果返回 true再随便点开一个代码块确认.kwd和.str的样式确实落在自己写的 CSS 变量上。这一步能避免“表面高亮、实际上一团黑”的假成功。第二件事是检查语言扩展是否真的生效。比如代码块里写的是 Python但页面没有引入 lang-python.jsprettify 会把def当成普通文本。验证方式是查找.kwd的 span 中是否有def这个文本如果找不到说明扩展没加载。第三件事是性能。老页面的代码块一多自动高亮会占用一点主线程。如果页面卡顿可以考虑给 run_prettify 加上defer属性或者改成页面加载完成后再手动触发。defer的写法和普通 script 一样只是放在 head 里script defer srcstatic/prettify/run_prettify.min.js?langcss/script第四件事是确认重复执行没有造成无限嵌套。动态页面最容易出这个问题。如果发现代码块里的 span 每刷新一次就多一层说明你在同一个容器上多次调用了 PR.prettyPrint。解决办法是把调用时机控制在异步数据到达后或者为每个容器缓存一个style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />
阅读完成 · 觉得有帮助?
咨询建站