開発メモ
ローカル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.json | TypeScript設定 |
packages/remark-absolute-images/src/index.ts | プラグイン本体 |
packages/remark-absolute-images/dist/index.js | ビルド出力 |
packages/remark-absolute-images/dist/index.d.ts | 型定義出力 |
変更したファイル一覧
| ファイル | 変更内容 |
|---|---|
pnpm-workspace.yaml | packages/* をワークスペースに追加 |
apps/web/package.json | remark-absolute-images: workspace:* を依存に追加 |
apps/web/nuxt.config.ts | remarkPluginsに 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 が設定されていない
解決策:
- tsconfig.jsonで
declaration: trueを設定 pnpm buildでビルド