個人ブログのパッケージマネージャーをYarnからpnpmに移行した作業ログ
新年早々Yarnを利用していた個人ブログのプロジェクトをpnpmに移行したので、その際の作業ログを記事にします。
個人ブログの技術構成
私の個人ブログは以下のような技術構成を取っています。
- パッケージマネージャー: Yarn v1
- フレームワーク: Next.js
- UIライブラリ: Chakra UI
- CI: GitHub Actions
- ホスティング: Vercel
pnpm
pnpmは速さや効率性、厳格さを特徴に掲げるNode.js向けのパッケージマネージャーです。
本記事ではその特徴の詳細について記載しないため、下記のような解説記事を参考ください。
pnpmへの移行動機
npmやYarnは使った経験がありましたがpnpmはなかったので、個人ブログのリポジトリを使って技術検証してみようと思ったのが最初の動機です。
また、最近プライベートで利用しているMacのストレージがカツカツになってきたので、ディスク効率のいいpnpmに全面的に移行してローカルマシンの容量節約を図る意図もあります。
移行
pnpmの有効化
さっそくpnpmを利用できるようにします。
現在利用しているNode.jsのバージョンはCorepackが同梱されているので、Corepack経由で利用します。
$ node -v
v18.12.1
$ corepack enable
$ corepack prepare pnpm@latest --activate
私はasdfを利用してNode.jsのバージョン管理しているため、Corepack経由でインストールしたpnpmを利用するためにreshimする必要がありました。
$ asdf reshim nodejs
さらに、package.jsonにpackageManagerフィールドを追加してプロジェクトで利用するパッケージマネージャーのバージョンを指定します。
{
  "packageManager": "pnpm@7.21.0"
}
以上でpnpmを有効化できました。
 pnpm import
pnpmにはpnpm importというコマンドが用意されています。これはnpmやYarnなど他のパッケージマネージャーのlockfileからpnpm-lock.yamlを生成するコマンドです。Yarnにも同等の機能を有するyarn importコマンドが存在します。
純粋にlockfileを再生成すると、依存するパッケージのバージョンが意図せず上がってしまうことがあります。既存のlockfileから新たなlockfileを生成することで、各パッケージのバージョンを移行前と変えることなくパッケージマネージャーを移行できることが期待されます。
pnpm importを実行すると、自動でyarn.lockを検出してpnpm-lock.yamlを生成してくれました。
$ pnpm import
 pnpm install
lockfileの移行ができたので、pnpm installを実行してnode_modulesを作り直します。
$ rm -rf node_modules
$ pnpm install
 save-exact=trueにする
個人ブログのリポジトリではRenovateを使ってパッケージを更新しています。
Renovateを利用する上ではpackage.jsonにおけるパッケージバージョンのSemVer range指定(^1.0.0や~1.0.0)は利用しないことが多いです。そのため、pnpm addする際に自動でバージョンが固定(pin)されるようにします。
pnpmのドキュメントには記載されていませんが、pnpmは.npmrcのsave-exact値の設定を尊重して動作を変えてくれます。今回はこちらを利用しました。
Yarnを利用している間は.yarnrcのsave-prefix ''という設定でバージョンが固定されるようにしていたので、このファイルを削除した上で下記コマンドを実行して.npmrcを作成しました。
$ rm .yarnrc
$ pnpm config set save-exact true
# このようなファイルが生成されます
save-exact=true
CIの修正
個人ブログのリポジトリでは、GitHub ActionsでESLintやNext.jsのビルドの実行などを行っています。パッケージのインストールはYarnに依存しているため、当然修正が必要になります。
pnpmのセットアップを行うための公式のActionが用意されているためこちらを利用します。
name: lint
on:
  push:
    branches:
      - main
  pull_request:
jobs:
  eslint:
    name: eslint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
+      - uses: pnpm/action-setup@v2
      - uses: actions/setup-node@v3
        with:
          node-version-file: '.tool-versions'
-          cache: 'yarn'
-      - run: yarn --frozen-lockfile
+          cache: 'pnpm'
+      - run: pnpm install --frozen-lockfile
      - name: run eslint
-        run: yarn lint
+        run: pnpm lint
Yarnに依存していた箇所の修正
その他、コード内でYarnに依存していた箇所を修正しました。
今回のリポジトリではhusky + lint-stagedを使ってpre-commit時にESLintやPrettierを実行しています。
lint-stagedを動かすためにyarn lint-stagedというコマンドを実行するようにしていたため、この部分もpnpmに変更しました。
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
- yarn lint-staged
+ pnpm lint-staged
パッケージimportの修正
pnpmは、デフォルトではpackage.jsonに書かれていない暗黙的な依存パッケージをアプリケーションコードから参照できません。これはnpmやYarnが行っているようなパッケージの巻き上げ(hoisting)を伴うフラットなnode_modulesの構造をpnpmが採用していないためです。
npm/Yarnを使っていたときにhoistingされた暗黙的な依存パッケージをアプリケーションコードから呼び出していた場合、pnpmへ移行すると正常に参照できなくなります。
今回の個人ブログではUIライブラリとしてChakra UIを利用しています。
Chakra UIでは各種UIコンポーネントは@chakra-ui/buttonや@chakra-ui/layoutといった小さなライブラリに分割されており、@chakra-ui/reactをインストールすることで分割されたライブラリを暗黙的に利用しています。@chakra-ui/reactでは単にこれらの分割されたライブラリのコンポーネントや型をre-exportしているだけです。
VSCodeなどのエディタやIDEで自動import機能を使っていると、意図せず@chakra-ui/layoutなどからコンポーネントを直接importしてしまうケースがあります。私のリポジトリではこのケースがいくつか存在しておりビルドが失敗していたため、@chakra-ui/reactからコンポーネントをimportするように修正しました。
- import { Heading, Text } from '@chakra-ui/layout';
+ import { Heading, Text } from '@chakra-ui/react';
デプロイ
個人ブログのデプロイ先にはVercelを利用し、Vercelの提供するGitHub連携でデプロイされています。パッケージインストールコマンドは自動で検出されるプロジェクト内のlockfileによって変わります。pnpmもサポートされているため、特に設定の変更なくデプロイできました。
一方、Corepackは実験的ツールであるため、VercelのデプロイフローにおいてデフォルトではCorepackを利用することはありません。ENABLE_EXPERIMENTAL_COREPACK環境変数を1に設定することでCorepackを有効化できます。
試しにCorepackを有効化してみましたが、問題なくデプロイできました。
まとめ
大きな落とし穴もなくYarnからpnpmへの移行を完了できました。
移行作業や実際の開発作業を少し行ってみましたが特に問題もなかったため、他のリポジトリも移行していくつもりです。



Discussion