サブディレクトリから import する npm のパッケージを作る

サブディレクトリから import する npm のパッケージを作る

import の from にある /(スラッシュ) の意味 #

npm のパッケージを import する時に from の中でスラッシュを記述するものがある。例えば Firebase SDK はこのパターン。

import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
import { getFunctions } from "firebase/functions";

これはサブディレクトリから import している。

実際に node_modules の中身を除いてみると、firebase パッケージは次のような構造になっていることがわかる。

└ node_modules/
  └ firebase/
    ├ app/
    │ ├ dist/
    │ └ package.json
    ├ auth/
    │ ├ dist/
    │ └ package.json
    ├ firestore/
    │ ├ dist/
    │ └ package.json
    └ functions/
      ├ dist/
      └ package.json

TypeScript で作ると dist や lib が入ってしまう #

dist や lib といったディレクトリにビルド結果を出力し、これを npm publish する。

そうすると、パッケージ利用者が install した際にも実際のコードが dist や lib 配下に入ってしまう。

パッケージ開発者側 #

src/

└ src/
  ├ foo/
  │ └ index.ts
  └ bar/
    └ index.ts

tsconfig.json

{
  "compilerOptions": {
    "outDir": "./dist"
  }
}

package.json

{
  "files": ["dist"]
}

パッケージ利用者側 #

node_modules

└ node_modules/
  └ example-package/
    ├ package.json
    └ dist/
      ├ foo/
      │ ├ index.js
      │ └ index.d.ts
      └ bar/
        ├ index.js
        └ index.d.ts

foo や bar のサブディレクトリから import するには、あいだに dist を記載しないといけなくなる。

import foo from "example-package/dist/foo";
import bar from "example-package/dist/bar";

上記ではなく dist は除いた形で import できるようにしたい。

dist をパッケージのルートとして npm publish したい #

同様の質問をしている人がいた。

How to npm publish specific folder but as package root - Stack Overflow

結局のところ、これを解決する方法を npm が提供してはいないため、皆それぞれの方法で実現している様子。

以下では自分なりの簡易的な解決方法を提示する。

対応方法 #

src 配下の foo と bar ディレクトリに資産があり、ビルド結果は dist ディレクトリに出力される想定とする。

└ src/
  ├ foo/
  │ └ index.ts
  └ bar/
    └ index.ts

1. .npmignore を作成 #

.npmignore を以下の内容で作成する。

*
!*/
!/foo/**
!/bar/**

2. package.json を更新 #

package.json から以下を削除する。

{
  "files": ["dist"]
}

package.json に以下を追加する。

{
  "scripts": {
    "build": "tsc",
    "prepublishOnly": "npm run build && cp -r ./dist/* . && rm -rf ./dist",
    "postpublish": "git clean -fd"
  }
}

3. npm publish #

あとは publish するだけ。

npm publish

説明 #

まず prepublishOnly で dist 配下に資産が作られ、続くコマンドでそれらをルートディレクトリに移動させる。

そして publish する。package.json の files を削除したことで、プロジェクトに含まれるすべてが publish 対象となっているが、ルートディレクトリにある設定ファイルなどは publish したくない。

そこで .npmignore を利用する。まずすべての資産を ignore 対象に指定し、それを上書きするように foo と bar のみを ignore から外す。

そのため、最終的にはルートディレクトリに直下の foo と bar のみが publish される。

publish が終わった後は postpublish が動き、ルートディレクトリ直下に一時的に作成された foo と bar が削除される。

結果 #

これによりパッケージ利用者が install した際は、以下のように install される。

node_modules

└ node_modules/
  └ example-package/
    ├ package.json
    ├ foo/
    │ ├ index.js
    │ └ index.d.ts
    └ bar/
      ├ index.js
      └ index.d.ts

これで以下のように使用することができる。

import foo from "example-package/foo";
import bar from "example-package/bar";