Yank Note 是我编写的一款面向程序员的笔记利用。这里我将会写下一些对于 Yank Note 的文章
前言
在 Yank Note 开发初期,根本没思考过性能这方面的问题。前面因为一些性能问题影响到了交互体验,开始着手优化性能。
这里我将形容为性能优化所做的工作:
- 引入虚构 DOM
- 提早简单内容渲染
- 微调用户体验
引入虚构 DOM
Yank Note 采纳 Markdown-it 做解析器。Markdown-it 默认应用 HTML 输入。因而在 Yank Note 3.0 以前,都是采纳给渲染容器 innerHTML 间接赋值的形式来做渲染。
从上图能够看到,HTML 直出毛病很显著,每一次编辑文本,都要走一遍渲染流程,从新构建的 HTML。在每一次打字编辑的时候,都会造成页面从新布局,一些嵌入的性能如脑图、小工具也会重头渲染,导致页面闪动,用户体验差。
所以这里须要想方法做增量渲染,即在编辑时候,只渲染变动局部。
考查了一些增量渲染计划,如 markdown-it-incremental-dom。因为自身这个我的项目应用 Vue,用 Vue 也能更好和 Vue 组件配合工作,所以前面决定应用 Vue 虚构 DOM 的形式来做增量渲染。
收益
从交互角度,分为两个局部来探讨:
- 首次渲染: 关上一个新的文件,HTML 直出性能和 虚构 DOM 相差不大,或者比虚构 DOM 还好一些,毕竟省略了虚构节点结构的流程。
- 编辑渲染: 编辑文件,生成的 Vue VNode 虚构 DOM 来,后续的渲染均由 Vue 优化接管,做增量渲染,这一点就比 HTML 性能高了太多了。
增量渲染的益处不仅仅是在编辑时候有更高的渲染性能,也能更好的反对 Vue 组件,优化如脑图、HTML 小工具等的交互体验。
具体实现
1. Token 流转换
在这个文件里,我将 Markdown-it 默认的 HTML 渲染器,替换为了本人的渲染器。
Markdown-it parse 后产生的是 token 流而不是语法树,token nesting 属性示意层级关系(-1 敞开标签,0 自闭合,1 开标签)。
因而在 token 流遍历过程中,我构建了一个栈来把 token 流转换为 VNode 树。对于开标签和自闭合标签,产生一个新的 VNode 节点,开标签入栈;对于闭标签,出栈上一个 VNode。
这对于大部分的渲染场景是足够了。
2. 插件兼容
一些 Markdown-it 插件定义了渲染办法,会输入 HTML 字符串。自定义渲染器还是要能解决他们,不然就不能享受 Markdown-it 的生态劣势了。
参考张鑫旭大牛的文章 盘点 HTML 字符串转 DOM 的各种办法及细节,通过衡量还是抉择了应用 innerHTML 的办法,足以笼罩大部分场景了。
3. HTML 解析
如果不进行 HTML 解析,那么下面的步骤是足够了。然而 Yank Note 作为一款异样凋谢的 Markdown 编辑器,怎么能不放开 HTML 渲染呢?
Markdown-it 解析器是反对 HTML 的,然而这些 HTML 根本就是原样输入的,也就说,Markdown-it 对于这些 HTML,从技术上都当作了一般文本一样,基本没解析成有构造的 token。
因而我参照原有得 HTML 解析器,写了一个新的 HTML 解析器。具体逻辑都在这个文件外面。
相比原有的全面 HTML 反对,这个解析器不能做到任意地位写 HTML,然而大部分状况下是能够满足了。
总结
引入虚构 DOM,相当于对 Markdown-it 原有 render 做了一次大手术。一些非凡状况可能不能笼罩到,有问题的中央也能躲避,总体来说收益较大。
提早简单内容渲染
先次要关注首次渲染,即用户关上文件后,第一次将 Markdown 渲染进去的过程。
我创立了一个性能测试文件,内容是 Yank Note 的 Features 文档,复制粘贴后有 3600 行,渲染后有大量的简单 Dom 构造。
我的电脑配置是 M1 芯片,8G 内存。再 dev 模式下开启利用,应用 Chrome 性能面板录制后果如下
几乎太可怕了,从关上这个文件到展现进去居然要 7 秒钟!
性能优化上,有这样一句话:“ 你不能让计算机变得更快,你只能让它少做一点事 ”。
然而在波及用户体验的优化上,我本人也总结了一句话:“ 如果你不能让计算机少做一点事,那么想方法让用户感知不到计算机正在做事 ”。
所以我这里做的优化次要是把自定义的一些简单性能如脑图,小工具,都提早渲染,不要阻塞渲染次要内容。尽管前面可能会造成页面从新布局,但都是块元素的化这个问题会加重很多。
优化后,能在一秒内展现出内容了。
微调用户体验
这里次要关注编辑时,从用户输出文字到渲染的过程。
编辑这一部分是 Monaco 编辑器做的事,自身 Monaco 就曾经做得很好了,无需操心。
渲染这块,下面曾经引入了虚构 DOM。个别的文件没什么问题,从按键到渲染进去只须要几毫秒。Markdown 解析过程,一般来说 1 毫秒都用不到。
然而如果编辑一个超大的文件,这里的 Markdown 解析老本就不可疏忽了。
再创立一个性能测试文件,内容是 Yank Note 的 Readme 文档,复制粘贴后有一万行。
看一下 Markdown 解析工夫
解析这个一万行的文件,parse 过程都须要 100ms。
这个解析工夫临时我想不到什么方法来优化,所以那就优化用户体验吧。
未优化前,我是应用一个固定工夫距离的防抖办法来做渲染。在大文件中,这个防抖距离就显得不够用了,打字十分卡。
那么就把渲染防抖函数的等待时间设置为动静的吧。记录 render 工夫,而后动静更新 debounce 工夫。小文件设置短一点,大文件长一点。在编辑大文件时,优先保障输出体验。
另外针对中文输出的状况,监听编辑器 composition 事件,在打字时候暂停渲染,这也能晋升一部分的用户体验。
通过下面的解决和其余一些微调的优化后,在这个测试文件内打字,尽管还是有些卡顿,但状况好了不少。
优化浏览器渲染
我再次创立了一个两万行,60 万个字符的测试文件,编辑卡顿的状况进一步加剧了。
然而 Yank Note 采纳的是 Monaco 编辑器,实践上说文字编辑性能应该会和 VSCode 统一,然而我用 VScode 编辑这个两万行的文件,简直没有卡顿,所以问题该当呈现在我的代码上。
再次应用 Chrome 性能面板,排查问题。发现每一次按键后,浏览器破费了大量工夫在布局上。
当我尝试用 Chrome 的层面板性能来看渲染时候,可怜的事件产生了,页面居然解体了。可能页面元素太多,调试器扛不住了吧。
那就猜测一下造成从新布局的起因:整个利用的布局我是采纳的 Flex 布局形式,每一次渲染要依据内容状况来计算整个页面的款式和布局。然而实践上渲染 Markdown 的地位和其余中央是无关的,因为所有的内容都在一个可滚动的盒子里。这个盒子的高度是不会随着渲染内容变动而变动,应该不存在重排的问题。
验证猜测,我把 Markdown 渲染容器设置为 100vh,不论输出打字或者更改窗口尺寸,都不会有问题了,那问题就肯定是出在这下面。
所以优化形式就是将 Markdown 渲染容器定位设置为相对定位,问题解决。当初在这个 2 万行,60 万字符的文件外面打字,基本上和 VSCode 没什么两样了,更改窗口尺寸也不卡顿了。
进一步优化
下面的优化只是一些大的和容易的方面,还有一些不好优化的中央。
- markdown-it-attrs 插件性能比拟低,在渲染大文档时候破费了不少工夫,看代码还有很大的优化空间
- 尝试将解析 Markdown 的过程放入 worker 中,不阻塞主线程
- 如有可能,尝试 Markdown 增量编译,即只编译编辑的内容,然而这仿佛不容易做到。
好了,下面就是我优化 Yank Note 渲染性能过程中的一些记录,如果你对 Yank Note 感兴趣,想应用或者参加奉献,能够到 Github 理解更多。