ローカルファイルを読み書きできた。

ブツ

20220816094410_020.png

ファイルを読み込む

  1. Openボタンを押す
  2. 拡張子txtファイルを選ぶ
  3. 2のファイル内容がテキストエリアに表示される

ファイルを書き込む

  1. テキストエリアに適当なテキストを書く
  2. Saveボタンを押す
  3. 入力したファイルに1の内容が書き込まれる

前回まで

インストール&実行

NAME='Electron.File.Read.Write.20220803171508'
git clone https://github.com/ytyaru/$NAME
cd $NAME
npm install
npm start

コード

package.json

{
  ...
  "main": "main.js",
  ...
  "devDependencies": {
    "electron": "^19.0.9"
  }
}

main.js

const fs = require('fs')
const path = require('path')
const { app, BrowserWindow, ipcMain, dialog } = require('electron')
...
ipcMain.handle('open', async (event) => {
    const { canceled, filePaths } = await dialog.showOpenDialog({
        filters: [{ name: 'Documents', extensions: ['txt'] }],
    })
    if (canceled) return { canceled, data: [] }
    const data = filePaths.map((filePath) =>
        fs.readFileSync(filePath, { encoding: 'utf8' })
    )
    return { canceled, data }
})
ipcMain.handle('save', async (event, data) => {
    const { canceled, filePath } = await dialog.showSaveDialog({
        filters: [{ name: 'Documents', extensions: ['txt'] }],
    })
    if (canceled) { return }
    fs.writeFileSync(filePath, data)
})

preload.js

const {remote,contextBridge,ipcRenderer} =  require('electron');
...
contextBridge.exposeInMainWorld('myApi', {
    setup: ()=>{
        document.querySelector('#open').addEventListener('click', async () => {
            console.debug(`openをclickした!`)
            const { canceled, data } = await ipcRenderer.invoke('open')
            if (canceled) { return }
            document.querySelector('#text').value = data[0] || ''
        })
        document.querySelector('#save').addEventListener('click', async () => {
            const data =  document.querySelector('#text').value
            await ipcRenderer.invoke('save', data)
        })
    },
})

renderer.js

window.myApi.setup();

index.html

<!DOCTYPE html>
<html>
    ...
    <script src="./renderer.js"></script>
    </body>
</html>

ポイント

  1. IPC通信
  2. ファイルダイアログUI
  3. ファイル読書

1. IPC通信

EelctronやNode.jsのようなブラウザ外機能はIPC通信を介して実行する。

IPC通信はプロセス間通信のこと。別プロセスで動作している別プログラムの間でデータ通信する。おそらくElectronがローカルシステム用プロセスと、HTML描画&JS実行エンジン用プロセスに分けることでセキュリティを保ちつつ、二者間でIPC通信することで、ローカルシステムにアクセスしながらHTMLやJSの機能も使えるようにしているのだろう。

ファイル 内容
main.js ipcMain.handleで実装する
preload.js ipcRenderer.invokeで呼び出す

IPC通信はEelctronでのお約束っぽい。バージョンが違うと作法も変わるかも。ブラウザ版のときとは明確にコードが異なり共通化できない部分。

このようなEelctron固有の部分で苦労させられる。ネットで調べてもバージョン差異により動作しないこともある。きっと今後もハマるだろう。

2. ファイルダイアログUI

const { app, BrowserWindow, ipcMain, dialog } = require('electron')
...
const { canceled, filePaths } = await dialog.showOpenDialog({
    filters: [{ name: 'Documents', extensions: ['txt'] }],
})

electronパッケージの中にdialogがある。UI表示するAPIを呼び出す。

読込と書込で異なるAPIをもつ。

const { canceled, filePath } = await dialog.showSaveDialog({
    filters: [{ name: 'Documents', extensions: ['txt'] }],
})
API 概要
showOpenDialog 読込用ファイルダイアログ表示
showSaveDialog 書込用ファイルダイアログ表示

3. ファイル読書

ファイルシステム操作用パッケージfsを取り込む。

const fs = require('fs')

ファイル読込APIが以下。

const data = filePaths.map((filePath) =>
    fs.readFileSync(filePath, { encoding: 'utf8' })
)

ファイル書込APIが以下。

fs.writeFileSync(filePath, data)

Electron用ドキュメントにfsはなかった。Node.js用ドキュメントにfsドキュメントがあった。これでファイル操作するようだ。