npmの依存関係とv7のロックファイルについて調べてみた
はじめに
以下の URL にて npm や TypeScript を使ったプラグイン開発に関する記事を書いた際に npm に関するわからなかったこと、主に「npm の依存関係と package-lock.json
」について調べてみました。
そもそも
Obsidian のプラグイン開発において Github のリポジトリから clone したリポジトリで手動でビルドする必要があり、npm i
で必要なパッケージが node_modules
というディレクトリにインストールされるということは知っていたが、node_moduels
ディレクトリになぜこんなにも多くのパッケージがインストールされているのか疑問だった。package.json
ファイルの dependencies の項目に記載されているパッケージが動作に必要で devDependencies は開発に必要なパッケージであるということはわかったが、その 2 つのセクションに書いていないパッケージが node_modules
に入っており意味がわからないというのが調査の発端。
また npm もかなり漠然と使ってたため、良い機会なので概念や目的も色々と調べてみた。
要点
『依存関係 (dependencies)』とは
プログラム内で外部ライブラリを使っていて、それが無いと動かないという状態を依存関係という。npm を導入する理由で最も多いのはこの依存関係を管理するためとのこと。
- プロジェクトルートにある
pacakge.json
の dependencies に記載されているパッケージがインストールされる (場合によっては devDependencies も)。 - インストールされる dependencies に記載されているパッケージが持つ各
package.json
ファイルの dependencies に記載されているパッケージが更にインストールされる。そして、すべての依存しているパッケージがインストールされる。 -
package-lock.json
にはインストールされたすべてのパッケージの正確なバージョン情報が記載されており、このファイルを使えば異なるマシンで異なる時間にパッケージのインストールをしても正確に同じ環境を作ることができる。 - npm にはバージョンがあり、使用しているバージョンの
package-lock.json
構造について知る必要があり、npm v7 ではpackage
オブジェクトが読み取られ、dependencies
オブジェクトは無視されるため気にかける必要はない。
npm の依存関係
npm i でインストールされるパッケージ
npm i
でインストールするといっても色々とパターンがあるのでそれぞれ試してみた。
- パターン 1: 最初から package.json がある状態で
npm i
- 自分で
npm init
せずに Github のリポジトリからクローンしたものなどはpackage.json
が最初からある状態でnpm i
コマンドでインストールすると、dependenceis に書かれているパッケージ以外のパッケージが node_modules にインストールされた。
- 自分で
- パターン 2: 自分で package.json を用意してから
npm i
-
npm init
でプロジェクトを初期化してpackage.json
を用意、dependencies に 1 つだけパッケージを記載してnpm i
を行ってみたところ、まったく知らないパッケージがnode_modules
にインストールされた。
-
- パターン 3: package.json を用意してから
npm i パッケージ名
- パターン 2 と同様に
node_modules
ディレクトリに知らないパッケージがインストールされた。
- パターン 2 と同様に
どのパターンでもプロジェクトの package.json
に記載されていない多くのパッケージがインストールされていた。ググって記事を漁ってみてもあまりよく分からなかったところ、以下の YouTube の動画をみたら一発で理解できた (npm の全貌についてもかなりわかり易く解説されていた)。
6:00~の「Dealing with npm package dependencies」のところを参照。
つまり、インストールしたいパッケージが特定の 1 つであってもそれを動かすためには依存しているパッケージもろもろすべてが必要であるためプロジェクトルートの package.json
に記載されていないそれらを node_modules
にインストールしていたということだった。
たとえば動画のように npm install chalk
で chalk というパッケージ 1 つをインストールしてみると node_moduels
には chalk 以外の 5 つのパッケージがインストールされている。
各パッケージのディレクトリを覗いてみるとそれぞれのパッケージごとに package.json
ファイルが存在し、それには dependencies の項目に別のパッケージが記載されていた。
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
chalk というパッケージはこの dependencies に記載されている ansi-styles、supports-color というパッケージが動かすために必要ということだ。そのため npm i chalk
では chalk を実際に使うために ansi-styles、supports-color というパッケージを追加でインストールされている。
しかし、まだ3つも知らないパッケージが node_modues
には存在している。理由は簡単で chalk を動かすために必要な 2 つのパッケージにもそれぞれ動かすために必要なパッケージがあったということだ。2 つのパッケージのディレクトリも覗いてみると chalk と同様にそれぞれ pacakge.json
ファイルを持ち、dependencies を持っていることがわかった。
"dependencies": {
"color-convert": "^2.0.1"
},
"dependencies": {
"has-flag": "^4.0.0"
},
よって、これら 2 つのパッケージを動かすためには更に color-convert と has-flag というパッケージをインストールする必要があるといことがわかる。そしてそれら 2 つの追加パッケージの package.json もみてみると dependencies の項目がないので追加でインストールする必要のあるパッケージがないことがわかる。
従って、すべてのパッケージの package.json の dependencies に記載されているパッケージがインストールされているのでこれでようやく chalk が動かすことができる。
プロジェクトフォルダをみてみると上の画像のようにあるパッケージは単独で package.json
ファイルを各々持ち、dependencies に必要なパッケージがかかれている場合にはそれぞれの依存を満たすために必要なパッケージをすべてインストールする必要がある。
依存関係の把握に使えるコマンド
ここで依存関係を把握するために便利な npm コマンドを知ることができたので以下の cowsay
というパッケージを単体でインストールしてみたログを使って紹介する。
# npmのプロジェクト初期化
$ npm init -y
# package.jsonが作成される
# cowsayというパッケージをインストール
$ npm i cowsay
# 作成されたpackage.jsonをみてみる
$ cat package.json
{
"name": "nodetest2",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cowsay": "^1.5.0"
}
}
# dependenciesにかかれている依存のみ表示
$ npm ls
nodetest2@1.0.0
└── cowsay@1.5.0
$ npm ls -a
# すべての依存関係をツリー表示
nodetest2@1.0.0
└─┬ cowsay@1.5.0
├── get-stdin@8.0.0
├─┬ string-width@2.1.1
│ ├── is-fullwidth-code-point@2.0.0
│ └─┬ strip-ansi@4.0.0
│ └── ansi-regex@3.0.0
├── strip-final-newline@2.0.0
└─┬ yargs@15.4.1
├─┬ cliui@6.0.0
│ ├─┬ string-width@4.2.3
│ │ ├── emoji-regex@8.0.0 deduped
│ │ ├── is-fullwidth-code-point@3.0.0
│ │ └── strip-ansi@6.0.1 deduped
│ ├─┬ strip-ansi@6.0.1
│ │ └── ansi-regex@5.0.1
│ └─┬ wrap-ansi@6.2.0
│ ├─┬ ansi-styles@4.3.0
│ │ └─┬ color-convert@2.0.1
│ │ └── color-name@1.1.4
│ ├─┬ string-width@4.2.3
│ │ ├── emoji-regex@8.0.0 deduped
│ │ ├── is-fullwidth-code-point@3.0.0
│ │ └── strip-ansi@6.0.1 deduped
│ └─┬ strip-ansi@6.0.1
│ └── ansi-regex@5.0.1
├── decamelize@1.2.0
├─┬ find-up@4.1.0
│ ├─┬ locate-path@5.0.0
│ │ └─┬ p-locate@4.1.0
│ │ └─┬ p-limit@2.3.0
│ │ └── p-try@2.2.0
│ └── path-exists@4.0.0
├── get-caller-file@2.0.5
├── require-directory@2.1.1
├── require-main-filename@2.0.0
├── set-blocking@2.0.0
├─┬ string-width@4.2.3
│ ├── emoji-regex@8.0.0
│ ├── is-fullwidth-code-point@3.0.0
│ └─┬ strip-ansi@6.0.1
│ └── ansi-regex@5.0.1
├── which-module@2.0.0
├── y18n@4.0.3
└─┬ yargs-parser@18.1.3
├── camelcase@5.3.1
└── decamelize@1.2.0 deduped
# deduped は重複排除されたパッケージ
# explainコマンドでbottom up view(このパッケージがなぜツリーに含まれるのかを構造的に表示)
$ npm explain cowsay
cowsay@1.5.0
node_modules/cowsay
cowsay@"^1.5.0" from the root project
$ npm explain get-stdin
get-stdin@8.0.0
node_modules/get-stdin
get-stdin@"8.0.0" from cowsay@1.5.0
node_modules/cowsay
cowsay@"^1.5.0" from the root project
# strip-anisはdeduped(重複)した依存なので色々なパッケージから必要とされていることがわかる
$ npm expalin strip-ansi
strip-ansi@6.0.1
node_modules/cliui/node_modules/strip-ansi
strip-ansi@"^6.0.0" from cliui@6.0.0
node_modules/cliui
cliui@"^6.0.0" from yargs@15.4.1
node_modules/yargs
yargs@"15.4.1" from cowsay@1.5.0
node_modules/cowsay
cowsay@"^1.5.0" from the root project
strip-ansi@"^6.0.1" from string-width@4.2.3
node_modules/cliui/node_modules/string-width
string-width@"^4.2.0" from cliui@6.0.0
node_modules/cliui
cliui@"^6.0.0" from yargs@15.4.1
node_modules/yargs
yargs@"15.4.1" from cowsay@1.5.0
node_modules/cowsay
cowsay@"^1.5.0" from the root project
strip-ansi@4.0.0
node_modules/strip-ansi
strip-ansi@"^4.0.0" from string-width@2.1.1
node_modules/string-width
string-width@"~2.1.1" from cowsay@1.5.0
node_modules/cowsay
cowsay@"^1.5.0" from the root project
strip-ansi@6.0.1
node_modules/wrap-ansi/node_modules/strip-ansi
strip-ansi@"^6.0.0" from wrap-ansi@6.2.0
node_modules/wrap-ansi
wrap-ansi@"^6.2.0" from cliui@6.0.0
node_modules/cliui
cliui@"^6.0.0" from yargs@15.4.1
node_modules/yargs
yargs@"15.4.1" from cowsay@1.5.0
node_modules/cowsay
cowsay@"^1.5.0" from the root project
strip-ansi@"^6.0.1" from string-width@4.2.3
node_modules/wrap-ansi/node_modules/string-width
string-width@"^4.1.0" from wrap-ansi@6.2.0
node_modules/wrap-ansi
wrap-ansi@"^6.2.0" from cliui@6.0.0
node_modules/cliui
cliui@"^6.0.0" from yargs@15.4.1
node_modules/yargs
yargs@"15.4.1" from cowsay@1.5.0
node_modules/cowsay
cowsay@"^1.5.0" from the root project
strip-ansi@6.0.1
node_modules/yargs/node_modules/strip-ansi
strip-ansi@"^6.0.1" from string-width@4.2.3
node_modules/yargs/node_modules/string-width
string-width@"^4.2.0" from yargs@15.4.1
node_modules/yargs
yargs@"15.4.1" from cowsay@1.5.0
node_modules/cowsay
cowsay@"^1.5.0" from the root project
使用コマンド
-
npm ls
- 依存関係をツリー構造で表示してくれるコマンド。オプション
-a
をつけることで完全なツリー構造表示になる。 - 出力が多すぎるため途中のバージョン変更において
-a
オプションをつけないとすべてを表示しない仕様に変更された。 deduped は重複しているパッケージを示す- deduped は 重複排除 されたパッケージを示す。
- 依存関係をツリー構造で表示してくれるコマンド。オプション
-
npm explain
-
npm ls -a
ではルートという上層から見た構造表示であったが、このコマンドではパッケージを指定することでそのパッケージが逆になぜツリーに含まれているか、どのパッケージから必要とされているかを階層表示してくれる。 - 重複でインストールされているバージョンの異なるパッケージについてそれぞれのバージョンがどのパッケージの dependencies に記載されているかをルートまでさかのぼってそれぞれを表示する。
-
ちなみに cowsay パッケージの package.json
ファイルは以下。
"dependencies": {
"get-stdin": "8.0.0",
"string-width": "~2.1.1",
"strip-final-newline": "2.0.0",
"yargs": "15.4.1"
},
"devDependencies": {
"@rollup/plugin-commonjs": "15.0.0",
"@rollup/plugin-node-resolve": "9.0.0",
"execa": "5.0.0",
"rollup": "2.26.5",
"rollup-plugin-string": "3.0.0",
"tap-dot": "2.0.0",
"tape": "5.0.1"
},
dependencies に記載されているパッケージ (get-stdin, string-widht, strip-final-newline, yargs の 4 つ) は cowsay が動作する上で必要なのでこのパッケージもインストールされる。しかし、この 4 つのパッケージにもそれぞれ package.json
ファイルがあり、それぞれ dependencies を持つ。つまりそれらの dependencies も動作する上で必要なのでインストールされる。更にインストールされたパッケージも更に dependencies を持つ、動作に必要なのでインストールされる....ということを繰り返してすべての依存関係のあるパッケージがインストールされた結果として node_modules ディレクトリが大きくなり、npm ls -a
で表示されるような依存ツリーが出来上がる。
次のようなパッケージの依存関係をグラフ表示てくれるサービスもあるので試してみた。
chalk の依存関係は比較的少ないのでかなりわかりやすい。
一方、cowsay は 33 個もの node が存在しており依存がより複雑になっていることがわかる。
ロックファイル
package.json
ファイルに記載された dependencies や npm install パッケージ名
でインストールしたパッケージの依存する他パッケージがインストールされるのはわかったが、node_modules
に入っている全パッケージの情報はどうやって確認するのかという疑問が立ち上がる。例えば Github のプロジェクトで clone してから実際に npm i
してみるまでインストールされるものがわからないということは無いはずだ。どんなサイズになるかもわからないし、セキュリティ的にもよろしくないのではないか。
もちろんその方法は存在しており、すべてのインストールされるべきパッケージを記載しており、依存関係をすべて把握しているのが package-lock.json
ファイルということになる。
ロックファイルの目的
ということで、npm v7 で使用される package-lock.json
ファイルの目的について公式ドキュメントから調べてみた。
package-locl.json
ファイルはnode_modules
ツリーもしくはpackage.json
ファイルを変更するすべての操作対して自動的に生成されるファイルであり、後のインストールによって同一の依存ツリーを生成できるように正確なツリーを記述する。このファイルはソースリポジトリへコミットするように意図されており、以下の様々な目的に役立つ。
- 同一の依存関係を正確にインストールできることを保証するために依存ツリーを単一の表現として記述する
node_modules
についてディレクトリそのものをコミットする必要なく、推移的な状態を確認することを可能にする- ソースの読みやすい差分コントロールによってツリー変更の可読性を向上させる
- 過去にインストールされたパッケージについて繰り返される冗長なメタデータ解決を省略することでインストールプロセスを最適化する
- npm v7 においてロックファイルはパッケージツリーの完全な全体図を得るための十分な情報を含んでおり、
package.json
ファイルを読む必要を減らした結果、パフォーマンスが大幅に向上する
pacakge.json だけではだめな理由
そもそもの package.json ではなぜ正確な依存関係をインストールできないのか。
それはセマンティックバージョニング (Semantice Versioning: semver) と呼ばれるバージョン管理番号の指定方法と関連がある。
セマンティックバージョニングでは x.y.z
という方法でバージョン番号を付ける (x,y,z は整数)。それぞれの数字には意味があり、次のように使う。
- x: メジャーバージョン
API 変更等、非後方互換 - y: マイナーバージョン
新規機能実装、フレームワークや機能向上、後方互換 - z: パッチバージョン
バグフィクス、メンテンスリリース、後方互換
このセマンティックバージョニングを使ってソフトウェアのバージョン管理を行うのが一般的であり、package.json
ではこの番号を dependencies や devDepenendencies にパッケージ名と共に記述する。しかし、このバージョン指定の方法によって正確な依存関係をインストールできなくなっている。例えば、先程の chalk のパッケージ内に含まれる package.json
の dependenceis では以下のようにセマンティックバージョニングのバージョン番号の頭にキャレット ^
と呼ばれる記号がついている。
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
このキャレットがついたバージョン番号は「一番左側にある、ゼロではないバージョンは変えず、それ以下のバージョンが有る場合には許容してインストールする」ことを意味している。つまり、ansi-styles というパッケージについては「4.1.0以上4.2.0未満
の最新バージョンをインストールする」というバージョンの範囲を指定していることになる。したがって npm i
ではこの範囲での ansi-styles の最新バージョンをインストールする。
ある時間にインストールした ansi-sytles のバージョンが 4.1.1
であったとしても、ソフトウェアの開発は時間と共に進み ansi-styles というパッケージのバージョンは進むので、別の時間インストールすると 4.1.7
というバージョンがインストールされることがある。このバージョンの範囲指定での最新を入れるので他の人が同じ package.json
ファイルを使ってインストールしても異なるバージョンのパッケージ群が入ってしまう可能性が大きい。より複雑なプロジェクトになれば依存先はかなり多いため各パッケージのバージョン更新が短い間にいくつもあるかもしれない。
といことで、実際に開発の環境でインストールしたパッケージの正確なバージョン番号というものが必要になってくるわけだ。
参考
そもそも、dependencies
は バージョン範囲 を指定するものであると公式ドキュメントに書いてある。これを知らなかったため、なぜ npm i
で異なるバージョンを入れてしまうのかが分からなかった。といことで、公式ドキュメントを参考にして dependencies
と devDependencies
についてもそれぞれ明確にしておくと以下のような違いがある。
-
dependencies
- dependencies(依存関係) はパッケージ名を バージョン範囲 にマップするシンプルなオブジェクトで指定される。バージョン範囲は 1 つ以上のスペースで区切られる記述子を持つ文字列で記述できる。dependencies は tarball または Git URL でも指定できる。
- チルダやキャレットによるバージョン範囲の記法があるため紛らわしい。
- The semver parser for node に範囲の記述方法のすべてが記載されている。
-
devDependencies
- そのパッケージを開発・ビルドするための外部ツールやフレームワークなど、ただプログラムを実行して使いたいユーザーには必要ないパッケージ。
- この項目にリストされているパッケージはこれが記載されている
pakcage.json
ファイルがルートにある状態でnpm install
まはたnpm link
コマンドを実行したとき。
package-lock.json の構造
主要なフィールドについて同様に公式ドキュメントから抜粋して調べてみた。npm v5/v6 からこのロックファイルの構造は変わったようだ。ドキュメントを読んでいると以前のバージョンでは package.json
と package-lock.json
の両方を読んでいたようだが、いまや pakage-lock.json
が完全な依存ツリーの情報を持つようになったのでパフォーマンスが向上したとのこと。
-
name
: パッケージの名前
package.json
の name と一致する -
version
:package-lock.json
のバージョン
package.json
の vserision と一致する -
lockfileVersion
: 整数のバージョン番号- バージョン番号がない: npm v5 以前の古い shrinkwarp ファイルであることを示す
-
1
: npm v5 または v6 で使用されているロックファイルであることを示す -
2
: npm v7 によって使用されているロックファイルであることを示し、v1 のロックファイルへの後方互換性を持っている -
3
: npm v7 によって使用されているロックファイルであることを示すが、後方互換性はない。node_modules/.package-lock.json
の隠しロックファイルに使用され、npm v6 のサポートが終了しだい将来的な npm のバージョンで使用される可能性が高い。
-
requires
: 公式ドキュメントに記載なし (ブール値) -
package
: パッケージの情報を含むオブジェクトへのパッケージロケーションをマッピングするオブジェクト- ルートプロジェクトは
""
というキーでリストされており、他のパッケージはルートプロジェクトフォルダからの相対パスで記載される。 -
version
: 実際にインストールされたパッケージ内部のpackage.json
に記載されているバージョンと一致する。 -
resolved
: パッケージの実際のロケーション。- npm レジストリから取得されている場合には tarball への URL
- Git 依存の場合にはコミットハッシュ付き git のフル URL
-
integrity
: このロケーションに解凍された依存パッケージに対する SRI(サブリソース完全性: Standard Subresource Integrity) として使われる文字列 (sha512 または sha1 で暗号化されたハッシュ値) -
bin
,license
,engines
,dependenceis
,optionalDependnecies
:package.json
に記載されているフィールドから転記。
- ルートプロジェクトは
-
dependencies
:lockfileVersion: 1
を使用している npm のバージョンをサポートするレガシーデータ。npm v7 ではpacakge
セクションが存在すればこのセクションを完全に無視するが、npm v6 と v7 のスイッチングをサポートするためにデータを保持している。なので基本的に v7 を使っていれば中身は無視してよい。
{
"name": "ProjectName",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"package": {
"": { // こいつがルートプロジェクト
"name": "ProjectName",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"chalk": "^4.1.2"
}
},
// 以下インストールしたパッケージをアルファベット順ですべて記載
"node_modules/ansi-styles": {
"version": "4.3.0", // これが実際にインストールされたパッケージのバージョン
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
// 中略
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"dependencies": {
// npm v7ではこのセクションは基本的に無視するので省略
// "package"と同じ様に依存ツリーについて記述されている
}
}
隠しロックファイルについて
npm v7 においては、node_modules/.package-lock.json
という隠しロックファイルが使用されており、node_moduldes フォルダに対して繰り返される処理を避けるようにしているとのこと。このファイルには依存ツリーに関する情報が含まれており、次の条件が満たされている場合には node_modules
フォルダの階層全体を読み取る代わりに使用される。
この隠しロックファイルは古いバージョンの npm からは無視されるようになっているので現在の通常ロックファイルにあるような後方互換性は含まない。よって npm v5/v6 はこのファイルを無視する。現時点では lockfileVersion
は 2 になっている。
{
"name": "ProjectName",
"version": "1.0.0",
"lockfileVersion": 2,
"package": {
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
// 中略
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
}
}
}
pakcage-lock.json
との違いは npm v5/v6 用のフィールドとしてあった "dependencies" がなくなっているのとルートプロジェクトを示す "" がなくなっている以外は同じ。
参考:
メモ
- npm について調べるには使用しているバージョンの npm についての情報が必要でありバージョンが変更されたら公式ドキュメントを再び読む必要がある。
- YouTube の英語版の動画にはかなり分かりやすくクオリティの高いものがあるのでドキュメントだけではなく動画も調べる価値がある。
追記
npm v8 について
というかいつの間に npm CLI v8 がリリースされていました。メジャーバージョンが上がったので破壊的変更があったのではないでしょうか。ドキュメントはまだないみたいですね。追って確認したいと思います。
勘違いしていたこと
今回の記事を書いた後に気づいたことや、勘違いしていたことが多々あったのでそれについて別の記事にまとめました。
Discussion