Sansan Tech Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

Vol. 01 Prettierにおけるインポート順ソートライブラリを prettier-plugin-sort-imports に移行した話 ─ 技術的判断を支えたADR

この記事は、Bill One 開発 Unit ブログリレー2025の第1弾になります! こんにちは、技術本部 Bill One Engineering Unit の今村です。社内では「ゆせくん」と呼ばれています。2025年4月にSansan株式会社に新卒として入社しました。普段はフルスタックエンジニアとして働いています。

今回は、TypeScript のインポートを自動でソートする Prettierプラグイン についてお話ししたいと思います。

TypeScript を利用した開発では、コードフォーマッタとして Prettier を使っているプロジェクトが多いでしょう。 Bill One でも、フロントエンドの開発に Prettier を採用しています。 さらに、インポート順を自動でソートするために prettier-plugin-organize-imports というプラグインを利用していました。

しかし、ある日からインポート順の自動ソートが正しく機能していないことが発覚しました。この問題は、 prettier-plugin-organize-imports から @IanVS/prettier-plugin-sort-imports というプラグインに移行することで解決しました。

本記事では、なぜ prettier-plugin-organize-imports が機能しなくなったか、また、なぜ @IanVS/prettier-plugin-sort-imports を採用したかなどについてお話しします。

目次

なぜインポート順を自動ソートする必要があるのか

そもそも、なぜインポート順を自動でソートする必要があるのでしょうか。私は、次の2つの点でメリットがあると考えています。

  • 関連するモジュールをまとめて整理できる
  • 依存関係を把握しやすくなる

それぞれを見ていきます。

関連するモジュールをまとめて整理できる

開発を続けていると、インポート文はどんどん増えていきます。中には、ファイルの冒頭がほとんどインポート文で埋まってしまうこともあります。こうした状況で、インポートの自動ソートがないと、次のように雑然としたファイルが生まれます。

// --- ソートされていない例 ---
import { useUser } from "../hooks/useUser";
import { useEffect } from "react";
import fs from "node:fs";
import { UserCard } from "../components/UserCard";
import React, { useState } from "react";

上記のように、関連するモジュールがバラバラに並んでいると、コード全体の構造を把握しづらくなります。

自動ソートを適用すると、次のように整理されます。

// --- 自動ソート後 ---
import fs from "node:fs";
import React, { useEffect, useState } from "react";
import { UserCard } from "../components/UserCard";
import { useUser } from "../hooks/useUser";

これにより、可読性が向上するだけでなく、コードレビュー時にも「どの変更が本質的な修正か」を判断しやすくなります。 結果として、レビュワーの認知負荷も軽減されます。

依存関係を把握しやすくなる

インポート順を統一するもう一つの目的は、依存関係の構造を視覚的に整理することです。近年では、Prettier の代替として注目されている Biome においても、インポート順序の整理はルール化されています。特に「距離(distance)」という概念を用いて、モジュールの位置関係に基づいた並べ方を推奨しています。

Sources are ordered by “distance”. Sources that are “farther” from the current module are put on the top, sources “closer” to the user are put on the bottom.
Biome Docs: Import and export sorting

// --- ソートされていない例 ---
import { useHoge } from "../hooks/useHoge";
import { formatDate } from "src/utils/formatDate";
import fs from "node:fs";
import React from "react";
import { UserCard } from "../components/UserCard";
import { Button } from "@ui";
import { useEffect } from "react";
import path from "node:path";

上記はさまざまなモジュールが煩雑に並んでいるため、可読性の観点で望ましくありません。一方で、自動ソートを適用すれば次のように整理できます。

// --- 自動ソート後 ---
// 1. Node.js 標準モジュール
import fs from "node:fs";
import path from "node:path";

// 2. 外部ライブラリ(npm パッケージ)
import React, { useEffect, useState } from "react";

// 3. 内部モジュール(packagesなど)
import { Button } from "@ui";

// 4. 絶対・相対パスでの内部モジュール
import { formatDate } from "src/utils/formatDate";
import { UserCard } from "../components/UserCard";
import { useHoge } from "../hooks/useHoge";

このように整列することで、外部依存 → 内部依存 → ローカルコード という流れが視覚的に整理されます。結果として、ファイル全体の依存構造を「上から下へ読むだけで理解できる」ようになり、コードリーディングやレビュー時の理解コストも下がります。

自動インポート整形が効かなくなっていた

本題です。

フロントエンドの改善を進めていた際、いくつかのファイルのインポートの順番がソートされていないことに気づきました。当時利用していた prettier-plugin-organize-imports は確かに導入済みでしたが、何度実行してもソートが反映されませんでした。

原因の特定:プラグインの非互換

調査の結果、次の構成に問題があることがわかりました。

# prettierrc.yaml
plugins:
  - "prettier-plugin-organize-imports"
  - "@prettier/plugin-oxc"

当初は prettier-plugin-organize-imports により、インポート順の自動ソートは正常に動作していました。しかし後に、Prettier の実行高速化を目的として @prettier/plugin-oxc を追加導入したところ、このプラグインが prettier-plugin-organize-imports に対応していなかったことが判明しました。つまり、@prettier/plugin-oxc が、ソート機能を持つプラグインとの互換性を欠いていたことが原因でした。

解決方針の検討

@prettier/plugin-oxc は無効化したくない

この課題解決に向けて、@prettier/plugin-oxc を無効化するという選択肢もありました。それが一番シンプルだからです。

@prettier/plugin-oxc は、Prettierの実行を高速化するプラグインです。Rust 製の高速なパーサ Oxc (Oxidized Compiler) を内部的に利用しており、JavaScript / TypeScript の構文解析を効率化します。以下のドキュメントで詳しく説明されています。

prettier.io

社内に残されていた@prettier/plugin-oxc導入時の ADR (Architecture Decision Record) を参照した結果、プロジェクト全体で約2.8倍の高速化が確認されていました。Prettierはローカル開発やCIパイプラインなどあらゆる場面で実行されているため、無効化すると開発体験が大きく悪化してしまいます。したがって、今回の方針は「@prettier/plugin-oxc を維持したまま、インポートの自動ソートを正しく動作させる」としました。

ADRが支えた意思決定

今回の判断をスムーズに進められた背景には、上記の通り、ADRの存在があったからです。ADRとは、アーキテクチャや技術選定など、技術的意思決定の経緯や根拠を文書化する仕組みのことです。どんな課題を解決しようとしていたのか、どんな選択肢が検討されたのか、最終的にどう合意されたのか、その一連の流れを明確に残されるようになっています。

実際、今回の @prettier/plugin-oxc 導入時のADRにも、導入の背景や比較検討の経緯、想定されるリスク、そして最終的な採用理由などが整理されていました。こうした記録が残っていたおかげで、今回「非互換が発生した」という問題が起きた際も、"@prettier/plugin-oxcを外すか・維持するか"という判断を迅速に行うことができました。

@IanVS/prettier-plugin-sort-imports を採用する

最終的に採用したのは、@trivago/prettier-plugin-sort-imports をフォークした @IanVS/prettier-plugin-sort-imports です。このプラグインは @prettier/plugin-oxc に対応しており、同一モジュールからのインポート統合や、型インポート(import type)と値インポートの自動結合など、独自機能を柔軟に設定できます。

独自フォークであるため、将来的なメンテナンスリスクは残ります。また、ソースコードを書き換えるという性質上、悪意あるコードが混入するリスクもゼロではありません。しかし、フォーク理由やメンテナンス状況、コミュニティからの支持状況(スター数)などを確認し、信頼性と保守性を総合的に判断しました。また、本プラグインはインポートのソートにのみ関わる補助的なツールであり、アプリケーション実行ロジックには直接の影響はありません。そのため、問題が発生した場合でも容易にロールバック可能であり、採用リスクは限定的と判断しました。

READMEにも記載されている通り、importOrder のオプションではNode.js 組み込みやサードパーティ、独自指定でインポートのソート順を柔軟に設定することができます。デフォルトでは以下のように設定されています。

[
    '<BUILTIN_MODULES>', // Node.js built-in modules
    '<THIRD_PARTY_MODULES>', // Imports not matched by other special words or groups.
    '^[.]', // relative imports
],

prettier コマンドの実行速度調査

今回の問題は、フロントエンドのフォーマッタ設定全体に影響する変更だったため、この変更提案をADRとして起票しました。

ADRを起票する過程で、prettier-plugin-organize-imports@IanVS/prettier-plugin-sort-imports の実行速度を比較し、実際の差分を計測しました。これは、@IanVS/prettier-plugin-sort-imports に切り替えたことで Prettier の実行速度がどの程度変化するかを定量的に把握し、影響を正確に評価するためです。仮に大きな遅延が発生するようであれば、本来の目的である開発体験の向上に反してしまうため、定量的な検証を行いました。

実行コマンド(フラグ) ライブラリ 平均時間 (s) 標準偏差 (σ) 比率 (vs organize-imports)
--check organize-imports 1.36 0.07 1× (基準)
--check sort-imports 1.53 0.06 0.89× 遅い
--write organize-imports 1.27 0.04 1× (基準)
--write sort-imports 1.57 0.06 0.81× 遅い

補足:
organize-importsprettier-plugin-organize-imports
sort-imports@IanVS/prettier-plugin-sort-imports
・ すべてのコマンドは prettier --experimental-cli で実行

考察

@IanVS/prettier-plugin-sort-imports の導入により、実行時間が平均で 200〜300ms 程度増加しました。しかし、prettier-plugin-organize-imports が正常に動作していない現状を踏まえると、この差は、実用上十分に許容できる範囲と判断しました。また、標準偏差(σ)は 0.04〜0.07 秒に収まり、実行時間の安定性にも問題はありませんでした。

なお、コマンドの実行時間の計測には hyperfine を使用しました。 github.com

まとめ

今回の事例では、prettier-plugin-organize-imports@prettier/plugin-oxc に対応していないことが原因で、インポートの自動ソートが動作しなくなっていました。高速化を目的として導入した @prettier/plugin-oxc を維持しながら問題を解決するために、最終的に @IanVS/prettier-plugin-sort-imports を採用しました。

この変更により、@prettier/plugin-oxc の恩恵(約2.8倍の高速化)を維持したまま、安定してインポートの自動ソートを実現できました。

また、本対応を進める中で改めて感じたのは、ADR(Architecture Decision Record)を残しておく重要性です。過去の意思決定や議論が文書として残っていたことで、今回の判断を迅速に下すことができました。特に、チームやプロジェクトが大きくなるほど「なぜこの構成にしたのか」を追跡できることは大きな価値を持つことを実感しました。

そして今回、自分自身もこの変更提案をADRとして起票しました。書く過程で「なぜこの判断をしたのか」を整理でき、開発組織に説明しやすくなっただけでなく、将来この判断を読む誰かに向けて情報を残す意識が生まれました。小さな設定変更のように見えることでも、全体に影響する技術判断をどう記録し、どう共有するか、その重要性を改めて感じた経験になりました。

同様の課題に直面しているチームにとって、今回の知見が参考になれば幸いです。

Sansan技術本部ではカジュアル面談を実施しています

Sansan技術本部では中途の方向けにカジュアル面談を実施しています。Sansan技術本部での働き方、仕事の魅力について、現役エンジニアの視点からお話しします。「実際に働く人の話を直接聞きたい」「どんな人が働いているのかを事前に知っておきたい」とお考えの方は、ぜひエントリーをご検討ください。

© Sansan, Inc.