コード圧縮とは何か?
コード圧縮(Code Minification)とは、空白文字、コメント、長い変数名、冗長な構文など、ソースコードから不要な文字をすべて削除する自動化プロセスです。コードの実行時の動作は一切変えずに、機能的に同一でありながら大幅に小さなファイルを生成します。ダウンロードが速くなり、パースが速くなり、サーバーの帯域幅コストも削減できます。
この手法の起源は2000年代初頭にまでさかのぼります。当時はダイヤルアップ接続が主流で、1バイトも無駄にできませんでした。開発者は手作業でJavaScriptファイルのコメントや空白を削除していました。Webアプリケーションが複雑化するにつれ、手動での圧縮は非現実的になり、YUI Compressor(2007年)、GoogleのClosure Compiler、そして現代のTerser・esbuild・統合ビルドパイプラインを中心としたエコシステムへと発展しました。
現在、圧縮は本番デプロイに不可欠です。React・Vue・Angular・Svelteなどの主要フレームワークはすべて、本番ビルド時に自動的に圧縮を適用し、バンドルサイズを30〜70%削減することが多いです。
JavaScriptの圧縮はどのように動作するか
現代のJS圧縮は、単純なテキストの検索と置換ではなく、複数段階のパイプラインです。各ステップを詳しく見ていきましょう。
ステップ1:抽象構文木(AST)へのパース
圧縮ツールはまず、JavaScriptのソースコードを抽象構文木(Abstract Syntax Tree、AST)にパースします。ASTとは、コードのロジックを構造化したメモリ上の表現です。Terserは AcornなどのパーサーでASTを構築します。ASTは、すべての関数宣言・変数代入・式・制御フローの分岐をツリーのノードとして表現します。
このステップが重要なのは、生のテキストではなくASTを操作することで、正規表現ベースのアプローチでは絶対に保証できないような、意味的に安全な変換が可能になるからです。
ステップ2:空白文字とコメントの削除
可読性のためだけに存在する空白文字(スペース・タブ・改行)がすべて削除されます。コメント(// の単一行コメントと /* */ のブロックコメント)も破棄されます。コメントが豊富なコードベースでは、この処理だけでファイルサイズを15〜30%削減できます。
ステップ3:変数・関数名のマングリング(難読化)
calculateTotalPriceのような長い説明的な識別子が、a・b・cなどの短い1〜2文字の名前に改名されます。これをマングリングと呼びます。ASTによって、特定の変数へのすべての参照がスコープ内で一貫して改名されることが保証されます。マングリングにより、空白文字削除に加えてさらに10〜20%節約できます。
ステップ4:デッドコードの除去
到達不能なコードが特定されて削除されます。定義されているが一度も呼び出されない関数は削除されます。常にfalseになる条件分岐(例:if (false) { … })も除去されます。これにより出力サイズが縮小し、エンジンがパース・コンパイルするコードが減るため実行時パフォーマンスも向上します。
ステップ5:定数の畳み込みと式の単純化
定数式はコンパイル時に評価されます。var x = 2 + 3 は var x = 5 になります。true && someFunc() は someFunc() になります。!0 が true を、!1 が false を置き換えます。これらのマイクロ最適化は大規模なコードベースで積み重なると大きな効果をもたらします。
ステップ6:ASTからのコード再生成
最後に、修正されたASTが不要な文字をすべて取り除いた形でJavaScriptソースコードとして直列化されます。出力は、1行の密度の高い有効なJavaScriptになります。
例:圧縮前後の比較
// 圧縮前(元のコード)
function calculateTotal(items, taxRate) {
// 小計を計算する
var subtotal = 0;
for (var i = 0; i < items.length; i++) {
subtotal = subtotal + items[i].price * items[i].quantity;
}
var tax = subtotal * taxRate;
var total = subtotal + tax;
return total;
}
// 圧縮後(Terserの出力)
function calculateTotal(t,a){var l=0;for(var r=0;r<t.length;r++)l+=t[r].price*t[r].quantity;return l+l*a}
元の9行・236文字の関数が、1行99文字に圧縮されました——58%の削減です。
CSSの圧縮はどのように動作するか
CSS圧縮も同様のパース・変換・再生成パイプラインに従います。主な変換処理は以下の通りです。
空白文字とコメントの削除 — すべてのインデント・改行・/* */ コメントが取り除かれます。何百行にも及ぶCSSファイルが通常1行に折りたたまれます。
省略記法プロパティへのマージ — margin-top: 10px; margin-right: 5px; margin-bottom: 10px; margin-left: 5px; が margin: 10px 5px; になります。padding・border・background・font プロパティも同様です。
カラー値の短縮 — #ffffff が #fff に、rgb(255, 0, 0) が red や #f00 になります。名前付きカラーは、より短い16進数の等価値に置き換えられます。
ゼロ値の最適化 — 0px が 0 に、0% が 0 になります。値がゼロの場合、単位は不要です。
冗長なルールの削除 — 重複するセレクタと上書きされたプロパティが統合されます。cssnano(PostCSS上に構築)がこれらすべての変換を処理します。
典型的なCSS圧縮では、元の記述方法に応じて 20〜50% のファイルサイズ削減が達成できます。
HTMLの圧縮はどのように動作するか
HTML圧縮は、HTML構造がレンダリングやアクセシビリティに影響するため、やや保守的です。主な手法は以下の通りです。
空白文字の折りたたみ — タグ間の複数の連続したスペースや改行が、1つのスペースに折りたたまれるか、視覚的な影響がない場所では完全に削除されます。
省略可能なタグの削除 — HTML5では特定のコンテキストで特定の終了タグ(</li>・</td>・</p> など)を省略できます。圧縮ツールはそれらを安全に削除します。
属性の引用符削除 — 値にスペースや特殊文字が含まれない場合、<div class="container"> が <div class=container> になります。
インラインJS/CSSの圧縮 — HTML内の <script> タグと <style> タグは、対応するJS/CSS圧縮ツールを使って圧縮されます。
真偽値属性の短縮 — <input disabled="disabled"> が <input disabled> になります。
典型的なHTML圧縮の節約効果は 5〜20% で、JS/CSSより小さいです。HTMLの構文がそもそも冗長でないためです。
ビルドツールのエコシステム
Terser
Terser はJavaScript圧縮の業界標準です。ES6+を完全サポートするUglifyJSのフォークで、Webpack・Vite・Rollupおよびほとんどの主要バンドラーの圧縮ステップを担っています。
# Terser CLIの使い方
npx terser input.js -o output.min.js --compress --mangle
cssnano
cssnano はPostCSSベースのCSS最適化ツールです。複数の最適化パスを実行し、WebpackのCSSパイプラインでデフォルトで使用されます。
# PostCSSでcssnanoを使う
npx postcss input.css -o output.min.css --use cssnano
html-minifier-terser
クラシックな html-minifier の維持管理されたフォークで、モダンなHTML5をサポートし、インラインスクリプトの圧縮のためにTerserを統合しています。
Webpack
Webpackは本番モードでJS用にTerserPlugin、CSS用にCssMinimizerPluginを使用します。
{
"optimization": {
"minimize": true,
"minimizer": ["...new TerserPlugin({ terserOptions: { compress: { drop_console: true } } })"]
}
}
drop_console: true オプションは、本番バンドルからすべての console.log() 呼び出しを削除します。
Vite
Viteは開発モードのトランスパイルにesbuildを使用し、本番ビルドにはRollup + Terserを使用します。圧縮は完全自動で、vite build を実行するだけで、追加設定不要で圧縮・分割された出力が生成されます。
esbuild
esbuild はGoで書かれており、JavaScriptベースのバンドラーより10〜100倍高速です。バンドルステップの一部として圧縮を実行します。Terserの高度な圧縮パスをすべてサポートしているわけではありませんが、その速度から開発ビルドのデフォルト選択となっており、本番環境でも採用が広がっています。
Tree Shakingと圧縮の違い
Tree shakingと圧縮は補完的ですが、異なる技術です。
Tree shaking はモジュールレベルでデッドコードを除去します。ユーティリティライブラリをインポートして20個の関数のうち2つしか使わない場合、Tree shakingはバンドル作成前に未使用の18個の関数を完全に削除します。これにはESモジュール(import/export)が必要で、その静的な構造によりバンドラーがどのエクスポートが実際に使われているかを追跡できます。
圧縮 は、必要であると判断されたコードのサイズを縮小します——Tree shakingが実行された後に生き残ったコードを圧縮します。
両者を組み合わせることで、Tree shaking + 圧縮によってフルライブラリのインポートを数百KBからわずか数KBに削減できます。
Source Maps:圧縮されたコードのデバッグ
圧縮されたコードは読めません。本番環境でエラーが発生した場合、スタックトレースは圧縮ファイルの1行目847列目を指し示します——デバッグには役立ちません。
Source maps(.map ファイル)は、圧縮コードの位置から元のソースコードの位置へのマッピングを提供することでこの問題を解決します。ブラウザの開発者ツールはSource mapsを自動的に使用して、デバッグ時に元の読みやすいコードを表示します。
npx terser input.js -o output.min.js --source-map "url='output.min.js.map'"
ベストプラクティス:Source mapsを生成しつつ、認証済みユーザーのみに提供するか、知的財産を守るためにパブリックCDNから除外しましょう。
圧縮 vs 転送圧縮(gzip / Brotli)
これらはよく混同されますが、異なるレベルで動作し、完璧に補完し合います。
| 技術 | 動作レベル | 典型的な節約 |
|---|---|---|
| コード圧縮 | ソースコードレベル | 30〜70% |
| gzip | HTTP転送レイヤー | 圧縮後サイズの60〜80% |
| Brotli | HTTP転送レイヤー | 圧縮後サイズの70〜85% |
コード圧縮は、エントロピー(空白文字・コメント・長い名前)を除去することでテキストをより圧縮しやすくします。gzip/Brotliはその後、すでにコンパクトなテキストをさらに圧縮します。効果は積み重なります。100KBのファイルが圧縮で40KBになり、BrotliによるHTTP転送後は12KBになる場合があります。
両方を有効にしましょう。サーバーまたはCDNで Content-Encoding: br(Brotli)を設定し、提供前にコードを圧縮するビルドパイプラインを使用しましょう。
実際のパフォーマンス数値
以下の数値は実際の本番デプロイを代表するものです。
- React本番ビルド: 開発バンドル約2.5 MB → 本番圧縮後約130 KB(Tree shaking + 圧縮 + gzip後に95%削減)
- Bootstrap CSS: 未圧縮約185 KB → 圧縮後約157 KB → gzip後約23 KB
- jQuery 3.x: 未圧縮約290 KB → 圧縮後約87 KB → gzip後約30 KB
- 典型的なSPA: 圧縮だけでバンドルサイズを40〜70%削減
- 大規模CSSフレームワーク: cssnanoで30〜60%削減
JavaScriptを100KB節約するごとに、中程度のスペックのモバイルデバイスで約1秒のパース・コンパイル時間を削減できます。低速な3G回線では節約効果はさらに顕著です。
ユースケース
本番Webデプロイ — 主要なユースケース。ユーザーに提供されるすべてのファイルは圧縮と転送圧縮が施されるべきです。
CDN配信 — Cloudflare・Fastly・AWS CloudFrontなどのCDNはアセットを自動圧縮できますが、ビルド時の圧縮の方が速くコントロールしやすいです。
プログレッシブWebアプリ(PWA) — PWAはブラウザにリソースをキャッシュします。アセットが小さいほど、初期インストールが速くなり、オフラインパフォーマンスが向上し、ユーザーのデバイスのストレージ使用量も減ります。
メールテンプレート — メールテンプレートのインラインHTML/CSSはコンパクトでなければなりません。多くのメールクライアントにはサイズ制限があり、モバイルでのレンダリング速度も重要です。
サーバーレス関数 — コールドスタート時間はバンドルサイズに一部依存します。LambdaやCloudflare Workerのコードを圧縮することで、コールドスタートのレイテンシを計測可能なレベルで削減できます。
npmパッケージ公開 — 適切な exports フィールドを持つ圧縮済みのTree shaking可能なパッケージを公開することで、ライブラリユーザーに優れた開発体験を提供できます。
手動圧縮 vs ビルドツール統合
| 手動(オンラインツール) | ビルドパイプライン | |
|---|---|---|
| 速度 | 1ファイルは即時 | プロジェクト全体を自動化 |
| 一貫性 | まちまち | 毎回のビルドで再現可能 |
| Source maps | オプション | 自動生成 |
| チームワーク | スケールしない | バージョン管理された設定 |
| 最適な用途 | クイックチェック、学習、プロトタイプ | すべての本番プロジェクト |
オンラインツール(当社のツールなど)は、圧縮の仕組みを理解したり、1つのファイルを素早く縮小したり、ビルド設定なしでプロトタイプを作成したりするのに最適です。ビルドツールの統合はすべての本番プロジェクトに不可欠です。
ベストプラクティス
- 本番環境では必ず圧縮する。 未圧縮のファイルをユーザーに提供してはいけません。
- 必ずSource mapsを生成する。 本番エラーのデバッグ時に必要になります。
- サーバーまたはCDNでBrotli圧縮を有効にする(コード圧縮と併用)。
- Terserで
drop_console: trueを使用して、本番バンドルからデバッグログを除去する。 - 圧縮前にTree shakingを実行する。 ViteやRollupなどのバンドラーは自動的に実行します。
- 圧縮ツールを最新に保つ。 Terserとesbuildのバージョンアップには改善された圧縮アルゴリズムが含まれます。
- 圧縮前後を計測する。 Lighthouse・WebPageTest・Chrome DevToolsのNetworkタブでサイズ削減を確認しましょう。
- 圧縮済みファイルを手動編集しない。 常にソースから圧縮する。手動編集は次のビルドで上書きされます。
- 積極的なCSS圧縮後はCSSの優先度問題をチェックする — 省略記法のマージで有効な優先度が変わることがあります。
- コンテンツハッシュを使用する(例:
bundle.a3f9b2.min.js)ことで、圧縮アセットの積極的なCDNキャッシュが可能になります。
よくある質問
Q: 圧縮はコードの動作を変えますか?
A: いいえ。正しい圧縮ツールは、動作に影響しないものだけを削除または改名します。空白文字・コメント・識別子(一貫して改名)です。圧縮後のコードが異なる動作をする場合、通常は Function.name・関数の toString()・または名前のマングリングで壊れる同様のリフレクションベースのパターンへの依存が原因です。
Q: 開発環境でも圧縮すべきですか? A: 一般的にはいいえ。圧縮されたコードはデバッグが難しくなります。ステージング環境ではSource mapsを使用し、本番ビルドにのみ完全な圧縮を有効にしましょう。
Q: オンライン圧縮ツールを使うのは安全ですか? A: 当社のツールは完全にブラウザ内で動作します——コードはサーバーに送信されません。サードパーティのオンラインツールを使用する際は、DevToolsのNetworkタブで確認しましょう。
Q: 圧縮と難読化の違いは何ですか? A: 圧縮の主な目標はファイルサイズの削減で、読みやすさの低下は副作用です。難読化は文字列エンコーディング・制御フローの平坦化・デッドコード注入などの技術を使って、意図的にコードを理解しにくくします。圧縮されたコードはフォーマッタで回復できますが、適切に難読化されたコードはそうはいきません。
Q: 圧縮はJavaScriptの実行速度を向上させますか? A: 直接的にはほとんどありません——最新のJSエンジンはフォーマットに関わらずコードをパースしてJITコンパイルします。主なパフォーマンス上の利点はダウンロードとパース時間の短縮で、モバイルネットワークでは特に重要です。定数の畳み込みは軽微な実行時の利点をもたらします。
Q: 積極的な圧縮でコードが壊れるリスクはありますか?
A: Terserやesbuildなどのメンテナンスされているツールを使用する場合、リスクは非常に低いです。最も一般的な問題は:.name プロパティに依存するコード、eval() を使用するコード(Terserは保守的に処理)、省略記法のマージによるCSS優先度の変化です。常に圧縮出力に対してテストスイートを実行しましょう。