Node.jsを学ぶ-The definitive Node.js handbook-4

Tatsuya Asami
14 min readDec 9, 2018

--

【所要時間】

1時間56分(2018年 12月8,9日)

【概要】

【要約・学んだこと】

Uninstalling npm packages locally or globally

ローカルに以前インストールしたパッケージをアンインストールするには、

npm uninstall <package-name>

-S フラグか--saveを使うことで、package.jsonファイルのリファレンスからも削除できる。

パッケージがdevdependenciesの場合は、 -D / --save-dev を使う。

npm uninstall -S <package-name>
npm uninstall -D <package-name>

グローバルにインストールされている場合には-g / --global フラグをつける。

npm uninstall -g <package-name>

例:

npm uninstall -g webpack

このグローバルに対するコマンドはシステムのどこからでも実行できる。

npm global or local packages

パッケージをグローバルにインストールべきなのはいつか。

ローカルとグローバルの違いは下記のようになる。

  • local packages
    npm install<package-name>を実行したディレクトリにインストールされる。そしてそのディレクトリの下のnode_modulesに置かれる。
  • global packages
    npm install -g <package-name>を実行した場所に関わらず、システム内の一箇所(設定次第で変わる)に置かれる。

コードにも同様の要求がされる:

require('package-name')

一般的に全てのパッケージはローカルにインストールされるべきだ。

コンピュータ内にはたくさんのアプリが存在し、それぞれのパッケージが必要とするバージョンが異なるからだ。

グローバルパッケージが更新されることで、全てのプロジェクトがそれを使うことになる。これではdependenciesの整合性がとれずに非常に維持が難しこととなる。

全てローカルに入れることで、リソースは勿体無いかもしれないが、それ以外のデメリットが少ない。

シェルから実行できるパッケージはグローバルにインストールされるべきだ。プロジェクトを超えて同じコマンドを実行することができる。

実行可能なコマンドをローカルにインストールすることもでき、npxを使うことでそれらを実行できる。しかし、パッケージによってはグローバルにインストールした方がいい。

グローバルパッケージとして有名な例は:

  • npm
  • create-react-app
  • vue-cli
  • grunt-cli
  • mocha
  • react-native-cli
  • gatsby-cli
  • forever
  • nodemon

などがある。これらは

npm list -g --depth 0

を実行すると確かめることができる。

3つグローバルにインストールしていた

npm dependencies and devDependencies

dependenciesとdevDependenciesはどのように使い分けるか。

npm install <package-name> でインストールするとdependencyになる。

packageは自動的にpackage.jsonのdependencies listにリストされる。(npm5時点では手動で — saveを入力する必要がある。)

-D フラグか--save-dev を加えると、devDependencies リストに追加される。

Development dependencies
開発時に必要なパッケージを意図している。プロダクションには必要ない。testパッケージ、webpackやBabelなどがある。

本番環境に入り、npm installを実行し、そのフォルダにpackage.jsonファイルが含まれていると、npmはこれを開発用のデプロイだと想定してインストールする。

devDependenciesのインストールを避けるには、--production フラグをセットする必要がある。

The npx Node Package Runner

npx はNode.jsを実行するための使い勝手のいい特徴を提供してくれるコマンド。

ここではnpm ver 5.2から始まったnpxを紹介する。

npxはNode.jsでコードのビルトを実行し、npm登録を通じて公開する。

Easily run local commands

Node.js開発者は、パスに入るすぐに実行できるようにするため、ほとんどの実行可能なコマンドをグローバルパッケージとして公開してきた。

同じコマンドの違うバージョンをインストールできなかったので、これは苦痛だった。

npx commandname は、正確なパスを知らなくてもプロジェクトのフォルダのnode_modules内のコマンドを自動的に正しく参照し、グローバルにパッケージがインストールされていることや、ユーザーパスを必要としない。

Installation-less command execution

npmには最初のインストールなしで実行を許可するという別の特徴がある。

これらは下記の点で有用だ。

  1. 何もインストールしなくていい。
  2. @versionシンタックスを使うことでじコマンドの異なるバージョンを実行できる。

npxの典型的なデモンストレーションはcowsayコマンドを通じて行われる。coswayはコマンドで書いたことを出力する。

cowsay "Hello" は下記のようになる。

_______
< Hello >
-------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||

npmから過去にグローバルにcowsayコマンドがインストールされていれば実行され、そうでなければエラーとなる。

npxはローカルにインストールしなくてもnpmコマンドを実行することを許可する。

npx cowsay "Hello"

これは使い道のないコマンドだが、他には

  • 新しいアプリを作成するためにvue CLIツールを実行する: npx vue create my-vue-app
  • 新しいreactアプリを実行するために実行する。 create-react-app: npx create-react-app my-react-app

などがある。一度ダウンロードされると、ダウンロードされたコードは消える。

Run some code using a different Node.js version

@でバージョンを指定し、node npm パッケージとコンバインする:

npx node@6 -v #v6.14.3
npx node@8 -v #v8.11.3

これはnvmや他のNodeバージョン管理ツールを避けるのに役立つ。

Run arbitrary code snippets directly from a URL

npx はnpmレジストリにパッケージを発行するのを制限しない。

GitHubの要点にあるコードを実行できる:

npx https://gist.github.com/zkat/4bc19503fe9e9309e2bfaa2c58074d32

当然大きな力を持ち、大きな責任も伴うので、コントロールできないコードを実行するには注意が必要。

The Event Loop

Event LoopはJSを理解する上で最も大事な側面の一つ。ここではJSがシングルスレッドでどのように動作するかを説明し、非同期処理がどのように機能するかを説明する。

JSはシングルスレッドを実行し、1度に1つのことしか起こらない。

この制限は実際にありがたい。並行性の問題を気にせずプログラムできるからだ。

コードを書くことと、同期ネットワークコールや、無限ループといった、スレッドをブロックする点に気をつければ良い。

一般的にほとんどのブラウザでは、それぞれのタブでイベントループができ、それぞれのプロセスは独立している。また、無限ループやブラウザ全体をブロックする思い処理は避ける。

この環境は複数のイベントループを管理し、APIコールなどを扱う。 Web Workersは自身のイベントループ内でも実行する。

主に気にかけるのは、コードがシングルイベントループ上で実行されるということだ。そして、それをブロックするのを避けるようにすることだ。

Blocking the event loop

イベントループにコントロールを戻すのに時間がかかりすぎるJSコードは、ページ内のJSコードの実行をブロックする。- UIスレッドさえぶろっくする。- そしてユーザーがクリック、スクロールすることすらできなくなる。

JSのほとんどの原始的なI/Oはノンブロッキングだ。ネットワークの要求、Node.jsファイルシステム操作などだ。ブロッキングとは例外的なもので、JSはコールバックに基づき、最近はプロミス、async/await(非同期/待機)に基づいている。

The call stack

コールスタックはLIFOキューのことだ。(Last In, First Out)

イベントループは実行する必要がある機能があるかどうかを確かめるために絶えずコールバックをチェックしている。

その間に、コールスタックに見つかった関数コールを追加し、それぞれを順番んい実行する。

エラースタックトレースというのがある。ブラウザはコールスタックの中から関数名を探し、どの関数が現在のコールを作成したのかを知らせる。

A simple event loop explanation

シンプルな例として

const bar = () => console.log('bar')const baz = () => console.log('baz')const foo = () => {
console.log('foo')
bar()
baz()
}
foo()

このスニペットは下記のように出力する。

foo
bar
baz

foo()→console.log(‘foo’)→bar()→console.log(‘bar’)→baz()→console.log(‘baz’)と実行を続け、コールスタックがからになるまで反復が繰り返される。

Queuing function execution

先ほどの例は特別なことはしていない。JSが実行することを探し、順番に実行しているだけだ。

次の例は関数をコールするのにsetTimeout(() => {}), 0)を使っているが、コードないの他の機能全てが実行された後にこれは実行される。

const bar = () => console.log('bar')const baz = () => console.log('baz')const foo = () => {
console.log('foo')
setTimeout(bar, 0)
baz()
}
foo()

このコードは下記のように出力することに驚くだろう。

foo
baz
bar

このコードを実行するとまずはfoo()がコールされる。foo()内部ではconsole.log(‘foo’)の後に、setTimeoutをコールし、引数としてbarにパスする。そしてそれを可能な限りすぐに実行し、タイマーとして0をパスする。その後baz()をコールする。

foo()→console.log(‘foo’)→bar()→console.log(‘bar’)→baz()→console.log(‘baz’)

なぜこうなるのか。

The Message Queue

setTimeout()がコールされる時、ブラウザかNode.jsはタイマーをスタートする。タイマーが期限切れとなると、timeoutとして0をセットし、すぐにコールバック関数がメッセージキューに入れられる。

メッセージキューは、コードが反応する前に、クリックイベント、キーボードイベント、レスポンスの取得などのユーザー開始イベントがキューに入れられる。もしくはonLoadのようなDOMイベントとなる。

ループはコールスタックに優先権を与える。最初にコールスタックで見つかったものは全て処理され、何も入っていなければイベントキュー内のものを取得する。

setTimeout, fetch, その他の自身で動作することのような機能を待つ必要はない。なぜならそれらにブラウザに提供されるからだ。また、それらは自身のスレッドに存在する。例えば、setTimeoutを2秒で設定しても、2秒待つ必要はない。待ち時間は他の場所で発生する。

ES6 Job Queue

ECMAScript 2015ではJob Queueの概念が導入された。これはPromisesに使われる。それはコールスタックの最後に置かれるのではなく、async(非同期)関数の結果として可能な限りすぐに実行する方法だ。

現在の関数が終了する前に解決するPromisesは、現在の関数の直後に実行される。

メッセージキューはキューにいる他の全ての人の後に並び、ジョブキューはファストパスで、事前の関数が終わるとすぐに実行されるようなものである。

const bar = () => console.log('bar')const baz = () => console.log('baz')const foo = () => {
console.log('foo')
setTimeout(bar, 0)
new Promise((resolve, reject) =>
resolve('should be right after baz, before bar')
).then(resolve => console.log(resolve))
baz()
}
foo()

下記のように出力される。

foo
baz
should be right after foo, before bar
bar

Promises(async/awaitといったpromisesで構築されているもの)とsetTimeout()か他のAPIプラットフォームのような古い非同期関数には大きな違いがある。

Understanding process.nextTick()

Node.jsのイベントループを理解する上で大事なことの一つに、process.nextTic()がある。特別な方法でイベントループをするのに相互作用する。

イベントループがフルトリップすることをtickと呼ぶ。

関数をprocess.nextTick()に渡す時、次のイベントループtickがスタートする前に、現在の操作の最後にこの関数を呼び起こすためにエンジンを導入する。

process.nextTick(() => {
//do something
})

イベントループは現在の関数コードでは忙しく処理する。

この操作が終わるとき、JSエンジンは操作中にnextTickをコールのために渡された関数全てを実行する。

これは非同期関数を処理するためにJSエンジンを指示する方法(現在の関数の後で)だが、すぐにキューを入れるわけではない。

setTimeout(() => {}, 0)をコールすることは、nextTick()を使う時よりもはるか後に次のtickの関数を実行する。

nextTick()を使うと、コードがすでに実行されている次のイベントループの反復で、確実に実行される。

【わからなかったこと】

特になし

【感想】

今までも出てきたイベントループやコールバックがついに出てきた。残り半分。

--

--

Tatsuya Asami
Tatsuya Asami

Written by Tatsuya Asami

Front end engineer. React, TypeScript, Three.js

No responses yet