Electron

初めてのElectron

Electron

Electronとは

  • 旧)Atom Shell
  • Node.jsに基づいたDesktop Application Flatform
    • HTML, CSS, JavaScriptで、Cross-Flatformで 動くDesktop Applicationを作れる
    • WebPageのGUIとjavascriptで操作するChromium Browser
  • Desktop Application : 似た技術としてNW.js (node-webkit)が存在
    • 技術的な違いについては、ここを参考
  • Electronで作られたアプリについては、Awesome Electron 参照

環境構築手順

目標

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

基本設定

  • 装置1スペック

    • CPU : i3-4000M 2.40GHz
    • RAM : 4.0GB
    • OS : Microsoft Windows 8.1 Enterprise K 64Bit
    • Electron Version: 0.36.8
  • 装置2スペック

    • CPU : i3-3240 3.40GHz
    • RAM : 4.0GB
    • OS : Microsoft Windows 7 Professional SP1 32Bit
    • Electron Version: 0.36.8

下準備

  • Node.js : Stable Version (v.5.7.1)
    • npmを使うので、Node.jsの設置は必須!
    • Windowsのバージョンに合わせて、msiファイルをダウンロードし、設置するだけでOK
  • Node.jsの設置確認
    • cmd > node
    • 簡単なjavascriptコードを入力し、テスト

cmdTest.png

  • jsファイルでのテスト : app.js (エンコード:UTF-8)
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/");

nodeJSTest2.png

Electronのインストール

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

inst.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 initpackage.jsonを作成
    • 何を言ってるのか全然理解できなかったら、ただEnterだけ押せばほとんどOK (ここではプロジェクト名をelectronに設定する)
    • ただ、entry pointには必ず「main.js」を設定すること!
    • もしpackage.jsonmain scripttが設定されてない場合、 Electronは自動で同じディレクトリーのindex.jsをロードする

initProject.png

  • 上の作業で、下のような「package.json」が生成される
/**
 * 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」みたいな物を出力してみよう。
initmain script「main.js」で設定したので、main.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.jsindex.htmlを呼ぶことにしたので、今度はindex.htmlを作成する。
実際目に見える部分はここで作成する。

  • index.html
<!--
/*
 * index.html
 */
 -->
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Sample</title>
</head>
<body>
  <p>Hello, Electron!</p>
</body>
</html>

これで準備はOK。
実行してみよう。

sample.png

Packaging

  • プロジェクトを誰でも使えるように、パッケージ化してみよう。
  • ここでは、electron-packagerを使って作業。 これもnpmでもってこればOK

packager.png

これでダウンロード終わり。

  • 早速使ってみよう

packager2.png

この通り、electron-win32-ia32とelectron-win32-x64フォルダが生成された。
書いた内容を説明すると…

platform : all, linux, win32, darwin
arch : all, ia32, x64
version : 今は要らなくなったので、上のScreenShotに書いてあるのは無視

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

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

  • 実行

run.png

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

Electronでのアプリケーション開発

0. Source Code

  • これを書いてから時間もたって、Electronにもちょっと変化があった。
  • 自分のGithubに新バージョンのソースを載せといたので、それを参照

1. 目標

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

2. 準備

2-1. Atom

  • Atom ダウンロード (https://atom.io) Windowsのバージョンに合わせてインストールするだけでOK
  • 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

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

  • 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, JQueryをダウンロードして入れる。

3-2. プロジェクトソース

  • package.json
{
  "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
/**
 * 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
/**
 * 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
<!--
/*
 * 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
<!--
/*
 * 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
<!--
/*
 * 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
/**
 * 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.jsondevDependenciesとして定義されているmoduleを全部インストールする。
    • C:\PROJECTにてnpm install
  • Atom Minifymemo.min.js作成
    • atom-minifyの設定で「Minify on save」を設定すると、保存する時に自動でmemo.min.jsを生成
    • この画面が出ればOK。

success_minify.png

  • Grunt RunnerGruntDefaultで動かす
    • Gruntは勉強用で入れたのがほとんどで、実際この作業で必須なのはincludesだけ。
    • link.htmlmemo.htmlindex.htmlに含め、/app/view/index.htmlを作成
    • この画面が出ればOK。

grunt_success.png

  • C:\PROJECTにてelectron memo/で動作確認
    • この画面が出ればOK。

run_electron.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

まとめ

すごく簡単!

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

Creatorとしての喜び

  • こうやって簡単にアプリを作れるから、創作意欲が沸く。
  • 自分に必要な物は、自分で作ってみよう!

参考