使用 PrismJS 实现代码语法高亮
问题背景
虽然 Typecho 原生支持 Markdown
语法,但是代码块部分只是用等宽字体+颜色置灰进行简单装饰,并不能像 Github 那样运用丰富多彩的颜色规则来高亮代码。然而没有颜色的代码,就像没有了灵魂,可读性可谓是呈断崖式下降,关键字、变量名和函数名都不分你我,共灰白一色。
我的需求
为了可读性,建站之后,代码高亮便提上日程。我梳理了自己的需求,主要有
- 着色代码: 语法高亮
- 多种语言:基础的包括
JS
,PHP
,Go
,Python
和C/C++
等,后续如果要学习Rust
或TS
希望能方便地接入 - 软件生态:使用的人数多不多,代码更新迭代是否勤快,Issue 处理及时性
- 可扩展性:能否扩展小功能?如代码支持拷贝,左侧显示行数,显示所用语言
- 方便部署:部署时会考虑代码包压缩后的大小,是否容易嵌入已有的代码结构中
可行分析
Q1: 在哪实现?
首先,确定着色代码是在前端还是后端实现?
颜色展示,这是前端的功能。从扩展性考虑,为了便于后期切换不同的代码着色方案,比如 IDE 中常见的 Github
、 Solarized Light / Dark
, Tomorrow Dark
, XCode
等配色。
因此,这部分不宜直接把着色完毕的富文本存在数据库中,我们把着色的时机延后,等页面在浏览器展示时——即 Dom Content Load
内容加载完毕后——再根据配色方案来进行惰性着色。
Q2: 选哪个轮子?
网页代码高亮插件一搜一大把,比如 Google Code Prettify,Highlight.js, SyntaxHighlighter, Prism.js 等。
横向对比后,发现 Prism.js 和我的需求最匹配:
丰富多彩
自带多种样式,所有的样式通过 CSS 完成,并使用直观的类名,如:.token, .string, .comment 等。这意味着,将来对现有颜色方案不满意,还可以通过类名直接针对某类代码进行改色。
开放扩展
官方提供基础的核心功能和基本样式,并把接口对外开放。于是由开源社区力量贡献了五颜六色的配色方案,和五花八门的的功能插件。这些扩展均作为可选项,就像是超市里琳瑯满目的小商品,想要哪个,点点鼠标就能把它们加入购物车——加入最终的代码包中——打包📦带回家。
方便部署
代码核心部分压缩版只有 7 KB。而且每添加一个语言平均增加 0.3-0.5 KB,主题在 1 KB 左右。
受众广泛
据官方公布的使用者,不少大厂也在用,可靠性得到了背书,代码后续维护自然不必担心了。
Q3: 怎么部署?
移步下一节
集成部署
下载 dist 源码
如上图所示,勾选想要的主题配色和功能组件,下载 Minimized version 的代码包:
- prism.js
- prism.css => prism_light.css
此外,为了后续便于切换不同配色方案,我把颜色部分代码,即 css 文件加上后缀来唯一命名。
前端埋点
在合适的位置,例如 post 文章视图中都会包含 header 头部模版文件,那么我们可以分别置入 js 和 css 文件:
- 把
prism.css
放到 header 中,一般位置要比较靠前,确保里面的选择器+渲染规则能引用到 HTML 标签 - 把
prism.js
放到 post 里面,与 css 放置原则相反,js 尽量靠近 标签的末尾,以免加载 javascript 时内容还没完全加载
<!DOCTYPE html>
<html>
<head>
<!-- CSS 放在 head 头部 -->
<link href="themes/prism.css" rel="stylesheet" />
<!-- ... -->
</head>
<body>
<!-- ... -->
<script src="prism.js"></script>
<!-- JS 靠近 body 末尾 -->
</body>
</html>
效果展示
下面以 Go 语言的并行计算(素数筛)为例,展示语法高亮功能。
// A concurrent prime sieve
package main
import "fmt"
// Send the sequence 2, 3, 4, ... to channel 'ch'.
func Generate(ch chan<- int) {
for i := 2; ; i++ {
ch <- i // Send 'i' to channel 'ch'.
}
}
// Copy the values from channel 'in' to channel 'out',
// removing those divisible by 'prime'.
func Filter(in <-chan int, out chan<- int, prime int) {
for {
i := <-in // Receive value from 'in'.
if i%prime != 0 {
out <- i // Send 'i' to 'out'.
}
}
}
// The prime sieve: Daisy-chain Filter processes.
func main() {
ch := make(chan int) // Create a new channel.
go Generate(ch) // Launch Generate goroutine.
for i := 0; i < 10; i++ {
prime := <-ch
fmt.Println(prime)
ch1 := make(chan int)
go Filter(ch, ch1, prime)
ch = ch1
}
}
总结
Tpyecho 框架本身缺少的语法高亮,让代码难以阅读,更难以理解。以此需求为引子,我们调研了开源社区现有的成熟的语法高亮库,根据需求匹配度,选中了小巧轻便、开箱即用的 Prism.js。随后,作者将其集成到现有的前端代码结构中,并注明了 js 和 css 这两个,准确来说是,两类文件的常规放置位置;由此可推出,类似的前端包/插件都可以这种方式集成。最后,以 Go 代码为例进行语法高亮效果展示,为此次博客代码可读性『装修改造』画上圆满的句号。