/**
* js 语法高亮组件,适合在文章中演示代码片段
*/
import React from 'react'
import styles from './HighlightSyntax.module.css'
interface HighlightSyntaxProps {
code: string
}
// JavaScript/TypeScript 关键字
const keywords =
'async|await|function|const|let|var|if|else|for|while|return|try|catch|finally|class|extends|from|import|export|default|undefined|throw|break|continue|switch|case|do|with|yield|delete|typeof|void|static|get|set|super|debugger'
// TypeScript 特定关键字
const tsKeywords =
'interface|type|enum|namespace|module|declare|abstract|implements|public|private|protected|readonly|as|satisfies|infer|keyof|is'
// 布尔值和空值
const literals = 'true|false|null|undefined|NaN|Infinity'
// TypeScript 内置类型
const tsTypes =
'string|number|boolean|any|unknown|never|void|object|symbol|bigint|Array|Promise|Record|Partial|Required|Readonly|Pick|Omit|Exclude|Extract|NonNullable|ReturnType|Parameters|ConstructorParameters|InstanceType|ThisType|Uppercase|Lowercase|Capitalize|Uncapitalize'
// 辅助函数:转义 HTML 特殊字符
function escapeHtml(text: string): string {
return text.replace(/&/g, '&').replace(//g, '>')
}
// 语法高亮函数,先提取 token 再转义和高亮
function highlightSyntax(code: string): string {
// 构建正则模式,包含更多 token 类型(在原始文本上匹配)
const pattern = new RegExp(
'(' +
// 1. 字符串(双引号、单引号、模板字符串)
'"([^"\\\\]|\\\\.)*"|' +
"'([^'\\\\]|\\\\.)*'|" +
'`([^`\\\\]|\\\\.)*`|' +
// 2. 注释(单行和多行)
'//[^\\n]*|' +
'/\\*[\\s\\S]*?\\*/|' +
// 3. 装饰器
'@[a-zA-Z_$][\\w$]*|' +
// 4. 数字(包括小数、十六进制、科学计数法)
'\\b0[xX][0-9a-fA-F]+\\b|' +
'\\b\\d+\\.?\\d*(?:[eE][+-]?\\d+)?\\b|' +
// 5. TypeScript/JavaScript 关键字
'\\b(?:' +
keywords +
'|' +
tsKeywords +
'|' +
literals +
')\\b|' +
// 6. TypeScript 内置类型
'\\b(?:' +
tsTypes +
')\\b|' +
// 7. 箭头函数
'=>|' +
// 8. 函数调用(函数名后跟括号)
'\\b[a-zA-Z_$][\\w$]*(?=\\()|' +
// 9. 属性访问
'\\.[a-zA-Z_$][\\w$]*|' +
// 10. 运算符和特殊符号
'[+\\-*/%&|^!~<>=?:]+|' +
'[{}\\[\\]();,.]' +
')',
'g'
)
const tokens: string[] = []
let lastIndex = 0
let match: RegExpExecArray | null
while ((match = pattern.exec(code)) !== null) {
if (match.index > lastIndex) {
const gap = code.slice(lastIndex, match.index)
// 将间隙按空白符分割,保留空白符
tokens.push(...gap.split(/(\s+)/))
}
tokens.push(match[0])
lastIndex = pattern.lastIndex
}
if (lastIndex < code.length) {
tokens.push(...code.slice(lastIndex).split(/(\s+)/))
}
const highlighted = tokens
.map((token) => {
// 空白符直接返回
if (/^\s+$/.test(token)) {
return token
}
// 1. 注释(单行和多行)
if (/^\/\/.*$/.test(token) || /^\/\*[\s\S]*?\*\/$/.test(token)) {
return ``
}
// 2. 字符串
if (
/^"([^"\\]|\\.)*"$/.test(token) ||
/^'([^'\\]|\\.)*'$/.test(token) ||
/^`([^`\\]|\\.)*`$/.test(token)
) {
return `${escapeHtml(token)}`
}
// 3. 数字
if (/^(0[xX][0-9a-fA-F]+|\d+\.?\d*(?:[eE][+-]?\d+)?)$/.test(token)) {
return `${escapeHtml(token)}`
}
// 4. 布尔值和特殊字面量
if (new RegExp(`^(?:${literals})$`).test(token)) {
return `${escapeHtml(token)}`
}
// 5. JavaScript/TypeScript 关键字
if (new RegExp(`^(?:${keywords})$`).test(token)) {
return `${escapeHtml(token)}`
}
// 6. TypeScript 特定关键字
if (new RegExp(`^(?:${tsKeywords})$`).test(token)) {
return `${escapeHtml(token)}`
}
// 7. TypeScript 内置类型
if (new RegExp(`^(?:${tsTypes})$`).test(token)) {
return `${escapeHtml(token)}`
}
// 8. 装饰器
if (/^@[a-zA-Z_$][\w$]*$/.test(token)) {
return `${escapeHtml(token)}`
}
// 9. 箭头函数
if (token === '=>') {
return `${escapeHtml(token)}`
}
// 10. 函数调用和标识符
if (/^[a-zA-Z_$][\w$]*$/.test(token)) {
return `${escapeHtml(token)}`
}
// 11. 属性访问
if (/^\.[a-zA-Z_$][\w$]*$/.test(token)) {
return `${escapeHtml(token)}`
}
// 12. 运算符
if (/^[+\-*/%&|^!~<>=?:]+$/.test(token)) {
return `${escapeHtml(token)}`
}
// 13. 其他符号,需要转义
return escapeHtml(token)
})
.join('')
return highlighted
}
const HighlightSyntaxClient: React.FC = ({ code }) => {
const htmlContent = highlightSyntax(code)
// eslint-disable-next-line react-dom/no-dangerously-set-innerhtml
return
}
export default HighlightSyntaxClient