PostCSSとpostcss-importでCSSの構成を分割管理する

概要

CSSファイルが1枚で数千行に膨れ上がると、どこに何を書いたか分からなくなり、修正のたびに影響範囲の調査に時間を取られます。

この記事では、PostCSS と postcss-import プラグインを使って、CSS を役割ごとのファイルに分割しつつ、ビルド時に1枚へまとめるハンズオンを扱います。Sass/SCSS を導入するほどではないが、@import をビルド時に解決してファイル分割だけ実現したい、というケースを想定しています。

環境

  • Node.js 20.x
  • postcss 8.x
  • postcss-cli 11.x
  • postcss-import 16.x

バージョンによってオプション名や挙動が変わることがあるため、手元で動かす際は package.json のバージョンを確認してください。

発生した問題

CSS を素朴に書いていくと、次のような状態になりがちです。

  • style.css が1枚で数千行に膨らみ、変更箇所の特定に時間がかかる
  • コンポーネントごとにファイルを分けたいが、ネイティブの @import はHTTPリクエストが増える
  • 変数・メディアクエリ・ベンダープレフィックスの扱いがファイルごとにバラバラ

ネイティブの CSS @import は、ブラウザが CSS を読み込んだ後に追加でリクエストを飛ばすため、分割すればするほど読み込みが遅くなります。「保守性のための分割」と「配信効率」が真っ向からぶつかります。

原因

問題の本質は、@import の解決タイミングがブラウザ(実行時)になっている点です。分割した各ファイルがそれぞれ独立したリクエストになり、依存が深いほど読み込みが直列化されます。

これを避けるには、@import を ビルド時 に解決し、最終的に1枚の CSS へインライン展開してしまえばよく、その役割を担うのが postcss-import です。

対応方法

インストール

npm init -y
npm install -D postcss postcss-cli postcss-import autoprefixer postcss-custom-media

ディレクトリ構成

「基盤・レイアウト・コンポーネント」の3層に分ける例です。

src/css/
  main.css            # エントリ(@importだけ書く)
  base/
    reset.css
    variables.css
    custom-media.css
  layout/
    header.css
    footer.css
  components/
    button.css
    card.css
dist/
  style.css           # ビルド成果物

エントリファイル

エントリには原則 @import 以外を書きません。読み込み順がそのまま適用順になります。

/* src/css/main.css */
@import "base/reset.css";
@import "base/variables.css";
@import "base/custom-media.css";

@import "layout/header.css";
@import "layout/footer.css";

@import "components/button.css";
@import "components/card.css";

各ファイルの中身

/* src/css/base/variables.css */
:root {
  --color-primary: #2563eb;
  --color-text: #1f2937;
  --space-md: 16px;
  --radius: 6px;
}
/* src/css/base/custom-media.css */
@custom-media --sp (max-width: 640px);
@custom-media --tab (max-width: 1024px);
/* src/css/components/button.css */
.button {
  display: inline-flex;
  align-items: center;
  padding: 8px var(--space-md);
  border-radius: var(--radius);
  background: var(--color-primary);
  color: #fff;
}

@media (--sp) {
  .button {
    width: 100%;
    justify-content: center;
  }
}

PostCSS設定

postcss-import を先頭に置くのが重要です。@import を先に展開してから、後続のプラグインが全ファイルを対象に処理できるようにするためです。

// postcss.config.js
module.exports = {
  plugins: [
    require("postcss-import"),
    require("postcss-custom-media"),
    require("autoprefixer"),
  ],
};

ビルド

{
  "scripts": {
    "build:css": "postcss src/css/main.css -o dist/style.css",
    "watch:css": "postcss src/css/main.css -o dist/style.css --watch"
  }
}
npm run build:css

dist/style.css に、各ファイルの内容が @import の順でインライン展開された1枚の CSS が生成されます。

実装例

postcss-import は node_modules 内のパッケージも解決できます。

@import "normalize.css";      /* node_modules から解決 */
@import "base/variables.css";  /* 自前ファイル(相対パス) */

探索先を明示したい場合はオプションで指定します。

module.exports = {
  plugins: [
    require("postcss-import")({ path: ["src/css"] }),
    require("autoprefixer"),
  ],
};

確認方法

  1. インライン展開の確認
grep -n "@import" dist/style.css
# => 何も出なければ、すべてビルド時に解決されている

ただし @import url(...) や一部のメディアクエリ付き @import は、仕様上展開されず残る場合があります。

  1. ベンダープレフィックス: autoprefixer が効いているか。付与対象は browserslist 設定に依存します。
  1. 適用順: エントリの @import の記述順が出力順になります。リセットがコンポーネントより後ろに展開されていないか目視します。

注意点

  • プラグインの順序: postcss-import は必ず先頭。後ろに置くと分割ファイルに変数やプレフィックスが反映されません。
  • 循環 import: 依存の向きは一方向(基盤 → レイアウト → コンポーネント)に保ちます。
  • ネイティブ @import との違い: ここで扱うのはビルド時解決です。成果物に @import を残したい場合は目的が異なります。
  • ソースマップ: --map を付けると DevTools で元の分割ファイルを辿れます。

まとめ

  • ネイティブ @import は実行時解決でリクエストが増え、分割と配信効率が両立しにくい
  • postcss-import で @import をビルド時に解決し、1枚へインライン展開できる
  • プラグインは postcss-import を先頭に置き、その後に autoprefixer などを並べる
  • 役割ごとにディレクトリを分け、エントリは @import だけに保つと見通しが良い
  • 成果物から @import が消えているか、適用順が崩れていないかを必ず確認する

Sass を入れるほどではないが CSS のファイル分割だけは欲しい、という場面で手軽に導入できる構成です。既存の PostCSS パイプラインがあれば、プラグインを1つ足すだけで始められます。

\ 最新情報をチェック /

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください。