TypeScript で書いたプログラムを npm パッケージとして配布する手順を書いていく。
まだ npm パッケージの配布をしたことがない人を、想定読者としている。
よりよい書き方、詳細な設定、は措いておき、まずは最低限の要件を満たすものを作り上げる。
今回の「最低限の要件」は以下。
npm install
やyarn add
でインストールできる
import
でもrequire
でもインポートすることが出来る
- 型定義ファイルを同梱し、TypeScript アプリにもスムーズに導入できる
require
(CommonJS
)にも対応させるかどうかはライブラリの性質によって異なると思うが、今回は対応する。
npm パッケージに限らず、粗削りでいいから最初から最後まで動くものをまずは作り、あとから必要に応じて勉強や調査をすればいいと思っている(セキュリティやコンプライアンスに関わることは除く)。今回もその方針でいく。
この記事で利用しているライブラリのバージョンは以下。
npm@6.2.0
typescript@3.5.2
@types/node@12.0.10
dayjs@1.8.14
TypeScript の設定とプログラムの作成
何はともあれプログラムを作らないと、配布も何もない。
今回は、YYYY-MM-DD
形式の文字列を渡すと、その日付の曜日を英語で返すプログラムを作る。
といっても、主な処理はライブラリに任せてしまい、自分ではほとんどコードを書かないが。
まず、パッケージの名前を決めておく。既に存在するパッケージの名前は利用できないので、使いたい名前が既に使われていないかどうか公式サイトで検索して確かめておく。
今回は実際には公開しないので何でもよいが、day-of-week
にする。
次に TypeScript をインストール。
$ yarn add typescript
次に TypeScript の設定ファイルであるtsconfig.json
を作成。
あとで追加する項目もあるが、取り敢えずは以下の内容で進める。
{
"compilerOptions": {
"target": "es5",
"lib": ["es2018"],
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"outDir": "./dist/",
"sourceMap": true
},
"include": [
"src"
]
}
TypeScript については自分も初心者なので、詳しい説明は避ける。
src/
の中身をコンパイルしてdist/
に出力すること、出力後のコードはes5
で動くものであること。
それさえ把握しておけば、この記事を読み進めるのに問題はないはず。
dist/
はコンパイルしたコードを置くだけなので、.gitignore
に追加して Git の管理から外しておく。
今回は Node.js 環境でも使えるライブラリにするので、型定義ファイル@types/node
をインストールしておく。
$ yarn add @types/node
他に、依存ライブラリとしてdayjs
をインストール。
$ yarn add -D dayjs
実は今回のケースでは-D
オプションは付けてはいけないのだが、説明の都合上、敢えてこうしている。後で修正するので、そのときに説明する。
下準備が出来たのでsrc/
以下にコードを書いていく。
以下の内容でsrc/index.ts
を作る。
import dayjs from 'dayjs';
const DayOfWeek = (date: string): string => dayjs(date).format('dddd');
export default DayOfWeek;
これで完成したので、以降は、パッケージとしての形を整えるための作業になる。
まずはコンパイル。
$ yarn run tsc
でコンパイルできるので実行すると、dist/index.js
とdist/index.js.map
が生成されている。
この内容でも動作はするのだが、せっかく TypeScript で作ったのだから、型定義ファイルも同梱させておきたい。そうすることで、TypeScript アプリの開発者がこのパッケージをインポートしたときに、型情報も自動的にインポートされるようになる。
tsconfig.json
にdeclaration
を追加すると、コンパイル時に型定義ファイルも作られるようになる。
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -6,6 +6,7 @@
"strict": true,
"esModuleInterop": true,
"outDir": "./dist/",
+ "declaration": true,
"sourceMap": true
},
"include": [
再度コンパイルすると、以下の内容のdist/index.d.ts
も生成された。
declare const DayOfWeek: (date: string) => string;
export default DayOfWeek;
これで、配布したいプログラムをdist/
に生成できるようになった。
あとは、それを npm パッケージとして配布するための作業をすればよい。
package.json
パッケージの配布においては、package.json
の記述内容が重要になる。
ライブラリをインストールした時点でpackage.json
作成され、以下の内容になっているはず。
{
"dependencies": {
"@types/node": "^12.0.10",
"typescript": "^3.5.2"
},
"devDependencies": {
"dayjs": "^1.8.14"
}
}
ここに、必要な項目を追加していく。
name
パッケージの名前。前述の通り、既存のパッケージと被ってはいけない。
version
パッケージのバージョン。name
とversion
の組み合わせで、パッケージが一意に特定される。
license
パッケージのライセンスの種類を書く。
main
ここで指定したファイルが、パッケージをインポートしたときに読み込まれることになる。
types
main
で指定したファイルに対応する型定義ファイルを、このフィールドに指定する。
files
パッケージとして配布したいファイルやディレクトリを、ホワイトリスト形式で記述していく。
今回の例だと、dist/
を指定する。そうすることで、パッケージで配布する必要のないsrc/
などを除外することが出来る。
package.json
など一部のファイルは、files
で指定した内容に影響を受けない。
https://docs.npmjs.com/files/package.json#files
上記以外にも様々なフィールドがあり、公式ドキュメントで確認できる。
docs.npmjs.com
完成形は以下。
{
"name": "day-of-week",
"version": "1.0.0",
"license": "MIT",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"dependencies": {
"@types/node": "^12.0.10",
"typescript": "^3.5.2"
},
"devDependencies": {
"dayjs": "^1.8.14"
}
}
これでパッケージとして配布できるようになったので、次は動作確認を行う。
動作確認
動作確認のために実際にパッケージを配信するわけにはいかないので、$ npm pack
を使う。
このコマンドを使うことで、実際に配信することなくローカル環境で、パッケージとして問題なく機能するかどうか確認することが出来る。
早速実行してみる。
$ npm pack
npm notice
npm notice 📦 day-of-week@1.0.0
npm notice === Tarball Contents ===
npm notice 282B package.json
npm notice 77B dist/index.d.ts
npm notice 409B dist/index.js
npm notice 243B dist/index.js.map
npm notice === Tarball Details ===
npm notice name: day-of-week
npm notice version: 1.0.0
npm notice filename: day-of-week-1.0.0.tgz
npm notice package size: 735 B
npm notice unpacked size: 1.0 kB
npm notice shasum: ec082bdcf157f89045c19737ec853ea3ddc47dc2
npm notice integrity: sha512-SY1YfL2l+eG67[...]K1JoGgnMn/sHQ==
npm notice total files: 4
npm notice
day-of-week-1.0.0.tgz
day-of-week-1.0.0.tgz
というファイルが作成され、package.json
の他、files
で指定したdist/
が含まれていることが分かる。
この.tgz
ファイルをパッケージとして指定してインストールすることで、動作確認が出来る。
何か適当に新しいプロジェクトを作り、試してみる。
$ yarn add day-of-week-1.0.0.tgzのパスを指定
そして、動作確認用のプログラムをindex.js
として書く。
const DayOfWeek = require('day-of-week').default;
console.log(DayOfWeek('2019-06-28'));
満を持して$ node index.js
を実行、すると、エラーになる。
$ node index.js
internal/modules/cjs/loader.js:583
throw err;
^
Error: Cannot find module 'dayjs'
dayjs
がないと怒られるのでyarn.lock
を確認してみると、day-of-week
の他にtypescript
と@types/node
は入っているが、dayjs
が入っていない。
依存関係について
これは、dayjs
をインストールする際に$ yarn add -D dayjs
としてしまった(この説明をするためにわざとそうしたのだが)のが原因。
パッケージをインストールしたときに一緒にインストールされるのはdependencies
に書かれているライブラリのみで、devDependencies
はインストールされない。
なので、dayjs
をdependencies
にする。
$ yarn remove dayjs
$ yarn add dayjs
これで修正完了。
package.json
のversion
を1.0.1
にした上で、$ npm pack
を行う。
day-of-week-1.0.1.tgz
が生成されるので、それを、動作確認用のプロジェクトでインストールする。
改めて$ node index.js
を実行すると、今度は正しく動いた。
$ node index.js
Friday
このように、開発するパッケージの依存関係には注意する必要がある。
依存関係ついては TypeScript の公式ドキュメント にも記述がある。
簡単に動作確認できるのでrequire
を使ったが、import
することも出来るし、TypeScript なら型チェックも行われる。
import DayOfWeek from 'day-of-week';
console.log(DayOfWeek('2019-06-28'));
const result: number = DayOfWeek('2019-06-28');
公開作業
無事にパッケージを作れたので、ここから先は、公開(パブリッシュ)のための作業を行っていく。
この手順通りに作業すると実際に公開されてしまうので、注意すること。
npm scripts
にprepublishOnly
を設定する。
このスクリプトは、パブリッシュの前に必ず実行される。なので、ビルド作業などを設定しておくとよい。
package.json
に以下の内容を追記。
"scripts": {
"prepublishOnly": ビルド作業など
},
ただ、prepublishOnly
には類似のコマンドが複数あるうえ、npm cli
のバージョンによって挙動が異なるらしい。
実際に使う際にはよく確認しておく。
参考:npm の prepublish と prepare の変遷 - Qiita
パブリッシュのためには npm のアカウントが必要なので、まだ持っていない場合は作成する。
Sign Up - npm
$ npm login
でログインする。
ログインしているかどうかは、$ npm whoami
で確認できる。ユーザー名が表示されたら、そのユーザーでログインしている。
$ npm whoami
ユーザー名
あとは、パッケージのルートディレクトリで$ npm publish
を実行すれば、prepublishOnly
のあとに、パブリッシュが実行される。
$ npm install
や$ yarn add
でそのパッケージをインストールできれば成功。
パブリッシュが終わったあとは$ npm logout
で忘れずにログアウトしておく。
まとめ
npm パッケージを公開すること自体はすごく簡単に出来る。
t-wada さんも、細かすぎて伝わらない package.json 小ネタ三選という記事でこう言っている。
Node.js のエコシステムの豊穣さは、モジュール利用者からモジュール作成者に進むためのハードルが低いことで成り立っています。このエントリを読んだ皆さんも、ぜひ npm author になってみてください。そのハードルを越えるのは、意外と難しくありません。
ということで、自分以外の誰かにも役立ちそうなプログラムを書けたときは、積極的にパッケージとして公開していこう。
参考資料