h_nari @ 熊本市のブログ。電子工作、プログラミング、ゲーム、TV、 政治、インターネットなどに日々の思い付きを、 うだうだ~と書いていきたい。
このブログにはコメント欄を設けておりません。 記事への御意見、ご質問はtwitter @h_nari宛に お願い致します。


アーカイブ

メタ情報
RSS
Login

Electron + typescript で serialportを使う

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 を集めていきたい。