SeaPig is converter from markdown to html with marked.js and highlight.js.
3年ぶりに SeaPig 0.8.3 をリリースしました。
至極当然の話ですが、DOM を直接操作せずに、Virtual DOM を使用するフレームワーク Mithril を通して操作するようにしたら、再描画が早くなったというお話です。
まずは SeaPig の概要を
SeaPig は、Qiita の投稿にも使用する Markdown と呼ばれる記法で左ペインのエディタ部分に入力すると右ペインのブラウザー部分に HTML で表示してくれるコンバーターです。
変換した HTML を保存できるのは当然のこと、PDF として出力することもできます。GitHub Flavored Markdown 記法を使える他に以下の JavaScript ライブラリーを利用して、図を描画することも可能です。
- mermaid(flowchart、sequence、gantt が利用可能。Pie chart は未対応。)
- viz.js
- uiflow(use forked version)
Markdown から HTML への変換には、marked を利用しており、コードブロックにおいては、highlight.js による簡単な Sytax ハイライトを付与することができます。
これまでのプレビューの再描画
Editor で改行などが行われたときにプレビューを担当するプロセス seapig/js/preview.js にエディターのデータを送信し、marked で変換した結果を body の innerHTML にそのまま突っ込んでおりました。
当然、body 全体の再描画が走るため、大きなファイルになるほど、再描画に時間がかかりました。(なので1文字入力ではなく改行をトリガーにしていました。)
/**
* Refresh preview
* @param {object} event
* @param {string} data - markdown text
* @param {string} baseURI
* @listens preview
*/
ipcRenderer.on('preview', (event, data, baseURI) => {
let base = document.getElementsByTagName("base")[FIRST_IDX];
if (baseURI != "") {
base.setAttribute("href", baseURI);
}
base.setAttribute("target", "_blank");
document.getElementById('body').innerHTML = md2html.convert(data);
// process task list items
var listitems = document.getElementsByTagName("li");
for(var i=0; i<listitems.length; i++) {
var fchild = listitems[i].firstElementChild;
if(fchild != null && fchild.nodeName === "INPUT" && fchild.type === "checkbox") {
listitems[i].classList.add("task-list-item");
}
}
document.getElementById('body')
なんてしなくても document.body
で行けたよね、というのもあったり…。
Mithril を活用したプレビューの再描画
Editor での変更をトリガーにプレビューを担当するプロセス seapig/js/preview.js にエディターのデータを送信し、marked で変換した結果を m.trust() で受け取り、m.render() で body に設定します。これにより、変更のあった部分のみ DOM の再構築が行われるようになり、高速になりました。
/**
* Refresh preview
* @param {object} event
* @param {string} data - markdown text
* @param {string} baseURI
* @listens preview
*/
ipcRenderer.on('preview', (event, data, baseURI) => {
let base = document.getElementsByTagName("base")[FIRST_IDX];
if (baseURI != "") {
base.setAttribute("href", baseURI);
}
base.setAttribute("target", "_blank");
// render body
m.render(document.body, m.trust(md2html.convert(data)));
// process task list items
var listitems = document.getElementsByTagName("li");
for(var i=0; i<listitems.length; i++) {
var fchild = listitems[i].firstElementChild;
if(fchild != null && fchild.nodeName === "INPUT" && fchild.type === "checkbox") {
listitems[i].classList.add("task-list-item");
}
}
差分は、わずか、以下の通りです。
index 625fb1b..4af4792 100644
--- a/js/preview.js
+++ b/js/preview.js
@@ -30,6 +30,7 @@
const {ipcRenderer} = require('electron');
const fs = require('fs');
const path = require('path');
+ const m = require("../external/mithril/mithril.min.js");
const Md2Html = require('./md2html.js');
const FIRST_IDX = 0;
const NO_SCROLL = 0;
@@ -91,7 +92,9 @@
base.setAttribute("href", baseURI);
}
base.setAttribute("target", "_blank");
- document.getElementById('body').innerHTML = md2html.convert(data);
+
+ // render body
+ m.render(document.body, m.trust(md2html.convert(data)));
// process task list items
var listitems = document.getElementsByTagName("li");