在这里插入图片描述

目录

  1. 功能需求分析
  2. 技术选型对比
  3. 代码高亮组件实现
  4. Markdown 渲染实现
  5. 性能优化策略
  6. 实际效果展示
  7. 总结与优化建议

一、功能需求分析

1.1 核心功能

在开发 AtomGit APP 时,代码高亮和 Markdown 渲染是最核心的功能之一。用户需要能够流畅地浏览代码文件,并且能够阅读格式化的 Markdown 文档。具体需求包括:

代码高亮需求:

  • 支持主流编程语言(JavaScript、Python、Java、Go、Rust 等 100+ 种语言)
  • 代码行号显示,方便定位和讨论
  • 一键复制代码功能,提升开发效率
  • 支持主题切换(明暗模式)

Markdown 渲染需求:

  • 支持 GitHub Flavored Markdown(GFM)规范
  • 正确渲染代码块、表格、列表等复杂元素
  • 支持数学公式、流程图等扩展语法
  • 防止 XSS 攻击,确保内容安全

1.2 用户体验目标

根据用户反馈和行业最佳实践,我们设定了以下体验目标:

  • 代码渲染速度 < 500ms(针对 1000 行以内的文件)
  • 支持 100+ 种编程语言
  • 内存占用优化,避免大文件导致应用卡顿
  • 主题切换流畅,无闪烁

二、技术选型对比

2.1 代码高亮库对比

在技术选型阶段,我们对比了主流的代码高亮库:

特性 Prism.js Highlight.js Shiki
体积 小(按需加载) 中等
语言支持 200+ 190+ 180+
主题 丰富(8 个官方主题) 较多 VSCode 主题
插件生态 丰富 中等
性能 优秀 良好 优秀
浏览器兼容性 优秀 优秀 中等

经过综合评估,我们选择 Prism.js,原因如下:

  1. 体积优势:支持按需加载语言包,初始加载体积小
  2. 插件丰富:提供行号显示、工具栏、复制按钮等插件
  3. 性能优异:纯 JavaScript 实现,渲染速度快
  4. 社区活跃:文档完善,问题容易解决

2.2 Markdown 解析器对比

特性 marked.js markdown-it remark
体积 中等
性能 中等 中等
扩展性 优秀 优秀
GFM 支持 需插件 需插件
学习曲线

我们选择 marked.js,因为它体积小、性能好、易于集成。同时配合 DOMPurify 进行 HTML 净化,确保安全性。


三、代码高亮组件实现

3.1 基础组件封装

首先,我们创建一个可复用的代码高亮组件 components/code-highlight.vue

<template>
  <view class="code-container">
    <!-- 代码头部:语言标签和复制按钮 -->
    <view class="code-header">
      <view class="language-tag">
        <text class="language-name">{{ language }}</text>
        <text class="line-count">{{ lineCount }} 行</text>
      </view>
      <button class="copy-btn" @click="handleCopy">
        <text class="btn-icon">{{ copyIcon }}</text>
        <text class="btn-text">{{ copyText }}</text>
      </button>
    </view>
    
    <!-- 代码内容区域 -->
    <scroll-view class="code-wrapper" scroll-x="true">
      <view class="code-content">
        <rich-text :nodes="highlightedCode"></rich-text>
      </view>
    </scroll-view>
  </view>
</template>

<script>
import Prism from 'prismjs'
import 'prismjs/themes/prism-tomorrow.css'
// 导入行号插件
import 'prismjs/plugins/line-numbers/prism-line-numbers.css'
import 'prismjs/plugins/line-numbers/prism-line-numbers.js'

export default {
  name: 'CodeHighlight',
  
  props: {
    // 代码内容
    code: {
      type: String,
      required: true
    },
    // 编程语言
    language: {
      type: String,
      default: 'javascript'
    },
    // 是否显示行号
    showLineNumbers: {
      type: Boolean,
      default: true
    }
  },
  
  data() {
    return {
      copyText: '复制',
      copyIcon: '📋'
    }
  },
  
  computed: {
    // 高亮后的代码
    highlightedCode() {
      if (!this.code) return ''
      
      try {
        const grammar = Prism.languages[this.language] || Prism.languages.javascript
        const highlighted = Prism.highlight(this.code, grammar, this.language)
        
        // 如果需要行号,添加 line-numbers 类
        if (this.showLineNumbers) {
          return `<pre class="line-numbers"><code class="language-${this.language}">${highlighted}</code></pre>`
        }
        
        return `<pre><code class="language-${this.language}">${highlighted}</code></pre>`
      } catch (error) {
        console.error('代码高亮失败:', error)
        return `<pre><code>${this.code}</code></pre>`
      }
    },
    
    // 代码行数
    lineCount() {
      return this.code ? this.code.split('\n').length : 0
    }
  },
  
  methods: {
    // 复制代码
    async handleCopy() {
      try {
        await uni.setClipboardData({
          data: this.code
        })
        
        this.copyText = '已复制'
        this.copyIcon = '✅'
        
        uni.showToast({
          title: '代码已复制',
          icon: 'success',
          duration: 2000
        })
        
        // 2秒后恢复
        setTimeout(() => {
          this.copyText = '复制'
          this.copyIcon = '📋'
        }, 2000)
      } catch (error) {
        console.error('复制失败:', error)
        uni.showToast({
          title: '复制失败',
          icon: 'none'
        })
      }
    }
  }
}
</script>

3.2 动态语言加载

为了优化初始加载性能,我们实现了按需加载语言包的功能:

// utils/language-loader.js

// 语言映射表
const languageMap = {
  javascript: () => import('prismjs/components/prism-javascript'),
  js: () => import('prismjs/components/prism-javascript'),
  typescript: () => import('prismjs/components/prism-typescript'),
  ts: () => import('prismjs/components/prism-typescript'),
  python: () => import('prismjs/components/prism-python'),
  py: () => import('prismjs/components/prism-python'),
  java: () => import('prismjs/components/prism-java'),
  go: () => import('prismjs/components/prism-go'),
  rust: () => import('prismjs/components/prism-rust'),
  c: () => import('prismjs/components/prism-c'),
  cpp: () => import('prismjs/components/prism-cpp'),
  'c++': () => import('prismjs/components/prism-cpp'),
  css: () => import('prismjs/components/prism-css'),
  html: () => import('prismjs/components/prism-markup'),
  vue: () => import('prismjs/components/prism-vue'),
  react: () => import('prismjs/components/prism-jsx'),
  json: () => import('prismjs/components/prism-json'),
  yaml: () => import('prismjs/components/prism-yaml'),
  markdown: () => import('prismjs/components/prism-markdown'),
  bash: () => import('prismjs/components/prism-bash'),
  shell: () => import('prismjs/components/prism-bash')
}

// 已加载的语言缓存
const loadedLanguages = new Set()

/**
 * 动态加载语言包
 * @param {string} language - 语言名称
 * @returns {Promise<boolean>} - 加载结果
 */
export async function loadLanguage(language) {
  const normalizedLang = language.toLowerCase()
  
  // 如果已经加载过,直接返回
  if (loadedLanguages.has(normalizedLang)) {
    return true
  }
  
  // 查找加载函数
  const loader = languageMap[normalizedLang]
  if (!loader) {
    console.warn(`不支持的语言: ${language}`)
    return false
  }
  
  try {
    await loader()
    loadedLanguages.add(normalizedLang)
    console.log(`语言包加载成功: ${language}`)
    return true
  } catch (error) {
    console.error(`语言包加载失败: ${language}`, error)
    return false
  }
}

/**
 * 批量加载语言包
 * @param {string[]} languages - 语言列表
 */
export async function loadLanguages(languages) {
  const promises = languages.map(lang => loadLanguage(lang))
  await Promise.allSettled(promises)
}

3.3 使用示例

在页面中使用代码高亮组件:

<template>
  <view class="code-page">
    <code-highlight 
      :code="codeContent" 
      :language="language"
      :show-line-numbers="true"
    />
  </view>
</template>

<script>
import CodeHighlight from '@/components/code-highlight.vue'
import { loadLanguage } from '@/utils/language-loader'

export default {
  components: {
    CodeHighlight
  },
  
  data() {
    return {
      codeContent: '',
      language: 'javascript'
    }
  },
  
  async onLoad(options) {
    // 获取文件内容
    this.codeContent = await this.fetchFileContent(options.fileUrl)
    this.language = this.detectLanguage(options.fileName)
    
    // 动态加载语言包
    await loadLanguage(this.language)
  },
  
  methods: {
    // 检测语言类型
    detectLanguage(fileName) {
      const ext = fileName.split('.').pop().toLowerCase()
      const langMap = {
        js: 'javascript',
        ts: 'typescript',
        py: 'python',
        java: 'java',
        go: 'go',
        rs: 'rust'
      }
      return langMap[ext] || 'javascript'
    }
  }
}
</script>

四、Markdown 渲染实现

4.1 基础配置

首先安装必要的依赖:

npm install marked dompurify

创建 Markdown 渲染组件 components/markdown-renderer.vue

<template>
  <view class="markdown-container">
    <rich-text :nodes="renderedContent" class="markdown-content"></rich-text>
  </view>
</template>

<script>
import { marked } from 'marked'
import DOMPurify from 'dompurify'

// 配置 marked
marked.setOptions({
  breaks: true,          // 支持 GitHub 风格的换行
  gfm: true,            // 启用 GitHub Flavored Markdown
  headerIds: true,      // 为标题添加 ID
  mangle: false,        // 不混淆邮箱地址
  sanitize: false       // 使用 DOMPurify 进行净化
})

export default {
  name: 'MarkdownRenderer',
  
  props: {
    content: {
      type: String,
      required: true
    }
  },
  
  computed: {
    renderedContent() {
      if (!this.content) return ''
      
      try {
        // 解析 Markdown
        const rawHtml = marked(this.content)
        
        // 使用 DOMPurify 净化 HTML,防止 XSS 攻击
        const cleanHtml = DOMPurify.sanitize(rawHtml, {
          ALLOWED_TAGS: [
            'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
            'p', 'br', 'hr',
            'ul', 'ol', 'li',
            'blockquote', 'pre', 'code',
            'strong', 'em', 'del', 'ins',
            'a', 'img',
            'table', 'thead', 'tbody', 'tr', 'th', 'td',
            'div', 'span'
          ],
          ALLOWED_ATTR: [
            'href', 'src', 'alt', 'title', 'class', 'id',
            'target', 'rel'
          ]
        })
        
        return cleanHtml
      } catch (error) {
        console.error('Markdown 渲染失败:', error)
        return `<p>${this.content}</p>`
      }
    }
  }
}
</script>

4.2 样式优化

为了提供更好的阅读体验,我们需要为 Markdown 内容添加样式:

<style lang="scss" scoped>
.markdown-container {
  padding: 32rpx;
  background-color: #ffffff;
  line-height: 1.8;
  font-size: 28rpx;
  color: #333333;
  
  .markdown-content {
    // 标题样式
    ::v-deep h1 {
      font-size: 48rpx;
      font-weight: bold;
      margin: 40rpx 0 20rpx;
      padding-bottom: 16rpx;
      border-bottom: 2rpx solid #e0e0e0;
    }
    
    ::v-deep h2 {
      font-size: 40rpx;
      font-weight: bold;
      margin: 32rpx 0 16rpx;
    }
    
    ::v-deep h3 {
      font-size: 36rpx;
      font-weight: bold;
      margin: 24rpx 0 12rpx;
    }
    
    // 段落样式
    ::v-deep p {
      margin: 16rpx 0;
      text-align: justify;
    }
    
    // 代码块样式
    ::v-deep code {
      background-color: #f5f5f5;
      padding: 4rpx 8rpx;
      border-radius: 4rpx;
      font-family: 'Courier New', Courier, monospace;
      font-size: 24rpx;
      color: #e83e8c;
    }
    
    ::v-deep pre {
      background-color: #2d2d2d;
      padding: 24rpx;
      border-radius: 8rpx;
      overflow-x: auto;
      margin: 24rpx 0;
      
      code {
        background: none;
        padding: 0;
        color: #f8f8f2;
      }
    }
    
    // 列表样式
    ::v-deep ul, ::v-deep ol {
      padding-left: 48rpx;
      margin: 16rpx 0;
    }
    
    ::v-deep li {
      margin: 8rpx 0;
    }
    
    // 引用块样式
    ::v-deep blockquote {
      border-left: 8rpx solid #007AFF;
      padding-left: 24rpx;
      margin: 24rpx 0;
      color: #666666;
      background-color: #f9f9f9;
      padding: 16rpx 24rpx;
      border-radius: 0 8rpx 8rpx 0;
    }
    
    // 链接样式
    ::v-deep a {
      color: #007AFF;
      text-decoration: none;
      
      &:hover {
        text-decoration: underline;
      }
    }
    
    // 表格样式
    ::v-deep table {
      width: 100%;
      border-collapse: collapse;
      margin: 24rpx 0;
      
      th, td {
        border: 1rpx solid #e0e0e0;
        padding: 12rpx 16rpx;
        text-align: left;
      }
      
      th {
        background-color: #f5f5f5;
        font-weight: bold;
      }
      
      tr:nth-child(even) {
        background-color: #fafafa;
      }
    }
    
    // 图片样式
    ::v-deep img {
      max-width: 100%;
      height: auto;
      display: block;
      margin: 24rpx auto;
      border-radius: 8rpx;
    }
    
    // 分割线样式
    ::v-deep hr {
      border: none;
      border-top: 2rpx solid #e0e0e0;
      margin: 32rpx 0;
    }
  }
}
</style>

五、性能优化策略

5.1 虚拟滚动优化

对于大文件(超过 1000 行),我们使用虚拟滚动技术优化性能:

// 计算可视区域
const calculateVisibleRange = (scrollTop, itemHeight, visibleCount, totalCount) => {
  const startIndex = Math.floor(scrollTop / itemHeight)
  const endIndex = Math.min(startIndex + visibleCount, totalCount)
  
  return { startIndex, endIndex }
}

// 渲染可视区域的代码行
const renderVisibleLines = (startIndex, endIndex, allLines) => {
  return allLines.slice(startIndex, endIndex)
}

5.2 缓存机制

为了提升二次加载速度,我们实现了代码缓存:

// utils/code-cache.js
class CodeCache {
  constructor() {
    this.cache = new Map()
    this.maxSize = 50 // 最多缓存 50 个文件
  }
  
  set(key, value) {
    if (this.cache.size >= this.maxSize) {
      // 删除最早的缓存
      const firstKey = this.cache.keys().next().value
      this.cache.delete(firstKey)
    }
    this.cache.set(key, value)
  }
  
  get(key) {
    return this.cache.get(key)
  }
  
  has(key) {
    return this.cache.has(key)
  }
  
  clear() {
    this.cache.clear()
  }
}

export const codeCache = new CodeCache()

5.3 懒加载语言包

只在用户打开特定语言的代码文件时,才加载对应的语言包:

// 在组件中使用
async mounted() {
  const lang = this.detectLanguage(this.fileName)
  
  // 先显示未高亮的代码
  this.displayCode = this.code
  
  // 异步加载语言包并高亮
  await loadLanguage(lang)
  this.highlightCode()
}

六、实际效果展示

6.1 代码高亮效果

通过 Prism.js,我们实现了专业的代码高亮效果,支持多种主题切换:

  • 明亮主题:适合白天阅读
  • 暗黑主题:适合夜间阅读,减少眼睛疲劳

6.2 Markdown 渲染效果

正确渲染了以下复杂元素:

  • 多级标题、列表、引用
  • 代码块(带语法高亮)
  • 表格、链接、图片
  • 数学公式(通过扩展插件)

七、总结与优化建议

本文详细介绍了 AtomGit APP 中代码高亮与 Markdown 渲染功能的实现。通过 Prism.js 和 marked.js 的深度集成,我们实现了专业级的代码阅读体验。

7.1 核心要点

  1. 技术选型:选择 Prism.js 和 marked.js,平衡了体积、性能和功能
  2. 组件封装:创建了可复用的代码高亮和 Markdown 渲染组件
  3. 动态加载:按需加载语言包,优化初始加载性能
  4. 安全防护:使用 DOMPurify 防止 XSS 攻击
  5. 性能优化:通过虚拟滚动、缓存机制提升大文件渲染性能

7.2 后续优化方向

  • 支持更多编程语言和主题
  • 优化超大文件(10000+ 行)的渲染性能
  • 添加代码折叠、搜索等高级功能
  • 支持自定义主题配置

7.3 参考资料

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐