Skip to content

Commit 2f99346

Browse files
azuwindchime-yk
andauthored
feat: Node.jsの章の変更をmainにマージ (#1758)
* mochaを`node:test`に変更する (#1737) * fix(nodecli): ユニットテストのMochaをnode:testに変更 * fix(nodecli): Mochaのリンクを削除 * fix(nodecli): 修正箇所の言い回しを訂正 * fix(nodecli): Windowsで動作しないサンプルコードを削除 * fix(nodecli): refactor-and-unittestからMochaを削除 * fix(nodecli): commanderパッケージ を `node:util` の `parseArg` に変更 (#1757) * fix(nodecli): commanderをコードから削除 * refactor: commanderのインストールを削除、セットアップセクションを追加 * fix: commanderをparseArgに置き換える * fix * fix * fix: main-3.jsは利用しな苦なった * fix * fix * fix * fix order * fix: node:fsに統一 * `node:`をコラムに移動 * fix * fix * fix * fix * fix * remove empty line * fix * fix * npm link * link * fix * fix * fix * シンプルに * 標準モジュールはインストールが不要なことを明記 * fix --------- Co-authored-by: WhyK <[email protected]>
1 parent 68b8606 commit 2f99346

File tree

27 files changed

+409
-1279
lines changed

27 files changed

+409
-1279
lines changed

source/use-case/nodecli/argument-parse/README.md

Lines changed: 47 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -44,78 +44,42 @@ $ node main.js one two=three four
4444

4545
## コマンドライン引数をパースする {#parse-args}
4646

47-
`process.argv`配列を使えばコマンドライン引数を取得できますが、取得できる情報にはアプリケーションに不要なものも含まれています
47+
`process.argv`配列を使えばコマンドライン引数を取得できますが、アプリケーションには不要なものも含まれています
4848
また、文字列の配列として渡されるため、フラグのオンオフのような真偽値を受け取るときにも不便です。
4949
そのため、アプリケーションでコマンドライン引数を扱うときには、一度パースして扱いやすい値に整形するのが一般的です。
5050

51-
今回は[commander][]というライブラリを使ってコマンドライン引数をパースしてみましょう。
52-
文字列処理を自前で行うこともできますが、このような一般的な処理は既存のライブラリを使うと簡単に書けます。
53-
54-
### `commander`パッケージをインストールする {#install-commander}
55-
56-
commanderは[npm][]`npm install`コマンドを使ってインストールできます。
57-
まだnpmの実行環境を用意できていなければ、先に「[アプリケーション開発の準備][]」の章を参照してください。
58-
59-
npmでパッケージをインストールする前に、まずは`package.json`というファイルを作成します。
60-
`package.json`とは、アプリケーションが依存するパッケージの種類やバージョンなどの情報を記録するJSON形式のファイルです。
61-
`package.json`ファイルのひな形は、`npm init`コマンドで生成できます。
62-
通常は対話式のプロンプトによって情報を設定しますが、ここではすべてデフォルト値で`package.json`を作成する`--yes`オプションを付与します。
63-
64-
`nodecli`のディレクトリ内で、`npm init --yes`コマンドを実行して`package.json`を作成しましょう。
65-
66-
```shell
67-
$ npm init --yes
68-
```
69-
70-
生成された`package.json`ファイルは次のようになっています。
71-
72-
[import, title:"package.json"](src/package.init.json)
73-
74-
`package.json`ファイルが用意できたら、`npm install`コマンドを使って`commander`パッケージをインストールします。
75-
このコマンドの引数にはインストールするパッケージの名前とそのバージョンを`@`記号でつなげて指定できます。
76-
バージョンを指定せずにインストールすれば、その時点での最新の安定版が自動的に選択されます。
77-
次のコマンドを実行して、commanderのバージョン9.0をインストールします。[^1]
78-
79-
```shell
80-
$ npm install [email protected]
81-
```
82-
83-
インストールが完了すると、`package.json`ファイルは次のようになっています。
84-
85-
[import, title:"package.json"](src/package.install.json)
86-
87-
また、`npm install`をすると同時に`package-lock.json`ファイルが生成されています。
88-
このファイルはnpmがインストールしたパッケージの、実際のバージョンを記録するためのものです。
89-
先ほどcommanderのバージョンを`9.0`としましたが、実際にインストールされるのは`9.0.x`に一致する最新のバージョンです。
90-
`package-lock.json`ファイルには実際にインストールされたバージョンが記録されています。
91-
これによって、再び`npm install`を実行したときに、異なるバージョンがインストールされるのを防ぎます。
92-
51+
今回は、Node.jsの標準モジュールである[`node:util`モジュール][][parseArgs][]という関数を使ってコマンドライン引数をパースしてみましょう。
52+
コマンドライン引数のパース処理を自前で行うこともできますが、このような一般的な処理はNode.jsの標準モジュールやサードパーティ製のライブラリを使うことで簡単に実装できます。
53+
今回利用する`node:util`モジュールは、Node.js自体に同梱されている標準モジュールであるため、npmを使って別途インストールする必要はありません。
9354

9455
### ECMAScriptモジュールを使う {#esmodule}
9556

96-
今回のユースケースでは、インストールした`commander`パッケージを利用するにあたって、基本文法で学んだ[ECMAScriptモジュール][]を使います。
97-
`commander`パッケージはECMAScriptモジュールに対応しているため、次のように`import`文を使って変数や関数などをインポートできます
57+
今回のユースケースでは、`node:util`モジュールを利用するにあたって、基本文法で学んだ[ECMAScriptモジュール][]を使います。
58+
`node:util`モジュールは、次のように`import`文を使ってインポートできます
9859

9960
<!-- doctest:disable -->
10061
```js
101-
import { program } from "commander";
62+
// `node:util`モジュールを、utilオブジェクトとしてインポートする
63+
import * as util from "node:util";
10264
```
10365

104-
ただし、ECMAScriptモジュールのパッケージをインポートするには、インポート元のファイルもECMAScriptモジュールでなければなりません
66+
ただし、ECMAScriptモジュールを扱う場合には、Node.jsに対してJavaScriptファイルがどのモジュール形式であるかを明示する必要があります
10567
なぜなら、[Node.js][][CommonJSモジュール][]という別のモジュール形式もサポートしており、CommonJSモジュール形式では`import`文は利用できないためです。
106-
そのため、これから実行するJavaScriptファイルがどちらの形式であるかをNode.jsに教える必要があります。
10768

10869
Node.jsはもっとも近い上位ディレクトリの `package.json` が持つ `type` フィールドの値によってJavaScriptファイルのモジュール形式を判別します。
109-
`type`フィールドが `module` であればECMAScriptモジュールとして、`type`フィールドが `commonjs` であればCommonJSモジュールとして扱われます。[^2]
70+
`type`フィールドが `module` であればECMAScriptモジュールとして、`type`フィールドが `commonjs` であればCommonJSモジュールとして扱われます。[^1]
11071
また、JavaScriptファイルの拡張子によって明示的に示すこともできます。拡張子が `.mjs` である場合はECMAScriptモジュールとして、`.cjs` である場合はCommonJSモジュールであると判別されます。
11172

112-
今回は `main.js` を ECMAScriptモジュールとして判別させるために、次のように `package.json``type` フィールドを追加します。
73+
今回は `main.js` を ECMAScriptモジュールとして判別させるために、次のコマンドで `package.json``type` フィールドを追加します。
74+
まだ、`package.json`を作成していない場合は、先に「[Node.jsプロジェクトのセットアップ][]」を参照してください。
11375

11476
```shell
115-
# npm pkg コマンドで type フィールドの値をセットする
77+
# npm pkg コマンドで、package.jsonの type フィールドの値をセットする
11678
$ npm pkg set type=module
11779
```
11880

81+
コマンドが実行できたら`package.json`ファイルに `type` フィールドが追加されていることを確認してください。
82+
11983
[import, title:"package.json"](src/package.json)
12084

12185
#### [コラム] CommonJSモジュール {#commonjs-module}
@@ -125,7 +89,7 @@ CommonJSモジュールは[ECMAScriptモジュール][]の仕様が策定され
12589

12690
現在はNode.jsでもECMAScriptモジュールがサポートされていますが、`fs` などの標準モジュールはCommonJSモジュールとして提供されています。
12791
また、サードパーティ製のライブラリや長く開発が続けられているプロジェクトのソースコードなどでも、CommonJSモジュールを利用する場面は少なくありません。
128-
そのため、この2つのモジュール形式が共存する場合には、開発者はモジュール形式間の相互運用性(互いを組み合わせた時の動作)に注意する必要があります。[^3]
92+
そのため、この2つのモジュール形式が共存する場合には、開発者はモジュール形式間の相互運用性(互いを組み合わせた時の動作)に注意する必要があります。[^2]
12993

13094
Node.jsはECMAScriptモジュールからCommonJSモジュールをインポートする方向の相互運用性をサポートしています。
13195
たとえば、次のようにCommonJSモジュールで`exports`オブジェクトを使ってエクスポートされたオブジェクトは、ECMAScriptモジュールで`import`文を使ってインポートできます。
@@ -146,40 +110,51 @@ import { key } from "./lib.cjs";
146110

147111
### コマンドライン引数からファイルパスを取得する {#get-file-path}
148112

149-
先ほどインストールした`commander`パッケージを使って、コマンドライン引数として渡されたファイルパスを取得しましょう。
113+
`node:util`モジュールを使って、コマンドライン引数として渡されたファイルパスを取得しましょう。
150114
このCLIアプリケーションでは、処理の対象とするファイルパスを次のようなコマンドの形式で受け取ります。
151115

152116
```shell
153117
$ node main.js ./sample.md
154118
```
155119

156-
commanderでコマンドライン引数をパースするためには、インポートした`program`オブジェクトの`parse`メソッドにコマンドライン引数を渡します
120+
コマンドライン引数をパースするためには、`node:util`モジュールの`parseArgs`関数を利用します
157121

158122
<!-- doctest:disable -->
159123
```js
160-
// commanderモジュールからprogramオブジェクトをインポートする
161-
import { program } from "commander";
162-
// コマンドライン引数をcommanderでパースする
163-
program.parse(process.argv);
124+
// `node:util`モジュールを、utilオブジェクトとしてインポートする
125+
import * as util from "node:util";
126+
127+
// コマンドライン引数をparseArgs関数でパースする
128+
const {
129+
values,
130+
positionals
131+
} = util.parseArgs({
132+
// オプションやフラグ以外の引数を渡すことを許可する
133+
allowPositionals: true
134+
});
135+
console.log(values); // オプションやフラグを含むオブジェクト
136+
console.log(positionals); // フラグ以外の引数の配列
164137
```
165138

166-
`parse`メソッドを呼び出すと、コマンドライン引数をパースした結果を`program`オブジェクトから取り出せるようになります。
167-
今回の例では、ファイルパスは`program.args`配列に格納されています。
168-
`program.args`配列には`--key=value`のようなオプションや`--flag`のようなフラグを取り除いた残りのコマンドライン引数が順番に格納されています。
139+
`parseArgs`関数は、コマンドライン引数をパースした結果として`values``positionals`の2つのプロパティを持つオブジェクトを返します。
140+
`values`オブジェクトには、`--key=value`のようなオプションや`--flag`のようなフラグをパースした結果が保存されています。
141+
`positionals`配列には、オプションやフラグ以外の引数が配列として順番に格納されています。
142+
デフォルトでは、`positionals`配列はパース結果には含まれないため、`allowPositionals`オプションを`true`にすることで含まれるようになります。
169143

144+
今回の`main.js`に渡す`./sample.md`引数はオプションやフラグではないので、`positionals`配列に格納されます。
170145
それでは`main.js`を次のように変更し、コマンドライン引数で渡されたファイルパスを取得しましょう。
171146

172147
[import title:"main.js"](src/main-2.js)
173148

174-
次のコマンドを実行すると、`program.args`配列に格納された`./sample.md`文字列が取得されてコンソールに出力されます。
175-
`./sample.md``process.argv`配列では3番目に存在していましたが、パース後の`program.args`配列では1番目になって扱いやすくなっています。
149+
次のコマンドを実行すると、`positionals`配列の先頭に格納された`./sample.md`文字列が取得されてコンソールに出力されます。
150+
`./sample.md``process.argv`配列では3番目に存在していましたが、パース後の`positionals`配列では1番目になって扱いやすくなっています。
176151

177152
```shell
178153
$ node main.js ./sample.md
179154
./sample.md
180155
```
181156

182-
このように、`process.argv`配列を直接扱うよりも、commanderのようなライブラリを使うことで宣言的にコマンドライン引数を定義して処理できます
157+
このように、`process.argv`配列を直接扱うよりも、`node:util``parseArgs`関数を利用すると、コマンドライン引数をより扱いやすい形にパースできます
183158
次のセクションではコマンドライン引数から取得したファイルパスを元に、ファイルを読み込む処理を追加していきます。
184159

185160
#### [エラー例] SyntaxError: Cannot use import statement outside a module {#syntax-error-import-statement}
@@ -188,7 +163,7 @@ $ node main.js ./sample.md
188163

189164
<!-- doctest:disable -->
190165
```shell
191-
import { program } from "commander";
166+
import * as util from "node:util";
192167
^^^^^^
193168

194169
SyntaxError: Cannot use import statement outside a module
@@ -199,18 +174,18 @@ SyntaxError: Cannot use import statement outside a module
199174
## このセクションのチェックリスト {#section-checklist}
200175

201176
- `process.argv`配列に`node`コマンドのコマンドライン引数が格納されていることを確認した
202-
- npmを使ってパッケージをインストールする方法を理解した
203177
- ECMAScriptモジュールを使ってパッケージを読み込めることを確認した
204-
- commanderを使ってコマンドライン引数をパースできることを確認した
178+
- `node:util`モジュールの`parseArgs`関数を使ってコマンドライン引数をパースできることを確認した
205179
- コマンドライン引数で渡されたファイルパスを取得してコンソールに出力できた
206180

207-
[commander]: https://github.com/tj/commander.js/
208-
[npm]: https://www.npmjs.com/
181+
209182
[npmのGitHubリポジトリ]: https://github.com/npm/npm
210183
[CommonJSモジュール]: https://nodejs.org/docs/latest/api/modules.html
211184
[Node.js]: https://nodejs.org/
212185
[アプリケーション開発の準備]: ../../setup-local-env/README.md
213186
[ECMAScriptモジュール]: ../../../basic/module/README.md
214-
[^1]: --saveオプションをつけてインストールしたのと同じ意味。npm 5.0.0からは--saveがデフォルトオプションとなりました。
215-
[^2]: [package.json and file extensions](https://nodejs.org/api/packages.html#packagejson-and-file-extensions)
216-
[^3]: [Interoperability with CommonJS](https://nodejs.org/api/esm.html#interoperability-with-commonjs)
187+
[`node:util`モジュール]: https://nodejs.org/api/util.html
188+
[parseArgs]: https://nodejs.org/api/util.html#utilparseargsconfig
189+
[Node.jsプロジェクトのセットアップ]: ../helloworld/README.md#setup-nodejs-project
190+
[^1]: [package.json and file extensions](https://nodejs.org/api/packages.html#packagejson-and-file-extensions)
191+
[^2]: [Interoperability with CommonJS](https://nodejs.org/api/esm.html#interoperability-with-commonjs)
Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
// commanderモジュールからprogramオブジェクトをインポートする
2-
import { program } from "commander";
1+
import * as util from "node:util";
32

4-
// コマンドライン引数をcommanderでパースする
5-
program.parse(process.argv);
6-
7-
// ファイルパスをprogram.args配列から取り出す
8-
const filePath = program.args[0];
3+
// コマンドライン引数をparseArgs関数でパースする
4+
const {
5+
positionals
6+
} = util.parseArgs({
7+
// オプションやフラグ以外の引数を渡すことを許可する
8+
allowPositionals: true
9+
});
10+
// ファイルパスをpositionals配列から取り出す
11+
const filePath = positionals[0];
912
console.log(filePath);

source/use-case/nodecli/argument-parse/src/package-lock.json

Lines changed: 1 addition & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

source/use-case/nodecli/argument-parse/src/package.install.json

Lines changed: 0 additions & 15 deletions
This file was deleted.

source/use-case/nodecli/argument-parse/src/package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,5 @@
1010
"keywords": [],
1111
"author": "",
1212
"license": "ISC",
13-
"dependencies": {
14-
"commander": "^9.0.0"
15-
}
13+
"dependencies": {}
1614
}

0 commit comments

Comments
 (0)