2020/08/28 追記
electron-webpackを使った新作を書きました。
electron-webpackでTypescript+Reactプロジェクトを作成する
#背景
- create-react-appは便利
- typescript対応もしている
- webpack書かなくて良い
- electronは便利
- フロントエンドの表現力
- ローカルへのアクセス
ということで適当に足したら爆発したため、まとめていきます。
動くプロジェクトはこれです
thalathalaylah/create-react-electron-typescript-app
記事の構成としては、
1.やること
2.やったこと
3.なぜなのか
の3部構成です。何をどうしたかだけ気になる方はやったことだけ読んでいただければ。
#参考文献
-
Building an Electron application with create-react-app
- electron + create-react-app(not typescript)によるアプリ構築
-
React+Electronアプリを作ってみよう
- 上記の記事の日本語訳+パッケージング周りの設定
#1.やること
- create-react-appから作成したプロジェクトにelectronを組み込む
- Nodeの能力が利用できることを示すために「ボタンを押すとファイルを生成する」機能を実装する
- ReactDevToolsを利用可能にする
#2.やったこと
コミットのリンクと、コミットでやったことを書いていきます
###Initial commit from Create React App
-
yarn create react-app <project name> --typescript
でreact+typescriptのプロジェクトを作成
###[add] electronからcreate-react-appのページを表示できる
-
electron-quick-startのmain.jsを
src_main/entrypoint.js
にコピー -
src_main/entrypoint.js
のBrowserWindowを生成している箇所で、webPreferenceを空にする -
src_main/entrypoint.js
のmainWindow.loadFile('index.html')
をmainWindow.loadURL('http://localhost:3000')
に変更 -
package.json
に"main": "src_main/entrypoint.js"
を追加 -
package.json
のscriptsに"electron": "electron ."
を追加 -
yarn add --dev electron
でelectronを追加 - この時点で
yarn start
が起動している状態でyarn electron
を実行するとelectronでcreate-react-appのページが表示できる
###[add] packageコマンドでappを生成できる
- コミットの内容ではないが、Macの場合は
brew install wine
しておく -
src_main/entrypoint.js
にapp.isPackaged
によってmainWindow.loadURL
の読み込み対象をurlかpathか分岐させる処理を追加 -
package.json
に"homepage": "./"
を追加 -
.gitignore
に/release
を追加 -
package.json
のscriptsに"package": "yarn build && electron-packager . my-app --platform=all --arch=x64 --prune --out=release --overwrite"
を追加 - この時点で
yarn package
によってパッケージを生成し、yarn start
を行わずに生成されたパッケージからアプリを起動できる
###[add] packageコマンドではMac用のビルドのみ行う
-
package.json
のscriptsでpackage
コマンドをpackage-all
にリネーム、package
コマンドはplatformをdarwin
のみ指定するようにした
###[add] devコマンドからサーバー起動とelectron起動を同時に行うことができる
-
yarn add --dev foreman
でforemanを追加 -
dev_env/Procfile
を追加 -
dev_env/Procfile
から読むdev_env/electron-wait-react.js
を追加 -
package.json
のscriptsに"dev": "nf start -j dev_env/Procfile"
を追加 -
src_main/entrypoint.js
で文字列でURLを指定している部分を環境変数から読むように変更
- 手元のChromeにReactDevTools extensionをインストール ReactDevTools
-
dev_env/.dotenv
を作成し、ELECTRON_DEV_TOOLS_PATH
という環境変数を定義し、インストールしたReactDevToolsのパスを設定(コミット上ではtemplateとして追加している) -
package.json
のdevコマンドがdev_env/.dotenv
を読み込めるように変更 -
app.isPackaged
がfalseの時にELECTRON_DEV_TOOLS_PATHからReactDevToolsを読むように変更 -
app.isPackaged
がfalseの時は最初から開発者ツールが開くように設定、付随してウィンドウサイズも大きくする
###[add] ボタン作成
-
src/CreateFileButton.tsx
を作成 - CreateFileButtonを
src/App.tsx
から読み込む - この時点でelectronアプリのページの下方にクリック可能なボタンが生成されるようになる
yarn add --dev uuid @types/uuid
-
src/CreateFileButton.tsx
でuuidによってファイル名を生成し、console.log
で出力するように変更 -
window.require
でelectronを読み込めるようにdeclare global
でWindow interfaceにrequireを追加 - electronからRemoteをimport
-
window.require('electron').remote
でRemote型のオブジェクトであるremoteを取得
-
.gitignore
に/test
を追加 -
fs
とpath
をfsType
とpathType
としてimport - remoteから
fs
とpath
をrequireし、typeof fsType
とtypeof pathType
で型付けする - ファイルを生成する処理を書く
これで無事、ボタンを連打するとファイルが大量に生成されるアプリが完成しました!
#3.なぜなのか
###Initial commit from Create React App
特筆することは無いです、スッと作る
###[add] electronからcreate-react-appのページを表示できる
ここはBuilding an Electron application with create-react-appとほぼ同じですが、typescriptを使うために一部調整しています。
まず、webPreferenceを空にしているのはelectron-quick-startにpreload.jsを読み込む仕組みが組み込まれたためです。
今回のアプリにはpreloadする処理は無いため、preloadの処理を消しています。
また、entrypoint.js
をsrc
に入れずsrc_main
に入れています。これは、src下のファイルはcreate-react-app内部で設定されたwebpackの支配下にあるため、適当にjsを入れておくとエラーが発生してしまうためです。
###[add] packageコマンドでappを生成できる
brew install wine
はWindows用のビルドのために必要となっています。
app.isPackaged
はdev環境か否かの判別に使っています。Package化している場合はProduction、Package化していない場合はDevelopという判定です。
package.json
に"homepage": "."
を加えるのはBuilding an Electron application with create-react-appの通りで、これが無いとpackage化した際にindex.html
から他のファイルが読めなくなります。
package
コマンドの内容はReact+Electronアプリを作ってみようの通りです。
###[add] packageコマンドではMac用のビルドのみ行う
electronのv5.0.3
で試したところ、package
コマンドにかかる時間がv5.0.1
と比べて2倍くらい長くなったため開発中はMacのみをターゲットにビルドすることにしました(利用しているOSを対象にすると良いでしょう)。
###[add] devコマンドからサーバー起動とelectron起動を同時に行うことができる
Building an Electron application with create-react-appの通りです。
React+Electronアプリを作ってみようではnpm-run-all
を使うと良い、と書いてありますが、yarn start
が起動し終わるのを待たずにyarn electron
が走ってしまうため、electron側をリロードせねばならないのが良くないと思いました。
そのため、今回はforemanの方を採用しています。
###[add] ReactDevToolsの追加
DevTools Extensionにある通りです。
このアプリでは先述の通り、app.isPackaged
でdev、prodを区別しているため、app.isPackaged
がfalseの場合のみ有効にしています。
ここで、devでReactDevToolsを有効にした場合、有効にしたことが~/LibraryApplication Support/<App名>
に記録されるのですが、package化したprodも同じ場所を参照するため、prodでも有効なままになります。
同ディレクトリを削除した状態でprod環境のものを起動した場合は、無効なまま正常に起動するのでご安心を。
###[add] ボタン作成
シュッとボタン作るだけです。
###[add] uuidで作成するファイル名を生成
ここはuuidでファイル名を生成するというコミットメッセージになっていますが、うっかりelectronのremoteオブジェクトを取得するところまでやっています。
electronの能力にアクセスするためにelectronをimportしたいのですが、普通にimportを書いてしまうとtsからのトランスパイル時にcreate-react-app側のwebpackが解決しようとするため、失敗します。
これを回避するために、window.require
というものがelectronでは定義されています。
しかし、create-react-app+typescriptの文脈ではwindowはWindowインターフェースを実装したものなので、window.require('electron')
などと書いてもトランスパイル時に「window.require
なんてメソッドは無いよ」と言われてしまいます。
そこで、
declare global {
interface Window {
require: any;
}
}
と書いてやることでrequireを呼べるようにします。
また、window.require
は上に書いた通りanyが返ってしまうため、electronのRemoteの型情報はimportしておいて、型付けを行っています。
###[add] ボタンを押すとファイルが生成される
ここからはwindow.require
からではなくremote.require
でnodeの機能を呼び出しています。
window.require
でも呼び出せるのですが、Breaking Changesを見た感じremote.require
から呼ぶ方が良いのかな、特にv6.0.0以降は、という感じです。
fs
とpath
をimportしていますが、これはremote.require
でこれらを取得した際に型をつけるためです。
型定義ファイルを見るとわかるのですが、fs
とpath
はモジュールであってinterfaceとかではないので、typeofで無理やり型にして型付けをしています。
#コンクルージョン
これで無事electronからcreate-react-app+typescriptを利用することができるというわけです。
electron+create-react-appの場合の罠とelectron+typescriptの場合の罠が複合して出現したため厳しい気持ちになりましたが、これで型付きelectron生活を無事送ることができるようになりました。