yarn pnp 環境化で VSCode 拡張機能の prettier を動かす

yarn pnp で作成したプロジェクトで VSCode 拡張機能の prettier が動かなかったので対応方法を紹介します。

やりたいこと

yarn pnp で作成したプロジェクトでプロジェクトの prettier とフォーマットルールを使って VSCode 拡張機能の prettier (prettier-vscode) を動かしたかったのですが、ハマったので事象と対応方法をメモしておきます。

環境

OS
Microsoft Windows 22H2
Node.js
v22.12.0
yarn
v4.5.3
prettier
v3.4.2
prettier-vscode
v11.0.0

事象

以下の手順でセットアップしたのですが prettier-vscode が動きませんでした。

yarn プロジェクト作成と prettier インストール

  1. yarn プロジェクト作成

    yarn init
  2. prettier インストール

    yarn add --dev --exact prettier
  3. フォーマットルール作成

    .prettierrc
    {
      "singleQuote": true
    }
  4. この時点で yarn prettier . --write はワークしている

VSCode 拡張機能 prettier-vscode のインストールとセットアップ

  1. VSCode 拡張機能 Prettier - Code formatter - Visual Studio Marketplace をインストールする

  2. 前節で作成した yarn プロジェクトルートに .vscode フォルダを作成し、settings.json ファイルを作成し以下内容で保存する

    {
      // フォーマッターを prettier-vscode にする
      // https://github.com/prettier/prettier-vscode?tab=readme-ov-file#default-formatter
      "editor.defaultFormatter": "esbenp.prettier-vscode",
    
      // 保存時に自動フォーマットをオンにする
      // https://github.com/prettier/prettier-vscode?tab=readme-ov-file#format-on-save
      "editor.formatOnSave": true,
    }

    時々プロジェクトの .prettierrc を prettier-vscode に認識させるために "prettier.configPath": ".prettierrc" のような設定が必要と記載されているサイトがあったりしますが、prettier-vscode はデフォルトでプロジェクトにある prettier が許容しているルールファイル (Configuration File · Prettier)を参照してくれるので不要です。

  3. npm プロジェクトではこれで動くはずですが、.js ファイルを作成して適当なスクリプトを書いて保存してみても全くフォーマットされません

原因

VSCode で prettier-vscode のログを見てみると以下のエラーが出ていました。

["ERROR"] Failed to load module. If you have prettier or plugins referenced in package.json, ensure you have run `npm install`

prettier-vscode の仕様はプロジェクトの package.jsonprettier があれば、プロジェクトの node_modules 内の prettier を使用し、VSCode の設定で prettier.resolveGlobalModulestrue になっていればユーザーグローバルにインストール (npm install -g) された prettier を使用し、何れにも当てはまらなければ prettier-vscode にバンドルされている prettier が使用されるようです。

This extension will use prettier from your project’s local dependencies (recommended). When the prettier.resolveGlobalModules is set to true the extension can also attempt to resolve global modules. Should prettier not be installed locally with your project’s dependencies or globally on the machine, the version of prettier that is bundled with the extension will be used.
GitHub - prettier/prettier-vscode: Visual Studio Code extension for Prettier

上記エラーは package.jsonprettier があるのに node_modules 内の prettier が見つからなかったので発生しているようです。

yarn pnp (Plug’n’Play: プラグアンドプレイ)では npm の node_modules のようにインストールしたパッケージをプロジェクト毎に展開しません。インストールしたパッケージはユーザーグローバル (%LOCALAPPDATA\Yarn\Berry\cache%) で保有し、各プロジェクト内の .pnp.cjs を通じて必要なパッケージがロードされます。

ちょっと長いですが、公式ドキュメントの説明を引用しておきます。

If you look into the files in your project, you may notice the absence of a node_modules folder. This is unusual! We regularly get asked on Discord where the folder is, by people thinking yarn install silently failed.
The thing is, this is actually expected! The way Yarn PnP works, it tells Yarn to generate a single Node.js loader file in place of the typical node_modules folder. This loader file, named .pnp.cjs, contains all information about your project’s dependency tree, informing your tools as to the location of the packages on the disk and letting them know how to resolve require and import calls.
Plug’n’Play | Yarn

さて yarn pnp では各プロジェクトにパッケージを展開せず、.pnp.cjs を通じてパッケージがロードされるという部分を何とかしないと prettier-vscode は動きそうにないですね。

対応

プロジェクトの .yarnrc.ymlnodeLinker: node-modules を設定すれば node_modules に展開されるようになりますが、これは pnp の恩恵 (Plug’n’Play | Yarn) が失われ本質的ではありません。

Define how Node packages should be installed.
Yarn supports three ways to install your project’s dependencies, based on the nodeLinker setting. Possible values are:

  • If pnp, a single Node.js loader file will be generated.
  • If pnpm, a node-modules will be created using symlinks and hardlinks to a global content-addressable store.
  • If node-modules, a regular node_modules folder just like in Yarn Classic or npm will be created.

Settings (.yarnrc.yml) | Yarn

どうしたものかと色々調べていたところ公式ドキュメントにこんなページが。

Why are SDKs needed with Yarn PnP?
Yarn PnP works by generating a Node.js loader, which has to be injected within the Node.js runtime. Many IDE extensions execute the packages they wrap (Prettier, TypeScript, …) without consideration for loaders. The SDKs workaround that by generating indirection packages. When required, these indirection automatically setup the loader before forwarding the require calls to the real packages.
Editor SDKs | Yarn

Editor SDK なるものが用意されており、まさに「yarn pnp では各プロジェクトにパッケージを展開せず、.pnp.cjs を通じてパッケージがロードされる」という部分を何とかしてくれると書かれています。

早速上記ページに沿って Editor SDK をインストールします。

yarn dlx @yarnpkg/sdks vscode

適当な JavaScript を保存してみると、無事にフォーマットされました!

prettier-vscode のログを見てみると prettier がプロジェクトの .yarn/sdks/prettier に解決されるようになっていることが確認できます。

["INFO"] PrettierInstance:
{
  "modulePath": "c:\\path\\to\\project\\.yarn\\sdks\\prettier\\index.cjs",
  "messageResolvers": {},
  "version": "3.4.2"
}

あとがき

yarn pnp のプロジェクトで VSCode 拡張機能の prettier-vscode をプロジェクトの prettier を使って動かす方法を紹介しました。Editor SDK は yarn dlx でインストールするのでプロジェクトのパッケージ依存関係に影響を与えませんが、その分各開発者のプロジェクトインストール作業がひと手間増えます。プロジェクトでエディタが固定されている場合はひょっとするとプロジェクトの依存に入れちゃってもよいのかもしれませんが、yarn add でインストールした場合も、問題なく動くかは検証できていません。