何年もかけてようやく心から理解した真実があります:コードの見た目は、コードが何をするかとほぼ同じくらい重要です。 きちんとフォーマットされたコードはレビューが速く、バグが少なく、メンテナンスが劇的に楽になります。そして逆に、適切にミニファイされたコードは読み込みが速く、ユーザーの帯域幅を節約します。
このコインの両面について話しましょう。
フォーマットが思っている以上に重要な理由
タブ vs スペース、セミコロンの有無、波括弧の位置について、コードレビューで何時間も議論するチームを見てきました。それは機能をリリースするために使われていない時間です。一貫したフォーマットはこれらの議論を完全に排除します。
しかし、チームの調和だけの問題ではありません。一貫してフォーマットされたコードはバグを見つけやすくします。これを考えてみてください:
このsendNotification()呼び出しは管理者だけでなく毎回実行されます — インデントが誤解を招いています。ほとんどのフォーマッターが強制する必須の波括弧があれば、このバグは明白です:
フォーマットのベストプラクティス
スタイルを選んで自動化しましょう。 人間に一貫したフォーマットを期待しないでください。Prettierを使いましょう — 意見が強いツールですが、それがポイントです。保存時に実行されるように設定すれば、フォーマットについて二度と考える必要はありません。
インデント: 2スペースがJavaScript/TypeScriptの慣例です。Googleスタイルガイド、Airbnbスタイルガイド、Standardスタイルすべてがこれに同意しています。
セミコロン: 使いましょう。はい、JavaScriptにはASI(Automatic Semicolon Insertion)がありますが、よく知られた落とし穴があります。セミコロンを付けましょう。
行の長さ: 80-100文字以内に抑えましょう。長い行は横スクロールを引き起こし、diffを読みにくくします。
ミニフィケーションの実際の仕組み
ミニフィケーションはフォーマットの逆です — プロダクション用にコードを可能な限り小さくします。Terserのようなミニファイアが行うことは以下の通りです:
- 空白とコメントの削除 — すべてのスペース、タブ、改行、
// TODO: fix laterコメント?消えます。 - 変数名の短縮 —
userAccountBalanceがaになります。calculateMonthlyPaymentがbになります。ローカル変数のみ — 外部コードが参照する可能性のあるものは名前変更しません。 - デッドコードの除去 — 一度も呼ばれない関数は削除されます。
- 定数の事前計算 —
const TAX_RATE = 0.2; total * (1 + TAX_RATE)がtotal*1.2になります。
結果は?典型的に50-70%のファイルサイズ削減です。ビフォー・アフターをどうぞ:
前(読みやすい、147バイト):
後(ミニファイ済み、62バイト):
同じ機能で58%小さくなります。
ソースマップを忘れずに
ミニファイされたコードは読めないため、本番エラーのデバッグが辛くなります。ソースマップはミニファイされたコードを元のソースにマッピングすることでこの問題を解決します。すべてのモダンなビルドツールがソースマップを生成します — エラートラッキングサービス(Sentry、Bugsnagなど)にアップロードしていることを確認してください。
実践的なワークフロー
実践的なワークフロー
開発時:フォーマットされた読みやすいコードを書きましょう。自動フォーマットとリンティングにPrettier + ESLintを使いましょう。本番では:ビルドツール(webpack、Vite、esbuild)を通じてすべてをミニファイしましょう。
Prettier + ESLintを60秒でセットアップ
まだフォーマットを自動化していないなら、最速のセットアップはこちらです:
そしてpackage.jsonにフォーマットスクリプトを追加します:"format": "prettier --write src/**/*.{js,ts,jsx,tsx}"。一度実行してコードベース全体をフォーマットし、エディタを保存時にフォーマットするように設定しましょう。最初のdiffは巨大かもしれませんが、その後はフォーマットの議論は永遠に終わりです。
Tree Shaking vs ミニフィケーション
この2つを混同する人が多いですが、これらは一緒に機能する異なる最適化テクニックです:
- ミニフィケーションは、空白の削除、名前の短縮、式の事前計算によって個々のファイルを小さくします。使われていないエクスポートは削除しません。
- Tree shakingはモジュール間の未使用コードを除去します。lodash-esから
{ debounce }だけをインポートすると、tree shakingはバンドルから他のすべてを除去します。
どちらもプロダクションビルドに不可欠です。Viteやesbuildのようなモダンなバンドラーは両方を自動的に行います — tree shakingが依存関係グラフを分析できるように、CommonJS(require)ではなくESモジュールのインポート(import)を使っていることを確認してください。
よくあるミニフィケーションの落とし穴
1. 本番でfunction.nameに依存すること。 ミニファイアは関数名を変更するので、myFunction.nameは本番で"a"や同様に役に立たないものを返します。安定した関数名が必要な場合(ロギング、エラートラッキング、リフレクション用)は、代わりに明示的な文字列識別子を使いましょう。
2. eval()を使うコードのミニファイ。 eval()は名前で任意の変数を参照できるため、ミニファイアはそのスコープ内の何も安全に名前変更できません。ほとんどのミニファイアはeval()を含む関数での名前変更をスキップしますが、これは最適化を大幅に制限します。可能な限りeval()を完全に避けましょう。
3. ミニファイされたビルドをテストしないこと。 一部のバグはミニフィケーション後にのみ発生します — 特にプロパティ名のマングリング周りで。テストスイートは開発ビルドだけでなく、常にプロダクションビルドに対して実行しましょう。
バンドルサイズの測定
ミニフィケーションは、何から始めているかを知って初めて意味があります。バンドルサイズを確認する簡単な方法はこちらです:
これらのツールは、どの依存関係がバンドルを膨らませているかを正確に示すビジュアルツリーマップを生成します。驚くかもしれません — 以前、moment.jsのロケールが最終バンドルの500KBを占めていたプロジェクトを見つけたことがあります。dayjsに切り替えることで即座に490KB節約できました。
Prettier vs ESLint:同じものではありません
これだけは言わせてください。この混乱を*あちこち*で見かけるからです:PrettierとESLintは同じツールではありません。同じ仕事すらしていません。それなのに、開発者がこの2つを交換可能なものとして扱っているのを常に見かけます。説明させてください。
Prettierはコードフォーマッターです。コードの*見た目*を気にします — 空白、改行、セミコロン、末尾のカンマ、引用符のスタイル、インデント。それだけです。コードが実際に動くかどうかは知りもしないし気にもしません。データベース全体を削除する関数があっても、Prettierはきれいにインデントされていることを確認するだけです。
ESLintは一方、リンターです。コードの*品質*を気にします — 未使用の変数、到達不能なコード、潜在的なバグ、不足しているエラーハンドリング、アクセシビリティの問題。土曜の深夜2時にアラートで叩き起こされるような問題をキャッチしてくれます。
しかし問題は — ESLintにも*組み込みの*フォーマットルールがいくつかあり、そこが厄介なところです。両方のツールを適切に設定せずに実行すると、お互いに喧嘩します。Prettierがある方法でコードをフォーマットし、ESLintが別の方法にすべきだと文句を言い、ESLint用に直すと、Prettierが再フォーマットし...フラストレーションの無限ループです。
解決策はとてもシンプルです:eslint-config-prettierを使いましょう。Prettierと競合するすべてのESLintルールをオフにして、ESLintがコード品質に集中し、Prettierがフォーマットを担当するようにします。もう喧嘩はありません。
"prettier"がextendsの配列の最後にあることに注目してください。これが重要です — 上にリストされたプラグインのフォーマットルールをオーバーライドする必要があるからです。ESLint/Prettierの競合をデバッグするのに何時間もかけたチームが、単に順序が間違っていただけだったのを見たことがあります。これは信じてください。
おすすめの設定:Prettierに見た目のすべてを任せ、実際にバグをキャッチするESLintルールを設定しましょう。Prettierがすでに処理しているセミコロンについてESLintに文句を言わせるのは無駄です。
タブ vs スペース大論争(決着済み)
さて、プログラミングの聖戦について話しましょう。タブかスペースか?この議論で友情が壊れるのを見てきました。*何日も*続くSlackスレッドを見てきました。シニア開発者がタブを擁護する2,000語のConfluenceページを書くのを実際に目撃しました。壮大で、完全に不必要でした。
実際のデータを見てみましょう。Stack Overflow開発者調査は一貫してスペースがより人気であることを示しています — 開発者の約60-65%がスペースを好みます。GitHubの公開リポジトリの分析も同様の結果を示しています。
しかし興味深いのは、言語によって大きく異なることです。Goはタブを使います — これは議論の余地すらなく、gofmtがタブを強制し、誰もgofmtに逆らいません。Pythonは4スペースを使います — PEP 8がそう言っており、PEP 8にも逆らいません。JavaScriptとTypeScript? コミュニティは概ね2スペースに落ち着いており、ほぼすべての主要なスタイルガイドが同意しています。
タブのアクセシビリティに関する議論は実際に説得力があります — タブなら各開発者が好みの表示幅を設定でき、これは視覚障害のある開発者にとって重要です。これはタブを好む現実的で正当な理由です。
でも知っていますか?ここに*本当の*正解があります。心からそう思います:フォーマッターが強制するものを使って、議論をやめましょう。 プロジェクトがPrettierでtabWidth: 2とuseTabs: falseを使っているなら、2スペースを使います。以上。フォーマッターが決定を下し、あなたはそれを受け入れ、本当に重要なことにエネルギーを使いましょう — 例えば、誰かが検索ボックスに絵文字を入力するとアプリがクラッシュするかどうか、とか。
フォーマットの議論に費やす人生は短すぎます。ロボットに決めさせましょう。
モダンなミニフィケーション:esbuild、SWC、そしてスピード革命
長年、TerserはJavaScriptミニフィケーションの王様でした。UglifyJSに取って代わり、実戦で鍛えられ、素晴らしく動作していました。唯一の問題?遅いのです。大規模なコードベースでは*本当に*遅い。「コーヒーを取りに行く」レベルの遅さではなく — 「CIパイプラインを見つめながらキャリアの選択を再考する」レベルの遅さです。
そこにesbuildが登場し、すべてを変えました。Goで書かれたesbuildはTerserより10-100倍速いです。誇張ではありません — ベンチマークはほとんどコミカルです。Terserがミニファイに30秒かかるプロジェクト?esbuildは300ミリ秒で完了します。初めて見た時、あまりに速く終わったので本当に何か壊れたと思いました。
SWCはもう一つの競合で、Rustで書かれています。純粋なミニフィケーションではesbuildほど速くはありませんが、より完全なツールチェーンです — トランスパイル、バンドル、ミニフィケーションをすべて1つで処理します。Next.jsを使っているなら、すでに裏でSWCを使っています。
中規模プロジェクト(約500のJSファイル)での大まかな比較はこちらです:
| Tool | Language | Minification Time | Notes |
| Terser | JavaScript | ~25s | Battle-tested, most compatible |
| esbuild | Go | ~0.3s | Blazing fast, some edge cases |
| SWC | Rust | ~0.8s | Full toolchain, great ecosystem |
速度の違いは本当に重要でしょうか?正直、5秒のビルドの小さなプロジェクトでは、おそらく重要ではありません。しかし、大規模なモノレポでCI/CDを実行していてビルドパイプラインが1日50回走るなら?それらの分数は急速に積み上がります。TerserからesbuildへのCIの切り替えだけで10分以上短縮したチームを見てきました。
良いニュースは、Viteを使っているなら、開発ビルドにはすでにesbuildが、プロダクションにはRollup(Terserまたはesbuildとともに)が使われているということです。Next.jsはSWCを使っています。Angularもesbuildを実験しています。エコシステムはここで急速に動いています。
一つ注意:esbuildとSWCは常にTerserとバイト単位で同一の出力を生成するわけではありません。稀なケースでは、Terserのより積極的な最適化がわずかに小さなバンドルを生成します。しかし、おそらく1-2%の差です — ほとんどの場合、100倍のスピード向上の価値は十分にあります。
CSSとHTMLのミニフィケーションも
JavaScriptの話をしてきましたが、CSSとHTMLのミニフィケーションも見逃さないでください。真剣に、CSSバンドルがJavaScriptより*大きい*プロジェクトを見たことがあります。特にTailwindのようなユーティリティファーストのフレームワークを使っている場合(PurgeCSSが処理する前)。
CSSには、cssnanoが定番ツールです。空白を除去する以上のことをします — 重複ルールのマージ、色をより短い形式に変換(#ff0000が#f00やredに)、冗長なプロパティの削除、calc()式の最適化も行います。一般的なスタイルシートで30-50%の節約が期待できます。
HTMLについては、html-minifier-terserがタグ間の空白を除去し、オプションの閉じタグを削除し、インラインCSSとJSをミニファイし、HTMLコメントを削除し、boolean属性を折りたたみます。驚くほど効果的で — HTMLが多いページで20-30%の削減を見たことがあります。
そしてここからが良い話です:Angular、React、Vue、またはビルドステップを持つほぼすべてのモダンフレームワークを使っているなら、これはすでに自動的に行われています。 ビルドツールがプロダクションビルドの一部としてCSSとHTMLのミニフィケーションを処理します。しかし、裏で何が起きているかを知ることは本当に役立ちます — 特にプロダクションHTMLがソースと一致しない理由をデバッグする必要がある時や、最後のクリティカルレンダーパスを最適化しようとしている時に。
CI/CDパイプラインでのコードフォーマット
いいですか、「保存時にフォーマット」の問題はこれです — *チームの全員*が正しく設定していないと機能しません。そして以前これで痛い目を見ました。こんなシナリオを知っていますよね:誰かがチームに加わり、リポをクローンし、変更を始め、5行の実際のコードに400のフォーマット変更が混ざったPRを提出する。そのPRをレビューするのは悪夢です。
解決策は?CIでフォーマットを強制しましょう。 フォーマットされていないコードをマージすることを不可能にします。方法はこちらです:
ステップ1:CIチェックを追加。 CIパイプラインにprettier --check .を追加します。どのファイルもPrettierのフォーマットに一致しない場合、コード1で終了します。議論なし、討論なし — CIがルールです。
ステップ2:pre-commitフックを追加。 フォーマットの問題をCIでキャッチするのは素晴らしいですが、コードが*コミットされる前に*キャッチする方がさらに良いです。そこでHuskyとlint-stagedの出番です。
lint-stagedの素晴らしいところは、コードベース全体ではなく、実際に変更したファイルだけに対して実行されることです。そのため、pre-commitフックは30秒ではなく1-2秒で済みます。1秒のフックを無効にする人はいません。
「フォーマットはPRの摩擦の絶え間ない原因」から「フォーマットについて文字通り考えることがなくなった」まで、1週間でたどり着いたチームを見てきました。15分のセットアップが千倍の価値を生む、そういう類のものです。
実際のバンドルサイズケーススタディ
本当の数字について話しましょう。「バンドルを小さく保つ」という抽象的なアドバイスは、文脈がなければあまり役に立ちません。これらのシナリオをプロダクションプロジェクトで実際に見てきましたが、その違いは本当に衝撃的です。
ケース1:lodash vs lodash-es。 これは定番です。import _ from 'lodash'として_.debounceだけを使うと、ライブラリ全体をインポートしています — ミニファイ + gzip圧縮で71.5 KB。 import { debounce } from 'lodash-es'に切り替えてtree shakingすると、その関数だけで約1.5 KBになります。98%の削減です。Bundlephobiaで自分で確認してみてください。
ケース2:moment.js vs dayjs。 Moment.jsは何年もの間、日付処理のゴールドスタンダードでしたが、ミニファイ + gzip圧縮で72.1 KBです — しかもデフォルトですべてのロケールデータを含みます。Day.jsはほぼ同じAPIを持ちながら2.9 KBです。タイプミスではありません。基本的に同じ機能で72 KB vs 3 KBです。Moment.jsチーム自身が今や代替案を推奨しています。
ケース3:アイコンライブラリ。 これは常に人々を驚かせます。import { FaHome } from 'react-icons/fa'なら大丈夫です — これはtree-shake可能です。しかし一部のアイコンライブラリはtree shakingに対応しておらず、5つしか必要ないのに何百ものSVGアイコンをインポートしてしまいます。アイコンのインポートがバンドルに200KB以上追加しているのを見たことがあります。アイコンライブラリがtree shakingをサポートしていることを常に確認するか、アイコンを個別にインポートしましょう。
ケース4:日付フォーマットライブラリ。 特定のタイムゾーンで1つの日付をフォーマットする必要がありましたか?以前、moment-timezone(すべてのタイムゾーンデータを含むフルバージョン)をインポートしたプロジェクトを見たことがあります — ミニファイ + gzip圧縮で97 KB — たった1つの日付をフォーマットするためだけに。ネイティブのIntl.DateTimeFormat APIは、バンドルコストゼロでほとんどのタイムゾーンフォーマットをネイティブに処理します。
ここでの教訓は「依存関係を絶対に使うな」ではありません — それはばかげています。教訓は:何をインポートしていて、それがどれだけのコストかを知りましょう。 あの輝かしい新しいライブラリを追加する前に、npx source-map-explorer build/static/js/*.jsを実行するか、Bundlephobiaをチェックしましょう。遅いモバイル接続のユーザーが感謝してくれるでしょう。
自分で試してみましょう
散らかったコードをすぐにきれいにしたいですか?JavaScript Formatterに貼り付ければ、即座に読みやすい出力が得られます。プロダクション用に縮小する準備はできましたか?JavaScript Minifierが最小限まで削ぎ落とします。そしてコードが有効かどうか確信がない場合は、まずJavaScript Validatorで確認しましょう。