【UniApp 终极实战】手撸代码高亮阅读器 (Highlight.js + 行号渲染)
欢迎来到UniApp 实战 GitCode系列的大结局!
今天,我们迎来了整个项目中最硬核、最炫酷的功能——代码语法高亮 (Syntax Highlighting)。
想象一下,用户费劲点进了 main.js,结果看到的是黑底白字、密密麻麻的纯文本,那种心情就像吃泡面没有调料包。我们要做的,是把代码变成艺术品,支持关键词变色、行号显示、长按复制,还原 IDE 般的阅读体验!
目录
创建高亮工具类 (utils/highlighter.js)
核心挑战
在 Web 端,我们直接引入 Prism.js 或 Highlight.js 然后操作 DOM 就在行了。但在 UniApp(特别是小程序端):
-
没有 DOM: 无法直接操作
innerHTML插入带 class 的 span 标签。 -
包体积限制:
Highlight.js完整包有几百 KB,必须按需引入。 -
行号对齐: 高亮库通常只返回一坨 HTML 字符串,不带行号,我们需要自己实现“行号+代码”的对齐布局。


第一步:引入 Highlight.js (瘦身版)
不要直接 npm install highlight.js 然后引入整个包!那太大了。我们要使用 core 版本,按需注册语言。
npm install highlight.js
创建高亮工具类 (utils/highlighter.js)
为了保持组件干净,我们将高亮逻辑抽离。
// utils/highlighter.js
import hljs from 'highlight.js/lib/core';
// 1. 按需注册语言 (根据你的项目需求添加)
// 引入常用语言以减小体积
import javascript from 'highlight.js/lib/languages/javascript';
import typescript from 'highlight.js/lib/languages/typescript';
import xml from 'highlight.js/lib/languages/xml'; // HTML/Vue template
import css from 'highlight.js/lib/languages/css';
import json from 'highlight.js/lib/languages/json';
import markdown from 'highlight.js/lib/languages/markdown';
hljs.registerLanguage('javascript', javascript);
hljs.registerLanguage('typescript', typescript);
hljs.registerLanguage('xml', xml);
hljs.registerLanguage('css', css);
hljs.registerLanguage('json', json);
hljs.registerLanguage('markdown', markdown);
// 2. 导出核心函数
export default function highlight(code, lang) {
// 如果没传语言,或者语言不支持,就直接返回转义后的文本
if (!lang) return escapeHtml(code);
// 容错处理:比如传入 'vue',但我们只注册了 'xml',可以做个映射
const langMap = { 'vue': 'xml', 'js': 'javascript', 'ts': 'typescript' };
const targetLang = langMap[lang] || lang;
try {
// 检查语言是否注册
if (hljs.getLanguage(targetLang)) {
return hljs.highlight(code, { language: targetLang }).value;
}
} catch (e) {
console.warn('Highlight parsing failed', e);
}
// 兜底:返回纯文本
return escapeHtml(code);
}
// 简单的 HTML 转义,防止 XSS 或格式错乱
function escapeHtml(value) {
return value
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
第二步:引入配色主题 (CSS)
Highlight.js 生成的 HTML 标签只有 class="hljs-keyword" 这种类名,没有样式。我们需要手动引入 CSS。
推荐去 highlight.js/styles 找一个喜欢的,比如 atom-one-dark.css。
在 UniApp 中,你可以把这个 CSS 内容复制到 App.vue 的 style 中,或者作为一个单独的 code-theme.css 引入。
/* static/code-theme.css (示例:Atom One Dark) */
.hljs { color: #abb2bf; background: #282c34; display: block; overflow-x: auto; padding: 0.5em; }
.hljs-comment, .hljs-quote { color: #5c6370; font-style: italic; }
.hljs-keyword, .hljs-selector-tag { color: #c678dd; }
.hljs-string, .hljs-attribute { color: #98c379; }
.hljs-title, .hljs-section { color: #e06c75; }
/* ...更多样式... */
第三步:实现行号渲染组件 (CodeViewer.vue)
这是最显功力的地方。如果不显示行号,直接用 rich-text 渲染上面的结果就行了。但为了专业感,我们需要把代码按行切分。
布局策略: 使用 Flex 布局,左边一列显示行号,右边一列显示代码。
<template>
<view class="code-viewer">
<scroll-view scroll-x scroll-y class="code-scroll-view">
<view class="code-wrapper">
<view class="line-numbers">
<text
v-for="n in lineCount"
:key="n"
class="line-num"
>{{ n }}</text>
</view>
<view class="code-content">
<rich-text :nodes="highlightedHtml" class="code-text"></rich-text>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { computed, toRefs } from 'vue';
import highlight from '@/utils/highlighter';
const props = defineProps({
code: { type: String, default: '' },
lang: { type: String, default: 'javascript' },
fileName: { type: String, default: '' }
});
const { code, lang } = toRefs(props);
// 1. 核心计算:高亮处理
const highlightedHtml = computed(() => {
if (!code.value) return '';
// 步骤 A: 获取高亮后的 HTML 字符串
let html = highlight(code.value, lang.value);
// 步骤 B (关键优化):
// highlight.js 生成的 HTML 可能会跨行(比如一个多行注释)
// 如果我们直接按 \n 切割字符串来做每行渲染,会打断 span 标签,导致样式失效。
// 所以,这里我们采取 "整体渲染" 策略,
// 只要保持行号的高度(line-height)和代码的高度一致即可!
// 给每行代码外面包一个 div 也可以,但 rich-text 性能最好是渲染一大坨字符串
return `<div class="hljs code-body">${html}</div>`;
});
// 2. 计算行数
const lineCount = computed(() => {
if (!code.value) return 0;
return code.value.split('\n').length;
});
</script>
<style scoped>
/* 引入刚才保存的主题样式 */
@import url('@/static/code-theme.css');
.code-viewer {
background-color: #282c34; /* 背景色要和主题一致 */
min-height: 100vh;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 28rpx;
}
.code-scroll-view {
width: 100%;
height: 100vh;
}
.code-wrapper {
display: flex;
min-width: 100%;
}
/* 左侧行号样式 */
.line-numbers {
width: 80rpx;
background-color: #21252b; /* 比背景稍微深一点 */
color: #636d83;
text-align: right;
padding: 20rpx 10rpx 20rpx 0;
border-right: 1px solid #3b4048;
flex-shrink: 0; /* 防止被挤压 */
user-select: none;
}
.line-num {
display: block;
height: 40rpx; /* ⚠️ 严格控制行高 */
line-height: 40rpx;
font-size: 24rpx;
}
/* 右侧代码样式 */
.code-content {
flex: 1;
padding: 20rpx;
overflow-x: visible; /* 允许横向撑开 */
}
/* ⚠️ 深度选择器:控制 rich-text 内部样式 */
:deep(.code-body) {
line-height: 40rpx !important; /* 必须和行号高度绝对一致! */
white-space: pre; /* 保留空格和换行 */
font-family: inherit;
}
</style>
第四步:性能优化 (进阶)
如果用户打开了一个 5000 行的 bundle.js,上面的代码可能会让页面卡顿 2-3 秒。
优化方案:
-
Web Worker (强烈推荐): 高亮计算 (
hljs.highlight) 是纯 CPU 密集型任务。在小程序中,可以开启一个 Worker 线程来处理字符串高亮,主线程只负责渲染。 -
分片渲染 (Time Slicing): 不要一次性渲染 5000 行。先渲染前 100 行,用户滚动时再追加渲染(类似虚拟列表)。
-
大文件拦截: 如果检测到文件体积 > 500KB,直接提示“文件过大,请下载查看”,避免崩溃。
// 简单的大文件保护逻辑
const isTooLarge = computed(() => props.code.length > 100000);
最终集成
回到上一期创建的 read.vue,把刚写好的组件放进去:
<template>
<view>
<CodeViewer
v-if="!loading"
:code="fileContent"
:lang="fileExtension"
:fileName="fileName"
/>
</view>
</template>
<script setup>
// ... 获取 fileExtension 的逻辑 ...
// const fileExtension = fileName.value.split('.').pop();
</script>
全系列总结
恭喜你!坚持看完了整个实战系列。从API封装到递归目录,从Markdown渲染到代码高亮,你现在手里已经拥有了一个功能完备的“口袋代码阅读器”的核心源码。
这不仅仅是一个 Demo,它是你迈向高级前端开发的敲门砖。
如果你把这套项目写进简历,记得加上这几个关键词:Vue 3 Composition API、UniApp 跨端开发、递归组件优化、虚拟 DOM 与原生渲染性能调优。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)