Yank Note is a note-taking app I wrote for programmers. Here I will write some articles about Yank Note

  • Yank Note Series 01 – Why write your own Note software?
  • Yank Note Series 02 – Markdown Rendering performance optimization road
  • Yank Note series 03 – Battle memory Leaks!

preface

In the early days of Yank Note development, performance issues were barely considered. Later, due to some performance problems affecting the interactive experience, we started to optimize the performance.

Here I will describe the work done for performance tuning:

  1. Introducing the virtual DOM
  2. Delay complex content rendering
  3. Fine-tune the user experience

Introducing the virtual DOM

Yank Note uses Markdown-it as the parser. Markdown-it uses HTML output by default. Until Yank Note 3.0, rendering was done by assigning the innerHTML directly to the render container.

As you can see from the figure above, the drawback of HTML straight out is obvious. Every time you edit the text, you have to go through the rendering process and rebuild the HTML. In each typing and editing, the page will be rearranged, and some embedded functions such as brain maps and small tools will also be re-rendered, resulting in page flickering and poor user experience.

So you need to figure out how to do incremental rendering, that is, only render the moving parts at edit time.

Some incremental rendering schemes, such as Markdown-it-incremental-DOM, were examined. Since the project itself uses Vue, Vue can work better with Vue components, so we later decide to use Vue virtual DOM to do incremental rendering.

earnings

From the perspective of interaction, the discussion is divided into two parts:

  1. First render: When you open a new file, the PERFORMANCE of THE HTML output is similar to that of the virtual DOM, perhaps even better than that of the virtual DOM, after all, the process of constructing virtual nodes is omitted.
  2. Edit render: edit file, generate Vue VNode virtual DOM, subsequent render are taken over by Vue optimization, do incremental render, this point is much higher than HTML performance.

The benefit of incremental rendering is not only higher rendering performance at edit time, but also better support for Vue components, optimizing the interactive experience such as brain maps, HTML widgets, etc.

The specific implementation

1. Token flow conversion

In this file, I replaced the markdown-it default HTML renderer with my own.

Markdown-it Parse produces a token stream rather than a syntax tree. The token nesting attribute indicates the hierarchy (-1 closes the tag, 0 self-closes, and 1 opens the tag).

So during the token stream traversal, I built a stack to convert the token stream into a VNode tree. For open label and self-closing label, a new VNode is generated and the open label is pushed. For closed tags, a VNode is removed from the stack.

This is sufficient for most render scenes.

2. Compatibility with plug-ins

Some Markdown-it plug-ins define rendering methods that output HTML strings. Custom renderers still need to be able to handle them, or they won’t be able to enjoy markdown-it’s ecological advantages.

Read Zhang Xinxu’s article on the various methods and details of HTML string DOM conversion, but I chose to use innerHTML, which is enough to cover most scenes.

3. HTML parsing

If you don’t do HTML parsing, the above steps are sufficient. But how could Yank Note, an unusually open Markdown editor, not let HTML render go?

The Markdown-it parser supports HTML, but the HTML is printed as is, meaning that markdown-it technically treats the HTML as normal text and does not parse it into a structured token.

So I wrote a new HTML parser based on the existing HTML parser. The logic is all in this file.

In contrast to the original full HTML support, this parser can’t write HTML anywhere, but it will work in most cases.

conclusion

The introduction of the virtual DOM is equivalent to a major operation on the original Render of Markdown-it. Some special cases may not be covered, problems can also be avoided, overall benefits are greater.

Delay complex content rendering

We’ll focus on first rendering, which is the process of rendering Markdown for the first time after the user opens the file.

I created a performance test file with Features documentation for Yank Note that had 3600 lines copied and pasted and a lot of complex Dom structures rendered.

My computer is equipped with an M1 chip and 8GB of ram. Start the application in dev mode. The following result is recorded using the Chrome performance panel

It was terrible. It took 7 seconds to open the file and display it!

In performance optimization, there’s a saying: “You can’t make a computer faster, you can only make it do less.”

But when it comes to user experience optimization, I’ve concluded that “if you can’t get the computer to do less, find a way to make the user feel that the computer is doing something.”

So the optimization I made here is mainly to customize some complex functions such as brain map, small tools, are delayed rendering, do not block rendering main content. Although the layout of the page may be rearranged later, this problem will be mitigated by the simplification of all block elements.

After optimization, the content can be displayed in one second.

Fine-tune the user experience

The focus here is on editing, from user input to rendering.

Editing this part is the job of the Monaco editor, which already does a pretty good job of it.

Render, where the virtual DOM has been introduced. Normal files are fine, and can go from keystroke to rendering in milliseconds. Markdown parsing typically takes less than a millisecond.

But if you edit a very large file, the Markdown parsing costs here are not negligible.

Create a performance test file with the Readme document for Yank Note, copy and paste it with 10,000 lines.

Take a look at the Markdown resolution time

The parse process takes 100ms to parse this 10,000-line file.

I can’t think of any way to optimize this time, so let’s optimize the user experience.

Before optimization, I used a fixed time interval method for rendering. In large files, this shake-proof interval is insufficient, making typing very difficult.

Set the wait time for the render stabilization function to dynamic. Record the render time and then dynamically update the Debounce time. Set small files to be shorter and large files to be longer. Make input experience a priority when editing large files.

Listening for composition events and pausing render while typing in Chinese will also improve the user experience.

After the above processing and some other fine-tuning optimization, typing in this test file, although there is still some lag, but the situation is much better.

You can also turn off autorender and focus on typing, which is much smoother.

Further optimization

The optimizations above are just the big and easy ones, and there are some bad ones.

  1. Specific refinements in browser layout and composition optimization. Yank Note crashes every time I open the Layers panel in Chrome. Check back in the future when Chrome doesn’t crash anymore and you learn more optimization tips with a large memory device.
  2. The markdown-it-Attrs plugin is low performance and takes a lot of time to render large documents, so there is a lot of room for optimization in the code
  3. If possible, try Markdown incremental compilation, which compiles only the edited content, but this doesn’t seem easy to do.

Ok, these are some of my notes on how TO optimize the rendering performance of Yank Note. If you are interested in Yank Note, you can go to Github to learn more.

This article was written by Yank Note, a Markdown note-taking app for programmers