初学者向けpackage.jsonハンズオン

これは [Node.js Advent Calendar 2019](https://qiita.com/advent-calendar/2019/nodejs) の8日目の記事です。
昨日は @yuta-ron さんによる [スクレイピング & サーバレスAPIでNode.jsの雰囲気を体験してみる](https://qiita.com/yuta-ron/items/e752947b9e395b0c2ee8) でした。


# 初学者向けpackage.jsonハンズオン

Node.js初学者向けにpackage.jsonハンズオンを実施しましたので、その時に作ったハンズオン資料を公開します。

npmコマンドやpackage.jsonに慣れることが目的のハンズオンの資料ですので、Node.jsとは何か？といった説明はしません。ご了承ください。 

文章の手順通りに進めていけば、npmコマンドやpackage.json編集が体験できます！ 



# 事前準備
## Node.jsのインストール

事前にNode.jsのインストールをお願いします。（すでにNode.jsがインストール済の方は飛ばしてください。）

### Windowsの場合

Windowsの方は、こちらの記事をご参照ください。Chocolatey > Nodist > Node.jsの順番でインストールします。

[Windowsで、Chocolateyとnodistで、バージョン切り替え可能なNode.jsの環境を構築する](https://qiita.com/masatomix/items/bcb0e7c64013e61af4d4)

### Macの場合

Macの方は、こちらの記事をご参照ください。Homebrew > nodebrew > Node.jsの順番でインストールします。

[macOS に Homebrew で nodebrew をインストールして Node.js を使う](http://sig9.hatenablog.com/entry/2018/10/18/000000)



# package.jsonハンズオン

それではハンズオンを始めます。



## 作業ディレクトリを作ろう

お好きな場所に作業ディレクトリを作りましょう。

### Windowsの場合

今回のハンズオンでは以下のディレクトリを `作業ディレクトリ` と呼ぶことにします。

`D:\JavaScript\package-json-handson`



### Macの場合

今回のハンズオンでは以下のディレクトリを `作業ディレクトリ` と呼ぶことにします。

`~/JavaScript/package-json-handson`



## WindowsならGit Bashを使おう

Web界隈ではLinuxサーバーが主流ですので、DOSコマンドよりもLinuxコマンドが使える環境の方が、何かと都合がよいと思います。

`Git for Windows` をインストールしていれば、 `Git Bash` というターミナルがインストールされており、そこでLinuxコマンドが使えます。

今回のハンズオンでは、Git Bashを使う前提で進めます。

カレントディレクトリを作業ディレクトリにしてください。



## 作業ディレクトリに移動

カレントディレクトリを作業ディレクトリにしてください。

以下のコマンドを実行してみましょう。

```bash
# Windows
cd /d/JavaScript/package-json-handson

# Mac
cd ~/JavaScript/package-json-handson
```



## npmコマンドでpackage.jsonを生成してみる

作業ディレクトリにnpmパッケージを作っていきます。

以下のコマンドを実行して、npmパッケージを初期化してみましょう。

```bash
# 作業ディレクトリをnpmで初期化
npm init
```

以下のようなメッセージが表示されます。

```
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
```

```bash
package name: (package-json-handson)
```

最後の行で上記のように表示されると思います。

ここでは、これから作るnpmパッケージの名前をどうするのか質問されています。デフォルトでディレクトリ名が表示されていますので、そのまま何も入力せずEnterしてください。

そうすると、以下のように次の質問が表示されます。

```bash
version: (1.0.0)
```

ここでは、npmパッケージのバージョンをどうするのか聞かれています。

デフォルトは `1.0.0` となっていますが、ここでは、 `0.1.0` を入力してください。（理由は後で説明します。）

そうすると、以下のように次の質問が表示されます。

```bash
description: 
```

ここでは、npmパッケージの説明文の入力を求められています。

とりあえず `handson` と入力してください。（後から変更できますので、真剣に悩まなくても大丈夫です。）

そうすると、以下のように次々と質問されます。

```bash
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
```

とりあえず、何も入力せず、すべてEnterしてください。

そうすると、最後に以下の質問が表示されます。

```
Is this OK? (yes)
```

ここでも、何も入力せずEnterしてください。（`yes` の意味になります。）

これで、npmパッケージの初期化が完了しました！

`package.json` が作業ディレクトリに生成されていますので、内容を確認してみましょう！

```bash
# package.json の中身を表示する
cat package.json
```

以下のようにJSON形式のデータが表示されれば成功です！

```json
{
  "name": "package-json-handson",
  "version": "0.1.0",
  "description": "handson",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}
```

自分が入力してきた値が格納されていることがわかると思います。



## npm-scriptをつくってみる

テキストエディター（こだわりがなければVS Codeがオススメです）で `package.json` を開いてください。

`scripts` という項目があります。この部分には、独自のスクリプトを登録できます。

```json
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "show-files": "ls -al"
  },
```

試しに、上記のように `show-files` の行を追加してみてください。

`package.json` を保存したら、以下のコマンドを実行してみましょう。

```bash
npm run show-files
```

そうすると、 `ls -al` が実行されます！

Linuxコマンドが動くターミナル（Git Bash等）なら、ファイル一覧が表示されます！

もし、コマンドプロンプトを使っている場合は、Linuxコマンドが動かないので、エラーが表示されます。

この独自のスクリプトのことを `npm-script` と呼びます！覚えておきましょう。




## app.jsをつくってみる

作業ディレクトリに `app.js` というファイルを作ってください。

中身に以下の一行を書いて保存してください。

```javascript
console.log("Hello World!");
```

以下のコマンドを実行してみてください。

```bash
node app.js
```

以下が表示されれば成功です。

```bash
Hello World!
```



### おまけ： `index.js` と `app.js` の使い分け

たったいま、 `app.js` を作りましたが、先ほどの `package.json` には `index.js` という記述がありました。

これらは何が違うのでしょうか？

実は、今作っているnpmパッケージが

- アプリケーションなら `app.js` に、最初に実行するコードを書く。
- ライブラリなら `index.js` に、最初に実行するコードを書く。

という慣習があります。（諸説あります）



## Start Scriptを作ってみる

今度は、 `package.json` の `scripts` に `start` の行を追加してください。

```json
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "show-files": "ls -al",
    "start": "node app.js"
  },
```

以下のコマンドを実行してみてください。

```bash
npm run start
```

以下が表示されれば成功です。

```bash
Hello World!
```

次に、以下のコマンドを実行してみてください。

```bash
npm start
```

こちらのコマンドでも同じ結果になりました！以下が表示されれば成功です。

```bash
Hello World!
```

実は `npm start` というのは `npm run start` のエイリアスになっています。

メインに実行する処理はnpm-scriptの `start` に登録しましょう。



## `moment` をインストールしてみる

まず、以下のコマンドを実行してみましょう。

```bash
# moment をインストール
npm install moment
```

作業ディレクトリに `node_modules` フォルダーが生成されました。

このフォルダーを開くと、 `moment` フォルダーが見つかります。

加えて、 `package.json` に以下の行が自動で追加されます。

```json
  "dependencies": {
    "moment": "^2.24.0"
  }
```

バージョン番号が勝手に追加されましたね。このバージョンの先頭についている `^` に関しては、あとで説明します。

次に、`app.js` を以下のように書き換えてください。

```javascript
// ライブラリを読み込む
const moment = require("moment");

// 現在時刻を取得する
const now = moment().format("YYYY-MM-DD HH:mm:ssZ");

// 現在時刻を表示する
console.log(now);
```

以下のコマンドを実行してみてください。

```bash
npm start
```

すると、以下のように現在時刻が表示されます。

```bash
2019-11-20 18:51:02+09:00
```

`moment` は日付に関する処理を扱った歴史のあるライブラリで、Node.js界隈で広く使われています。

歴史がある分、多機能でファイルサイズが大きいという欠点もあります。

`dayjs` のような軽量なライブラリも台頭してきていますので、必要に応じて使い分けていきましょう。



## `moment` をアンインストールしてみる

せっかく入れた `moment` ですが、今度はアンインストールしてみます。

以下のコマンドを実行してみましょう。

```bash
# moment をアンインストール
npm uninstall moment
```

`node_modules` フォルダーから `moment` フォルダーが削除されました。

同時に `package.json` からも消えました。

```json
  "dependencies": {}
```

以下のコマンドを実行してみてください。

```bash
npm start
```

`moment` がなくなったので、以下のようにエラーが表示されるはずです。

```bash
internal/modules/cjs/loader.js:626
    throw err;
    ^

Error: Cannot find module 'moment'
```



## `package.json` から `moment` をインストールしてみる

今度は、 `package.json` に以下の行を手動で追加してみましょう。

```json
  "dependencies": {
    "moment": "^2.24.0"
  }
```

以下のコマンドを実行してみます。

```bash
# dependencies に書かれたライブラリをインストール
npm install
```

実は、これでも `moment` がインストールされます！

以下のコマンドを実行してみてください。

```bash
npm start
```

以下のように現在時刻が表示されれば成功です。

```bash
2019-11-20 18:51:02+09:00
```



## `dependencies` に色々インストールしてみる

試しに色々インストールしてみましょう

```bash
# dependencies にインストール
npm install express
npm install cors
```

`package.json` の `dependencies` にアルファベット順で追加されていくのがわかると思います。

```json
  "dependencies": {
    "cors": "^2.8.5",
    "express": "^4.17.1",
    "moment": "^2.24.0"
  }
```



## `eslint` を `dev-dependencies` にインストールしてみる

ESLintというライブラリがあります。
これを使うと、JavaScriptの文法を検査し、問題があればVS Code上に警告を表示してくれたりします。

このライブラリは開発環境では使いますが、サーバー環境では使いません。

こういったライブラリを扱うのが `devDependencies` です。

以下のコマンドを実行してみてください。

```bash
# devDependencies にインストール
npm install --save-dev eslint
npm install --save-dev eslint-config-airbnb-base
```

`package.json` に `dependencies` とは別に `devDependencies` という項目が増えたと思います。

```json
  "devDependencies": {
    "eslint": "^5.16.0",
    "eslint-config-airbnb-base": "^13.1.0",
  },
```



## `dependencies` のみインストールする

サーバー環境等で、 `devDependencies` をインストールせずに、 `dependencies` のみをインストールしたいということがあります。

以下のコマンドを使えば可能です。

```bash
# dependencies のみインストール
npm install --production
```



## セマンティックバージョニングについて

いつのまにか `package-lock.json` というものが生成されています。これは何なのでしょうか？

これが何なのか説明する前に、セマンティックバージョンについて説明します。

Node.js界隈では **セマンティックバージョニング** `X.Y.Z` という考え方が浸透しています。

おおざっぱに説明すると、以下のようにバージョン番号を付けます。

-  `X` : **メジャーバージョン** : 互換性がなくなったら上昇
-  `Y` : **マイナーバージョン** : 互換性を保ちつつ、機能を追加したら上昇
-  `Z` : **パッチバージョン** : バグ修正したら上昇

それでは、一番最初の開発版のバージョンはどうするのか？というと、セマンティックバージョニングによれば、 `0.1.0` から始めるのが良いとされています。

このハンズオンの最初に、バージョン番号を `0.1.0` にしたのも、それが理由です。



## バージョンの範囲指定について

`package.json` でのバージョンの範囲指定についても説明します。

先ほど `^` という記号が登場しました。以下のような特徴があります。

- `~X.Y.Z` : パッチバージョン `Z` の上昇まで許容します。
- `^X.Y.Z` : マイナーバージョン `Y` の上昇まで許容します。

たとえば、以下の表のように、複数のバージョンを持つライブラリがあるとします。

| バージョン | 2.x                | 2.1.x              | ~2.1.1             | ^2.1.1             |
| ---------- | ------------------ | ------------------ | ------------------ | ------------------ |
| 1.35.7     | :x:                | :x:                | :x:                | :x:                |
| 2.0.0      | :white_check_mark: | :x:                | :x:                | :x:                |
| 2.1.0      | :white_check_mark: | :white_check_mark: | :x:                | :x:                |
| 2.1.1      | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| 2.1.2      | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| 2.2.0      | :white_check_mark: | :x:                | :x:                | :white_check_mark: |
| 3.0.0      | :x:                | :x:                | :x:                | :x:                |

:white_check_mark: がついているものの中で、最新のものがインストールされます！

このようにバージョンを範囲指定することで、 `npm install` するだけで、バグが修正された新しいライブラリを使うことができます。

ただし、以下のようなことも発生します。

- Aさん、11月に `npm install` を実行したところ、 `2.1.1` がインストールされた。
- Bさん、12月に `npm install` を実行したところ、 `2.1.2` がインストールされた。

**`npm install` を実行したタイミングで異なるバージョンのライブラリがインストールされます！**

結局どのバージョンがインストールされているのか？は、 `package.json` を見ただけではわかりません。

それを記録しているのが、 `package-lock.json` です！

これは自動で生成されるファイルなので、このファイルを手動で編集しないようにお願いします。



## `npm ci` で `package-lock.json` から高速にインストールする

せっかく色々インストールしましたが、ここでいったん、 `node_modules` フォルダーを削除してみましょう。

（VS Codeで `node_modules` を開いている場合は、エラーが起きるかもしれません。その場合は、VS Codeを閉じてください。）

削除ができたら、以下のコマンドを実行してください。

```bash
npm ci
```

`node_modules` フォルダーが復活しました！

実は、 `npm ci` は `package-lock.json` を元にライブラリをインストールするんです！

また、 `npm install` よりも `npm ci` の方が、インストールが早く終わったことに気づいたかもしれません。

`npm install` を実行したときは、ライブラリが依存している別のライブラリを検索したりと、依存関係の解決に時間がかかります。

しかし、 `package-lock.json` には、そういった依存関係の検索が済んだデータが記載されています。そのため、 `npm ci` の方が `npm install` よりも高速になったんです。



## グローバルに `npm install`

今度は、以下のコマンドを実行してくてみましょう。

```bash
npm install --global fixpack
```

`fixpack` というライブラリがインストールされたはずですが、 `package.json` にも `node_modules` にも `fixpack` は追加されていません。

実は、npmにはグローバル用の `node_modules` があり、そこに追加されています。

WindowsでNodistを使ってNode.jsをインストールした場合、 `C:\Program Files (x86)\Nodist\bin\node_modules` にあるようです。



## `fixpack` を使ってみる

グローバルにインストールした `fixpack` を使ってみましょう。

以下のコマンドを実行してください。

```bash
fixpack
```

`package.json` の中身が自動でソートされたと思います。

とくにぐちゃぐちゃになりがちな `dependencies` の中身がアルファベット順にソートされるので便利です。

積極的に使っていきましょう！



# さいごに

以上おつかれさまでした。

ひととおり、npmコマンドや `package.json` の編集を体験できたと思います。

本記事作成にあたり、以下のページを参考にしました。ありがとうございました。

- [npm Documentation / npm-package.json](https://docs.npmjs.com/files/package.json)
- [セマンティック バージョニング](https://semver.org/lang/ja/)
- [package.json のチルダ(~) とキャレット(^)](https://qiita.com/sotarok/items/4ebd4cfedab186355867)
- [npm ciを使おう あるいはより速く](https://qiita.com/mstssk/items/8759c71f328cab802670)
- [fixpackでpackage.jsonの各項目を並び替える](https://qiita.com/0x50/items/1ed7e7c9d403db17d72c) 

これは [Node.js Advent Calendar 2019](https://qiita.com/advent-calendar/2019/nodejs) の8日目の記事でした。
明日は @shimataro999 さんによる [Node.js v12のES Modulesと、Babel/TypeScriptの対応について](https://qiita.com/shimataro999/items/8a63ec06f33ccd2ea9ca) です！