hacomono TECH BLOG

フィットネスクラブ・スクールなど施設・店舗のための会員管理・予約・決済システム「hacomono」 開発チームの技術ブログ

npm dependencies が大量になるのをなんとかしてみる

hacomono フロントエンド テックリードのみゅーとん(@_mew_ton)です

npm を使うプロジェクトを立ち上げる際、フレームワークやら、テストやらをどんどん導入していくと、一つの package.json に大量の依存パッケージが書き込まれて、管理が煩雑になるケースがしばしばあります

特にありがちなのが、 “このパッケージ、なんの目的で導入してるんだっけ?” となるなど

今回、いい感じに管理する方法を見つけたので、ご紹介します

TL; DR

  • monorepo 構造にして、 dependencies-eslint のような、特定の目的で利用する依存パッケージのみをな管理するだけのサブプロジェクトを作成する
  • 内容に package.json のみを配置し、利用するパッケージを dependencies に記載する
  • ルートの package.json 及び、利用先のプロジェクトで、 dependencies, devDependencies など適切なタイプの依存先として dependencies-eslint を設定する

太りすぎた dependencies

下はうちのプロジェクトで使っている package.json です (※開発時に使用するパッケージのみ記載しています)

{
  "name": "package-sample",
  "version": "0.0.1",
  "private": true,
  "workspaces": [
    "packages/*"
  ],
  "scripts": {
    "dev": "turbo run dev",
    "lint": "turbo run lint",
    "test": "turbo run test",
    "format": "prettier --write . --ignore-path .eslintignore",
    "dedup": "yarn-deduplicate --strategy=fewer",
    "postdedup": "yarn install"
  },
  "devDependencies": {
    "@hacomono/eslint-config-vue3": "0.0.11",
    "@hacomono/prettier-config": "0.0.11",
    "@storybook/addon-a11y": "6.5.13",
    "@storybook/addon-actions": "6.5.13",
    "@storybook/addon-essentials": "6.5.13",
    "@storybook/addon-interactions": "6.5.13",
    "@storybook/addon-links": "6.5.13",
    "@storybook/addon-postcss": "2.0.0",
    "@storybook/jest": "0.0.10",
    "@storybook/test-runner": "0.9.0",
    "@storybook/testing-library": "0.0.13",
    "@storybook/testing-vue3": "0.0.2",
    "@storybook/vue3": "6.5.13",
    "@testing-library/vue": "6.6.1",
    "@typescript-eslint/eslint-plugin": "5.42.0",
    "@vitejs/plugin-vue": "3.2.0",
    "@vue/eslint-config-typescript": "11.0.2",
    "axe-core": "4.5.1",
    "chromatic": "6.7.4",
    "eslint-config-prettier": "8.5.0",
    "eslint-config-turbo": "latest",
    "eslint-plugin-import": "2.26.0",
    "eslint-plugin-storybook": "0.6.7",
    "eslint-plugin-vue": "9.7.0",
    "eslint": "8.27.0",
    "jest-environment-node": "29.2.2",
    "jest": "29.2.2",
    "playwright": "1.27.1",
    "prettier": "2.7.1",
    "storybook-addon-designs": "6.3.1",
    "storybook-builder-vite": "0.1.23",
    "turbo": "latest",
    "typescript": "4.8.4",
    "vite": "3.2.2",
    "yarn-deduplicate": "latest"
  },
  "resolutions": {
    "postcss": "^8.0.0"
  },
  "engines": {
    "node": ">=16.18.0"
  }
}

見て分かる通り、カオスです。

このプロジェクトでは、以下の理由で devDependencies を構成しています

  • monorepo 構成を管理してのために turborepo を導入している
  • フロントエンドの基本構成として、 typescript, vue, vite を採用している
  • フォーマッターとして eslint 及びそのプラグインを大量導入している
  • テスト・ドキュメンテーションのために storybook, jest とそのプラグインを導入している

しかし、この構成では例えば axe-coreのような名前に storybook, jest などの共通のパッケージ名が含まれていないと、どういう用途で導入したパッケージがわかりづらくなるなど、管理が非常に辛くなります

dependencies の整理方法

1. パッケージを用途別に整理する

devDependencies に記載されているパッケージ群を用途別に分類してみます

今回は eslint と storybook に関する依存パッケージが多いため、これらを分類しました

eslint の依存パッケージ

  • @hacomono/eslint-config-vue3
  • @typescript-eslint/eslint-plugin
  • @vue/eslint-config-typescript
  • eslint
  • eslint-config-prettier
  • eslint-plugin-import
  • eslint-plugin-storybook
  • eslint-plugin-vue
  • eslint-config-turbo

storybook の依存パッケージ

  • @storybook/addon-a11y
  • @storybook/addon-actions
  • @storybook/addon-essentials
  • @storybook/addon-interactions
  • @storybook/addon-links
  • @storybook/addon-postcss
  • @storybook/jest
  • @storybook/test-runner
  • @storybook/testing-library
  • @storybook/testing-vue3
  • @storybook/vue3
  • @testing-library/vue
  • @vitejs/plugin-vue
  • axe-core
  • chromatic
  • playwright
  • jest-environment-node
  • jest
  • storybook-addon-designs
  • storybook-builder-vite

一見、jest (Unitテスト用ライブラリ), playwright (E2Eテスト用ライブラリ) は Storybook の分類に含めるのは変では?と思われるかもしれませんが、

これらは、Storybook の integration テストを実施するために導入しています

2. 依存管理用サブプロジェクトを作成する

eslint のみにターゲットを絞って説明します。

./packages/dependencies-eslint というサブプロジェクトを作り、そのルートに 以下の内容で package.json を作成します

{
  "name": "@hacomono/dependencies-eslint",
  "version": "0.0.1",
  "private": true,
  "dependencies": {
    "@hacomono/eslint-config-vue3": "0.0.11",
    "@typescript-eslint/eslint-plugin": "5.42.0",
    "@vue/eslint-config-typescript": "11.0.2",
    "eslint": "8.27.0",
    "eslint-config-prettier": "8.5.0",
    "eslint-plugin-import": "2.26.0",
    "eslint-plugin-storybook": "0.6.7",
    "eslint-plugin-vue": "9.7.0",
    "eslint-config-turbo": "latest"
  }
}

作成後、ルートの package.json 側で、eslint のための依存パッケージを消し、 @hacomono/dependencies-eslint に置き換えます。

{
  "name": "package-sample",
  "version": "0.0.1",
  "private": true,
  "workspaces": [
    "packages/*"
  ],
  "scripts": {
    "dev": "turbo run dev",
    "lint": "turbo run lint",
    "test": "turbo run test",
    "format": "prettier --write . --ignore-path .eslintignore",
    "dedup": "yarn-deduplicate --strategy=fewer",
    "postdedup": "yarn install"
  },
  "devDependencies": {
    "@hacomono/dependencies-eslint": "*", // ← eslint の依存パッケージのまとめ
    "@hacomono/dependencies-storybook": "*", // ← storybook の依存パッケージのまとめ
    "@hacomono/prettier-config": "0.0.11",
    "prettier": "2.7.1",
    "turbo": "latest",
    "typescript": "4.8.4",
    "yarn-deduplicate": "latest"
  },
  "resolutions": {
    "postcss": "^8.0.0"
  },
  "engines": {
    "node": ">=16.18.0"
  }
}

超すっきりしました

Pros, Cons

目的ごとに依存パッケージを整理できるので、dependencies の管理が圧倒的にしやすくなったと思います。

反面、パッケージの構造上うまく動かない可能性も無きにしもあらずのため、対応は慎重に行うべきかなと思います。

まとめ

依存パッケージだけを管理するサブプロジェクトを作る という方法で、

package.json の肥大化を防ぐ方法を今回紹介しました。

依存パッケージを整理して、きれいな monorepo 構成を維持していきたいですね。