Three Musketeers of CSS precompiler and PostCSS

This article contains two parts, the first part is a CSS pre-compiler: Sass, Less, Stylus, the comparison between them, and the second part is the popular PostCSS.

Why is there such a thing as a CSS pre-compiler? This is about the shortcomings of CSS: no variables (the new specification already supports), no nesting, weak programming ability, and poor code reusability .
These shortcomings result in the poor maintainability of the written CSS, and it contains a lot of repetitive code; in order to make up for these shortcomings, the CSS pre-compiler came into being. When it comes to CSS pre-compilers, these three swordsmen Sass, Less, and Stylus are inseparable. In history, Sass was the first to appear on the stage, because it appeared the earliest, so it is the most complete, with a variety of rich functions; the appearance of Less accompanied the popularity of Bootstrap, so it also gained a large number of users; the last is Stylus, developed by TJ Great God (King Dashen), because of its concise syntax, it is more like a programming language, and it is very cool to write. So let's make a simple comparison below.

Less & SCSS:
.wrap {
display: block;
}

Sass:
.wrap
display: block

Stylus:
.wrap
display block
Sass initially controlled the hierarchical relationship through indentation, spaces, and line breaks . Students who have written Python will not be unfamiliar with it. Later, they supported the traditional CSS-like syntax of Scss. Less is quite satisfactory and uses CSS style, which is very friendly to novices and is also conducive to the migration of existing projects. Stylus can be written using Sass style syntax, and it is also compatible with CSS style.

All three support the definition of variables, but the definition methods are different:

Less:
@smallFont: 12px;
small {
font-size: @smallFont;
}

Sass:
$smallFont: 12px;
small {
font-size: $smallFont;
}

Stylus:
smallFont = 12px
small
font-size smallFont

It should be noted that the variable declared in Stylus, if the variable name is the same as the literal value in CSS, it will overwrite the literal value in CSS.

There is a fun thing: I believe that everyone usually comes into contact with the component library more or less. The CSS pre-compiler will be used to write the style in the preparation of the component library, and the custom style can be provided by passing in external variables. Function, just like Ant.design and ElementUI. This involves the scope of CSS variables. Among the three precompilations, Sass/Stylus is the same, and Less is another case. Let’s look at the example below:

Less:
@color: red;
.content-1 {
color: @color;
}

@color: black;
.content-2 {
color: @color;
}

/* The compiled CSS*/
.content-1 { color: black; }

.content-2 { color: black; } The variable in Less, when used in the declaration, if there are multiple assignments, it will take the value of the last assignment, which is the above .content-1, content- The colors in 2 are all black reasons. Based on this feature of the variable in Less, we can easily change the value of the variable in the original component library or class library, and we only need to assign a value to a specific variable after importing the Less file. At the same time, it will also bring certain hidden dangers: if different component libraries or class libraries use the same variable name, then there will be an overwrite situation, so a modular approach should be adopted.


Sass:
$color: red;
.content-1 {
color: $color;
}

$color: black;
.content-2 {
color: $color;
}

Stylus:
color = red;
.content-1
color color

color = black;
.content-2
color color;

/* The compiled CSS*/
.content-1 { color: red; }

.content-2 { color: black; }As we can see above, if a variable in Sass/Stylus has multiple assignments, it will take the value of the most recent assignment before the declaration. This is why .content-1 The color of .content-2 is red and the color of .content-2 is black. At the same time, the variables in different component libraries or class libraries written by Sass/Stylus will not conflict, but this poses a challenge for customizing the style by overriding the value of the variable. What should we do? The test point is here. Sass/Stylus provides a variable declaration "assign if it doesn't exist":


Sass:
$a: 1;
$a: 5 !default;
$b: 3 !default;
// $a = 1, $b = 3

Stylus:
a = 1
a := 5
b = 3
// a = 1, b = 3
If you look at the code carefully, you will surely understand: using the syntax of "assign if it doesn't exist", the compiler will check whether the variable with the same name is defined before. If there is no definition, execute the variable definition; if the variable with the same name is defined before, the assignment operation will not be executed. Therefore, we can define variables in the component library or class library that uses Sass/Stylus in the way of "assign if it does not exist", and define the variable with the same name before importing, and then we can achieve the goal.

At the same time, the variables we define can not only be used as values ​​declared by CSS, but can also be nested into selectors and attributes:

Less:
@prefix: ui;
@prop: background;
.@{prefix}-button {
@{prop}-color: red;
}

Sass:
$prefix: ui
KaTeX parse error: Expected 'EOF', got '#' at position 21: … background; .#̲{ prefix}-button{
#{$prop}-color: red;
}

Stylus:
prefix = ui
prop = background
.{prefix}-button
{prop}-color red

The variable-related content is basically covered, and the next step is the reuse of styles. When it comes to style reuse, mixin and inheritance must be mentioned. For mixin, these three pre-compilers also have different implementations:

Less:

.mixin-style(@color: red) {
color: @color;
}

.content {
.mixin-style(red);
}

Sass:
@mixin mixin-style {
color: red;
}

.content {
@include mixin-style;
}

Stylus:
mixin-style(color)
color color

.content
mixin-style(red) // or mixin-styls red transparent mixin
in Less, you can directly introduce a CSS class as a mixin (this method is very not recommended), and also provide the above mixin that can pass in parameters ; Sass is quite satisfactory, and mixins are defined and introduced through @mixin and @include; Stylus does not require explicit declaration of mixins, and also provides the function of transparent mixins, which are introduced just like attributes.

Next, we will talk about inheritance. Among them, Sass/Stylus inherits styles through the @extend keyword, while Less inherits through pseudo-classes:

Sass/Stylus:

.message{
padding: 10px;
border: 1px solid #eee;
}

.warning{
@extend .message;
color: #e2e21e;
}

Less:

.message {
padding: 10px;
border: 1px solid #eee;
}

.warning { &:extend(.message); color: #e2e21e; } You can judge the pros and cons of them. For more detailed comparison, you can check Gu Yiling's article.



After talking about the CSS preprocessor, let's talk about the popular PostCSS. When it comes to PostCSS, people always ask, what is PostCSS? For this question, let’s take a look at its definition on the official website:

PostCSS is a tool for transforming styles with JS plugins. These plugins can lint your CSS, support variables and mixins, transpile future CSS syntax, inline images, and more.
So my understanding is that PostCSS is a tool for transforming styles with JS plugins. tool. The positioning of PostCSS and CSS preprocessor is different. After reading the previous content, everyone knows what the role of CSS preprocessor is. The main role of PostCSS is lint css, supporting CSS Next syntax, automatically adding prefixes, and so on. Plug-ins can basically cover the functions of CSS preprocessors, while at the same time achieving functions that many preprocessors cannot. Since PostCSS is so awesome, let's take a closer look.

For PostCSS, there are a few points that need to be understood first:

  1. PostCSS is not a replacement for the CSS preprocessor, although it can be replaced by PostCSS. For existing projects, it is easy to use PostCSS, no matter which preprocessor or build tool you are using, there will not be too much migration cost.
  2. The advantage of PostCSS lies in its rich plug-in ecology, which can cover all aspects of development, and we can easily develop our own plug-ins. At the same time, because PostCSS uses Javascript, Javascript itself and its prosperous ecology provide PostCSS with more powerful capabilities.
  3. In PostCSS, the most well-known one is Autoprefix, and Autoprefix is ​​used in a large number of preprocessing. Relying on its modern architecture design, PostCSS gets rid of the burden of history and uses its plug-in system to more elegantly strengthen the robustness of CSS

The PostCSS architecture is roughly as follows:
Insert picture description here

Convert CSS to AST (Abstract Syntax Tree) through PostCSS, which corresponds to JavaScript objects; then traverse the AST through plug-ins, add, delete, and modify; and finally generate CSS files. This is the entire process, which is very similar to the architecture of babel.

Among them, CSS is parsed into AST and AST serialized to generate CSS. PostCSS has done it for us. We only need to pay attention to plug-in development and how to operate AST.

So below we use a simple example to illustrate how to develop PostCSS plugins. There is such a style:

.content{ color: red; } If we detect a style rule like "color: red" in a certain style, we need to add a rule like "background-color: red" automatically.


PostCSS officially provides templates written by plugins, just download them:

git clone [email protected]:postcss/postcss-plugin-boilerplate.git

Then enter the directory of the folder, enter the command:

At
this time, node start will require you to enter some information, and then it will automatically generate a template folder for the plug-in, or enter the corresponding folder, at this time you can see the familiar package.json, index.js and other files , After installing the dependencies using npm, you can write the logic of the plug-in in the index.js file. Similarly, we can install any dependencies in the project through npm, and then import them through require in index.js, which is the advantage of relying on JavaScript.

The default content of index.js is this:

var postcss = require(‘postcss’);

module.exports = postcss.plugin(‘postcss-practice’, function (opts) {
opts = opts || {};

// Work with options here

return function (root, result) {

    // Transform CSS AST here

};

});
The plug-in code written is like this:

const postcss = require(‘postcss’);

const postcss = require(‘postcss’);

module.exports = postcss.plugin(‘postcss-practice’, function (opts) {
opts = opts || {};
console.log(opts);
// Work with options here

return function (root) {
    root.walkRules(rule => {
        console.log(rule.selector);
        rule.walkDecls(decl => {
            if (
                decl.prop === 'color' &&
                decl.value === 'red'
            ) {
                rule.append({
                    prop: 'background-color',
                    value: 'red'
                });
            }
        });
    });

};

});
Very simple, explain: root.walkRules will traverse every CSS rule, you can get the selector in each set of rules through rule.selector, and then traverse the style declarations in each set of rules through rule.walkDecls, Through decl.prop, decl.value get the attributes and values ​​in the style declaration. The above judges whether the attribute is'background-color' and the value is'red', and if the conditions are met, a new style declaration is inserted into the rule (here for simplicity, it does not consider whether a background-color declaration already exists).

After the plug-in is written, index.test.js is also included in the sample project to test whether the plug-in is reasonable. The following is the test code in index.test.js:

var postcss = require(‘postcss’);

var plugin = require(’./index.js’);

function run(input, output, opts) {
return postcss([ plugin(opts) ]).process(input)
.then(result => {
expect(result.css).toEqual(output);
expect(result.warnings().length).toBe(0);
});
}

// Write tests here

it('does something', () => { return run('a{color: red;}', 'a{color: red;background-color: red;}', {}); }); then Run: The result of "npm run test":




Insert picture description here

Then it can be packaged and released, and then installed and used in the project, just like other plug-ins. PostCSS also provides many useful functions to help you operate AST and easily complete development.

Guess you like

Origin blog.csdn.net/s8806479/article/details/115213339