服务端marked + highlight.js导致接口慢的问题

前言

由于博客后台只保存了markdown内容,html为后端实时渲染出来的,而目前代码高亮又是在前端渲染的,为了兼容博客前端小程序的代码高亮(之前的代码未显示高亮),在昨天把博客代码高亮从前端渲染改为了后端渲染。

import hljs = require('highlight.js');
import marked = require('marked');

marked.setOptions({
  renderer: new marked.Renderer(),
  highlight(code: string, lang: string) {
    const language = hljs.getLanguage(lang) ? lang : 'plaintext';
    return hljs.highlight(code, { language }).value;
  },
  langPrefix: 'hljs language-', // highlight.js css expects a top-level 'hljs' class.
  pedantic: false,
  gfm: true,
  breaks: false,
  sanitize: false,
  smartLists: true,
  smartypants: false,
  xhtml: false,
});

这样确实是方便了,不需要各种端额外渲染文本,但是对于服务器是一笔不小的负担,在本地(m1)上渲染一个万字文章要40ms以上,而在服务器(1核2g1m)上更久至少要200ms

而优化方式有三种:

  1. 提升服务器
  2. 服务器保存文章时保存一份渲染好的html文章
  3. 改回前端渲染高亮文本

第1种方式提升服务器选项可是要花一笔不少的钱的,自然是pass掉了。

第2种方式以空间换时间,写文章和看文章的比例差距是很大的(只有我在写),把时间压缩在提交那刻也不失为一种好的优化方式。
不过需要存放一份渲染好的文本对于数据库压力较大,一份markdown渲染成html会膨胀好几倍,备份数据库时备份会很大。而且相当于写项目git提交代码时把打包后的代码也git保存起来一样。不是很妥,pass。

这样只剩最后一种方案了。为了安全,markdown原文是不能给出的,所以获取文章时服务器还是需要用marked把文章渲染成html,而在客户端把代码高亮渲染出来。


把服务端高亮去掉后,本地接口返回万字文章只需要7ms,比之前的40ms可谓是质的提升。

前端渲染

浏览器和小程序渲染有一点不同。

浏览器端

浏览器端比较简单方便

import highlight from 'highlight.js';

const blocks = articleEl.querySelectorAll<HTMLElement>('pre code');
blocks.forEach((block) => {
  highlight.highlightBlock(block);
});

可以使用浏览器api查找出所有的code,然后用 highlight渲染一下就好了。

小程序端

小程序端由于没有浏览器那么方便的api,而且highlight.js也不支持小程序端渲染方式,好在highlight.js支持纯js渲染,而且可以使用正则查找所有的代码块。

import hljs from 'highlight.js';

Page({
  data: {
    article: '',
    articleId: ''
  },
  onLoad({ id }) {
    this.setData({ articleId: id }, () => {
      this.getArticle();
    });
  },
  async getArticle() {
    const { data: article } = await getArticleDetail(this.data.articleId);


    (article.content.match(/<code class="[^"]+">[^<]+<\/code>/gm) || []).forEach((code) => {
      const lang = /"language-([^" ]+)"/.test(code) ? RegExp.$1 : '';
      const codeContent = code.replace(/<code class="[^"]+">|\r?\n?<\/code>/g, '');

      const language = hljs.getLanguage(lang) ? lang : 'plaintext';
      // 新`highlight.js`的language和code位置是反过来的,现在`towxml`用的是旧版的
      const renderedCode = hljs.highlight(
        language,
        // 还原转译过的符号
        codeContent
          .replace(/&lt;/g, '<')
          .replace(/&gt;/g, '>')
          .replace(/&#39;/g, "'")
          .replace(/&quot;/g, '"'),
      ).value;
      article.content = article.content.replace(codeContent, renderedCode);
    });

    wx.stopPullDownRefresh();
    
    wx.setNavigationBarTitle({
      title: article.title,
    });
    this.setData({ article: article.content });
  },
});

评论

0 / 800
全部评论()