⏱️

Electronでストップウォッチを作る

2023/02/24に公開

そう言えばJavaScript(Node.js)でデスクトップアプリ作れるよなと思って、以前Reactで作ったストップウォッチをElectronでデスクトップアプリ化してみようと思い立ったのでやってみた。

セットアップ

Electron + Reactでデスクトップアプリを作ろう! - Qiita

この記事を参考に Electron React Boilerplate を使ってセットアップを行う。
Installation | Electron React Boilerplate

satoshie@MainMachine ~/git> git clone --depth=1 \
                                  https://github.com/electron-react-boilerplate/electron-react-boilerplate \
                                stop-watch-by-electron
Cloning into 'stop-watch-by-electron'...
remote: Enumerating objects: 93, done.
remote: Counting objects: 100% (93/93), done.
remote: Compressing objects: 100% (86/86), done.
remote: Total 93 (delta 8), reused 54 (delta 3), pack-reused 0
Receiving objects: 100% (93/93), 697.12 KiB | 3.67 MiB/s, done.
Resolving deltas: 100% (8/8), done.
satoshie@MainMachine ~/git> cd stop-watch-by-electron/
satoshie@MainMachine ~/g/stop-watch-by-electron (main)> npm i
npm WARN deprecated stable@0.1.8: Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility
npm WARN deprecated asar@3.2.0: Please use @electron/asar moving forward.  There is no API change, just a package name change
npm WARN deprecated electron-osx-sign@0.6.0: Please use @electron/osx-sign moving forward. Be aware the API is slightly different
npm WARN deprecated @types/terser-webpack-plugin@5.2.0: This is a stub types definition. terser-webpack-plugin provides its own type definitions, so you do not need this installed.
npm WARN deprecated @npmcli/move-file@2.0.1: This functionality has been moved to @npmcli/fs

> postinstall
> ts-node .erb/scripts/check-native-dep.js && electron-builder install-app-deps && cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.dev.dll.ts

  • electron-builder  version=23.6.0
  • loaded configuration  file=package.json ("build" field)
  • installing production dependencies  platform=darwin arch=x64 appDir=/Users/satoshie/git/stop-watch-by-electron/release/app

added 1392 packages, and audited 1393 packages in 57s

208 packages are looking for funding
  run `npm fund` for details

6 high severity vulnerabilities

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.

パッケージの問題は追々対応する。

起動してみる

$ npm start

> start
> ts-node ./.erb/scripts/check-port-in-use.js && npm run start:renderer


> start:renderer
> cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./.erb/configs/webpack.config.renderer.dev.ts

Starting preload.js builder...
Starting Main Process...
<i> [webpack-dev-server] Project is running at:
<i> [webpack-dev-server] Loopback: http://localhost:1212/
<i> [webpack-dev-server] On Your Network (IPv4): http://172.22.5.60:1212/
<i> [webpack-dev-server] On Your Network (IPv6): http://[fe80::1]:1212/
<i> [webpack-dev-server] Content not from webpack is served from '/Users/satoshie/git/stop-watch-by-electron/public' directory
<i> [webpack-dev-server] 404s will fallback to '/index.html'

> start:main
> cross-env NODE_ENV=development electronmon -r ts-node/register/transpile-only .


> start:preload
> cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.preload.dev.ts

[electronmon] waiting for a change to restart it
[90295:0224/030606.617624:ERROR:trust_store_mac.cc(838)] Error parsing certificate:
ERROR: Failed parsing extensions

(node:90295) ExtensionLoadWarning: Warnings loading extension at /Users/satoshie/Library/Application Support/Electron/extensions/fmkadmapgofadopljbjfkapdkoienihi:
  Permission 'scripting' is unknown or URL pattern is malformed.

(Use `Electron --trace-warnings ...` to show where the warning was created)
03:06:08.074 › Skip checkForUpdates because application is not packed and dev update config is not forced
03:06:08.079 › checkForUpdatesAndNotify called, downloadPromise is null
(node:90295) ExtensionLoadWarning: Warnings loading extension at /Users/satoshie/Library/Application Support/Electron/extensions/fmkadmapgofadopljbjfkapdkoienihi:
  Permission 'scripting' is unknown or URL pattern is malformed.

03:06:08.164 › Skip checkForUpdates because application is not packed and dev update config is not forced
03:06:08.165 › checkForUpdatesAndNotify called, downloadPromise is null
(node:90295) ExtensionLoadWarning: Warnings loading extension at /Users/satoshie/Library/Application Support/Electron/extensions/fmkadmapgofadopljbjfkapdkoienihi:
  Permission 'scripting' is unknown or URL pattern is malformed.

03:06:08.235 › Skip checkForUpdates because application is not packed and dev update config is not forced
03:06:08.239 › checkForUpdatesAndNotify called, downloadPromise is null
[90295:0224/030608.773653:ERROR:extensions_browser_client.cc(62)] Extension Error:
  OTR:     false
  Level:   2
  Source:  chrome-extension://jajkcfjjopeknfifcbmdnjehnepnldgn/build/background.js
  Message: Uncaught TypeError: Cannot read properties of undefined (reading 'registerContentScripts')
  ID:      jajkcfjjopeknfifcbmdnjehnepnldgn
  Type:    RuntimeError
  Context: chrome-extension://jajkcfjjopeknfifcbmdnjehnepnldgn/build/background.js
  Stack Trace:
    {
      Line:     107
      Column:   1
      URL:      chrome-extension://jajkcfjjopeknfifcbmdnjehnepnldgn/build/background.js
      Function: (anonymous function)
    }
[90295:0224/030609.061827:ERROR:extensions_browser_client.cc(62)] Extension Error:
  OTR:     false
  Level:   2
  Source:  chrome-extension://jajkcfjjopeknfifcbmdnjehnepnldgn/build/background.js
  Message: Uncaught TypeError: Cannot read properties of undefined (reading 'registerContentScripts')
  ID:      jajkcfjjopeknfifcbmdnjehnepnldgn
  Type:    RuntimeError
  Context: chrome-extension://jajkcfjjopeknfifcbmdnjehnepnldgn/build/background.js
  Stack Trace:
    {
      Line:     107
      Column:   1
      URL:      chrome-extension://jajkcfjjopeknfifcbmdnjehnepnldgn/build/background.js
      Function: (anonymous function)
    }
[90295:0224/030609.269791:ERROR:extensions_browser_client.cc(62)] Extension Error:
  OTR:     false
  Level:   2
  Source:  chrome-extension://jajkcfjjopeknfifcbmdnjehnepnldgn/build/background.js
  Message: Uncaught TypeError: Cannot read properties of undefined (reading 'registerContentScripts')
  ID:      jajkcfjjopeknfifcbmdnjehnepnldgn
  Type:    RuntimeError
  Context: chrome-extension://jajkcfjjopeknfifcbmdnjehnepnldgn/build/background.js
  Stack Trace:
    {
      Line:     107
      Column:   1
      URL:      chrome-extension://jajkcfjjopeknfifcbmdnjehnepnldgn/build/background.js
      Function: (anonymous function)
    }
[90295:0224/030609.270111:ERROR:extensions_browser_client.cc(62)] Extension Error:
  OTR:     false
  Level:   1
  Source:  manifest.json
  Message: Service worker registration failed. Status code: 15
  ID:      jajkcfjjopeknfifcbmdnjehnepnldgn
  Type:    ManifestError
IPC test: ping
IPC test: ping
IPC test: ping
[90295:0224/030611.894170:ERROR:CONSOLE(2)] "Electron sandboxed_renderer.bundle.js script failed to run", source: node:electron/js2c/sandbox_bundle (2)
[90295:0224/030611.894231:ERROR:CONSOLE(2)] "TypeError: object null is not iterable (cannot read property Symbol(Symbol.iterator))", source: node:electron/js2c/sandbox_bundle (2)
[90295:0224/030611.945934:ERROR:CONSOLE(2)] "Electron sandboxed_renderer.bundle.js script failed to run", source: node:electron/js2c/sandbox_bundle (2)
[90295:0224/030611.946017:ERROR:CONSOLE(2)] "TypeError: object null is not iterable (cannot read property Symbol(Symbol.iterator))", source: node:electron/js2c/sandbox_bundle (2)
[90295:0224/030611.973589:ERROR:CONSOLE(2)] "Electron sandboxed_renderer.bundle.js script failed to run", source: node:electron/js2c/sandbox_bundle (2)
[90295:0224/030611.973642:ERROR:CONSOLE(2)] "TypeError: object null is not iterable (cannot read property Symbol(Symbol.iterator))", source: node:electron/js2c/sandbox_bundle (2)

すぐには起動しないみたい。しばらくするとアプリとして起動した。

useStopWatchを使えるようにする

Git cloneしただけなので、オリジナルのElectron React Boilerplateをベースに必要なコードを足していく。

electron-react-boilerplate/electron-react-boilerplate: A Foundation for Scalable Cross-Platform Apps

まずは react-timer-hookを使えるようにするために npm install でライブラリをインストールする。

satoshie@MainMachine ~/g/stop-watch-by-electron (main)> npm i react-timer-hook

added 1 package, and audited 1394 packages in 8s

208 packages are looking for funding
  run `npm fund` for details

6 high severity vulnerabilities

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.

デジタル時計なフォントをインストールする

ストップウォッチなのでデジタル時計っぽいフォントを使いたい。
今回はフリーフォントの7セグ・14セグフォント 「DSEG」を利用する。

npmパッケージとして公開されているのでnpmコマンドでインストールが可能。

satoshie@MainMachine ~/g/stop-watch-by-electron (main)> npm i dseg

added 1 package, and audited 1395 packages in 7s

208 packages are looking for funding
  run `npm fund` for details

6 high severity vulnerabilities

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.

CSSでフォントをインポートする

フォントを使えるようにCSSファイル内でインポートを記述する。

App.css
@import "dseg/css/dseg.css";

ストップウォッチの実装を追加

以前Reactで作ったStopWatchのコードを移植してくる。
https://github.com/satoshi-nishinaka/stop-watch/blob/main/src/App.tsx

ほぼコピペで出来た。

サイズの調整

main/main.ts内にウィンドウサイズの記述があるので適切なサイズに調整する。

main/main.ts
  mainWindow = new BrowserWindow({
    show: false,
    width: 320,
    height: 200,
    icon: getAssetPath('icon.png'),
    webPreferences: {
      preload: app.isPackaged
        ? path.join(__dirname, 'preload.js')
        : path.join(__dirname, '../../.erb/dll/preload.js'),
    },
  });

アプリのタイトルを設定する

package.jsonに記載されている productName がアプリ名なのでこれを書き換える。

package.json
     "webpack-merge": "^5.8.0"
   },
   "build": {
-    "productName": "ElectronReact",
+    "productName": "Stop Watch",

アプリのウィンドウタイトルは main/menu.ts で定義されているのでこちらも変更。

/src/main/menu.ts
@@ -54,7 +54,7 @@ export default class MenuBuilder {

   buildDarwinTemplate(): MenuItemConstructorOptions[] {
     const subMenuAbout: DarwinMenuItemConstructorOptions = {
-      label: 'Electron',
+      label: 'Stop Watch',

アプリのビルド

npm run package を実行すると、 ./release/build ディレクトリに各プラットフォーム向けのアプリケーションが作成される。

今回の作業レポジトリ

https://github.com/satoshi-nishinaka/stop-watch-by-electron

Discussion