[Electron] Electron+React+Typescriptでマルチウィンドウアプリの作成

2023-02-01

2023-02-01

Electron+React+Typescript でマルチウィンドウアプリを作成した際の知見をまとめました。

検証環境

  • Windows 10 Pro 22H2
  • Node.js v18.14.0
  • Electron 22.2.0

Renderer Process 側の対応

HashRouter によるルーティング

react-router-domをインストールし、HashRouterコンポーネントでルーティングを定義します。

npm install react-router-dom

App.tsx

import React from 'react';

import MainView from './MainView';
import SubView from './SubView';

import { HashRouter, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <HashRouter>
      <Routes>
        <Route path='/' element={<MainView />} />
        <Route path='sub' element={<SubView />} />
      </Routes>
    </HashRouter>
  );
};

export default App;

Main Process 側の対応

URLの末尾に#/[Renderer ProcessのRouteコンポーネントに設定したパス]を追加します。

下記のコードでは、アプリの起動時にウィンドウが重ならないように、BrowserWindowのインスタンス作成時に、x, yプロパティにウィンドウの初期位置を設定しています。

parentプロパティに別のBrowserWindowを指定することで、ウィンドウに親子関係を持たせ、親ウィンドウが閉じたときに子ウィンドウも自動で閉じるようにすることもできます。

Main.tsx

...
mainWindow = new BrowserWindow({
    width: 1024,
    height: 728,
    x: 0,
    y: 0,
    ...
});

subWindow = new BrowserWindow({
    width: 1024,
    height: 728,
    x: 1024,
    y: 0,
    parent: mainWindow, 
    ...
});

if (app.isPackaged) {
    mainWindow.loadURL(`file://${__dirname}/../index.html#/`);
    subWindow.loadURL(`file://${__dirname}/../index.html#/sub`);
} else {
    mainWindow.loadURL('http://localhost:3000/index.html#/');
    subWindow.loadURL(`http://localhost:3000/index.html#/sub`);
}
...

Electron React Boilerplateを使用した場合

Electron React Boilerplateを使用してプロジェクトを作成した場合、src/main/util.tsを修正する必要があります。

src/main/util.ts

import { URL } from 'url';
import path from 'path';

export function resolveHtmlPath(htmlFileName: string, hash: string = '') {// ハッシュの引数を追加
  if (process.env.NODE_ENV === 'development') {
    const port = process.env.PORT || 1212;
    const url = new URL(`http://localhost:${port}`);
    url.pathname = htmlFileName;
    url.hash = hash;// 開発用のHTMLパスにハッシュを追加
    return url.href;
  }
  return `file://${path.resolve(__dirname, '../renderer/', htmlFileName)}#${hash}`;// 本番用のHTMLパスにハッシュを追加
}

src/renderer/App.tsx

import { HashRouter, Routes, Route } from 'react-router-dom';
import icon from '../../assets/icon.svg';
import './App.css';

function MainWindow() {
  return (
    <div>
      <div className="Hello">
        <img width="200" alt="icon" src={icon} />
      </div>
      <h1>Main Window</h1>
    </div>
  );
}

function SubWindow() {
  return (
    <div>
      <div className="Hello">
        <img width="200" alt="icon" src={icon} />
      </div>
      <h1>Sub Window</h1>
    </div>
  );
}

export default function App() {
  return (
    <HashRouter>
      <Routes>
        <Route path="/" element={<MainWindow />} />
        <Route path="/sub" element={<SubWindow />} />
      </Routes>
    </HashRouter>
  );
}

src/main/main.ts

...
mainWindow = new BrowserWindow({
    width: 1024,
    height: 728,
    x: 0,
    y: 0,
    ...
});

subWindow = new BrowserWindow({
    width: 512,
    height: 728,
    x: 1024,
    y: 0,
    parent: mainWindow, 
    ...
});

mainWindow.loadURL(resolveHtmlPath('index.html'));
subWindow.loadURL(resolveHtmlPath('index.html', '/sub'));
...