Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all 8920 articles
Browse latest View live

Node.jsで簡単にサーバーを立てる

$
0
0

Node.jsでサーバーを立てるモジュールについて書きます。

前提条件

  • npmがインストールされていること
  • package.jsonがあること

インストール

$ npm install-D http-server

使い方

設定

npm run serverコマンドでhttp-serverを動かすように定義します。

package.json
{"scripts":{"server":"http-server -o"}}

実行

npmコマンドを実行します。

$ npm run server

出力結果

Starting up http-server, serving ./
Available on:
http://127.0.0.1:8080

出てきたパスを叩けば現在のディレクトリのファイルを見ることができます。

参考文献

この記事は以下の情報を参考にして執筆しました。


Node.js: CPU負荷で3秒かかっていた処理を「Worker Threads」で1秒に時短する

$
0
0

本稿では、Node.jsのWorker Threadsとその基本的な使い方について説明します。

本稿で知れること

  1. Worker Threadsの概要
    • Worker Threadsとは何か?
    • それが解決してくれる問題は何か?
  2. worker_threadsモジュールの基本的な使い方
    • スレッド起動時にデータを渡すにはどうしたらいいか?
    • 3秒かかる処理を、並列処理で1秒に短縮する方法。

Worker Threadsとは?

  • CPUがボトルネックになる処理を、別スレッドに負荷分散し、効率的に処理する仕組み。

Worker Threadsが解決する問題

  • Node.jsはシングルプロセス、シングルスレッド。
  • シングルプロセス、シングルスレッドは、シンプルさという利点がある。
  • 一方で、CPUに高い負荷がかかる処理は、他の処理を止めてしまう欠点があった。
  • Worker Threadsは、複数のスレッドを使えるようにすることで、この欠点を解決する。

Worker Threadsが解決しない問題

  • I/Oがボトルネックになる処理。
    • これは、Node.jsの非同期I/OのほうがWorkerより効率的に処理できる。

worker_threadsモジュールとは?

  • JavaScriptを並列(parallel)で実行するスレッドが利用できるモジュール。
  • libuvを用いた本物のスレッド(イベントループやマルチプロセスはない)。
  • Web WorkerそっくりのAPI。つまりフロントエンドの知識が活きる。
  • Node.js 10.5.0から使える。
  • Node.js 11.7.0未満は、--experimental-workerフラグをつけてNodeを起動する必要があった。

child_processモジュール、clusterモジュールとの違い

  • worker_threadsはメモリを共有できる。
  • child_processとclusterはメモリが共有できない。

worker_threadsモジュール入門

worker_threadsモジュールの基本的な使い方を見ていきましょう。

Workerを起動するには?

まず、Workerを起動する方法を見ておきましょう。Workerの起動はシンプルに言って、Workerクラスをnewするだけです。第一引数は、ワーカーの処理を書いたファイル名です。

main.js
const{Worker}=require('worker_threads')constworker=newWorker('./worker.js')
worker.js
console.log('Hello from worker')

このmain.jsをnodeで起動すれば、worke.jsがスレッドで実行されます。

console
$ node main.js
Hello from worker

Worker起動時にデータを渡すには?

次に、Worker起動時にmain.jsからデータを渡す方法を見てみましょう。データを渡すには、Workerクラスをnewするときに、第2引数にworkerDataに渡したいデータを入れます。

main.js
const{Worker}=require('worker_threads')constworker=newWorker('./worker.js',{workerData:'message from main.js!',})

ワーカー側のコードでは、workerDataworker_threadsモジュールからインポートすることで、渡されたデータを参照できます。

worker.js
const{workerData}=require('worker_threads')console.log('Hello from worker')console.log(workerData)

この例では、'message from main.js!'がワーカーに伝わっているのがわかります。

console
$ node main.js
Hello from worker
message from main.js!

workerDataは複製される

Workerにデータを渡せることは渡せるのですが、共有はされないので注意してください。次の例では、配列をワーカーに渡し、ワーカーがその配列を変更するコードですが、main.jsにはワーカーが加えた変更が伝わってきません。つまり、workerDataで渡されるデータは、複製されるのです。

main.js
const{Worker}=require('worker_threads')constworkerData=[1,2,3]constworker=newWorker('./worker.js',{workerData})// オブジェクトを渡すsetTimeout(()=>console.log('main.js: %O',workerData),1000)// どうなる?
worker.js
const{workerData}=require('worker_threads')console.log('worker.js %O',workerData)workerData.push(4,5,6)// Worker側で変更を加えるconsole.log('worker.js %O',workerData)
実行結果
worker.js [ 1, 2, 3 ]
worker.js [ 1, 2, 3, 4, 5, 6 ]
main.js: [ 1, 2, 3 ]

複数のWorkerを起動するには?

Workerにデータを渡す方法が分かったので、今度は複数のWorkerを起動してみましょう。

複数のWorkerを起動するには、単純にWorkerインスタンスを複数作るだけです:

const{Worker}=require('worker_threads')constworker1=newWorker('./worker.js',{workerData:'worker1',})constworker2=newWorker('./worker.js',{workerData:'worker2',})
const{workerData}=require('worker_threads')console.log(`I'm a ${workerData}`)
実行結果
$ node main.js
I'm a worker1
I'm a worker2

CPU高負荷な処理を分散してみよう

worker_threadsモジュールの基本的な使い方が分かったと思うので、CPU高負荷な処理をマルチスレッドで分散することを試してみましょう。

高負荷な関数を準備する

処理分散を試すために、CPUに高負荷がかかり、処理に時間がかかる関数を用意します。

このhighLoadTask関数は、単純に20億回ループするだけですが、実行するとCPU使用率が100%になるくらいの負荷が発生します。(CPUの性能によって実行時間が左右されるので、手元の環境で実行してみる際は、20億回の部分を調整して数秒で終わる程度の回数に直してください)

highLoadTask.js
functionhighLoadTask(){for(leti=0;i<2_000_000_000;i++){}}module.exports={highLoadTask}

どのくらい負荷と時間がかかるか確認してみよう

highLoadTask関数をシングルスレッドで3回実行するようにしたコードが次です:

main.js
const{highLoadTask}=require('./highLoadTask')console.time('total')console.time('task#1')highLoadTask()console.timeEnd('task#1')console.time('task#2')highLoadTask()console.timeEnd('task#2')console.time('task#3')highLoadTask()console.timeEnd('task#3')console.timeEnd('total')

このスクリプトを実行すると、(僕のPCでは)合計で約3秒かかります:

実行結果
node main.js
task#1: 1.455s
task#2: 1.460s
task#3: 478.089ms
total: 3.399s

CPU負荷のほうは、Activity Monitorで「node」に検索を絞って、モニタリングすると、99%が使われていることがわかります。使用率が100%を超えていないので、当てられているCPUコア数は1個ということもわかります。(CPU使用率が上がりきらない場合は、highLoadTask関数のループ数を増やしてください)

Screenshot_2020_03_10_10_28.png

CPU高負荷処理は非同期処理でも解決しない

ちなみに、次のようにPromiseを使って各タスクを非同期処理にしても、かかる時間は変わりませんので、この関数はシングルスレッドでは限界があるということが確認できます:

main.js
const{highLoadTask}=require('./highLoadTask')functionasyncHighLoadTask(taskName){returnnewPromise(resolve=>{console.time(taskName)highLoadTask()console.timeEnd(taskName)resolve()})}(asyncfunction(){console.time('total')awaitPromise.all([asyncHighLoadTask('task#1'),asyncHighLoadTask('task#2'),asyncHighLoadTask('task#3'),])console.timeEnd('total')})()

Workerを使って3秒かかる処理を1秒にする

では実際にWorkerを使って、処理を分散するコードを書いてみます。

まず、Worker側の実装です:

worker.js
const{workerData}=require('worker_threads')const{highLoadTask}=require('./highLoadTask')console.time(workerData)highLoadTask()console.timeEnd(workerData)

次に、メイン側の実装です。

main.js
const{Worker}=require('worker_threads')console.time('total')constworker1=newWorker('./worker.js',{workerData:'worker1',})constworker2=newWorker('./worker.js',{workerData:'worker2',})constworker3=newWorker('./worker.js',{workerData:'worker3',})Promise.all([newPromise(r=>worker1.on('exit',r)),newPromise(r=>worker2.on('exit',r)),newPromise(r=>worker3.on('exit',r)),]).then(()=>console.timeEnd('total'))

main.jsでは、ワーカーを3つ起動して、3並列で処理させるようにしました。

最後のPromise.allの部分は、ワーカーの終了を待って合計所要時間を計測するためのコードですので、ここでは気にしないでください。

実行してみましょう:

実行結果
$ node main.js
worker1: 1.526s
worker3: 1.529s
worker2: 1.529s
total: 1.579s

実行結果を見てのとおり、各タスクの処理は1.5秒程度で変化はありませんが、並列実行したため3秒かかっていた合計所要時間が1.5秒に短縮されました。

気になるCPU使用率は、298%になっているので、コアが3つがきびきび働いているのがわかります。

Screenshot_2020_03_10_10_37.png

おわり

本稿では、Node.jsのWorker Threadsの概要と、worker_threadsモジュールの基本的な使い方を解説しました。

CPUがボトルネックとなる処理をマルチスレッドで分散すると、マルチコア環境で眠っているCPUを効率的に働かせられたり、その結果処理時間を短縮できることが分かったかと思います。

今後投稿するかもしれないこと

本稿では基本的なことがらにしか触れませんでしたが、下記のような疑問も気になるところなので、追って投稿できたらと思います。

  • スレッドで例外が発生したらどうなる?
  • 素のNode, child_process, worker_threadsのアーキテクチャ上どういう違いが出てくるか?
  • メインスレッドとの通信方法は?
  • メモリ共有は具体的にどうやるのか?
  • Workerを扱いやすくするライブラリはある?
  • Workerを停止するには?
  • 通信のオーバーヘッドは?
  • Worker生成のオーバヘッドはどのくらい?

WSL(openSUSE)にNode.jsをインストールする

$
0
0

はじめに

せっかくの春休み中なので勉強を兼ねて新しいことに挑戦したいと思い、Electornを触ってみることにしました。
今回は前準備として、WSLにNode.jsとnpmをインストールしました。

開発環境

  • Windows 10 Pro 1909
  • WSL(openSUSE-Leap-15-1)

Node.jsのインストール

WSLを起動し、リポジトリにあるnodejsのバージョンを調べます。

$ sudo zypper se nodejs
Loading repository data...
Reading installed packages...

S | Name                    | Summary                                          | Type
--+-------------------------+--------------------------------------------------+-----------
  | nodejs-common           | Common files for the NodeJS ecosystem            | package
  | nodejs-emojione         | A set of emojis designed for the web             | package
  | nodejs-emojione-awesome | Emojione templates                               | package
  | nodejs-emojione-demo    | EmojiOne Demos                                   | package
  | nodejs-emojione-meteor  | EmojiOne utility for Meteor                      | package
  | nodejs-emojione-python  | EmojiOne utility for Python                      | package
  | nodejs-emojione-swift   | EmojiOne utility for swift                       | package
  | nodejs-packaging        | Node.js Dependency generators for openSUSE       | package
  | nodejs-underscore       | A utility belt library for JavaScript            | package
  | nodejs10                | Evented I/O for V8 JavaScript                    | package
  | nodejs10                | Evented I/O for V8 JavaScript                    | srcpackage
  | nodejs10-devel          | Files needed for development of NodeJS platforms | package
  | nodejs10-docs           | Node.js API documentation                        | package
  | nodejs8                 | Evented I/O for V8 JavaScript                    | package
  | nodejs8                 | Evented I/O for V8 JavaScript                    | srcpackage
  | nodejs8-devel           | Files needed for development of NodeJS platforms | package
  | nodejs8-docs            | Node.js API documentation                        | package
$ sudo zypper info nodejs10
Loading repository data...
Reading installed packages...


Information for package nodejs10:
---------------------------------
Repository     : leap-15.1-update
Name           : nodejs10
Version        : 10.16.3-lp151.2.6.1
Arch           : x86_64
Vendor         : openSUSE
Installed Size : 23.6 MiB
Installed      : No
Status         : not installed
Source package : nodejs10-10.16.3-lp151.2.6.1.src
Summary        : Evented I/O for V8 JavaScript
Description    :
    Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js
    uses an event-driven, non-blocking I/O model. Node.js has a package ecosystem
    provided by npm.

v10.16.3が最新のようなので、今回はnodejs10をインストールします。
同時にnpm10もインストールします。

$ sudo zypper in nodejs10 npm10

インストールが終わったらバージョン確認をします。

$ node -v
v10.16.3
$ npm -v
6.9.0

無事インストールできていることが確認できました。

今回は以上になります。
次は実際にElectronを動かしてみるところまでやってみたいと思います。

[Windows] Dockerを使用してホスト環境を汚さずにAngularの開発環境を構築する

$
0
0

TL;DR

  • Node.jsの開発環境をDockerをまとめたかった
  • ローカル環境にはDocker Desktopとvisual studio codeのみインストール
  • Angularでng newから書いている記事がなかったので書いた。これをベースに開発環境を育てていってほしい

環境

  • Windows10
  • Docker Desktop 2.2.0.3
  • docker-composeはDocker Desktopに同梱
  • visual studio code 1.42.1[Dockerの拡張機能を使用]

完成したリポジトリ

https://github.com/MegaBlackLabel/angular-docker-sample

ファイル

\---node-angular-sample
│  .gitignore
│  docker-compose.yml
│  docker-entrypoint.sh
│  Dockerfile
Dockerfile
FROM node:ltsCOPY docker-entrypoint.sh ./COPY ./frontend ./RUN npm install-g @angular/cli

WORKDIR /frontendEXPOSE 4200ENTRYPOINT ["/docker-entrypoint.sh"]
docker-compose.yml
version:'3'services:node:build:.environment:NG_CLI_ANALYTICS:"ci"NG_CLI_ANALYTICS:"false"ports:-"4200:4200"volumes:-"./frontend:/frontend"-nodemodules:/frontend/node_modulestty:truevolumes:nodemodules:driver:"local"
  • マウントしているfrontendについては初回起動時に作成する
  • NG_CLI_ANALYTICS、NG_CLI_ANALYTICSはnpm ci実行時に途中で確認メッセージが出て止まらないようにするために設定
docker-entrypoint.sh
#start SQL Server, start the script to create/setup the DB#!/bin/bashFILE="/frontend/package.json"if[-e$FILE];then
  npm ci
fi

/bin/bash
  • package.jsonがあるときはnpm ciを実行する
  • /bin/bashを実行してコンテナが停止しないようにする

マウントするフォルダの作成

docker-composeに記載しているホストのマウント先のフォルダを作成(無いとdocker-compose起動時にエラー発生)
mkdir frontend\node_modules

docker-composeビルド

コンテナのビルドを実施
docker-compose build

docker-compose起動

ビルドしたコンテナをバックグラウンドで起動
docker-compose up -d

Angularのプロジェクト作成

起動しているコンテナにアタッチしてコマンド実行
cd ..
ng new frontend --skipGit=true

※、これでfrontendフォルダにAngularプロジェクトが作成されます

Angular起動

外部から見えるようにhostを設定します
# attach shell
ng serve --host=0.0.0.0

docker-compose終了

コンテナを初期化して終了
docker-compose down -v

Angularプロジェクト作成後のフォルダ構成(.git内は省略)

\---node-angular-sample
│  .gitignore
│  docker-compose.yml
│  docker-entrypoint.sh
│  Dockerfile
│
└─frontend
    │  .editorconfig
    │  .gitignore
    │  angular.json
    │  browserslist
    │  karma.conf.js
    │  package-lock.json
    │  package.json
    │  README.md
    │  tsconfig.app.json
    │  tsconfig.json
    │  tsconfig.spec.json
    │  tslint.json
    │
    ├─e2e
    │  │  protractor.conf.js
    │  │  tsconfig.json
    │  │  
    │  └─src
    │          app.e2e-spec.ts
    │          app.po.ts
    │
    ├─node_modules
    └─src
        │  favicon.ico
        │  index.html
        │  main.ts
        │  polyfills.ts
        │  styles.scss
        │  test.ts
        │
        ├─app
        │      app.component.html
        │      app.component.scss
        │      app.component.spec.ts
        │      app.component.ts
        │      app.module.ts
        │
        ├─assets
        │      .gitkeep
        │
        └─environments
                environment.prod.ts
                environment.ts

git cloneして使う場合

初回時にマウント先のフォルダを作成
mkdir frontend\node_modules
docker-compose build

まとめ

この構成だとホストにNode.jsをインストールしないで初めからDockerの開発環境で進められる。
ただし、作成済みのプロジェクトをgit cloneして使う際にはnode_modulesを作成する必要があるので注意。

以上

IBM CloudでNode-REDを開始する手順【2020/03最新版】

$
0
0

はじめに

ここでは、IBM Cloud上でビジュアルプログラミング用のフローベース開発ツールであるNode-REDを使用・開始する手順を説明します。

なぜこんなことを今更書くのかというと、ずばりIBM CloudのUIが変わり私自身が混乱したためです。
以前は Qiita: IBM CloudでNode-REDの立ち上げ方IBM Developer: Node-RED を使用してリアルタイムのチャット・アプリケーションを 5 分で作成するに書かれているような手順、つまりカタログのサービスメニュからNode-RED Starterが探せ、かつ比較的簡単にアプリが開始できたため迷うこともなかったのですが、これが(個人的に)大きく変わったため同じく困る人がいるのではないかと想像し、最新版として記載することとしました。

なお、当記事の情報は2020年3月10日時点の情報です。
きっとまた変わることがあると思います。その時にはご容赦ください。

0. IBM Cloudにログイン

https://cloud.ibm.comよりIBM Cloudにログインします。

1. Node-RED Appを構成

1-1. 「カタログ」をクリック

まずは以前と同じくダッシュボード上部にあるく「カタログ」をクリックします。

image.png

1-2. 「ソフトウェア」メニューから「Node-RED App」を作成

ここで早速変更点です。
以前は「サービス」メニューからNode-Red Starterを選択していましたが、現在は「ソフトウェア」メニューから「Node-RED App」を選択する手順になっています。
Node-RED Appを見つけ、「作成」をクリックします。

image.png

1-3. アプリの作成

上部にある「アプリの作成」ボタンを押してアプリの作成を開始します。

image.png

アプリの詳細およびCloudantサービスに関する詳細を入力します。

  • アプリの詳細 - アプリ名:任意のアプリケーション名を入力して下さい。
  • Cloudant - リージョン:好みのリージョンを選択します。現在は「東京」も選択可能です。
  • Cloudant - 価格設定プラン:ある種一番重要です。無料のプランで始めたい場合は、「Lite」プランとなっていることを確認してください。

入力が完了したら、上部の「作成」ボタンを押します。

image.png

アプリが作成されると以下の画面に戻ります。

image.png

ここでまた変更点です。
以前ならこの時点で「アプリ URL にアクセス」というリンクが、アプリ名の右横に表示され即座にフローエディタにアクセスが可能でした。
しかし、現在のUIではそうなっておらず、ここからまたいくつかステップを踏む必要があります。

2. Continuous Delivery の構成

2-1. アプリのデプロイ

1-3の画面から続けます。
右側もしくはCloudantの下にContinuous Delivery の構成メニューがあるので、その中の「アプリのデプロイ」をクリックします。

image.png

2-2. DevOpsツールチェーンの構成

DevOpsツールチェーンを構成するための詳細入力画面が表示されるので、必要な値を入力していきます。
なお、当構成を完了させるには、事前に「Cloud Foundryの組織」および「スペース」を作成しておく必要があります。
適切に作成されていない場合、以下のが表示されるため、右サイドバーの「組織を作成します。」から対応します。

image.png

  • IBM Cloud API 鍵:「New」ボタンをクリックするとCreate a new API key with full accessというダイアログが表示されます。
    チェックボックスのSave this key in a secrets store for reuseは、このキー(鍵)を他のツールチェーンやスターター・キット・ワークフローなどで再使用する場合に選択します。 これにより、IBM Key Protect シークレット・ストアと統合され、そこにキーが保存されます。
    内容を確認したら「OK」をクリックします。

image.png

  • インスタンスの数:必要なインスタンス数をプルダウンから選択します。
  • インスタンス当たりのメモリー割り振り:必要なメモリー量を選択します。
  • デプロイするリージョン:リージョンを選択します。この記事の時点では「ダラス」が自動選択されました。
  • 組織: Cloud Foundry組織情報に基づき、自動で入力されますので、適切なものを選択します。
  • スペース: Cloud Foundry組織情報に基づき、自動で入力されますので、適切なものを選択します。
  • ホスト:アプリ名と同じものが自動で入ります。アプリ名から変えることも可能です。
  • ドメイン:プルダウン形式でいくつかのドメインが選択可能です。
  • DevOpsツールチェーン名:アプリ名と同じものが自動で入ります。アプリ名から変えることも可能です。
  • ツールチェーンのリージョン:いくつかのリージョンから選択可能です。

入力が完了したら、上部の「作成」ボタンをクリックします。

image.png

ツールチェーンの作成が完了すると、以下の通りツールチェーンが表示されます。

image.png

2-3. Delivery Pipelineの確認

Delivery Pipelineの状況の横の「進行中」からBUILD/DEPLOYの進捗が確認可能です。
数分でDEPLOYまで完了するはずですので、正常に完了することを確認します。

image.png

DEPLYの枠内にあるジョブ部分の「ログおよび履歴の表示」をクリックすればDEPLOYに関する詳細なログを確認できます。

3. Node-REDフローエディタの開始

3-1. 「リソース・リスト」から「Cloud Foundry アプリ」をクリック

ここまでくると、やっとフローエディターが開始できます。
ハンバーガーメニューから「リソース・リスト」をクリックします。

image.png

リソース・リストでは、Cloud Foundry アプリセクションとAppsセクションに今回作成しているアプリが表示されます。
実はフローエディターへはどちらを選んでも辿り着けますが、ここではDEPLOYされたアプリの詳細が確認可能な、かつ従来と似た画面であるCloud Foundry アプリセクションのアプリをクリックします。

image.png

3-2. 「アプリ URL にアクセス」をクリック

やっと以前の画面が表示されました。
「アプリ URL にアクセス」をクリックして、フローエディタを開始しましょう。

image.png

ちなみに、3-1で選択しなかったAppsセクションの方は以下のようになっています。
最初はなかった「Apps URL にアクセス」が追加されています。

image.png

4. Node-REDフローエディタの設定

アプリの初回アクセスでは、従来通り設定メニューが表示されます。
ここでは多くを語りませんが、従来通りユーザーやそのパスワードを設定します。

image.png

5. フローエディタの起動

設定が完了すると、Node-RED on IBM Cloudの初期画面が表示されます。
「Go to your Node-RED flow editor」をクリックして、フローエディタでの開発を開始しましょう。

image.png

終わりに

当手順は以下リンク先の英語のTutorialを参考に実施しました。
いくつか簡素化しているところもあるので、興味のある方はリンク先を参照してください。

初めてのGraphQL ~特徴・導入・簡単なクエリまで

$
0
0

最近Reactを学びはじめたこともあって同じFacebookが作ったGraphQLにも手を出してみました。
GraphQLの輪郭もわからない時は「NoSQLのクエリの弱さを独自の検索エンジンで全て解決してくれる魔法のクエリ言語」なんて思ってました。(浅はかなり)

GraphQLを勉強しはじめて2日目で書いた記事なのであやふやなところや間違ってる点など多々あると思います...

GraphQLとは

実際のところはクエリとレスポンスの構造に対応関係を持たせることができたり、スキーマ設定によりエディタにおける補完や型チェックなどが行えたり、REST APIと違い処理によってGETエンドポイントを分ける必要がなく冗長化を防ぐことができたりと、いわばAPIのフレームワークのような印象を受けました。

イメージ図を作ってみました↓
GraphQL流れ.png

インストール(導入)

今回はNodeベースのexpress + apollo + GraphQLでAPIサーバーをたてていきます。

// プロジェクトフォルダを作成
$mkdir first_graphql
$cd first_graphql

// プロジェクトのセットアップ
$npm init

// 必要なモジュールをインストール
$npm i --save express apollo-server-express graphql

// index.jsを作成
$touch index.js

コードを書いていく

index.js

constexpress=require('express')const{ApolloServer,gql}=require('apollo-server-express')constapp=express()// GraphQL言語のschemaを使ったconstruct定義// レスポンスするデータの型を設定しておくconsttypeDefs=gql`
    type Query {
        firstQuery: String
    }
`// schemaごとに取得ロジックを設定していく constresolvers={Query:{firstQuery:()=>"Hello,world!",},}constserver=newApolloServer({typeDefs,resolvers})server.applyMiddleware({app})app.listen({port:4000},()=>console.log(`Server ready at http://localhost:4000${server.graphqlPath}`)

動かしてみる

$nodemon index.js
//$node index.jsでも可

動かしたら
http://localhost:4000/graphqlにアクセス!
スクリーンショット 2020-03-10 22.17.22.png
こんな画面が出たらうまく立ち上がっています。
これはapolloサーバーが提供しているものでGraphQLへのクエリをGUIでテストすることができます。

クエリを実行してみる

index.jsの↓の部分で設定したQueryを実行してみます。

// GrapohQL言語のschemaを使ったconstruct定義// レスポンスするデータの型を設定しておくconsttypeDefs=gql`
    type Query {
        firstQuery: String
    }
`// schemaごとに取得ロジックを設定していく constresolvers={Query:{firstQuery:()=>"Hello,world!",},}

GUIの左側のエディタに以下の通り書いて実行してみましょう。

{
  firstQuery
}

右側の実行結果画面にこのように表示されればデータの引き出し成功です。

{
  "data": {
    "firstQuery": "Hello,world!"
  }
}

最後に

少し勉強してみるとGraphQLは決して魔法の言語などではなく、型の定義やデータ取得ロジックを自分でしっかり書いていかなければならないということがわかりました。

しかしながらGETエンドポイントを1つに集約できること、フロント側からの直感的なクエリを実装できること、TypeScriptのようにコード管理を容易にしてくれることなどなど...便利なことに違いはありません!

まだまだ勉強しはじめたばかりなのでこれからどんどん知識を蓄えていきたいと思います!

それでは!

nvm + Node.js (npm)導入

$
0
0

はじめに

React.js使ってWebアプリ開発するために、ちょっくらNode.jsの導入が必要になったので、メモメモ。
後述するように、Macのログインシェルの推奨がbash -> zshに変わり、
ちょっと細かい点の修正が必要となったので、自分でまとめてみました。

事前の注意

macOS Catalina(10.15)からデフォルトのShellが、bash -> zshに変わってる関係で、
コマンドのパス設定などのやり方が変わります。
ご自身でお使いのmacOSと、ログインシェルとして何を使ってるか確認してから構築お願いします。

環境

  • macOS Catalina(10.15.3)

nvmインストール

まずは、事前準備
インストール時に、自動的にnvmのコマンドパスが設定されるように.zshrcを作成

ターミナル
~ % touch ~/.zshrc

ここで、macOS Mojave以前をお使いの方に注意!!!
デフォルトログインシェルが、bashになるので、ここで作成するファイルは、
.bash_brofileor.bashrcとなります。

公式サイトGithub:nvm-sh/nvmからインストールコマンドをコピー
使うのは、curlの方
ターミナルを起動して、コピーしたコマンドをペーストして実行!

ターミナル
~ % curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash

GithubからCloneされる形でインストールが実行される。
色々な処理経過が表示されて、done.の表示が続く。
終わったら、以下のようなメッセージが表示される。

=> Close and reopen your terminal to start using nvm or run the following to use it now:
export NVM_DIR=“$HOME/.nvm”
[ -s “$NVM_DIR/nvm.sh” ] && \. “$NVM_DIR/nvm.sh” # This loads nvm
[ -s “$NVM_DIR/bash_completion” ] && \. “$NVM_DIR/bash_completion” # This loads nvm bash_completion

nvmのコマンドパスを通すためにターミナルを再起動
ターミナル再起動後に以下のコマンドを実行して、バージョンが表示されれば完了

ターミナル
~ % nvm -v
Node Version Manager (v0.35.3)
Note: <version>refers to any version-like string nvm understands. This includes:
 - full or partial version numbers, starting with an optional “v” (0.10, v0.1.2, v1)
 - default (built-in) aliases: node, stable, unstable, iojs, system
 - custom aliases you define with `nvm alias foo`

(以下略)

node.js(+npm) インストール

npmはNode.jsをインストールすると一緒にインストールされます。

開発に使用するNode.jsのバージョン指定してインストール
-> 使用するフレームワークとかで指定があるので、必要なバージョンをコマンドで指定

ターミナル
~ % nvm install [version]

---
v.8.14.0をインストールする場合は。。。
~ % nvm install v8.14.0

以下となれば成功

Computing checksum with shasum -a 256
Checksums matched!
Now using node v8.14.0 (npm v6.4.1)

開発で使用するバージョンを設定
-> 複数バージョンをインストールする場合は、この操作でバージョンの選択が必要

ターミナル
~ % nvm use [version]

---
v.8.14.0を利用する場合は。。。
~ % nvm use v8.14.0

以下となれば完了

Now using node v8.14.0 (npm v6.4.1)

デフォルトで使用するバージョンを設定
もし複数のバージョンをインストールして、デフォルトを指定したい場合に実行

ターミナル
~ % nvm alias defalut v8.14.0
defalut ->v8.14.0

Node.js設定の確認
以下のコマンドを実行

ターミナル
~ % nvm ls

利用中のバージョンには先頭に、->が表示される

     v8.10.0
->   v8.14.0

デフォルトに設定されたバージョンは以下の表示

default -> v8.10.0

ついでにnodeコマンドで、本当に上記の設定が反映しているか確認

ターミナル
~ % node -v
v8.14.0

って、設定したバージョンが表示されれば全て完了

小技

nvmで取得可能なバージョン一覧や、ltsのバージョンなどを確認するコマンド

ターミナル
nvm ls-remote

小言

nvmのインストールでうまくいかなかった話

最初、特に気にせず先人の記事、自分のMojave時代の構築方法として、
.zshrc作らず、インストールして、パス設定を後からやったけど、パス通らなかった。。。
ちなみに、.zshrcとか作らずインストールすると、以下のメッセージが出てる。

=> Compressing and cleaning up git repository
=> Profile not found. Tried ~/.bashrc, ~/.bash_profile, ~/.zshrc, and ~/.profile.
=> Create one of them and run this script again
  OR
=> Append the following lines to the correct file yourself:
export NVM_DIR=“$HOME/.nvm”
[ -s “$NVM_DIR/nvm.sh” ] && \. “$NVM_DIR/nvm.sh” # This loads nvm
=> Close and reopen your terminal to start using nvm or run the following to use it now:
export NVM_DIR=“$HOME/.nvm”
[ -s “$NVM_DIR/nvm.sh” ] && \. “$NVM_DIR/nvm.sh” # This loads nvm

よくよく見ると、以下の設定足りてなくない?

[ -s “$NVM_DIR/bash_completion” ] && \. “$NVM_DIR/bash_completion” # This loads nvm bash_completion

とりあえず、事前に.zshrc作っておくことで問題なかったので、深く原因究明してないです。。。
おすすめは、作っておいてねですね。

参考記事

以下の記事を参考にさせていただきました。ありがとうございました。
nvm + Node.js + npmのインストール @sansaisoba
nvmの導入と使い方 @tanishi

Node.js Worker Threads: もしWorkerが例外を投げたらどうなる?→メインスレッドもろとも死ぬ

$
0
0

前回に引き続き、Node.jsのWorker Threadsについて解説していきます。

本稿では、もしもWorkerが例外を投げたらどうなるのかを検証します。

前回: CPU負荷で3秒かかっていた処理を「Worker Threads」で1秒に時短する

本稿で分かること

  • Worker Threadsでエラーが発生した場合、メインスレッド側はどうなるのか?
  • Worker側の例外をメインスレッドでエラー処理できるのか?

Workerで例外を起こすと?

まずは、Workerで例外を投げるとどうなるのか見ていきましょう。

メインスレッド側のコード、シンプルにWorkerを起動するだけにします:

main.js
const{Worker}=require('worker_threads')constworker=newWorker('./worker.js')

Worker側のコードは常に例外を投げるようにします:

worker.js
thrownewError('Error was thrown in worker')

実行してみると、どうなるのか試してみます。

$node main.js

events.js:293
      throw er;// Unhandled 'error' event
      ^
Error: Error was thrown in worker
    at Object.<anonymous>(/Volumes/dev/nodejs-playground/worker-threads/07-error-thrown-in-worker/worker.js:1:7)    at Module._compile (internal/modules/cjs/loader.js:1147:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1167:10)
    at Module.load (internal/modules/cjs/loader.js:996:32)
    at Function.Module._load (internal/modules/cjs/loader.js:896:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
    at MessagePort.<anonymous>(internal/main/worker_thread.js:167:24)    at MessagePort.emit (events.js:316:20)
    at MessagePort.onmessage (internal/worker/io.js:78:8)
    at MessagePort.exports.emitMessage (internal/per_context/messageport.js:11:10)
Emitted 'error' event on Worker instance at:
    at Worker.[kOnErrorMessage] (internal/worker.js:209:10)
    at Worker.[kOnMessage] (internal/worker.js:219:37)
    at MessagePort.<anonymous>(internal/worker.js:145:57)    at MessagePort.emit (events.js:316:20)
    at MessagePort.onmessage (internal/worker/io.js:78:8)
    at MessagePort.exports.emitMessage (internal/per_context/messageport.js:11:10)

何もエラーハンドリングをしないと、例外ログが出力されることがわかりました。

Workerで例外が発生すると、メインスレッドも落ちる?

今度は、Workerで例外が発生し、メインスレッドで一切エラーハンドリングしなかったら、メインスレッドが落ちるのか試してみましょう。

メインスレッドの生死を確認できるように、main.jsの最後にログを出力するコードを追加します:

main.js
const{Worker}=require('worker_threads')constworker=newWorker('./worker.js')// 追加↓setTimeout(function(){console.log('The main thread is alive!')},3000)

メインスレッドが生き残れたら、3秒後に「The main thread is alive!」が出力されるはずです。

さっそく動かしてみます:

$node main.js

events.js:293
      throw er;// Unhandled 'error' event
      ^
Error: Error was thrown in worker
    at Object.<anonymous>(/Volumes/dev/nodejs-playground/worker-threads/08-error-thrown-in-worker/worker.js:1:7)...(略

なんと、先程と同じエラーログだけ出て、プロセスが3秒持たずに終了してしまいました。Workerの例外はメインスレッドのプログラムを停止させてしまうことがわかりました。

Workerの例外をメインスレッドで補足するには?

Worker側で発生した例外を、メインスレッド側で補足するには、errorイベントのハンドラを作る必要があります。イベントハンドラは、Workerクラスのonメソッドで登録します:

worker.on('error',err=>{// エラー処理})

先程のmain.jsにエラー処理のコードを追加します:

main.js
const{Worker}=require('worker_threads')constworker=newWorker('./worker.js')// 追加↓worker.on('error',err=>{console.error(err.message)})setTimeout(function(){console.log('The main thread is alive!')},3000)

今度はこのmain.jsを実行してみて、メインスレッドが終了しないかを確認してみましょう。

$node main.js
Error was thrown in worker
The main thread is alive!

上の実行結果を見ると、1行目にWorker側で発生したエラーの内容が出ており、エラーハンドリングが効いているのがわかります。2行目からは、メインスレッドがエラー発生後も動作しつづけたことが確認できます。

まとめ

以上の検証結果をまとめると……

  • Workerで例外が発生したとき、メインスレッドでエラーハンドリングしないと、メインスレッドも死ぬ。
  • メインスレッドでエラーハンドリングにするには、errorイベントのハンドラを登録する。

所感

Worker側の例外がメインスレッドに致命傷を与えるのは意外だったので、エラーハンドリングはサボらずしたほうが絶対いいですね。


【小ネタ】Electron + TypeScript で nodeIntegration: false (preload.ts)

$
0
0

TL;DR

参考にさせていただきました

経過

TypeScript では以下のような preloadスクリプトはエラーになってしまう。

src/preload.ts
import{ipcRenderer}from'electron';process.once('loaded',()=>{window.ipcRenderer=ipcRenderer;});

プロパティ 'ipcRenderer' は型 'Window & typeof globalThis' に存在しません。ts(2339)

で、やや強引だが次のような型定義ファイルで Windowの名前空間を拡張した。

@types/global.d.ts
import{IpcRenderer}from'electron';declareglobal{interfaceWindow{ipcRenderer:IpcRenderer;}}

とりあえずこれで target: 'web'でもメインプロセスとIPC通信は出来るようになった。

src/renderer.ts
window.ipcRenderer.send('async-message','ping!');

Node.js の fspathの(一部)もプリロードしたいがまだやり方がわからない...

結論

TypeScript わからん

express-generator作成:自分用メモ

$
0
0

express-generatrでアプリ作成

express -e "ファイル名"

npm install

作成したファイルに移動し、

npm install

※もしnpm start がエラーになる場合は、

npm install --save npm 

と打つ。

アプリをスタート

npm start

これで動きます。

Node.js Worker Threads: スレッド間でデータを送受信する方法

$
0
0

前回、Node.js: CPU負荷で3秒かかっていた処理を「Worker Threads」で1秒に時短するという投稿をしました。

本稿では、Node.jsで本物のスレッドが扱えるWorker Threadsにて、スレッド間でデータを送受信する方法について解説します。

本稿で分かること

  • メインスレッドからワーカーをデータを送信するのはどうやるのか?
  • ワーカーからメインスレッドにデータを送信するのはどうやるか?
  • ワーカー同士のデータの送受信は?

メインスレッドからワーカーへデータを送信する方法

メインスレッドからワーカーへデータを送信するには、WorkerpostMessageメソッドを用います。次の例は、'Hello!'という文字列データをワーカーに送信するものです:

main.js
const{Worker}=require('worker_threads')constworker=newWorker('./worker.js')worker.postMessage('Hello!')

ワーカーでこのデータを受信するには、worker_threadsモジュールのparentPortを使います。この、parentPortオブジェクトには、onメソッドが生えており、'message'イベントを処理するイベントハンドラーを登録することで、メインスレッドからのデータを受信できるようになります:

worker.js
const{parentPort}=require('worker_threads')parentPort.on('message',message=>{console.log('worker received message: %o',message)})

このサンプルコードのmain.jsを実行してみると、ワーカーがデータを受信できていることがわかります:

$ node main.js
worker received message: 'Hello!'

ワーカーからメインスレッドにデータを送信する方法

逆に、ワーカーからメインスレッドにデータを送信するには、parentPortオブジェクトのpostMessageメソッドを使用します。次のサンプルコードは、ワーカー側のコードで、メインスレッドに文字列の'Hello!'を送信するものです:

worker.js
const{parentPort}=require('worker_threads')parentPort.postMessage('Hello!')

受信側のメインスレッドのコードでは、生成したWorkerオブジェクトのonメソッドにて、'message'イベントを処理するイベントハンドラを登録しておくことで、ワーカーから送信されたデータを受信できます:

main.js
const{Worker}=require('worker_threads')constworker=newWorker('./worker.js')worker.on('message',message=>{console.log('Main thread received message: %o',message)})

ワーカー同士のデータの送受信方法

ワーカーAから、別のワーカーBにデータを送信するには、メインスレッドが送受信を中継してあげる必要があります。

まず、データ発信者のワーカーは、メインスレッドにデータを送信するようにします:

worker1.js
const{parentPort}=require('worker_threads')parentPort.postMessage('message from worker1')

次に、データ受信者のワーカーは、メインスレッドからデータを受信するようにします:

worker2.js
const{parentPort}=require('worker_threads')parentPort.on('message',message=>{console.log('worker2 received message: %o',message)})

最後に、2つのワーカーの送受信を中継するコードをメインスレッドに実装します:

main.js
const{Worker}=require('worker_threads')constworker1=newWorker('./worker1.js')constworker2=newWorker('./worker2.js')worker1.on('message',message=>worker2.postMessage(message))

このmain.jsを実行すると、2つのワーカーでデータの送受信ができているのがわかります:

$ node main.js
worker2 received message: 'message from worker1'

MessageChannelを使ったワーカー間データ送受信

上の例では、メインスレッドでメッセージを中継する方法で、ワーカー間のデータ送受信を実現する方法を紹介しました。

ワーカー間でデータ送受信をする別の方法として、MessageChannelを使う手段があります。

メイン側でMessageChannelを作ると、MessagePortが2つ生成されます。それぞれを、各ワーカーに渡すようにします。

main.js
const{Worker,MessageChannel}=require('worker_threads')const{port1,port2}=newMessageChannel()constworker1=newWorker('./worker1.js')constworker2=newWorker('./worker2.js')worker1.postMessage({worker2:port1},[port1])worker2.postMessage({worker1:port2},[port2])

MessagePortを受け取ったワーカーは、以後MessagePortを通じてワーカー間データ送受信ができるようになります。

worker1.js
constassert=require('assert')const{parentPort,MessagePort}=require('worker_threads')parentPort.once('message',({worker2})=>{assert(worker2instanceofMessagePort)worker2.postMessage('message from worker1')})
worker2.js
constassert=require('assert')const{parentPort,MessagePort}=require('worker_threads')parentPort.once('message',({worker1})=>{assert(worker1instanceofMessagePort)worker1.on('message',message=>{console.log('worker2 received message: %o',message)})})

まとめ

  • メインスレッドからワーカーをデータを送信するのは、メインスレッド側でworker.postMessageを呼び出し、ワーカー側は、parentPort.on('message', ...)でイベントハンドリングする。
  • ワーカーからメインスレッドにデータを送信するのは、ワーカー側でparentPort.postMessageを呼び出し、メインスレッド側は、worker.on('message', ...)でイベントハンドリングする。
  • ワーカー同士のデータの送受信には、メインスレッドによる中継、もしくは、MessageChannelを通じて行う。

【まじで注意】NestJSをAPIサーバとして使ってユーザの認証管理をする時の注意事項

$
0
0

昨日公開したサーバの挙動がどうもおかしかったのです。ガチガチにValidationかけてたので不具合とはならなかったのですが、想定してるよりバリデーションにひっかかる可能性が高い。具体的には、Controllerの上のほうの処理と下のほうの処理で、ユーザが異なるような挙動が確認される。

具体的なコードで紹介すると

@Injectable()exportclassAuthService{publicauthId:number;getuserId(){returnauthId;}// ...認証やらいろいろして、認証できたらauthIdにユーザIDを入れる処理とか}

といったサービスをDIした以下のようなControllerがあったとします。

exportclassStatusController{constructor(privateauth:AuthService,){}@Get()asyncuserStatus():Promise<IResponseGetUserStatus>{console.log(this.auth.userId);// 【1】// ...時間のかかる非同期処理console.log(this.auth.userId);// 【2】}}

みたいな処理をしていると、【1】と【2】でそのリクエスト内では再ログインや上書きをしていないのに、なぜか値が異なる!!

ちょっと各位に相談したりドキュメントみたりしてたのですが、どうもNestJSではデフォルトではDIはHTTP Request単位ではなく、サーバ全体で行われるようです。簡単にいうと、ControllerもServiceもユーザ間で共有します。なので、プロパティでユーザIDをいれようものなら、取り合いがはじまって容赦なく書き換わります。

Issuesはこちらですね。

Http Request based DI #1376
https://github.com/nestjs/nest/issues/1376

いやー、焦った。テストでまったくでてこないし、当然ローカルテストで発見できるようなものではないので。確認したところ、ちょうど1年前にこれを解決するための機能が実装されていました。

Injection scopes
https://docs.nestjs.com/fundamentals/injection-scopes

この機能を使うことによって、DIをHTTP単位で生成するようにすることができます(代わりにコストが高くなることを注意する旨書かれていたりしますので、何でもすればいいわけじゃないです。認証といったクリティカルなものはそうしないとだめなだけです)。

先程の例だと、以下のようにすれば治ります。

@Injectable({scope:Scope.REQUEST}// 追記)exportclassAuthService{publicauthId:number;getuserId(){returnauthId;}}

実環境で動かすまで気づけない的にはかなりハマりポイントだと思いますので、ぜひ設計段階でNodeのDIをどの範囲で行うかは考えるようにすることをおすすめします。ブラウザと違って、DIは他のユーザと共有するリスクがあることにご注意ください。

それではまた。

Ubuntu と Node.js と npm

$
0
0

この記事はメモとして残して置いた限定公開記事を全体公開にしたものです。記事の内容は古い可能性があります。

Ubuntu で Node.js を管理する方法はいくつかある。

  1. 普通に apt する
  2. nvmを利用する
  3. n packageを利用する

代表的なところではこんなところ。それぞれメリット、デメリットがあると思うが、今回は、一番シンプルだと感じた n package を使う方法をおススメします。

導入

まず、nodejs と npm を普通にインストール

sudo apt install -y nodejs npm

n package を npm で global にインストール

sudo npm install n -g

n package を使って node をインストール

sudo n lts

最初に入れた nodejs、npm を削除 & 再ログイン

sudo apt purge -y nodejs npm
exec $SHELL -l

バージョン確認

node -v

バージョン管理

別バージョンのインストール、切り替えもとてもシンプルです。list で確認できたバージョン番号を指定するだけ

別バージョンのインストール&切り替え

n ls
sudo n 11.15.0
node -v
v11.15.0

推奨、最新バージョンの確認

n --lts     # 推奨
n --stable  # 推奨(alias)
n --latest  # 最新

LTS(Long Term Support)バージョン/推奨バージョンのインストール&切り替え

sudo n lts
sudo n stable

最新バージョンのインストール&切り替え

sudo n latest

npm 自体の更新

最新バージョンを確認

npm info npm version

最新バージョンに更新

sudo npm install -g npm
sudo npm update -g npm

npm パッケージのバージョン確認

バージョン一覧

npm info (パッケージ名) versions
npm info expo versions
[
  '0.1.0-pre',           '0.1.0-pre2',          '0.1.1',
~中略
  '35.0.0-rc.0',         '35.0.0'
]

バージョンの絞り込み

npm info expo@33 version
expo@33.0.0 '33.0.0'
expo@33.0.1 '33.0.1'
expo@33.0.2 '33.0.2'
expo@33.0.3 '33.0.3'
expo@33.0.4 '33.0.4'
expo@33.0.5 '33.0.5'
expo@33.0.6 '33.0.6'
expo@33.0.7 '33.0.7'

最新バージョン

npm info expo version
35.0.0

インストール済みのバージョン確認(グローバル)

npm list --depth=0 -g

インストール済みのバージョン確認(ローカル)

npm list --depth=0

バージョン指定のインストール(@以降に指定するだけ)

npm install -g expo@33.0.7

nodenvのinstall -l で表示されるnodeバージョンリストを最新にする。

$
0
0

東京都 新型コロナウイルス感染症対策サイト / Tokyo COVID-19 Task Force website
tokyo-metropolitan-gov/covid19

東京都 新型コロナウイルス感染症対策サイトのソースをforkし、ローカルで環境を構築中にnodeバージョンを入れるように警告がでましたがnodenvのinstall -lを実施したところ該当のバージョンがなく、nodeバージョンリストを最新にするためにやったことを記載しております。

$ yarn install
nodenv: version `10.19.0' is not installed (set by /{**}/covid19/.node-version)
$ cat covid19/.node-version
10.19.0

そこで$ nodenv install -lを実行したところ該当のnodeバージョンがありません。。 1

[node バージョンリスト表示]
$ nodenv install -l
...
10.15.3
10.16.0
10.16.1
10.16.2
10.16.3
10.17.0
...

私のnodenvはanyenv経由で構築しているのではじめはanyenvでなんとかしないといけないかなと思いましたが
どうやらnodenvのリストを自分で更新する必要があるようです。

github.com/nodenv/node-build#upgrading

[公式のドキュメントによると以下を実行するように記載されています。]
$ git -C "$(nodenv root)"/plugins/node-build pull

これを自分の環境に置き換えて対応します。

[① nodenvがどこにあるのか確認]
$ which nodenv
YOUR/INSTALL/PATH/.anyenv/envs/nodenv/bin/nodenv
[② anyenv配下のnodenvへ移動]
$ cd YOUR/INSTALL/PATH/.anyenv/envs/nodenv
[③ git pullを実行 公式にある指示]
$ git pull
remote: Enumerating objects: 839, done.
remote: Counting objects: 100% (839/839), done.
remote: Compressing objects: 100% (168/168), done.
remote: Total 934 (delta 646), reused 792 (delta 622), pack-reused 95
Receiving objects: 100% (934/934), 182.67 KiB | 422.00 KiB/s, done.
Resolving deltas: 100% (658/658), completed with 301 local objects.
From https://github.com/nodenv/node-build
   3df19246..cdb2593c  master               -> origin/master
 * [new branch]        scrape-workflow      -> origin/scrape-workflow
 * [new tag]           ruby-build/v20191024 -> ruby-build/v20191024
 * [new tag]           ruby-build/v20191030 -> ruby-build/v20191030
...
 rename test/helpers/warning_messages => script/lint/lts (84%)
 create mode 100644 share/node-build/10.18.0
 create mode 100644 share/node-build/10.18.1
 create mode 100644 share/node-build/10.19.0 [→ 今回ほしい該当のバージョン]
 create mode 100644 share/node-build/12.14.0

git pullを実行するとログからnodeで追加されたバージョンを確認することができます。
改めて $ nodenv install -lを実行します。

[node バージョンリスト表示]
$ nodenv install -l
...
10.16.1
10.16.2
10.16.3
10.17.0
10.18.0
10.18.1
10.19.0 [→ 今回ほしい該当のバージョン]
11.10.0
...

ほしいバージョンが表示されています。

蛇足

一応ローカルでサイトが立ち上がることを確認します。

$ nodenv install 10.19.0
Downloading node-v10.19.0-darwin-x64.tar.gz...
-> https://nodejs.org/dist/v10.19.0/node-v10.19.0-darwin-x64.tar.gz
Installing node-v10.19.0-darwin-x64...
Installed node-v10.19.0-darwin-x64 to YOUR/INSTALL/PATH/.anyenv/envs/nodenv/versions/10.19.0
$ yarn install
yarn install v1.22.4
[1/5] 🔍  Validating package.json...
[2/5] 🔍  Resolving packages...
[3/5] 🚚  Fetching packages...
[4/5] 🔗  Linking dependencies...
warning "babel-jest > babel-preset-jest > @babel/plugin-syntax-object-rest-spread@7.8.3" has unmet peer dependency "@babel/core@^7.0.0-0".
warning "vue-jest > @babel/plugin-transform-modules-commonjs@7.8.3" has unmet peer dependency "@babel/core@^7.0.0-0".
・・・
✨  Done in 12.70s.

無事に$yarn installに成功しました。
そして、都内の最新感染動向 | 東京都 新型コロナウイルス感染症対策サイトを起動することができました。


  1. それぞれの環境で表示されるバージョンは異なると思います。 

初心者|node.jsでリネーム。フォルダからファイル名�一覧を取得し一括変更する

$
0
0

あるディレクトリの中にあるファイルたちの名前をまとめて変更したいなぁ、というときがあり、node.jsでやってみました。

要件

  • node.js がインストールされている
  • 特定のフォルダ内のファイルを一括リネームしたい
  • 対象のファイルを種類(拡張子)で抽出したい(フィルタリングしたい)

スクリーンショット 2020-03-12 14.50.45.png

  • ディレクトリ「textBox」内にある「●●●.txt」ファイルをリネームします(他の拡張子は無視)
  • リネームの内容:ファイル名の先頭にディレクトリ名を追加します(「ディレクトリ名_ファイル名」にする)
  • 実行するjsファイル名は「changeName.js」にしています(任意のものでOK)

手順

1)jsファイルの作成

changeName.js
constfs=require("fs");constpath=require("path");constdir="./textBox";// ← 変更してねconstaddHead="textBox_";// ← 変更してねconstfileNameList=fs.readdirSync(dir);consttargetFileNames=fileNameList.filter(RegExp.prototype.test,/.*\.txt$/);// ← 変更してね// console.log(targetFileNames);targetFileNames.forEach(fileName=>{// console.log(fileName)constfilePath={};constnewName=addHead+fileName;filePath.before=path.join(dir,fileName);filePath.after=path.join(dir,newName);// console.log(filePath);fs.rename(filePath.before,filePath.after,err=>{if(err)throwerr;console.log(filePath.before+"-->"+filePath.after);});});
  • const dir = "./textBox"には changeName.jsのあるカレントディレクトリからのパスを入力
  • const addHead = "textBox_";にはファイル名の先頭に付与する文字列を入力
  • const targetFileNames = fileNameList.filter(RegExp.prototype.test, /.*\.txt$/);の後半部の .txtで抽出したいファイルの拡張子を指定

2)jsファイルの実行

terminal
$node changeName.js

スクリーンショット 2020-03-12 15.11.29.png

以上です。


解説

changeName.js
constfs=require("fs");// nodeのfsモジュール読み込みconstpath=require("path");// nodeのpathモジュール読み込みconstdir="./textBox";// 対象のディレクトリを指定constaddHead="textBox_";// ファイル名に付与したい文字列を入力constfileNameList=fs.readdirSync(dir);// 対象ディレクトリからファイル名一覧を取得console.log(fileNameList);// [ 'textBox_aaa.txt', 'textBox_bbb.txt', 'textBox_ccc.txt', 'xxx.jpg' ]consttargetFileNames=fileNameList.filter(RegExp.prototype.test,/.*\.txt$/);// ファイル名一覧から、拡張子で抽出console.log(targetFileNames);// [ 'textBox_aaa.txt', 'textBox_bbb.txt', 'textBox_ccc.txt' ]/* 拡張子で抽出したファイル名一覧「targetFileNames」を forEach で回す */targetFileNames.forEach(fileName=>{console.log(fileName);// textBox_aaa.txtconstfilePath={};// ファイル名の前後を入れる箱(連想配列)constnewName=addHead+fileName;// 元のファイル名の先頭に、文字列「addHead」を加えるfilePath.before=path.join(dir,fileName);// 変更前のフルパス(ディレクトリパス+ファイル名)を箱に入れるfilePath.after=path.join(dir,newName);// 変更後のフルパスを箱に入れるconsole.log(filePath);// { before: 'textBox/aaa.txt', after: 'textBox/textBox_aaa.txt' }/* リネーム処理 */fs.rename(filePath.before,filePath.after,err=>{if(err)throwerr;console.log(filePath.before+"-->"+filePath.after);// textBox/aaa.txt-->textBox/textBox_aaa.txt});/* END リネーム処理 */});/* END forEach */

require

本件は、node.jsの組み込みモジュール(標準モジュール)だけで実施可能です。
公式ドキュメント → File System Path

changeName.js(抜粋)
constfs=require("fs");// nodeのfsモジュール読み込みconstpath=require("path");// nodeのpathモジュール読み込み

fs.readdirSync(path)

指定フォルダ(path)内のファイル・サブフォルダ名を取得します(詳細)。
フィルタリングする前なので「xxx.jpg」も含まれています。

changeName.js(抜粋)
constfileNameList=fs.readdirSync(dir);// 対象ディレクトリからファイル名一覧を取得console.log(fileNameList);// [ 'textBox_aaa.txt', 'textBox_bbb.txt', 'textBox_ccc.txt', 'xxx.jpg' ]

Array.filter()

filter() メソッドは、引数として与えられたテスト関数を各配列要素に対して実行し、それに合格したすべての配列要素からなる新しい配列を生成します。(詳細

ようは、「●●●.txt」だけを抽出するフィルタとして使っています。
もしフィルタが不要でしたら .filter(RegExp.prototype.test, /.*\.txt$/)を除去すればOKです。
(もしくは forEach()にはフィルタ前の fileNameListを渡すようにする)

changeName.js(抜粋)
consttargetFileNames=fileNameList.filter(RegExp.prototype.test,/.*\.txt$/);// ファイル名一覧から、拡張子で抽出console.log(targetFileNames);// [ 'textBox_aaa.txt', 'textBox_bbb.txt', 'textBox_ccc.txt' ]

Array.forEach()

forEach() メソッドは与えられた関数を、配列の各要素に対して一度ずつ実行します。(詳細

拡張子で抽出したファイル名一覧「targetFileNames」からファイル名を1つずつ取り出して、リネームの処理を実行していきます。

changeName.js(抜粋)
targetFileNames.forEach(fileName=>{console.log(fileName);// textBox_aaa.txt// 〜省略〜});

連想配列にリネーム前後のファイル名を入れる

changeName.js(抜粋)
constfilePath={};constnewName=addHead+fileName;filePath.before=path.join(dir,fileName);filePath.after=path.join(dir,newName);console.log(filePath);// { before: 'textBox/aaa.txt', after: 'textBox/textBox_textBox_aaa.txt' }

filePathという連想配列の箱を用意して、その中にリネーム前とリネーム後のファイル名を入れています。
リネームするときはフルパス(ディレクトリパス+ファイル名)が必要なので、 path.joinでフルパスにしています(詳細)。

fs.rename(oldPath, newPath, callback)

リネーム処理をする部分です。第一引数に変更前のファイルのフルパス、第二引数には変更後のファイルのフルパスを入れます(詳細)。

changeName.js(抜粋)
fs.rename(filePath.before,filePath.after,err=>{if(err)throwerr;console.log(filePath.before+"-->"+filePath.after);// textBox/aaa.txt-->textBox/textBox_aaa.txt});

あとがき

【注意!】 本件を試す際は、どうかリネーム前にフォルダ(ファイル)のコピーを作成ください。リネーム後に戻すのは大変ですよ!

本件はファイル名の先頭に文字を足すだけでしたが、文字列を検出して置換したり、連番を振ったり...処理を足して色々と応用できると思います。

お役に立てば幸いです。

リスペクトな記事

本件のために参照させていただきました記事です。


ngコマンドの中身を覗いてみる

$
0
0

Disclaimer: この記事を執筆現在私はGoogleで働いています。記事内でGoogleのプロダクトについて言及するため明確に所属を記載していますが、このことは私が記事内に登場するプロダクトについて特別詳しい知識を持っていることを意味しません。1ユーザーが書いた記事としてお読みいただければ幸いです。


唐突に自作コマンドを作りたくなって、参考にngコマンドを開いてみたところ中身が気になったので読んでみました:dog2:読んでいるファイルの全量はこちらです。

第一部

ng
#!/usr/bin/env node'use strict';

Shebangでインタプリタにnodeを指定して、strictモードを使う宣言をしています。

第二部

ng
// Provide a title to the process in `ps`.// Due to an obscure Mac bug, do not start this title with any symbol.try{process.title='ng '+Array.from(process.argv).slice(2).join('');}catch(_){// If an error happened above, use the most basic title.process.title='ng';}

psで使われるプロセスのタイトルを決めているようです。process.argvの2個目以降の要素(=コマンドライン引数)を取り出して結合したものをngと繋いでprocess.titleとします。ちなみに0個目にはnodeのフルパス、1個目にはコマンドのスクリプトファイルのフルパスが格納されています。エラーが発生した際には、一番普通のネーミングということでngになります。

第三部

ng
// This node version check ensures that extremely old versions of node are not used.// These may not support ES2015 features such as const/let/async/await/etc.// These would then crash with a hard to diagnose error message.// tslint:disable-next-line: no-var-keywordvarversion=process.versions.node.split('.').map(part=>Number(part));if(version[0]<10||version[0]===11||(version[0]===10&&version[1]<13)){process.stderr.write('Node.js version '+process.verson+' detected.\n'+'The Angular CLI requires a minimum Node.js version of either v10.13 or v12.0.\n\n'+'Please update your Node.js version or visit https://nodejs.org/ for additional instructions.\n',);process.exit(3);}

process.versions.nodeにはnodeのバージョンが11.13.0のようなフォーマットで入ってくるので.で区切って配列に詰め直します。そして、

  • メジャーバージョンが9以下 or
  • メジャーバージョンが11 or
  • メジャーバージョンが10でマイナーバージョンが12以下

の場合には「AngularCLIはv10.13またはv12以上のNodeを必要とします。(意訳)」のメッセージを表示して終了します。

第四部

ng
require('../lib/init');

手元でwhich ngすると/usr/local/bin/ngで、この相対パス通りに遡ってもinitというファイルがないので一瞬???となりましたが、「Node.js スクリプトをコマンドのように使えるようにする方法」という記事をみて謎がとけました。
ls -lすると

$ ls-l /usr/local/bin/ng
lrwxr-xr-x ...(割愛)... /usr/local/bin/ng -> ../lib/node_modules/@angular/cli/bin/ng

となっていて実際は../lib/node_modules/@angular/cli/bin/ngにいることがわかります。
そこでls /usr/local/lib/node_modules/@angular/cliしてみると、確かにlibフォルダーがいてその中にinit.jsがいます。ということで、最後にこれを読み込んでいました。


おしまいです。最初の最初しか読んでいないのでそんなにAngular独特の内容はないですね。
気が向いたら最後に読み込んでいたinit.tsの中身ものぞいてみようと思います:sunny:

TypeScriptでHot Reloadしつつデバッグもしたい!

$
0
0

🛸 TypeScriptでHot Reloadしつつデバッグもしたい!

したさすぎるけど既存のBoilerplateはよくわからない!そんじゃ1から学んでいこうかというところで一本作ったので、そこまでに調べたことをつららんっと!大まかにはpackage.jsonの中身を調べた話です。

🔎 package.jsonは怖くない!

一見するとパッケージの依存関係やらライセンス、バージョン情報などががつらつら書いてあるのはわかるものの、scriptのところは呪文のようになっていてよくわかりません!

しかし冷静に読んでみると非常に単純です。

npm run cmdcmd部分に対応するコマンドが列挙してあるだけです。つまりショートカット。

以下に一例を示します。

  • startの実体はnpm run serveで、serveの実体はnode dist/server.jsなので、実質的にnpm run startnode dist/server.jsと同義です。ね?怖くないでしょ?
"scripts":{"start":"npm run serve","serve":"node dist/server.js",}

📜 今回の要件を満たすscript

ぶっちゃけmicrosoft / TypeScript-Node-Starterほぼそのままですが、簡単に意味合いを説明します。

  • debug
    • buildしてwatch-debugします。要するに開発用です。
    • devの方が馴染みあるのですがこれはデバッグ用ということでdevelopmentとはまた違うなということで、MS Wayに則りました。
    • proddevは必要に応じて生やせばいいと思います。
  • build
    • build-tsしてlintします。要するにTSをJSにビルドし、Lintをかけます。
  • serve
    • buildしたJSを起動します。
  • lint
    • Lintするやつです。
  • build-ts
    • tscが走りJSが生えます。
  • watch-ts
    • tsc -wが走り、TSファイルの変更を検知してくれます。細かいことはwww.typescriptlang.orgへ。
  • watch-node
    • nodemonがビルドしたJSを監視し、変更があれば再起動してくれます。
  • watch-debug
    • concurrentlyを利用してwatch-tsserve-debugを並列実行します。
    • オプション説明(英語力低いので上のリンク辿ったほうが正確です)
    • -k:実行中のプロセスがどれか一つでも死んだら全部殺す
    • -p:プロセスログを吐くときのプレフィクスフォーマット
    • -n:プロセスログのプレフィクス名(配列)
    • -c:プロセスログのプレフィクスカラー(配列)
    • 一番最後:コマンド(-n, -cの配列に対応)
  • serve-debug
    • nodemonがビルドしたJSをinspectモードで監視し、変更があれば再起動してくれます。
    • こうしておくとnodemonがport:9229でwsを垂れ流してくれるので、VSCodeからinspectorプロトコルを使ってアタッチすることができるようになります。
    • ポート番号の変更はnodemon --inspect=9300みたいにすればいけます
"scripts":{"debug":"npm run build && npm run watch-debug","build":"npm run build-ts && npm run lint","serve":"node dist/server.js","lint":"tsc --noEmit && eslint \"**/*.{js,ts}\" --quiet --fix","build-ts":"tsc","watch-ts":"tsc -w","watch-node":"nodemon dist/server.js","watch-debug":"concurrently -k -p \"[{name}]\" -n \"TypeScript,Node\" -c \"cyan.bold,green.bold\"\"npm run watch-ts\"\"npm run serve-debug\"","serve-debug":"nodemon --inspect dist/server.js"},

🚀 launch.json

デバッグ用のやつです。

{"version":"0.2.0","configurations":[{"type":"node","request":"attach","name":"Attach by Process","protocol":"inspector","port":9229}]}

💎 今回作ったTypeScript汎用Boilerplate

  • TypeScript-Node-Starterとして公開しています。
  • 本家に付いてる贅肉を削りまくり、最低限必要な要素を残したつもりです。
  • 一応サンプル程度にビルドして動かせるものはつけています。

Node.js: ランダムな文字列を生成する1行の関数

$
0
0

Node.jsでランダムな文字列を生成する関数です。

  • lengthで与えた長さの文字列を生成します。
  • 生成される文字列は/[0-9a-z]*/です。
  • 用途としては、初期パスワードの生成や短縮URLのキーの生成など。
const{randomBytes}=require('crypto')functiongenerateRandomString(length){returnrandomBytes(length).reduce((p,n)=>p+n.toString(36).slice(-1),'')}

実行例

constassert=require('assert')constlengthList=[0,1,2,4,8,16]for(letlengthoflengthList){constrandomString=generateRandomString(length)console.log('string(%i) %o',randomString.length,randomString)assert(randomString.length===length)}

実行結果

string(0) ''
string(1) '3'
string(2) 'dy'
string(4) 'jk0g'
string(8) 'lvyzo8do'
string(16) '85hnhykoxwd2ofdd'

これよりも暗号学的に正しい実装があれば教えて下さい。

ElectronでToDoアプリを開発する

$
0
0

背景

Electronを用いたアプリ開発の記事を探していたところ、Mediumで@codedrakenさんのBuild a Todo App with Electronという記事に出会いました。
この記事ではToDoアプリ(メモ帳)の実装方法がチュートリアル形式で掲載されており、とても勉強になったので、ぜひこの記事を紹介(和訳)したいと思います。
自分が実装する上でつまづいた点や、気づいた点などを補足しながら、実装の流れをみていきたいと思います。

環境

機種: Mac Book Air 2017
OS : MacOS Mojave ver 10.14.5
node.js : v12.13.0
npm : 6.13.4
electron : 8.1.1

開発する上での前提条件

PCにNode.jsとnpmがインストール済み

読者に要求する前提知識

javascriptに関する基礎知識
Electronに関する基礎知識
(公式サイトのサンプルコードを実行できれば十分です)

作るもの

@codedrakenさんによるgithubのコードはこちら
1_mv-mAe1RuqgdfzuE-fGH2g.gif

アプリはメインウィンドウ(Todos)とサブウィンドウ(Add Todo)の2窓構成で、Todoリストを作成・追加および削除することができます。また、Todoリストはjson形式のファイルとして保存されるため、アプリを終了後も情報が保持されます。

<操作内容>
メインウィンドウで「Create a new Todo」ボタンを押すと、サブウィンドウが開き、ToDoリストを入力できるようになります。
サブウィンドウのフォームから文字列を入力後、Enterキーを押下あるいは「Add Todo」ボタンを押すと、メインウィンドウのリストに入力内容が付加されます。
なお、メインウィンドウのリストの内容は、クリックすることで削除されます。

内部構成

Electronのプロセスは、MainプロセスとRendererプロセスの2つに大別されます。Rendererプロセスは個々の「画面」を制御するもので、今回の場合は「Todos」と「Add Todo」の2つの画面がそれぞれRendererプロセスにより制御されています。MainプロセスはRendererプロセスの上位に存在し、個々のRendererプロセスのライフサイクルを制御します。
image.png

また、ElectronではRendererプロセスどうしの通信ができないため、Rendererプロセス間の通信は、必ずMainプロセスを介する必要があります(プロセスどうしが干渉したり、デッドロックを引き起こしたりするのを防ぐため)。
今回の例では、メインウィンドウ「Todos」からサブウィンドウ「Add Todos」を呼び出したり、サブウィンドウのフォーム入力によってメインウィンドウのリスト内容を変更したりします。これらのやりとりは、すべてMainプロセスを介することになります。

フォルダ構成

image.png

プロジェクトフォルダの直下(ルートディレクトリ)にメインプロセスのコード(main.js)をおき、rendererプロセスのコード一式はrendererフォルダ下にまとめます。なお、index.htmlとindex.jsでメインウィンドウを実装し、add.htmlとadd.jsでサブウィンドウを実装します。また、Window.jsとDataStore.jsで、それぞれメインプロセスで用いるクラスを記述します。

プロジェクトフォルダの作成

プロジェクトを進めるにあたって、プロジェクトフォルダを作成しましょう。場所は、desktopなど、好きなところで大丈夫です。フォルダの名前はelectron-todoにします。
Terminalを起動し、以下を実行します。

$ mkdir electron-todo
$ cd electron-todo

次に、作成したelectron-todoフォルダをnpmでイニシャライズし、package.jsonファイルを作成します。

$ npm init -y

npm initを実行したら、まずはelectron-todoフォルダ内にpackage.jsonファイルが作成されていることを確認します。

package.json
{"name":"electron-todo","version":"1.0.0","description":"","main":"index.js","scripts":{"test":"echo \"Error: no test specified\"&& exit 1"},"keywords":[],"author":"","license":"ISC"}

作成されたpackage.jsonファイルの"main"と"scripts"の項目について、以下のように修正しましょう。この修正により、terminal上からelectronを実行するとき、main.jsファイルがエントリーポイントとなります。

package.json
{"name":"electron-todo","version":"1.0.0","description":"todo app","main":"main.js","scripts":{"start":"electron .","test":"echo \"Error: no test specified\"&& exit 1"},"keywords":[],"author":"","license":"ISC",}

最後に、あとでcodingする予定の、html,css,jsファイルをそれぞれ作成しておきましょう。

$ touch main.js Window.js DataStore.js
$ mkdir renderer
$ touch renderer/index.html renderer/index.js
$ touch renderer/app.html renderer/app.js
$ touch renderer/style.css

必要なパッケージの追加

npmを用いて、必要なパッケージをインストールしていきます。
今回インストールするパッケージは以下のとおりです。
Electron
electron-reload
electron-store
spectre.css

electron-reloadは、renderer画面のホットリロードを実現します。また、electron-storeは内部データをjson形式で保持するために必要です。そして、spectre.cssは、htmlを簡単にイケてるデザインにしてくれるパッケージです

terminalで以下のコードを実行します。

$ npm i electron
$ npm i electron-reload
$ npm i electron-store
$ npm i spectre.css

これで下準備は完了しました。あとはガリガリコードを書いていきます。

メインウィンドウを表示する

まずはメインウィンドウが表示されるところまで実装します。
メインウィンドウを描画するために必要な、index.html、index.js、style.cssを次のように記述します。

index.html
<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"/><title>Todo</title><linkrel="stylesheet"href="../node_modules/spectre.css/dist/spectre.min.css"/><linkrel="stylesheet"href="style.css"/></head><body><divclass="container"><divclass="columns"><divclass="column col-10"><h1class="text-center">Todos</h1><buttonid="createTodoBtn"type="button"class="btn">
                        Create a New Todo
                    </button><ulid="todoList"></ul></div></div></div><script src="./index.js"></script></body></html>
index.js
// 後で実装する機能// 1. createTodoBtnボタンが押されたら、そのことをMainプロセスに知らせる// 2. 「todoリストを更新せよ」という命令をMainプロセスから受け取り、実行する
style.css
body{font:caption;}.todo-item{background:none;padding:0.5rem;margin:0;cursor:pointer;font-size:1rem;}.todo-item:nth-child(even){background:#f4f4ff;}.todo-item:hover{background:#d8d8d8;}

次に、メインプロセスを制御するためのmain.jsを以下のように記述します。

main.js
const{app,BrowserWindow}=require('electron');letmainWindow=null;app.on('ready',()=>{mainWindow=newBrowserWindow({webPreferences:{nodeIntegration:true,},});mainWindow.loadFile('./renderer/index.html');mainWindow.on('closed',()=>{mainWindow=null;});});app.on('window-all-closed',()=>{app.quit();});

これでメインウィンドウを立ち上げることができます。
それでは、一度この状態でterminalからelectronを起動してみましょう。

$ npm start

現時点では、index.jsには何も記述していない上に、main.jsも必要最低限の記述しかしていないため、メインウィンドウのボタンを押しても何も反応がありません。
スクリーンショット 2020-03-12 16.29.55.png

main.jsの中で、webPreferencesとしてnodeIntegration: trueとしたことに注意してください。これは、rendererプロセス中でnode.jsの機能を使えるようにする設定であり、あとでindex.jsおよびadd.jsをコードするときに必要となります。ところが、Electronの公式ではこの設定を一般的には推奨していません。Local環境で用いるアプリの場合は問題ありませんが、webを介するアプリを作成する場合には、XSSなどのセキュリティリスクを生むためです。
もし今後Electronを用いたアプリを作成される場合は、公式ドキュメントに目を通すようにしてください。

Window.jsファイルを編集する

上の例では、画面を実装するにあたってBrowserWindowクラスをそのまま用いました。以降では複数の画面を設定することになるので、BrowserWindowクラスを継承したWindowクラスを新たに定義します。Window.jsには、先ほど述べたnodeIntegrationの設定や、ファイル読み込みの設定などを記述します。これにより、main.jsに記述するコード行を減らすことができます。

Window.js
const{BrowserWindow}=require('electron');// default window settingsconstdefaultProps={width:500,height:800,show:false,webPreferences:{nodeIntegration:true,},};classWindowextendsBrowserWindow{constructor({file,...windowSettings}){super({...defaultProps,...windowSettings});this.loadFile(file);this.once('ready-to-show',()=>{this.show();});}}module.exports=Window;

メインウィンドウからサブウィンドウを呼び出す

次のステップとして、main.jsとindex.htmlを編集し、メインウィンドウのボタンを押すとサブウィンドウが表示されるようにしましょう。まずは、サブウィンドウを描画するのに必要なadd.htmlとadd.jsファイルについてcodingします。

add.html
<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"/><title>Add Todo</title><linkrel="stylesheet"href="../node_modules/spectre.css/dist/spectre.min.css"/><linkrel="stylesheet"href="style.css"/></head><body><divclass="container"><divclass="columns"><divclass="column col-10"><h1class="text-center">Add Todo</h1><formid="todoForm"><divclass="form-group"><labelclass="form-label"for="add-input">Todo</label><inputclass="form-input"type="text"name="add-input"placeholder="I have to..."/></div><buttonclass="btn">Add Todo</button></form></div></div></div><script src="./add.js"></script></body></html>
add.js
// 後で実装する機能// フォームに内容が入力されたらMainプロセスに通知する

サブウィンドウの設定が完了したので、メインウィンドウの設定を行います。メインウィンドウのボタンが押された場合、このことをMainプロセスに通知する内容をindex.jsに記述しましょう。
RendererプロセスからMainプロセスへの通信を行うときは、Electronに内蔵されているipcRendererというモジュールを利用します。

index.js
const{ipcRenderer}=require('electron');// createTodoボタンが押されたら、そのことをMainプロセスに伝えるdocument.getElementById('createTodoBtn').addEventListener('click',()=>{ipcRenderer.send('add-todo-window');});

ipcRendererによってMainプロセスに通知が送られるようになりました。この通知は'add-todo-window'というチャンネルに送られるようになります。
では次に、Mainプロセスがこの通知を受け取れるように、main.jsを修正しましょう。
MainプロセスからRendererプロセスへの通知を行うときや、Rendererプロセスからの通知をMainプロセスが受け取る際には、ipcMainというモジュールを利用します。

main.js
constpath=require('path');const{app,ipcMain}=require('electron');constWindow=require('./Window');app.on('ready',()=>{letmainWindow=newWindow({file:path.join('renderer','index.html'),});// add todo windowletaddTodoWin;// create add todo windowipcMain.on('add-todo-window',()=>{if(!addTodoWin){addTodoWin=newWindow({file:path.join('renderer','add.html'),width:400,height:400,parent:mainWindow,});addTodoWin.on('closed',()=>{addTodoWin=null;});}});});app.on('window-all-closed',()=>{app.quit();});

それでは、一度この状態でterminalからelectronを起動してみましょう。

$ npm start

「Create a New Todo」ボタンを押すと、あらたに「Add Todo」ウィンドウが表示されるようになりました。

スクリーンショット 2020-03-13 10.33.20.png

現時点ではAdd Todoウィンドウのボタンについては何も設定していないため、ボタンを押しても何も反応はありません。

DataStore.jsファイルを編集する

今回作成するアプリでは、Todoリストをjson形式のファイルとして保持できるようにします。そのために、electron-storeモジュールを利用します。electron-storeはデータを保存するためのクラスとしてStoreクラスを用意していますが、今回はStoreクラスを継承したDataStoreクラスをあらたに定義します。

DataStore.js
constStore=require('electron-store');classDataStoreextendsStore{constructor(settings){super(settings);// initialize with todos or empty arraythis.todos=this.get('todos')||[];}saveTodos(){// save todos to JSON filethis.set('todos',this.todos);// returning 'this' allows method chainingreturnthis;}getTodos(){// set object's todos to todos in JSON filethis.todos=this.get('todos')||[];returnthis;}addTodo(todo){// merge the existing todos with the new todothis.todos=[...this.todos,todo];returnthis.saveTodos();}deleteTodo(todo){// filter out the target todothis.todos=this.todos.filter(t=>t!==todo);returnthis.saveTodos();}}module.exports=DataStore;

公式にある通り、jsonファイルはapp.getPath('userData')に保存されます。app.getPath('userData')の具体的なパスについては、こちらを参照してください

サブウィンドウの操作をメインウィンドウに反映させる

最後のステップになりました。あと実装しなければならない機能は、サブウィンドウのフォームに入力された内容をMainプロセスに通知し、その内容をDataStoreに保存すること、および、DataStoreの内容をメインウィンドウに反映させることです。

まずは、サブウィンドウのフォームに入力した内容を、Mainプロセスに通知する機能を実装しましょう。今回も、ipcRendererモジュールを利用するように、add.jsファイルに記述します。また、この通知は、'add-todo'チャネルとします。

add.js
const{ipcRenderer}=require('electron');document.getElementById('todoForm').addEventListener('submit',evt=>{// prevent default refresh functionality of formsevt.preventDefault();// get input on the formconstinput=evt.target[0];// send input.value to main proecssipcRenderer.send('add-todo',input.value);// reset inputinput.value='';});

次に、main.jsを編集します。以下では、DataStoreインスタンスを作成して、その中にtodoリストを保存できるようにします。また、Rendererプロセスからの通知に対する応答に対しても記述しています。

main.js
constpath=require('path');const{app,ipcMain}=require('electron');constWindow=require('./Window');constDatastore=require('./DataStore');// ホットリロード機能を有効化require('electron-reload')(__dirname);// create a new todo store name "Todos Main"consttodosData=newDatastore({name:'Todos Main'});app.on('ready',()=>{letmainWindow=newWindow({file:path.join('renderer','index.html'),});// add todo windowletaddTodoWin;// initialize with todosmainWindow.once('show',()=>{mainWindow.webContents.send('todos',todosData.todos);});// create add todo windowipcMain.on('add-todo-window',()=>{if(!addTodoWin){addTodoWin=newWindow({file:path.join('renderer','add.html'),width:400,height:400,parent:mainWindow,});addTodoWin.on('closed',()=>{addTodoWin=null;});}});ipcMain.on('add-todo',(event,todo)=>{constupdatedTodos=todosData.addTodo(todo).todos;mainWindow.send('todos',updatedTodos);});ipcMain.on('delete-todo',(event,todo)=>{constupdatedTodos=todosData.deleteTodo(todo).todos;mainWindow.send('todos',updatedTodos);});});app.on('window-all-closed',()=>{app.quit();});

最後に、メインウィンドウにtodoリストの内容が反映されるように、index.jsファイルを編集します。

index.js
const{ipcRenderer}=require('electron');// create add todo window buttondocument.getElementById('createTodoBtn').addEventListener('click',()=>{ipcRenderer.send('add-todo-window');});// delete todo by its text valueconstdeleteTodo=e=>{ipcRenderer.send('delete-todo',e.target.textContent);};// on receive todosipcRenderer.on('todos',(event,todos)=>{// get todoListconsttodoList=document.getElementById('todoList');// create html stringconsttodoItems=todos.reduce((html,todo)=>{html+=`<li class="todo-item">${todo}</li>`;returnhtml;},'');// set list html to the todo itemstodoList.innerHTML=todoItems;// add click handlers to delete the clicked todotodoList.querySelectorAll('.todo-item').forEach(item=>{item.addEventListener('click',deleteTodo);});});

これで一通りのcodingが終了しました。terminalからアプリを起動してみます。

$ npm start

1_mv-mAe1RuqgdfzuE-fGH2g.gif
問題なく動作しました。Electronの基本的な機能を学ぶには、とても良いサンプルアプリだと思います。

参考文献など

元の記事:Build a Todo App with Electron
ElectronのnodeIntegrationについて:【Electron】nodeIntegration: falseのまま、RendererプロセスでElectronのモジュールを使用する
Electronのipc通信について:【Electron連載】第4回 基本編-メイン/レンダラープロセスの話

おすすめの書籍: Electron in Action

Node.js: worker_threadsのスレッド間通信は、child_processのプロセス間通信の2〜11倍速い。

$
0
0

Node.jsでもマルチスレッドプログラミングができるworker_threadsというモジュールがあります。

語弊がありますが似たようなモジュールにchild_processというものもあります。本稿では、worker_threadsとchild_processをワーカー間通信の速さという観点で比較していきます。

本稿でわかること

  • child_processとworker_threadsどっちの通信速度のほうが速いか?

child_processは古参モジュールとして、マルチコアでの分散処理を支えてきた

worker_threadsは比較的新しいモジュールで、スレッドがNode.jsに導入される以前は、マルチコア環境のリソースを活かすには、Node.jsでは複数のプロセスを起動して負荷分散するというアプローチが取られてきました。Node.jsでマルチプロセス型の分散処理をするためによく使われるのが、child_processやclusterといったモジュールです。

worker_threadsもchild_processも似たようなワーカー間通信ができる

worker_threadsもchild_processも、処理をフォークして並列処理できるだけでなく、親ワーカーと子ワーカーの通信ができます。用語が多少異なりますが、worker_threadsの場合は、親スレッドと子スレッドとの間でデータを送受信できます。child_processの場合も、親プロセスと子プロセスの間でデータの送受信が可能です。

worker_threadsで、親スレッドから子スレッドにデータを送信する例:

const{Worker}=require('worker_threads')constworker=newWorker('./worker.js')worker.postMessage('Hello!')

child_processで、親プロセスから子プロセスにデータを送信する例:

const{fork}=require('child_process')constchildProcess=fork('./worker.js')childProcess.send('Hello!')

どちらも似たようなワーカー間通信ができるのがコードからも分かるかと思います。

ちなみに、worker_threadstとchild_processの2つは、そもそもアーキテクチャが異なるので、通信方法もデータのシリアライズ方法は異なります。そのへんの違いについては、簡単な比較表を載せておきます:

Pasted_Image_2020_03_12_12_36.png

どちらのワーカー間通信のほうが速い?

マルチコアを生かした分散処理をしようとすると、今や選択肢として歴史の深いchild_processと、新機能のworker_threadsの2つの選択肢があるわけですが、ワーカー間通信の効率という観点ではどちらが優れているのでしょうか? 気になって検証しました。

検証方法

検証方法としては下記のとおりです。

  • 親ワーカーと子ワーカー間で、N回メッセージの送受信を繰り返す。
  • そのN回の送受信にかかる時間を測定する。
  • 送受信するデータのパターンをいくつか用意し、データの内容によってどういう違いがでるかもついでに調べる。
  • 各データパターンごとに1回ずつ測定。

検証コード

検証するために書いたコードが下記です。

child-process.js
const{repeat,data}=require('./config.js')const{fork}=require('child_process')if(process.send===undefined){// parent processletcount=0fork(__filename).on('message',function(message){if(message==='end'){console.timeEnd('test')this.kill()return}elseif(message==='start'){console.time('test')}this.send(++count<=repeat)})}else{process.send('start')process.on('message',continues=>{process.send(continues?data:'end')})}
worker-threads.js
const{repeat,data}=require('./config.js')const{isMainThread,Worker,parentPort}=require('worker_threads')if(isMainThread){letcount=0newWorker(__filename).on('message',function(message){if(message==='end'){console.timeEnd('test')this.terminate()return}elseif(message==='start'){console.time('test')}this.postMessage(++count<=repeat)})}else{parentPort.postMessage('start')parentPort.on('message',continues=>{parentPort.postMessage(continues?data:'end')})}

パラメータは共通して設定できるように別ファイルにしました:

config.js
module.exports={repeat:1000,data:true,// data: 'a',// data: Array(10000).fill('x')// data: 'a'.repeat(100000),// data: Array(10000).fill({a: 1}),}

検証結果

測定結果としては、下記のグラフのようになりました。

Pasted_Image_2020_03_13_14_30.png

3つ目の1万要素ある配列を送受信するのを除くと、worker_threadsのほうがchild_processより2〜11倍速いということがわかりました。

Viewing all 8920 articles
Browse latest View live