Prism 代码高亮库 100 问:从原理到实战的完整指南

发布时间:2026/10/20 23:26:49
Prism 代码高亮库 100 问:从原理到实战的完整指南
Prism 这个名字做前端的朋友应该不陌生。它是一个非常轻量的代码语法高亮库核心压缩之后大概只有 2KB不依赖任何框架也不需要 jQuery。你只要在页面里引入一份主题 CSS 和核心 JS再给代码块加上language-xxx这个类名它就会自动把代码切成带 token 类名的 HTML 再渲染出来。我前前后后用了这个库两三年从个人博客到文档站再到一个后台日志系统凡是涉及“把代码或者日志变成彩色可读文本”的场景最后基本都落在 Prism 上。使用过程中踩过的坑、想明白的原理、改过的样式我攒了 100 多个问答。这篇就把它们按主题重新梳理了一遍整理成一份可以随时查的 Prism 100 问。适合什么人看如果你是刚接触想给自己的博客加代码高亮重点看基础篇和配置篇如果你已经在项目里用起来了但是遇到不高亮、错位、体积大、动态内容渲染等问题排障篇会更对口如果你想往深走自己写语言规则或者插件机制篇是绕不开的。每个问题我尽量给出结论、原因和能直接抄的写法能少踩一个坑就算这篇值了。1. 这份 Prism 100 问的阅读地图1.1 你先判断自己属于哪类场景写这篇问答之前我心里其实是按三批人来划分的第一批是博主和文档维护者他们只是想把 Markdown 里的代码块变得好看属于“接入型”第二批是做中后台系统的开发者要在表格、弹窗、日志面板里动态展示代码属于“工程型”第三批是想深度定制的人要自定义语法、写插件、控制包体积属于“扩展型”。这三批人读这篇问答的路径完全不同。接入型用户把配置篇和行号、复制按钮、主题相关的几个问答看完就够了机制篇可以暂时跳过工程型用户建议重点看配置篇里 React、Vue、动态渲染那几问再配合排障篇的排查清单使用扩展型用户则要把机制篇从头啃到尾尤其是 grammar、hooks 和插件机制这三块是 Prism 所有进阶能力的地基。1.2 100 问是怎么分布的这 100 个问答我分成了四个主题块第 1~25 问是基础认知和选型对比第 26~50 问是安装配置和框架接入第 51~75 问是高亮原理、语法规则和插件扩展第 76~100 问是排障实战、性能优化和进阶经验。每个主题块里我会挑几个被问得最多的展开讲剩下的放在速查表里给一句话结论。这样做的原因是Prism 的很多问题其实是同一个根因比如“不高亮”这个现象背后可能是类名写错、语言组件没引、脚本执行时机不对单独拆成一个完整问答反而会把读者绕晕。用“详细讲 速查表”的组合既能说透高频问题又能让你在遇到冷门问题时快速定位方向。1.3 我这两年对 Prism 的整体判断先说我的结论Prism 解决的是“静态代码展示”这个窄问题但在这个赛道里做到了足够好用。它的强大不在功能丰富而在于“轻”和“可拆”——核心只做分词和生成 HTML主题、语言、插件全部独立你可以按需组合。但它的边界也很清楚它不是一个编辑器也不是一个重量级语法分析器。如果你拿它去做实时编辑场景的高亮或者想让它自动猜测语言那体验会很难受。把它用在正确的场景里它几乎不会给你找麻烦用错场景你会觉得哪哪都不顺手。所以前面这 25 问我花了不少篇幅谈边界。2. 基础篇先认清楚 Prism 的 25 问第 1~25 问2.1 Prism 到底是怎么工作的Q1Prism 到底是什么一个纯前端的语法高亮库。它的核心工作是把代码字符串通过正则 grammar 拆成 token再输出成带语义 class 的 HTML比如span classtoken keywordconst/span、span classtoken stringhello/span最后配合主题 CSS 变成我们看到的彩色代码。它不依赖任何库这也是它最大的优点——随便一个原生页面都能跑。Q2能把它理解成“把代码变成彩色文本的工具”吗可以但更准确的说法是“把代码转成语义化 HTML 的工具”。颜色只是 CSS 的事Prism 的核心其实是分词和加类名。这带来一个额外好处你可以基于 token 类型做很多事不止染色。比如给关键字加高亮提示、统计代码里用了哪些 API、给特定 token 加点击事件这些在拿到 token 结构之后都能做。Q3输出后的 HTML 长什么样比如说高亮这样一行 JavaScriptconst x hello;Prism 输出的 HTML 大致是pre classlanguage-javascriptcode classlanguage-javascript span classtoken keywordconst/span x span classtoken operator/span span classtoken stringhello/spanspan classtoken punctuation;/span /code/pre注意它把每个片段包成了独立的 span这就是后面你能做所有定制的基础。Q4和 highlight.js 相比差异在哪里两者都是老牌高亮库但设计理念不同。highlight.js 开箱即用自带语言自动检测你什么都不配置就能得到不错的效果代价是核心体积更大、检测也有误判率。Prism 则坚持“你告诉我要高亮什么我也不猜”需要你手写language-xxx类名换来的是更小的核心、更可控的输出和更简洁的扩展接口。选型上我的习惯是纯静态博客、想精细控制样式、愿意手动标注语言选 Prism内容来自用户上传、没法保证语言类名、又不想做预处理选 highlight.js 更省心。2.2 它的能力边界在哪里Q5适合哪些场景适合所有“只读展示”的场景博客文章、文档站、README 生成器、后台日志查看、教学平台、代码片段分享页。这类场景的共同特征是代码内容已经是确定的字符串不需要用户交互编辑只要渲染的时候好看、准确、可定制就行。Q6不适合哪些场景不适合在线编辑器、IDE 内嵌面板、需要实时增量解析或需要精确定位光标位置的产品。编辑器的高亮要处理文档变更、选区、撤销重做那得有完整的 token 流和位置映射这不是 Prism 的设计目标。类似需求请直接考虑 CodeMirror、Monaco 这类编辑器组件。Q7自动语言检测为什么不是它的强项因为 Prism 压根不提供这个能力。它的设计哲学是你告诉我要高亮什么我也不猜。好处是输出准确、体积小、没有误判坏处是如果内容来自第三方且没有语言标注你就得自己想办法。我常用的替代方案是写一个简单的启发式判断比如代码首字符是{且能解析成 JSON 就按 JSON 高亮以#!开头按 Bash 处理。真要做得严谨换 highlight.js 是更务实的选择。Q8浏览器兼容性怎么样主流现代浏览器完全支持移动端也没有问题。如果还要兼容远古浏览器官网文档里有提到需要额外补丁但这种场景在 2025 年已经很少见了我一般直接放弃老浏览器兼容。2.3 第 9~25 问速查表序号问题一句话结论Q9上手需要会哪些基础会 HTML/CSS 就能接入进阶定制需要一点 JS。Q10官方文档重点看哪几页下载页、语言组件列表页、插件页、API 页。Q11和 Vue、React 冲突吗不冲突但要注意渲染时机后面有专门问答。Q12语言组件都是官方维护吗大部分是社区贡献质量参差依赖关系要留意。Q13能直接高亮 Markdown 吗能高亮 Markdown 里的代码不负责渲染 Markdown。Q14能自动高亮日志里的时间戳、IP 吗默认不会需要自定义 grammar 或渲染前做文本处理。Q15有没有官方中文文档官方是英文社区有中文翻译版本。Q16高亮结果能复制吗配合 copy-to-clipboard 插件可以HTTP 环境有兼容问题。Q17能高亮 JSON、YAML 吗支持JSON、YAML、TOML 都有对应组件。Q18能高亮 Bash、Dockerfile 吗支持Bash 组件很常用Dockerfile 用 markup 扩展。Q19对 SEO 友好吗服务端渲染出 HTML 最友好纯前端渲染搜索引擎也能读但体验一般。Q20需要在后端安装什么吗纯前端完全不用后端渲染只需 Node 端运行 Prism。Q21支持暗色主题吗本身只是用 CSS 配色暗色主题随意切换。Q22可以同时用两套主题吗可以比如页面亮色、代码块暗色。Q23有现成的 React 封装吗有第三方封装但自己封装一个组件也不难。Q24有现成的 Vue 封装吗有社区封装但自己写一个组件更可控。Q25到底选 Prism 还是 highlight.js看重体积和可控性选 Prism看重零配置和自动检测选后者。3. 配置篇从 CDN 到框架集成的 25 问第 26~50 问3.1 安装与按需加载Q26CDN 方式怎么快速接入在页面里引三样东西主题 CSS、核心 JS、你要用的语言组件 JS。核心 JS 和语言组件建议都用defer或放在body底部避免阻塞渲染。link relstylesheet hrefhttps://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css / script srchttps://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js defer/script script srchttps://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js defer/script这套做法适合博客、单页工具、临时演示不用构建工具也能跑。公共 CDN 上都有现成文件选一个稳定的源即可。Q27npm / Webpack / Vite 怎么接入先安装再按需引入。npm install prismjsimport Prism from prismjs; import prismjs/themes/prism-tomorrow.css; import prismjs/components/prism-python; import prismjs/components/prism-jsx;这里有个细节prismjs主入口默认带了 markup、css、clike、javascript 这几个基础语言其他语言要走 components 目录单独引入。如果你导入语言组件时报 “Prism is not defined” 之类的错检查一下是否先引入了主入口以及打包器对组件文件的处理方式。Q28语言组件之间的依赖关系要注意什么组件之间是有依赖的。比如jsx依赖markup和javascripttsx依赖jsx和typescriptmarkdown依赖markup-templating。只引入一个文件而不引入它的依赖运行时会报 “Cannot read property xxx of undefined” 这种错误。我的建议是遇到报错先打开该语言组件源码看它头部有没有Prism.languages.xxx ...之前引用了别的 language这比瞎排查快得多。Q29怎么做到只引入 10 个语言、把打包体积压到最小在 webpack / vite 里只import用到的prismjs/components/prism-具体语言文件。这样构建工具会把未引入的语言全部摇树摇掉。社区里也有一些 babel 插件可以自动按使用情况裁剪但插件维护活跃度不一我通常手动管理一个语言清单简单直接。3.2 主题与常用插件的接入Q30主题怎么切换主题就是一份 CSS 文件官方提供十几套常见的有默认的prism.css、prism-okaidia.css、prism-tomorrow.css、prism-vs.css。切换主题只需要换 link 或者 import 的 CSS 路径HTML 结构不用动。我目前一直用 tomorrow对比度适中代码块看起来不累。Q31怎么自定义主题两个思路。一个是直接改主题 CSS 里的 token 颜色比如把.token.keyword的 color 从蓝色改成紫色另一个是覆盖 CSS 变量Prism 官方主题没有统一的变量规范所以覆盖变量不一定有效。我推荐直接改同一份主题文件把--code-font-size、背景色、.token系列颜色统一调整维护起来最直观。code[class*language-], pre[class*language-] { font-family: JetBrains Mono, Consolas, monospace; font-size: 14px; background: #1e1e1e; color: #d4d4d4; }Q32行号插件怎么接需要引入 line-numbers 插件的 JS 和 CSS然后给pre加上line-numbers类。pre classline-numbers language-jscode classlanguage-jsconst a 1;/code/pre行号样式本质是给代码块加了一个绝对定位的序号列所以代码块的padding-left要和行号列宽度匹配否则会错位。这个具体问题在排障篇 Q100 附近还有更多细节。Q33复制按钮和语言标签怎么加复制按钮是 toolbar 插件和 copy-to-clipboard 插件一起工作的产物。引入顺序是核心 JS - toolbar 插件 - copy-to-clipboard 插件。插件会在代码块右上角生成一个复制按钮。语言标签由 show-language 插件提供它会给pre打上对应的语言名然后你通过 CSS 把它定位到角落做样式。script srchttps://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/toolbar/prism-toolbar.min.js/script script srchttps://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js/script script srchttps://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/show-language/prism-show-language.min.js/script3.3 框架接入与动态内容Q34React 里怎么接入最稳建议把高亮逻辑封装成一个组件在 useEffect 里执行。关键点是高亮前先把原始代码通过textContent写进去而不是用dangerouslySetInnerHTML直接塞已经高亮过的 HTML这样可以避免二次高亮和转义问题。import { useEffect, useRef } from react; import Prism from prismjs; import prismjs/themes/prism-tomorrow.css; import prismjs/components/prism-json; function CodeBlock({ code, language }) { const ref useRef(null); useEffect(() { if (ref.current) { ref.current.textContent code; Prism.highlightElement(ref.current); } }, [code, language]); return ( pre className{language-${language}} code ref{ref} className{language-${language}} / /pre ); }Q35Vue 里怎么接入思路和 React 一样在onMounted和监听数据变化的回调里执行高亮。同样先给 code 赋值 textContent再做高亮避免直接把已经处理过的 HTML 交给 Vue 的模板渲染。template pre :classlanguage- languagecode refcodeRef :classlanguage- language{{ code }}/code/pre /template script setup import { ref, watch, onMounted } from vue; import Prism from prismjs; const props defineProps({ code: String, language: String }); const codeRef ref(null); function highlight() { if (codeRef.value) { codeRef.value.textContent props.code; Prism.highlightElement(codeRef.value); } } onMounted(highlight); watch(() props.code, highlight); /scriptQ36Markdown 渲染怎么接以 markdown-it 为例在 highlight 回调里调用 Prism然后返回带完整precode包裹的结果markdown-it 会直接采用你返回的 HTML。const MarkdownIt require(markdown-it); const Prism require(prismjs); const md new MarkdownIt({ highlight: (str, lang) { if (lang Prism.languages[lang]) { return pre classlanguage- lang code classlanguage- lang Prism.highlight(str, Prism.languages[lang], lang) /code/pre; } return ; } });Q37Node 服务端怎么用Prism 可以在 Node 里直接运行核心 API 是Prism.highlight(code, grammar, language)它返回 HTML 字符串。这样就能在服务端把代码渲染成高亮好的 HTML再输出给前端前端完全不用再跑高亮逻辑。注意一点有些插件依赖 DOM比如 line-numbers 的某些实现在 Node 里提前渲染出来的效果可能和浏览器里不完全一致纯高亮则没有任何问题。3.4 第 38~50 问速查表序号问题一句话结论Q38动态插入的代码块怎么高亮插入后调用Prism.highlightElement或Prism.highlightAllUnder。Q39页面有多个异步加载的区块怎么办每个区块渲染完成后分别对容器调用高亮 API。Q40可以通过 autoloader 自动加载语言吗可以引 autoloader 插件会自动按需从 CDN 拉语言。Q41多语言混合代码怎么高亮分开成多个代码块或用自定义 grammar 嵌入。Q42高亮时换行和缩进会乱吗默认保留原文本也可以配合 normalize-whitespace 校准。Q43高亮时想保留手动添加的 HTML 标签用 keep-markup 插件但要注意 XSS 风险。Q44行号错位怎么调检查代码块 padding-left 和 line-height行号列宽度要一致。Q45大段代码想分段高亮按行截断成多个小块或使用手动模式延迟处理。Q46高亮结果可以缓存吗服务端渲染或构建期生成 HTML 后可以直接静态化缓存。Q47防止重复高亮的办法高亮前给节点加标记已有标记就跳过。Q48可以全站共用一套主题吗可以全局引一份主题 CSS 就行。Q49有按页面按需加载主题的方式吗有每个页面只引自己需要的主题文件。Q50不想写类名能用>Prism.languages.custom { comment: /\/\/.*/, string: /([^\\]|\\.)*/, keyword: /\b(?:if|else|while|return)\b/ };每个 key 对应一个 token class主题 CSS 里定义这些 class 的颜色。Q53为什么正则顺序很重要Prism 会按对象的属性顺序依次匹配先匹配到的优先。所以通常要把注释、字符串放在关键词前面否则字符串或者注释里的关键字会被误判成 keyword。写 grammar 的时候遵循这个顺序注释和字符串优先然后是数字、关键字、标识符最后才是容易误伤的通配规则。这个顺序不是我拍脑袋定的是正则分词最不容易出错的排列方式。Q54token 类型为什么用 class 而不是直接上色这是 Prism 最值得学习的设计。把“语义”和“样式”分开之后你得到的是结构化的信息而不是一串彩色的字符串。你可以在不改变代码内容的情况下随意换主题可以针对token.keyword加 hover 效果可以在高亮结果上做代码分析后续所有插件和扩展都是建立在这个数据结构之上的。4.2 自定义规则与 hooksQ55给现有语言加规则怎么做用Prism.languages.insertBefore可以把新 token 类型插到某个已有类型之前。Prism.languages.insertBefore(javascript, string, { api-call: /(?:fetch|axios)\.\w/ });如果只是想在现有规则上增加别名或继承可以用Prism.languages.extend。注意 insertBefore 是在原对象上做修改改动会影响所有使用该语言的地方尽量在应用启动时做一次初始化。Q56hooks 有哪些常用的常用的是这几个before-highlight在开始分词前触发可以拿到底层元素并修改原始文本before-insert在 HTML 插入 DOM 前触发适合做输出内容的后处理after-highlight在插入后触发wrap在每个 token 生成 span 时触发可以给 token 加额外 class 或者改标签complete在整段高亮完成后触发。Q57自定义一个语言的完整步骤是什么三步在Prism.languages.你的语言名下定义一个 grammar 对象在主题 CSS 里为对应的 token class 加上颜色然后给代码块标上language-你的语言名。调试时可以先在浏览器控制台打印Prism.languages.你的语言名确认对象已经注册成功再检查是不是渲染时机的问题。Q58插件是通过什么机制工作的插件本质上是 hooks 的消费者。比如 copy-to-clipboard 插件在 toolbar 渲染时注册按钮toolbar 又是在before-highlight等阶段找代码块位置。所以写插件前你不需要理解整个高亮引擎只需要搞清楚自己要在哪个阶段介入。这是 Prism 扩展性好的根本原因——它把流程的关键节点都开放出来了。4.3 常用插件与棘手的嵌套场景Q59line-numbers、toolbar、copy-to-clipboard 怎么组合这三个是表达场景里出现频率最高的组合行号方便引用位置复制按钮方便读者取代码toolbar 负责承载按钮。接入顺序固定是先核心再插件HTML 结构上要在同一个pre上同时挂line-numbers类。它们的 CSS 彼此独立不会冲突但如果代码块的父容器设置了奇怪的overflow行号和按钮的定位可能偏移。Q60line-highlight 插件怎么高亮指定行在pre上设置>