9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[Node.js] Electron、それは果てしなく「今日は世界!」が続く世界

Posted at

Electron、それは果てしなく「今日は世界!」が続く世界

0. 作ったもの

image.png

1.やりたかったのはWebアプリをWindowsのデスクトップアプリへ移植すること

 Node.jsを使い、MyDNSのユーザ情報を管理するWebアプリケーションを作成しました。しかし他人のユーザ情報を管理するのはさすがに怖いので、サービスとしては公開していませんでした。いっそデスクトップアプリにしてしまえば問題ないと思い立ち、Electronを使いパッケージ化することにしました。

2.Electronの利用方法を調べると、みんな挨拶しかしていない

 Node.jsをパッケージ化するための方法はいくつかありますが、一番まともそうな方法はElectronを使うことでした。ということで情報を調べていくと、ことごとくが「今日は世界」を表示するところで終わっています。「おまえら、どれだけ挨拶すれば気が済むんだ?」と心の中で叫ぶしかありませんでした。しかも、挨拶ラッシュが起こってからけっこう時代が進んでいるらしく、パッケージ化の情報が新旧入り交じっていました。

3.とにかく簡単に移植したい

 Electronをがっつり使いたいわけではありません。実質、ローカルWebServerと専用ブラウザとしての機能が使えれば良いのです。Electronでメインととレンダラを繋ぐためのプロセス間通信モジュールとかありますが、それを使った時点でWebアプリの移植に余計な改変を加えなければなりません。一切使わない方針を決めました。普通にAjaxが使えるんだから、移植だったらそのままいけば良いだけです。欠点があるとすればTCPのポートを一個、起動時に用意しなければならないぐらいです。

4.SQLite3の恐怖

 Electronはネイティブのライブラリが含まれているモジュールをそのまま動かしてくれないようです。大半の人が引っかかるであろうモジュールは、SQLite3だと思われます。ということで専用ビルドをかけなければなりません。コンパイルに必要な設定は以下の通りです。

  • Python2.7系統にpathを通す(先頭に記述しないとStoreが起動)
  • VisualStudioのインストール
    • VS2019の場合
      • C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin にパスを通す
      • インストーラを起動してVS2015のビルドツールの追加
  • npm install sqlite3 --build-from-source --save --runtime=electron --target=5.0.1 --dist-url=https://atom.io/download/electron

5. メインプロセスのconsole.logの文字化け

 Electronの動作をPowerShellから実行するとそのままではconsole.logで文字化けします。対処法はElectron実行前に、

chcp 65001

で、文字コードをUTF8にすることです。Electron利用時のconsole.logはデバッグ出力をするためなので、実際のところ日本語を出さなければ話は簡単です。

6. パッケージの作成

 以下の設定をpackage.jsonに追加し、electron-builderを実行します。electron-packagerの方は設定が不便なので使いません。

package.json
{

  "build": {
    "productName": "出力アプリケーション名",
    "appId": "hogehoge-hoge",
    "directories": {
      "output": "build"
    },
    "files": [
      "dist/**/*"
    ],
    "win": {
      "target": "nsis"
    }
  },
  
}

7. Webアプリ移植時に追加するコード

 余計な改変を最小限にするためバックエンド起動処理のindex.tsに、Electronのウインドウ表示処理を入れています。本来のWebアプリはバックエンドの処理のみを記述していました。この部分以外、Electron専用のソースは書いていません。

index.ts
import * as amf from 'active-module-framework'
import * as electron from 'electron'
import * as path from 'path'

const listenPort = 58621

/**
 * Electron用起動設定
 */
const app = electron.app
let window: electron.BrowserWindow|null = null
//Electronで実行されているかとUNIXドメインソケットを使用していないか確認
if (app) {
	//ウインドウが閉じられた場合の処理
	app.on("window-all-closed", async () => {
		if (process.platform != "darwin") {
			await manager.destory() //バックエンド処理を終了させる
			app.quit()				//アプリケーション終了
		}
	})
	//ウインドウ表示処理
	const init = () => {
		window = new electron.BrowserWindow({ width: 1280, height: 720, autoHideMenuBar: true })
		//起動メッセージの表示
		window.loadURL(`file://${path.resolve(__dirname,'../template/electron.html')}`)
	}
	//準備完了時に初期化
	if (app.isReady()) {
		init()
	} else {
		app.once("ready", () => {
			init()
		})
	}
}


/**
 * バックエンドが準備完了になった場合の処理
 */
const listened = (port: number | string|null)=> {
	if (window){
		if (port === null){
			//ポート使用中のエラー表示
			window.loadURL(`file://${path.resolve(__dirname, '../template/electron.html')}?cmd=error&port=${listenPort}`)
		}
		else if(typeof port === 'number'){
			window.loadURL(`http://localhost:${port}`)
		}
	}
}


/**
 * バックエンドの設定
 */
const manager = new amf.Manager({
	remotePath: '/',										//一般コンテンツのリモートパス
	execPath: '/',											//コマンド実行用リモートパス
	indexPath: path.resolve(__dirname, '../template/index.html'),//index.thmlテンプレート
	rootPath: path.resolve(__dirname, '../public'),			//一般コンテンツのローカルパス
	cssPath: ['css'],										//自動ロード用CSSパス
	jsPath: ['js'],											//一般コンテンツのローカルパス
	localDBPath: path.resolve(__dirname,'../db/app.db'),	//ローカルDBパス
	//localDBPath: path.resolve('app.db'),	//ローカルDBパス(カレントパスに設定)
	modulePath: path.resolve(__dirname, './modules'),		//モジュール配置パス
	jsPriority: [],											//優先JSファイル設定
	debug: false,											//デバッグ用メッセージ出力
	listened,												//初期化完了後コールバック
	//listen: listenPort									//受付ポート/UNIXドメインソケット
	listen: path.resolve(__dirname, '../sock/app.sock')		//UNIXドメインソケットを使用する場合
})

 ウインドウを表示後にバックエンド処理がlisten可能になったらページを切り替えています。Electronから起動していない場合はappにインスタンスが入らないので、Electronがらみの処理を行いません。こうやって書いておけば、通常のNodeプロセスとして起動しても動きます。その場合、通常のWebアプリモードとなります。

8. ビルド手順

 package.jsonのscriptが以下のようになっています。フロントエンドはWebPack、バックエンドはtsc、パッケージ化はelectron-builderを利用します。さらにSQLite3のインストールを忘れないように書いておきました。

package.json
{
・・・
  "scripts": {
    "start": "node dist/app/index.js ",
    "watch": "tsc -b -w",
    "build-app": "tsc -b",
    "build-front": "npx webpack",
    "build-electron": "npx electron-builder --win",
    "install-sqlite": "npm install sqlite3 --build-from-source --save --runtime=electron --target=5.0.1 --dist-url=https://atom.io/download/electron",
    "run": "electron ."
  },
・・・
}

 手数が多いというか、使っているものがどんどん増えていく感じがなんとも言えません。

9. 使っているもの

 Webアプリを素早く作成するため、オレオレフレームワークで固めました。フロントエンドは有名どころのVue.jsやReactが、私としては使いにくかったので全部自作です。

 Webアプリを作るときの理想は、フロントエンド側はJavaScriptを呼び出すための初期ページ以外はHTMLを書かないことです。バックエンド側の理想は、初期ページの吐き出し以外に一切のHTMLを出力せず、データのやりとりのみを記述することです。

10. まとめ

 最近のWebプログラムは、エコシステムが発達したおかげで、コマンド一発で便利なライブラリやフレームワークが手に入ります。入門記事を見ながら進めれば、なんとなく雰囲気をつかむところまでは到達できることでしょう。しかし「今日は世界」を卒業しようとすると、途端に情報量が少なくなり、その先は茨の道が待っています。何をやろうにも必要とする技術が多岐に渡り、初学者が始めようとすると、そこそこの地獄を見ているのではないでしょうか? 

 結局のところ試行錯誤して、自分なりの正解を見つけるしかありません。用意されているものを敢えて使わないという選択肢もあるのです。プログラムはやりたいことを書いていけば、思った通りには動きませんが書いた通りには動きます。「今日は世界」を突破して、新たな世界を開拓しましょう。そこはそもそも挨拶の必要が無い、誰もいない不毛の土地かもしれませんが。

9
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?