🐿️

ElectronでWindows/Linux向けアプリの習作開発を通して感じた注意点・感想

2024/05/20に公開

はじめに

screenshot

TypeScript + Electronで「PDF Shift」というWindows, Linux向けのデスクトップアプリを個人の習作で作成しました。

内容としてはPDFのページ入れ替えなどが出来るツールで、アプリとしては他に機能がずっと良いものが沢山あります(何よりMac OSではデフォルトのビューアで同様の操作が手軽に出来た!笑)。
が、今回「マルチプラットフォームでデスクトップアプリを作ることに挑戦したい!」って気持ちがあり、かつ過去にもElectronを学んでいたことから、トライしてみました。
後半ちょっと詰まってしまいましたが、大体1週間ちょっとくらいかけて作成し、ここらで良いかな、という段階の出来上がったソースコードと配布物を公開しています。未熟者ですが、よかったら見てみてください。もし参考になったら、スターがあるととても嬉しいです。

https://github.com/kawana77b/pdf-shift

なお、マルチプラットフォームにアプリを作成できるフレームワークは随分出てきています。TauriFlutterは最近人気ですし、.NETでもMAUIみたいなものが出てきています。

この記事では、上記の習作アプリの開発を通して私が感じたことを、振り返りとして記載したいと思います。

環境・目標にしていたこと

私の主な開発環境・構成および目標(仕様)にしたことについては次のとおりです。

  • 言語はTypeScript・パッケージマネージャはpnpm
  • WindowsおよびLinuxのx64環境で動くアプリを作る。使用したOSはWindows 11(+WSL)とUbuntu 22.04 (Mac OSは断念)
  • フロントエンドはUIフレームワークとしてReactを利用するようにする。今回そこまで意味はないが、Tailwind CSSをすぐ使いたかったのでNext.js (app router)を使用
  • 成果物の出力にはelectron-builderを利用
  • 配布形式はNSISインストーラ形式 (Windows) と AppImage (Linux)
  • 自動更新やその他諸々深い機能には触れない。普通の野良アプリ
  • GitHub Actionsでとりあえずビルドして、GitHub上でリリースできるCIを作成する。署名は行わず、野良配布でアプリをリリースする、ということが出来るようになりたい

preloadスクリプトの為の対処

Electronにはメインプロセスとレンダラプロセスという2つのプロセスがあり、プリロードスクリプトというプロセスの橋渡しをするようなスクリプトが存在します。
ここではcontextBridge.exposeInMainWorld()関数によってDOM上のwindowオブジェクトを拡張し、渡したAPIを生やしてレンダラプロセスとメインプロセスの交信ができるようにします。
ここで私が課題だと思ったことは2点あります。

  1. メインプロセス、レンダラプロセス、プリロードスクリプトはある種の別物だが、レンダラプロセスとプリロードスクリプトは開発上、APIは型参照して使えるようにすると利便性があるだろうと思ったこと(補完が効くため)
  2. サンドボックス化されたプリロードスクリプトは2024年5月現在、ESMをサポートしていないこと

2についてはプリロードスクリプトを読み込みません。やや気付きにくいですが、ChromiumのDevToolsのコンソールをチェックするなどで気付けます。私の気分もありますが、メインプロセスのtsconfig.jsonには"module": "Node16"を設定していました。

そこで、私がやったことを以下に記載します。

共通して使える型宣言を用意するようにする

ルートディレクトリにtypesというフォルダを用意しました。そこで、WindowオブジェクトやAPIとして使いたい関数、Fileオブジェクトをglobal変数で拡張し、エディタが警告なしに認識してくれるようにしました。

declare global {
  interface Window {
    api: ElectronAPI;
  }

  // contextBridge.exposeInMainWorld()で渡すオブジェクト用の型
  interface ElectronAPI {
    // ...
  }

  // ElectronではFileオブジェクトが拡張されており、pathプロパティでフルパスが取れます
  interface File {
    path: string;
  }

例えば、これをpreloadに関するフォルダに入れたtsファイルで利用可能にするために、tsconfig.jsoninclude属性を設定します。

"include": ["../types/*.d.ts", "./**/*.ts"]

main, frontend, preloadで別々にトランスパイル

上記2の解決方法としては、(前言と重複しますが)preloadスクリプトをメインプロセス用のトランスパイルルートとは別のフォルダとして用意し、個別に出力する形としました。
Electronがプリロードスクリプトを単純にcommonjs形式でトランスパイルされるようtsconfig.jsonを設定する例を以下に示します。

"module": "commonjs",

このようにして、至極単純ではありますが、メインのビルド時に各プロジェクトがトランスパイルされるようpackage.jsonにスクリプト設定しました。

"preload:build": "cd preload && tsc",
"frontend:build": "cd frontend && npm run build",
"build": "tsc",

ちなみに今回はプリロードスクリプトは1ファイルしかないのでシンプルにしていますが、ファイル数を増やしたいときはRollup.jsなどを活用し、最終的に単一のpreload.jsを吐かせるようにすれば、このやり方では無難に開発できると思います。

sharpはasarUnpackを試してみる

成果物の出力にはelectron-builderを利用しました。
これはElectron公式が説明しているElectron Forgeと分かつ一般的なビルドツールだと思います(基本的にはelectron-builderの方が人気かも)。

ここで、今回私の作成したアプリには、利用したライブラリの依存関係としてsharpが使われていました。このライブラリはjavascriptの画像処理の定番で、Node.jsアプリを作る上では非常に使いやすいものになっていると思います。

ただ、Electron上ではやや問題があり、特に設定なしでWindowsやWSL上の実行では期待通りに機能したように見えましたが、Ubuntu Desktopで実行してみると、asarにアーカイブした場合エラーを吐いてしまい、私の環境では成果物が機能しませんでした。
これはelectron-builder.ymlに以下の設定をすることで、Windows/Linuxどちらでも動作するように解決できました。

asarUnpack:
  - node_modules/sharp/**

pnpmは.npmrcの設定を忘れずに

electron-builder公式の抜粋になりますが、設定が必須です
私は比較的ダウンロードが速いのでパッケージマネージャーにpnpmを好んでいます。しかし、electron-builderに適用すると依存関係読み込みでエラーが発生し、起動に失敗してしまいます。
以下のような.npmrcを作成して、ルートディレクトリに配置することで問題を解決できます。とても単純なことですが、それ故ドキュメントのトップに書いてあっても見落としやすい箇所なので、npm以外を利用される方は注意が必要です。

node-linker=hoisted
public-hoist-pattern=*
shamefully-hoist=true

electron-builderのpublishオプション

今回、リリース向けのパイプラインとしてGitHub ActionsでCI作成に挑戦してみました(やっていることは単純なことの積み重ねです)。
しかし、electron-builderが動作するタイミングで、発行用にGH_TOKENが必要な警告が出てしまいました。これはelectron-builderは自動公開に関わる機能が存在しており、publishというオプションがあることに関連します。

ドキュメントの注意書きにはリンク先の通り、please consider using automatic rules instead of explicitly specifying publishとあったのですが、
今回は作ったきりのものを内部で生成して地道なやり方でリリースに流したいだけで、習作のつもり、ということもあったので、-p neverとすると問題なく動作しました。

electron-builder -p never

Macでは公証が必要

マルチプラットフォームのデスクトップアプリに挑戦してみたい!って気持ちが強くあった今回の習作ですが、そんな気持ちを吐露していたらある方に「公証が必要かも」と教えて頂きました(ありがとうございます!)。

調べてみると実際に署名及び公証が必要でありその前提としてApple Developer Programに加入する必要がありました。これは2024年現在年会費約1万5千円ほどします。
私はMacには詳しくなく、そんなに高額なお金が払えないことがまずハードルでした。

別に署名付き、さらに公証がある、というのは随分前から世のアプリ配布としては至極当然のことですし、特にお金にする為のアプリでは普通だと思います。Windowsだってストア配布するには署名必須です。どうやらかなり前から言われてたことみたいなので、多くの人にとって当たり前なことかもしれません。
しかし、私はただ自分の作った野良アプリを世に出したいだけでした。以前からちょっとした興味で手持ちとして検証用に買っていた低スペックMacで、実際に動いて有頂天になっていたので、物凄く落胆してしまいました。

その為、今回は諦めることにしました。
「動いて嬉しい!」という気持ちがあるのは好奇心を探求している証左なので、自分はプログラマとして良いことだと思っています。が、そんな失望した気持ちにならないよう、前提にしておくと良いと思います。

Linux向けならちゃんとLinux Desktopで試してみる

私は普段Windowsを使用しており、Linuxに関連するものとしてWSLという環境があります。
すごい時代になったもので、2024年現在はWSLgという技術が出てきており、LinuxのGUIアプリが起動できるようにまでなっています。見た目はお粗末ですが、WSLでもElectronプロジェクトを実行できるようになりました。
ただしElectron向けにはある程度プラスアルファの依存関係の準備が必要で、apt installを試す必要があります。私はこのStack Overflowの質問などを参考にしたら、動くようになりました(この点の必須要件はまだ完全に理解できていません)。

しかし、やはりLinux向けに作るとなると、「ちゃんとWSLじゃないもので試してみる必要があるな」と思い、PCにUbuntu Desktopの環境を作りました。Ubuntuにしたのはやはりメジャーだろうと考えたことと、以前も使っていたこと、WSLや自宅の趣味サーバでUbuntuを採用していたことが理由です。
そこでは、先述の「sharpはasarUnpackを試してみる」の通り、上手く動きませんでした。
Linuxは玉石混交なので色々な環境に対応するには色々な努力が必要だと思いますが、少しでもみんなが使うような環境は用意できると、少しでも多くの人が動かせるかな、ってアプリが作れると思いました。

なお、蛇足ですが私は最初snapパッケージでのNode.jsを導入しており、sharpのインストールに失敗してしまったりしました。詳細と理由は調査を省いてしまいました。なぜかパッケージマネージャがyarnな時だけ謎に通りましたが、結局成果物の実行で失敗したのは先の通りです。
そこで、nvm.fishというツール(私はシェルにfishを導入した為)でシンプルに(サンドボックスでない)素に動くNode.jsを入れたら、普通にインストール出来るようになったりしたので、どの実行環境でNodeを入れるかなども、開発環境を考える上で大事な指針だと思いました。

終わりに 作ってみて思ったこと

疲れました(笑)。これが私の現在の能力なんだな、と思いました。
至極単純なことしかやっていないと思いますが、色々なことを考える必要も、環境を揃える必要もあります。沢山の環境に対応しているクロスプラットフォームアプリを出していることはすごいし、必要な知識は多いなと感じました。

世の素晴らしい開発者に敬意を払い、私もスキルアップしたいな、と感じる思いが強くなりました。

私はずっとWindowsばかり使っていましたし、デスクトップアプリと言えばWPFWindows FormsなどのWindows向け、.NETのフレームワークでアプリを作ったりしていました。Electronは以前学びましたが、Windows向けのことしか考えたことはありませんでした。CLIアプリはGoや.NETで今では簡単にクロスプラットフォームで作れますが、やはり見た目のインパクトに欠けます。だからこそ、他のOSで自分が書いてみたデスクトップアプリが同じように期待通り動作することを見たときの喜びはひとしおでした。

また、Ubuntu Desktopを入れてみて気付いたのですが、Electron, Tauriなどのマルチプラットフォーム向けフレームワークが整備されてきたこと、PWAの普及はLinux DesktopのUI/UX向上に物凄く一役買っている、ということは結構大きいと思います。

私は以前から趣味でLinux Desktopを触っていたのですが、しばらくブランクがあり、3年くらいぶりにまたUbuntuを入れてみました。

数年前までは(私が単に知らなかっただけなのもあると思いますが)KDE・Qtのフレームワークなどを利用したアプリが基本になっていたような印象があって、アプリとしては全然使えますが、やはりUIと言えば失礼極まりないのですが「カッコよくない、無骨なもの」が多かった気がします。
しかし、Web技術はリッチなガワを作りやすいので、以前に増してLinux Desktopと他のOSとの日常使いでのアプリ体験は似たようなものになってきたのだな、と感じました。正直Electronはサイズの大きさが問題でもあると思いますが、やはり見た目のインパクトって意外と大事だと思います。

VSCodeはもちろんのこと、DiscordやSlack、Obsidianなどの見た目がキレイな著名アプリをSnap StoreFlatHubなどで私のような素人でも手軽に導入することができます。
また、HoppscotchやMisskeyのようなアプリはPWAとしてLinuxでも同様に導入でき、それらもまた、javascript/cssフロントエンドに力を入れたオシャレなUIをしていて、デスクトップ感覚で手軽に起動することが出来ます。
改めて、そのようなアプリの凄さとともに、「デスクトップアプリにフロントエンドでWeb技術を利用できるフレームワークの強み」を感じました。

以上が私の感じたことになります。
「Electornでデスクトップアプリを開発したい!」という方の参考に少しでもなれば幸いです。

Discussion