Electron+React+TypeScriptの環境構築

## はじめに
create-react-app(TypeScript)で生成したプロジェクトをElectron上で動作/パッケージ化できる環境を構築します。

### 前提条件
- yarnコマンドが使用できること

## この記事でできること
- Electron上でReactのページの表示/パッケージ化
- メインプロセスとレンダラープロセスの接続
<img width=50% src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1157442/f95e5a41-e192-d010-8649-115468cfa047.png>

## create-react-appのインストールとプロジェクトの生成
```
$ yarn global add create-react-app
$ yarn create react-app <プロジェクト名> --template typescript
```

## 必要なパッケージのインストール
- [Electron](https://github.com/electron/electron)
- [electron-builder](https://github.com/electron-userland/electron-builder)
```
$ cd <プロジェクト名>
$ yarn add -D electron electron-builder
```

## Electron+Reactの構成
### 最終的なディレクトリ構成
create react-appで生成された雛形に'++'マークがついているディレクトリ/ファイルを追加します。
```
<プロジェエクト>
 |- node_modules/
 |- public/
 |- src/
 |   |- @types/ ++
 |   |   |- rendere.d.ts ++
 |   |- App.tsx
 |   |- ...
 |
 |- electron/ ++
 |   |- src/ ++
 |   |   |- main.ts ++
 |   |   |- preload.ts ++
 |   |- tsconfig.json ++
 |   
 |- package.json 
 |- tsconfig.json
 |- README.md
 |- yarn.lock
```

### Electron用のtsconfig.jsonを追加
<プロジェクト名>/electron/tsconfig.jsonを生成します。
```tsconfig.json
{
    "compilerOptions": {
        "target": "es5",
        // reactと共通のbuildフォルダへ出力
        "outDir": "../build",
        // build/electron/に出力
        "rootDir": "../"
    },
    "include": ["src/**/*"]
}
```

### Electronのメインプロセス(main.ts)の生成
[Electronのクイックスタート](https://www.electronjs.org/docs/latest/tutorial/quick-start)を参考に<プロジェクト名>/electron/src/main.tsを生成します。

```main.ts
import { app, BrowserWindow } from 'electron'
import path = require('path')


const mainURL = `file:${__dirname}/../../index.html`

const createWidnow = () => {
    const mainWindow = new BrowserWindow({
        width: 500,
        height: 500,
        webPreferences: {
            preload: path.join(__dirname, "preload.js")
        }
    })
    mainWindow.loadURL(mainURL)
}

app.whenReady().then(() => {
    createWidnow()
    app.on('activate', () => {
        if(BrowserWindow.getAllWindows().length === 0) createWidnow()
    })
})

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

```
### package.jsonの修正
create react-appで生成されたpackage.jsonにElectron用の設定を追加します。
```package.json
{
  ...
  "main": "build/electron/src/main.js",
  "homepage": "./",
  "description": "description",
  "author": "author",
  "build": {
    "extends": null,
    "files": [
      "build/**/*"
    ]
  },
  "scripts": {
    ....
    // Electronの起動
    "electron:start": "yarn　build && tsc -p electron && electron ."
    // アプリのパッケージ化
    "electron:build": "yarn build && tsc -p electron && electron-builder"
  },
  ...
}
```

## 起動とパッケージ化
ここまでの作業が完了すると、Reactの初期画面をElectron上で表示できます。
```
# 起動
$ yarn electron:start
# パッケージ化
$ yarn electron:build
```
<img width=50% src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1157442/93cbeb9a-e4fe-b78d-6878-e068d36b15bf.png>



## メインプロセスとレンダラープロセスを接続する
### [preload.ts](https://www.electronjs.org/ja/docs/latest/tutorial/tutorial-preload)を作成

<プロジェクト名>/electron/src/preload.tsを生成します。

```preload.ts
import { contextBridge } from "electron"

contextBridge.exposeInMainWorld("electronAPI", {
    version: () => "1.0.0",
})

```

### [renderer.d.ts](https://www.electronjs.org/ja/docs/latest/tutorial/context-isolation)を生成
<プロジェクト名>/src/@types/renderer.d.tsを生成します。

```renderer.d.ts
export interface IElectronAPI {
    version: () => string
}

declare global {
    interface Window {
        electronAPI: IElectronAPI
    }
}
```

## メインプロセスの値をレンダラープロセスに表示する
ReactのApp.tsxからversion()を呼び出します。
```App.tsx
import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      ...
      <div>version: {window.electronAPI.version()}</div>
    </div>
  );
}

export default App;
```


#### 実行確認
```
$ yarn electron:start
```
<img width=50% src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1157442/f95e5a41-e192-d010-8649-115468cfa047.png>


## 最後に
コード等、間違いがあればご指摘ください。