Tips & Tricks (更新: 2026/6/2)

Claude Codeでnpmパッケージを作成・公開する実践ガイド

Claude Codeでnpmパッケージを作成し、tsup、型定義、npm pack、CI公開まで安全に整える手順。

Claude Codeでnpmパッケージを作成・公開する実践ガイド

Claude Codeに「npmパッケージを作って」と頼むだけでも、雛形はすぐ出てきます。けれど公開まで考えると、見るべき場所は意外に多いです。ESMとCJSの両対応、exports、型定義、files、README、npm packの中身、GitHub Actions、公開前のスモークテストまで抜けると、インストールできても実務では使いにくいパッケージになります。

この記事では、初心者でもそのまま試せるTypeScript製npmパッケージを題材にします。Claude Codeは「全部を自動で信じる相手」ではなく、設定の抜け漏れを洗い出すペアプログラマーとして使います。npmの仕様は変わるため、公式の package.json docsscoped public packagesnpm packtrusted publishing と、Claude Code公式ドキュメントを基準にしてください。

先にClaude Codeへ渡す設計メモ

最初の依頼で曖昧にしないことは、公開名、利用者、対応ランタイム、ビルド形式、検証方法です。ここが空欄だと、Claude Codeは見た目だけ整ったpackage.jsonを作りがちです。特にtype: "module"mainexportstypesは互いに関係しているため、後から直すより最初に決める方が安全です。

項目今回の決定Claude Codeに確認させること
パッケージ名@acme/string-kitnpm上で使える名前か、scope公開なら--access publicが必要か
利用者Node.js/TypeScript利用者ESM importとCJS requireの両方で読めるか
ビルドtsupでESM/CJS/型定義を生成distだけを公開し、ソースマップを誤って含めないか
検証Vitest、npm pack --dry-run、distのimport確認公開前にtarballの中身を人間が見られるか
公開GitHub Actions + npm Trusted Publishing長期npm tokenに依存しない構成にできるか

全体像は次の流れです。図にしてから依頼すると、Claude Codeが「コードだけ」「CIだけ」に寄りすぎるのを防げます。

flowchart LR
  A["設計メモ"] --> B["package.json"]
  B --> C["src/index.ts"]
  C --> D["Vitest"]
  D --> E["tsup build"]
  E --> F["npm pack dry-run"]
  F --> G["CI publish"]

CLIとして公開する話は Claude CodeでCLIツール開発 に分けています。日常の依頼の型は Claude Code生産性Tips と合わせて読むと、今回のプロンプトも運用しやすくなります。

最小パッケージを作る

まずは空ディレクトリで始めます。既存リポジトリに混ぜる前に、最小構成でビルド、テスト、packを通す方が原因を切り分けやすいです。Claude Codeに作らせる場合も、最初はこのコマンド列を完了条件として渡します。

mkdir string-kit
cd string-kit
npm init -y
npm install -D typescript tsup vitest @types/node
mkdir src scripts

package.jsonは公開物の契約書です。mainはCJS利用者向け、moduleは古いbundler向けの補助、typesは型定義、exportsは現在の入口制御として置きます。filesを絞ると、テスト、設定、下書きメモをtarballに入れにくくなります。

{
  "name": "@acme/string-kit",
  "version": "0.1.0",
  "description": "Small TypeScript string utilities used as an npm package example.",
  "type": "module",
  "main": "./dist/index.cjs",
  "module": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    },
    "./package.json": "./package.json"
  },
  "files": ["dist", "README.md", "LICENSE"],
  "sideEffects": false,
  "scripts": {
    "build": "tsup",
    "test": "vitest run",
    "docs": "node scripts/write-readme.mjs",
    "test:pack": "npm pack --dry-run",
    "prepublishOnly": "npm run test && npm run build && npm run test:pack"
  },
  "keywords": ["string", "typescript", "utilities"],
  "license": "MIT",
  "devDependencies": {
    "@types/node": "^22.15.0",
    "tsup": "^8.5.0",
    "typescript": "^5.8.0",
    "vitest": "^3.2.0"
  }
}

TypeScript設定は、パッケージ自体からJSを出さず、tsupに出力を任せます。moduleResolution: "Bundler"は、exportsを意識したライブラリ開発で現代的な解決に寄せるための設定です。

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "declaration": true,
    "declarationMap": true,
    "skipLibCheck": true,
    "noEmit": true
  },
  "include": ["src", "tsup.config.ts"]
}

実装コードとテスト

初心者向けの記事で一番危ないのは、動かない疑似コードを載せることです。ここでは文字列ユーティリティを4つに絞ります。slugifyはURLやファイル名に使いやすい英数字スラッグへ変換し、truncateは最大長を守って省略し、interpolateはREADMEやメッセージ生成で使う簡単な置換を行い、byteLengthはUTF-8のバイト数を返します。

export function slugify(input: string): string {
  return input
    .normalize("NFKD")
    .replace(/[\u0300-\u036f]/g, "")
    .toLowerCase()
    .trim()
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "");
}

export function truncate(input: string, maxLength: number, suffix = "..."): string {
  if (!Number.isInteger(maxLength) || maxLength < 1) {
    throw new RangeError("maxLength must be a positive integer");
  }
  if (suffix.length >= maxLength) {
    throw new RangeError("suffix must be shorter than maxLength");
  }
  if (input.length <= maxLength) return input;
  return `${input.slice(0, maxLength - suffix.length)}${suffix}`;
}

export function interpolate(template: string, values: Record<string, string | number>): string {
  return template.replace(/\{\{\s*([\w.-]+)\s*\}\}/g, (match, key: string) => {
    return Object.hasOwn(values, key) ? String(values[key]) : match;
  });
}

export function byteLength(input: string): number {
  return new TextEncoder().encode(input).length;
}

テストは「代表例」だけでなく、失敗しやすい境界も入れます。Claude Codeにテストを頼むときは、成功例、境界値、例外、Unicodeを必ず条件に入れると、公開後に利用者が踏むバグを減らせます。

import { describe, expect, it } from "vitest";
import { byteLength, interpolate, slugify, truncate } from "./index";

describe("slugify", () => {
  it("turns a title into an npm-friendly slug", () => {
    expect(slugify("Hello npm Package!")).toBe("hello-npm-package");
  });

  it("removes accents before replacing separators", () => {
    expect(slugify("Crème brûlée utils")).toBe("creme-brulee-utils");
  });
});

describe("truncate", () => {
  it("keeps short text unchanged", () => {
    expect(truncate("short", 10)).toBe("short");
  });

  it("adds a suffix inside the requested length", () => {
    expect(truncate("Claude Code package", 12)).toBe("Claude Co...");
  });

  it("rejects invalid lengths", () => {
    expect(() => truncate("abc", 2)).toThrow(RangeError);
  });
});

describe("interpolate", () => {
  it("replaces known placeholders and keeps unknown ones", () => {
    expect(interpolate("Hi {{ name }}, ship {{pkg}} {{missing}}", {
      name: "Masa",
      pkg: "@acme/string-kit",
    })).toBe("Hi Masa, ship @acme/string-kit {{missing}}");
  });
});

describe("byteLength", () => {
  it("counts UTF-8 bytes", () => {
    expect(byteLength("npm")).toBe(3);
    expect(byteLength("日本語")).toBe(9);
  });
});

tsup、型定義、README生成

tsupは小さなライブラリを配布するには十分軽い選択肢です。outExtensionでESMを.js、CJSを.cjsに分けると、type: "module"のパッケージでもCJS利用者がrequireしやすくなります。sourcemap: falseにしているのは、公開パッケージに内部ソースを余計に入れないためです。必要なら公開前に意図して有効化してください。

import { defineConfig } from "tsup";

export default defineConfig({
  entry: ["src/index.ts"],
  format: ["esm", "cjs"],
  dts: true,
  clean: true,
  sourcemap: false,
  minify: false,
  target: "es2022",
  outDir: "dist",
  outExtension({ format }) {
    return { js: format === "esm" ? ".js" : ".cjs" };
  },
});

READMEもClaude Codeに任せきりにせず、生成スクリプトで最低限の使用例を固定しておくと便利です。公開前にnpm run docsを実行すれば、インストール方法と使い方が古いまま残る事故を減らせます。

import { writeFile } from "node:fs/promises";

const fence = String.fromCharCode(96).repeat(3);
const readme = `# @acme/string-kit

Small TypeScript string utilities packaged with tsup.

## Install

${fence}bash
npm install @acme/string-kit
${fence}

## Usage

${fence}ts
import { slugify, truncate } from "@acme/string-kit";

console.log(slugify("Hello npm Package!"));
console.log(truncate("Claude Code package", 12));
${fence}
`;

await writeFile(new URL("../README.md", import.meta.url), readme);

npm packで公開前に検証する

npm publishの直前に初めて中身を見るのは遅すぎます。npm pack --dry-runは、実際にnpmへ送られるファイル一覧に近いものを確認できます。files.npmignoreREADMELICENSE、型定義が意図通り入っているかを必ず見ます。

npm run docs
npm test
npm run build
npm pack --dry-run
node -e "import('./dist/index.js').then((m)=>console.log(m.slugify('Hello ESM')))"
node -e "const m=require('./dist/index.cjs'); console.log(m.slugify('Hello CJS'))"

さらに安全に見るなら、生成された.tgzを別ディレクトリへインストールします。ローカルのsrcを直接読んで成功しているだけでは、利用者環境で動く証拠になりません。

npm pack
mkdir ../string-kit-smoke
cd ../string-kit-smoke
npm init -y
npm install ../string-kit/acme-string-kit-0.1.0.tgz
node -e "import('@acme/string-kit').then((m)=>console.log(m.truncate('Claude Code package', 12)))"

実用例は3つあります。1つ目は社内UIで共通の文字列整形を使う場合です。各アプリに同じslugifyを貼るより、テスト済みパッケージへ寄せた方がレビューしやすくなります。2つ目はドキュメント生成です。interpolateのような小さな関数でも、README、通知文、リリースノートの文言を統一できます。3つ目は記事サイトやCMSです。byteLengthtruncateを使うと、descriptionやカード表示の長さを機械的に確認できます。

GitHub Actionsで公開する

公開は手元のnpm publishだけに頼らない方が安全です。npmのTrusted Publishingを使うと、GitHub ActionsやGitLab CI/CDからOIDCで公開でき、長期npm tokenを置かずに済みます。npm側でtrusted publisherを設定したうえで、release作成時だけ公開するworkflowにします。まだtoken運用の場合は、npm公式のprovenanceや2FA設定も確認してください。

name: package

on:
  push:
    branches: [main]
  pull_request:
  release:
    types: [published]

permissions:
  contents: read
  id-token: write

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          registry-url: https://registry.npmjs.org
          cache: npm
      - run: npm ci
      - run: npm run docs
      - run: npm test
      - run: npm run build
      - run: npm pack --dry-run

  publish:
    if: github.event_name == 'release'
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          registry-url: https://registry.npmjs.org
          cache: npm
      - run: npm ci
      - run: npm run docs
      - run: npm test
      - run: npm run build
      - run: npm publish --access public

リリース管理をもう少し丁寧にするなら、Claude CodeとChangesetsによるバージョン管理も合わせて使えます。SemVer、CHANGELOG、release PRを分けておくと、Claude Codeに「これはpatchかminorか」をレビューさせやすくなります。

よくある落とし穴

1つ目は、exportsを置かずにmainだけで済ませることです。古い例では動いても、現代のbundlerやTypeScript利用者にとって入口が曖昧になります。Claude Codeがmainだけを出したら、ESM、CJS、型定義の3点を再確認させます。

2つ目は、filesを絞らずに公開することです。テストfixture、下書き、スクリーンショット、ソースマップ、ローカル設定がtarballに入ることがあります。npm pack --dry-runの出力をレビューする作業を完了条件に入れてください。

3つ目は、READMEの例が実装とずれることです。関数名を変えたのにREADMEだけ古い、default exportの例が残っている、インストール名が違う、といった小さなズレは信頼を落とします。README生成スクリプトかスモークテストで防ぎます。

4つ目は、Unicodeの扱いを過信することです。今回のtruncateはJavaScriptの文字列長を使うため、絵文字や結合文字を完全に人間の見た目単位で扱う関数ではありません。必要ならIntl.Segmenterを使う別関数として設計し、READMEにも制約を書きます。

5つ目は、Claude Codeに公開操作まで丸投げすることです。公開名、scope、2FA、Trusted Publishing、初回公開の--access publicは人間が確認します。Claude Codeには差分作成、チェックリスト、コマンド列、CI修正を任せ、公開ボタンに相当する判断は分けます。

Claude Codeへの依頼テンプレート

次のテンプレートを使うと、Claude Codeが余計なファイルを触りにくくなります。npmパッケージでは、成果物よりも「公開される中身」が重要なので、完了条件にnpm packを必ず入れます。

TypeScriptのnpmパッケージを作成してください。

目的:
- パッケージ名は @acme/string-kit
- ESM import と CJS require の両方に対応
- tsupで dist/index.js, dist/index.cjs, dist/index.d.ts を生成
- README生成スクリプト、Vitest、npm pack検証を含める

制約:
- package.json, tsconfig.json, tsup.config.ts, src, scripts, .github/workflows 以外は触らない
- 疑似コードではなく、npm install後にそのまま動くコードにする
- source mapや不要なテストファイルを公開tarballに入れない

完了条件:
- npm test が通る
- npm run build が通る
- npm pack --dry-run の内容を説明する
- ESM import と CJS require のスモークテスト結果を示す
- 公開前に人間が確認すべき項目を最後に列挙する

CTA: 公開品質の型を手元に置く

npmパッケージ公開は、一度通れば終わりではありません。バージョン更新、README更新、CIのNode.js更新、依存関係の更新、公開権限の見直しが続きます。まずは 無料チートシート でClaude Codeへの安全な依頼と検証コマンドを手元に置いてください。テンプレートやレビュー観点をまとめて使いたい場合は ClaudeCodeLabの商品一覧 が近道です。チームで公開フロー、CLAUDE.md、CI、レビュー基準まで整えるなら Claude Code研修・導入相談 から相談できます。

この記事で紹介した内容を実際に試した結果、Windows環境の一時ディレクトリでnpm installnpm testnpm run buildnpm pack --dry-run、ESM/CJSのnode -e確認まで通りました。Masaの運用では、Claude Codeに最初からnpm packの出力説明を求めるだけで、READMEだけ更新されてdistが古い、不要なファイルがtarballへ入る、といった公開前の見落としがかなり減りました。

まとめ

Claude Codeでnpmパッケージを作るときは、実装よりも先に公開契約を決めます。package.jsonexports、型定義、tsup、README、npm pack、CIを1本の流れとして扱えば、AIが生成した雛形を公開できる品質に近づけられます。

公開直前の合言葉は「distを見たか、tarballを見たか、利用者と同じ入口で読んだか」です。この3つをClaude Codeの完了条件に入れるだけで、npm公開の失敗はかなり減らせます。

#Claude Code #npm #繝代ャ繧ア繝シ繧ク蜈ャ髢・ #TypeScript #OSS
無料

無料PDF: Claude Code はじめてのチートシート

まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。

スパムは送りません。登録情報は厳重に管理します。

Claude Codeを仕事で使える形にしませんか?

無料PDFで基礎を固めたあと、すぐ使えるテンプレート集で試し、必要なら業務自動化や導入相談まで進められます。

Masa

この記事を書いた人

Masa

Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。

PR

関連書籍・参考図書

この記事のテーマに関連する書籍を楽天ブックスで探せます。

※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。