メインコンテンツへスキップ
qwqb.net

初めてのnpmパッケージ公開が思ったより簡単だった

·
ProgrammingTypeScript

少し前に qwq-editor (GitHub) というリッチテキストエディタを作っていました。このサイトの記事作成に組み込む想定で、実装や動作確認もこのサイトのリポジトリ上で進めていた形です。

開発中はローカルのパッケージを link: で直接参照していたのですが、npm への公開はそのうちやるつもりでずっと後回しにしていました。手元では普通に動いていたので、特に困ることもなかったというのが正直なところです。

そんな中、マダめもくん のリリースに合わせて Projects ページの追加作業をしていたところ、Cloudflare Pages のビルドでこんなエラーが出ました。

ERR_PNPM_FETCH_404  GET https://registry.npmjs.org/@qwq-net%2Fcore: Not Found - 404

package.json を見てみると、こうなっています。

{
  "@qwq-net/core": "link:../qwq-editor/packages/core",
  "@qwq-net/instant": "link:../qwq-editor/packages/instant"
}

link: はローカルのファイルパスを直接参照する仕組みなので、CI やホスティング環境にはそのパスが存在せず、当然 404 になります。すっかり忘れていた npm 公開をやらないと先に進めない状況でした。

そのような訳で、初めての npm パッケージ公開をやってみることにしました。

npm アカウントと Organization の作成

@qwq-net/core のようなスコープ付きパッケージを公開するには、npm 上に Organization が必要です。

  • npmjs.com でアカウントを作成
    • とりあえずユーザー名は qwqb で作成
  • ログイン後、Organization @qwq-net を作成
  • ターミナルで npm login を実行
    • 認証用URLが出たら、そちらへアクセスして認証を実施

アカウントは無料で作れます。自分はそもそも npm のアカウントすら持っていなかったのですが、ささっと作成してしまいました。特に詰まるようなところはないと思います。

公開前の準備

npm に公開するにあたって、いくつか準備が必要でした。

まず LICENSEREADME.md を各パッケージに配置します。LICENSE がないと利用者がライセンスを判断できません。README.md は npm のパッケージページにそのまま表示されるので、最低限の説明は書いておきたいところです。

次に、各パッケージの package.json に公開用のフィールドを追加します。

{
  "publishConfig": { "access": "public" },
  "files": ["dist", "README.md"],
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    }
  },
  "main": "./dist/index.cjs",
  "module": "./dist/index.js",
  "types": "./dist/index.d.ts"
}
  • publishConfig: スコープ付きパッケージはデフォルトで private 扱いになるので、"access": "public" を明示する必要があります
  • files: npm に含めるファイルを指定します。dist/ だけ含めればソースコードは配布されません
  • exports: ESM / CJS それぞれのエントリポイントと型定義を指定します。mainmodule も併記しておくと、古いバンドラとの互換性が取れます

ビルド設定

qwq-editor は pnpm ワークスペースのモノレポ構成で、ビルドには tsup を使っています。tsup は esbuild ベースのバンドラで、設定がシンプルなのが特徴です。

自分の場合はすでに設定済みでしたが、以下のような tsup.config.ts があると ESM / CJS のデュアル出力と型定義の生成をまとめてやってくれます。

import { defineConfig } from 'tsup';

export default defineConfig({
  entry: ['src/index.ts'],
  format: ['esm', 'cjs'],
  dts: true,
  sourcemap: true,
  clean: true,
});

これで dist/ 配下に index.js(ESM)、index.cjs(CJS)、index.d.ts(型定義)が出力されます。さきほど filesdist/ を指定したのは、この出力を npm に含めるためです。

ビルドと公開

準備ができたので、早速公開してみます。

pnpm build

モノレポ全体をビルドした後、publish を実行します。

pnpm -r publish --access public

初回は各パッケージごとに OTP(ワンタイムパスワード)認証を求められ、ブラウザでの認証が3回必要でした。パッケージが3つあるので3回です。

レジストリへの反映ラグ

公開コマンド自体は成功して + @qwq-net/core@0.0.1 のような表示が出たものの、直後に npm view を叩いても 404 が返ってきました。

npm error 404 Not Found - GET https://registry.npmjs.org/@qwq-net%2Fcore - Not found

初回は特にレジストリへの伝播に時間がかかるようです。適当に時間を潰しながら待ちましょう。

公開したパッケージは npm のプロフィールページ から確認できます。

公開後の対応

ポートフォリオ側の package.jsonlink: からバージョン指定に変更しました。

{
  "@qwq-net/core": "^0.0.1",
  "@qwq-net/instant": "^0.0.1"
}

ついでにパッケージマネージャ周りも整理しています。

  • package-lock.json の削除: pnpm を使っているのに npm の lockfile が残っていた
  • pnpm-workspace.yaml の修正: pnpm v10 で必須になった packages フィールドが欠けていた

どちらも link: で手元では動いていたので気づかなかったものです。

まとめ

Cloudflare Pages のビルドエラーをきっかけに、自作パッケージの npm 公開を初めてやってみました。思っていたより気軽に公開できるので、何かパッケージにできそうなものがあれば積極的にやってみても良いかもしれませんね。

そういえばバージョンは 0.0.1 で適当に公開してしまいましたが、major / minor / patch ってどういう基準で上げていくのが正しいのか……。