こんにちは。
キャスレーコンサルティングのSI(システム・インテグレーション)部の佐藤です。

今回「ElectronとAngular2でHello World」をテーマにElectronとAngular2 についての紹介をさせていただきます。

今回使用するものは次の通りです。
・Electron
・node.js
・Angular2

環境はUbuntu 14.04 LTSを使用します。

目次

0. ElectronとAngular2とは
1. Electron環境を整える
2. Electronのプロジェクトを作ってみる
3. Angular2を使ってみる
4. まとめ

ElectronとAngular2とは

Electronとは

Electronとは、Javasriptでデスクトップアプリを作るためのプラットフォームの1つです。
Electronを使うとWeb技術だけでアプリケーションが簡単に作れます。

煩わしいUI用の勉強は不要です。
JavascriptとHTMLとCSSが使えれば、デスクトップアプリは作れます。
びっくりです。

私事ですが、以前Javaでデスクトップアプリを作成した際にはSwingに苦しめられたものです。
ロジック部分に取り掛かりたいのに、それ以前にボタン一つ配置できないとか、配置できても思った通りに動かないとか。

UIを整える以前にUIを作れず、UIに依存するからロジックも作れず。
どこから手を付けていいものか悩むということがありました。

これがWeb技術だけで済むと言うなら話が変わってきます。
HTMLとCSSならばいくらでも情報が拾えますし、ブラウザを開けば詰まることなんて基本的にありません。
ロジック部分がJavascriptだけでは荷が重いかと一瞬思いますが、今はTypeScriptなどのように複雑な構成で作成し、
後ほどJavascriptに変換できる使い勝手のよい言語がいくつもあります。

すごい、Electron。

多少Electronのための勉強コストはかかりますが、単純なアプリでしたらそんなに複雑なことは行いません。
node.js上で動くため、node.jsの作法を勉強する必要もありますが、こちらも単純なアプリでしたら複雑ではありません。

ちなみに、Electronは内部でブラウザを生成し、html等を読み込む事でデスクトップアプリとして表示させています。
そのブラウザにはChromiumを使用しています。

Chromiumはオープンソースで提供されているブラウザで、これを使って提供されているのがGoogle Chromeです。
つまり、軽いし速い。

加えてElectron、というかnode.jsは、クロスプラットフォーム対応です。
Windows でも、Mac でも、Linuxでも、場所を選ばず動きます。
作業環境に依存しないので、どこでも作れるし、どの環境の情報でも使えます。
いちいち情報を読み替えて実装する必要はないのです。

Anguar2とは

もう一つのタイトルであるAngular2についてです。
”2”ということは当然”1”もあります。しかしほとんど互換性がないため、この記事では紹介しません。

一般的に言われる大きな違いであるデザインパターンは、
・Angular1はMVCの思想
・Angular2はDIコンポーネント
というように変化しています。

Angular1では、デザインパターンとしてMVCを採用していました。
Model-View-Controllerの3つの要素に分けることで構造を分かりやすくしています。

一方で、Angular1.5以降ではデザインパターンとしてDependency-Injectionを採用しています。
役割で分けるのではなく、階層で分ける思想です。

しかし、私のような初心者にとっての1番大きな変化は「コンパイラが要るか要らないか」です。
互換性がない、と書きましたが、本当にもうそのレベルから互換性ないです。

Angular2の勉強を「よし、やってみよう」と思って、調べて最初に出くわすのがAngular1だと、
もうその段階でやっていることが違いすぎて大きく躓きます。

そもそもAngular1 ではMVCを分けるだけだったので、それぞれ1つずつファイルを用意して
リンクでつないであげればよかったのです。

ところが、Angular2になったことで、コンポーネントという考え方が出てきました。
コンポーネントを使うと要素を分割して書くことができる一方で、
最後にそれらをまとめて一つのオブジェクトにする必要があります。

ここでコンパイラでコンパイルする必要性が出てくるのです。

つらつら書きましたが、結局は「純粋なJavascript で書けるか、書けないか」というところに落ち着きます。
Angular1 はJavascriptで書けます。コンパイラは要りません。
Anguar2 はJavascript で書けません。コンパイラが必要です。

そして今回はAngular2 を使うので、Javascript で書けません。
コンパイラが必要です。

ちなみにコンパイルするとJavascript になってくれる言語は色々あるようですが、今回はTypeScript を使っています。
拡張子が.ts となっているファイルは全てTypeScript で書いています。

実際にアプリケーションを作っていくにあたって、以下のように進めていきます。

・環境構築
・ElectronのHello World実装(基礎)
・Angular2のHello World実装(応用)

1. Electron環境を整える

node.jsのインストール

Electronはnode.js上で動作するため、まずはnode.jsの環境を整えます。

(1). ダウンロード:まずはnode.jsをダウンロードします。
https://nodejs.org/en/ v6.9.2 LTS

pct1

(2). 展開:好きなディレクトリで展開します。

(3). シンボリックリンクを貼る:名前が長いので、短くわかりやすくします。

% ln -s node-v6.9.2-linux-x64 node

(4). PATHを通す
このままだと今いるディレクトリでしかnode.jsを使えないので、どこでも使えるようにパスを通します。
.bashrcに以下を記載します。

export NODE_HOME=$HOME/node
export NODE_PATH=$NODE_HOME/lib/node_modules
export PATH=$NODE_HOME/bin:$PATH

記載したら適用してください。

% source .bashrc

(5). 動作確認
以上でnode.jsのインストールは終わりです。動作確認をしてみます。

% node --version
% npm --version

どちらもバージョンが表示されれば成功です。

Electronのインストール

(1). npmを用いてインストールする

% npm -g install electron-prebuilt

これで終わりです。

2.Electronのプロジェクトを作ってみる

Electronが実行できるプロジェクトを作成します。

流れとしては、
・作業ディレクトリを作成する
・node.js用に初期化する
・Electronの設定をする
となります。

まずは、以下のコマンドを実行します。

% mkdir hello_world
% cd hello_world
% npm init -y // node.js用の初期化

すると、以下のような階層になったかと思います。

hello_world
└package.json

このpackage.jsonの内容が、node.jsで使用する内容となります。

pct2

ここに、Electronの実行に必要なindex.htmlとmain.jsを追加して、以下のような階層にします。

hello_world
├index.html
├main.js
└package.json

index.htmlは内容、main.jsは画面周りの設定になります。

pct3

それぞれ、以下のサンプルコードを書いてみてください。

[index.html]

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>あいさつ</title>
  </head>
  <body>
    <h1>Hello World!</h1>
  </body>
</html>

[main.js]

'use strict';

var electron = require('electron');
var app = electron.app;
var BrowserWindow = electron.BrowserWindow;

var mainWindow = null;

// 画面がすべて閉じたらアプリを終了する
app.on('window-all-closed', function() {
    if (process.platform != 'darwin')
    app.quit();
});

app.on('ready', function() {

    // ブラウザ(Chromium)の起動, 400 * 300
    mainWindow = new BrowserWindow({width:300, height: 400});
    // 最初に読み込む画面のパスを指定
    mainWindow.loadURL('file://' + __dirname + '/index.html');

    mainWindow.on('closed', function() {
        mainWindow = null;
    });
});

また、package.jsonも以下のように書き換えてください

[package.json]

{
  "name": "hello_world",
  "version": "1.0.0",
  "description": "",
  "main": "main.js", // ここをindex.js → main.js にする
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

この状態で実行準備が整いました。 以下のコマンドで実行です。

electron .

pct4

どうですか?Hello Worldと表示されましたか?

3.Angular2を使ってみる

先ほど作成したElectronの内容を拡張したいと思います。
今度は、Hello WorldをAngular2のコンポーネントを用いて表示させたいと思います。

Angular2を使う上での流れは次の通りです。
(1).HTMLを書き換える。
(2).componentを作成する。
(3).NgModuleに1で作成したコンポーネントを登録する。
(4).index.tsにエントリポイントを記述する。
(5).コンパイルする。

Angular2はにコンパイルするとJavascriptを生成する言語を用いる必要があるので、TypeScriptを使ってみます。
今回導入したコンパイラはbrowserifyです。

準備

Angular2をインストールする

% npm i -S @angular/{core,common,compiler,platform-browser,platform-browser-dynamic} rxjs@5.0.1 zone.js@0.7.2 core-js
% npm i -D typescript browserify

UNMET PEER DEPENDENCY
と出ているものは、足りていないパッケージがあったり、バージョンがずれているということです。
個別で同様にインストールしてください。

今回の実装当初、以下のような表示がでました。

pct5

なので、rxjsとzone.jsはバージョンを変更して再度インストールしました。
上記のインストールの方には修正してあります。

コンパイルの設定をする

package.jsonを以下のように書き換えます。

[package.json]

{
  "name": "sand2",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "tsc": "tsc -p .",
    "browserify": "browserify ./index.js -o ./bundle.js",
    "build": "npm run tsc && npm run browserify"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "browserify": "^13.1.1",
    "typescript": "^2.1.4"
  },
  "dependencies": {
    "@angular/common": "^2.4.1",
    "@angular/compiler": "^2.4.1",
    "@angular/core": "^2.4.1",
    "@angular/platform-browser": "^2.4.1",
    "@angular/platform-browser-dynamic": "^2.4.1",
    "core-js": "^2.4.1",
    "rxjs": "^5.0.0-beta.12",
    "zone.js": "^0.6.21"
  }
}

[tsconfig.json]

{
  "compilerOptions": {
    "target": "es5",
    "noImplicitAny": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "moduleResolution": "node"
  },
  "filesGlob": [
    "./**/*.ts",
    "!./**/*.d.ts",
    "!./node_modules/**/*",
    "./node_modules/typescript/lib/lib.es6.d.ts"
  ],
  "files": [
    "./index.ts",
    "./node_modules/typescript/lib/lib.es6.d.ts"
  ]
}

(1).HTMLを書き換える。

まず、先ほどのindex.htmlを次のように書き換えてください。

[index.html]

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>あいさつ</title>
  </head>
  <body>
    <hello-world>Loading…</hello-world>
    <script src="./bundle.js"></script>
  </body>
</html>

この状態で実行してみてください。

pct6

「…Loading」とだけ表示され、Hello Worldとは表示されないと思います。
これからAngular2を使ってコンポーネントを作成し、Hello Worldと表示されるようにしたいと思います。

着地点は
・コンポーネントが<hello-world></hello-world>で読み込まれること
・<script src=”./bundle.js”></script>でコンパイルした内容を読み込むこと
です。

(2).componentを作成する。

ファイル名をapp.component.tsとする以下の内容のTypeScriptファイルを作成してください。

[app.component.ts]

import { Component } from '@angular/core';

@Component({
  selector: 'hello-world',
  template: `</pre>
             <h1>Hello World</h1>
             <pre>`
})
export class AppComponent {}

これはAppComponentというコンポーネントです。
selectorで「hello-world」と指定しているので、
HTML内で「<hello-world></hello-world>」と呼び出すことができるようになりました。

また、ここで実行される内容はtemplateに記載している「<h1>Hello World</h1>」となります。

(3).NgModuleに2で作成したコンポーネントを登録する。

app.module.tsというファイルを作成し、以下のように記述します。

[app.module.ts]

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';          // ポイント1

@NgModule({
  imports: [
    BrowserModule
  ],
  declarations: [
    AppComponent       // ポイント2
  ],
  bootstrap: [AppComponent]        // ポイント3
})
export class AppModule {}

基本的には決まり文句なので、注目すべきはポイント1~3の3箇所です。

ここでは先ほど作成したHello Worldを表示するコンポーネントであるAppComponentについての指示を記述します。

まず、importで読み込むコンポーネント名とそのソースの場所を指定しています。
次にNgModule内のdeclarationsで実際に使用するコンポーネントを指定します。

そして、bootstrapでアプリケーションのエントリポイントとなるコンポーネントを指定します。
これらは、全てまとめて「AppModule」というモジュールとして管理されることになります。

(4).index.tsにエントリポイントを記述する。

index.tsというファイルを作成し、以下のように記述します。

[index.ts]

import 'core-js';
import 'zone.js/dist/zone';

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';            // ポイント1

platformBrowserDynamic().bootstrapModule(AppModule);      // ポイント2

やはり、ほとんど決まり文句ですが、注目すべきは「AppModule」を指定しているところです。

importで読み込むモジュールを指定し、
platformBrowserDynamic().bootstrapModule(AppModule)
でエントリポイントを登録します。

この段階で作業ディレクトリ内はこのような状態になっていると思います。

pct7

(5).コンパイルする。

この状態でコンパイルしてみます。

% npm run build

pct8

ごそっとファイルが増えたと思います。
これでTypeScriptで書かれたファイルがJavascriptに変換されたので、実行してみましょう。

pct9

「…Loading」が「Hello World」に変わったでしょうか?

おまけ:実行ファイルを生成してみる。

簡単に配布ファイルを生成できるツールを導入します。

% npm install --save-dev electron-packager

次に、package.jsonに以下を書き加えます。

[package.json]

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "tsc": "tsc -p .",
  "browserify": "browserify ./index.js -o ./bundle.js",
  "build": "npm run tsc && npm run browserify",
  "package-build": "electron-packager . --platform=linux --arch=x64 --version=0.37.8" // -platform= でプラットフォームを指定します。今回はlinuxです。
},

以下のコマンドで実行します。

% npm run package-build

すると、ディレクトリ内に新しくフォルダーができたと思います。
内部に実行ファイルがありますので、ダブルクリックで実行してみてください。

4.まとめ

以上のようにElectronを用いると、デスクトップアプリの基本的な部分に関してはとても簡単に作れました。
途中Angular2を使うことで多少複雑にはなりましたが、基礎の書き方さえ理解できれば応用が利く技術だと思います。

また、Web技術だけで作れるというのもあって、比較的思い立ったらすぐに手を出しやすいというのも魅力的です。
日ごろのふとした瞬間の思いつきをちょっとした時間で形にできるので、ぜひ使ってみてください。