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

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

February 3, 2022

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 が入ってしまう #

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

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

パッケージ開発者側 #

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

foobar のサブディレクトリから 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 配下の foobar ディレクトリに資産があり、ビルド結果は 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

説明 #

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

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

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

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

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

結果 #

これによりパッケージ利用者が 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';