20201121のNode.jsに関する記事は5件です。

簡単 (電子) レシート receiptline の新機能を試してみた

マークダウン言語で紙のレシートや電子レシートを簡単に作れる receiptline。
https://github.com/receiptline/receiptline
https://www.npmjs.com/package/receiptline

少し前にマイナーバージョンアップがあったので、新機能を試してみました。
(2020年11月21日時点で Verion 1.1.1)

Web フォントで表示

前回の記事で試していた Google Fonts の「Kosugi Maru」が採用されました。
これで iOS, Android, Windows, Linux, Mac どの環境でも同じ表示になるはずです。
00.png

データは過去の記事から。

ReceiptLine
{image:iVBORw0KGgoAAAANSUhEUgAAASAAAAAwAQMAAACL7DXsAAAABlBMVEUAAAD///+l2Z/dAAABqElEQVQ4y+3VMW7bMBQGYLIqwqUo10ymj5AxBQwouUmO0NEFBFOCBo05Qs7QG5DV0K1noOGhKwMvLEDw7yOVALEUIB46BdUofJTe+x8pMZxxsXeFol4gJucm1HMUopght0SPmCO2RCOGGTJLNBB783UShzladidxfBMluYjpFaT+FYqEKE4O/8VtRA7WrRO8Sl+pEIPWq2Nd0LGPHvc81CJYYGgTfslwdy2wql1UP1RBv+2foFVHaO8AaYAHYW4uq1hLF1dsQj/tY9wpS2j0hAgq4TZVXkVP+jChwfrU1BPSSTkkJfxO2KAHQv1U04P1OhbUMh0z0oQk3e0zmrpD50UoqKdWCSGdopxT4oGfojBDNBZvIztF4ynKA95bsG1B3RMaFugw2qPfqmeUI5AzNODQU06NzMgkqpm6m9BTBGX7jn33PTYUy0qYBqki9PmiIApzW9Aeol1fxcR45MJxmjlDYm1GNBb/saDQCHO3/QZ2GyvuK6BdK/AuI6yU/yT6cjjVPqQK1kKqSIE42vPDwVxWTBvEzY69POb2nG/Bf4T2HMT0u/sj/AWoqMYMK2VNkgAAAABJRU5ErkJggg==}|
^^領収書|
2020/07/20 01:23|
#NGC17TH|

缶ビール | ¥211~
シュークリーム | ¥129*
-
小計 | ¥340~
(10%対象 | ¥211)
( 8%対象 | ¥129)
(内消費税等 | ¥28)
^合計 | ^¥340
お 預 り | ¥1,000
お 釣 り | ¥660

*印は軽減税率対象商品です。

USB (Bluetooth) プリンターで印刷

仮想シリアルポート対応 USB (Bluetooth) プリンターでテスト印刷できるようになりました。

そこで、ネットオークションで新たに SII RP-E10 と CITIZEN CT-S255 を入手。
どちらも USB 接続モデルで、店舗で予備機として保管されていたものだそうです。
01.jpg

仮想シリアルポートの設定

RP-E10 用「SII RP Series Communication Software for Windows」をインストール。
02.png

「RP通信設定ユーティリティ」で仮想シリアルポートドライバーに切り替え。
03.png

CT-S255 用「シチズンサーマルプリンタ仮想 COM ポートドライバー」をインストール。
04.png

デバイスマネージャーで確認。RP-E10 は「COM3」、CT-S255 は「COM7」になりました。
05.png

Serial-LAN Converter の設定

servers.json で、 COM3 (または COM7) と TCP 9100 ポートをつなぎます。

servers.json
"serial": {
    "host": "127.0.0.1",
    "port": 9100,
    "device": "COM3"
}

ReceiptLine Designer の設定

printers.json に、RP-E10 と CT-S255 を登録。localhost がプリンターになります。

printers.json
{
    "rp_e10": {
        "host": "127.0.0.1",
        "port": 9100,
        "cpl": 48,
        "encoding": "cp932",
        "gamma": 1.8,
        "upsideDown": false,
        "spacing": false,
        "cutting": true,
        "command": "sii"
    },
    "ct_s255": {
        "host": "127.0.0.1",
        "port": 9100,
        "cpl": 48,
        "encoding": "cp932",
        "gamma": 1.8,
        "upsideDown": true,
        "spacing": false,
        "cutting": true,
        "command": "escpos"
    }
}

テスト印刷

Node.js でサーバーを起動。ReceiptLine Designer で印刷できました。
この開発ツールの使い方はこちら

$ node designer.js

06.jpg

新しい印刷オプション

printers.json で行間隔と用紙カットの有無が選択できるようになりました。

行間隔

spacingtrue にすると、行間が空いて読みやすくなります。
ただし、罫線もしくは縦倍角以上の拡大文字がある行の間隔は変わりません。
07.jpg
ちなみに、前々回の記事は、上下反転印刷に対応できない中途半端な改造でした…

用紙カット

cuttingfalse にすると、印刷完了後に自動カットされなくなります。
08.jpg

また何か作ったら投稿します。ではまた!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Jest】テスト用のAPIとDBとうまく付き合っていく方法

始めに

apiのnodeのバージョンを8から14にしました(まだリリースはしてない)!!
14への移行自体は簡単だったもののテストの移行でかなり苦戦しました。。。
テストは古のライブラリmocha-coで書かれており、仕方なくmocha-coのシンタックスをjestに置き換える作業を開始しました。これが予想以上にめんどくさくその時のHow toを解決する方法がこの記事になります。
以下の問題にお悩みの方はこちらを参考していただけるかと思います。またいい方法をご存知の方はお教えください!!

  • TooManyConnectionとなってしまう
  • 毎回のテストでサーバー立ち上げとクローズをするのめんどくさい
  • --ranInBandつけたけどなんか挙動がよくわからない・・・。
  • mochaからのjestに移行したい

正確にjestのランタイムの仕様が分かっているわけではなく、調べたり試したりした結果なので間違いやもっと詳細が分かる方がいらっしゃいましたらぜひお教えください!!
また正確なコードを書くと記述量が多いので雰囲気で書いているところもあるのでご了承ください。

前提条件

  • テスト用のDBを立ててデータを流しこみながら行っている
  • モジュール単位のテストではなくApiに対してリクエストを投げてそのレスポンスとDBのデータを確認してテストを行う

といったことを前提とします。データベースはインメモリではなくデータベースサーバーを立てる前提です。ですのでテストは順次実行をしていく必要があります。(もともとそうなっていたのが理由としては大きいですが)実際の環境に近い環境を立てることができることがこの作りのメリットだったりします。逆にインメモリにすると並列でテストを実行することができるので、実行速度をあげることができます。

では、データベースサーバーを立てるテストの場合、順次実行をしていかないといけない理由としては、

スクリーンショット 2020-11-21 13.17.32.png

こんな感じに一斉に更新がかかると他のテストに影響が出ることがあります。
たとえば、テスト前にテーブルを消したり、データの更新をしたりとか・・・。すると想定されていた状態にないデータが生まれテストが落ちたします。しかも恐ろしいことにそれが処理時間によって変わるのでテストガチャが生まれます。
そのためこの場合は、順次実行をしていくのがよいです。

スクリーンショット 2020-11-21 13.21.50.png

こうすればテストファイルが一斉に更新されることによっておこるデータベースの不整合を防ぐことができます。
そのためのオプションとして、jestでは--runInBandオプションを利用します。

jest --runInBand

ただし、これだけでは問題が発生します。

テスト用のAPIサーバーが立ち上がらない or 立てたり切ったりし続けると遅い問題

具体的にどういう問題かについての前にAPIテストで行うことを書きます。

APIテスト

APIのテストでは

  • モジュール単位でテストする方法
  • APIサーバーを立ててリクエストを送ることによって

があります。後者の方法で有名なライブラリとしてはsupertestが挙げられます。
例えばexpresssupertestの両方を使ってサーバーを立てるコードは下記のように書けます。

// app.js
const express = require('express');
const app = express();

app.get('/', function (req, res) {
    res.send('Hello World');
}); 
module.exports = app;
// test-request.js
const supertest = require('supertest');
const app = require('./app');

module.exports = supertest.agent(app.listen(3000));
// index.test.js
const testRequest = ('./test-request');

describe('test', () => {
    it('200?', () => {
        testRequest
            .get('/')
            .expect(200);
    });
});

またテスト用のデータを流し込むためのモジュールも下記のように定義します。

// test-model.js
const Sequelize = require('sequelize');
const sequelize = new Sequelize('test', 'test', 'test', {
    host: '127.0.0.1',
    dialect: 'mysql',
});

const models = {...};

module.exports = {
    sequelize: sequelize,
    models: models
};

mochaの場合はtest-requestやtest-modelを読み込んでテストをすればそれでよく、問題は発生しません。
しかし、jestの場合はそうはいきません。

mochaにおける挙動

スクリーンショット 2020-11-21 18.53.43.png

といった形になります。そのため先ほどのコードで一度しかコネクションはできないですし、一度しかサーバーは立ち上がりません。

jestにおける挙動と問題

jestにおいてはどのようになるのかというと、

スクリーンショット 2020-11-21 18.54.12.png

といった形になります。そのためmochaのときのような書き方はやめてテストファイルごとに『立てて、切って』とする必要があります(書き換えの際はめっちゃ大変です)。
とはいえ、毎回そのようにするのは結構大変ですし、毎回『起動、コネクト、停止、切断』と実行していくとテスト完了にかかる時間が増えてしまいます。

  • 毎回のテストでサーバーの起動・停止、dbへのコネクション・停止をする必要がある
    • 記述量が増え、closeを忘れるとテストが落ちる
    • 処理が増えるため必然的に実行時間が長くなる
  • エラーハンドリングをしてcloseしなくても動くようにする
    • 大量のコネクションが貼られることになりtoo many connectionとなってしまう

といった問題が発生します。

そのためには、globalSetupとtestEnvironmentを使います。

globalSetupを使い一度のみ起動する

globalSetupを使います。

スクリーンショット 2020-11-21 18.54.37.png

jestではこういった形で各ファイルが実行されるため、一度しか実行されないglobalSetupでサーバーの起動をなどを行うのが良いかと思われます。

// globalSetup
const supertest = require('supertest');
const app = require('./app');

global.__request = supertest.agent(app.listen(3000));
// test-utils/request.js
module.exports = global.__request;
// test.ts
import request from 'test-utils/request';

describe('test', () => {
   it('200', () => {
     request.get('/').expect(200);
   });
});

気持ち的にこんな感じにglobalにロードしたモジュールを保存させてテストで利用できるようにしたいんですが、ここにはここで落とし穴があります。その落とし穴はsetUpFilesにあります。
setUpFilesでは何を行っているのかというか『あたらしい環境のセットアップです』具体的にはglobalをはじめとしたテスト実行用の環境を生成するタイミングになり、新しくglobalを作っているため、下記のようなコードの場合、

// globalSetUp
global.__global = 'I am from global setup';
// setUpFiles
console.log(global.__global); // <- 新しいコンフィグになるためundefined
global.__setUp = 'I am from setup files'; // <- ただしここで追加したglobalメンバーにはテストファイルがアクセス可能

となります。そのため、実際のテストファイルでも

// test.js
describe('test', () => {
  console.log(global.__global); // undefined
  console.log(global.__setUp); // I am from setup files;

となります。
親玉のランナーの実行環境とは別の環境が作られるというところがポイントになります。
setUpAfterEnvもタイミングは異なれど同じ挙動をします。

そこでどうするのかというとtestEnvironmentを利用します。

testEnvironment

testEnvironmentを使うことで柔軟にテスト環境を構築することができます。

// package.json
    "testEnvironment": "./my-custom-environment.js",
// my-custom-environment
const NodeEnvironment = require('jest-environment-node');

class CustomEnvironment extends NodeEnvironment {
  constructor(config, context) {
    super(config, context);
  }

  async setup() {
    this.global.__request = global.__request;
    await super.setup();
  }
}
module.exports = CustomEnvironment;

このようにsetupメソッドでglobal.__requestthis.global__request に設定することによってテストファイルの方で global.__requestにアクセスすることができます。

まとめ

最終的にはこのようになります。

アプリケーションコード

// app.js
const express = require('express');
const app = express();

app.get('/', function (req, res) {
    res.send('Hello World');
}); 
module.exports = app;
// model.js
const Sequelize = require('sequelize');
const sequelize = new Sequelize('test', 'test', 'test', {
    host: '127.0.0.1',
    dialect: 'mysql',
});

const models = {...};

module.exports = {
    sequelize: sequelize,
    models: models
};

テストユーティリティコード

// test-request.js
module.exports = global.__request;
// test-model
module.exports = global.__db;
// global-setup.js
const supertest = require('supertest');
const app = require('./app');
const db = require('model');

global.__request = supertest.agent(app.listen(3000));
global.__db = db;
// my-custom-environment
const NodeEnvironment = require('jest-environment-node');

class CustomEnvironment extends NodeEnvironment {
  constructor(config, context) {
    super(config, context);
  }

  async setup() {
    this.global.__request = global.__request;
    this.global.__db = global.__db;
    await super.setup();
  }
}
module.exports = CustomEnvironment;
// jest.config.json
{
    "testEnvironment": "./global-setup.js",
    "globalSetup": "./my-custom-environment.js"
}

テストコード

// test.js
const testRequest = ('./test-request');
const testModel = ('./test-model');

describe('test', () => {
    beforeAll(async () => {
      await testModel.reset();
    });
    it('200?', () => {
        testRequest
            .get('/')
            .expect(200);
    });
});

全体図

こんな感じに実装することができます。
また、mockを使ってinjectionすることも可能です。

Mockを使ったインジェクションによる方法

この方法では、管理するファイルは増えるもののアプリケーション内でデータベースなどにコネクトするためのモジュールなどを一括で変更できる点が優秀です。

テストユーティリティコード

// test-request.js
const supertest = require('supertest');
const app = require('./app');

module.exports = supertest.agent(app.listen(3000));
// test-model
const db = require('model');

module.exports = db;
// global-setup.js

global.__request = require('./test-request');
global.__db = require('./test-model');
// setup-after-env.js
jest.mock('test-request', () => global.__request);
jest.mock('test-model', () => global.__db);
// my-custom-environment
const NodeEnvironment = require('jest-environment-node');

class CustomEnvironment extends NodeEnvironment {
  constructor(config, context) {
    super(config, context);
  }

  async setup() {
    this.global.__request = global.__request;
    this.global.__db = global.__db;
    await super.setup();
  }
}
module.exports = CustomEnvironment;
// jest.config.json
{
    "testEnvironment": "./global-setup.js",
    "globalSetup": "./my-custom-environment.js",
    "setupFilesAfterEnv": "./setup-after-env.js",
}

テストコード

// test.js
const testRequest = ('./test-request');
const testModel = ('./test-model');

describe('test', () => {
    beforeAll(async () => {
      await testModel.reset();
    });
    it('200?', () => {
        testRequest
            .get('/')
            .expect(200);
    });
});

問題点

ただし、jestのmockと相性が悪いという問題点があります・・・。
というのもglobal上にサーバーコードがロードされているため、それらを実行したい時は各ファイルごとに一回サーバーを落としてもう一度ロードする必要があります。
そのため、globalにこれらをやるための入り口を開けてあげる必要がありました。
ここら辺みなさんどうしてるんですかね?いろいろとご意見伺いたいのもあって書かせていただきました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Electron入門】GithubのIssueを一瞬で確認できるアプリを作って業務効率化してみた #2 GithubAPI編

はじめに

前回はwindowの作成をしました。
今回はGithubAPIを導入して完成させようと思います。
長くなってしまうのでJavaScriptでDOMの作成をする部分やCSSの説明は省きます。
気になる方はコードをご確認ください!

完成品

option(alt) + Spaceで表示/非表示が切り替えられます。

scc.gif

現状

test.gif

Access Tokenを生成する

それではやっていきましょう。

Githubにログインし、
Settings > Developer settings > Personal access tokens
と進みます。

image.png

Generate new tokenを押すとtoken作成画面に進めます。

Noteにはtokenを識別するために用途を書きます。自分はgit-electronとしました。

Select scopesでtokenの権限を選択できます。今回はrepoの権限全てにチェックをつけます。

Generate tokenを押すとAccess Tokenが生成されるので、
後ほど使うので、赤く囲まれている部分のAccess Tokenをコピーしておきます。

スクリーンショット 2020-11-21 12.13.34.png

AccessTokenを使ってAPIにアクセスする

実際にGithubAPIを叩いてみます。

通信にはaxiosを使用しました。
Tokenの管理にはdotenvを使用しました。

1 . アプリのルートパスで.envファイルを作成して以下のように編集します。

.env
GH_ACCESS_TOKEN=[先ほどコピーしたAccess Token]
GH_BASE_URL=https://api.github.com

2 . main.jsに以下を追記して.envファイルを読み込みます。

main.js
require("dotenv").config({ path: __dirname + "/.env" });

※ AccessTokenは外部に見られたくないので、Githubで公開する際には.gitignoreを作成してremoteに送らないようにしましょう。

.gitignore
.env



3 . axiosを拡張する

axiosの設定を共通化するために拡張します。
自分はpluginsディレクトリを作成して、配下にaxios.jsを作成しました。

plugins/axios.js
const axiosBase = require("axios");

const axios = axiosBase.create({
  baseURL: process.env.GH_BASE_URL,
  headers: {
    Authorization: "Bearer " + process.env.GH_ACCESS_TOKEN,
    "Content-Type": "application/json",
  },
});
module.exports = axios;

これ以降、axiosを呼び出すときはこのaxios.jsを呼び出します。

これでGithub APIを呼び出す準備は整いました!

フロント(Window)からAPIを呼び出す

Electronにはメインプロセスレンダラープロセスがあります。

メインプロセス内(ここで言うmain.js)でBroeserWindowインスタンスを作成し、そのBroeserWindowインスタンスがレンダラープロセス内でWebページ(html)を表示します。
公式ドキュメント

よってhtmlから呼び出したJavaScriptなどはレンダラープロセスで実行する事になります。

ここで困ることが一点あります。
main.jsはメインプロセス上で動くので、Node.jsの文法が使えますが、レンダラープロセスでは使えません。

requireなどNode.js上で使えるメソッドをフロントからの要求に応じて使えるようにするには、
contextBridgeを使って、メイン⇔プロセス間の通信をセキュアにする必要があります。


詳しく知りたい方は以下の記事がうまくまとまっていると思うので参考にしてみてください!

ElectronでcontextBridgeによる安全なIPC通信 - Qiita
ElectronでcontextBridgeによる安全なIPC通信(受信編) -Qiita

contextBridgeを実装する

contextBridgeを使用する為のpreload.jsをルートディレクトリに作成します。

preload.js
const { contextBridge } = require("electron");

contextBridge.exposeInMainWorld("api", {
  github: {
    getIssues: () => {
      // メインプロセスで行いたい処理を書く
      const axios = require("./plugins/axios");
      axios
        .get("/issues")
        .then((res) => {
          console.log(res);
        })
        .catch((e) => console.log(e));
    },
  },
});

次にmain.jsを修正して、BrowserWindowでpreload.jsを読み込むよう設定します。

main.js
  const win = new BrowserWindow({
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
      preload: __dirname + "/preload.js",
    },
    // .....省略......
  })

最後にindex.htmlにscriptタグと発火用のボタンを追加して、preload.jsで定義した関数を呼び出せるようにします。

index.html
  <body style="-webkit-app-region: drag">
    <div style="background: #fff; height: 300px">
      <h1>Hello World!</h1>
      <button onclick="fetchAPI()">get issues</button>
    </div>
  </body>
  <script>
    function fetchAPI() {
      window.api.github.getIssues();
    }
  </script>

ボタンを押して、Electron上でDeveloper toolを開くとIssueのデータが取れていると思います!

image.png

デスクトップアプリとして使いたい場合は、npm run buildでelectron-builderが起動し、dist配下にアプリが生成されます。

DOMの生成やCSSはリポジトリを参考にしてみてください!

最後に

今回はElectroとGithubAPIを組み合わせて業務効率化アプリを作ってみました。

かなり手軽にデスクトップアプリを作成することができた一方、いくつか制限もあったので大規模開発での導入には検討が必要かもしれません。(と言ってもVSCodeなどもelectronで開発されているのである程度安心していいと思っています)

こんな感じで週1~2でアウトプットを記事にしていこうと思います。是非LGTM、フォローの方お願い致します!

Twitterのフォローもお願い致します!
https://twitter.com/1keiuu

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Electron入門】GithubのIssueを一瞬で確認できるアプリを作って業務効率化してみた #1 Alfredっぽいwindow作成編

完成品

option(alt) + Spaceで表示/非表示が切り替えられます。

scc.gif

ソースコード

https://github.com/ikkei12/git-app.pub

はじめに

image.png

Alfred最高ですよね。
彼のおかげでマウスを使う頻度が激減して助かっています。

Alfredと同じ手軽さで、自分がアサインされているissueを確認できるデスクトップアプリが作りたいなと思ったので、Electronで作ってみようと思いました。

Electronには

  • JavaScriptで作れる
  • Chromiumベースなのでwebエンジニアにも優しい

といった利点があり、HTML/CSS/JavaScriptを学んだ初心者の方でも扱いやすいかと思います!

Electronのはじめかた

基本的に公式ドキュメントを参考にElectronのセットアップをするだけですが、一部変更を加えています。

1. 以下のコマンドを実行

mkdir git-app && cd git-app
npm init -y
npm i --save-dev electron
npm i --save-dev electron-builder

2. html/jsファイルを作成

標準的なNode.jsアプリと同様に以下のような構成で始めます。

git-app/
├── package.json
├── main.js
└── index.html
main.js
const { app, BrowserWindow } = require('electron')

function createWindow () {
  const win = new BrowserWindow({
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
    },
    frame: false,
    alwaysOnTop: true,
    useContentSize: true,
    transparent: true,
  })

  win.loadFile('index.html')

  win.on("blur", (e) => {
    // window外をクリックしたタイミング(blur)で閉じる
    app.hide();
  });
}

app.whenReady().then(createWindow)

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

app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow()
  }
})
index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello World!</title>
    <meta
      http-equiv="Content-Security-Policy"
      content="script-src 'self' 'unsafe-inline';"
    />
  </head>
  <body style="-webkit-app-region: drag">
    <div style="background: #fff; height: 300px">
      <h1>Hello World!</h1>
    </div>
  </body>
</html>

3. package.jsonを修正

package.jsonは以下のように書き換えます。

package.json
{
  "name": "git-app",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "NODE_ENV=development electron .",
    "build": "NODE_ENV=production electron-builder"
  },
  "build": {
    "appId": "git-app",
    "mac": {
      "category": "git-app"
    }
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "electron": "^10.1.5"
  }
}

4. 起動

npm start
でこのようなwindowが表示されると思います。

image.png

どこからでもショートカットでアプリを呼び出せるようにする

このままではwindowの表示/非表示を切り替えられないし、PCの起動時に毎回アプリを立ち上げる必要があります。

Alfredのような挙動を実現するには
 1. electronアプリを常駐で起動させておいて、
 2. 表示/非表示を切り替えるショートカットを登録する
必要があります。

1. electronアプリを常駐で起動させる

main.js
app.dock.hide();

if (process.env.NODE_ENV !== "development") {
  app.setLoginItemSettings({
    openAtLogin: true,
    path: app.getPath("exe"),
  });
}

解説

app.dock.hide()
常駐のアプリをDockに表示したくないので、Dockから非表示にします。

app.setLoginItemSettings()
でログイン時に自動で起動してくれるようになります。
ローカルでは起動したくないので、NODE_ENVで判定しています。

・macではログイン時に起動されるアプリは
ユーザーとグループ > ログイン項目に追加されます。
image.png

2. 表示/非表示を切り替えるショートカットを登録する

main.js
app.on("ready", function () {
  globalShortcut.register("alt+space", function () {
    // 現在focusしているwindowを取得
    const window = BrowserWindow.getFocusedWindow();
    // windowが存在すればhide、なければshow
    window ? hideWindow(window) : showWindow();
  });
});

app.on("will-quit", function () {
  // 終了するタイミングで全てのglobalShortcutを解除
  globalShortcut.unregisterAll();
});

function showWindow() {
  // focusさせる事でBrowserWindowのblurイベントを検知させる
  app.focus({ steal: true });
  app.show();
}

function hideWindow(window) {
  // center()する事でshowする時に中央で表示される
  window.center();
  app.hide();
}

解説

・アプリがready状態になった段階で、ショートカットキーを登録しています。自分はalt+spaceで起動したいのでregisterの第一引数に渡しています。

・第二引数ではショートカットキーが押されたタイミングで実行する関数を登録しています。現在focusされているwindowを取得し、存在する場合はhideメソッド、しなければshowメソッドを実行する事で表示を切り替えています。

globalShortcutと言うのがポイントで、普通のショートカット(localShortcut)ではElectronアプリがフォアグラウンドになっている場合にしか反応しません。


このように動くと思います!

test.gif

Tips

Windowをドラッグできるようにする

<body style="-webkit-app-region: drag">

参考: https://www.electronjs.org/docs/api/frameless-window#draggable-region

まとめ

ご覧いただきありがとうございました!
次回はGithub APIからIssueを取得して表示する部分を作成していきます!

#2 GithubAPI編

Twitterのフォローもお願い致します!
https://twitter.com/1keiuu

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Elasticsearchのindexの引越し。Elasticdumpはlimitオプション再定義で高速処理。

はじめに

Elasticsearch を docker を使って、ローカルマシンで使用している。
Elasticsearchのバージョンをあげてみたくなったので、データをDumpして引越ししてみることにした。

事前調査

2020/11/20現在、Googleを使って、「elasticsearch インデックス 移行」と調べてみると、Elasticdumpというスクリプトを使っているひとが多いようだ。

(以下、Googleの検索結果 表示される順に抜粋)

ElasticSearchのindexデータを移行する - Qiitaqiita.com › Node.js
https://qiita.com/taai/items/9244e7dc4dacf29ab95a

よく使うElasticSearchのクエリ(elasticdump) - Qiitaqiita.com › Elasticsearch
https://qiita.com/nakazii-co-jp/items/3199433d685d0600c6d6

Elasticsearchのデータをdumpしてコピーしたい - DRYな備忘録otiai10.hatenablog.com › entry › 2015/03/04
https://otiai10.hatenablog.com/entry/2015/03/04/152842

Elasticsearch 上のデータを割と簡単にダンプして他の ...inokara.hateblo.jp › entry › 2020/05/02
https://inokara.hateblo.jp/entry/2020/05/02/074402

...
..
.

elasticsearch-dump/elasticsearch-dump: Import and ... - GitHubgithub.com › elasticsearch-dump › el...
https://github.com/elasticsearch-dump/elasticsearch-dump

Qiita先輩方の記事の作成日が5年前であっったりしたが、githubは2020年に入っても頻繁にメンテナンスされているようだ。elasticsearchのバージョンは気にせず使えるのではないだろうか。
Google検索結果で表示される情報は、やや作成日が古いものもあることが気になったが、2020年もメンテされているなら使っても問題は少ないのではないか、と期待して、私もこの node.js スクリプトを使ってみることとした。

準備

elasticdump は、nodeスクリプトなので、マシンにnode実行環境が必要であった。私の環境ではすでにnode実行環境があるので npm install elasticdump した(あとで気づいたのだがdockerもあるようだ。)。

実行1 ( dump output=json, input=elasticsearch/index )

事前調査のいずれの先輩たちも、「elasricdump --output ^^^ --input ~~~」と、インプットとアウトプットの指定だけでdumpをし、dumpをelasticsearchに読み込ませているので、そのまま真似してやってみたのだが・・・

ドキュメント数が1200万を超え、ストレージサイズが6GBを超えていた私のindexではjsonのdumpデータの出力完了までに先輩たちと同じやり方では、24時間かかった

出力されたファイルのサイズ: 5302184689 byte

<参考:マシン環境> intel i7-3770k, 16GB Memory, SSD 960GB
<参考:docker Engine> v19.03.13
<参考:docker resources> CPUs:4, Memory:5.50GB, Swap:2GB, Disk image size:59.6GB

振り返り1

そんなに時間かかるものなのか?
動作ログを見てみる・・・なるほど、objectを100ずつ扱っていたのか。
これ、もっと増やせないのか?

elasticsearch-dump のGitHubを読むと、設定オプションがあった。

--limit
                How many objects to move in batch per operation
                limit is approximate for file streams
                (default: 100)

よし、では、dumpしたJsonを新環境に書き込むにあたっては、「--limit」オプションを追加しよう。

実行2 ( dump output=elasticsearch/index, input=json )

実行1の output と input のオプションの対象を入れ替え、 さらに limitオプションを付け足して実行する。いちどにあつかうオブジェクトの量を増やした効果を見てみた。limitの値をどの程度にするのが適切なのかわからなかったが、今回は なんとなく8000 とした。

この設定では、処理時間は 1時間半弱 であった。 概算でも16倍ほど高速だ。

振り返り2

limitの値を80倍に設定して、処理時間は24時間から1時間半へと16分の1になった。この程度の時間であれば、まだ個人利用のPCであっても我慢できる範囲だろう。まだどこかほかにボトルネックがあるのだろうが、このスクリプトで処理時間を変動させる要因のひとつはlimit値であることはまちがいない。次の機会があれば、さらにlimit値をあげてみたい。ただし、適切な値の決定方法がわからない。技術者の勘と経験で決めていいのだろうか。

まとめ

Elasticsearchのデータ移行にあたり、elasticdumpスクリプトを使った。inputとoutputのデータダンプの方向の違いはあるが、limitオプションの設定値をデフォルトの数十倍にして処理速度を比較した。その結果、limitオプションの設定値を大きくすることで、処理時間を大幅に短縮できることがわかった

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む