🤪

そもそもnpmからわからない

2022/02/10に公開
2

はじめに

やっぱりwebpackがわからない(エピソード1)エピソード2を公開しているのですが、そもそもnpmからわからない、という人もいると思いますので、今回はnpmに関して説明します。

なお、やっぱりwebpackがわからないではViteに関して触れていますが、Node.jsもDenoという新しいランタイム環境が登場しています。ですが、やはりまだ開発現場で使用するには難しいと思いますので、Node.jsを使用するうえでnpmはちゃんと理解しておいた方がいいです。

npmとは

npm とはNode.jsのパッケージを管理するシステム、所謂パッケージ管理システムです。アプリケーションを作成する際、便利なパッケージをそのプロジェクトにインストールして、使用することができます。

ところで、パッケージとは一体何なのでしょうか?

パッケージとは

システム開発ではモジュール、パッケージ 、ライブラリという言葉が頻繁に飛び交い、みなさんも聞いたことがあると思います。これが、プログラム言語などによって意味が変わってくるので非常にややこしくなります。

一般的にモジュール(Module) とは、他のプログラムから利用することを目的としたクラスや関数などのプログラムのことです。やっぱりwebpackがわからない(エピソード1)で説明していますので、これを見た人は感覚がつかみやすいと思います。そして、このモジュールをまとめて機能するようにしたものがパッケージ、もしくはライブラリと呼ばれています。更にパッケージ、もしくはライブラリをまとめたものが、どちらかの名称で呼ばれたりしますので、非常にややこしくなります。

ここまでは一般的な話となり、npmには独自の定義があります。まず、npmにライブラリという言葉は登場しませんので、頭の片隅にでも追いやってください。npmに存在するのは、パッケージとモジュールです。そして、詳しい説明をこの段階で行うと頭が混乱すると思いますので、ひとまずはnpmでダウンロードして使用するものが、パッケージだと思ってください。これに関しては、この記事の最後の方で説明します。

npmの必要性

直接パッケージをダウンロードして利用すればいいのではと考えるかもしれませんが、以下のような問題が発生するため、npmでの管理が必要となってきます。

  • パッケージの依存関係
    パッケージxを使用するためにパッケージyが必要な場合、パッケージxはパッケージyに依存していると言えます。したがって、あるパッケージをインストールする際、その依存関係にあるパッケージをインストールし、さらにその依存関係にあるパッケージを……と複雑になります。
  • パッケージの競合関係
    パッケージxによって使用しているパッケージyが、これからインストールするパッケージzでも必要な場合、パッケージyを更にインストールしていまい衝突してしまいます。これをパッケージの競合関係といい、あるパッケージをインストールする際、競合するパッケージやモジュールがないかを調べなくてはいけません。

npmはパッケージを管理してくれて、これらの問題を解決します。

Yarn

Yarnとは、npmよりもインストールが速く、セキュリティーが高いなどの特徴を持つ、Facebook 社(Meta)が開発したパッケージ管理システムです。npmと互換性があり、npmの設定ファイルであるpackage.jsonが使用できます。npmよりYarnを推奨する人も多いのですが、個人的には改善されてきたnpmで十分かなと思っています。ちなみにFacebookは、React周りなどを筆頭に、最初は便利だけど途中で取って代わられるシステムをぶっこんでくる癖があります。

npmのインストール

npmは、Node.jsをインストールすると同時にインストールされます。つまり、昨今のフロントエンド開発はNode.jsをインストールすることから始まるといっても過言ではありません。

まず、npmはパッケージ管理システムといいましたが、Node.jsもnvm(Node Version Manager) というNode.jsのバージョン管理システムで管理することができます。nvmの使用は必須ではなく、そのままNode.jsをインストールしてもいいのですが、Node.jsはバージョンアップが頻繁に行われていますので、できるだけnvmで管理するようにしましょう。これで管理すると、Node.jsのバージョンを上げたり戻したりするのを簡単に行えます。

mac

macの場合はnodebrewなどでも管理できますが、ここではnvmをインストールします。

さて、おしゃれなmacで最先端な開発をするには、ここで洗礼を受けます。購入したmacのOSがCatalina前と以後で使用するシェルが違うのです。Catalina前はbashで、Catalinaからはzshとなります。どちらを使用しているかは、ターミナルの上部で確認できるのですが、次のコマンドでも確認できます。

$ echo $SHELL

/bin/zshもしくは/bin/bashが表示されます。まずzshの場合、nvmは更新するために.zshrcを検索するのですが、これがデフォルトで作成されていません。したがって、次のコマンドで作成しましょう。

$ touch ~/.zshrc

次にbashの場合でも安心できません。使用する.bash_profileファイルがない場合があります。次のコマンドで確認してみましょう。

$ ls -a ~/  return 

もしなければ、次のコマンドで作成します。

$ touch ~/.bashrc

まだ、安心できません。Xcodeコマンドラインツール(xcode command line tools)がインストールされていない場合は、次のコマンドでインストールします。

$ xcode-select --install

さて、ようやくnvmをインストールできます。インストールはcurlまたはwgwtコマンド、もしくは直接ダウンロードをしてインストールできますが、nvm-shのGitHubページで確認し、次のようにインストールしましょう。

# curlの場合
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
# wgetの場合
$ wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash

とりあえずcommand + qなどで一度ターミナルを終了させ、再度ターミナルを起動させてください。もしくは、sourceコマンドを実行して変更を反映させてください。

$ source ~/.bash_profile return  # bashの場合
$ source ~/.zshrc                # zshの場合

次のコマンドでバージョンを確認することで、インストールを確認します。

$ nvm -v
$ nvm -v
# または
$ command -v nvm

Linux

無骨なLinuxは、macのように無粋な設定など必要ありません。早速、curlコマンドもしくはwgetコマンドでインストールしましょう。

# curlの場合
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
# wgetの場合
$ wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash

これでインストール完了です。

インストールが完了すると、.bashrcに追記したのでbashを開きなおしなさいと書かれています。

=> Appending nvm source string to $HOME/.bashrc
=> Appending bash_completion source string to $HOME/.bashrc
=> Close and reopen your terminal to start using nvm or run the following to use it now:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # This loads nvm bash_completion

確認してみましょう。

$ tail -n 3 $HOME/.bashrc 
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # This loads nvm bash_completion

bashを開き直し、バージョン確認しましょう。

$ nvm --version

Windows

Windowsのnvmは主にnvm-windows、nodistの2種類があります。機能面は両方とも変わりありませんが、ここではLinuxのnvmとコマンドが近いnvm-windowsをインストールすることにします。インストールは、GitHubのページからインストーラ(nvm-setup.zip)をダウンロードして実行してください。ライセンスの同意、インストール先の決定、シンボリックリンクの作成先という簡単な設定となりますが、特に変更せずに進めましょう。

インストールが終了したら、バージョン確認しましょう。

$ nvm -v

nvmの使い方

バージョンの確認は、以下のコマンドとなります。

$ nvm --version
# または、
$ nvm -v

ヘルプです。

$ nvm --help

インストール可能なNode.jsのバージョンを確認します。

$ nvm ls-remote

nvm-windowsの場合は、次となります。

$ nvm list available

最新バージョンを使う理由が特に無く、バックエンドで使用するならLTSのインストールを推奨します。LTS(Long Term Support) とは、少なくとも18ヶ月間のサポートとメンテナンスがあるバージョンで、Releaseでスケジュールが公開されています。コードネームがArgon、Boron、Carbon、Dubnium……とバージョンアップしていますが、一番数字の大きいLast LTS:を選択しましょう。

インストールは、次のようにバージョンを指定します。

$ nvm install 16.13.2

32bit版の場合は指定する必要があります。

$ nvm install 16.13.2 32

使用するバージョンを切り替えます。1つのバージョンだけのインストールでも、これを行わないとNode.jsが使用できません。

$ nvm use 12.19.0

インストールされているバージョンの一覧を表示します。

$ nvm list

アンインストールは次となります。

$ nvm uninstall 16.13.2 32

これでNode.jsがインストールされ、同時にnpmもインストールされています。

バージョンは以下で確認できます。

$ npm -v
$ npm --version
$ npm version

以下はヘルプです。

$ npm help
$ npm --help
$ npm help コマンド
$ npm -h コマンド

npmの使い方

プロジェクトの作成

冒頭でも説明しましたが、npmはプロジェクト内でパッケージを管理するものですので、まずはプロジェクトを作成する必要があります。プロジェクトの作成というと大げさに聞こえるかもしれませんが、要は作成するアプリケーションなどに関する物をまとめたフォルダのことです。したがって、PCのどこかにファルダを作成してください。

作成しましたか?言うなれば、それは空のプロジェクトとも言えます。このプロジェクトの中で、アプリケーションなどを作成していきます。では、このプロジェクトをnpmで管理していきましょう。

初期化

プロジェクトをnpmで管理するには、初期化(initialization)を行います。初期化というと、これまた大げさに聞こえるかもしれませんが、これはpackage.jsonというプロジェクトの管理ファイルをカレントディレクトリに作成することです。したがって、package.jsonを自分で作成してもかまいません。なお、この初期化を行わない、つまり、package.jsonを作成しないでも、パッケージをインストールして操作すること自体は可能です。ただし、それはnpmで管理していることにはらなず、npmの恩恵にあずかることができません。

次のコマンドで初期化を行い、対話形式でpackage.jsonを作成できます。

$ npm init

package name  # パッケージの名前
version        # バージョン
description    # パッケージの説明
entry point    # メインファイル(一番最初に実行されるファイル)
test command   # scriptsのtestに設定されるコマンド
git repository #  gitリポジトリのURL
keywords       #  キーワード。パッケージを公開した際の検索のため。
author         #  著者
license        #  パッケージのライセンス

とりあえずは、すべてリターンで進めても問題ありません。後から修正できます。また、次のオプションで、対話形式を飛ばすこともできます。

$ npm init -y

カレントディレクトリに、package.jsonが次の内容で作成されます。

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

では、細かな説明の前に、とりあえずは使い方を一通り説明します。

proxy

早速インストールの説明をしたいのですが、その前に注意したいのがproxyです。個人的に使用する場合は、ほとんどの人が関係ないと思いますが、企業内などで使用する場合はproxyをかましている可能性があります。

次のコマンドで、設定を確認できます。

$ npm config list

# より詳しい情報
$ npm config ls -l

proxyを経由を経由させるには、以下のコマンドで設定します。

$ npm config set proxy http://proxy.foo.com:8080
# パスワードが必要な場合
$ npm config set proxy http://<ID>:<password>@proxy.foo.com:8080

SSL関連のエラーが発生する場合は、以下のコマンドでregistryをhttpsからhttpに変更します。

$ npm config set registry http://registry.npmjs.org/

インストール

パッケージのインストールには、現在のプロジェクトへ個別にインストールをするローカルインストールと、使用しているPCにインストールするグローバルインストールがあります。

ローカルインストールは以下となり、プロジェクトのカレントディレクトリのnode_modules配下にインストールされます。

$ npm install パッケージ名
$ npm i パッケージ名
# バージョン指定
$ npm i パッケージ名@x.y.z

グローバルインストールは、以下となります。

$ npm i -g パッケージ名

インストールには、次のようなオプションがあります。dependenciesなどの細かな説明はpackage-lock.jsonで説明します。

# package.jsonに書き込まれません
$ npm i --no-save パッケージ名

# package.jsonのdependenciesに登録します。
$ npm i --save パッケージ名

# package.jsonのoptionalDependenciesに登録します。
$ npm i --save-optional パッケージ名

# package.jsonのdevDependenciesに記録します。
$ npm i --save-dev パッケージ名
$ npm i -D パッケージ名

package.jsonをディレクトリに置いて、次のコマンドを実行すると、そのpackage.jsonに設定されたパッケージをインストールします。

$ npm install

これは、最新のバージョンがインストールされます。バージョンを指定したい場合はpackage-lock.jsonファイルを使用します。

また、npm自身をバージョンアップするのも、installコマンドを使用します。npmでnpmをインストールするというトリッキーな方法です。

$ npm install -g npm 

パッケージ一覧

インストール済のパッケージの一覧を表示

# インストール済のパッケージ一覧を表示
$ npm list
$ npm ls
$ npm la
$ npm ll
# 直接インストールしたパッケージだけを表示
$ npm list --depth=0
# グローバルインストールをしたパッケージの一覧を表示
$ npm list -g
# グローバルインストールの場合
$ npm list -g | head -1

インストールできるパッケージを検索します。

# npmリポジトリから検索
$ npm search パッケージ名
# パッケージの詳細情報を表示
$ npm info パッケージ名
$ npm view パッケージ名
$ npm show パッケージ名
# パッケージのバージョンを表示
$ npm info パッケージ名 version  # 最新バージョンを表示
$ npm info パッケージ名 versions # インストール可能なバージョン一欄を表示

アップデート

パッケージをアップデートします。

# アップデートの確認
$ npm outdated
$ npm outdated -g
# アップデート
$ npm update パッケージ名
$ npm update -g パッケージ名
# パッケージ名を省略すると、package.jsonに記述された全てのパッケージをアップデート
$ npm update

npm-check-updates

npm-check-updatesというパッケージで、複数のパッケージのアップデートを便利に行うことができます。

まずはインストールします。このように、どのプロジェクトでも使用する汎用的なパッケージは、次のようにグローバルインストールすることが多いですが、ローカルインストールしても問題ありません。

$ npm i -g npm-check-updates

次のコマンドで、アップデート情報を表示します。

$ ncu

# 表示例
@types/react                  ^17.0.0  →  ^17.0.39
@types/react-dom              ^17.0.0  →  ^17.0.11
react-scripts                   4.0.3  →     5.0.0
typescript                     ^4.1.2  →    ^4.5.5

# 全てのパッケージが最新の場合、次のように表示されます。
All dependencies match the latest package versions :)

例えば、上記のtypescriptを見ると、現在インストールしているバージョンが4.1.2で、最新版は4.5.5であることが分かります。ここでは表現できませんが、各最新版には色付きで表示されているものがあり、意味は次となります。

  • レッド: メジャーアップグレード
  • シアン: マイナーアップグレード
  • グリーン: パッチのアップグレード

インストールしているパッケージが多い場合、パッケージごとなどでアップデート情報を表示できます。

$ ncu パッケージ名    # 特定のパッケージのみを調べる
$ ncu -x パッケージ名 # 指定したパッケージを除外する
# 続けてパッケージを指定することで、複数のパッケージを指定できる
$ ncu パッケージ名 パッケージ名
$ ncu -x パッケージ名 パッケージ名

このアップデート情報をpackage.jsonに反映させるには、次のコマンドを実行します。

$ ncu -u

パッケージを指定することで、特定のパッケージのみをアップデートでき、続けてパッケージを指定することで、複数のパッケージを指定してアップデートできます。

$ ncu -u パッケージ名
$ ncu -u パッケージ名 パッケージ名……

次は、特定のパッケージを除外して、アップデートします。

$ ncu -u -x パッケージ名

また、これらのコマンドに正規表現を使用することが可能です。例えば次は、先頭にreactが付いているパッケージのアップデート情報を表示します。

$ ncu '/^react.*$/'

次は、先頭にreactが付いているパッケージ以外となります。

$ ncu -u '/^(?!react-).*$/'

これ以外のコマンドは、以下で確認してください。
https://github.com/raineorshine/npm-check-updates

アンインストール

以下のコマンドで、アンインストールを行います。

$ npm uninstall パッケージ        # ローカル
$ npm uninstall -g パッケージ -g # グローバル
# 以下のコマンドでもアンインストールできます。
$ npm remove パッケージ
$ npm rm パッケージ
$ npm r パッケージ
$ npm un パッケージ
$ npm unlink パッケージ

以下のオプションで、アンインストールの結果をpackage.jsonに反映させます。

$ npm uninstall --save パッケージ            # dependencies
$ npm uninstall --save-optional パッケージ   # optionalDependencies
$ npm uninstall --save-dev パッケージ        # devDependencies

ローカルインストールの罠

よく、情報通りにコマンドを入力しているのに、パッケージが動作しないと困っている人がいます。これは、結論から言うとローカルインストールを行ったため、パスが通っていないことが原因です。

パスを通す

PCでソフトウェアを使用する際、アイコンをクリックしていると思いますが、このアイコンは実行ファイルへのシンボリックリンク(ショートカット)で、実際は実行ファイルを実行していることになります。パス(PATH) とはこの実行ファイルの場所です。/usr/bin/C:\Program Files\Adobe\などと表現されるやつです。例えば、C:\Program Files\nodejs\node.exeなどと、実行ファイルの場所を指定する、もしくは、実行ファイルのあるディレクトリで、その実行ファイルを指定することでも、実行することが可能です。つまり、アイコンをクリックすると設定しているパスが実行されています。または、その場合にある実行ファイルを直接クリックすることで実行できます。

さて、例えばnpmでwebpackをグローバルインストールして、webpackと実行するとビルドされますが、これはwebpackの実行ファイルを実行しているからです。でもおかしいですよね、webpackのパスを指定していませんし、そのディレクトリにwebpackの実行ファイルもありません。ではなぜ、webpackが実行されるのでしょうか。これは、OSがそのコマンドの場所が指定されていないと、このパスを探せというリストを所持しているからです。webpackと実行するとOSはそのリストを見て、リストに記載されているパスのディレクトリにwebpackがないかを探して行きます。

手動でもこのリストにパスを設定できるのですが、Node.jsをインストールすると、自動で設定されています。このように、パスを設定することをPATHを通すといいます。つまり、Node.jsをインストールした時点でパスが通っていますので、そこにたどり着くことができ、webpackというコマンドだけで実行できるというわけです。

グローバルインストールがどこにインストールされているかは、次のコマンドで確認できます。

$ npm root -g

パッケージを探す際に検索するパスは、次のコマンドで確認できます。

$ node -e "console.log(global.module.paths)"

npmのインストール先は次のコマンドになり、環境変数にはこれが設定されています。この中にグローバルインストールした各パッケージの実行ファイルがいますので、それを直接実行することでも、そのパッケージを実行できます。

$ npm bin -g

ちなみに、例えばWindowsの場合だと、「システムのプロパティ > 環境変数」のPathで、設定の確認などができます。

グローバルインストールのモジュール

モジュールに関しては後で説明しますが、ようはソースコード内で読み込む(require()する)パッケージだと思ってください。例えば、次のようにグローバルインストールしたパッケージを読み込むとします。

const readlineSync = require('readline-sync');

すると、次のようにモジュールが見つかりませんとエラーとなります。

Error: Cannot find module 'readline-sync'

node.jsはNODE_PATHという環境変数を読みに行きます。したがって、NODE_PATHにこのパッケージを設定しなければいけません。

まずは、PATHとして設定するため、node_modulesの場所を調べましょう。

$ npm root -g

例えば、Windowsなら大抵次のようになると思います。

C:\Program Files\nodejs\node_modules

NODE_PATHにパスを追加するは、まずnodeコマンドでnodeモードにします。

$ node
Welcome to Node.js v14.18.1.      
Type ".help" for more information.
>

パッケージを探す際に検索するPATHを見てみましょう。恐らく先ほど確認したパスは表示されないはずです。

> global.module.paths

Ctrl + Dでnodeモードを抜けます。

以下のコマンドで、NODE_PATHにパスを追加します。

$ export NODE_PATH=<PATH>
# Windows
$ set NODE_PATH=<PATH>

再度確認すると、パスが追加されています。

> global.module.paths

とりあえずはこれでPATHが通りますが、できるだけローカルインストールしましょう。

ローカルパッケージの実行

問題なのはローカルインストールです。ローカルインストールは、そのプロジェクトのカレントディレクトリにあるnode_modulesディレクトリにインストールされます。実際に中を見てみてください。1~2個のパッケージをインストールしただけでも、その他多くのパッケージがインストールされています。これは、その依存関係にあるパッケージもインストールされているからです。それらパッケージは独自のpackage.jsonを持ち、パッケージを管理しあっています。そして、ローカルインストールは自動でPATHを通してくれません。つまり、直接PATHを指定しなければいけないのです。これらパッケージの実行ファイルは、各パッケージの.binディレクトリにありますので、それを指定します。例えば、webpackなら以下となります。

$ ./node_modules/webpack/bin/webpack.js

ただし、実行の度にこんな面倒くさいことはやっていられません。実は、node_modulesディレクトリには.binディレクトリというのがいまして、パッケージによっては実行ファイルのシンボリックリンクが置かれています。ちなみに、binコマンド./node_modules/.binへの絶対パスを返してくれます。

$ npm bin webpack

つまり、以下のコマンドでパッケージを実行できます。

$ ./node_modules/.bin/webpack

これで、PATHの最後だけパッケージごとに変えればよくなりました。ですが、これでめでたしではないですよね。こんな面倒くさいことやってられません。そこで、詳しくは後述しますが、package.jsonのscriptsフィールドに設定して、npm runで実行するなどをします。

package.json
"scripts": {
  "webpack": "./node_modules/.bin/webpack"
},
$ npm run webpack

これはかなりの解決策のようです。短いコマンドで済みます。ただし、その都度scriptsフィールドに設定するのは面倒で、管理も大変です。

では、環境変数にこの/node_modules/.bin/を追加すればどうでしょうか。それなら確かにwebpackコマンドで実行でき、別のパッケージをインストールした際も、そのコマンドだけで実行できます。ただし、プロジェクトの度に環境変数を設定することになり、OSの環境変数を汚してしまいます。

結局、どうやっても面倒な入力からは抜け出せそうにないのですが、そこに登場したのがnpxです。

npx

npxは、先ほど説明をした./node_modules/.binからコマンドを探して実行します。それだけでも便利なのでが、秀逸なのはそのコマンドがインストールされていなくても、それを専用の場所へ一時的にダウンロードをして実行し、処理が完了したら削除するというところです。つまり、環境を汚さずにコマンドを試すことができます。

$ npx webpack

これは、かなりベストな解決策のようです。ローカルインストールしたパッケージでも、コマンドの前にnpxと付けるだけでいいのですから。

なお、インストールされていないコマンドは一時的にダウンロードされると書きましたが、これには注意が必要です。npmレジストリから探すのですが、これは指定したコマンドと同じ名前のパッケージを探します。つまり、コマンドをパッケージ名が異なる場合は、探すことができません。その場合、-pオプションでパッケージ名を、-cオプションでコマンドをそれぞれ指定します。

$ npx -p express-generator -c "express -h"

逆に言えば、そのコマンドのパッケージがあればダウンロードして実行しますので、何だかヤバいような気もします……。

また、一時的なインストールはnpmレジストリからだけでなく、GitHubやGistなどからでも可能です。

$ npx https://gist.github.com/リポジトリ

ちなみにこれは、npm installでも可能です。

$ npm install https://gist.github.com/リポジトリ

これも、親切なふりをして悪意のあるURLを教えてごにょごにょと……、一方ではヤバいような気もします……。

ヤバそうと書いていますが、普通に使用するには問題ありませんので、臆せず使用しましょう。ですが、訳が分からないものをコピペして使用するのはやめましょう。

package.json

いよいよpackage.jsonの説明に入ります。package.jsonとはプロジェクトの構成、構造を示した設計書のようなものです。

ここで敏感な方は、このファイル名に違和感を感じたかもしれません。プロジェクトに関して書かれてあるファイルなら、project.jsonなのでは?package.jsonというなら、ダウンロードしたパッケージに設定するべきではと。実はこれには訳があります。実は、プロジェクトはパッケージでもあるのです。衝撃の事実ではありますが、その説明の前にとりあえずはpackage.jsonの各設定の説明をしますので、プロジェクトはパッケージでもあるということを念頭に置いて進めてください。

name

パッケージの名前です。214文字以下で好きな名前を付けることができます。

npmは、後述するパッケージレジストリからダウンロードをしているのですが、そこでは同じ名前のパッケージを登録することはできませんので、ネーミングは早い者勝ちです。そこで、@/の間にユーザーネーム(or 組織名)を入れて名前空間とし、その後ろにモジュール名をつけるscoped packages形式でネーミングすることも多いです。@types/react@babel/coreなどがこれに当たります。

version

パッケージのバージョンを設定します。バージョンの記述方法に決まりはありませんが、大抵がセマンティックバージョニング(Semver:Semantic Versioning) という規則に則って設定されます。これは3.12.1という風に、3つの数字をカンマ区切りで表現します。

  • メジャーナンバー
    一番左の番号はメジャーナンバーです。大きな変更、いわゆるメジャーアップデートがあれば数字が上がります。
  • マイナーナンバー
    真ん中の番号はマイナーナンバーです。新しい機能が追加されると数字が上がります。
  • パッチナンバー
    一番右の番号はパッチナンバーです。バグが修正されると数字が上がります。

つまり、最初は1.0.0などになります。バグなどが修正されると1.0.1となり、後方互換性を維持した新しい機能が追加されると1.1.0となります。そして、メジャーアップデートがあれば2.0.0となります。

description

パッケージの説明です。わかりやすい説明文を書いてください。npm searchで表示されますので、誰かがパッケージを探し理解するのに役立ちます。

private

パッケージを公開しない設定です。これをtrueに設定しておくと、パッケージは公開されません。誤って公開しないように、開発時はtrueにしておいた方がいいでしょう。

author

パッケージの管理者を設定します。次のような形式で設定することが多いです。

{"author": "Joji Shimaki <patipati@punch.com> (https://github.com/patipati-punch/)"}

license

ライセンス情報です。ライセンスに関しての詳しい説明は省きますが、MITなどと設定します。

homepage

パッケージのホームページのURLを設定します。

repository

パッケージのソースコードを管理している場所を設定します。

"repository": {
  "type": "git",
  "url": "https://github.com/npm/foo.git"
}

bugs

パッケージのバグに関するURLと、その報告をするメールサーバーを設定します。GitHubのIssueのURLを設定することが多いです。

"bugs": {
  "url": "https://github.com/patipati-punch/joji-shock/issues",
  "email": "patipati@punch.com"
}

main

mainは、そのパッケージをインストールする際に開始となるファイルを指定します。HTMLでいうとindex.htmlのようなものです。つまり、そのパッケージ自体を公開しないのなら、特に意味はありません。

bin

ローカルパッケージの実行で、./node_modules/.binにシンボリックリンクが置かれていると書きましたが、これはこのbinで設定した内容のシンボリックリンクが作成されることとなります。パッケージ自体を公開しないのなら、特に意味はありません。

例えば、xyzというパッケージに、次のようなbinの設定をしているとします。

"bin": {
  "foo": "./aa/bb.js"
},

このパッケージをインストールすると、./node_modules/.bin/fooというシンボリックリンクが作成されます。本体は./node_modules/xyz/aa/bb.jsにあります。なお、既に同名のシンボリックリンクが存在する場合は、上書きされます。

scripts

ローカルパッケージの実行でも使用しましたが、scriptsはコマンドのエイリアスを設定します。

例えば、既に説明済みですが次のような設定があるとします。

"scripts": {
  "webpack": "./node_modules/.bin/webpack",
},

次のようにnpm run ○○で実行します。

$ npm run webpack

実行すると、設定している./node_modules/.bin/webpackが実行されます。つまり、次を実行しているのと同じです。

$ ./node_modules/.bin/webpack

名前は自由に設定できるのですが、startは絶対ではありませんが通常プログラムを実行するコマンドを設定し、npm startだけで実行できます。また、testも絶対ではありませんが通常テストを実行するコマンドを設定し、npm testだけで実行できます。

また、名前の先頭にpreを付けると、preの付いていないコマンドを実行する際に、必ずpreの付いているコマンドが実行されます。例えば、次の設定があるとします。

"scripts": {
  "prefoo": "./node_modules/.bin/aa",
  "foo": "./node_modules/.bin/bb",
},

この設定でnpm run fooを実行するすると、その前にprefooが実行され、その後にfooが実行されます。逆にpostを付けるとその逆となり、npm run fooを実行すると、その後にpostfooが実行されます。

単にパッケージを実行するだけなら、npxを使用したほうが早いですが、オプションなどで長くなるコマンドは、scriptsで設定しておいた方がいいでしょう。また、大抵のパッケージには、最初から何らかの便利なscriptsが設定されていますので、調べてみましょう。

dependencies

dependenciesには、そのパッケージを実行するのに必要なパッケージを設定します。--saveオプションを付けてインストールすると、dependenciesに記述されます。

npm installで、package.jsonに設定されたパッケージをインストールすると説明しましたが、これはdependenciesと次に紹介するdevDependenciesに設定されたパッケージがインストールされます。

dependencies、またはdevDependenciesに設定されているパッケージを見ると、よくバージョンに^(キャレット)や~(チルダ)が付いています。これは、^が付いていると、メジャーナンバーは一致するがマイナーナンバーとパッチナンバーは設定しているもの以上という意味になり、~が付いていると、メジャーナンバーとマイナーナンバーは一致するがパッチナンバーは設定しているもの以上という意味になります。また、^~もなければ、全てのナンバーに一致するとなります。npm installを行うと、このルールに則ってインストールされます。なお、全てのナンバーに一致させたい場合、後述するpackage-lock.jsonの使用で実行できますので、この記述を修正する必要はありません。

では、なぜこのように面倒な設定を行うのでしょうか。普通に最新のパッケージをインストールすればいいようにも思えます。これは、開発現場ではよくあることなのですが、関連するシステムのバージョンを上げると、そのシステム自体が正常に機能しなくなることがあります。例えばメジャーアップデートなどで、処理の内容を変更されているなどです。そこで、比較的安心できるマイナーナンバーからインストールしたり、バグが修正のみのパッチナンバーからインストールしたりします。

devDependencies

devDependenciesには、そのパッケージを実行するのには関係なく、開発やテスト時に使用するパッケージを設定します。---save-devまたは-Dオプションを付けてインストールすると、devDependenciesに記述されます。

そのパッケージを公開し、それをインストールする際、つまり、npm install ***とした場合、devDependenciesに設定されたパッケージはインストールされません。一方、手元のpackage.jsonからnpm installする場合は、dependenciesとdevDependenciesに設定された両方のパッケージがインストールされます。これは、npm install --productionとオプションを付けることで、devDependenciesに設定されたパッケージはインストールされなくなります。

optionalDependencies

optionalDependenciesに設定したパッケージは、何らかの原因でインストールに失敗したとしても、他のインストールの処理が続行されます。つまり、なくても問題のないパッケージを設定します。ここで設定した場合、プログラム内でそのパッケージが無かったとしても問題ないようなコードを記述する必要があります。ちなみに、あまり設定することはありません。

package-lock.json

package-lock.jsonは、パッケージのバージョンをロックするファイルをです。パッケージをnpm installでインストールすると作成され、その後npm installでインストールする度に更新されます。これには、依存関係にあるものも含め、ダウンロードした全てのパッケージのバージョンとその情報が管理されています。

例えば、ある開発プロジェクトにおいて複数人で開発を進める場合、全員の開発環境を揃えるためパッケージのバージョンも全て同じにする必要があります。dependenciesで説明した^~を使えばよさそうですが、パッケージには依存関係にあるパッケージがありますので、それらのパッケージは最新のバージョンがインストールされてしまします。これでは、揃えているのは表面上だけで、その内部にあるパッケージのバージョンはバラバラになってしまいます。具体的に言うと、新たにインストールする人の方が、バージョンが新しくなる傾向にあります。

そこで使用するのが、package-lock.jsonです。package-lock.jsonには、ダウンロードした全てのパッケージのバージョンとその情報が管理されていますので、これを利用してインストールすることで、全てのパッケージを同じバージョンに合わせられそうです。

package-lock.jsonでインストールするには、npm installではなく次のコマンドを使用します。

$ npm ci

npm ciでインストールすると、設定されているバージョンのパッケージをインストールしますので、当然package-lock.jsonは更新されません。また、node_modules内のパッケージをすべて削除してからインストールするという徹底ぶりです。

パッケージとモジュール

パッケージとモジュールに関しては、本来ならば冒頭で説明すべきなのですが、基本的な事の割には非常にややこしく、package.jsonがわかっていないと理解しにくいため、ここで説明させていただきます。

npmでのモジュールとは、node_modulesディレクトリに存在し、require() 関数で読み込んで使用されるものです。単なる1つのJavaScriptのファイルであっても、これに該当すればモジュールとなります。つまり、大抵はpackage.jsonで管理され、mainフィールドを持っているパッケージとなり、また、その内部で読み込まれて使用されているJavaScriptのファイルも、モジュールとなります。

npmでのパッケージとは、package.jsonで管理しているプロジェクトのことを言います。例えば、プロジェクトを作成し、package.jsonを設定してプログラムを作成すると、それはパッケージと言えます。package.jsonで、プロジェクトはパッケージでもあると説明しているのはこのためです。

このパッケージを別のパッケージにインストールするとします。その際、node_modulesの中にインストールされるのですが、そのインストールされたものが、そのパッケージで読み込まれて使用されるものなら、それはモジュールとなります。また、そうではなく、そのパッケージ内で、コマンドラインなどで単体に使用する場合、それはパッケージとなります。

例えば、webpackなどはそれ単体で使用するのでパッケージとなり、そのwebpackで読み込んで使用するプラグインはモジュールといえます。

非常にややこしいのでまとめると、

  • 最初は全てパッケージです。
  • パッケージを他のパッケージインストールして、その使われ方によってパッケージとモジュールに分かれます。
  • 単体で使用するならパッケージ。読み込まれて使用されるならモジュール。

また、モジュールの中でそのモジュールなどに読み込まれて使用されているJavaScriptファイルがあると、それもモジュールと言えます。つまり、node_modulesの中にある、読み込まれてることによって機能するファイルは、全てモジュールです。

パッケージの公開

さて、これまで説明したように、npmでパッケージをダウンロードして使用するのですが、このパッケージは一体どこからダウンロードをしているのでしょうか。これは、npmjs.comというnpm Incが管理しているパッケージレジストリからダウンロードをしています。ややこしいのですが、このサービス自体もnpmという名前です。ここでは混乱しないために、パッケージレジストリの方をnpmjsと呼びます。

npmjsは、世界中の誰もがパッケージを登録することができます。つまり、あなたもパッケージを登録することが可能です。そのため、膨大な数のパッケージが管理されているのですが、脆弱性のある、または悪意のあるパッケージがないとは言えません。どこまでの粒度で精査するかは、その企業やプロジェクトによると思いますが、ある程度はその安全性を確認するべきです。これが信用できず、npmを使用しない企業もあります。その依存関係にあるパッケージも調べていたらきりがないですからね。

それでは、npmjsでパッケージを公開してみましょう。

パッケージの開発

では、簡単なパッケージを作成してみましょう。

まずは、package.jsonを作成します。

{
  "name": "motorcycles",
  "version": "1.0.0",
  "description": "Returns the name of your favorite motorcycle.",
  "main": "index.js",
  "scripts": {
    "choose": "node index.js"
  },
  "author": "ANTEZ(https://zenn.dev/antez)",
  "license": "MIT",
  "devDependencies": {
    "webpack": "^5.68.0",
    "webpack-cli": "^4.9.2"
  },
  "dependencies": {
    "readline-sync": "^1.4.10"
  }
}

webpackは特に使用しませんが、devDependenciesの挙動を確認するために設定しています。このパッケージの動作にはreadline-syncというパッケージを使用しますので、npm i --save readline-syncでインストールして、dependenciesに登録してください。

作成するパッケージは何でもいいのですが、とりあえずカレントディレクトリにindex.jsを次のように作成しました。

index.js
const readlineSync = require('readline-sync');

const motorcycles = ['CBX400F', 'XJ400', 'Z400FX', 'GS400'];
const index       = readlineSync.keyInSelect(motorcycles, 'Choose your favorite motorcycle.');

console.log(`You like ${motorcycles[index]}`);

コンソールでバイクを選んで表示するだけの、単純なパッケージです。実際に動かしてみたい方は、package.jsonに記載しているようにnode index.jsnpm run chooseで実行してください。ちなみに、チョイスしている単車がアレですが、僕は旧○會ではありません。

アカウント作成

npmjsにパッケージを公開するには、アカウントの作成が必要です。

各入力を行い、Create an Accountをクリックしてください。

登録したメールアドレスにワンタイムパスワードが送られますので入力してください。

これで、アカウントの作成が管理しました。

パッケージの公開

パッケージのディレクトリから、npmjsにログインします。ユーザー名、パスワード、メールアドレスを入力すると、ワンタイムパスワードが送らてくるので、入力してください。

$ npm login
Username: <ユーザー名>
Password: <パスワード>
Email: (this IS public)  <メールアドレス>
npm notice Please check your email for a one-time password (OTP)
Enter one-time password from your authenticator app: <ワンタイムパスワード>
Logged in as **** on https://registry.npmjs.org/.

npm publishコマンドで公開しましょう。

$ npm publish
npm notice 
npm notice package: motorcycles@1.0.0
npm notice === Tarball Contents ===
npm notice 259B index.js
npm notice 48B  src/index.js
npm notice 50B  src/modules/module.js
npm notice 391B package.json
npm notice === Tarball Details === 
npm notice name:          motorcycles
npm notice version:       1.0.0
npm notice package size:  609 B
npm notice unpacked size: 748 B
npm notice shasum:        14d21f8b27b0eb608b05d297f6c7a8ba555fa326
npm notice integrity:     sha512-aII6oCp6ByOF1[...]ydy0/3LHZRLGg==
npm notice total files:   4
npm notice
+ motorcycles@1.0.0

これで、パッケージの公開は完了しました。ログアウトしましょう。

$ npm logout

公開の確認

本当に公開されているかを確認してみましょう。npmjsのページでも確認できいますが、ここはコマンドで確認してみます。

$ npm search motorcycles
NAME                      | DESCRIPTION          | AUTHOR          | DATE       | VERSION  | KEYWORDS
motorcycles               | Returns the name of… | =*****          | 2022-02-07 | 1.0.0    |

公開されているのが確認できます。更に情報を確認してみます。

$ npm info motorcycles

motorcycles@1.0.0 | MIT | deps: 1 | versions: 1
Returns the name of your favorite motorcycle.

dist
.tarball: https://registry.npmjs.org/motorcycles/-/motorcycles-1.0.0.tgz
.shasum: 14d21f8b27b0eb608b05d297f6c7a8ba555fa326
.integrity: sha512-aII6oCp6ByOF1/mnRe5CN4ThfJQO8LC1QIs2G939xDCH+FfSZ0fM+nOz/0eeVKCmb9VQ2QFH6ydy0/3LHZRLGg==
.unpackedSize: 748 B

dependencies:
readline-sync: ^1.4.10

maintainers:
- antez <****@gmail.com>

dist-tags:
latest: 1.0.0

published 12 minutes ago by **** <****@gmail.com>

インストール

無事、公開が確認できたとことで、実際にインストールをしてみましょう。新たにディレクトリを作成して、インストールをしてください。

$ npm init y
……
$ npm install motorcycles
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN test@1.0.0 No description
npm WARN test@1.0.0 No repository field.

+ motorcycles@1.0.0
added 2 packages from 2 contributors and audited 2 packages in 2.461s
found 0 vulnerabilities

無事、インストールできました。では、package.jsonを見てみましょう。

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

devDependenciesに登録したwebpackとwebpack-cliはインストールされておらず、dependenciesにmotorcyclesが設定されています。node_modulesディレクトリも確認して見てください。自分で作成したパッケージがダウンロードされています。

アップデート

では、アップデートをしてみましょう。本来はもちろんソースコードを更新するのですが、とりあえずpackage.jsonのversionだけ上げます。

"version": "1.0.1",

package.jsonのversionを上げずにアップロードすると、エラーになるので注意してください。アップデートは公開時と同じようにnpm loginでログインして、npm publishでアップロードします。

$ npm login
……
$ npm publish
……
$ npm logout
$ npm search motorcycles
NAME                      | DESCRIPTION          | AUTHOR          | DATE       | VERSION  | KEYWORDS
motorcycles               | Returns the name of… | =*****          | 2022-02-07 | 1.0.1    |

パッケージの削除

ログインをして、次のコマンドでパッケージを削除できます。

$ npm unpublish パッケージ名

ただし、削除したパッケージ名 + バージョンは、2度と登録することができませんので、ご注意ください。

最後に

長文になってしましましたが、最後までお読みいただき、ありがとうございました。
よければ業務ができる中級者になるためのJavaScript入門(文法編)DOM編を公開していますので、無料公開だけでも読んでください。

ちなみに、最近ではWordPressでもnpmを使ったりします。WordPressが今一つわからないエンジニアの方は、よければ次を参考にしてください。
https://zenn.dev/antez/articles/8e576daf822a84

Discussion

Satoshi BandoSatoshi Bando

windows環境で nvm use するには管理者権限が必要の様でした。
使用したのはnvm-windowsの1.1.9です。

nvm use 16.14.0
exit status 1: You do not have sufficient privilege to perform this operation.

(エラーメッセージですぐにわかるので困っている人も少ないかもしれませんが😅)

ANTEZANTEZ

確かに、コマンドラインツールに管理者権限が必要ですね。ありがとうございました!