長めの文章です。
Electron は、元々は GitHubによるテキストエディタ Atom を開発するために使用されていたJavaScript、HTML および CSS を使用してクロスプラットフォーム向けにデスクトップアプリケーションを開発できるフレームワークです。
現在の所、Microsoft Windows、Appleの macOS や各種 Linux ディストリビューションに対応しています。
Microsoft、Facebook、Slack および Docker などの企業でも使用されているようです。
他人に教えられるくらい自分自身の知識を整理するため、このチュートリアルを書きたいと思います。
「マークダウンでプレゼンテーションしたいなら、『Electron で Markdownプレゼン作成ツールを作って公開するまで - Qiita』で紹介されている『Marp - Markdown Presentation Writer』を使えばいいじゃないか。』
というあなた。正解ですが、学習のためには時には車輪の再発明も必要なのです。
筆者の環境
- VineSeed (Vine Linux 開発版) 64bit
- nodebrew use v4.5.0(LTS)
作成するアプリケーションのイメージ
今回、作成するアプリケーションは、大きく左右の2つのペインで構成されています。
- 左側のペインはマークダウン形式のテキストを編集するためのエディタ
- 右側のペインはスライドのプレビュー
エディタには、Cloud9 IDE の主要なエディタとして開発されているAceを採用します。(Electron アプリケーションでは、CodeMirror もよく採用されているようです。興味のある方は、CodeMirror で実装してみてください。)
スライドショーのフレームワークとしては、remark(http://remarkjs.com/) という JavaScript のライブラリを採用します。
また、デザインには Photon のスタイルシートを採用します。
(第1回では、エディタ部分にマークダウンをコピペして、実際にスライドに反映させる部分、全画面でスライドショーを実行する部分まで作成します。第2回以降を書くかは未定ですが… )
まずは Node.js 環境を準備しよう
Node.jsは、WebブラウザChromeに搭載されているV8と呼ばれるJavaScriptエンジンを元に作成されたJavaScriptの実行環境です。
ブラウザに依存しない範囲であれば、そのまま JavaScript を実行できますし、PhantomJS を使用すれば、ブラウザが必要な JavaScript もブラウザなしで実行できるようになります。
現在、Node.js のトップページでは、LTS(Long Time Support)の推奨版と最新の V8 の機能を取り込んだ Current 版の大きく2つのバージョンのダウンロードボタンが表示されていますが、筆者も LTS 版の使用を推奨します。(2016/8/19現在のLTS版は、v4.5.0 です。)
Node.js から、ダウンロードして普通にインストールしても良いですし、複数バージョンの Node.js を管理できる nodebrew、nodeenv、nvm などのツールを利用しても良いでしょう。
いずれにしてもこの記事では、Node.js のインストール方法などについては、割愛させていただきます。(Qiita にもこれらの記事がいくつかあるようです。)
プロジェクトの開始
Node.js の準備が完了したら、いよいよプロジェクトを開始します。まずは、プロジェクトを管理するためのフォルダ(ディレクトリ)を作成します。ここでは、remark-editor という名前で作成します。(なんという安直 )
GitHub などのアカウントがあれば、そちらでリポジトリを作成してクローンしても良いでしょう。GitHub の場合、リポジトリ作成時に Node.js 向けの .gitignore ファイルや LICENSE ファイルなどを同時生成することが可能です。(こういったサービスを使わないにしろ、何かしらのバージョン管理ツールを使うことは良い考えです。)
フォルダが作成できたら、Node.js のプロジェクトとして認識させるため、package.json というファイルを作成します。Electron1.3.xの最小構成 - Qiita などから、雛型をコピーしてきても良いのですが、ここでは、Node.js のパッケージマネージャーである npm というコマンドラインツールを使用して作成したいと思います。
$ cd /path/to/remark-editor ← 実際のパスを入力。行頭の $ は、コマンドの入力を受け付けるプロンプトで環境により異なる。
$ npm init ← このコマンドを実行する最初にいくつかの説明が表示されます。
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg> --save` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit. ← Ctrl + C 同時押しでいつでも強制終了できますよ、と。
name: (remark-editor) ← パッケージ名を入力します。そのままエンターを押すと()内のデフォルト値が設定されます。(大文字使用不可)
version: (1.0.0) 0.1.0 ← パッケージのバージョンを入力します。
description: Markdown editor for remark.js (For tutorial articles) ← パッケージの説明
entry point: (index.js) main.js ← 最初に起動される JavaScript のファイル名
test command: ← テスト用のコマンドを入力します。そのままエンターを押しても良い。
git repository: ← 必要に応じ、Git の公開リポジトリの URL を記述
keywords: Markdown, Presentation ← キーワードをカンマ区切りで入力
author: Anatano Namae ← 作成者の名前を入力
license: (ISC) MIT ← ライセンスを入力します。
About to write to /path/to/remark-editor/package.json: ← 以下、作成される package.json の内容が表示されます。
{
"name": "remark-editor",
"version": "0.1.0",
"description": "Markdown editor for remark.js (For tutorial articles)",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"Markdown",
"Presentation"
],
"author": "Anatano Namae",
"license": "MIT"
}
Is this ok? (yes) ← 上記内容で問題なければ、Enter キーを押す。
Electron の開発に必要なパッケージをインストールする
これから作成する JavaScript 等を Electron アプリケーションとして起動するための electron-prebuilt パッケージをインストールします。(実際には、プロジェクトフォルダの配下の node_modules というフォルダの中に配置されます。)
npm の install
サブコマンドを使用します。
$ npm install --save-dev --global-style electron-prebuilt
--save-dev
オプションを指定すると package.json の "devDependencies" (開発時に依存するパッケージリスト)に追記されます。
"devDependencies": {
"electron-prebuilt": "^1.3.3"
}
--global-style
オプションは、就筆当時の LTS 版では無視されますが、Current 版を使用している時は、つけた方がよいオプションです。詳細は、npm v3 で Electron のパッケージサイズが大きくなる - Qiitaなどを参照してください。
アプリケーションの配布を考えている場合は、electron-packager もインストールしておくと良いでしょう。
$ npm install --save-dev --global-style electron-packager
Ace Code Editor を Node.js のパッケージとしてインストールする
「作成するアプリケーションのイメージ」で紹介したエディタ部分を担う Ace Code Editor を Node.js パッケージとしてインストールします。
$ npm install --save ace-min-noconflict
--save
オプションを指定することで package.json の "dependencies" (実行時に依存するパッケージリスト)に追加されます。
"dependencies": {
"ace-min-noconflict": "^1.1.9"
}
Photon をダウンロードして必要なフォルダ・ファイルをコピーする
npm リポジトリに同名のパッケージがありますが、別物ですのでインストールする必要はありません。
Photon のサイトで Download Photon というボタンからダウンロードして展開します。
remark-editorのフォルダに photon フォルダを作成し、
- photon-0.1.2-alpha/dist/css/photon.min.css ファイルを remark-editor/photon/css/photon.min.css へ
- photon-0.1.2-alpha/dist/fonts/ フォルダを remark-editor/photon/fonts/ へ
- photon-0.1.2-alpha/LICENSE を remark-editor/photon/LICENSE へ
それぞれコピーします。最終的に以下のファイルがプロジェクトフォルダ配下に存在するようになります。
remark-editor/photon/css/photon.min.css
remark-editor/photon/LICENSE
remark-editor/photon/fonts/photon-entypo.eot
remark-editor/photon/fonts/photon-entypo.svg
remark-editor/photon/fonts/photon-entypo.ttf
remark-editor/photon/fonts/photon-entypo.woff
fonts 以下は今回の記事では使用されませんが、じ後、ボタン等のアイコンとして使用できるようにコピーしています。
remark をダウンロードして必要なファイルをコピーする
npm リポジトリに同名のパッケージがありますが、別物ですのでインストールする必要はありません。
remark の releases から、v0.13.0 のソースをダウンロードして展開します。
プロジェクトのフォルダに js フォルダを作成し、remark-0.13.0/out/remark.min.js をコピーします。
実行には必要ありませんが、権利関係を明確にしておくため、remark-0.13.0/LICENSE を remark-editor/LICENSE-remark として保存します。
remark-editor/js、remark-editor/LICENSE-remark
メインウィンドウの画面構成を HTML で作成する
以下のようにメインウィンドウの画面構成を HTML で作成します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="photon/css/photon.min.css">
<title>Remark Editor</title>
</head>
<body>
<div class="window">
<div class="pane-group">
<div id="editor" class="pane"></div>
<webview id="webview" class="pane" src="slideview.html" preload="js/webview.js"></webview>
</div>
</div>
<script type="text/javascript" src="js/mainwindow.js"></script>
</body>
</html>
説明不要かもしれませんが、以下の部分で Photon のスタイルシートを読み込んでいます。
<link rel="stylesheet" href="photon/css/photon.min.css">
画面構成に関わる部分は、以下の部分です。
<div class="window">
<div class="pane-group">
<div id="editor" class="pane"></div>
<webview id="webview" class="pane" src="slideview.html" preload="js/webview.js"></webview>
</div>
</div>
window クラス内に pane-group クラス要素があり、editor と webview という id がついた pane クラス2つをグループ化しています。
どのような部品が使えるかは、Photon · Components(英語)を参照してください。
<webview>
は、別プロセスで動作する埋め込み Web ブラウザです。今回は、remark のスライドを表示しようと目論んでいます。
<webview>
に表示するファイル(これから作成)を src="slideview.html"
で指定しており、表示の際に注入する JavaScript ファイル(これから作成)を preload="js/webview.js"
で指定しています。
現段階で mainwindow.html をブラウザで表示することができますが、ペインを分ける中央の線が表示されるだけです。
なお、以下はこれから作成するメインウィンドウの表示等を制御するレンダラープロセスのJavaScript を読み込むための記述です。
<script type="text/javascript" src="js/mainwindow.js"></script>
スライドのプレビューを表示する HTML ファイルを作成する
remark のソースに含まれている remark-0.13.0/boilerplate-local.html を参考に以下の slideview.html を作成します。
<!DOCTYPE html>
<html>
<head>
<title>Slides</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<textarea id="source"></textarea>
<script src="js/remark.min.js" type="text/javascript">
</script>
<script type="text/javascript">
var slideshow = remark.create();
</script>
</body>
</html>
この内、以下の部分は、スライドのソースをマークダウンで記述する部分です。
<textarea id="source"></textarea>
id="source"
のように識別をつけているのがポイントです。
各プロセスを制御する JavaScript ファイルを作成する(基本部分)
Electron のアプリケーションは、大きくアプリケーション全体を制御するメインプロセスとアプリケーションの描画を制御するレンダラープロセスで構成されます。作成の仕方によっては、レンダラープロセスが複数になる場合もあります。
メインプロセス
Electron1.3.xの最小構成 - Qiita の index.js を参考にメインプロセスである main.js を記述します。
// app および BrowserWindow への参照を読み込む
const {app, BrowserWindow} = require('electron');
// Window を管理する変数
let win;
// メインウィンドウを作成するための関数
function createWindow() {
win = new BrowserWindow({width: 800, height: 600});
win.loadURL(`file://${__dirname}/mainwindow.html`);
win.on('closed', () => { win = null; });
}
// アプリケーションが準備できたらメインウィンドウを作成する
app.on('ready', createWindow);
// macOS ではすべてのウィンドウが閉じた際にアプリケーションを終了させる
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});
以下、要点を解説します。
まず、electron のライブラリから必要な参照を取得します。electron は複数の参照を返すため、{}
で受け取っています。
const {app, BrowserWindow} = require('electron');
次のコードでWebブラウザのウィンドウを幅 800 ピクセル、高さ 600 ピクセルで作成します。
win = new BrowserWindow({width: 800, height: 600});
BrowserWindow へ渡せる他のオプションは、new BrowserWindow([options])(英語)を参照してください。
次のコードでWebブラウザに先ほど作成した mainwindow.html を win.loadURL(url[, options]) で読み込ませます。
win.loadURL(`file://${__dirname}/mainwindow.html`);
file://
はローカルファイルを読み込むためのスキームを指定しています。
${__dirname}
はスクリプトが存在するフォルダへのパスを返します。
次のコードは、ブラウザが閉じられた時のイベントハンドラを記述しています。
win.on('closed', () => { win = null; });
() =>
という記述は無名関数の宣言であり、function ()
と同義です。
~macOS でアプリケーションを正常に終了させるために次のコードを記述しています。~
次のコードは、macOS 以外でウィンドウがすべて閉じられた時にアプリケーションを終了させることを意味しています。
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});
process.platform には、アプリケーションが実行されているプラットフォームを返します。darwin
は macOS です。Microsoft Windows の場合は win32
を Linux の場合は、linux
を返します。
2016/8/22追記
当初、上記の説明で「macOS でアプリケーションを正常に終了させるために次のコードを記述しています。」という誤った記述をしておりました。「次のコードは、macOS 以外でウィンドウがすべて閉じられた時にアプリケーションを終了させることを意味しています。」に訂正させて頂きます。
なお、macOS だけ扱いが異なる理由は、以下の記事を参照してください。
- OS X用Electronアプリで,closeボタン処理と終了処理を区別する - Qiita(Electron v0.31.0 向け)
- OS X用Electronアプリで,closeボタン処理と終了処理を区別する(Electron v1.1) - Qiita
起動確認
Linux や macOSの場合、次のコマンドでアプリケーションを起動できます。
$ ./node_modules/.bin/electron .
Windows の場合は、次のコマンドで起動します。
$ .\node_modules\.bin\electron.cmd .
メニューが表示されていますが、これは electron-prebuilt パッケージが開発用に付与しているメニューです。アプリケーション配布時には表示されません。(メニューの作成方法は、今回は触れません。)
毎回、上記のようなコマンドを入力するのも面倒なので npm start
で起動できるように package.json を修正します。(行頭の + は書かない。)
"scripts": {
+ "start": "electron .",
"test": "echo \"Error: no test specified\" && exit 1"
},
これで次のコマンドでアプリケーションを起動できるようになります。
$ npm start
> remark-editor@0.1.0 start /path/to/remark-editor
> electron .
レンダラープロセス(メインウィンドウの表示等を制御)
まず左側のペインにエディタを表示するように以下の js/mainwindow.js を作成します。
// エディタの初期化
require('ace-min-noconflict');
require('ace-min-noconflict/mode-markdown');
var editor = ace.edit("editor");
editor.getSession().setMode("ace/mode/markdown");
editor.getSession().setUseWrapMode(true);
editor.focus();
まず、次のコードで Ace Code Editor の必要なライブラリを読み込ませています。
require('ace-min-noconflict');
require('ace-min-noconflict/mode-markdown');
次のコードでエディタを実際に生成します。
var editor = ace.edit("editor");
次のコードでシンタックスハイライトや入力補完が、マークダウン用になるようにモードを設定しています。
editor.getSession().setMode("ace/mode/markdown");
次のコードで行が長くなった場合に自動で折り返して表示されるようになります。
editor.getSession().setUseWrapMode(true);
最後にエディタにフォーカスを与えます。
editor.focus();
起動確認
ここで実際にアプリケーションを起動してみます。
$ npm start
左側のペインに実際にエディタが生成されたことを確認してみましょう。実際に次の文を入力してみます。
# マークダウンでスライドショー
- はじめに
-
問題がなければ、- はじめに
を入力して改行した際に行頭の -
が自動入力され、次のように表示されるはずです。
うまく行かない人は、アプリケーションのメニューから View Toggle Developer Tools の順に選択し、Console の内容を確認してみてください。
例えば、2行目の require
を忘れた場合は、以下のようなエラーが表示されます。
レンダラープロセス(<webview>
の表示等を制御)
webview に注入される js/webview.js を記述します。とりあえず、preload が成功しているかどうかを確認するために console にログを出力させます。
// preload されていることを console にログ出力する
console.log("js/webview.js was loaded");
このログは、メインウィンドウ側の開発者ツールには表示されないため、ゲスト側の開発者ツールを表示できるように js/mainwindow.js に以下のコードを追加します。
// DEBUG_GUEST=true の時にwebview 側の開発者ツールを開く
var webview = document.getElementById('webview');
if (process.env.DEBUG_GUEST) {
webview.addEventListener('dom-ready', () => {
webview.openDevTools()
});
}
これは、環境変数 DEBUG_GUEST
の値が true
の場合にゲスト側の開発者ツールを表示します。bash を使用している場合は、以下のように起動するとゲスト側の開発者ツールを表示できます。
$ DEBUG_GUEST=true npm start
Microsoft Windows では、次のように起動します。
> SET DEBUG_GUEST=true
> npm start
問題がなければ、以下のようにログが出力されているはずです。
エディタの入力内容をスライドに反映させる
editor
の change
イベントを補足し、改行などが行われたら、ゲスト側にメッセージと文書の内容を送信します。
// 文書の変更を受け取るイベントハンドラ
editor.on("change", (e) => {
if (e.data.range.start.row != e.data.range.end.row) {
// 変更前と変更後で行が変わっていたら webview にメッセージと文書の内容を送る
webview.send('update-markdown', editor.getValue());
}
});
ゲスト側でこのメッセージを受け取れるように以下の内容を記述します。
const {ipcRenderer} = require('electron');
ipcRenderer.on('update-markdown', (event,markdown) => {
let source = document.getElementById('source');
source.innerHTML = markdown;
slideshow.loadFromString(source.innerHTML);
});
実際に起動してエディタに以下の内容を入力してみてください。
class: center, middle
# マークダウンスライドショー
???
タイトルページなり
---
# 目次
1. Electron とは
1. Node.js 環境の準備
1. プロジェクトの開始
1. ...
???
`???`でノートを記述できます。
ゲスト側の全画面表示を捕捉してエディタ部分の表示を切り替える
ゲスト側にフォーカスがある時に f
を押すと全画面表示に切り替わります。
ですが、エディタ部分が表示されたままなのでどうせならエディタの表示・非表示を切り替えるようにしたいと思います。
// 全画面表示時の制御
var editorPane = document.getElementById('editor');
webview.addEventListener('enter-html-full-screen', () => {
editorPane.setAttribute('style', 'display:none');
});
webview.addEventListener('leave-html-full-screen', () => {
editorPane.removeAttribute('style');
});
実際に起動して、ゲスト側にフォーカスを与え、f
キーを押してみましょう。なお、h
キーを押すと簡単なヘルプが表示できます。
パッケージ化
以下のようなコマンドで Linux 64bit 向けのパッケージが、
releases/remark-editor-linux-x64 に展開されます。(アーカイブはされないので別途、zip
コマンドなどで1つのファイルにまとめる必要があります。)
$ ./node_modules/.bin/electron-packager --platform=linux --arch=x64 --prune --out=releases .
Windows で実行する場合は、次のようなコマンドになるでしょう。
$ .\node_modules\.bin\electron-packager.cmd --platform=linux --arch=x64 --prune --out=releases .
次のように package.json にスクリプトを追加しても良いでしょう。
"scripts": {
+ "package-darwin": "electron-packager --platform=darwin --arch=all --prune --asar=true --out=releases .",
+ "package-win32": "electron-packager --platform=win32 --arch=all --prune --asar=true --out=releases .",
+ "package-linux": "electron-packager --platform=linux --arch=all --prune --asar=true --out=releases .",
"start": "electron .",
"test": "echo \"Error: no test specified\" && exit 1"
},
このようにすると以下のコマンドで Microsoft Windows 用のパッケージを作成できます。
$ npm run-script package-win32
終わりに
参考になりましたでしょうか?
私自身もまだまだ学習中の身なので至らないところがあったと思います。ご意見、お待ちしております。
需要があるようであれば、第2回でメニューの追加、ファイルの入出力について記述したいと思います。
なお、今回作成したソースは、yasumichi/remark-editor として GitHub で公開しています。