CSS in Build Tools (Vite, Webpack)
In any non-trivial modern frontend project, the CSS you write doesn't reach the browser unmodified. A build tool processes it first — bundling multiple stylesheets together, running it through PostCSS for autoprefixing and modern-syntax transforms, minifying it, and (for CSS Modules) rewriting class names to be collision-safe. Knowing roughly what your build tool does to your CSS matters most when something goes wrong: a missing prefix, an unexpectedly global class name, or a production-only bug all trace back to this pipeline.
What the CSS pipeline generally does
Bundling — collects every CSS file reachable from your JS/component imports into one (or a few) output stylesheets.
PostCSS transforms — runs plugins like Autoprefixer and
postcss-preset-envso you can write modern or plain CSS and still support older target browsers.Minification — strips whitespace/comments and shortens values for production output.
CSS Modules — for files named
*.module.css, rewrites class names to unique, scoped identifiers so they can't accidentally collide with a class of the same name elsewhere in the app.Source maps — in development, maps the generated CSS back to your original source files so DevTools shows the right file and line.
Vite: near-zero-config CSS
.css import just works, injecting the styles at runtime in development and extracting them into a real stylesheet for production:// main.jsx
import './styles.css' // global CSS, just works
import styles from './Card.module.css' // CSS Modules — scoped class names
function Card() {
return <div className={styles.card}>Hello</div>
}Sass/Less/Stylus support follows the same "just works" philosophy — Vite detects the file extension and only asks that you install the matching preprocessor package, with no loader configuration of your own:
npm install -D sass
import './styles.scss' // works immediately once 'sass' is installed
Webpack: more explicit, more manual
webpack.config.js:const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader, // extracts CSS to its own file (prod)
'css-loader', // resolves @import / url() in CSS
'postcss-loader', // runs Autoprefixer etc.
],
},
{
test: /\.module\.css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: { modules: true }, // enables CSS Modules scoping
},
],
},
],
},
plugins: [new MiniCssExtractPlugin()],
}Vite | Webpack | |
|---|---|---|
Plain CSS import | Works with zero config | Requires |
CSS Modules | Detected automatically via | Requires |
Sass/Less | Install the preprocessor package, nothing else | Requires an additional loader ( |
Mental model | Convention-driven, hidden pipeline | Explicit pipeline you assemble yourself, rule by rule |
Why this matters for debugging
Most confusing "the CSS just isn't applying" bugs in a bundled project come down to a pipeline step doing something you didn't expect: a CSS Modules file whose class names got hashed differently than assumed, a PostCSS plugin silently rewriting a modern selector your target browsers don't support, or an import order that changed which rule wins in the final bundled output. Knowing the pipeline exists — bundle, transform, minify — is usually enough to know where to look first.