create-react-appとelectron-builderでTypeScriptとHot Reloadに完全対応したElectronアプリ開発環境を作成する


create-react-appで楽にReact+Electronアプリを書けるようになりました。("How to build an Electron app using Create React App and Electron Builder")

それをさらに推し進め、より安全・快適なReact+Electronアプリ開発環境を構築してみます。(長い)タイトルにある通り、目標は以下の通りです。

  • create-react-appでTypeScript対応プロジェクトをさくっと生成
  • Main Processでも、すべてのコードをTypeScriptを書く
  • Main Processでも、Hot Reloadで生産性をアップ
  • パッケージングはelectron-builderで簡単に

手順

プロジェクトの作成

npx create-react-app my-app --typescript
yarn add electron-is-dev electron-reload
yarn add -D concurrently electron electron-builder wait-on

Main Process

ディレクトリの作成

mkdir electron

tsconfig.json

create-react-app付属の yarn buildスクリプトと同様、buildディレクトリに生成物を出力するようoutDirを設定します。

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

main.ts

基本的にElectronのサイトにあるサンプルをTypeScript化し、Hot Reloadのコード追加します。

electron/main.ts
import { app, BrowserWindow } from 'electron';
import * as path from 'path';
import * as isDev from 'electron-is-dev';

let win: BrowserWindow | null = null;

function createWindow() {
  win = new BrowserWindow({ width: 800, height: 600 })

  if (isDev) {
    win.loadURL('http://localhost:3000/index.html');
  } else {
    // 'build/index.html'
    win.loadURL(`file://${__dirname}/../index.html`);
  }

  win.on('closed', () => win = null);

  // Hot Reloading
  if (isDev) {
    // 'node_modules/.bin/electronPath'
    require('electron-reload')(__dirname, {
      electron: path.join(__dirname, '..', '..', 'node_modules', '.bin', 'electron'),
      forceHardReset: true,
      hardResetMethod: 'exit'
    });
  }
}

app.on('ready', createWindow);

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

app.on('activate', () => {
  if (win === null) {
    createWindow();
  }
});

以下の部分がHot Reloadの対応コードです。

  // Hot Reloading
  if (isDev) {
    // 'node_modules/.bin/electronPath'
    require('electron-reload')(__dirname, {
      electron: path.join(__dirname, '..', '..', 'node_modules', '.bin', 'electron'),
      hardResetMethod: 'exit'
    });
  }

package.jsonの調整

Electron化に必要なプロパティの追加

package.json
  "homepage": ".",
  "main": "build/electron/main.js",

homepageでRender Process内のpath指定をrelativeにし、mainでMain Processのエントリポイントを指定。

Electron Builderに必要なプロパティの追加

package.json
  "author": "Your Name",
  "description": "...",
  "build": {
    "extends": null,
    "files": [
      "build/**/*"
    ],
    "directories": {
      "buildResources": "assets"
    }
  },

create-react-appはMain Processのエントリポイントを強制的にpublic/electron.jsに変更してしまうので、extendsnullをセットして無効化します。(https://github.com/electron-userland/electron-builder/issues/2030)

create-reac-appはビルド結果をbuildディレクトリに出力しますが、 electoron-builderもリソースファイルをbuildディレクトリに保存しようとします。それでbuildResourcesを指定して保存先を変更します。

npm scriptの追加

package.json
  "scripts": {
    "postinstall": "electron-builder install-app-deps",
    "electron:dev": "concurrently \"BROWSER=none yarn start\" \"wait-on http://localhost:3000 && tsc -p electron -w\" \"wait-on http://localhost:3000 && tsc -p electron && electron .\"",
    "electron:build": "yarn build && tsc -p electron && electron-builder",

electron:devが開発用スクリプトで、Main/Render Process共にHot Relaodに対応しています。まずReactプロジェクトをビルドし、開発用サーバーを立ち上げます。その後Main Processのコードをビルドし、開発モードのelectronアプリを起動します。またHot Reloadのためにtscをwatchモードで起動します。

electron:buildでDistribution Packageをビルドします。生成されたパッケージはdistディレクトリに保存します。パッケージ化されていないアプリそのものだけをビルドしたい場合は、yarn electron:build --dirとします。

postinstallは直接起動することはありません。yarn install後に、インストールされているElectronが使用するのと同じバージョンのNativeモジュールをインストールします。

ディレクトリ構成の概要

my-app/
├── package.json
│
## render process
├── tsconfig.json
├── public/
├── src/
│
## main process
├── electron/
│   ├── main.ts
│   └── tsconfig.json
│
## build output
├── build/
│   ├── index.html
│   ├── static/
│   │   ├── css/
│   │   └── js/
│   │
│   └── electron/
│      └── main.js
│
## distribution packges
└── dist/
    ├── mac/
    │   └── my-app.app
    └── my-app-0.1.0.dmg

まとめ

以下が今回のコード(+α)です。
https://github.com/yhirose/react-typescript-electron-sample-with-create-react-app-and-electron-builder

TypeScriptの安心感とHot Reloadの快適さで、さらに楽しく開発しましょう!