📚

『ハンズオンNode.js』package.jsonまとめ

2023/08/23に公開

npmを構成するレジストリ、CLI、Webサイト

  • npmのCLIでは、パッケージをダウンロードしたり公開したりする。
  • npmのレジストリは、JavaScriptパッケージやコードのメタデータが保存されている公開データベース。
  • npmのWebサイトは、公開されているパッケージを検索したり、詳細情報の参照、npmアカウントの設定などを行うもの。
    • npmアカウントは、パッケージのダウンロードのみなら必要なく、公開する際に必要となる。

package.jsonのフィールド

name

  • レジストリに公開する場合は、Node.jsコアのモジュール名や既存のパッケージ名と重複してはならない。
  • 外部からパッケージに依存する際には名前の指定が必要。
  • パッケージ名には、名前空間(=スコープ)を付けることができる。
    • パッケージ群をグループとしてまとめることができる。
    • 「@スコープ名/」というプレフィックスがパッケージ名につく。
    • JavaScriptコンパイラのBabelの例
      • @babel/core, @babel/parser, @babel/generator

version

  • レジストリに公開する場合は必須。
  • セマンティックバージョニングのv2.0.0の仕様を満たす書式であること。
    • 「1.2.3」というバージョンを例にすると...
      • メジャーバージョン: 1
        • 以前のバージョンと互換性のない(破壊的な)変更が加えられたとき
      • マイナーバージョン: 2
        • 互換性を保ったまま機能を追加したとき
      • パッチバージョン: 3
        • 互換性を保ったまま不具合を修正したとき
  • ただし、メジャーバージョンが0の間は、メジャーバージョンの変更なしに破壊的な変更を加えることが許容される。
    • 開発初期の不安定な段階でメジャーバージョンが無闇に上がらないようにするため。
    • プレリリースバージョンとして「1.0.0-alpha.1」のような表現もある。
  • バージョン書き換えはpackage.jsonから行うこともできるが、npmコマンドでも操作ができる。
    • 直接バージョンを指定する方法
      $ npm version 1.1.0
      
    • バージョンを上げる部分(メジャーorマイナーorパッチ)を指定する方法
      $ npm version patch
      

description, keywords

  • パッケージの説明文とキーワードの指定。
  • パッケージを検索するのに使われる。
  • CLIからもパッケージの検索ができる。
    $ npm search {{検索語}}
    

main

  • パッケージのエントリポイントとなるJavaScriptファイルを指定する。
  • モジュールをrequireする際には、このmainに指定したファイルでmodule.exportsしたものが返される。

author, contributors

  • 開発者の情報を記述する。
    • 開発者が1人の場合はauthor
    • 開発者が複数人の場合はcontributors
  • 記述内容
    • name(必須)
    • email(省略可)
    • url(省略可)

license, private

  • パッケージ利用にあたってのライセンスを指定する。
  • 第三者による利用を許可しない場合は、"license": "UNLICENSED" を指定する。
    • さらにパッケージ自体を非公開にするなら、 "private": true を併せて指定する。
      • これを指定した場合は、npm publish コマンドで公開しようとした場合に失敗する。(意図せぬ公開を防止)

dependencies

  • npm install {{パッケージ名}} したものが追加される。
  • dependenciesの例
    {
      // ...
      "dependencies": {
        "rimraf": "^2.6.3"
      },
      // ...
    }
    
  • npm install {{パッケージ名}}@2.0.0 のようにバージョンを明示的に指定しない限りは、最新のバージョンになる。
  • ^: 指定されたバージョンと互換性のあるバージョンに依存することを示す。
    • ^2.6.3の場合
      • 2.6.3以上かつ3.0.0未満
    • ^0.6.3の場合
      • 0.6.3以上かつ0.7.0未満
    • ^0.0.3の場合
      • 0.0.3以上かつ0.0.4未満
  • "rimraf": "2.6.3" のように範囲を指定しなかった場合
    • 厳密に一致するバージョンのパッケージにしか依存しなくなる。

依存ツリー(依存関係の構造)の可視化

$ npm ls --all
  • --allオプションをつけない場合、1階層目のみが表示される。
  • 表示されたパッケージの中には、dedupedと付いているものもある。
    once@1.4.0 deduped
    
    • これは、重複が排除されたことを表す。
    • 複数のパッケージで同じパッケージの依存がありバージョンが共有できる場合は、1つのバージョンのパッケージのみがインストールされる。
      • 一方で、同一パッケージで異なるバージョンが必要な場合は、それぞれのバージョンのパッケージがインストールされる。

package-lock.jsonの役割

  • 動作確認したアプリケーションの動作を保証するために、完全にそのままのパッケージ群(完全に一致するバージョン)でリリースするための役割を持つ。
  • 実際にインストールしたパッケージの情報が全て記述される。
    • 基本的に手で編集することはない。

npm cinpm install

  • npm ci: node_modulesを削除してからインストールする。
    • CIでの利用を想定したコマンド。
  • npm install: 未インストールのものだけをインストールする。

gitとnpmレジストリの区別

  • git: node_modules.gitignore ファイルで除外する。
  • npmレジストリ: node_modulespackage-lock.jsonは特別な設定なしに公開対象から除外される。
    • 依存バージョンに幅のある状態が望ましく、package-lock.jsonのピンポイントな依存バージョンの指定は不要であるため。

パッケージのアンインストール

$ npm uninstall {{パッケージ名}}

devDependencies

  • 実行時に動作する上では必要ないパッケージをインストールした場合に追加される。
    • テストやビルドのために必要なパッケージ
    • パッケージとして用いられる時に、ここに指定したパッケージは依存パッケージとしてインストールされない。
  • 追加するコマンド
    $ npm install --save-dev {{パッケージ名}}
    # 省略形
    $ npm i -D {{パッケージ名}}
    

peerDependencies

別のパッケージと一緒に使うことを前提としたパッケージに対してpeerDependenciesが使われる。
典型的には、何らかのライブラリのプラグインをnpmパッケージとして開発するケース。

  • dependenciesでは、プラグインが、あるライブラリのどのバージョン向けなのかを宣言できない。
  • plugin-alib@1.0.0に依存しており、plugin-blib@2.0.0に依存している場合
    • これらのプラグインをインストールすると、node_modules内では、以下のような構造になる。
      • lib@2.0.0
      • plugin-a
        • lib@1.0.0
      • plugin-b
    • 利用側でrequire('lib')した際にロードされるのは、バージョンが常に2.0.0になる。
    • これにplugin-aを適用することは、そのプラグインの意図に反する。
  • 前提として、異なるバージョン向けのプラグインを併用すること自体が誤り であるため、npm install時点で検出できた方が望ましい。
  • peerDependenciesは、これを実現するもの。
  • npmでは、パッケージのインストール時に、peerDependenciesに指定されたパッケージをインストールする。
    • この時、全てのパッケージ要件を満たす状態でインストールできない場合がある(plugin-aplugin-bを同時インストールしようとした時)
      • この場合、npm installでエラーを出力して失敗する。

scripts

  • pre, postというプレフィックス付きのスクリプトを書くと、特定のスクリプトの前後に処理を追加できる。
      "scripts": {
        "precreate-dir": "echo \"ディレクトリを作成します。\"",
        "create-dir": "mkdir foo/bar",
        "postcreate-dir": "echo \"ディレクトリを作成しました。\"",
        // ...(省略)
      }
    
  • イベントをフックして処理を実行するためのスクリプトも記述できる。
    • パッケージの公開前後
    • npm install
    • など

bin

  • パッケージが提供するコマンドを定義する。
      "bin": {
        "greeting": "./greeting.js"
      },
    
  • 提供するコマンドが1つであり、かつ名前がパッケージ名と同じ場合、次のように記述することもできる。
      "bin": "./bin.js"
    
  • scriptsとbinの違い
    • scriptsは、内部で実行するときに用いるもの
    • binは、外部のパッケージで実行する時に用いられるもの
  • scriptsでコマンドが使えるようになっている仕組み
    • binが指定されたパッケージをnpm installする。
    • node_modules/.binディレクトリにbinコマンドと同じ名前の実行可能ファイルへの「シンボリックリンク」が作られる。
    • scriptsのスクリプトを実行する際に、node_modules/.binに対してPATHが通される。
    • コマンドが使えるようになる。

実行可能ファイルを実装してみる

  • 実行可能ファイルの実体はJavaScriptファイル。
  • コマンドがNode.js上で実行されるように、ファイルの先頭に以下を記述する必要がある。
    • (npm-package/greeting.js)
      #!/usr/bin/env node
      'use strict'
      
      // ...(省略)
      
  • 別のディレクトリで上記実装したファイルのパスをnpm installで指定すれば、パッケージとしてインストールができる。
    $ npm init -y
    $ npm install ../npm-package
    
  • package.jsonscriptsに、インストールしたパッケージのスクリプトを書けば、実行することができる。
    • npxを用いて、scriptsに書かずに実行することも可能。

repository

  • そのパッケージを管理しているリポジトリの情報を記載する。

engines

  • パッケージの動作に必要なNode.js, npmのバージョンを指定する。
    • ただし、デフォルトではこの設定自体に効果はない。バージョンを強制させるには、別途設定する(p.392)

exports

  • パッケージ内のモジュールのうち、どれを外部に公開するか明示的に宣言するもの。
  • これを行うことで、以下を防ぐことができる。
    • 外部に公開することを意図していないモジュールがrequire()できてしまう。
    • パッケージの利用者が、パッケージ内のディレクトリに依存したrequire()を行うことで、ディレクトリ構成の変更が破壊的変更につながる。
  • エントリポイントのみを公開したい場合の記述例
    {
      "main": "index.js",
      "exports": {
        ".": "./index.js"
      }
    }
    
  • 公開したいのがエントリポイントだけなら、次のように短縮した書き方もできる。
    {
      "exports": "./index.js"
    }
    
  • 注意点: mainフィールドと異なり、モジュールのパス指定を「./」から始める必要がある。
  • mainフィールド+他に公開したいモジュールがある場合の記述例
    {
      "main": "index.js",
      "exports": {
        ".": "./index.js",
        "./sub": "./lib/sub.js" // 実際にrequire()するときのパスは任意に指定することができる。
      }
    }
    
  • require()のパスが任意に指定できるため、パッケージ側でディレクトリ構成の変更があった場合も、利用者には影響がない。

type

  • パッケージ内のJavaScriptファイルを、CommonJSモジュール or ESモジュールどちらで扱うかを指定する。

カスタムフィールド

  • 実際の例として、Jestを用いる場合、jestというフィールドでテストの設定ができる。
    {
      "jest": {
        "testMatch": ["**/test/jest/**/*.js"],
        // ...(省略)
      }
    }
    

npmパッケージに含まれるファイル、含まれないファイル

  • いくつかのファイルは、拒否・許可がデフォルトで決まっている。
  • 任意に拒否したいファイルがある場合は、.npmignoreファイルに記述する
    • このファイルが存在しなければ、.gitignoreが流用される。
  • 任意に許可したいファイルがある場合は、package.jsonfilesフィールドに指定する。

npmパッケージのグローバルインストールとnpx

  • npm install -g オプション付きで実行すると、npmパッケージがグローバルにインストールされる。
  • グローバルにインストールされたパッケージのコマンドは、グローバルにPATHが通った状態になる。
    • このため、scriptsnpxを介さずに直接実行が可能。
  • npm CLIやnpxコマンドが使えるようになっている(※)のは、npmというパッケージがグローバルインストールされているため。
    • ※Node.jsのインストールに伴って使えるようになる。
  • 公式で、可能な限りグローバルインポートを使わないことが推奨されている。
    • グローバルインストールを使うと、パッケージへの依存に関する情報をコードで共有できなくなる。
    • このため、開発者間で同じツールを使っているつもりでも、バージョンの違いにより挙動が異なる可能性がある。
  • プロジェクト外でnpmパッケージが提供するコマンドを実行したい場合は、npxを使えば良い。
    • 1回実行するとインストールされたパッケージのキャッシュが保存されるため、2回目以降の実行は初回よりも高速に動作する。

参考文献

この記事は以下の情報を参考にして執筆しました。

https://www.oreilly.co.jp/books/9784873119236/


オプティマインドでは「多様性が進んだ世の中でも、全ての人に物が届く世界を持続可能にする」という物流業界の壮大な社会課題を解決すべく、 一緒に働く仲間を大募集中です。 少しでも興味が湧いた方は是非お気軽にカジュアル面談をお申し込みください!

https://recruit.optimind.tech/

Discussion