RSS

カテゴリー別アーカイブ: Node.js

Node.js: Node.js で SQLite3 を使う。

準備

Node.js から SQLite3 のデータベースにアクセスするには、sqlite3 モジュールをインストールする必要がある。

 $ npm install sqlite3

使用手順

sqlite3 モジュールをロードし、データベースオブジェクトを得るには次のような手順が必要である。

const sqlite3 = require('sqlite3');
let db = new sqlite3.Database(':memory:', (err) => {
  if (err) {
    return console.error(err.message);
  }
// db を使用してクエリーを行う。
....
});

よく使うメソッド

db オブジェクトでよく使いそうなメソッドには次のようなものがある。

  • run: INSERT文のような非SELECTクエリーを行う。
  • all: SELECT 文を実行して結果を行のリストとして返す。行は辞書形式である。
  • get: 1 行の結果を返すような SELECT 文を実行し、その 1 行を取得する。行は辞書形式である。
  • each: SELECT 文を実行して取得した行ごとに指定した関数をコールする。(all では非常に多くの行数になる場合に使用する)
  • serialize: run メソッドを順番に実行する。(run メソッドはコールバックを使うので、順番に実行するためにはコールバックのネストが深くなるため)
  • close: データベースの接続を閉じる。

サンプル

run メソッド

// メモリ上のデータベースを作成する。(db.run())
const sqlite3 = require('sqlite3');
let db = new sqlite3.Database(':memory:', (err) => {
  if (err) {
    return console.error(err.message);
  }
  console.log('Connected to the in-memory SQlite database.');
  db.run("CREATE TABLE pairs (key text not null primary key, value text not null)", (err) => {
    db.run("INSERT INTO pairs VALUES(?, ?)", ["A","aa"], (err) => {
       if (err)
         console.log("OK")
       else
         console.log(err);
       db.close();
    });
  });
});

all メソッド

// テーブルのデータ取得 (db.all())
const DbPath = "data.db";
const sqlite3 = require('sqlite3');
let db = new sqlite3.Database(DbPath, (err) => {
  if (err) {
    return console.error(err.message);
  }
  console.log(`Connected to the SQlite database "${DbPath}".`);
  db.all("SELECT * FROM table1", [], (err, rows ) => {
    if (err)
      console.log(err.message);
    else {
      for (let row of rows) {
        console.log(row);
      }
      db.close();
    }
  });
});

get メソッド

// 1行のデータを取得する。(db.get())
const DbPath = "data.db";
const sqlite3 = require('sqlite3');
let db = new sqlite3.Database(DbPath, (err) => {
  if (err) {
    return console.error(err.message);
  }
  console.log(`Connected to the SQlite database "${DbPath}".`);
  db.get("SELECT * FROM table1 where n=?", [1], (err, row ) => {
    if (err)
      console.log(err.message);
    else {
      console.log(row);
      db.close();
    }
  });
});

each メソッド

// クエリー結果を行ごとに取得する。(db.each())
const DbPath = "data.db";
const sqlite3 = require('sqlite3');
let db = new sqlite3.Database(DbPath, (err) => {
  if (err) {
    return console.error(err.message);
  }
  console.log(`Connected to the SQlite database "${DbPath}".`);
  db.each("SELECT * FROM table1", [], (err, row ) => {
    if (err)
      console.log(err.message);
    else {
      console.log(row);
    }
  });
});

serialize メソッド

// メモリ上のデータベースを作成して利用する。(db.serialize())
const sqlite3 = require('sqlite3');
let db = new sqlite3.Database(':memory:', (err) => {
  if (err)
    return console.error(err.message);
});

db.serialize(() => {
  db.run("CREATE TABLE pairs (key text not null primary key, value text not null)")
     .run("INSERT INTO pairs VALUES(?, ?)", ["A","aa"])
     .run("INSERT INTO pairs VALUES(?, ?)", ["B","bb"])
     .each("SELECT * FROM pairs", [], (err, row) => {
       if (err)
         throw err;
       else
         console.log(row);
     });
});

 
コメントする

投稿者: : 2023/04/05 投稿先 JavaScript, Node.js

 

タグ: ,

Electron: 実用的なプログラムを作ってみた。

前に、Python + PySimpleGUI で似たようなプログラムを作ったのですが、Electron で作るとはるかに難易度が高くコード量も何倍にもなりました。

はっきり言って、Electron で GUI プログラムを作るメリットは感じませんでした。

たぶん、ウェブに関連したプログラムなら Python より JavaScript のほうがいい面もあるかもしれませんが・・・。

このプログラムの外観と機能

このプログラムは、指定したフォルダ内の JPEG ファイルを検索して、その一覧を HTML ファイルとして保存します。

「仮想フォルダ」は物理フォルダ名を置き換えるためのものです。

「作成する」ボタンの下の枠はメッセージを表示するための領域です。

プロジェクトの作成

まず、プロジェクト用のディレクトリを作成します。

ここで、npm init で package.json を作っても良いですが、すでにある hello_world プロジェクトから中身をこのディレクトリへコピーします。

ただし、node_modules/ のサイズは大きいので、コピーはせずシンボリックリンクを作って代用します。

Windows の場合は、mklink コマンドでシンボリックリンクを作れます。

そのディレクトリで npm start を実行し、hello_world/ と同じ画面が表示されるのを確認します。

このプロジェクトの内容は次の通りになります。preload.js と renderer.js、style.css は hello_world/ にないかもしれません。その場合は、新しく作成します。

また、index.js は hello_world/ では main.js になっているかもしれません。

  • package.json の name と main を変更する。
  • ない場合は preload.js と renderer.js、style.css を新規作成する。

プロジェクトのファイル

package.json

{
  "name": "make_html",
  "version": "1.0.0",
  "description": "Create a HTML files.",
  "main": "index.js",
  "scripts": {
    "start": "electron ."
  },
  "author": "your_name",
  "license": "ISC"
}

index.html

画面デザインになるファイルです。

最近は人気がないですが、jQuery を使って簡素化しています。

HTML の最後の方で、renderer.js を連携しています。

<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8" />
  <title>HTMLファイル作成</title>
  <link rel="stylesheet" href="style.css" />
  <script src="https://code.jquery.com/jquery-3.6.0.slim.min.js"
    integrity="sha256-u7e5khyithlIdTpu22PHhENmPcRdFiHRjhAuHcs05RI=" crossorigin="anonymous">
  </script>
</head>

<body>
  <h1>HTMLファイル作成</h1>
  <!-- フォーム form1 -->
  <form id="form1">
    <!-- textBox folder -->
    <fieldset>
      <div class="form_row">
        <label for="folder">検索先のフォルダ</label>
      </div>
      <div class="form_row">
        <input type="text" id="folder" name="folder" size="80" />
      </div>
    </fieldset>
    <!-- textBox vfolder -->
    <fieldset>
      <div class="form_row">
        <label for="vfolder">仮想フォルダ</label>
      </div>
      <div class="form_row">
        <input type="text" id="vfolder" name="vfolder" size="80" />
      </div>
    </fieldset>
    <!-- textBox saveFile-->
    <fieldset>
      <div class="form_row">
        <label for="saveFile">保存先のファイル</label>
      </div>
      <div class="form_row">
        <input type="text" id="saveFile" name="saveFile" size="80" />
      </div>
    </fieldset>
    <!-- ボタン btnOK -->
    <fieldset>
      <div class="form_row">
        <button type="button" id="btnOK"> 作成する </button>
      </div>
    </fieldset>
    <!-- メッセージ div message -->
    <fieldset>
      <div id="message" class="form_row message"></div>
    </fieldset>
  </form>
  <!-- renderer.js -->
  <script src="renderer.js"></script>
</body>
</html>

index.js

このファイルは、ネイティブ環境で動作するメインプログラムです。

app.whenReady イベントハンドラで、createWindow() 関数をコールしています。

この関数は、画面となる BrowserWindow オブジェクトを構築し、BrowserWindow オブジェクトの loadFile(‘index.html’) で画面となる HTML ファイルをロードしています。

BrowserWindow オブジェクトを構築する際、preload.js を連携させており、これにより画面とネイティブ間でのやりとりをできるようにしています。

ipcMain.handle() は画面からのボタンクリックイベントを受け取るハンドラで、JPEG ファイル一覧を取得し、それを元に HTML ファイルを作成します。

/* index.js */
'strict';
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('node:path');
const Buffer = require('node:buffer');
const { readdir, writeFile } = require('node:fs/promises');

/* JPEG ファイル一覧を返す。*/
async function list_jpegs(folder) {
  let jpegs = [];
  try {
    const files = await readdir(folder);
    for (let file of files) {
      if (path.extname(file) == '.jpg') {
        // console.log(file);
        jpegs.push(file);
      }
    }
  } catch (err) {
    console.error(err);
  }
  return jpegs;
}

/* JPEG ファイル一覧から HTML ファイルを作成する。*/
async function save_as_html(jpegs, vfolder, save_path) {
  let li_images = "";
  for (let jpg of jpegs) {
    li_images += `<li><img src="${vfolder}/${jpg}"></li>\n`;
  }

  let html = `<!doctype html>
  <html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>JPEGs</title>
  </head>
  <body>
    <h1 style="text-align:center;">JPEGs</h1>
    <hr>
    <ul style="list-style-type:none;">
      ${li_images}
    </ul>
  </body>
  </html>
  `;
  await writeFile(save_path, html);
  return `ファイル ${save_path} が作成されました。`;
}

/* メインウィンドウを構築する。*/
async function createWindow() {
  // BrowserWindow を構築する。
  let preloadjs= path.join(__dirname, 'preload.js');
  const win = new BrowserWindow({
    width: 800,
    height: 512,
    webPreferences: {
      preload: preloadjs,  // index.js と同じ場所にある preload.js を指定して実行する。
      contextIsolation: true  // 画面 renderer.js のコンテキストと分離する。
    }
  });

  // 「作成する」ボタンがクリックされたとき
  ipcMain.handle('make', async (event, folder, vfolder, save_path) => {
    console.log(event);
    //console.log(save_path);
    // folder を検索して JPEG ファイルの一覧を作成する。
    const jpegs = await list_jpegs(folder);
    //  HTML ファイルを作成して save_path へ保存する。
    const m = await save_as_html(jpegs, vfolder, save_path);
    return m;
  });

  // index.html をメインウィンドウにロードする。
  win.loadFile('index.html');
}

/* アプリケーションが準備できたとき */
app.whenReady().then(() => {
  createWindow()
});

preload.js

index.js で BrowserWindow オブジェクトを構築する際に、連携されるファイルで画面 (Chrome 相当) とネイティブ (Node.js) 間のやりとりを定義します。

preload.js 側では ipcRenderer.invoke() メソッドの呼び出しで index.js 側へイベントを送ります。

index.js 側は ipcMain.handle() でイベントを受け取り、イベント処理を行います。

// preload.js
const { contextBridge, ipcRenderer } = require('electron');

/* ブラウザの renderer.js と Node.js の index.js をつなぐ make メソッドを定義する。 */
contextBridge.exposeInMainWorld(
  'electronAPI',   // 名前 'electronAPI' に紐づく。
  {
    // メイン画面のテキストボックスの値をボタンが押されたとき渡す。ipcRenderer.invoke() を使う。
    make: (folder, vfolder, save_path) => ipcRenderer.invoke('make', folder, vfolder, save_path),
  }
);

renderer.js

index.html の最後の方で script タグの src 属性で指定します。

preload.js で定義したインタフェースの名前 (ここでは “electronAPI”) とメソッド名 (ここでは “make”) を使い、preload.js 経由で間接的にネイティブ側 (index.js) へイベントを送ります。

// renderer.js

/* 「作成する」ボタンがクリックされたとき */
$('#btnOK').click(()=> {
    // ブラウザのコントロールの値をメインプロセスへ渡す。
    const folder = $('#folder').val();
    const vfolder = $('#vfolder').val();
    const save_path = $('#saveFile').val();
    if (folder == "" || vfolder == "" || save_path == "") {
        alert("エラー:パラメータを入力してください。");
        return;
    }
    // peload.js で決めた名前でメソッドを呼び出す。
    window.electronAPI.make(folder, vfolder, save_path)
    .then((message) => $('#message').text(message))
});

style.css

画面の見栄えをよくするためのファイルで、なくても問題ないファイルです。

 
コメントする

投稿者: : 2022/08/10 投稿先 Electron, JavaScript, Node.js

 

タグ: , ,

Windows の VSCode で Electron アプリをデバッグするには

Electron のドキュメント「VS Code でデバッグする」で launch.json は次のようになっています。

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug Main Process",
      "type": "node",
      "request": "launch",
      "cwd": "${workspaceFolder}",
      "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
      "windows": {
        "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
      },
      "args" : ["."],
      "outputCapture": "std"
    }
  ]
}

ところが、実際にこの launch.json を組み込んで、デバッグしようとしたらプログラムが起動しませんでした。

調べてみると、 “${workspaceFolder}/node_modules/.bin/electron.cmd” が存在しないようです。

この部分を “C:\Users\user\AppData\Roaming\npm\electron.cmd” に変更して実行したら無事、起動しました。

(注意) “C:/Users/user” の “user” はユーザ環境により変わります。

 
 

タグ: , ,

Electron: Hello World について

Electron のページで Quick Start を開くと “Hello World” のサンプルが出ています。

このサンプルは、github にもソースが置いてあり次のようになっています。

github

Quick Start の主要ファイル

  • package.json
    このプロジェクトの主要ファイルとその詳細、依存関係のリスト
  • main.js
    アプリのメインプロセスで、アプリを開始しメインウィンドウを開く。
  • index.html
    メインウィンドウの画面を定義。

package.json

{
  "name": "helloworld",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "author": "",
  "license": "ISC",
  "scripts": {
    "start": "electron ."
  }
}

main.js

// main.js

const { app, BrowserWindow } = require('electron')
const path = require('path')

// app.whenReady() の中でコールされる関数でメインウィンドウの詳細を定義している。
const createWindow = () => {
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })

  mainWindow.loadFile('index.html')
}

app.whenReady().then(() => {
  createWindow()

  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

// この部分は Mac で実行しないなら不要。
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit()
})

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
    <meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'">
    <title>Hello World!</title>
  </head>
  <body>
    <h1>Hello World!</h1>
    We are using Node.js <span id="node-version"></span>,
    Chromium <span id="chrome-version"></span>,
    and Electron <span id="electron-version"></span>.
  </body>
</html>

その他のファイル

  • preload.js
    アプリが完全にロードされる前のプロセスを記述するが、後述するプロセス間通信を使う場合には改造することもある。
  • renderer.js (github サンプルでは存在)
    index.html のレンダリングプロセス中にコールされるが、このサンプルでは使わないので存在しない。index.html の <script> タグでロードする。後述するプロセス間通信を行う場合、改造する可能性がある。
  • style.css (github サンプルでは存在)
    スタイルシート。index.html の <link> タグでロードする。

独自アプリを作る際の主な改造箇所

package.json

  • name を適切なものに変更。

main.js

  • ウィンドウタイトル
  • ウィンドウサイズ
  • ウィンドウスタイル
  • イベントハンドラ

詳細は BrowseWindow 参照

index.html

  • フォームを追加するなど

イベントハンドラ

Electron のイベントの多くは app オブジェクト、BrowserWindow などで定義されており、それらについてハンドラを記述する。

イベントハンドラの例

const { app } = require('electron')
app.on('window-all-closed', () => {
  app.quit()
})

HTML のコントロールのイベントは、HTML DOM で定義されている通りであり、従来通りにイベントハンドラを定義して処理できる。

もちろん、jQuery や Vue.js のようなものも利用できる。

ローカルリソースへのアクセス

ブラウザはローカルファイルなどに直接アクセスできないが、Electron はネイティブアプリのようにローカルリソースにアクセスできるようなメカニズムを持っている。

ブラウザウィンドウは Chrome ベースなので直接、ローカルリソースにアクセスできないが、内部プロシージャコール (IPC) というメカニズムにより、ネイティブで動作する ipcMain というプロセス にローカルリソースをアクセスするのを委託する。

詳細はプロセス間通信参照

次のサンプルは Electron の「プロセス間通信」に出ているサンプルです。

main.js

const {app, BrowserWindow, ipcMain} = require('electron')
const path = require('path')

function createWindow () {
  const mainWindow = new BrowserWindow({
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })

  ipcMain.on('set-title', (event, title) => {
    const webContents = event.sender
    const win = BrowserWindow.fromWebContents(webContents)
    win.setTitle(title)
  })

  mainWindow.loadFile('index.html')
}

app.whenReady().then(() => {
  createWindow()
  
  app.on('activate', function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit()
})

preload.js

const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
    setTitle: (title) => ipcRenderer.send('set-title', title)
})

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
    <title>Hello World!</title>
  </head>
  <body>
    Title: <input id="title"/>
    <button id="btn" type="button">Set</button>
    <script src="./renderer.js"></script>
  </body>
</html>

renderer.js

const setButton = document.getElementById('btn')
const titleInput = document.getElementById('title')
setButton.addEventListener('click', () => {
    const title = titleInput.value
    window.electronAPI.setTitle(title)
});
 
コメントする

投稿者: : 2022/08/08 投稿先 Electron, JavaScript, Node.js

 

Node.js 各種のパッケージマネージャ

この記事では、Node.js のパッケージマネージャがいくつかあり、気になっていたのでざっくりとまとめてみました。

各パッケージマネージャの詳細については、ググればたくさんの説明があるのでここでは述べません。

npm

Node.js のパッケージマネージャとして標準的なものは npm で、Windows 版のインストーラで Node.js をインストールすると一緒にインストールされます。

npx

npm といっしょに配布されているもので、それ自体は npm の補助みたいなことをするコマンドらしいです。

モジュールの実行に使いますが、インストールされていないものもダウンロードして実行できるようです。(実行後に削除される)

yarn

npm より後に開発されたサードパーティ製のパッケージマネージャだそうです。

npm は最古参のパッケージマネージャのため、いろいろと問題を抱えており、それを改善することを目標にしたもので、npm とほぼ同じ機能を持っているそうです。

特に、node_modules の肥大化を抑えることができるそうです。

pnpm

これもサードパーティ製のパッケージマネージャで、高速化とnode_modules の肥大化抑止ができるそうです。

 

タグ: , , , ,

npm で警告 `–global`, `–local` are deprecated.

最近、Windows で npm を使うと、次のような警告メッセージが出るようになった。

npm WARN config global --global, --local are deprecated. Use --location=global instead.

npm のバージョンが古いのかもしれないと思って最新版にしてみたところ出なくなった。

PS C:\> cd c:\"program files"\nodejs
PS C:\Program Files\nodejs> npm install npm@latest
npm WARN config global `--global`, `--local` are deprecated. Use `--location=global` instead.

removed 1 package, changed 35 packages, and audited 202 packages in 3s

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

found 0 vulnerabilities
PS C:\Program Files\nodejs> npm -v
8.15.1
 
コメントする

投稿者: : 2022/08/03 投稿先 アプリやWindows機能, Node.js

 

タグ: ,

Electron: Electron アプリの基本構造

Electron を使うとクロスプラットフォームで GUI アプリを作ることができますが、ユーザインタフェース部分は Chrome と同じ Web Kit というブラウザエンジンを使っているそうです。

つまり、画面周りはブラウザと同じなので HTML を使って画面を定義できます。

そして、ローカルディスクなどネイティブ (OS) のリソースを使わなければ、シングル画面のウェブアプリと同じように動作します。

しかし、ネイティブリソースのアクセスはブラウザではできないので、ネイティブリソースにアクセス可能な別のバックグランドプロセスを走らせて、そのプロセス上でネイティブリソースにアクセスする構造になっています。

画面つまりブラウザプロセスとネイティブプロセスとはプロセス間プロシージャコールにより通信を行うことでやりとりします。

次の画像はこれを図にしたものです。

簡単な Electron アプリでは次のファイルが必要になります。(各ファイルの意味は上の図参照)
  • index.html
  • main.js (index.js)
  • preload.js
  • renderer.js
  • styles.css (なくてもよい)
  • package.json

package.json は npm init コマンドで作成できますが、Script セクションを修正して Electron を起動できるようにする必要があります。

(例)

{
  "name": "button7",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "author": "",
  "license": "ISC",
  "scripts": {
    "start": "electron ."
  }
}
 
コメントする

投稿者: : 2022/05/25 投稿先 Electron, Node.js

 

タグ: ,

Electron の Quick Start “Hello World” の実行方法

次のページにある Electron Node.js の Quick Start サンプルを Windows で実行させてみました。

https://www.electronjs.org/docs/latest/tutorial/quick-start

Electron のインストール

コマンドプロンプトを起動して、次のコマンドで Electron をインストールします。

npm install -g -D electron@latest

次のコマンドでインストールされたか確認します。(必須ではない)

npm list -g

プロジェクトの作成

コマンドプロンプトで cd コマンドでプロジェクトを作成する親ディレクトリへ移動し、次のコマンドでプロジェクト用のフォルダを作成します。

 mkdir HelloWorld

cd コマンドで HelloWorld フォルダへ移動して、次のコマンドを実行します。

 npm init

このとき、オプションを聞かれるので、すべてデフォルトにします。

これにより、package.json というファイルが作られるので、エディタでこのファイルを開きます。

次のコードを追加します。もし、Script グループがあれば、丸ごと置き換えます。

     "scripts": {
         "start": "electron ."
     }

package.json 全体は次のような感じになります。

{
  "name": "helloworld",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "author": "",
  "license": "ISC",
  "scripts": {
    "start": "electron ."
  }
}

プログラムの作成

package.json と同じ場所に index.html と index.js, preload.js を作ります。内容は Electron Quick Start ページに出ています。

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
    <meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'">
    <title>Hello World!</title>
  </head>
  <body>
    <h1>Hello World!</h1>
    We are using Node.js <span id="node-version"></span>,
    Chromium <span id="chrome-version"></span>,
    and Electron <span id="electron-version"></span>.
  </body>
</html>

index.js

// main.js

// Modules to control application life and create native browser window
const { app, BrowserWindow } = require('electron')
const path = require('path')

const createWindow = () => {
  // Create the browser window.
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })

  // and load the index.html of the app.
  mainWindow.loadFile('index.html')

  // Open the DevTools.
  // mainWindow.webContents.openDevTools()
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
  createWindow()

  app.on('activate', () => {
    // On macOS it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit()
})

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

preload.js

// preload.js

// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
window.addEventListener('DOMContentLoaded', () => {
  const replaceText = (selector, text) => {
    const element = document.getElementById(selector)
    if (element) element.innerText = text
  }

  for (const dependency of ['chrome', 'node', 'electron']) {
    replaceText(`${dependency}-version`, process.versions[dependency])
  }
})

実行

コマンドプロンプトでプロジェクトフォルダにおいて次のコマンドを実行します。

npm start

次のようなウィンドウが開くはずです。

 
コメントする

投稿者: : 2022/05/18 投稿先 Electron, JavaScript, Node.js

 

タグ: , ,

ファイルアップロード (Express.js)

Express.js ファイルアップロード (multer 編)

Node.js,Express.js,ファイルアップロード

はじめに

multer はファイルアップロードでよく使われる Express.js ミドルウェアです。サンプルや関連記事も多くて安定した人気です。

multer ではファイルアップロードに関して次のことが可能です。

  • 単一ファイルのアップロード
  • 単一エレメントからの複数ファイルのアップロード
  • 複数エレメントからの複数ファイルのアップロード

npmjs.com のサンプルや説明は簡単な物しかなかったので、上の3つのケースについて簡単に試してみました。

その前に multer の基本ですが次のようにします。

  • multer モジュールをロードする。
    const multer = require(‘multer’);
  • multer オブジェクトを初期化する。
    const upload = multer({dest:updir}); // updir はアップロード先のフォルダ(フルパス)。省略した場合は、OS の一時フォルダが使用される。
  • リクエストハンドラの第2引数には multer オブジェクトのオプションを設定する。
    router.post(‘/single’, upload.single(‘file1’), (req, res) => { .. });

multer の初期化

multer を使う場合、アップロード先のフォルダを指定しておく必要があります。ただし、これは省略可能で省略すると OS で決まる一時フォルダが使用されます。

const path = require('path');
const multer = require('multer');
const updir = path.dirname(__dirname).replace(/\\/g, "/") + "/tmp";  // アプリケーションフォルダのサブディレクトリ "./tmp" をアップロード先にしている。
const upload = multer({dest:updir});

単一ファイルのアップロード

フォームに 1 つの input[type=”file”] エレメントがあり、multiple 属性がない場合の例です。POST ハンドラの第二引数に upload.single(‘file1’) を指定します。ただし、”file1″ は input[type=”file”] エレメントの name 属性です。

router.post('/single', upload.single('file1'), (req, res) => {
    const path = req.file.path.replace(/\\/g, "/");
    if (path) {
        const dest = updir + "/" + req.file.originalname;
        fs.renameSync(path, dest);  // 長い一時ファイル名を元のファイル名にリネームする。
        res.render('upload', {message: `${dest} にアップロードされました。`});
    }
    else {
        res.render('upload', {message: "エラー:アップロードできませんでした。"});
    }
});

この時のフォームは次のような感じなります。

<form id="form1" method="POST" enctype="multipart/form-data" action="/upload/single">
    <fieldset class="form-group">
        <label for="file1">アップロードファイル (1)</label>
        <input type="file" id="file1" name="file1" class="form-control" />
    </fieldset>
    <fieldset class="form-group">
        <input type="submit" id="submitButton" name="submitButton" class="btn btn-primary" value=" 送信 " />
    </fieldset>
</form>
<p class="message" id="message"><%= message %></p>

複数ファイルのアップロード

複数ファイルをアップロードする場合は、post ハンドラの第二引数に upload.array(‘file1′, MAXFILES) が必要です。’file1’ はフォームの input[type=”file”] の name 属性、MAXFILES はアップロードできるファイルの最大数です。upload は multer オブジェクトです。

const MAXFILES = 3;
router.post('/multiple', upload.array('file1', MAXFILES), (req, res) => {
    const n = req.files.length;
    try {
        for (let i = 0; i < n; i++) {
            const path = req.files[i].path.replace(/\\/g, "/");
            const dest = updir + "/" + req.files[i].originalname;
            fs.renameSync(path, dest);  // 長い一時ファイル名を元のファイル名にリネームする。
        }
        res.render('upload', {message: `${n} 個のファイルがアップロードされました。`});
    }
    catch (err) {
        res.render('upload', {message: "エラー:アップロードできませんでした。"});
    }
});

この時のフォームは次のような感じなります。input[type=”file”] タグに multiple 属性が付与されています。multiple 属性を設定すると、ファイル選択ダイアログで Ctrl キーを押しながら複数ファイルを選択できます。

<form id="form1" method="POST" enctype="multipart/form-data" action="/upload/multiple">
    <fieldset class="form-group">
        <label for="file1">アップロードファイル (1)</label>
        <input type="file" id="file1" name="file1" class="form-control" multiple />
    </fieldset>
    <fieldset class="form-group">
        <input type="submit" id="submitButton" name="submitButton" class="btn btn-primary" value=" 送信 " />
    </fieldset>
</form>
<p class="message" id="message"><%= message %></p>

複数エレメントからのアップロード

フォームには複数の input[type=”file”] がある場合がありますが、そういう場合は post ハンドラの第二引数に upload.fields(namedOption) が必要です。upload は multer オブジェクトです。namedOption は、複数 input[type=”file”] の指定とアップロードできるファイルの最大数の一覧です。

const MAXFILES = 3;
const namedOption = [
    {'name':'file1', maxCount:1}, {'name':'file2', maxCount:MAXFILES}
];
router.post('/named', upload.fields(namedOption), (req, res) => {
    // file1
    const path1 = req.files['file1'][0].path.replace(/\\/g, "/");
    if (path1) {
        const dest1 = updir + "/" + req.files['file1'][0].originalname;
        fs.renameSync(path1, dest1);  // 長い一時ファイル名を元のファイル名にリネームする。
    }
    else {
        res.render('upload', {message: "エラー:アップロードできませんでした。(file1)"});
        return;
    }
    // file2
    const n = req.files['file2'].length;
    for (let i = 0; i < n; i++) {
        let path2 = req.files['file2'][i].path.replace(/\\/g, "/");
        let dest2 = updir + "/" + req.files['file2'][i].originalname;
        fs.renameSync(path2, dest2);
    }
    res.render('upload', {message: `${n+1} 個のファイルがアップロードされました。`});
});

この時のフォームは次のような感じなります。input[type=”file”] (name=”file2″) タグに multiple 属性が付与されています。multiple 属性を設定すると、ファイル選択ダイアログで Ctrl キーを押しながら複数ファイルを選択できます。

<form id="form1" method="POST" enctype="multipart/form-data" action="/upload/multiple">
    <fieldset class="form-group">
        <label for="file1">アップロードファイル (1)</label>
        <input type="file" id="file1" name="file1" class="form-control" />
    </fieldset>
    <fieldset class="form-group">
        <label for="file1">アップロードファイル (2)</label>
        <input type="file" id="file2" name="file2" class="form-control" multiple />
    </fieldset>
    <fieldset class="form-group">
        <input type="submit" id="submitButton" name="submitButton" class="btn btn-primary" value=" 送信 " />
    </fieldset>
</form>
<p class="message" id="message"><%= message %></p>

Express.js ファイルアップロード (express-form-data 編)

Node.js,Express.js,FormData,ファイルアップロード

はじめに

Express.js でファイルアップロードする場合、multer と呼ばれるミドルウェアがしばしば使用されます。
しかし、ファイルアップロードに対応するミドルウェアは他にもいろいろあって express-form-data もその1つです。
express-form-data は単なるファイルアップロードのためのミドルウェアではなく、JavaScript の FormData オブジェクトを Express.js で扱うためのミドルウェアです。

Express.js では POST データはフォームから submit されたデータと JSON にのみ標準で対応しています。このため、Fetch API で FormData を POST してもリクエストボディが undefined になってしまい処理が続行できません。

そこで、express-form-data をミドルウェアにすることで、FormData も正しく扱えるようになります。

インストール

express-form-data は次のようにしてインストールできます。
npm install express-form-data

単一ファイルのアップロード

express-form-data では multer のように単一ファイル、複数ファイル、複数エレメントでコードを変える必要はなく、すべて req.files に保存されています。下の例では、input[type=”file”] の name 属性が “file1” によりアップロードされたファイルを取得して、そのファイルの長い一時ファイル名を元の名前に変更するものです。

このケースでは、express-form-data をミドルウェアとして使用するので、モジュールの最初の方で次のようなコードを追加しておく必要があります。これは他の例でも同様です。
formData.parse() の引数は省略可能ですが、その場合、アップロードファイルの保存先が OS の一時フォルダになります。

const path = require('path');
const formData = require('express-form-data');
const updir = path.dirname(__dirname).replace(/\\/g, "/") + "/tmp"; // アップロード先のフォルダ
router.use(formData.parse({uploadDir:updir, autoClean:true}));

下のコードが post ハンドラの例です。multer のような第二引数は不要です。

router.post('/single', (req, res) => {
    const path1 = req.files.file1.path; // アップロードされたファイルのフルパス名
    const name = req.files.file1.name;  // 元のファイル名
    if (path1) {
        const dest = path.dirname(path1).replace(/\\/g, "/") + "/" + name;
        fs.renameSync(path1, dest);  // 一時ファイル名を元のファイル名に変更する。
        res.render('upload2', {message: dest + " にアップロードされました。"});
    }
    else {
        res.render('upload2', {message: "エラー:アップロードできませんでした。"});
    }
});

このときのフォームは次のような感じです。

<form id="form1" method="POST" enctype="multipart/form-data" action="/upload2/single">
    <fieldset class="form-group">
        <label for="file1">アップロードファイル </label>
        <input type="file" id="file1" name="file1" class="form-control" />
    </fieldset>
    <fieldset class="form-group">
        <input type="submit" class="btn btn-primary" value=" 送信 " />
    </fieldset>
    <br />
</form>
<br />
<p class="message" id="message"><%= message %></p>

複数ファイルのアップロード

express-form-data を使用する場合、アップロードファイルが単一か複数かの違いはなく req.files にそのデータが入っています。この例では input[type=”file”] の name 属性が “file1” の場合、アップロードファイルの情報が req.files.file1 に入っていますが、これが配列になっており、そこからアップロードファイルの情報を取り出します。

(注意) express-form-data の初期化が必要。単一ファイルのアップロード の説明参照。

router.post('/multiple', (req, res) => {
    const n = req.files.files1.length;  // アップロードされたファイル数
    for (let i = 0; i < n; i++) {
        let src = req.files.files1[i].path.replace(/\\/g, "/");
        let dest = path.dirname(req.files.files1[i].path).replace(/\\/g, "/") + "/" + req.files.files1[i].name;
        fs.renameSync(src, dest);  // 長い一時ファイル名から元の名前に変更する。
    }
    if (n > 0) {
        res.render('upload2Multi', {message: n + " 個のファイルがアップロードされました。"});
    }
    else {
        res.render('upload2Multi', {message: "エラー:アップロードできませんでした。"});
    }
});

この例でのフォームは次のようになっています。

<form id="form1" method="POST" enctype="multipart/form-data" action="/upload2/multiple">
    <fieldset class="form-group">
        <label for="file1">アップロードファイル </label>
        <input type="file" id="files1" name="files1" class="form-control" multiple />
    </fieldset>
    <fieldset class="form-group">
        <input type="submit" class="btn btn-primary" value=" 送信 " />
    </fieldset>
    <br />
</form>
<br />
<p class="message" id="message"><%= message %></p>

Fetch API と FormData を使用してのアップロード

express-form-data を使うと FormData オブジェクトとして input[type=”file”] を含むフォームを丸ごとサーバへ送信できるので、クライアント側のコードが簡単になります。
(注意) express-form-data の初期化が必要。単一ファイルのアップロード の説明参照。

router.post('/formdata', (req, res) => {
    const path1 = req.files.file1.path.replace(/\\/g, "/"); // アップロードされたファイルのフルパス名
    const name = req.files.file1.name;  // 元のファイル名
    if (path1) {
        const dest = path.dirname(path1).replace(/\\/g, "/") + "/" + name;
        fs.renameSync(path1, dest);  // 一時ファイル名を元のファイル名に変更する。
        res.send(dest + " にアップロードされました。");
    }
    else {
        res.send("エラー:アップロードできませんでした。");
    }
});

次のコードは、input[type=”file”] エレメントを含むフォームの例です。アップロードの結果は id=”message” で指定される p タグの内部文字列として表示されます。

<form id="form1" method="POST" enctype="multipart/form-data">
    <fieldset class="form-group">
        <label for="file1">アップロードファイル </label>
        <input type="file" id="file1" name="file1" class="form-control" />
    </fieldset>
    <fieldset class="form-group">
        <input type="button" class="btn btn-primary" value=" 送信 " onclick="javascript:submitClick()" />
    </fieldset>
    <br />
</form>
<br />
<p class="message" id="message"></p>

次がクライアント側のコードで、送信ボタンをクリックしたときのハンドラです。fetch 関数のパラメータが FormData オブジェクトなのですっきりします。

function element(id) {
    return document.getElementById(id);
}

function submitClick() {
    const formData = new FormData(form1);
    fetch('/upload2/formdata', {method:"POST", body:formData})
    .then(res => res.text())
    .then(text => element('message').innerText = text)
    .catch(err => element('message').innerHTML = err.message);
}

FormData を使った複数エレメントのファイルアップロード

この例は複数 input[type=”file”] エレメント (name=”file1″, name=”file2″) を持つフォームからのファイルアップロードのコードです。基本的に単一ファイルの場合と同じで、name=”file1″, name=”file2″ について同じコードを書くだけです。

router.post('/formdata2', (req, res) => {
    const path1 = req.files.file1.path.replace(/\\/g, "/"); // アップロードされたファイルのフルパス名 (1)
    const path2 = req.files.file2.path.replace(/\\/g, "/"); // アップロードされたファイルのフルパス名 (2)
    const name1 = req.files.file1.name;  // 元のファイル名
    const name2 = req.files.file2.name;  // 元のファイル名
    if (path1) {
        var dest1 = path.dirname(path1).replace(/\\/g, "/") + "/" + name1;
        fs.renameSync(path1, dest1);  // 一時ファイル名を元のファイル名に変更する。
        if (path2) {
            var dest2 = path.dirname(path2).replace(/\\/g, "/") + "/" + name2;
            fs.renameSync(path2, dest2);  // 一時ファイル名を元のファイル名に変更する。
        }
        res.send(`"${dest1}", "${dest2}" にアップロードされました。`);
    }
    else {
        res.send("エラー:アップロードできませんでした。");
    }
});

次のコードは、この場合のフォームの例です。

<form id="form1" method="POST" enctype="multipart/form-data">
    <fieldset class="form-group">
        <label for="file1">アップロードファイル (1)</label>
        <input type="file" id="file1" name="file1" class="form-control" />
    </fieldset>
    <fieldset class="form-group">
        <label for="file1">アップロードファイル (2)</label>
        <input type="file" id="file2" name="file2" class="form-control" />
    </fieldset>
    <fieldset class="form-group">
        <input type="button" class="btn btn-primary" value=" 送信 " onclick="javascript:submitClick()" />
    </fieldset>
    <br />
</form>
<br />
<p class="message" id="message"></p>

次のコードは、クライアント側の JavaScript コードの例です。

function element(id) {
    return document.getElementById(id);
}

// 送信ボタンがクリックされたとき
function submitClick() {
    const formData = new FormData(form1);  // form1 を元に FormData オブジェクトを作成する。
    fetch('/upload2/formdata2', {method:"POST", body:formData})
    .then(res => res.text())
    .then(text => element('message').innerText = text)
    .catch(err => element('message').innerHTML = err.message);
}

express-form-data を使うと multer より簡単にファイルアップロードを行えます。特に、Fetch API を使う場合は、FormData が使えるので非常に簡単になります。

Express.js でのファイルアップロードについて、multer と express-form-data をそれぞれ使った記事を Qiita にも投稿しました。

multer 編

express-form-data 編

 
コメントする

投稿者: : 2021/09/01 投稿先 Express, Node.js

 

タグ: , ,

Express.js では FormData が使えない訳

Express4 で Fetch API を使ってフォームデータ(FormData) を POST しても req.body が undefined になってしまいます。

これは適切なミドルウェアが不足しているためです。

POST されたデータはミドルウェアによって解析・処理されて req.body にセットされますが、デフォルトでは express.json と express.urlencoded がそれを行っています。

express.json は JSON データの処理、 express.urlencoded  は submit ボタンなどでポストされたフォームデータを処理するミドルウェアです。

そして、JavaScript の FormData クラスのデータはこのどちらも処理してくれないようです。

そのため、 req.body が undefined になってしまい、うまく動作しません。

よって、FormData を処理してくれるミドルウェアを追加してやる必要があります。

express-form-data” を導入したらうまく FormData を使ったデータを受け取ることができました。コード例を下に示します。

このミドルウェアはファイルアップロードに対応していますが、使わないならparse の引数(オプション)は {} でも大丈夫だと思います。

var formData = require("express-form-data");
app.use(formData.parse({uploadDir:path.join(__dirname, 'tmp'), autoClean:true}));

なお、インストールは npm install express-form-data で行います。

 
1件のコメント

投稿者: : 2021/08/24 投稿先 Express