Nuxt3 SSR + Electron で app packing

もう年末ですね〜
今年の後半はoF(c++,glsl)書かずにずっとwebのframework(Nuxt2,3,Node.js,Socket-io,etc)周り触ってました。

昨今のweb技術トレンドの進化が早すぎて10年近くweb開発に関わらずにいたらほぼ何も分からない浦島太郎状態になっており、20代前半に初めてActionScript3.0から始めたあの頃の気持ちになっている今日この頃。フレッシュ。←

んで作りたい物が色々と出来てきた中でどういった技術の組合わせでいこうかなとサーベイ中、Nuxt3を試していた中で、これまでちゃんと自分では書いてこなかったbackend側のserverへのapiやらmiddleware追加が、個人的に劇的に使いやすく、あーちょっと本格的にNuxt3で開発したいなと思ったのが今年の秋くらい。

という事で秋ごろから色々勉強してざっくりNuxt3での開発方法は分かったものの自分が案件で使う場合、普通のwebサイト開発に使うというイメージがあんまり湧かない。。そもそも自分にwebサイト開発の依頼こないw

ではwebアプリとしてのローカル運用はどうだ?と考えた時に
期間限定単発ものであればこれで全然良さそう。

ただ毎回フルスクラッチでの開発をやめたいなという背景もあり
ある程度自分で使うframeworkとして開発を進めたい。
最終的には日々の絵作りだけしてたい。
visualシーンの無限追加だけしてたい。

じゃあElectronでアプリに固めてベースアプリとして開発してしまうかと思って1週間前に触り始める。←イマココ
という訳でようやく本題のElectronでのappビルド。

Electron + Nuxt/Vue3 ssr

これ。調べてたらssrでのElectron運用があんまり出てこなかった。(相変わらずやってる事がニッチなんよ)
みんなstatic generate。いや静的サイト最高だけどさ。せっかくssrでなんか今風に開発してたのにビルド後は静的になるってなんかさ。

という訳で、buildしてpackingする所だけの超絶シンプルなサンプル書きました。
https://github.com/rettuce/nuxtron

ざっくりやってることはElectronのNode.jsからchild_process呼んで
Nuxt側のNode.js Server立ち上げてElectronのChromiumからはそのサーバを読みに行く、みたいな感じ。

基本使うモジュールもこれだけ。

"electron": "^22.0.0",
"electron-packager": "^17.1.1",
"npm-run-all": "^4.1.5",
"nuxt": "3.0.0"

Nuxt3をビルドしてElectronのパッケージングのところだけなのでNuxtの開発に関しては全く何もせずApp.vueもデフォルトの <NuxtWelcome /> だけ。

Electronのパッケージングもelectron-packagerでもelectron-builderでもどっちでもいいかと。というかアプリの開発が進んで納品まで来たら最終的にインストーラ作ったりしたいのでelectron-builder使ってそうw

git cloneして、あとは以下scriptsでアプリ作成まで。

# yarn
$ yarn install

# nuxt dev + electron browser
$ yarn dev
# or
# nuxt dev only
$ yarn nuxt:dev

# nuxt build (for before electron:packing)
$ yarn nuxt:build

# electron dev
$ yarn electron:start

# packing for production
$ yarn pkg # for mac+win
$ yarn pkg:mac
$ yarn pkg:win # not tried.

script
"dev": "run-p nuxt:dev electron:start"
npm-run-allで nuxt:dev のlocal開発サーバ起動と electron:start のブラウザ起動並列処理してる。
hotreloadアリで electron browser上で開発できる。

"nuxt:dev": "NODE_ENV=development nuxt dev"
普通のnuxt開発。最終的にonlineに上げるとかならコッチで開発でおk。

"nuxt:build": "nuxt build"
普通のnuxtビルド。パッケージングする前にこのbuildが必要。

"electron:start": "NODE_ENV=development electron ."
普通のelectron開発。サンプルではelectron/main.js に格納。

"pkg:mac": "NODE_ENV=production electron-packager . appName --platform=darwin --arch=x64 --overwrite"
electron-packager でapp作成。win環境ならpkg:winでできるはず。試してない。

src

ハマったのは一箇所、Electronでのビルド後のリソースまでのパスが変わるってやつ。
コマンドからNODE_ENVつけて判別ってのが主流らしいのでそのまま以下の箇所で流用。
Node.js の child_process 使ってアプリビルド後なら自身のリソース内のビルド後NuxtServer立ち上げ。
developmentならrootディレクトリ上にある .output/server/index.mjs 見に行ってNuxtServer立ち上げ。
$yarn dev 時はchildProcess前にnuxt devが走ってるのでhotreloadも効く、って感じ。

const isApp = process.env.NODE_ENV === "development" ? false : true;

var childprocess;
if (isApp) {
  const path = require("path");
  const { fork } = require("child_process");
  childprocess = fork(path.join(__dirname, "../.output/server/index.mjs"));
} else {
  const util = require("util");
  const childProcess = require("child_process");
  const exec = util.promisify(childProcess.exec);
  childprocess = exec(`node .output/server/index.mjs`);
}

BrowserWindowで loadURL(“http://localhost:3000”);

function createWindow() {
  const mainWindow = new BrowserWindow({
    width: 1280,
    height: 720,
  });
  mainWindow.loadURL("http://localhost:3000");
}
app.whenReady().then(() => {
  setTimeout(
    function () {
      createWindow();
    },
    isApp ? 10000 : 1000
  );
  app.on("activate", function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });
});

あとはNuxt側のサーバ立ち上げより先にelectron browserがlocalhost:3000見に行ってしまってページがない〜みたいなのがあったので適当にdevelopment時は1秒待ってブラウザ立ち上げ、app立ち上げはなんか結構時間かかったので10秒待って立ち上げ、とかにしてますがこの辺りはsplash/load画面とか用意して準備できたらevent受け取って改めてNuxt側読みにいくとかした方がいいと思います。

あと最後にアプリ終了時に自分で作ったchild_processを殺しておかないとゾンビ化します。

  if (childprocess) process.kill(childprocess.pid);
  app.quit();

まーアプリにするには他にもアイコン設定とかインストーラ作ったりとか、updater構成考えたり考えないといけない部分色々あって、てかそもそも中身書かないとダメなのでこれだけだと全然意味ないんですが、とりあえずこの組み合わせで行けそうかな〜という目処がたったのでサーベイ共有でした。🙋‍♀️

Nuxtは始めて数ヶ月、Electronに至っては数日なので
こっちの方がいいよとかこれ間違ってるよとかあれば教えてもらえると泣いて喜びます。

you