Yarn v1からv4への移行で直面した課題と解決策

はじめに

こんにちはアソビューの村井です。この記事では、弊社の座席指定システムプロジェクトにおいて、Yarn v1からv4への移行を実施する中で実際に直面した、monorepo構成や既存のワークフローに起因する特有の課題と、その具体的な解決策に焦点を当てて紹介します。

背景

Yarn v1で直面していた課題

本プロジェクトではstring-widthに依存するパッケージが原因で、yarn.lockに不要な差分が頻発する問題に直面していました。そのため、yarn.lockのコンフリクトが日常化して開発効率を損ねていました。これは Storybook の Issue string-width dependency stops storybook from executingで報告されている内容でした。 いくつかの回避策も提示されていましたが、チーム内での運用コストや将来的なメンテナンス性を考慮し、根本的な解決を目指すことになりました。この問題を根本から解決するため、パッケージマネージャーの移行を検討することになりました。

移行方針の決定

当初のnpm移行計画

当初は、Yarn v1からnpmへの移行を検討していました。npmはNode.jsに標準でバンドルされているため、Node.jsのバージョンアップデートに伴って、パッケージマネージャーも自動的に最新版が利用可能になるという利点があります。これにより、パッケージマネージャーの独立したアップデート管理が不要となり、運用コストを削減できると考えていました。

Yarn v4への方針転換

しかし、検討を重ねる中で、以下の理由からYarn v4への移行を選択しました。

  • yarn.lock互換性のリスク: npmへの移行では、yarn.lockファイルをpackage-lock.jsonに変換する必要がありましたが、依存関係の解決方法が異なるため、一部のパッケージで互換性の問題が発生する可能性がありました。
  • CI/CDおよびワークスペースの移行コスト: CI/CDパイプラインでyarnコマンドを使用している箇所が多く、またnpm workspaceの挙動がyarn workspaceと異なる点も考慮すると、npmへの移行は設定を大幅に変更する必要があり、移行コストが高くなると判断しました。

移行プロセスで発生した課題と解決策

node_modules方式に固定

Yarn v2以降では、デフォルトのインストール方式が PnP(Plug'n'Play)に変更されています。しかし、一部のツールやライブラリは依然として node_modules の存在を前提としているため、そのままでは動作しないケースがあります。 今回の移行では、既存ワークフローやツール群との互換性を優先し、node_modules 方式に固定しました。これにより、PnP対応や設定変更にかかる移行コストを抑え、スムーズに移行できました。

プロジェクトのルートディレクトリにある.yarnrc.ymlに以下の設定を追加しました。

nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.5.3.cjs
networkConcurrency: 1

また、.yarnrc.ymlには以下の設定も追加しています。

設定の詳細説明

  • yarnPath: Yarn v4の実行ファイルの場所を指定します。.yarn/releases内の特定バージョンを使うことでチーム全体でバージョンを統一し、環境差異を防ぎます。CIではグローバルインストール不要でセットアップの高速化にも繋がります。

  • networkConcurrency: ネットワークリクエストの同時実行数を1に制限します。この設定がない場合、yarn install 時にネットワークエラーが発生して失敗することがあったため、安定性を向上させる目的で追加しました。

Git Protocolの課題

課題

アソビューでは、自社開発のデザインシステムライブラリであるasoview-designというパッケージを開発しており、これをリポジトリのクローンでインストールしていました。移行作業を開始した際、asoview-designのクローンに失敗し、fatal: repository 'path' not foundというエラーが発生していました。

解決策

HTTPS経由でのクローンが失敗していたため、SSH経由でのクローンに変更することで解決しました。

# 変更前
"asoview-design": "organization/asoview-design#v0.0.67"

# 変更後
"asoview-design": "git@github.com:organization/asoview-design.git#v0.0.67"

Yarn の公式ドキュメントには、Yarn v4における Git Protocolの厳格化について明確な記載はありません。しかし、Yarn v1時代から git+ssh:// や ssh:// 形式の扱いに関するIssueが存在します。こうした背景から、Yarn v4では依存関係の解決やプロトコル解析の処理が厳格化されたのではないかと考えています。

ローカルコマンドの実行課題

課題

Yarn v4では多くのコマンドが変更されています。本プロジェクトでは yarn workspace を使ったモノレポ運用をしているため、パスが正しく設定されずコマンドが見つからない問題が発生しました。特に、yarn workspace name script がそのままでは動作しないケースがありました。例えば yarn workspace partner test を実行してもJestのテストスクリプトが実行されず、yarn workspace admin lint ではESLintが「コマンドが見つからない」と表示される事象が確認されました。

また、各ワークスペースからワークスペースルートにある依存パッケージを直接実行できない問題も発生しました。 例えば、あるワークスペースの package.json に "dev": "next dev" を定義しても、next をワークスペースルートにインストールしている場合にはコマンド実行が失敗するという状況です。

解決策

パッケージローカルのスクリプトを実行する際はルートに定義した共通スクリプトを呼び出すように統一しました。ルートのワークスペースを直接参照したい場合は yarn run -T script を使用します。

// packages/project/package.json
{
  "scripts": {
    "g:test": "cd $INIT_CWD && vitest run",
    "eslint": "yarn g:eslint",
    "ts-compile-check": "yarn g:ts-compile-check"
  }
}
// packages/project/packages/direct/package.json
{
  "scripts": {
    "test": "yarn g:test",
    "dev": "run -T next dev",
    "lint": "run-s 'eslint -- --fix'",
    "ts-compile-check": "tsc -p tsconfig.json --noEmit",
  }
}

この問題は、公式ドキュメントやGitHubのissueでも議論されています。

CIでのコマンドの実行課題

課題

CIでは、yarn.lockファイルの整合性を保つためにyarn install --frozen-lockfileコマンドを使用していました。しかし、Yarn v4ではこのコマンドが廃止され、新しいオプションに変更されていました。

  • v1: yarn install --frozen-lockfile
  • v4: このオプションが存在しないため、CIが失敗する

解決策

v4から追加された--immutableオプションに変更することで、同じ動作を実現できます。

# 変更前(v1)
yarn install --frozen-lockfile

# 変更後(v4)
yarn install --immutable

依存関係の競合の課題

課題

本プロジェクトでは、モノレポ構成で複数のパッケージを管理しており、partner、direct、urakata-designという3つの主要なパッケージが存在します。

// 改修前
project
├── packages
│ ├── partner        # 管理画面 / MUIを使用 date-fns@2.8.0
│ ├── direct         # ゲスト向けの販売画面 / MUI非依存 date-fns@4.1.0
│ └── urakata-design # 座席指定のデザインシステム / date-fns@2.8.0

この構成において、date-fnsライブラリのバージョンが以下のように異なっていました。

  • partnerパッケージはMUIに依存しているため、date-fns@2.8.0が必要でした。
  • directパッケージとurakata-designパッケージはMUIに依存していないため、date-fns@4.1.0を使用していました。

Yarn v1ではこれらの異なるバージョンのdate-fnsを各パッケージが利用できていましたが、Yarn v4への移行時に依存関係の解決方法が変わったことで、バージョン競合が顕在化し、意図せずすべてのパッケージでdate-fns@4.1.0を参照してしまう挙動になっていました。

解決策

この問題を解決するため、以下の手順で依存関係を整理しました。

urakata-designからの依存分離

urakata-designパッケージからdate-fnsを削除しました。これにより、date-fnsのバージョン管理は、このパッケージを参照するpartnerやdirectなどの各パッケージに委ねる形にしました。また、共通で定義していたdate-fns周りのロジックをそれぞれに定義し直すことで、最小限のコストでパッケージの分離を実現しました。

partnerパッケージの対応

partnerパッケージはMUIの制約上、特定のdate-fnsバージョンに依存する必要があります。そのため、特定のバージョンを強制的に適用するresolutionsフィールドを使用して、date-fnsのバージョンを2.8.0に固定しました。

directパッケージの対応

directパッケージはMUIに依存しないため、dependenciesにdate-fnsのバージョンを4.1.0に固定しました。

// 改修後
project
├── packages
│ ├── partner        date-fns@2.8.0
│ ├── direct         date-fns@4.1.0
│ └── urakata-design 

移行の完了と検証

上記の手順で課題をすべて解決し、Yarn v4への移行が完了しました。移行後の既存機能に影響がないかを確認するため、リグレッションテストを実行し、問題がないことを検証しました。

移行後の効果と改善点

Yarn v4の導入により、string-width への依存がなくなり、これに起因して発生していた yarn.lock の不要な差分が解消されました。その結果、チーム開発におけるマージ時のコンフリクトが顕著に減少し、レビューやリリース作業などの効率が向上しました。

Yarn v1からv4への移行は、当初予想していたよりも多くの課題がありましたが、段階的なアプローチにより、成功裏に完了することができました。 この移行経験を基に、他のプロジェクトでも同様の移行を進める予定です。また、Yarn v4のPlug'n'Play(PnP)のような新機能を活用して、さらに効率的な開発環境の構築を目指していきたいと思っています。

We're hiring!

アソビューでは、より良いプロダクトを世の中に届けるため、共に挑戦していただけるエンジニアを募集しています。カジュアル面談も実施していますので、興味を持たれた方はぜひエントリーをお願いいたします!

www.asoview.com

speakerdeck.com

技術情報を発信する公式アカウントもございます。ぜひフォローをお願いします! https://twitter.com/Asoview_dev