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"),
],
};
確認方法
- インライン展開の確認
grep -n "@import" dist/style.css # => 何も出なければ、すべてビルド時に解決されている
ただし @import url(...) や一部のメディアクエリ付き @import は、仕様上展開されず残る場合があります。
- ベンダープレフィックス:
autoprefixerが効いているか。付与対象はbrowserslist設定に依存します。
- 適用順: エントリの
@importの記述順が出力順になります。リセットがコンポーネントより後ろに展開されていないか目視します。
注意点
- プラグインの順序:
postcss-importは必ず先頭。後ろに置くと分割ファイルに変数やプレフィックスが反映されません。 - 循環 import: 依存の向きは一方向(基盤 → レイアウト → コンポーネント)に保ちます。
- ネイティブ
@importとの違い: ここで扱うのはビルド時解決です。成果物に@importを残したい場合は目的が異なります。 - ソースマップ:
--mapを付けると DevTools で元の分割ファイルを辿れます。
まとめ
- ネイティブ
@importは実行時解決でリクエストが増え、分割と配信効率が両立しにくい postcss-importで@importをビルド時に解決し、1枚へインライン展開できる- プラグインは
postcss-importを先頭に置き、その後にautoprefixerなどを並べる - 役割ごとにディレクトリを分け、エントリは
@importだけに保つと見通しが良い - 成果物から
@importが消えているか、適用順が崩れていないかを必ず確認する
Sass を入れるほどではないが CSS のファイル分割だけは欲しい、という場面で手軽に導入できる構成です。既存の PostCSS パイプラインがあれば、プラグインを1つ足すだけで始められます。
