众所周知,实现代码语法高亮使用最多的便是highlight.js
库,而其也实现了Angular
版本,然而,其却有不少的问题:
- Demo和网上的方案大多是静态内容(或同步方式,即内容在服务端生成后随页面一同返回)的渲染,对动态内容渲染的支持并不友好。
- 即便实现了动态内容的渲染,在页面前后跳转时(前进、后退,此时
SSR
环境下是异步方式获取文章内容,即ngAfterViewInit
只执行一次)却无法更新渲染内容重新渲染。 - 在code只有一行时无法强制显示代码行数,虽然API中的
options
提供了此选项。 - 无法优雅地自定义显示代码行数时的HTML结构(官方实现是table方式)和预览样式。
- 特殊字符的转义Bug。
综上,因此,需要自行想办法解决诸此种种问题。
一种方式是使用MutationObserver
监听DOM
的变化,但却有种种弊端和不便,且难免引入性能问题。
另一种方式便是本文的重点——使用正则表达式自行解析文章HTML内容。这种方式的好处不言自明了,既解决了动态内容的渲染问题,也避免了事件监听引发的一连串问题,还可以轻松定制HTML结构和样式,可谓一箭三雕、一石三鸟。
以下便是实现代码:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
this.post.postContent = this.post.postContent.replace(
/<pre(?:\s+[^>]*)*>\s*<code(?:\s+[^>]*)?>([\s\S]*?)<\/code>\s*<\/pre>/ig,
(preStr, codeStr: string) => {
const langReg = /^<pre(?:\s+[^>]*)*\s+class="([^"]+)"(?:\s+[^>]*)*>/ig;
const langResult = Array.from(preStr.matchAll(langReg));
let langStr = '';
let language = '';
if (langResult.length > 0 && langResult[0].length === 2) {
const langClass = langResult[0][1].split(/\s+/i).filter(
(item) => item.split('-')[0].toLowerCase() === 'language'
);
if (langClass.length > 0) {
langStr = langClass[0].split('-')[1] || '';
if (langStr && highlight.getLanguage(langStr)) {
language = langStr;
}
}
}
// unescape: ><&
codeStr = codeStr.replace(/</ig, '<')
.replace(/>/ig, '>')
.replace(/&/ig, '&');
const lines = codeStr.split(/\r\n|\r|\n/i).map((str, i) => `<li>${i + 1}</li>`).join('');
const codes = language
? highlight.highlight(codeStr, { language }).value
: highlight.highlightAuto(codeStr).value;
return `<pre class="i-code"${langStr ? ' data-lang="' + langStr + '"' : ''}>` +
`<ul class="i-code-lines">${lines}</ul>` +
`<code class="i-code-text">${codes}</code></pre>`;
}
);
简要介绍下代码要点:
- 使用惰性匹配获取所有
pre>code
标签,将其替换为highlight
渲染后的HTML。 - 替换时查询代码使用的编程语言class,作为渲染的参数之一。
- 转义特殊字符。
- 解析代码行数,并生成HTML。
- 组装完整的渲染后的HTML。
代码的关键便是几处正则表达式的使用,尤其是惰性匹配、捕获组、非捕获组的概念的使用。具体定义和Demo本文不再赘述,读者可自行查阅官方文档。
最后,一点感慨和收获……
代码写的越多,越发现正则作为效率利器,真是实至名归。如今,各大IDE的查找、替换,甚至包括office等都支持正则表达式了,熟悉并加以善用,节省的时间不是一星半点——其应用已远远超出编程、开发的范畴,以至各种实际的生活、办公场景。
另,不得不感慨下ES6,以上代码倘若不是用ES6,而是用传统的ES5甚至更原始版本的引擎的话,不知要增加多少代码行数和工作量。┓( ´∀` )┏