✨ Add cascading layers to CSS files ✨
English | 简体ä¸ć–‡
npm install @web-baseline/postcss-wrap-up-layerAdding cascading layers to CSS files is typically used in web development to handle automatically imported component library styles. Allow adding different cascading layers to different libraries through rules to facilitate style priority management.
Each file will only be processed according to the first matching rule. When the matching range of the current rule includes subsequent rules, the latter rules will not take effect.
import WrapUpLayer from '@web-baseline/postcss-wrap-up-layer';
WrapUpLayer({
rules: [
{
/** This rule will take effect */
includes: /^node_modules\/lib-name/,
layerName: 'lib.lib-name',
},
{
includes: /^node_modules\//,
layerName: 'lib',
},
{
/** This rule will not take effect because the scope of the previous rule includes this rule */
includes: /^node_modules\/other-lib/,
layerName: 'lib.other-lib',
},
{
includes: (file) => file.startsWith('src/assets/base'),
layerName: 'base',
},
],
});import WrapUpLayer from '@web-baseline/postcss-wrap-up-layer';
WrapUpLayer({
rules: [
{
map: (path, input) => {
// Dynamically determine layer name
if (path.startsWith('node_modules/')) {
const match = path.match(/node_modules\/([^\/]+)/);
return match ? `lib.${match[1]}` : 'lib';
}
if (path.startsWith('src/components/')) {
return 'components';
}
if (path.startsWith('src/')) {
return 'app';
}
// Return false to skip processing this file
return false;
},
},
],
});import WrapUpLayer from '@web-baseline/postcss-wrap-up-layer';
WrapUpLayer({
rules: [
{
includes: /^node_modules\//,
layerName: 'lib',
// Rule-level transform options
transformOptions: {
outsideAtRules: ['import'], // @import rules will stay outside @layer
},
},
],
// Global transform options
transformOptions: {
outsideAtRules: ['charset', 'namespace'], // These are included by default
},
});import WrapUpLayer from '@web-baseline/postcss-wrap-up-layer';
WrapUpLayer({
rules: [
{
map: (path, input) => {
if (path.startsWith('src/pages/')) {
return {
layerName: 'pages',
transformOptions: {
outsideAtRules: ['import'], // Configure transform options for specific files
},
};
}
return false;
},
},
],
});/** Input file: `node_modules/lib-name/index.css` */
/* <element class="component"> style */
.component {
height: 4rem;
}
/** Output: */
@layer lib.lib-name {
/* <element class="component"> style */
.component {
height: 4rem;
}
}/** Input file: `node_modules/other-lib/index.css` */
/* <p> style */
p {
margin-bottom: 0.2em;
}
/** Output: */
@layer lib {
/* <p> style */
p {
margin-bottom: 0.2em;
}
}/** Input file: `src/assets/base-normalize.css` */
@layer normalize {
/* <body> style */
body {
margin: 0;
}
}
/** Output: */
@layer base {
@layer normalize {
/* <body> style */
body {
margin: 0;
}
}
}The plugin has special handling logic for @import rules:
- When
@importis inoutsideAtRules:@importrules stay outside@layerwithout any modification - When
@importis not inoutsideAtRules: The plugin will attempt to addlayer()function to@importrules - When
@importalready haslayer()function: The plugin will wrap the existing layer name as a sub-layer under the current rule's layer name (e.g.,layer(base)becomeslayer(pages.base))
/** Input file: `src/pages/index.css` (configured with outsideAtRules: ['import']) */
@import "common.css";
.page {
padding: 1rem;
}
/** Output: */
@import "common.css";
@layer pages {
.page {
padding: 1rem;
}
}/** Input file: `src/pages/about.css` (default @import handling) */
@import "reset.css";
.about {
margin: 0;
}
/** Output: */
@import "reset.css" layer(pages);
@layer pages {
.about {
margin: 0;
}
}/** Input file: `src/pages/contact.css` (existing layer function) */
@import "base.css" layer(base);
.contact {
background: white;
}
/** Output: */
@import "base.css" layer(pages.base);
@layer pages {
.contact {
background: white;
}
}// Filter rule: Match files based on path
export interface FilterRuleItem {
includes: RegExp | ((path: string, input: import('postcss').Input) => boolean);
layerName: string;
transformOptions?: TransformOptions;
}
// Map rule: Dynamically generate layer names
export interface MapRuleItem {
map: (path: string, input: import('postcss').Input) =>
string | boolean | { layerName: string; transformOptions?: TransformOptions };
}
export type RuleItem = FilterRuleItem | MapRuleItem;
export interface TransformOptions {
/** Specify which @rules should stay outside @layer */
outsideAtRules?: string[];
}
export type PluginOptions = {
rules: RuleItem[];
/** If set to true, files containing only comments will be ignored */
ignoreOnlyComments?: boolean;
/** Global transform options */
transformOptions?: TransformOptions;
};- Filter Rule (FilterRuleItem): Uses the
includesproperty to match file paths, applying the specifiedlayerNamewhen matched - Map Rule (MapRuleItem): Uses the
mapfunction to dynamically determine layer names, can return:string: Layer nameboolean:falsemeans skip processing this fileobject: ContainslayerNameand optionaltransformOptions
transformOptions can be configured at:
- Global level: In
PluginOptions.transformOptions - Rule level: In
FilterRuleItem.transformOptions - Map return: In the object returned by
MapRuleItem.map
Option Priority and Merging Rules:
- Rule-level options have higher priority than global options
- Only first-level option objects are automatically merged, nested arrays or objects will not be automatically merged
outsideAtRulesarrays will not be merged, rule-level configuration will completely replace global configuration
- If nested processing is needed (such as merging arrays), it can be achieved by creating the plugin multiple times
import WrapUpLayer from '@web-baseline/postcss-wrap-up-layer';
// First plugin instance
const specificPlugin = WrapUpLayer({
rules: [
{
map: (path: string) => {
const g = /^node_modules[\\/](?:@([^\\/]+)[\\/])?([^\\/]+)[\\/]/.exec(path);
return g ? (g[1] ? g[1] : g[2]) : false;
},
},
],
});
// Second plugin instance: Add lib layer for entire node_modules
const globalPlugin = WrapUpLayer({
rules: [
{
includes: /^node_modules\//,
layerName: 'lib',
},
],
});
// Use multiple plugin instances in PostCSS configuration
export default {
plugins: [
specificPlugin,
globalPlugin,
],
};/** Input file content */
@import 'common.css';
a { width: 100%; }
/** When file is `node_modules/test-lib/index.css`, output: */
@import 'common.css' layer(lib.test-lib);
@layer lib { @layer test-lib { a { width: 100%; } } }
/** When file is `node_modules/@scoped/test-lib/index.css`, output: */
@import 'common.css' layer(lib.scoped);
@layer lib { @layer scoped { a { width: 100%; } } }
/** When file is `node_modules/index.css`, output: */
@import 'common.css' layer(lib);
@layer lib { a { width: 100%; } }By default, the following @rules will stay outside @layer:
charsetnamespacepropertyfont-facekeyframes
You can add more rules (like import) through transformOptions.outsideAtRules.