Zenn
Closed9

npmのworkspace と tsconfig の設定方法の確認

shikazukishikazuki

TypeScript のプロジェクト参照

https://www.typescriptlang.org/docs/handbook/project-references.html

tsconfig.json
{
    "compilerOptions": {
        // The usual
    },
    "references": [
        { "path": "../src" }
    ]
}

参照したいプロジェクトのtsconfig.jsonへのパスを指定することで、outDirに設定された出力ファイルの
.d.ts を読み込む。 { "path": "../src/tsconfig.build.json" } のように違う tsconfig を紐づけることも可能

https://www.typescriptlang.org/docs/handbook/project-references.html#build-mode-for-typescript
-b (--build) オプションは、references の参照を元に必要に応じて事前にビルドをしてくれる

 > tsc -b                            # Use the tsconfig.json in the current directory
 > tsc -b src                        # Use src/tsconfig.json
 > tsc -b foo/prd.tsconfig.json bar  # Use foo/prd.tsconfig.json and bar/tsconfig.json
shikazukishikazuki

tsconfig paths の指定

https://www.typescriptlang.org/tsconfig/#paths
https://www.typescriptlang.org/docs/handbook/modules/reference.html#paths

import のパスに別名をつけることができる

tsconfig.json
{
  "compilerOptions": {
    "module": "esnext",
    "moduleResolution": "bundler",
    "paths": {
      "@app/*": ["./src/*"]
    }
  }
}

コンパイル時にパスをそのまま出力してしまうので、以下は問題のあるコードになる

tsconfig.json
{
  "compilerOptions": {
    "module": "nodenext",
    "paths": {
      "node-has-no-idea-what-this-is": ["./oops.ts"]
    }
  }
}
// TypeScript: ✅
// Node.js: 💥
import {} from "node-has-no-idea-what-this-is";

https://www.typescriptlang.org/docs/handbook/modules/reference.html#paths-should-not-point-to-monorepo-packages-or-node_modules-packages
node_modules を paths で alias をつけるのは禁止。package.json の exports とバッテイングして期待通りの動作にならない可能性があるため。

tsconfig.json
{
  "compilerOptions": {
    "paths": {
      "pkg": ["./node_modules/pkg/dist/index.d.ts"],
      "pkg/*": ["./node_modules/pkg/*"]
    }
  }
}
shikazukishikazuki

tsconfig types の指定

https://www.typescriptlang.org/tsconfig/#types
types の指定がない場合は、node_modules に存在する @types が全て読み込まれる。
types の指定がある場合は、指定した @types だけが読み込まれる

tsconfig.json
{
  "compilerOptions": {
    "types": ["node", "jest", "express"]
  }
}
shikazukishikazuki

npm の workspace の初期化

非公開で作るとして private を true にしておく

package.json
{
  "name": "project_name",
  "private": true
}

workspace の初期化

$ npm init --scope project_name -w packages/app -w packages/core
shikazukishikazuki

npm の workspace のコマンド実行

前提

-workspace or -w は workspace を単体で指定する

$ npm run test -workspace packages/app
$ npm run test -w packages/app -w packages/core

-workspaces or -ws は workspace を全て指定する

$ npm run test -workspaces
$ npm run test -ws

--if-present でコマンドが存在する workspace での実行ができる

$ npm run test --workspaces --if-present

install

$ npm install xxx -w packages/app
# package も指定が可能
$ npm install project_name/core -w packages/app

workspace の package は node_modules にシンボリックリンクを作る

./node_modules
├── project_name
│   ├── app -> ../../packages/app
│   └── core -> ../../packages/core
└── typescript
shikazukishikazuki

Package のエントリーポイントについて

main

https://docs.npmjs.com/cli/v11/configuring-npm/package-json#main

package.json
{
  "main" : "./src/index.js"
}

と指定した場合 import * from "project_name" という指定で index.js のモジュールを読み込める

exports

https://docs.npmjs.com/cli/v11/configuring-npm/package-json#exports
https://nodejs.org/api/packages.html#package-entry-points

packages/core/src
├── index.js
└── test.js

例えば、上記のファイルがあった時に以下のようにエントリーポイントを複数指定できる。

core/package.json
{
  "exports": {
    "./*": "./src/*.js"
  },
}

つまり、以下のようにインポートが可能になる

import * from "package_name/core/index" // OK
import * from "package_name/core/test" // OK

https://www.typescriptlang.org/tsconfig/#moduleResolution

shikazukishikazuki

workspace と tsconfig の設定

前述の内容を踏まえてまずはNG例

NG

paths に node_modules のパスを設定する

paths には node_modules のパスは書いてはいけない

tsconfig.json
{
  "paths": {
         "@core/*": ["../../node_modules/package_name/core/dist/*"]
       },
}

paths に別パッケージの指定をする

外部依存のパッケージは package.json の exports の指定で読み込むようにする。
別名をつけることで単体で読み込んだ時に動作しなくなるはず。

tsconfig.json
{
  "paths": {
         "@core/*": ["../../packages/core/dist/*"]
       },
}

ちょっと自信がない

references の設定

references 自体はモジュールを分割してビルド量を減らすための目的であり、モノレポにおいてはこの設定はOKなはず。
また、モノレポでコードを編集する時に references の設定を入れないと IDE でうまくコードの参照を見つけられない。

tsconfig.json
  "references": [{
    "path": "../core"
  }],
shikazukishikazuki

workspace と tsconfig の構成例

間違いあるかもしれませんが、そこまでおかしくないはず。

フォルダ構成

./packages
├── app
│   ├── dist
│   └── src
└── core
    ├── dist
    └── src

Core

packages/core/package.json
{
  "name": "project_name/core",
  "version": "1.0.0",
  "exports": {
    "./*": "./dist/*.js"
  },
  "scripts": {
    "build": "tsc -b",
  },
}
packages/core/tsconfig.json
{
  "compilerOptions": {
    "composite": true,
    "target": "es2016",
    "module": "NodeNext",
    "declaration": true,
    "outDir": "./dist",
    "tsBuildInfoFile": "./dist/.tsbuildinfo",
    "rootDir": "./src",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true,
    "moduleResolution": "NodeNext"
  },
  "include": ["./src/**/*.ts"]
}

App

packages/app/package.json
{
  "name": "project_name/app",
  "scripts": {
    "build": "tsc -b",
  },
}

packages/app/tsconfig.json
{
  "compilerOptions": {
    "composite": true,
    "target": "es2016",
    "module": "NodeNext",
    "declaration": true,
    "outDir": "./dist",
    "tsBuildInfoFile": "./dist/.tsbuildinfo",
    "rootDir": "./src",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true,
    "moduleResolution": "NodeNext"
  },
  "references": [{
    "path": "../core"
  }],
  "include": ["./src/**/*.ts"]
}
このスクラップは3ヶ月前にクローズされました
作成者以外のコメントは許可されていません