arinoth's memo

arinothのメモ

Electron製Markdownプレビュー&コンバートツール制作中

ここ数年grunt-markdownを使った原稿整理をやっているわけですが、Grunt自体のハードルが高くて正直なところ社内ですらまったく広まってない、という事情があったりします。

そんなところへ、Electronというのを使えばインストーラー付きのWinMac兼用のツールが作れるらしい、これなら誰でも使えるじゃないか!と知って、挑戦してみているわけです。

ひとまずMarkdownファイルを開いてHTMLファイルに変換して書き出し、それを読み込んで表示する、元ファイルが更新されたら自動的にHTMLも更新するという、最低限必要なところまでできました。

Electronを使ったMarkdownビューワーというのはすでに結構存在していて、それと比べると見た目も機能もだいぶアレなのですが、そこはもうスッパリ諦めて、用が足せればいいとわりきってやっております……。

f:id:arinoth:20160505043751j:plain

github.com

特徴は、今のところ「プレビューを表示するだけでなく、勝手にHTMLファイルを書き出す」の一点です。

最終目標がVivliostyleでの書籍形式のプレビューと、InDesign向けのXML書き出しなので、HTML出力は外せないのです。

作り方メモ

いきなりですが、こちらのチュートリアル記事が大変参考になりました。

qiita.com

一回その通りにやったら、Electronの基礎から、Angularの使い方、クライアントでのmarkedの使い方まで、ひととおり勉強になります。アプリの基本的な作りもそのまま参考にしました。

以降はそれを踏まえての作り方メモですが……。

markedの利用

参考にした記事では画面表示時にMarkdown変換するのでbower(フロントエンド用のパッケージマネージャ)でmarkedをインストールしていますが、こちらはHTMLファイルを書き出したいので、npm(フロントエンドじゃない用のパッケージマネージャ)でインストールします。

インストール
npm install marked -g
npm install marked --save
利用コード
var marked = require('marked');
var hljs = require('highlight.js');

……中略……

    //markedのオプション設定
    marked.setOptions({
      renderer: new marked.Renderer(),
      gfm: true,
      tables: true,
      breaks: false,
      pedantic: false,
      sanitize: true,
      smartLists: true,
      smartypants: false,
      highlight: function(code){
        return hljs.highlightAuto(code).value;
      }
    });
    
    // markedで変換
    try{
      var src = fs.readFileSync(openfile, 'utf-8');
    } catch (err){
      require('dialog').showErrorBox('Error', 'cannot open file.');
      return 'cannot open file.';       
    }
    var html = marked(src);

これでMarkdownをコードハイライト付きのHTMLに変換してくれます。grunt-markdownのコードをしばらく眺めて何とかできました。

原稿ファイルはたいてい1章分丸ごとの長さを扱うので、非同期処理とかいるのかもとも思っていたのですが、MBPなら重くもない(むしろgrunt-markdownより軽い?)のでシンプルに同期処理です。

テンプレート変換にはlodashを使う

grunt-markdownと同じように、テンプレートファイルを使ってHTMLを作成したいので、またいろいろ調べた結果lodashというものを使えばいいとわかりました。

インストール
npm install lodash --save
利用コード
var _ = require('lodash');

……中略……

    //テンプレートの読み込み
    try{
      var template = fs.readFileSync(
        path.join(workfolder, '_template.html'), 'utf-8');
    } catch (err){
      require('dialog').showErrorBox('Error', '_template.html not found.');
      return '_template.html not found.'; 
    }

……中略……

    // lodashを使ってテンプレートにはめ込む
    var compiled = _.template(template);
    try {
      fs.writeFileSync(htmlfile, compiled({content: html}));    
    } catch (err){
      require('dialog').showErrorBox('Error', 'cannot write file.');
      return 'cannot write file.';       
    }

なぜテンプレートが必要かというと、任意のCSSを読み込めるようにするためです。

Markdownと同階層に_template.htmlというファイルを書いておくと(存在しないとエラーになる仕様ですが)、書籍の仕様にあわせたCSSを読み込めるわけです。

_template.htmlの例
<!DOCTYPE  html>
<html>
  <head>
    <meta charset="utf-8">
    
    <title>doc</title>
    <link rel="stylesheet" href="hljsstyles/vs.css">
    <link rel="stylesheet" href="_pagestyle.css">
  </head>
  <body>  
    <%= content %>
  </body>
</html>

タブ表示にはuI bootstrapを使った

タブをどうやって実装すればいいのかもしばらく悩んだのですが、参考例がAngular.jsを使ってUIを作っているので、それをマネしようということで、Angula.jsと併用する前提のuI bootstrapというものを使うことにしました。

見た目があまり気に入っていないのですが、とりあえずそこは捨てているのでイイかと。

インストール方法
bower init
bower install angular --save
bower install bootstrap --save
bower install angular-ui-bootstrap-bower --save

ファイル監視にはchokidarを使った

開いているMarkdownファイルが更新されたときにプレビューも自動更新させたいので、ファイル監視にchokidarというパッケージを使いました。Node.js何でもありますね。

インストール方法
npm install chokidar --save
利用コード
// ファイル監視
var chokidar = require('chokidar');

……中略……

    // 監視の準備
    watcher = chokidar.watch(openfile);
    // Markdownファイルが苦心されたらHTMLを作り直してリロードする
    watcher.on('change', function(path){
        console.log('change: ' + path);
        fileUtil.convertMarkdown(openfile);
        document.getElementById('html-preview').contentDocument
            .location.reload(true);
    });

HTMLの表示にはiframeを使った

最終的にはVivliostyleのビューワーを表示させたいので、たぶんiframeがいいのだろうと。実装はたぶん普通のWebでの表示と同じです。

ElectronはBrowserWindowパッケージを使ってメインウィンドウを表示し、そこにUIのHTMLファイルを読み込ませる仕組みなのですが、そのUI用HTMLの中では普通のWebページと同じように書けばいいようです。

index.html
        <!--生のHTMLプレビュー-->
        <uib-tab index="0" heading="HTMLPreview">
            <iframe id="html-preview" class="row-html" src=""></iframe>
        </uib-tab>
index.js
    // markdownからhtmlファイルを作成
    var htmlfilepath = fileUtil.convertMarkdown(openfile);
    
    // iframeに読み込む
    var iframe =  document.getElementById('html-preview');
    iframe.src = 'file://' + htmlfilepath;

開発用エディタはVisual Studio Codeを使った

何かデバッグもできるというので使ってみました。ちょっとしか使ってないですが、悪くないような気がします。

f:id:arinoth:20160505060032j:plain

ただ、ElectronはChromeデベロッパツールがそのまま使えるので、結局デバッグはそっちを使いました。よく見かけるツールなので安心です。

f:id:arinoth:20160505060550p:plain

今後

とりあえず動くようにはなりましたが、今後最低限やりたいことが4点あります。

置換ファイルに沿った後処理

Markdownには範囲ブロックを指定するような仕組みがないので、それを補うために「★★ここからコラム★★」「★★ここまでコラム★★」みたいな文字列をMarkdownの中に書いておくと、任意のdivタグ(例:<div class="column></div>)などに置換する仕組みを付けようと思っています。

これはJSON形式の置換リストを読み込んで、string.replaceするだけでいいはずなので何とかなるでしょう。

Vivliostyleを使ったプレビュー表示

Viviliostyleviewerを利用するにはAjaxが使えるWebサーバが必要ですが、Electronで簡易Webサーバを動かす記事は見つけてあるのでたぶん何とかなるでしょう。Vivliostyleは同梱しないつもりなので、Markdownファイルと同じ階層に自分で置いてね、みたいな仕様になっちゃいますが。

インストーラーを付ける

まだ調べてないですが何とかなるでしょう。

InDesign向けのXML書き出し

この機能は完全に社内でしか必要ではないので、どうしようかちょっと迷っています。とはいえXML書き出しだけGrunt使うというのもスマートではないし……。

上の3つぐらいなら、たぶん1日あればできるのではないかなと。