• #pnpm
  • #monorepo
  • #npm
  • #パッケージ
  • #ガイド
開発メモ

ローカルnpmパッケージ化ガイド

概要

ローカルnpmパッケージ化とは、npmに公開せずにモノレポ内でパッケージを作成・共有する手法。pnpmのワークスペース機能を使うことで、workspace:* という特殊な依存指定でローカルパッケージを参照できる。

メリット

項目説明
npm公開不要プライベートなコードをnpmレジストリに公開せずに再利用可能
バージョン管理不要workspace:* で常に最新版を参照
型安全TypeScriptの型定義も共有可能
テスト容易ローカルで即座に変更を反映・テスト可能

デメリット

項目説明
初期セットアップディレクトリ構造、package.json、ビルド設定が必要
ビルド必要TypeScriptの場合、使用前にビルドが必要
モノレポ限定ワークスペース外のプロジェクトからは参照不可

実装例: remark-absolute-images

画像の相対パス問題を解決するために作成したremarkプラグイン。

作成したファイル一覧

ファイル役割
packages/remark-absolute-images/package.jsonパッケージ定義
packages/remark-absolute-images/tsconfig.jsonTypeScript設定
packages/remark-absolute-images/src/index.tsプラグイン本体
packages/remark-absolute-images/dist/index.jsビルド出力
packages/remark-absolute-images/dist/index.d.ts型定義出力

変更したファイル一覧

ファイル変更内容
pnpm-workspace.yamlpackages/* をワークスペースに追加
apps/web/package.jsonremark-absolute-images: workspace:* を依存に追加
apps/web/nuxt.config.tsremarkPluginsに remark-absolute-images を追加

ステップバイステップ手順

1. pnpm-workspace.yaml にパッケージディレクトリを追加

# pnpm-workspace.yaml
packages:
  - "apps/*"
  - "apps/workers/*"
  - "packages/*"  # ← 追加

2. パッケージディレクトリを作成

mkdir -p packages/remark-absolute-images/src

3. package.json を作成

// packages/remark-absolute-images/package.json
{
  "name": "remark-absolute-images",
  "version": "1.0.0",
  "description": "Remark plugin to convert relative image paths to absolute paths",
  "type": "module",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "types": "./dist/index.d.ts"
    }
  },
  "scripts": {
    "build": "tsc",
    "prepare": "pnpm build"
  },
  "dependencies": {
    "unist-util-visit": "^5.0.0"
  },
  "devDependencies": {
    "@types/mdast": "^4.0.4",
    "typescript": "^5.0.0",
    "vfile": "^6.0.3"
  },
  "files": ["dist"]
}

重要なフィールド:

  • name: パッケージ名(他のパッケージから参照する際に使用)
  • type: "module": ESモジュールとして扱う
  • main: CommonJS用エントリポイント
  • exports: ESモジュール用エントリポイント(推奨)
  • types: TypeScript型定義の場所
  • prepare: pnpm install 時に自動ビルド(dist.gitignore の場合に必須)

4. tsconfig.json を作成

// packages/remark-absolute-images/tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "declaration": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}

重要な設定:

  • declaration: true: .d.ts 型定義ファイルを生成
  • outDir: ビルド出力先
  • rootDir: ソースファイルのルート

5. ソースコードを作成

// packages/remark-absolute-images/src/index.ts
import { visit } from 'unist-util-visit';
import type { Root, Image } from 'mdast';
import type { VFile } from 'vfile';

export default function remarkAbsoluteImages() {
  return (tree: Root, file: VFile) => {
    // 実装...
  };
}

6. ビルド

cd packages/remark-absolute-images
pnpm install
pnpm build

7. 依存パッケージから参照

// apps/web/package.json
{
  "dependencies": {
    "remark-absolute-images": "workspace:*"
  }
}

workspace:* は「ワークスペース内の同名パッケージの最新版」を意味する。

8. インストール

cd /path/to/monorepo
pnpm install

9. 使用

// apps/web/nuxt.config.ts
export default defineNuxtConfig({
  content: {
    build: {
      markdown: {
        remarkPlugins: {
          "remark-absolute-images": {}
        }
      }
    }
  }
});

他の活用例

以下のようなケースで、ローカルnpmパッケージ化は有用である。

1. 共通ユーティリティ

複数のアプリで使用する関数やクラスを共有。

packages/
└── shared-utils/
    └── src/
        ├── date.ts      # 日付フォーマット関数
        ├── validation.ts # バリデーション関数
        └── index.ts

2. UIコンポーネントライブラリ

複数のフロントエンドアプリで使用するコンポーネントを共有。

packages/
└── ui-components/
    └── src/
        ├── Button.vue
        ├── Modal.vue
        └── index.ts

3. API クライアント

バックエンドAPIと通信するクライアントを共有。

packages/
└── api-client/
    └── src/
        ├── client.ts
        ├── types.ts
        └── index.ts

4. 設定・定数

環境変数やアプリケーション設定を共有。

packages/
└── config/
    └── src/
        ├── constants.ts
        ├── env.ts
        └── index.ts

5. カスタムプラグイン(今回のケース)

フレームワーク(Nuxt、Vite等)のプラグインを共有。

packages/
└── remark-absolute-images/
    └── src/
        └── index.ts

トラブルシューティング

ERR_PNPM_VIRTUAL_STORE_DIR_MAX_LENGTH_DIFF

ERR_PNPM_VIRTUAL_STORE_DIR_MAX_LENGTH_DIFF
This modules directory was created using a different virtual-store-dir-max-length value.

解決策: node_modulesを削除して再インストール

rm -rf node_modules apps/*/node_modules packages/*/node_modules
pnpm install

パッケージが見つからない

原因: pnpm-workspace.yamlに該当ディレクトリが含まれていない

解決策: packages/* をpnpm-workspace.yamlに追加

型が認識されない

原因: ビルドしていない、または declaration: true が設定されていない

解決策:

  1. tsconfig.jsonで declaration: true を設定
  2. pnpm build でビルド

参考