初めてのElectron


# Electron
## Electronとは
- 旧)Atom Shell
- Node.jsに基づいた**Desktop Application Flatform**
 - HTML, CSS, JavaScriptで、Cross-Flatformで 動くDesktop Applicationを作れる
 - WebPageのGUIとjavascriptで操作する**[Chromium Browser](https://www.chromium.org/Home)**
- Desktop Application : 似た技術として**[NW.js (node-webkit)](http://nwjs.io/)**が存在
 - 技術的な違いについては、[ここ](http://qiita.com/SFPGMR/items/afe8ac0bfad95e5ed50d)を参考 
- Electronで作られたアプリについては、**[Awesome Electron](https://github.com/sindresorhus/awesome-electron)** 参照

## 環境構築手順
### 目標
- **Windows**での**Electron**開発環境構築
- Electronで**Hello World**の出力
- パッケージングでexeファイルを作成、動作確認

### 基本設定
- 装置１スペック
 - CPU : i3-4000M 2.40GHz
 - RAM : 4.0GB
 - OS : Microsoft Windows 8.1 Enterprise K 64Bit
 - Electron Version： 0.36.8

- 装置２スペック
 - CPU : i3-3240 3.40GHz
 - RAM : 4.0GB
 - OS : Microsoft Windows 7 Professional SP1 32Bit
 - Electron Version： 0.36.8

### 下準備
- **[Node.js](https://nodejs.org/en/#download)** : Stable Version (v.5.7.1)
 - npmを使うので、**Node.js**の設置は必須！
 - Windowsのバージョンに合わせて、msiファイルをダウンロードし、設置するだけでＯＫ
- **Node.js**の設置確認
 - cmd > node
 - 簡単なjavascriptコードを入力し、テスト

  ![cmdTest.png](https://qiita-image-store.s3.amazonaws.com/0/114660/9538d2ae-2bd3-1729-b39b-362c2baf2213.png)
 
 - jsファイルでのテスト : **app.js** (エンコード：UTF-8)
 
```javascript
var http = require('http');
 http.createServer(function(req,res){
   res.writeHead(200,{'Content-Type':'text/plain'});
   res.end('Hello World\nHello node.js!');
 }).listen(1337,"127.0.0.1");
 
 console.log("Server running at http://127.0.0.1:1337/");
```

  - Node.jsでapp.jsを実行し、指定したIP ([http://127.0.0.1:1337](http://127.0.0.1:1337))で確認
 
   ![nodeJSTest2.png](https://qiita-image-store.s3.amazonaws.com/0/114660/54f02471-171f-756b-d077-7d29e3460da3.png)

### Electronのインストール
- npmを使って、**electron-prebuilt**をダウンロード
- Globalで設置したので、どこでプロジェクトを作成しても**「electron プロジェクト名/**」で実行できる。

  ![inst.png](https://qiita-image-store.s3.amazonaws.com/0/114660/c9623d70-d763-90e2-532c-e40d8f821297.png)

### Electronの実行
#### Electron Appの構造

```
your-app/
├── package.json : main fieldにscript fileを指定し、main processのエントリーポイントとして使用
├── main.js      : Windowを作り、システムイベントを処理
└── index.html   : ユーザに見せるページ
```

#### プロジェクト生成
- Electronは、実行されるときに「package.json」のmain scriptを呼び出す。
 - **main script**：main processで作動し、GUI Component操作・Web Page生成
- **npm init**で**package.json**を作成
 - 何を言ってるのか全然理解できなかったら、ただ**Enter**だけ押せばほとんどＯＫ 
  (ここではプロジェクト名を**electron**に設定する)
 - ~~ただ、**entry point**には必ず「**main.js**」を設定すること！~~
 - **entry point**は**index.js**でも構わない（2019/06/04追記）
 - もし**package.json**に**main script**tが設定されてない場合、
   Electronは自動で同じディレクトリーの**index.js**をロードする
 
 
  ![initProject.png](https://qiita-image-store.s3.amazonaws.com/0/114660/94b55d28-f7c0-1732-f0e6-3a8828946064.png)


- 上の作業で、下のような「package.json」が生成される


```javascript
/**
 * package.json
 **/ 
{
  "name": "electron",
  "version": "1.0.0",
  "description": "print \"Hello, Electron\"",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Youngjae Kwon",
  "license": "ISC"
}
```

#### Hello, Electron!
一旦準備が終わったので、いつもの通り**「Hello World」**みたいな物を出力してみよう。
**init**で**main script**を**「main.js」**で設定したので、**main.js**から作成することに。

```js
/**
 * main.js
 **/ 
// アプリケーション基盤をコントロールするモジュール
const {app} = require('electron');
 
// ブラウザーウィンドーを作るモジュール
const {BrowserWindow } = require('electron');
 
// ウィンドーオブジェクトを全域に維持
let mainWindow = null;
 
// すべてのウィンドーが閉じられたら呼び出される (アプリケーション終了)
app.on('window-all-closed', function() {
  if (process.platform != 'darwin') {
    app.quit();
  }
});
 
// Electronの初期化が完了し、ブラウザーウィンドーを開く準備ができたら実行
app.on('ready', function() {
  // 新しいブラウザーウィンドーを生成
  mainWindow = new BrowserWindow({width: 800, height: 600});
 
  // 今のディレクトリーで「 index.html」をロード
  mainWindow.loadURL('file://' + __dirname + '/index.html');
 
  // ウィンドーが閉じられたら呼び出される  (アプリケーション終了)
  mainWindow.on('closed', function() {
    // ウィンドーオブジェクトの参照を削除
    mainWindow = null;
  });
});
```

**main.js**が**index.html**を呼ぶことにしたので、今度は**index.html**を作成する。
実際目に見える部分はここで作成する。

- **index.html**

```html
<!--
/*
 * index.html
 */
 -->
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Sample</title>
</head>
<body>
  <p>Hello, Electron!</p>
</body>
</html>
```
これで準備はＯＫ。
実行してみよう。

  ![sample.png](https://qiita-image-store.s3.amazonaws.com/0/114660/c5622a0e-74ae-93a7-7390-d0e18e238ae2.png)

#### Packaging
- プロジェクトを誰でも使えるように、パッケージ化してみよう。
- ここでは、**[electron-packager](https://github.com/electron-userland/electron-packager)**を使って作業。
これも**npm**でもってこればＯＫ

  ![packager.png](https://qiita-image-store.s3.amazonaws.com/0/114660/d4f682f7-fba3-3b5c-5bc4-a7709c1bf286.png)
 
 これでダウンロード終わり。


- 早速使ってみよう

  ![packager2.png](https://qiita-image-store.s3.amazonaws.com/0/114660/0886a369-3c43-60d1-0836-bac84197bbd9.png)

  この通り、electron-win32-ia32とelectron-win32-x64フォルダが生成された。
  書いた内容を説明すると…
 
  platform : all, linux, win32, darwin
  arch : all, ia32, x64
  ~~version : 今は要らなくなったので、上のScreenShotに書いてあるのは無視~~
  electron-version : electronのversionを記載（2019/06/04 追記）
  ※electronのversionは、`electron --version`で確認可能

  ここでallを選んだので、フォルダが32と64の二つができた。

  もし32bitWindowsの装置を使ってるとしたら、実は
  `electron-packager ./electron electron --platform=win32 --arch=ia32 --electron-version=1.4.13`
  これでＯＫ。

- 実行

  ![run.png](https://qiita-image-store.s3.amazonaws.com/0/114660/c81c17b5-33ca-f6dd-230b-801dadf8446c.png)

  プロジェクト名をElectronにしてしまって、ちょっとわかり辛くなったが…
  こうやってEXEファイルができ、それを実行したら普通にアプリが動く。

## Electronでのアプリケーション開発
### 0. Source Code
- これを書いてから時間もたって、Electronにもちょっと変化があった。
- 自分のGithubに新バージョンのソースを載せといたので、それを参照
 - [Electron-Notepad](https://github.com/idenrai/electron-notepad)

### 1. 目標
- 簡単なメモ帳アプリの作成 (テキストの保存とロード機能)
- WindowsでElectronを使うこと
- プロジェクト作成には**Atom**と**Grunt**を使ってみること
- パッケージングでEXEファイルを作り、動作を確認

### 2. 準備
#### 2-1. Atom
- Atom ダウンロード (https://atom.io)
 Windowsのバージョンに合わせてインストールするだけでＯＫ
- Packageの設置 (File→Settings→InstallでPackage検索)
 - **linter** : 文法上のエラー表示
 - **grunt-runner** : AtomでGrunt実行
 - **minimap**, **minimap-find-and-replace** : minimapが見える
 - **atom-minify** : JS, CSSの圧縮化 (ctrl+shift+m or 「Minify on save」設定)

![package-atom.png](https://qiita-image-store.s3.amazonaws.com/0/114660/7961b67e-3203-7b9e-efbb-8ed7bb134693.png)

#### 2-2. Grunt
- **Grunt**とは？
 - プロジェクト自動化のための**Command Line Build Tool**
 - **パッケージ管理者** (Yeoman , Bower , Gulp等と同じ)
 - もっとも基本的な自動化パッケージ

- **grunt-cli (Grunt's Command Line Interface) 設置**
 - grunt-cliの設置で、システム経路に「grunt」コマンドが追加され、gruntが使えるようになる。
 - grunt-cliの役割はただ**Gruntflie**というファイルがある場所に設置されたGruntを実行するだけ。

![grunt-cli.png](https://qiita-image-store.s3.amazonaws.com/0/114660/4f6a6aa6-c214-c80c-6f92-ba90c957b50c.png)

- **Grunt Module 設置方法** (package.jsonがあるディレクトリで設置)
 - 新しいModuleを設置し、package.jsonに記入 : `npm install grunt --save dev`
 - すでにpackage.jsonに記入されているModuleを設置 : `npm install`
  (package.jsonとGruntfile.jsがあれば、どこでも同じgrunt作業が可能！)
 - ここでは2の方法でプロジェクト作成

### 3. Project作成
#### 3-1. ディレクトリ構成図

```
C:\PROJECT\MEMO
│  Gruntfile.js
│  main.js
│  package.json
│  
├─app
│  ├─css
│  │      bootstrap-theme.css
│  │      bootstrap-theme.css.map
│  │      bootstrap-theme.min.css
│  │      bootstrap.css
│  │      bootstrap.css.map
│  │      bootstrap.min.css
│  │      
│  ├─fonts
│  │      glyphicons-halflings-regular.eot
│  │      glyphicons-halflings-regular.svg
│  │      glyphicons-halflings-regular.ttf
│  │      glyphicons-halflings-regular.woff
│  │      glyphicons-halflings-regular.woff2
│  │      
│  ├─html
│  │  │  index.html
│  │  │  
│  │  └─include
│  │          link.html
│  │          memo.html
│  │          
│  ├─js
│  │  │  memo.js
│  │  │  memo.min.js
│  │  │  
│  │  └─lib
│  │          bootstrap.js
│  │          bootstrap.min.js
│  │          jquery-1.12.1.js
│  │          jquery-1.12.1.min.js
│  │          npm.js
│  │          
│  └─view
│      │  index.html
│      │  
│      └─include
│              link.html
│              memo.html
│              
├─dist
│  ├─css
│  │      style.min.css
│  │      
│  └─js
│          site.js
│          site.min.js
│          
└─node_modules
    ├─grunt
    ├─grunt-cache-breaker
    ├─grunt-contrib-cssmin
    ├─grunt-contrib-uglify
    ├─grunt-includes
    ├─load-grunt-tasks
    ├─moment
    └─time-grunt                        
```

- css・fonts・libフォルダには、[Bootstrap](http://bootstrapk.com/getting-started/#download), [JQuery](http://jquery.com/download/)をダウンロードして入れる。

#### 3-2. プロジェクトソース
- **package.json**

```js
{
  "name": "memo",
  "version": "1.0.0",
  "description": "save and load txt file",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Youngjae Kwon",
  "license": "ISC",
  "devDependencies": {
    "electron": "^1.6.11",
    "grunt": "^0.4.5",
    "grunt-cache-breaker": "^2.0.1",
    "grunt-contrib-cssmin": "^1.0.0",
    "grunt-contrib-uglify": "^1.0.0",
    "grunt-includes": "^0.5.4",
    "load-grunt-tasks": "^3.4.1",
    "moment": "^2.8.3",
    "time-grunt": "^1.3.0"
  }
}
```

- **main.js**

```js
/**
 * main.js
 **/
'use strict';
const {app, BrowserWindow} = require('electron');
let mainWindow = null;

app.on('window-all-closed', function() {
  if (process.platform != 'darwin') {
    app.quit();
  }
});

app.on('ready', function() {
  mainWindow = new BrowserWindow({width: 550, height: 410});
  mainWindow.loadURL('file://' + __dirname + '/app/view/index.html');
  mainWindow.on('closed', function() {
    mainWindow = null;
  });
});
```

- **Gruntfile.js**

```js
/**
 * Gruntfile.js
 **/
module.exports = function(grunt) {
    'use strict';
    var moment = (require('moment'))();
    var timestamp = 'None';

    // 自動でgrunt Taskをロードする。(grunt.loadNpmTasksは省略可能)
    require('load-grunt-tasks')(grunt);

    // 作業時間表示
    require('time-grunt')(grunt);

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),

        // html task
        includes: {
            files: {
                cwd: 'app/html/',    // app/htmlのhtmlファイルにinclude処理をして
                src: ['**/*.html'],
                dest: 'app/view/',    // その結果をapp/viewに入れる
                options: {
                    flatten: true,
                    debug: true,
                    includePath: 'app/html/'
                }
            }
        },
        // css task
        cssmin: {
            options: {
                keepSpecialComments: 1,
            },
            dist: {
                src: 'dist/css/style.css',
                dest: 'dist/css/style.min.css'
            }
        },
        // js task
        uglify: {
            options: {
                banner: '<%= banner %>'
            },
            dist: {
                src: 'dist/js/site.js',
                dest: 'dist/js/site.min.js'
            }
        },
        cachebreaker: {
          dev: {
            options: {
              match: ['.js'],
              replacement: function () {
                return moment.format('YYYYMMDDhhmmss');
              }
            },
            files: {
              src: ['app/html/*.html', 'app/view/*.html']
            }
          }
        }
    });

    // html task
    grunt.registerTask('html', ['includes']);
    // css task
    grunt.registerTask('css', ['cssmin']);
    // javascript task
    grunt.registerTask('js', ['uglify', 'cachebreaker']);

    // default task
    grunt.registerTask('default', ['html', 'css', 'js']);
};
```

- **index.html**

```html
<!--
/*
 * index.html
 */
 -->
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Memo MK2</title>
  <link href="../css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
  include "include/link.html"
  include "include/memo.html"
</body>
</html>
```

- **link.html**

```html
<!--
/*
 * link.html
 */
 -->
<script src="../js/lib/jquery-1.12.1.min.js"></script>
<script src="../js/lib/bootstrap.min.js"></script>
<script type="text/javascript" src="../js/memo.min.js"></script>
```

- **memo.html**

```html
<!--
/*
 * memo.html
 */
 -->
<table class="table table-striped">
  <tr>
    <td style="text-align: left; width: 100px;">テキスト入力</td>
    <td colspan="2" style="text-align:left; width:300px;">
      <textarea id="ntText" class="input-xxlarge" style="width:95%; height:200px;" maxlength="4000"></textarea>
    </td>
  </tr>
  <tr>
    <td>ファイル名</td>
    <td>
      <input type="text" class="form-control"  id="nmSaveFile" />
    </td>
    <td>
      <button class="btn btn-default btn-primary" onclick="saveTxt()">保存する</button>
    </td>
  </tr>
  <tr>
    <td>ファイル選択</td>
    <td>
      <input type="file" class="form-control"  id="nmLoadFile" />
    </td>
    <td>
      <button class="btn btn-default btn-primary" onclick="loadTxt()">ロードする</button>
    </td>
  </tr>
</table>
```

- **memo.js**

```js
/**
 * memo.js
 **/
// テキスト保存
function saveTxt(){
  var ntText = document.getElementById("ntText").value;
  var ntBlobText = new Blob([ntText], {type:'text/plain'});
  var nmSaveFile = document.getElementById("nmSaveFile").value;
  var saveLink = document.createElement("a");
  saveLink.download = (nmSaveFile === null || nmSaveFile == "") ? "memo.txt" : nmSaveFile + ".txt";
  saveLink.innerHTML = "Download File";
  saveLink.href = window.webkitURL.createObjectURL(ntBlobText);
  saveLink.click();
}

// テキストロード
function loadTxt(){
  var nmLoadFile = document.getElementById("nmLoadFile").files[0];
  var fileReader = new FileReader();
  fileReader.onload = function(fileLoadedEvent){
    var ntLoadText = fileLoadedEvent.target.result;
    document.getElementById("ntText").value = ntLoadText;
  };
  fileReader.readAsText(nmLoadFile, "UTF-8");
}
```

#### 3-3. プロジェクト作成
- **node_modules** インストール
 - **package.json**に**devDependencies**として定義されているmoduleを全部インストールする。
 - `C:\PROJECT`にて`npm install`
- **Atom Minify**で**memo.min.js**作成
 -  **atom-minify**の設定で「Minify on save」を設定すると、保存する時に自動でmemo.min.jsを生成
 - この画面が出ればＯＫ。 
  
![success_minify.png](https://qiita-image-store.s3.amazonaws.com/0/114660/8f5e7196-8a67-4c43-b506-387b6b29733d.png)


- **Grunt Runner**で**Grunt**を**Default**で動かす
 - Gruntは勉強用で入れたのがほとんどで、実際この作業で必須なのは**includes**だけ。
 - **link.html**、**memo.html**を**index.html**に含め、**/app/view/index.html**を作成
 - この画面が出ればＯＫ。
  
![grunt_success.png](https://qiita-image-store.s3.amazonaws.com/0/114660/948088db-b20f-aca4-4e34-10c76f657e1e.png)


- `C:\PROJECT`にて`electron memo/`で動作確認 
 - この画面が出ればＯＫ。
  
![run_electron.png](https://qiita-image-store.s3.amazonaws.com/0/114660/630480d2-b2bf-8412-a4a1-84ef57289ace.png)


### 4. パッケージング・動作確認
#### 4-1. パッケージング
- **memoフォルダ**を**memoMK2**という名前でパッケージングする。

```
C:\Users\idenr\project>electron-packager ./memo memoMK2 --platform=win32 --arch=x64
Packaging app for platform win32 x64 using electron v1.6.11
Wrote new app to C:\Users\idenr\project\memoMK2-win32-x64
```
#### 4-2. 作動確認

![complete.png](https://qiita-image-store.s3.amazonaws.com/0/114660/eed83335-c18b-b816-244c-158be4a3d9c4.png)


## まとめ
### すごく簡単！
- Web Pageを作る感覚で、普通にDesktop Applicationが作れる。
- 環境設定に時間がかからないのもメリット
 - 以前のHadoopの時を考えたら…
 - 特にWindowsでも簡単にできるのが嬉い。

### Creatorとしての喜び
- こうやって簡単にアプリを作れるから、創作意欲が沸く。
- 自分に必要な物は、自分で作ってみよう！

## 参考
- [Electron Github](https://github.com/atom/electron)
- [electronとnw.jsの技術的な違い](http://qiita.com/SFPGMR/items/afe8ac0bfad95e5ed50d)
- [30分で出来る、JavaScript (Electron) でデスクトップアプリを作って配布するまで](http://qiita.com/nyanchu/items/15d514d9b9f87e5c0a29)
- [Outsider's Dev Story](http://blog.outsider.ne.kr/1169)
- [Grunt 가이드](https://github.com/demun/demun.github.com/wiki/01_00_grunt_guide)