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](https://www.codementor.io/randyfindley/how-to-build-an-electron-app-using-create-react-app-and-electron-builder-ss1k0sfer)"）

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

 * [create-react-app](](https://create-react-app.dev/))でTypeScript対応プロジェクトをさくっと生成
 * Main Processでも、すべてのコードを**TypeScript**を書く
 * Main Processでも、**Hot Reload**で生産性をアップ
 * パッケージングは[electron-builder](https://www.electron.build/)で簡単に

## 手順

### プロジェクトの作成

```bash
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`を設定します。

```json: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のコード追加します。

```ts: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の対応コードです。

```ts
  // 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化に必要なプロパティの追加

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

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

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

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

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

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

#### npm scriptの追加

```json: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モジュールをインストールします。

### ディレクトリ構成の概要

```bash
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の快適さで、さらに楽しく開発しましょう！