Node.jsまとめ

# 1. はじめに

Node.jsと、それを取り巻く環境。リリースから今日に至るまでの歴史や、現場での使われ方など――調べられるだけ調べましたので、ここに備忘録として残します。

※ 2020/07/13
誤字・脱字を修正。





# 2. Node.jsとは？

Node.jsはC++で書かれた **JavaScriptの実行環境** です。 **ECMAScript** や **CommonJS** のような規格・仕様の名称ではなく、 **jQuery** ・ **React.js** ・ **Vue.js** ・ **AngularJS** といったライブラリやフレームワークの名前でもなく、JavaScriptのソースファイルを受け取って、書いてあるコードの通りに処理を行ってくれるアプリです。

具体的には、以下のように動作します。

```javascript:test.js
console.log(1);
console.log("2");
console.log("3 knock out!");
```

```:コンソール
C:\Users\AGadget> node .\test.js
1
2
3 knock out!

C:\Users\AGadget>
```





## 2-1. 誕生秘話

最初のバージョンである **v0.0.1** がリリースされたのは2009年5月27日のことです。Node.jsの生みの親 **Ryan Dahl** 氏は、当時最も人気があったWebサーバーアプリ **Apache HTTP Server** が抱える **C10K問題** に対する批判とともに当アプリを開発・発表しました。

C10K問題とは、サーバーへの **同時接続数** が1万台を超えるほどになると、ハードウェアのスペックに余裕があっても、サーバーがパンクしやすくなるという現象のことです。重ねて触れておきますが、同時接続数が1万台を超えたら必ず発生するというわけではなく、ここでいう1万台というのはあくまで目安であり、大量接続数を表す比喩表現です。

具体的には、CPUの性能には概ね余裕があるものの、メモリが不足したり、プロセスIDが不足したり、ファイルディスクリプタが不足したりするためにサーバーとして機能しなくなるそうです。これは先のApache HTTP Serverで採用されているリクエストの処理方法が、大量の同時接続を捌くのに不向きであるために生じるものでした（向き不向きの話であり、Apache HTTP Serverは優秀なWebサーバーアプリです）。

この厄介なC10K問題に対応できるWebサーバーを構築する **プラットフォーム** として、Node.jsは誕生しました（Node.js自体はWebサーバーではない）。もちろん、実際に使ったことがある人なら分かるように、Node.jsを使ってWebサーバーを立てることは容易です。ただ、それはJavaScriptで書かれたWebサーバーを立てるためのライブラリをNode.js側から呼び出しているのであって、やはりNode.jsはWebサーバーではありません。





## 2-2. 需要

Node.jsの需要――現場での使われ方ですが、後述する **npm (Node Package Manager)** を利用して、Web開発に役立つライブラリやフレームワークの導入・管理に用いられるのが、まずあるかと思います。実際私もお世話になっています。

次に、Webサーバーアプリ（Apache HTTP Serverや **Nginx** など）から呼び出されるAPIサーバーのポジションにNode.jsが採用されることがあるようです。また、そこから **Go** などで書かれたプログラムに処理を振り分けたりするのに使われるとか（Webサーバー ⇔ Node.js ⇔ Goみたいな）。

その反面、WebサーバーをNode.jsで立てるというのは一般的ではないようです。現実問題として、Webサーバーに要求される性能・機能等に対して、Node.jsでは色々と不足しているため、そこはちゃんとしたWebサーバーアプリのほうが使われるそうです。





# 3. npmとは？

npm (Node Package Manager) はJavaScriptで書かれた、Node.js標準の **パッケージマネージャー** です。 ~~純利益率の略称ではありません。~~ 開発者は **Isaac Z. Schlueter** 氏。リリースされたのは2010年1月12日。Node.jsがリリースされてから、およそ8ヶ月後のことでした。

パッケージマネージャーとは呼んで字の如く、 **パッケージ** を管理するためのアプリのことです。ざっくりと表現するならば **パッケージと呼称されるプログラム群を超簡単にインストールしたりするためのアプリ** ということになろうかと思います。





## 3-1. パッケージとは？

パッケージ（ **モジュール** や **プロジェクト** などと呼ばれることも）は、ツール・ライブラリ・フレームワークなどの **再利用可能なコード** をNode.jsが制定した規格に準じるかたちでまとめたファイル群およびディレクトリのことです。

こう書くと何やら難解な代物に思えてきますが、Node.jsが定めたルールに基づいて、ディレクトリにソースコードを突っ込めば、それだけでモジュールになります。ルールというのは、例えば、パッケージのメタ情報をまとめた **package.json** というファイルを、パッケージとなるディレクトリのなかに配置する必要がある、といったものです。





## 3-2. package.json

package.jsonはnpmに対応したパッケージを構築するのに必要となる設定ファイルの名前です。

npmはこのファイルが配置されているディレクトリをパッケージとして認識します。

ところで、このpackage.jsonというファイル名ですが、これを別の名前に変えることはできません。例えば **manifest.json** とか **settings.json** といったようにです。また **package.ini** や **package.csv** など、ファイル形式を変更してもいけませんので注意してください。





# 4. Node.jsの操作

Windows 10 64bit版でのNode.jsの操作方法を説明します。

Node.jsを使用するには **node.exe** を呼び出します。Node.jsのインストール時に、インストールパスを初期設定から変更していないならば **C:\Program Files\nodejs\node.exe** にあるはずです。

コマンドの文法は以下の通りです。

```
C:\Users\AGadget> node [オプション] [スクリプトパス] [スクリプトに渡す引数]
```

具体的には、このような感じで使います。以下例ではgift-kibidango.jsというスクリプトに、3つの文字列を引数として渡して実行させています。

```
C:\Users\AGadget> node .\gift-kibidango.js "Falcon" "Tiger" "Locust"
```





## 4-1. ヘルプを表示する

node.exeのコマンドの一覧や、その使い方を確認するには **-hオプション** か **--helpオプション** を指定します。

```
C:\Users\AGadget> node -h

C:\Users\AGadget> node --help
```





## 4-2. Node.jsのバージョンを確認する

インストールされているNode.jsのバージョンを確認するには **-vオプション** か **--versionオプション** を指定します。

```
C:\Users\AGadget> node -v

C:\Users\AGadget> node --version
```





# 5. npmの操作

Windows 10 64bit版でのnpmの操作方法を説明します。npmの文法は概ね以下の通りです。

```
C:\Users\AGadget> npm [コマンド] [コマンドを制御する引数]
```

ところで、npmのコマンドには **フリーダム過ぎる** きらいがあることを、頭の片隅にでも置いておいてください。例えば、以下コマンドは全て同じ結果を表してしまいます。

```
C:\Users\AGadget> npm version -v

C:\Users\AGadget> npm version --v

C:\Users\AGadget> npm version -version

C:\Users\AGadget> npm version --version

C:\Users\AGadget> npm -v

C:\Users\AGadget> npm --v

C:\Users\AGadget> npm -version

C:\Users\AGadget> npm --version

C:\Users\AGadget> npm -v version --version
```

このようにフリーダムすぎると、どういった書き方が正しいのか分からなくなってきます。そのため当記事では以下ページに掲載されている書き方を参考に、ある程度統一感を持たせた書き方を掲載します。

[CLI documentation | npm Documentation](https://docs.npmjs.com/cli-documentation/)





## 5-1. npmのバージョンを確認する

インストールされているnpmのバージョンを知りたいときは **versionコマンド** を使用してください。

```
C:\Users\AGadget> npm version
```

versionコマンド単体で実行すると、npmのバージョンに加え、その他よくわからない要素のバージョンをまとめて取得することができます。

npmのバージョンだけ知りたい場合は **-vオプション** か **--versionオプション** を付けてください。

```
C:\Users\AGadget> npm version -v

C:\Users\AGadget> npm version --version
```





## 5-2. パッケージを初期化する

パッケージを初期化する場合は **initコマンド** を使用します。当コマンドを実行すると、対話的に **package.json** の設定を行うことができます。

```
C:\Users\AGadget> npm init
```

初期設定が面倒な場合は **-yオプション** か **--yesオプション** を付けることで標準設定のpackage.jsonが生成されます。

```
C:\Users\AGadget> npm init -y

C:\Users\AGadget> npm init --yes
```





## 5-3. インストールされているパッケージを確認

インストールされているパッケージを確認したいときは **lsコマンド** を使用してください。

```
C:\Users\AGadget> npm ls
```

インストールされたパッケージ階層のうち、特定の階層だけ見たい場合は **--depthオプション** を使用します。階層は最も浅い階層――トップレベルのパッケージを0としてカウントしていきます。

```
C:\Users\AGadget> npm ls --depth=0
```

また、グローバルインストールされたパッケージを見たいときは **-gオプション** か **--globalオプション** を付けてください。

```
C:\Users\AGadget> npm ls -g

C:\Users\AGadget> npm ls --global

C:\Users\AGadget> npm ls --depth=0 -g

C:\Users\AGadget> npm ls --depth=0 --global
```





## 5-4. パッケージをインストール

パッケージをインストールする場合は **installコマンド** を使用してください。installコマンドに続いて、インストールしたいパッケージ名を記述してください。

```
C:\Users\AGadget> npm install [インストールしたいパッケージ名]
```

具体例として、TypeScriptを導入する場合の書き方を掲載します。

```
C:\Users\AGadget> npm install typescript
```

また、インストール時にはインストールするパッケージのバージョンを指定することができます。バージョンを指定しなかった場合は最新バージョンがインストールされます。

```
C:\Users\AGadget> npm install typescript@1.2.3
```

グローバルインストールしたい場合は **-gオプション** か **--globalオプション** を付けてください。

```
C:\Users\AGadget> npm install typescript -g

C:\Users\AGadget> npm install typescript --global

C:\Users\AGadget> npm install typescript@1.2.3 -g

C:\Users\AGadget> npm install typescript@1.2.3 --global
```





## 5-5. 開発環境を再現する

パッケージを初期化する **initコマンド** を実行することで生成される **package.json** と、パッケージをインストールする **installコマンド** を組み合わせることで、他者の開発環境を簡単に再現することができます。具体例とともに説明していきます。

まず、既存のパッケージに含まれるpackage.jsonをコピーして、適当なディレクトリに配置します。

```
C:\Users\AGadget> COPY C:\Users\AGadget\old-pack\package.json C:\Users\AGadget\new-pack\package.json
```

コピー先となるディレクトリに移動し、installコマンドを実行します。

```
C:\Users\AGadget> CD .\new-pack

C:\Users\AGadget\new-pack> npm install
```

npmはカレントディレクトリにpackage.jsonが存在するか確認します。package.jsonが存在するならば、ファイル内を参照し、どのようなパッケージが導入されているかを確認します。そして最後に、記載されていたパッケージをインストールしてくれます。





### 5-5a. ちょっと注意

ところで、上記の方法だとコピー元と全く同じ開発環境を再現できない可能性があります。

以下.jsonファイルは、ある時点での自作パッケージの情報をまとめたpackage.jsonの内容です。TypeScriptだけが導入された状態になっています。

```json:package-json
{
  "name": "tes-tes-testoooooo",
  "version": "1.0.0",
  "lockfileVersion": 1,
  "requires": true,
  "dependencies": {
    "typescript": {
      "version": "^3.9.5",
      "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.6.tgz",
      "integrity": "sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw=="
    }
  }
}
```

ファイル中央より、やや下方にTypeScriptが導入されている旨と、そのバージョンが **^3.9.5** であると記されています。「What' the hat!?」という感じですが、このハット記号（キャレット記号とも）がバージョン情報の先頭に付いているかどうかでnpmの挙動が変わってきます。

例えば **3.9.5** であるならばnpmは「TypeScriptのv3.9.5を導入すれば良いのか」と判断します。しかし **^3.9.5** の場合は「TypeScriptの3.x系の最新バージョンを導入すれば良いのか」と解釈してしまいます。そのため実際にインストールされるのは3.9.5ではなく、バグフィックスが行われた **3.9.6** かもしれませんし、マイナーチェンジ版の **3.10.0** かもしれないのです。

まぁ、メジャーバージョンの変更ではないので **たぶん** 致命的な問題にはならないとは思いますが、チームで開発する場合などはバージョンを揃えておいたほうが安心できますね。

この問題への対応方法としては2つ考えられます。1つは、受け取ったpackage.jsonのバージョン情報にあるハット記号を削除してからinstallコマンドを実行すること。もう1つは、明示的に特定のバージョンをインストールすることです。

```
C:\Users\AGadget> REM この方法だとTypeScriptの最新バージョンがインストールされる

C:\Users\AGadget> npm install typescript

C:\Users\AGadget> REM 特定のバージョン――例えば3.9.5をインストールしたいならば以下のように指定すること

C:\Users\AGadget> npm install typescript@3.9.5
```





### 5-5b. 救世主（？）package-lock.json登場！

2015年10月29日にリリースされた **npm v5.0.0** からは **package-lock.json** という仕組みが新たに追加されました。

package-lock.jsonはpackage.jsonを **補完する** ファイルです。npm v5.0.0以降では、installコマンドを実行するとpackage.jsonとpackage-lock.jsonの2つのファイルが生成されるようになります。

package-lock.jsonの役割は **package.jsonで指定されたパッケージのうち、実際にインストールされたパッケージ（と、そのバージョン）は何か** ということを明確にすることです。実際の挙動を見てみましょう。

適当なパッケージを用意し、そこにTypeScriptを導入します。バージョンは指定せず、現時点での最新バージョンを取ってくるように指示します。ここでは仮に **3.9.5** が最新バージョンであったとします。

```
C:\Users\AGadget> CD .\test-pack

C:\Users\AGadget\test-pack> npm init --yes
Wrote to C:\Users\AGadget\test-pack\package.json:

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

C:\Users\AGadget\test-pack> npm init typescript
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN test-pack@1.0.0 No description
npm WARN test-pack@1.0.0 No repository field.

+ typescript@3.9.5
added 1 package from 1 contributor and audited 1 package in 1.185s
found 0 vulnerabilities

C:\Users\AGadget\test-pack>
```

上記コマンドを実行すると以下のように.jsonファイルが生成されます。

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

```json:package-lock.json
{
  "name": "test-pack",
  "version": "1.0.0",
  "lockfileVersion": 1,
  "requires": true,
  "dependencies": {
    "typescript": {
      "version": "3.9.5",
      "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.5.tgz",
      "integrity": "sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw=="
    }
  }
}
```

package.jsonに記載されたTypeScriptのバージョンは **^3.9.5** となっていますが、package-lock.jsonでは **3.9.5** となっていることが分かります。これは「当パッケージではTypeScript v3.9以上のバージョンを使用するが、現在導入されているバージョンはv3.9.5である」ということを意味しています。

さて、この状況の裏でTypeScriptのバージョンが **3.9.6** に上がっているとします。このとき両ファイルをもとに環境を再現するとどのようなことになるでしょうか。

```
C:\Users\AGadget\test-pack> COPY .\package.json ..\test-pack-new\package.json

C:\Users\AGadget\test-pack> COPY .\package-lock.json ..\test-pack-new\package-lock.json

C:\Users\AGadget\test-pack> CD ..\test-pack-new

C:\Users\AGadget\test-pack-new> npm install
```

正解は **TypeScript v3.9.5が導入される** です。

npmはinstallコマンドが実行されると、カレントディレクトリ内にpackage.jsonがあるか確認します。package.jsonが存在する場合、どのようなパッケージが導入されているかを見て回ります。このとき導入されているパッケージのバージョンが固定されている場合は、そのバージョンを導入します。それに対して、ハット記号が付くなどの条件が付けられている場合は、条件を満たすバージョンのなかでの最新バージョンをインストールしようとします。

……が、このときpackage-lock.jsonがあり、かつ、package-lock.json内に当該パッケージのバージョン情報が記載されていた場合は、そのバージョンをインストールするように働くのです。これにより安全に環境を再現することができるようになりました。

全体的に挙動がややこしいため実際に使ってみて覚えてください。





## 5-6. パッケージをアンインストール

既にインストール済みのパッケージをアンインストールする場合は **uninstallコマンド** を使用してください。uninstallコマンドに続いて、アンインストールするパッケージ名を指定してください。

```
C:\Users\AGadget> npm uninstall [インストールしたいパッケージ名]

C:\Users\AGadget> npm uninstall package-name
```

グローバルインストールされたパッケージをアンインストールするときは **-gオプション** か **--globalオプション** を付けてください。

```
C:\Users\AGadget> npm uninstall -g package-name

C:\Users\AGadget> npm uninstall --global package-name
```





## 5-7. パッケージをアップデート

導入済みのパッケージをアップデートするには **updateコマンド** を使用します。

```
C:\Users\AGadget> npm update [アップデートしたいパッケージ名]

C:\Users\AGadget> npm update package-name
```

グローバルインストールされたパッケージをアップデートするときは **-gオプション** か **--globalオプション** を付けてください。

```
C:\Users\AGadget> npm update -g package-name

C:\Users\AGadget> npm update --global package-name
```





## 5-8. 最新ではないパッケージを一覧表示

導入済みのパッケージのなかから、最新バージョンにアップデートされていないパッケージを一覧表示します。

```
C:\Users\AGadget> npm outdated
```

表示対象をグローバルインストールされたパッケージとする場合は **-gオプション** か **--globalオプション** を付けてください。

```
C:\Users\AGadget> npm outdated -g

C:\Users\AGadget> npm outdated --global
```