📦

npm パッケージを TypeScript で自作して公開、別モジュールからインストールして使うまでの手順

2022/03/08に公開

この記事で目指すこと

  • TypeScript を用いて npm パッケージの実装を行えること
  • パッケージを公開するにあたって、適切な設定がなされていること
  • 実際に npm にパッケージが公開されて npm i <package-name> でインストールができるようになっていること

やらないこと

  • パッケージを非公開にする設定

前提

  • Node.js が利用できること
    • ※ 今回主に用いる npm は Node.js に標準で付いてきます

プロジェクト毎での切り替えなどの利便性から自分は nodenv を使用しています。
nodenv のインストールや初期化手順などは以下が参考になります。
https://zenn.dev/donchan922/articles/b08a66cf3cbbc5

手順

  1. npm アカウントを作成する
  2. パッケージを初期化する
  3. サンプルモジュールを実装する
  4. TypeScriptを設定する
  5. パッケージ公開に関する設定を行う
  6. パッケージを公開する
  7. インストールして使ってみる

npm アカウントを作成する

npm コマンドを利用してCLIで作成可能です。
以下のようにインタラクティブにデータ入れていけば作れます。

$ npm adduser
npm notice Log in on https://registry.npmjs.org/
Username: ttogane
Password: 
Email: (this IS public) ****
npm notice Please check your email for a one-time password (OTP)
Enter one-time password: ****
Logged in as ttogane on https://registry.npmjs.org/.

セッションが切れた場合は npm login で再度ログインを試行できます。

パッケージを初期化する

npm init でパッケージのメタデータ(パッケージ本体の情報や、それが依存する他パッケージのリストなど)を管理するための package.json を作成できます。

※ モジュールにある程度一般的な名前をつける場合、名前空間がないと命名が衝突する場合(すでに登録されていて使えない)が多いので、ここではあらかじめ --scope オプションをつけます。

$ cd /path/to/somewhere/work_dir  # どこか適当な作業ディレクトリに移動しておく
$ mkdir npm-package-example  # ※ ディレクトリ名はパッケージ名の既定値に用いられる
$ cd $_
$ npm init --scope=ttogane
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (@ttogane/npm-package-example)   # 空Enter
version: (1.0.0) 0.1.0  # セマンティックバージョニングで指定(後述)
description: a sample of implemantation of a npm package  # 適当に入力
entry point: (index.js)  # 一番最初に実行されるファイル。空Enter(後で変更)
test command:  # package.json の scripts のtestに設定されるコマンド。空Enter
git repository:  # 今回は空Enter
keywords:  # 空Enter
author: Taro Togane
license: (ISC) MIT  # 今回は仮にMITに
About to write to /path/to/somewhere/work_dir/greeter/package.json:

{
  "name": "@ttogane/npm-package-example",
  "version": "0.1.0",
  "description": "a sample of implemantation of a npm package",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Taro Togane",
  "license": "MIT"
}

Is this OK? (yes)  # 空Enter
  • 後から package.json を直接編集することで修正可能です
  • npm init -y で対話形式での入力をスキップ可能です

バージョンについて

セマンティックバージョニングの仕様を満たす書式である必要があります。
メジャーバージョンは以前のバージョンと互換性のない変更(破壊的変更)が加えられたときにアップします。
ただ、それだと開発初期にメジャーバージョンがどんどん上がっていってしまうので、メジャーバージョンが0の間はメジャーバージョンの更新なしに破壊的変更を行うことが許されます。
その規約を利用して最初はバージョン0.1.0から始めるプラクティスがあるようですが、ここではそれに則っています。

サンプルモジュールを実装する

今回はソースコード(コンパイル前のコード)は src/ に配置することにして、ここに適当なクラスを1つ作ってそれをindexでエクスポートしておきます。

  • src/Greeter.ts
  • src/index.ts
$ mkdir src
$ touch src/Greeter.ts
$ touch src/index.ts
src/Greeter.ts
export class Greeter {
  greet(to: string): void {
    console.log(`Hello ${to}`)
  }
}
index.ts
export * from "./Greeter";

TypeScriptを設定する

下記コマンドでインストールと初期化します。

$ npm install typescript
$ npx tsc --init

git を使っている場合は /node_modules を管理下におかないように .gitignore を編集しておきます。

.gitignore
/node_modules
tsconfig.json
{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "declaration": true,  // ← 追記(コンパイルしたtsファイルの中でexportしているもの全ての型定義ファイルを出力する)
    "sourceMap": true,  // ← 追記(ソースマップを出力する)
    "outDir": "./dist",  // ← 追記(コンパイル結果の出力先を指定)
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*.ts"]  // ← 追記(コンパイルする対象ファイルの場所を指定)
}

各オプションの詳細は以下が詳しいです。

https://qiita.com/ryokkkke/items/390647a7c26933940470

上記の設定でコンパイルを行うと以下のような出力結果となります。

$ npx tsc
$ ls -l dist
total 24
-rw-r--r--  1 kokishima  staff   62  3  7 06:44 Greeter.d.ts
-rw-r--r--  1 kokishima  staff  241  3  7 06:44 Greeter.js
-rw-r--r--  1 kokishima  staff  221  3  7 06:44 Greeter.js.map

git を利用している場合は /dist は管理下に含めないように、 .gitignore を編集しておきます。

.gitignore
/node_modules
/dist

パッケージ公開に関する設定を行う

.npmignore の追加

パッケージに含める必要がないファイルを指定します。

.npmignore
/node_modules
/src
tsconfig.json

package.json の編集

package.json
{
  "name": "@ttogane/npm-package-example",
  "version": "0.1.0",
  "description": "a sample of implemantation of a npm package",
  "main": "dist/index.js",  // ← 編集(tscの設定に合わせる)
  "types": "dist/index.d.ts",  // ← 追加(型定義のルート)
  "scripts": {
    "build": "tsc",  // ← 追加(TypeScriptでビルド実行するコマンドの定義)
    "prepare": "npm run build",  // ← 追加(公開前に自動でビルドするためのコマンド定義)
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Taro Togane",
  "license": "MIT",
  "dependencies": {
    "typescript": "^4.6.2"
  }
}

パッケージを公開する

下記コマンドで公開できる。

$ npm publish --access=public

※この記事ではscope付きでパッケージ作成したので --access オプションで公開パッケージであることを明示している(これがないと npm ERR! code E402 npm ERR! 402 Payment Required みたいなエラーが出る)

成功したらこんな感じの結果になります。

npm notice 
npm notice 📦  @ttogane/npm-package-example@0.1.0
npm notice === Tarball Contents === 
npm notice 62B  dist/Greeter.d.ts  
npm notice 241B dist/Greeter.js    
npm notice 221B dist/Greeter.js.map
npm notice 27B  dist/index.d.ts    
npm notice 835B dist/index.js      
npm notice 129B dist/index.js.map  
npm notice 417B package.json     
npm notice === Tarball Details === 
npm notice name:          @ttogane/npm-package-example            
npm notice version:       0.1.0                                   
npm notice filename:      @ttogane/npm-package-example-0.1.0.tgz  
npm notice package size:  1.1 kB                                  
npm notice unpacked size: 1.9 kB                                      
npm notice shasum:        263bdf609256730979f5c74a0ba9ebc0e88a2188
npm notice integrity:     sha512-jVMX6KzfGUnGc[...]+dL4BKEhBLk2g==
npm notice total files:   47                                      
npm notice 
npm notice Publishing to https://registry.npmjs.org/
+ @ttogane/npm-package-example@0.1.0

npm にパッケージのページも作成されます。
https://www.npmjs.com/package/@ttogane/npm-package-example

※ 同じバージョンでは再度 publish はできないそうなのでその点は注意

インストールして使ってみる

パッケージをインストールして ts-node を使って実行してみます。

$ cd /path/to/somewhere/work_dir  # どこか適当な作業ディレクトリに移動しておく
$ mkdir example-package-test
$ cd $_
$ npm i @ttogane/npm-package-example
$ npm install --save-dev typescript ts-node
$ touch app.ts

実行するサンプルコードは以下です。

app.ts
import { Greeter } from "@ttogane/npm-package-example";

const greeter = new Greeter()
console.log(greeter.greet("Mr.Guest"))

実行結果は以下になります。

$ npx ts-node app.ts 
Hello Mr.Guest
undefined

前章で公開したパッケージをインポートし、利用することができました。

最後に

Next.js で決済機能を実装する本を書いたのでよかったら読んでください。

https://zenn.dev/k0kishima/books/f07cffba6e0fab

参考記事

Discussion