A set of tools inspired to CSS Modules and Tailwind CSS framework.
- Reduce the css size nesting the selectors where convenient.
- Handle CSS modules.
- Scopes CSS variables.
- Calculate atomic css utilities on the fly and assign them in the CSS module object.
Both the tools names above are great, just the use of Tailwind can make the source code verbose and sometime hard to read and maintain. This plugin try to bring the same idea reducing the CSS footprint using CSS utilities calculted at build time reading a standard CSS source code, and orgenise the class utilities in the CSS Module Object even keeping most of the CSS Modules functionalities. This approach keep the functionalities agnostic in terms of the CSS framework used putting in the center just the CSS standard. Also There are great CSS Frameworks out there quite largely used, such as Bootstrap, Bulma, SemanticUI and so on. Use this plugin in addition to those CSS Frameworks will improve the CSS footprint without change your code base. With atomic-css you are the framework rock star 🤘😎🤘.
- Options
- Plugins
The test
option allow to select the files must be processed if specified, all the files will be processed othewise.
example:
// options
{
test: {
include: /\.util\.css$/,
exclude: /\.not\.util\.css$/,
},
}
Both the test
options (include
and exclude
) can accept as parameter a RegExp
, a string
, a null
value or an array of them. In the example above all the files which ands with .util.css
will be processed, except for the files ending with .not.util.css
.
The modern browsers supports the nested selectors.
This option accept two values nested
or flat
, nested
is the default value.
The nested
option nests the selectors and flat them just where convenient in order to reduce the resultant css code output size.
Specially useful if you are using any css preprocessor, which flat all selectors.
The flat
option is flats all selectors, in case your target is any browser which is not supporting the selector nesting.
example:
// options
{
selectors: 'flat', // or 'nested'
}
The suffix scope ID is calculated with a SHA-256 hash 8
characters long by default preceded form _
, the key used is:
- the css code for the CSS Modules and
keyframes
names. - "/" by default if
cssVariables
option istrue
or the string assigned to the option intead for the CSS variables. - the CSS property
value
for the utilities insemireadable
mode orproperty
advalue
formcoded
mode.
Using the first 8
characters of a SHA-256 hash provides 16^8 (approximately 4.3 billion) possible combinations. While this is a large number, it's not guaranteed to be unique in all cases, especially in scenarios with a massive number of files. For most practical purposes, this should be sufficient to avoid collisions, but it's important to consider the context in which the plugin is used.
If you want to increase the uniqueness without adding too much complexity, you could use a longer portion of the hash. However, for most applications, 8
characters are generally enough.
shorthand to increse the scope suffix ID:
// Options
{
scope: 10 // length or you can assign a scope configuration object
}
// Options
{
scope: {
length: 10,
},
}
The scope ID suffix will be applied to all the classNames and keyframes names into the file.
It is possible assign a function (className: string, filePath: string, css: string) => string
for customize the className, this will be used just for the classNames, in this case the suffix scope will be attached at the end of the returned string.
By default is true
;
as example:
// Options
{
scope: {
classNames: true,
},
}
.title { color: green; }
.title:hover { color: red; }
.fade-in { animation: 3s linear fadeIn, 3s ease-out 5s moveIn; }
@keyframes fadeIn { opacity: 1; }
@keyframes moveIn { margin-top: 100px; }
After the transformation it will become like this:
.title_116zl1d3 { color: green; }
.title_116zl1d3:hover { color: red; }
.fade-in_116zl1d3 {
animation: 3s linear fadeIn_116zl1d3, 3s ease-out 5s moveIn_116zl1d3;
}
@keyframes fadeIn_116zl1d3 { opacity: 1; }
@keyframes moveIn_116zl1d3 { margin-top: 100px; }
Is possible using the CSS Module option to keep global (so without scope suffix) a class or an animation name.
You can use the syntax :global(.className)
for the classes and global(animationName)
for the keyframes.
:global(.title) { color: green; }
.title:hover { color: red; }
.fade-in { animation: 3s linear global(fadeIn), 3s ease-out 5s moveIn; }
@keyframes fadeIn { opacity: 1; }
@keyframes moveIn { margin-top: 100px; }
After the transformation it will become like this:
.title { color: green; }
.title:hover { color: red; }
.fade-in_116zl1d3 {
animation: 3s linear fadeIn, 3s ease-out 5s moveIn_116zl1d3;
}
@keyframes fadeIn { opacity: 1; }
@keyframes moveIn_116zl1d3 { margin-top: 100px; }
And the getModules function give you a Module object for transformed classes:
{
'fade-in': 'fade-in_116zl1d3'
}
The scope configuration object can contains a cssVariables
option, which accept as value a boolean
, a string
or a configuration object.
These options handle the behaviour related to che CSS Variable scoping.
If cssVariables
are set to true
, a scope suffix will be added for the CSS variables declared in the :root
selector using /
by default as key for generate the the suffix ID with the current length.
If a string
is assigned, this will be used as key for the suffix ID.
Configuration object example:
// Options
{
scope: {
cssVariables: {
key: '/' // by default - optional
include: /^-[A-Z]+/, // optional
exclude: /[a-z]$/ // optional
},
}
}
In the example above has been used the optional options include
and exclude
to filter the CSS variable names where apply the scope.
The getModules
option accept a function
or an async function
which get as parameters modules
(an object with the className as key and the scoped className as value), and filePath
(which is the full file path);
The function will be not called if scope.classNames
and utility
are set to false
.
example:
// options
{
getModules: (modules, filepath) => {
// do something
},
}
Where the modules object will be something like:
{
'title': 'title_116zl1d3',
'fade-in': 'fade-in_116zl1d3'
}
This function will be called if the modules
and/or the utility
options are set.
You can check here more info about the utility
option.
Useful to redulce the CSS resultant code size. This option accept a string array as value, where each string represent a regular expression of the classed used. The unused classes (and keyframes) will be removed from the resultant CSS code.
The utility
option accept as value a true
or a configuration object where the true
is a shorthend for dafault configuration.
// options
{
utility: {
mode: 'readable', // by default, could be 'readable' | 'semireadable' | 'coded'
atRules: { // all are true by default for process the @ rules
container: true,
layer: true,
media: true,
scope: true,
supports: true,
},
output: true, // by default, append the css utilites rules at the end of the file if true
},
}
The utility functionality read the CSS properies and values decalsered in each class and for each declaration generate a
new CSS rule .property-name[_value] { property-name: value; }
.
The property will be removed from the original CSS class rule and the utiliy className will be added to the Css module for that className.
Will be generated even a Css utility module you can obtain assigning by assigning a function for getUtilityModules
option.
// CSS module
{
'class1': 'class1 property-name[_value]',
}
// CSS utility module
{
'property-name[_value]': '.property-name[_value] { property-name: value; }'
}
If the original don't contains any other property and any other selector, the class will be removed from the resultant CSS and from the CSS module value.
/* CSS source */
.class1 {
animation: 3s linear animationName;
}
// CSS module
{
'class1': 'animation[_3s_linear_animationName]',
}
/* CSS result */
.animation[_3s_linear_animationName] { animation: 3s linear animationName; }
With nested selectors:
/* CSS source */
.class1 {
animation: 3s linear animationName;
div { margin: 0; }
}
// CSS module
{
'class1': 'class1 animation[_3s_linear_animationName]',
}
/* CSS result */
.class1 {
div { margin: 0; }
}
.animation[_3s_linear_animationName] { animation: 3s linear animationName; }
In order to support a better DX and optimise teh CSS footprint, there are 3 differnt mode
for the utility classNames generation:
readable
: the utility className will folow the syntaxproperty-name[_value]
, where the value is the CSS property value string with the replacement for any space, comma or dot using a single-
as in the example above.semireadable
: the className syntax will beproperty-name[_116zl1d3]
, where the hash code will be calculated using the property value as key.coded
: the className syntax will be_a26fl1d4
, where the hash code will be calculated using the property name and value as key.
For a more elastic usage as example, if the plugin want to be implemented in a bundle loader tool pligin such as vite, webpack or rspack,
is possible use in combination the getUtilityModules
and output
options for generate an utilities map and not having the utiliy classes in the resultant CSS code, giving the opportunity to implemet a more optimised logic for separate utiliies CSS code bundle generation using the Utility Modules.
// Options
{
utility: {
getUtilityModules(filePath, modules) { /** your logic */},
output: false,
}
}
/* CSS source */
.class1 {
animation: 3s linear animationName;
div { margin: 0; }
}
// CSS module
{
'class1': 'class1 animation[_3s_linear_animationName]',
}
// CSS utility module
{
'animation[_3s_linear_animationName]': '.animation[_3s_linear_animationName] { animation: 3s linear animationName; }'
}
/* CSS result */
.class1 {
div { margin: 0; }
}
Of course we can expect a CSS class to be reused and modified for a specific selector. In this case only the calss with the less specific selector will be processed as example:
/* CSS source */
.panel {
background-color: white;
.panel-box {
padding: 1em;
font-size: 1em;
}
.panel-header {
background-color: grey;
.panel-box { padding: 0.5em; }
}
.panel-footer {
background-color: lightgrey;
.panel-box { padding: 0.3em; }
}
}
// CSS module
{
'panel': 'panel background-color[_white]',
'panel-box': 'padding[_1em] font-size[_1em]',
'panel-header': 'panel-header background-color[_grey]',
'panel-footer': 'panel-footer background-color[_lightgrey]'
}
// CSS utility module
{
'background-color[_white]': '.background-color[_white] { background-color: white; }',
'padding[_1em]': '.padding[_1em] { padding: 1em; }',
'font-size[_1em]': '.font-size[_1em] { font-size: 1em; }',
'background-color[_grey]': '.background-color[_grey] { background-color: grey; }',
'background-color[_lightgrey]': '.background-color[_lightgrey] { background-color: lightgrey; }'
}
/* CSS result */
.panel {
.panel-header {
.panel-box { padding: 0.5em; }
}
.panel-footer {
.panel-box { padding: 0.3em; }
}
}
.background-color[_white] { background-color: white; }
.padding[_1em] { padding: 1em; }
.font-size[_1em] { font-size: 1em; }
.background-color[_grey] { background-color: grey; }
.background-color[_lightgrey] { background-color: lightgrey; }
Utility classes will be generated even for vendor perfixes --webkit-
, --moz-
, --ms-
and --o-
.
Duplicated Properties for the same className at the same levell will be overwritten.