问题背景

虽然 Typecho 原生支持 Markdown 语法,但是代码块部分只是用等宽字体+颜色置灰进行简单装饰,并不能像 Github 那样运用丰富多彩的颜色规则来高亮代码。然而没有颜色的代码,就像没有了灵魂,可读性可谓是呈断崖式下降,关键字、变量名和函数名都不分你我,共灰白一色。

我的需求

为了可读性,建站之后,代码高亮便提上日程。我梳理了自己的需求,主要有

  • 着色代码: 语法高亮
  • 多种语言:基础的包括 JS, PHP, Go, PythonC/C++ 等,后续如果要学习 RustTS 希望能方便地接入
  • 软件生态:使用的人数多不多,代码更新迭代是否勤快,Issue 处理及时性
  • 可扩展性:能否扩展小功能?如代码支持拷贝,左侧显示行数,显示所用语言
  • 方便部署:部署时会考虑代码包压缩后的大小,是否容易嵌入已有的代码结构中

可行分析

Q1: 在哪实现?

首先,确定着色代码是在前端还是后端实现?

颜色展示,这是前端的功能。从扩展性考虑,为了便于后期切换不同的代码着色方案,比如 IDE 中常见的 GithubSolarized 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 左右。

受众广泛

据官方公布的使用者,不少大厂也在用,可靠性得到了背书,代码后续维护自然不必担心了。

prism_used_by.png

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 代码为例进行语法高亮效果展示,为此次博客代码可读性『装修改造』画上圆满的句号。

添加新评论