返回

Comi —— 把 markdown 代码搞起来!

前端

Comi 读 ['kəʊmɪ],类似中文 科米,是腾讯 Omi 团队开发的小程序代码高亮和 markdown 渲染组件。有了这个组件加持,小程序技术社区可以开始搞起来了。感谢【小程序•云开发】提供技术支持。

先看 Comi 使用,再分析原理。

先拷贝此目录到你的项目。

先明确,markdown是一种写作语言。它介于自然语言和 HTML之间,其目标是实现易读易写,无论何时何地,Markdown 的文件都可以通过简单的纯文本编辑器打开。

在不加任何修改的情况下:

// pages/index/index.js

Component({
  options: {
    styleIsolation: 'shared',
  },
  properties: {},
  data: {
    value: `
      ```ts
      if (true) {
        console.log('true!');
      } else if (false) {
        console.log('false!');
      } else {
        console.log('else!');
      }
      ```
    `,
  },
  methods: {
    toggleValue() {
      this.setData({
        value: this.data.value === '===' ? '===' : '',
      })
    },
  },
})
<!-- pages/index/index.wxml -->
<view class="container">
  <view class="comi-wrapper">
    <comi-code lang="ts">
      {{value}}
    </comi-code>
  </view>
  <button class="comi-toggle" type="primary" bindtap="toggleValue">Toggle</button>
</view>
/* pages/index/index.wxss */

page {
  background: #f5f5f5;
}

.container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  width: 100%;
}

.comi-wrapper {
  width: 100%;
  max-width: 500px;
}

.comi-toggle {
  margin: 10px 0 0 10px;
}

实现原理

markdown 渲染原理

接下来,我们看看原理。
先看代码高亮,代码高亮首先会通过 regex 解析出 language tag,

// comi/src/utils/regex.ts
// 匹配 HTML 注释中的语言定义标签
const REGEXP_MATCH_LANG_TAG = /<!--\s+lang:(\S+)\s+-->/

接着,会把语言定义 tag 转换称 render 函数,

// comi/src/utils/lang-tags.ts
/**
 * 将语言标签转换为 render 函数
 */
const getRenderByLang = (lang: string): ((code: string) => string) | undefined => {
  // 根据语言标签,从 prism 仓库获取高亮渲染函数
  const prismRenderFunc = prism.languages[lang]
  if (prismRenderFunc) {
    return (code: string) => {
      // 返回渲染函数执行结果
      return prismRenderFunc(code, lang)
    }
  }
}

接下来,把 markdown 文本通过 marked 转换成 HTML 标签,再由 language-pack 匹配高亮代码的语言 tag,转换成 render 函数,最后执行渲染函数获得最终的渲染后的 HTML 代码,

// comi/src/main.ts
const findLangRender = (code: string) => {
  // 匹配 HTML 注释中的语言定义标签
  const langTagMatch = REGEXP_MATCH_LANG_TAG.exec(code)
  // 若没有语言定义标签,则默认使用 'text' 语言
  if (!langTagMatch) return getRenderByLang('text')
  // 转换语言定义标签为 render 函数
  const langTag = langTagMatch[1]
  return getRenderByLang(langTag)
}
// comi/src/main.ts
export const getHtmlFromMarkdown = (markdown: string) => {
  // 使用 marked 将 markdown 转换为 HTML
  let html = marked(markdown)
  // 查找并替换语言定义标签中的语言标签,并使用 language-pack 来渲染代码高亮
  const getLangRenderByLangTag = findLangRender
  html = html.replace(/<pre><code>\s*<!--\s+lang:(\S+)\s+-->\s*(.*?)\s*<\/code><\/pre>/g, (match, langTag, code) => {
    // 使用 prism 渲染代码高亮
    const renderFunc = getLangRenderByLangTag(langTag)
    if (!renderFunc) return match
    const highlightedHtml = renderFunc(code)
    // 将高亮后的 HTML 代码替换原有的 HTML 代码
    return `<pre><code>${highlightedHtml}</code></pre>`
  })
  // 返回渲染后的 HTML 代码
  return html
}

代码高亮原理

代码高亮的实现步骤如下:

  1. 匹配语言定义标签,例如 <!--s+lang:ts+-->
  2. 将语言定义标签转换成渲染函数。
  3. 将markdown文本通过marked转换成HTML标签。
  4. 通过language-pack匹配高亮代码的语言tag,转换成render函数。
  5. 执行渲染函数获得最终的渲染后的HTML代码。

代码高亮的原理就是通过正则表达式匹配出语言定义标签,然后将语言定义标签转换成渲染函数,最后执行渲染函数获得最终的渲染后的HTML代码。

小结

Comi 是一个小程序代码高亮和 markdown 渲染组件,它使用 marked 和 prism 来实现 markdown 的渲染和代码高亮的渲染,具体的实现原理如下:

  1. markdown 渲染:使用 marked 将 markdown 转换为 HTML。
  2. 代码高亮:通过正则表达式匹配出语言定义标签,然后将语言定义标签转换成渲染函数,最后执行渲染函数获得最终的渲染后的HTML代码。