ぼくのかんがえたさいきょうのpnpm workspace構成(workspace内での設定の共有まで含む)
インターネット上にサンプルは色々あるけど,tsconfig.jsonの共有を相対パスでやるなどしている例ばっかりだったので実用的なのを書きたい
サンプルのプロジェクトはそのうちGitHub上に作る
前提
プライベートなアプリケーションのworkspace
Webアプリのサーバーサイドを想定
DDDの文脈でdomain層,infrastructure層(infra層),application層,presentation層(api層)の4パッケージに分割する 使用するライブラリ・ツールなど
共通で使うライブラリ・ツールの設定は可能な限り使い回す
書かないこと
pnpm workspaceの説明・使い方
ディレクトリ構成
よくある例
packagesディレクトリを切ってその中に全てのパッケージを入れる方法
別にこれでも問題ない
自分がよくやる方法
アプリケーションのコア部分はルートに配置して,設定共有用パッケージ(後述)をpackagesディレクトリに置く方法
ルートにsample-{api,app,domain,infra}ディレクトリ
よく触るのでルートから近いほうが便利
それ以外をpackages以下のディレクトリ
頻繁には触らないのでpackages以下に閉じ込めておく
コア部分とそれ以外を分離できる
設定方法
pnpm-workspace.yamlを以下のようにする
code:/pnpm-workspace.yaml
packages:
- "sample-*" # アプリケーションのコア部分
- "packages/*" # アプリケーションのコア部分以外
最終的なディレクトリ構成
packagesの中身については後述
code:ディレクトリ構成(txt)
sample/
├─ packages/
│ ├─ eslint-config-shared/
│ │ ├─ package.json
│ ├─ prettier-config-shared/
│ │ ├─ package.json
│ ├─ tsconfig-shared/
│ │ ├─ package.json
├─ sample-api/
│ ├─ package.json
├─ sample-app/
│ ├─ package.json
├─ sample-domain/
│ ├─ package.json
├─ sample-infra/
│ ├─ package.json
├─ package.json
├─ pnpm-workspace.yaml
ワークスペースのルートのpackage.json
scripts
各パッケージ名でフィルターしたスクリプトを作っておくと簡単にコマンドを実行できて便利
code:/package.json
{
"scripts": {
"api": "pnpm -F sample-api",
"app": "pnpm -F sample-app",
"domain": "pnpm -F sample-domain",
"infra": "pnpm -F sample-infra"
}
}
$ pnpm api lintを実行すればapiパッケージのscripts.lintが実行される
pnpm execも$ pnpm api exec tscのように実行できるので作っておくと便利
dependencies / devDependencies
TypeScriptやfp-ts,,Jestのようなほぼ全てのパッケージ共通で使うライブラリ,後述の設定共有用パッケージをインストールしておく
特定のパッケージのみで使うライブラリは個別にインストールする
各種ツールの設定の共有
tsconfig.jsonやESLintなどの設定は可能な限り共有したい
tsconfig.jsonの共有
プロジェクトルートのをextendsする例をよく見るけど,$ pnpm deployで別ディレクトリに出力すると動かなくなるので設定共有用パッケージを作るのがおすすめ
設定共有用パッケージを作成し,各パッケージのdependenciesに追加する or ワークスペースのdependenciesに追加する
tsconfig-sharedというパッケージ名で作っているけど名前は何でもいい
code:/packages/tsconfig-shared/base.json
{
// ベースの設定を書く(ここでは省略)
}
作成した設定ファイルをextendsに指定する
code:/sample-api/tsconfig.json
{
"extends": "tsconfig-shared/base.json",
// 追加・上書きする設定を書く(ここでは省略)
}
ESLintの設定の共有
eslint-config-sharedのようなパッケージを作成して共有する
package.jsonのmainには設定を書いたjsファイルを指定する
code:/packages/eslint-config-shared/package.json
{
"name": "eslint-config-shared",
"main": "index.js",
"dependencies": {
// 依存するプラグインなどを追加する(ここでは省略)
}
}
code:/packages/eslint-config-shared/index.js
/** @type {import('eslint/lib/shared/types').ConfigData & { parserOptions: import('@typescript-eslint/types').ParserOptions }} */
const config = {
parser: '@typescript-eslint/parser',
extends: [
'plugin:@typescript-eslint/recommended',
'prettier',
],
// その他諸々の設定を書く(ここでは省略)
};
module.exports = config;
使う側
通常のESLintの設定と同様に,eslint-configで始まっていればその後ろだけ書けばOK
code:/sample-api/.eslintrc.js
/** @type {import('eslint/lib/shared/types').ConfigData & { parserOptions: import('@typescript-eslint/types').ParserOptions }} */
const config = {
root: true,
// 追加・上書きする設定を書く(ここでは省略)
};
module.exports = config;
Prettierの設定の共有
prettier-config-sharedのようなパッケージを作成して共有する
code:/packages/prettier-config-shared/package.json
{
"name": "prettier-config-shared",
"main": "index.mjs",
"dependencies": {
"prettier": "^3.2.0"
}
}
code:/packages/prettier-config-shared/index.mjs
/**
* @type {import('prettier').Config}
*/
const config = {
singleQuote: true,
trailingComma: 'all',
};
export default config;
使う側
code:/sample-api/prettier.config.mjs
import sharedConfig from 'prettier-config-shared';
/**
* @type {import('prettier').Config}
*/
const config = {
...sharedConfig,
// 追加・上書きする設定を書く(ここでは省略)
};
export default config;