CSS & JavaScript圧縮ツール

CSSとJavaScriptコードを圧縮

ミニファイヤが実際にやること — gzip 後に残る効果はどれだけか

本格的な JavaScript ミニファイヤ(terser、esbuild、swc)は空白削除よりはるかに多くのことをします。ソースを AST に解析し、意味を保つ変換を適用します — 識別子マングリング(ローカル変数を 1 文字に改名 — スコープが分かっているので安全)、定数畳み込み(1000 * 60 * 60 が 3600000 に)、デッドコード除去(if (false) の分岐が消える)、真偽値・プロパティの短縮書き換え(true が !0 に、obj['key'] が obj.key に)、関数インライン化。pure コメントマーカー付きの呼び出しは、結果が未使用なら丸ごと削除できます — ツリーシェイキングされたライブラリが公称サイズに到達する仕組みがこれです。 数字が重要なのは、gzip が話を変えるからです。ミニファイ単体ではアプリケーションコードの生バイトを通常 30〜60% 削減します。しかし gzip は空白や繰り返される長い識別子の圧縮が非常に得意なので、圧縮後の差は縮みます — 400KB から 160KB にミニファイされるファイルは、gzip 後にはミニファイ済み 52KB 対 未ミニファイ 61KB といった程度になります。それでも残る 15〜20% には価値があります — なお、マングリングが圧縮に効く度合いは期待より小さいです。短い名前は、gzip がどのみち利用したはずの冗長性を減らすからです。 実務ルール:生サイズではなく圧縮後サイズを測ること。生で 40% 削れても gzip 後は 3% しか削れないミニファイ工程なら、持ち込むソースマップの複雑さに見合わないかもしれません。逆に、デッドコード除去とツリーシェイキングは全空白削除の合計より圧縮バイトを多く節約することがよくあります — 削除されたコードは圧縮しても 0 バイトだからです。
# same bundle, four ways (typical mid-size app)
source                412,000 bytes
minified              168,000 bytes   (-59% raw)
minified + gzip        52,000 bytes
minified + brotli-11   46,000 bytes

gzip WITHOUT minify  ≈ 61,000 bytes
# → minification still buys ~15% after compression,
#   mostly from dead-code elimination, not whitespace

正規表現 vs AST:バイトレベルの JavaScript ミニファイが危険な理由

JavaScript は文字列置換で安全にミニファイできません。理由は構造的です。第一に、スラッシュ文字が曖昧です。a / b は除算ですが /b/.test(a) は正規表現リテラルです。あるスラッシュがどちらを意味するかはパース状態に依存し、正規表現ベースの置換器はその状態を持ちません。/https?:\/\// のような正規表現リテラル内の「コメント」を除去すれば、コードの意味は静かに変わります。 第二に、自動セミコロン挿入(ASI)により改行が意味を持ちます。return の後に改行、その次にオブジェクトリテラルが来ると undefined が返ります — ASI が return の直後にセミコロンを挿入するからです。行を素朴に連結すると、このバグを隠すか、さらに悪ければ新しいバグを作ります。変数で終わる行と開き括弧・角括弧で始まる次の行が結合すると、関数呼び出しやプロパティアクセスに融合します。本物のミニファイヤは先にパースするため、セミコロンが必須の位置を把握し、逆に ASI を利用して安全にセミコロンを削ることさえします。 第三に、文字列とテンプレートリテラルは不透明なコンテナです。スラッシュ 2 つを含む文字列はコメントではなく、テンプレートリテラル内の空白はデータであり、バイト単位で保存されなければなりません。 このような手軽なオンラインミニファイヤについての結論:後で目視確認できる些細なスニペット — ブックマークレット、インラインイベントハンドラ、短いスタイルブロック — には十分です。ユーザーに配布するコードならビルドで terser、esbuild、swc を使ってください。esbuild は terser の約 10〜100 倍高速で出力は 1〜2% 程度大きいだけ、terser は最も強く絞り、swc はその中間で優れた TypeScript サポートを持ちます。
// 1. ASI: this returns undefined, not the object
function pick() {
  return          // <- semicolon auto-inserted here
    { ok: true };
}

// 2. line joining creates a call that never existed
const total = fn
(a || b).forEach(log)   // joined: fn(a || b).forEach(log)

// 3. slash ambiguity — is it division or a regex?
const x = a / b / c;        // two divisions
const ok = /a\/b/.test(s);  // one regex containing a slash
// a byte-level replacer cannot tell these apart

CSS ミニファイ:安全な変換と calc() の罠

CSS は JavaScript より単純な言語なので、より多くの部分を機械的にミニファイできます — が、すべてではありません。確実に安全な変換:コメント除去(ただし /*! で始まるライセンスコメントは慣例としてビルドツールが保存)、文字列外の空白の畳み込み、各宣言ブロック末尾のセミコロン削除、桁がペアになる 6 桁 hex カラーの短縮(#ffffff → #fff)、長さ 0 からの単位除去(0px → 0 — ゼロはどの長さ単位でもゼロなので有効)、同一のボックス値 4 つの統合(margin: 4px 4px 4px 4px → margin: 4px)。 古典的な罠は calc() です。CSS の文法は calc 内の + と - の周囲に空白を要求します。数値に密着したマイナスは減算ではなく負の値として解析されるからです。calc(100% - 20px) は有効ですが calc(100%-20px) は無効で、ブラウザは宣言全体を黙って捨てます。同様の空白依存はメディアクエリ(screen and (min-width: ...))と CSS カスタムプロパティにもあります。カスタムプロパティの値はトークンのまま保存されるため、var() のフォールバックやカスタムプロパティ値の内部をミニファイすると、参照側が読む内容が変わり得ます。 もう 1 つ非自明な点:0% は常に 0 に置換できるわけではなく(flex-basis や一部のアニメーション文脈ではパーセントが意味を持つ)、フォント名や url() 値の引用符除去は内容に予約文字が含まれない場合のみ安全です。 実際のパイプラインでは cssnano(PostCSS ベース、細かく設定可能)や Lightning CSS(Rust 製で非常に高速、モダン構文の旧ブラウザ向けトランスパイルも)がこれを担います。どちらも AST に解析するからこそ、上記の変換を安全に行い、文脈が禁じる箇所ではスキップできるのです。
/* before */
.card { margin: 0px 0px 0px 0px; background: #ffffff; }
.col  { width: calc(100% - 20px); }

/* correct minify */
.card{margin:0;background:#fff}.col{width:calc(100% - 20px)}

/* BROKEN: whitespace inside calc() is syntax, not formatting */
.col{width:calc(100%-20px)}   /* declaration silently dropped */

/*! preserved license header — the ! is the convention */

ソースマップ:デバッグ能力を失わずにミニファイする

bundle.min.js の 1 行目 48213 列目を指す本番のスタックトレースは役に立ちません。ソースマップがこれを解決します。.map ファイル(VLQ エンコードされた mappings 文字列を持つ JSON)は、生成後のすべての位置について元のファイル・行・列、多くの場合は元の識別子名まで記録します。ミニファイ済みファイルは最終行の sourceMappingURL コメントでこれを参照し、ブラウザの開発者ツールが遅延取得します — 通常の訪問者がダウンロードすることはありません。 デプロイ上の論点は、誰がマップを見られるかです。よくある構成は 3 つ:(1)公開マップ — 最も単純ですが、興味を持った誰にでも元のソースが露出します。(2)隠しマップ — 生成はするがバンドルから参照せず、エラートラッカー(Sentry、Datadog、Rollbar)にアップロードしてサーバー側でスタックトレースをシンボリケートします。(3)認証済みの社内 IP にのみ配信する制限マップ。クローズドソース製品なら隠しマップが標準解です。sourcesContent を埋め込んだマップは、マップファイル自体に元ソースの全文が含まれる点にも注意してください。 実務上の落とし穴が 2 つ。チェーン内のすべての変換工程がマップを生成・消費しなければなりません(TypeScript → esbuild → terser)。さもないと最終マップは中間生成物を指し、全フレームが誤った位置に解決されます。また再リリース時、マップは生成時のバンドルとバイト単位で一致している必要があります — エラートラッカーがリリース識別子やコンテンツハッシュでマップを紐付けるのはまさにこのためです。シンボリケート済みトレースがもっともらしいのに微妙にずれて見えるなら、リリースの食い違った古いマップが定番の犯人です。
// last line of bundle.min.js
//# sourceMappingURL=bundle.min.js.map

// hidden-maps workflow (Sentry example)
esbuild app.ts --minify --sourcemap=external --outfile=dist/app.js
sentry-cli sourcemaps upload --release=v1.4.2 ./dist
rm dist/*.map        # maps never reach the CDN

// stack trace before / after symbolication
at t (bundle.min.js:1:48213)
at checkout (src/cart/checkout.ts:87:11)   // with map + matching release

ミニファイ・圧縮・バンドル:別々の 3 つの仕事、3 つともやる

チームは、異なるレイヤーで働く 3 つの最適化をよく混同します。ミニファイはビルド時にソーステキストを一度だけ書き換えます。転送圧縮(gzip または Brotli)は配信時にレスポンスごとにバイトをエンコードし、ブラウザが透過的にデコードします。バンドルはモジュールを結合してリクエストのオーバーヘッドを削り、モジュール横断のツリーシェイキングを可能にします。3 つの効果は掛け算であり — どれも他の代わりにはなりません。 圧縮設定は思われている以上に重要です。静的アセットはビルド中に最高レベルで事前圧縮し(Brotli 品質 11、gzip 9)、サーバーがコンテントネゴシエーションでエンコーディングを選ぶようにします。Brotli-11 はリクエストごとに走らせるには遅すぎますが、デプロイ時に一度やるならタダ同然です。動的レスポンスには Brotli 4〜5 か gzip 6 を — それ以上のレベルは一桁パーセントの利得のために CPU を燃やします。テキストの典型的な圧縮率:gzip で約 3〜4 倍、Brotli は JS/CSS で gzip よりさらに約 10〜20% 小さくなります。 このツールが想定するインラインスニペットのワークフローでの手順:読みやすいソースはリポジトリに置き、HTML 属性・GTM コンテナ・メールテンプレートに貼るコピーだけをミニファイし、結果がまだパースできるか検証します — JS ならブラウザコンソールに、CSS ならバリデータに貼ればよいのです。ミニファイ済みテキストを編集可能な原本として扱わないこと。いつでも再生成できる一方向の成果物は無害ですが、手で修正されたミニファイ済みの塊は取り消しのきかない技術的負債です。
最終更新:

ツールについて

CSS / JavaScript ミニファイヤはコメント・空白・冗長なトークンを取り除き、本番用にサイズを縮小します。ビルドツールが自動でやってくれますが、インラインスニペット・メール用スタイル・バンドラーを通さない小さなスクリプトには手軽なウェブミニファイヤが便利です。

使い方

  1. Choose CSS or JavaScript mode at the top.
  2. Paste your source into the input panel.
  3. Click Minify — the output appears on the right with the size saving in bytes and percent.
  4. Copy the minified result to embed in a <style> or <script> tag.
  5. Use the original source as the long-term editable copy and only ship the minified version.

主な使用例

  • Inlining critical CSS in the <head> for faster First Contentful Paint.
  • Shrinking a Google Tag Manager custom HTML snippet to the size limit.
  • Compacting a JS one-liner you want to embed in a bookmarklet.
  • Reducing the size of email-safe CSS where every byte matters.
  • Trimming a no-bundler vanilla JS file before deploying to a static host.
  • Estimating the bundle-size impact of a snippet before adding it to a build.

よくある質問

Q. Is this safe for production JavaScript?

A. For trivial scripts yes. For real applications use a battle-tested minifier like esbuild, terser, or swc inside your build pipeline so source maps and ECMAScript edge cases are handled.

Q. Why does the output break my CSS?

A. Most often a missing semicolon in the source. CSS minification assumes valid input — fix the source, then minify.

Q. Does minifying obfuscate my code?

A. Slightly — names are not changed, only whitespace is stripped. For real obfuscation use a dedicated tool, but understand it does not provide real security.

Q. Can I minify HTML the same way?

A. HTML minification needs different rules (preserved whitespace in <pre>, attribute quoting, etc.). Use a dedicated HTML minifier for that.