Skip to content

CSS Bundling and Layering Strategy

Dima Vyshniakov edited this page Jul 31, 2026 · 1 revision

CSS Bundling and Layering Strategy

Overview

Our project leverages Vite and PostCSS to process and bundle CSS. A core architectural decision in our styling approach is the heavy reliance on native CSS Layers (@layer) combined with CSS Modules. This guarantees that our component styles are safely encapsulated, establishing a predictable specificity hierarchy without fighting the global cascade.

PostCSS and Vite Configuration

Our styling pipeline is orchestrated through the css configuration block inside vite.config.ts.

  • CSS Modules: We utilize CSS modules and enforce a camelCase convention for local class names (localsConvention: 'camelCase').
  • PostCSS Preset Env: We use the postcss-preset-env plugin (stage 1) to enable modern CSS features. Crucially, we explicitly disable polyfilling and flattening for specific features:
    • 'cascade-layers': false - This ensures that our native @layer rules remain intact in the final bundle rather than being flattened by the preset.
    • 'all-property': false - This preserves the native all property (e.g., used for CSS resets).

The Custom wrapInLayerPlugin

To enforce our layer hierarchy across all isolated component styles, we created a custom PostCSS plugin called wrapInLayerPlugin.

This plugin acts on the Abstract Syntax Tree (AST) of our CSS during the build process to automatically inject CSS layers. Here is how it functions:

  1. Targeting Modules: The plugin specifically looks for files that match the /\.module\.css/ pattern, ensuring it only affects our scoped component stylesheets.
  2. Safety Check: Before making any AST modifications, the plugin verifies if the root nodes are already wrapped in an @layer rule. If it detects an existing layer, it aborts to prevent redundant wrapping.
  3. Layer Wrapping: All existing CSS nodes within the .module.css file are extracted, removed from the root, and then appended inside a new @layer koval-components block.
  4. Hierarchy Declaration: To ensure the browser knows the intended specificity order as soon as the file is parsed, the plugin prepends a layer declaration rule: @layer koval-reset, koval-components;.

By automating this via wrapInLayerPlugin.ts and vite.config.ts, we ensure that our developers can simply write standard CSS modules, while the build pipeline guarantees those styles are cleanly assigned to the lowest-priority component layers, making overrides by end-users (who write unlayered CSS) effortless.