PC用のプログラムを作成する場合、
Electron + typescript を使うのが最近の好みだ。
まだまだ理解していない部分も多いが
vscodeとの一体感とか
見た目の綺麗さとか
developer toolの使いやすさとか
intelisenseとかが気に入っている。
自作モータドライバ基板の
特性等を調べるプログラムに
前回はpython + TkInterを使ったのだが
今回はElectron + typescript を使ってみた。
シリアル通信を使うので
serialportライブラリを使用したのだが
使えるまでに色々とトラブったので記録しておく。
serialportが動かない
以前作ったプログラムを参考に
Electron + typescriptのプログラムを
作っていく。 sassも使用しwebpackで
まとめている。
npm install serialport し、
動かしてみるとエラーが出る。
bindingが見つからないというような
エラーが出ている。
electron最新バージョンとの相性を疑い
electron単体の雛形
にserialportを組み込んでみたが動いた。
エラーメッセージで検索した結果、
webpack.config.js に以下の記述を追加して
動くようになった。
externals: {
serialport: 'commonjs serialport'
},
指定したモジュールをバンドル対象から外し
外部依存のままにするという意味らしい。
rendererプロセスでは動かない
serialportをrendererプロセスで使うと
mainプロセスで使え というようなエラーが出る。
electronのrendererプロセスは
htmlファイルの描画を行うプロセスで、
ここで ローカルのPCのファイルシステムやハードウェア(シリアルポート等)に
アクセスできてしまうと、htmlファイル中に悪意のあるjavascriptプログラムが
ある場合、いろいろと悪いこと、例えばファイルを全部消すとか、意図せぬファイルを
読み出されるとかされる可能性がある。
だからserialportがrendererプロセスで動かない
というのは理解できる。
自分のプログラムでは外部のhtmlファイルを読むことは無いので
無頓着に全ての処理をrendererプロセス側で行っていて
electronの出す警告も、なんか面倒くさいと思いつつ
場当たり的に処理してきた。
しかし、electronのversionが進む毎に rendererプロセスへの
制限は厳しくなるようだし、自分も外部HTMLファイルを読むような
プログラムを将来書くかもしれないので、ここらでちゃんと対応することにした。
次の記事 :
ElectronでcontextBridgeによる安全なIPC通信 - Qiita を参考に
BrowserWindowのwebPreferencesオプションに
以下の指定を行った。
nodeIntegraton は false :
rendererプロセスでnodeの機能は使えない。
javascriptの機能のみ使用可能。
contextIsolation は true :
mainプロセスと rendererプロセスの
windowオブジェクトが別のものになる。
そのかわり contextBridge が使用可能になる。
preloadで通信用オブジェクトを設置:
preloadで指定したjavascripファイルで
contextBridgd.exposeInMainWorldを使用し
rendererプロセスのwindowに
通信用のオブジェクトを設定できる。
ここに ipcRenderer等を使用した関数を置き、
通信に使用する。 rendererプロセスの
javascriptが使用できる追加のAPIは
ここで作成したものだけに限定されることになる。
typescriptの型宣言
contextBridgeで追加したオブジェクトに
redererプロセス側からアクセスするコードを
書くとtypescriptで windowsオブジェクトにそんなpropertyは無いと
エラーになる。 対策を調べた結果、宣言をtypescriptで書き
importしてやればいいらしい。
以下の宣言を書いた。
IApi.ts
export default interface IApi {
onError: (func: (type: 'error' | 'log', ...args: any[]) => void) => void;
getPortList: () => Promise;
getComPort: () => Promise;
... 中略 ...
onPortData: (func: (data: string) => void) => void;
}
declare global {
interface Window {
api: IApi;
}
}
このIApiの定義は preload.tsからもImportしているので
食い違いがあればtypescriptがエラーを出してくれる。
preload.ts
const { contextBridge, ipcRenderer } = require('electron');
import IApi from './IApi';
import SerialPort from 'serialport';
const api: IApi = {
onError(func: (type: 'error' | 'log', ...args: any[]) => void) {
ipcRenderer.on('error', (ev, ...args) => { func('error', ...args); });
ipcRenderer.on('log', (ev, ...args) => {func('log', ...args); });
},
getPortList: () => { return ipcRenderer.invoke('get-port-list'); },
getComPort: () => { return ipcRenderer.invoke('get-com-port'); },
... 中略 ...
onPortData: func => { ipcRenderer.on('port-data', (ev, data: string) => { func(data); }); },
};
contextBridge.exposeInMainWorld("api", api);
IApiの型情報はtypescriptがpreload.tsから自動で抽出し
webpackが自動で上手く処理して手で書かなくても
良さそうなものだと思い調べてみたのだが
方法が見つからなかった。
jquery-confirmのエラーを消す
javascriptで使用するダイアログのライブラリでは
jquery-confirmがお気に入り。
しかしjquery-confirmをtypescriptで使用すると
$.alert({
title:'Alert!',
content:'Testだよ'}
);
などと書くことになるので、
typescriptがJQueryStaticには alertなどというpropertyは無いと
エラーが出るのが悩みだった。
jquery-confirmのtypescript用の型宣言を探すが見つからない。
しかしもう宣言を追加する方法を学んだので対処できる。
以下のファイルを作成しimportすることで対応できた。
jconfirm.ts
interface JQueryStatic {
alert: (any) => void;
confirm: (any) => void;
dialog: (any) => void;
}
rendererプロセスでエラー
これまでの修正を施したプログラムを実行すると
redererプロセスで 'require not defined'というような
エラーがでる。 requireは nodeの機能であり、
nodeの機能は使えなくしたので当然だ。
つまり webpackがnode用のコードを出力している
わけだが修正の仕方がわからない。
babelとか導入する必要があるのだろうかと悩んだが、
結局 webpack.config.jsの rederer用の設定の
targetを 'electron-renderer'から 'web'に変更することで対応できた。
メインプロセスのconsole.logの出力が見れない
これでプログラムは動くようになった。
vscodeのデバッガでプログラムを起動すると
デバッグ用に入れたメインプロセスの console.logの出力が見れない。
これまではメインプロセス側では、ほとんど仕事をしていなかったので
console.logの出力を見る必要がなかったが、
今後はUI以外の仕事は基本メインプロセス側でやることになるので、
見れないとかなり困る。
調べた結果、 .vscode/launch.jsonに以下の指定を追加することで
vscodeのデバッグコンソールに出力されるようになった。
"outputCapture": "std"
vscodeのデバッガも使えるはずなのだが、まだ使い方を憶えていない。
最後に
vscode + electron + typescript のプログラミング環境は
かなり気に入っている。
まだ分からないことも多いが徐々に憶えて
この環境でのknow how を集めていきたい。