skysan's programming notebook

コーディングして思ったことなどを気ままに

package.jsonのexportsフィールドを設定する(TypeScript)

はじめに

自作のnpmパッケージを参照するときにエントリポイントを複数公開して、別のパッケージでimportしたかった。しかし、モジュール '***/***/***' またはそれに対応する型宣言が見つかりません。となり、importに失敗したため、調査結果をメモする。

前提条件

  • pnpm workspaceでモノレポ構成
  • typescript v5.0.4
  • tscでビルドしない
    • 最終的にviteやwebpackなどのバンドラーツールでビルドする(参考)

フォルダ構成

  • モノレポ構成
  • 関係するもののみ表示
.
├── apps
│   └── webapp
│       └── (省略)
├── package.json
├── packages
│   ├── package1
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── Domain
│   │   │   │   ├── Model
│   │   │   │   │   ├── Activity.ts
│   │   │   │   │   └── index.ts
│   │   │   │   └── (省略)
│   │   │   └── Util
│   │   │       ├── DateUtil.ts
│   │   │       └── NumberUtil.ts
│   │   └── tsconfig.json
│   └── package2
│       ├── package.json
│       ├── src
│       │   └── index.ts
│       └── tsconfig.json
├── pnpm-lock.yaml
└── pnpm-workspace.yaml

実現したいこと

package1

  • エントリポイントを公開するnpmパッケージ
// Domain/Model/Activity.ts
export class Activity {
}

// Domain/Model/index.ts(Modelフォルダ内のファイルをまとめてexport)
export { Activity } from "./Activity"

// Util/NumberUtil.ts
export function parse(value: string): number {}

package2

  • package1を参照するパッケージ
import { Activity } from "@my-project/pakcage1/model"
import { parse } from "@my-project/pakcage1/util/NumberUtil"

エントリポイントの設定

  • 複数のエントリポイントを設定するには、package.jsonexportsフィールドを使用する
  • (利用可能なするnode.jsとtypescriptのバージョンは省略)

package1

  • エントリポイントを公開するpackage1のpackage.json
    • 必要な設定のみ抜粋
  • 今回はtypescriptのファイルを直接指定
// package.json
{
  "name": "@my-project/package1",
  "exports": {
    "./model":  "./src/Domain/Model/index.ts",
    "./util/*": "./src/Util/*.ts"
  },
    "devDependencies": {
    "typescript": "^5.0.4"
  }
}

package2

  • 参照するpackage2のpackage.jsonは以下
  • monorepoのworkspaceを利用
// package.json
{
  "name": "@my-project/package2",
  "dependencies": {
    "@my-project/package1": "workspace:*"
  }
}

Typescriptの設定

  • 当初、設定時に以下のようなエラーが表示され、うまくimportできなかった
// ERROR: `モジュール '@my-project/pakcage1/model' またはそれに対応する型宣言が見つかりません。`
import { Activity } from "@my-project/pakcage1/model"

package1

  • 修正後の状態
  • (動作確認のため、ts-nodeを設定してある)
// tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "CommonJS", // ts-nodeの実行
    "composite": false,
    "moduleResolution": "nodenext",
    "paths": {
      "@/*": ["./src/*"]
    },
    "rootDirs": ["./src", "./test"],
    "declaration": true,
    "outDir": "./build",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true,
    "noImplicitAny": true,
    "removeComments": true,
    "noEmit": false,
  },
  "exclude": [
    "test",
    "node_modules",
    "build"
  ],
  "ts-node": {
    "esm": true,
    "experimentalSpecifierResolution": "node"
  }
}

package2

  • 修正後の状態
// tsconfig.json
{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "nodenext", // (package.jsonのexportsフィールド)
    "paths": { "@/*": ["./src/*"] },
    "rootDirs": ["./src", "./test"],
    "declaration": true,
    "outDir": "./build",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  },
  "exclude": [
    "node_modules"
  ]
}

参考