LoginSignup
18
11

More than 3 years have passed since last update.

はじめに

NTTテクノクロス Advent Calendar 2020の14日目担当 @geekduck です🦆

最近はGolangでAPIサーバー、React.jsでSPA、そのSPAを開くElectron製常駐アプリを作るプロジェクトに関わっています。

そのプロジェクトの中で調べた、ElectronのTipsをいくつか紹介したいと思います。

以下のサンプルコードが動作するElectronアプリのソースコード: https://github.com/geekduck/electron-tips

この投稿で使用している言語、ライブラリのバージョン

  • Node.js v14.15.1
  • npm v6.14.9
  • Electron v11.0.4
  • TypeScript v4.1.2

TypeScriptでElectronアプリを作りたい&ホットリロードもしたい

TypeScript化

最近はTypeScriptが完全にデファクトになったので、ElectronもTypeScriptで書きたいですよね。

electron-quick-startをベースにTypeScript化するサンプルを載せます。

$ https://github.com/electron/electron-quick-start
$ npm i -D typescript
$ touch tsconfig.json

TypeScriptの設定ファイルをこんな感じで作ります。
srcディレクトリ以下のTypeScriptファイルをビルドして、buildディレクトリにJavaScriptファイルを出力します。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2020",
    "module": "commonjs",
    "sourceMap": true,
    "strict": true,
    "outDir": "./build",
    "rootDir": "./src",
    "noEmitOnError": true,
    "typeRoots": [
      "node_modules/@types"
    ]
  }
}

buildディレクトリにビルドしたJavaScriptファイルが出力されるので、package.jsonのmainプロパティの値を.build/main.jsに変更します。

package.json
{
  "name": "electron-typescript-sample",
  "version": "1.0.0",
  "main": "./build/main.js",
  "scripts": {
    "start": "electron ."
  },
  "devDependencies": {
    "electron": "^11.0.3",
    "typescript": "^4.1.2"
  }
}

index.htmlにもビルド前のパスが書かれているので変更します。

index.html
<!-- 17行目あたり -->
<script src="./build/renderer.js"></script> <!-- ./renderer.js のパスをbuildディレクトリからにする -->

JavaScriptファイルをsrcディレクトリに移動しつつTypeScript化します。

$ mv main.js src/main.ts
$ mv preload.js src/preload.ts
$ mv renderer.js src/renderer.ts
src/main.ts
import { app, BrowserWindow, Menu, Tray } from "electron"; // requireをimportに変更した
import * as path from "path";
// 以降のコードはそのままのため省略
src/preload.ts
window.addEventListener('DOMContentLoaded', () => {
  const replaceText = (selector:string, text:string) => {
    const element = document.getElementById(selector)
    if (element) element.innerText = text
  }

  for (const type of ['chrome', 'node', 'electron']) {
    replaceText(`${type}-version`, (process.versions as any)[type])
  }
})

renderer.jsは拡張子を.tsに変更するだけでOKです。

TypeScriptをビルドしてみます。

$ ll build/
# ファイルが何もない

$ npx tsc

$ ll build/                                                                                                                                                                                
total 48
-rw-r--r--  1 user  staff   1.5K 12 10 09:24 main.js
-rw-r--r--  1 user  staff   982B 12 10 09:24 main.js.map
-rw-r--r--  1 user  staff   403B 12 10 09:24 preload.js
-rw-r--r--  1 user  staff   497B 12 10 09:24 preload.js.map
-rw-r--r--  1 user  staff   343B 12 10 09:24 renderer.js
-rw-r--r--  1 user  staff   183B 12 10 09:24 renderer.js.map

ビルドできていますね。

Electronを立ち上げるとちゃんと表示されます。

$ npm start

スクリーンショット 2020-12-10 9.29.00.png

これでElectronをTypeScriptで作れるようになりました。

ホットリロード

次に、srcディレクトリ配下のファイルを書き換えるたびにtsc && npm startするのは面倒なのでホットリロード機能を追加します。

$ npm i -D electron-reload electron-is-dev npm-run-all
src/main.ts
import { app, BrowserWindow, Menu, Tray } from "electron";
import * as path from "path";
import * as isDev from "electron-is-dev";

// 開発モードの場合はホットリロードする。
if (isDev) {
  require("electron-reload")(__dirname, {
      electron: require("path").join(__dirname, "..", "node_modules", ".bin", "electron"),
      forceHardReset: true,
      hardResetMethod: "exit",
  });
}
// 以降のコードはそのままのため省略

package.jsonのscriptsを変更して、electronの起動と、TypeScriptファイルの監視をパラレルで起動するようにします。

package.json
{
  "name": "electron-tips",
  "version": "1.0.0",
  "main": "./build/main.js",
  "scripts": {
    "start": "run-p -r -l start:watch start:electron",
    "start:electron": "electron .",
    "start:watch": "tsc -w"
  },
  /* ...(省略)... */
}

npm startでアプリを起動して、適当なファイルを編集/保存するとElectronが自動でリロードされます。

Kapture 2020-12-10 at 14.37.45.gif

JavaScript内で実行環境がElectronかブラウザか判定する

現在関わっているPJではReact.jsでSPAを作り込んでHTTPサーバーで公開し、そのSPAに対してブラウザかElectronアプリでアクセスする構成をとっています。

Electronアプリはガワアプリとして最低限の機能(ウィンドウの表示/フォーカス、二重起動防止、常駐など)だけ作って、他の機能はSPA側で全て作るようにしています。

そのような構成の上で、ブラウザでSPAを開いた場合とElectronアプリでSPAを開いた場合にデザインを変更する必要があり、SPAのJavaScript内で実行環境がブラウザかElectronかを判定する方法がないかを調べました。

nodeIntegration: trueを指定してWebコンテンツを開いた場合、windowオブジェクトにいくつかのAPIが追加されるため、そのAPIの有無で実行環境がElectronかを判定します。

main.ts
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true, // レンダラープロセスのwindowオブジェクトにNode.jsのAPIを追加する。
      preload: path.join(__dirname, 'preload.js')
    }
  })
SPAのJavaScriptファイル
window?.process?.versions['electron']; 
// Electronで開いた場合 -> "11.0.3"
// ブラウザで開いた場合  -> undefined

nodeIntegration: trueを指定してしまうとセキュリティ的に不安が出てしまう場合は、contextBridgeを使いましょう。

main.ts
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: false,
      worldSafeExecuteJavaScript: true,
      contextIsolation: true,
      preload: path.join(__dirname, 'preload.js')
    }
  })
preload.ts
import { contextBridge } from "electron";

// レンダラープロセスのwindowにelectronというプロパティを作る。
contextBridge.exposeInMainWorld("electron", {
  isElectron: true,
});
SPAのJavaScriptファイル
window?.electron?.isElectron; 
// Electronで開いた場合 -> true
// ブラウザで開いた場合  -> undefined

SPA側でこのプロパティを使って表示するコンテンツを切り替えれば、ブラウザで開かれた場合とElectronで開かれた場合によってSPAの動作を変えることができます。

参考:
https://github.com/electron/electron/issues/2288
https://www.electronjs.org/docs/api/context-bridge
https://www.electronjs.org/docs/api/browser-window#new-browserwindowoptions

electron-builder紹介

Electronのアプリをビルドする場合、electron-packagerよりもelectron-builderを使うのがオススメです。

electron-packagerを使って*nix系OSでWindows向けのビルドを作ろうとするとwineが必要ですが、electron-builderでは不要です。

また、electron-builderでビルドするとWindows用のインストーラーやmacOS用のdmgまで作ってくれます。

$ npm i -D electron-builder

package.jsonのscriptsにビルド用のスクリプトを追加します。

また、TypeScript化しているため、buildディレクトリ配下をelectron-builderのビルド対象に指定してあげます。

package.json
{
  /* ...(省略)... */
  "scripts": {
    "start": "run-p -r -l start:watch start:electron",
    "start:electron": "electron .",
    "start:watch": "tsc -w",
    "build:typescript": "tsc",
    "build:installer:macOS": "electron-builder --mac --x64",
    "build:installer:windows": "electron-builder --win --x64",
    "build": "npm-run-all build:typescript --parallel build:installer:macOS build:installer:windows"
  },
  "build": {
    "files": [
        "*.html",
        "build/**/*"
    ]
  }
}

npm run buildを実行するとdistディレクトリ配下に成果物が出力されます。

$ npm run build
# ダウンロードとかビルドのログとか色々でる
$ ls -al dist/
total 441144
-rw-r--r--   1 user  staff   131B 12 10 15:39 builder-effective-config.yaml
-rw-r--r--   1 user  staff    53M 12 10 15:39 electron-tips Setup 1.0.0.exe # Windows用インストーラー
-rw-r--r--   1 user  staff    58K 12 10 15:39 electron-tips Setup 1.0.0.exe.blockmap
-rw-r--r--   1 user  staff    72M 12 10 15:40 electron-tips-1.0.0-mac.zip
-rw-r--r--@  1 user  staff    74M 12 10 15:39 electron-tips-1.0.0.dmg # macOS用インストーラー
-rw-r--r--   1 user  staff    81K 12 10 15:39 electron-tips-1.0.0.dmg.blockmap
-rw-r--r--   1 user  staff   527B 12 10 15:40 latest-mac.yml
-rw-r--r--   1 user  staff   354B 12 10 15:39 latest.yml
drwxr-xr-x   3 user  staff    96B 12 10 15:39 mac/ # acOS用インストーラーで配置される.appファイル
drwxr-xr-x  21 user  staff   672B 12 10 15:39 win-unpacked/ # Windows用インストーラーで展開されるファイル群

おわりに

以上、ElectronのTipsの紹介でした。参考になれば幸いです💪

明日の担当は @moris4ki です。お楽しみに🎉

18
11
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
18
11