Nuxt.js+Electronを試してみるv2


はじめに

以前nuxt+electronの記事を書いたのですが、だいぶ古くなってきていて最新バージョンのElectronだと動かないみたいなことが起きていたので書き直すことにしました
前記事をリンクしている記事とかもあったので、編集は避けて新規記事としています

今回の記事を元に少し手を加えたリポジトリ

準備

Nuxt.js

プロジェクトを作るフォルダをカレントディレクトリにして下記を実行

npx create-nuxt-app

実行されると色々質問されますが、この記事では下記のような感じで最小構成としています
※Single Page Appにすること

create-nuxt-app v2.10.1
✨  Generating Nuxt.js project in .
? Project name nuxt-electron
? Project description nuxt-electron
? Author name tamfoi
? Choose the package manager Npm
? Choose UI framework None
? Choose custom server framework None (Recommended)
? Choose Nuxt.js modules (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Choose linting tools (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Choose test framework None
? Choose rendering mode Single Page App
? Choose development tools (Press <space> to select, <a> to toggle all, <i> to invert selection)

Electron

下記コマンド実行

npm install --save-dev electron
npm install --save-dev electron-builder
npm install --save-dev electron-devtools-installer
  • electron = electron本体
  • electron-builder = electronのビルドツール
  • electron-devtools-installer = vue devtoolsを開発中に使うために必要

その他のモジュール

下記コマンド実行

npm install --save-dev cross-env
  • cross-env = スクリプト実行時に環境変数を設定できるようにする

ファイル編集 & 作成

package.json

下記のように編集

package.json
{
  "name": "nuxt-electron",
  "version": "0.0.0",
  "description": "nuxt-electron",
  "author": "tamfoi",
  "private": true,
  "main": "entry-point.js",
  "scripts": {
    "dev": "cross-env NODE_ENV=development electron .",
    "build": "cross-env NODE_ENV=production nuxt build && electron-builder"
  },
  "build": {
    "appId": "nuxt-electron",
    "directories": {
      "output": "build"
    }
  },
  "dependencies": {
    "nuxt": "^2.0.0"
  },
  "devDependencies": {
    "cross-env": "^5.2.1",
    "electron": "^6.0.7",
    "electron-builder": "^21.2.0",
    "electron-devtools-installer": "^2.2.4"
  }
}

nuxt.config.js

下記のように編集

nuxt.config.js
module.exports = {
  mode: "spa",
  /*
   ** Headers of the page
   */
  head: {
    title: process.env.npm_package_name || "",
    meta: [
      { charset: "utf-8" },
      { name: "viewport", content: "width=device-width, initial-scale=1" },
      {
        hid: "description",
        name: "description",
        content: process.env.npm_package_description || ""
      }
    ],
    link: [{ rel: "icon", type: "image/x-icon", href: "/favicon.ico" }]
  },
  /*
   ** Customize the progress-bar color
   */
  loading: { color: "#fff" },
  /*
   ** Global CSS
   */
  css: [],
  /*
   ** Plugins to load before mounting the App
   */
  plugins: [],
  /*
   ** Nuxt.js dev-modules
   */
  buildModules: [],
  /*
   ** Nuxt.js modules
   */
  modules: [],

  router: {
    mode: "hash"
  },

  dev: process.env.NODE_ENV === "development",

  /*
   ** Build configuration
   */
  build: {
    /*
     ** You can extend webpack config here
     */
    extend(config, ctx) {
      config.output.publicPath = "./_nuxt/";
    }
  }
};

entry-point.js

ファイルをプロジェクトのルート直下に作成して下記のように編集

entry-point.js
const http = require("http");
const path = require("path");
const { Nuxt, Builder } = require("nuxt");
let config = require("./nuxt.config.js");

config.rootDir = __dirname;

const nuxt = new Nuxt(config);
const builder = new Builder(nuxt);
const server = http.createServer(nuxt.render);

let _NUXT_URL_ = "";
if (config.dev) {
  builder.build().catch(err => {
    console.error(err);
    process.exit(1);
  });
  server.listen();
  _NUXT_URL_ = `http://localhost:${server.address().port}`;
  console.log(`Nuxt working on ${_NUXT_URL_}`);
} else {
  _NUXT_URL_ = "file://" + __dirname + "/dist/index.html";
}

let win = null;
const electron = require("electron");
const app = electron.app;
const newWin = () => {
  win = new electron.BrowserWindow({
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
      preload: path.resolve(path.join(__dirname, "preload.js"))
    }
  });
  win.on("closed", () => (win = null));
  if (config.dev) {
    const {
      default: installExtension,
      VUEJS_DEVTOOLS
    } = require("electron-devtools-installer");
    installExtension(VUEJS_DEVTOOLS.id)
      .then(name => {
        console.log(`Added Extension:  ${name}`);
        win.webContents.openDevTools();
      })
      .catch(err => console.log("An error occurred: ", err));
    const pollServer = () => {
      http
        .get(_NUXT_URL_, res => {
          if (res.statusCode === 200) {
            win.loadURL(_NUXT_URL_);
          } else {
            console.log("restart poolServer");
            setTimeout(pollServer, 300);
          }
        })
        .on("error", pollServer);
    };
    pollServer();
  } else {
    return win.loadURL(_NUXT_URL_);
  }
};
app.on("ready", newWin);
app.on("window-all-closed", () => app.quit());
app.on("activate", () => win === null && newWin());

preload.js

ファイルをプロジェクトのルート直下に作成してください。
中身は空で良くて実際に開発していく時に使うようになります。

おわりに

前回からいくつかアップデートしましたが一番大きいのはElectronのセキュリティ対策に追従したところになります。
どういう変更があったかは下記のスライドを見ると良いかと思います。このスライドにpreload.jsの使い道も書いてあります!
Electron: Context Isolationの欠如を利用した任意コード実行 / Electron: Abusing the lack of context isolation - CureCon(ja)

まだ今回のセットアップでアプリを作りきってないので何か不具合が出るかもしれません。その時はコメントで教えていただければ幸いです。