Help us understand the problem. What is going on with this article?

Electron で Markdownプレゼン作成ツールを作って公開するまで

More than 1 year has passed since last update.

Markdown でスライドを作るためのツールを漁っていたのですが、なかなか自分好みのものが無く、Electron の勉強を兼ねて作ってみた話です。

追記 (2018/12/10): その後の進捗はこちら
:arrow_right: Puppeteer & Carlo を Markdown スライド作成 CLI ツール (Marp CLI) で活用する - Qiita

成果物

今回作ったプレゼンツールは、以下にてプレリリース版を公開しています。

Marp - Markdown presentation writer
https://yhatt.github.io/marp/ (旧称 mdSlide)

Marp

追記 (2016/07/13 19:42)

_人人人人人人人人人人人人人人_
> 突然のGithubトレンド入り <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄

Markdown スライドツール比較表

私の欲しい機能 ▼ Deckset reveal.js md2key Pandoc mdslide Qiita
Markdown :o: :o: :o: :o: :o: :o:
PDF変換 :o: 印刷経由 Keynote :o: :x: :x:
ライブプレビュー 半自動 手動 :x: :x: :x: :o:
無料 3600円 :o: :o: :o: :o: :o:
クロスプラットフォーム Mac :o: Mac :o: :o: :o:

Electron による開発を選んだのは、クロスプラットフォームで、環境に依存せずPDFが作れる仕組みがある (printToPDF メソッドがある) のが一番大きいです。1度PDFになれば 可搬性と汎用性が抜群ですし。

reveal.js はかなり惜しいんですが、印刷経由なので、環境によってはPDF変換ツールが必要になってしまうのが…。

まず Markdown エディタを作る

スライドの前に、まずは Markdown エディタを作る所から始まるわけですが、ここは先人の知識を多分に拝借しました。ありがとうございます :bow:

Electron/Node.js初心者がマテリアルデザインなMarkdownエディタを作ってみた
http://qiita.com/hoshi-k/items/565acf909d46c0c5c28b

今回の構成

採用したもの 備考
JavaScript CoffeeScript / jQuery スピード重視で、慣れてるもので
CSS Sass 今後のスライドテーマ展開を見据えて
UI/CSS Framework Photon 良い感じのMacネイティブルック
エディタ部分 CodeMirror  
Markdownパーサー markdown-it スライド記述用に設定する(後述)
Syntax highlight highlight.js  
絵文字表示 twemoji  

Markdown パース結果をスライドにするための工夫

ページ区切りを --- にする

今回、Deckset 式の --- 区切りでの改ページを採用しました。

# Slide 1

hogehoge

---

# Slide 2

fugafuga

その際に、スライドプレビュー等の表示をコントロールするため、レンダリング結果がページごとの block 要素になってると嬉しいなーと思いまして、パーサーの設定をややいじりました。

md.renderer.rules.hr = (token, idx) ->
    '</div><div class="slidePage">'

rendered = "<div class=\"slidePage\">#{md.render(markdown)}</div>"

ちょっとローテクですが、<hr /> を出力する部分を <div> の終了要素+開始要素 のペアに変えて、レンダリング結果全体を同様の要素で囲みます。

rendered
<div class="slidePage">
    <h1>Slide 1</h1>
    <p>hogehoge</p>
</div>
<div class="slidePage">
    <h1>Slide 2</h1>
    <p>fugafuga</p>
</div>

今回作ったツールは、作成した Markdown パースクラスで markdown-it をラップし、この設定等を行っています。

スライドPDFを作成する

Electron で スライドPDFを作成するためには、

  1. <webview> にMarkdownをレンダリングして、
  2. webView.printToPDF で印刷の結果をPDFに変換

という感じになります。なので、スライドになる部分のCSSは @media print メディアクエリで制御すればよさそうです。

@media print {
  /* ... */
}

改ページ等はどうするのか?と思っていたんですが、そのものズバリな設定がCSSにありました。

body {
    page-break-inside: avoid;
}

.slidePage {
    width: 297mm;
    height: 210mm;
    overflow: hidden;

    /* 改ページ設定 */
    page-break-after: always;
}

page-break-inside: avoid; で、body 要素内での改ページを避け、page-break-after: always; で各スライドの後で確実に改ページする設定にすることで、1ページ1スライドを実現させました。

今回作ったアプリでは ライブプレビュー実装の関係もあり、実際には Sass や JS, CSS Variables も使って、もう少し細かく制御しています。

クロスプラットフォームで注意すること

クロスプラットフォームでネイティブルックなアプリを作る際には、OSの特性を知らないとハマリそうです。細かい機能については Electron のドキュメント を参照することにして、私がハマったポイントをいくつか挙げておきます。

メニュー

Windows / Linux ではメニューはウィンドウに紐づきますが、Mac ではアプリケーションに紐づきます。

Electron の menu API ドキュメント では、ウィンドウに紐づく時のサンプルとして以下のように書かれています。

{
  label: 'Reload',
  accelerator: 'CmdOrCtrl+R',
  click: function(item, focusedWindow) {
    if (focusedWindow)
      focusedWindow.reload();
  }
}

これを参考に、「開く」コマンド実装時、「現在開いているウィンドウのエディタに対し、ファイルの内容を読み込む」という感じで処理を書いていました。
その結果、Mac 環境で ウィンドウが無い状態から「開く」が出来ないバグを引き起こしました… :sweat:

focusedWindow に window が入ってこないため、この場合は 新規にウィンドウを作る処理を「開く」コマンドの処理に含める必要があります。

ファイルの関連付け

Windows や Linux の場合の関連付けはシンプルで、process.argv で得られる引数にファイルパスが入ってくるので、それを解釈して 開く処理を書けばOKです。

Mac の場合は、そのようなケースもありますが、主に open-file イベントの発火がメインです(アイコンにファイルが D&D されたりした時に呼び出されます)。メインプロセスで、以下の例のように、開く処理を呼び出します。

app.on 'open-file', (e, path) ->
  e.preventDefault()

  # パスを開く処理を呼び出す
  MdsWindow.loadFromFile path

MdsWindow は、Electron の window をラップしたクラスです。

Mac: どのファイルを関連付けるか

ただし、Macの場合これを設定しただけでは D&D してもシカトされてしまいます。Mac の場合、アプリケーションがどのファイルタイプに関連付けられるのかを設定する必要があります。

Marp では gulp タスク内でのビルドに electron-packager を使用していますが、その処理後に ***.app/Contents/Info.plist の設定を以下のように更新しています。

{
  CFBundleDocumentTypes: [
    {
      CFBundleTypeExtensions: ['md', 'mdown']
      CFBundleTypeIconFile: ''
      CFBundleTypeName: 'Markdown file'
      CFBundleTypeRole: 'Editor'
      LSHandlerRank: 'Owner'
    }
  ]
}

各パラメータについて、私は以下を参考にしました。iOSアプリ向けの解説ですが、仕組みは同様です。

Safx: iOSアプリケーションを特定のファイル形式に対応させる方法について
http://safx-dev.blogspot.jp/2010/11/ios_21.html

Win: 関連付けとジャンプリスト

似たようなケースは Windows にもあり、ジャンプリストにファイル履歴を表示させる 場合、システム上でアプリケーションとの関連付け設定がされている必要があります。

インストーラー等で関連付けを実施する のが王道パターンだと思いますが、ユーザーが「プログラムから開く」等で指定しても、ジャンプリストには現れる模様です。

参考

ファイル拡張子とプログラムの関連付け - Web/DB プログラミング徹底解説
http://keicode.com/windows/associate-app-with-file.php

公開

yhatt/marp: Markdown presentation writer, powered by Electron.
https://github.com/yhatt/marp/

開発およびリリースは 原則 Github上で行い、配布ページ には Github Pages を使用しました。

以前は Github が用意していたテンプレートを使用していましたが、現在は Jekyll + Bootstrap で、テンプレートも自ら作成しています。プラグイン等を使用しないのであれば、ローカルに用意した Jekyll の動作環境をそのままコミットすれば Github が Jekyll でホスティングしてくれるので、ラクチンです。

このツールではまだ実現できていませんが、もっとしっかりアプリケーションとして整備するためには、

  • アプリケーションへのコードサイニング (Win, Mac)
  • 自動アップデートの仕組み

などを整えなければなりません。このあたりは ヌーラボさんのTypetalkの例 が参考になります。

  1. 各種アイコンを用意する
  2. コードサイニングを行う
  3. AutoUpdaterに対応する

-- Electronアプリをプロダクトとして「正しく」リリースするために必要な3つのこと - ヌーラボ [Nulab Inc.]
https://nulab-inc.com/ja/blog/typetalk/3-points-for-releasing-production-electron-app/

まとめ

ということで、Electron でキチンとクロスプラットフォームなツールを作って公開してみて感じたのは以下です。

  • 作ること自体は結構カンタン (node 初心者の私でも 5日ぐらいで初期バージョンができた)
  • いざ公開するとなると、OS別のいろんなケースを考えないとハマる可能性が高い
    • WebView で完結するアプリならそうでもないのかも?

ここまでしっかり公開すると、「ちゃんと整備しないと!!!」という気持ちに否が応でもなるので、自分に負荷を課すつもりでアプリを作ってみるのはアリだと思います。

ツールの展望

実際に Marp (旧称 mdSlide) を使って作成したスライドPDFは、SlideShare にて見られます。

marp-ss.png

Electron には フレームレスなウィンドウを作成したり、外部ディスプレイの情報を得るAPIもありますので、ゆくゆくは Marp 単体でのプレゼンとか、発表者ツール機能とかができるといいですね。

2016/06/03 補足:printToPDF で出力サイズをカスタマイズできるようになった

Marp で PDF 出力の肝になる webContents.printToPDF メソッドですが、かつての Electron (<= 1.2.0) では出力できるサイズが限られていました
そのため、PowerPoint や Keynote 互換の 4:3 サイズのPDFは作成できませんでした。

言うほどたいした問題ではないのですが、とある環境で Marp で作ったスライドを画面に映し出したら、他のスライドに比べて、縦横比が微妙にズレを起こしていたなんてことも...。

しかし Electron 1.2.1 で printToPDF任意のサイズを渡して出力できるようになったようです。ミクロン単位 (1000micron = 1mm) でサイズを指定できます。

win.webContents.printToPDF({
  pageSize: {
    width:  254000, // 960px
    height: 190500  // 720px
  }
}, (error, data) => {
  // ...
});

1.2.1 のリリースノートには書かれていないので、まだ実験的な機能のようですが、今のところ 4:3 も 16:9 もバッチリ出力できているので、似たようなお悩みのある方はお試しください。

2016/07/17追記:掲載歴

会社のブログにも少し裏話を書きましたが、 他にもいろんなサイトに紹介していただいたので、一部紹介。

Hacker News

Marp: Markdown Presentation Writer | Hacker News:
https://news.ycombinator.com/item?id=12079671

ここに取り上げられただけで、各方面から普段の200倍ぐらいのアクセスが来て、嬉しい悲鳴を上げています。

GIGAZINE

無料でプレゼンのスライドをマークダウン記法で作成できる「Marp」
http://gigazine.net/news/20160713-marp/

個人の勉強のつもりが、こんな著名サイトでご紹介いただけるとは...!
マニュアル化してなかった機能までフォローしているところが凄い。(画像D&D)

speee
株式会社Speeeは「解き尽くす。未来を引きよせる。」というミッションを実現すべく、中長期的な目線で企業価値を最大化させていくため、組織・事業のStyleを大切にした永続的な価値創造を目指しています。
https://www.speee.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした