美文网首页
Sass、Less、Stylus和PostCSS

Sass、Less、Stylus和PostCSS

作者: CodeMT | 来源:发表于2022-09-02 15:32 被阅读0次

    CSS预编译器:Sass、Less、Stylus

    为什么会出现CSS预编译器这个东西呢?这就要谈到CSS的不足了:没有变量(新的规范已经支持了),不支持嵌套,编程能力较弱,代码复用性差。这些不足导致写出来的CSS维护性极差,同时包含大量重复性的代码;为了弥补这些不足之处,CSS预编译器应运而生。
    而谈到CSS预编译器,就离不开这三剑客SassLessStylus。历史上,最先登场的是Sass,因为出现最早,所以也是最完善的,有各种丰富的功能;Less的出现伴随着Bootstrap的流行,因此也获得大量用户;最后是Stylus,由TJ大神开发(敬大神),由于其简洁的语法,更像是一门编程语言,写起来非常Cool。所以下面我们来做一个简单的对比。

    Less & SCSS:
    .wrap {
      display: block;
    }
    
    Sass:
    .wrap
      display: block
    
    Stylus:
    .wrap
      display block
    
    • Sass最开始通过缩进,空格,换行的形式来控制层级关系,写过Python的同学一定不会陌生,后来又支持了传统的类CSS语法的Scss。Less中规中矩,使用CSS的风格,对新手非常友好,也利于现有项目的迁移。Stylus既可以使用Sass风格的语法来编写,也兼容CSS的风格。

    这三者都支持变量的定义,而定义方式又各不相同:

    Less:
    @smallFont: 12px;
    small {
      font-size: @smallFont;
    }
    
    Sass:
    $smallFont: 12px;
    small {
      font-size: $smallFont;
    }
    
    Stylus:
    smallFont = 12px
    small
      font-size smallFont
    

    注意:Stylus中声明的变量,如果变量名跟CSS中的字面值相同时,会覆盖CSS中的字面值。

    有一个好玩的东西:相信大家平时都或多或少会接触到组件库,组件库的编写中会使用到CSS的预编译器来编写样式,通过外部传入变量的方式来提供自定义样式的功能,就像Ant.design和ElementUI那样。这就涉及到CSS变量的作用范围了,在这三种中预编译中,Sass/Stylus是相同的,Less是另一种情况

    实例:

    Less:
    @color: red;
    .content-1 {
        color: @color;
    }
    
    @color: black;
    .content-2 {
        color: @color;
    }
    
    /* 编译出来的CSS*/
    .content-1 {
      color: black;
    }
    
    .content-2 {
      color: black;
    }
    
    • Less中的变量,在声明中使用时,如果出现多次赋值的情况,其会取最后一次赋值的值,这也就是上面的.content-1, content-2中的color都是black的原因。

    • 基于Less中变量的这个特性,我们可以很轻易的改变原有组件库或者类库中变量的值,只需要在引入Less文件后,对特定的变量赋值即可。

    • 同时也会带来一定的隐患:如果不同的组件库或类库使用了相同的变量名,那么就会出现覆盖的情况,所以应该采用模块化的方式。

    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;
    
    /* 编译出来的CSS*/
    .content-1 {
      color: red;
    }
    
    .content-2 {
      color: black;
    }
    
    • 上面我们可以看到,Sass/Stylus中的变量,如果出现多次赋值的情况,其会取声明前面最近的一次赋值的值,这就是为什么.content-1的color为red,.content-2的color为black的原因。

    • 在Sass/Stylus编写的不同组件库或类库中的变量,不会出现冲突,但是这就为通过覆盖变量的值来自定义样式提出了挑战,我们应该怎么做呢?考点来了,Sass/Stylus中提供了"不存在即赋值"的变量声明:

    Sass:
    $a: 1;
    $a: 5 !default;
    $b: 3 !default;
    // $a = 1, $b = 3
    
    Stylus:
    a = 1
    a := 5 
    b = 3
    // a = 1, b = 3
    
    • 细看代码,你一定会明白:使用"不存在即赋值"语法,编译器会检查前面是否定义了同名变量,如果没有定义,执行变量定义;如果前面定义了同名变量,则不会执行这个赋值的操作。因此,我们可以在使用了Sass/Stylus的组件库或类库中使用"不存在即赋值"的方式来定义变量,在引入之前定义好需要同名变量,就能达到目的了。

    同时,我们定义的变量不仅能用作CSS声明的值,同时也能嵌套到选择器及属性中:

    Less:
    @prefix: ui;
    @prop:  background;
    .@{prefix}-button {
          @{prop}-color: red;
    }
    
    Sass:
    $prefix: ui
    $prop:  background;
    .#{$prefix}-button{
         #{$prop}-color: red;
    }
    
    Stylus:
    prefix = ui
    prop = background
    .{prefix}-button
          {prop}-color red
    
    

    变量相关的内容基本上覆盖到了,接下来就是样式的复用了。提到样式复用,必定会提到mixin和继承了。对于mixin,这三个预编译器也都有不同的实现方式:

    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  透明mixin
    
    • 在Less中,可以直接引入一个CSS的class作为mixin(这种方式非常不推荐), 同时也提供上面的能够传入参数的mixin;Sass比较中规中矩,通过@mixin和@include的方式定义和引入mixin;Stylus不需要显示的声明mixin,同时还提供透明mixin的功能,就像属性一样引入。

    接下来就会讲到继承了,这其中,Sass/Stylus通过@extend关键字来继承样式,而Less通过伪类的方式来继承:

    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;
    }
    

    PostCSS:

    聊完了CSS预处理器,我们接下来聊聊大火的PostCSS。说到PostCSS,大家总会问,PostCSS是什么?这个问题,我们来看看官网对它的定义吧:

    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.

    所以我的理解是,PostCSS是一个使用JS插件来转换样式的工具。PostCSS跟CSS预处理器的定位不同,看了前面的内容,大家都知道CSS预处理器的作用是什么,而PostCSS的作用主要有lint css,支持CSS Next语法,自动添加前缀等等功能,通过插件,基本上可以覆盖CSS 预处理器的功能,同时实现多得多的预处理器实现不了的功能。既然PostCSS这么牛逼,那我们来深入研究一下。

    对于PostCSS,首先需要了解的有这么几个点:

    • PostCSS不是CSS预处理器的替代品,虽然真的可以用PostCSS来替换掉。对于现有的项目,能够很容易的使用上PostCSS,无论你使用的是哪一个预处理器或者哪一个构建工具,都不会有太大的迁移成本。
    • PostCSS的优势在于其丰富的插件生态,能够覆盖开发中的方方面面,我们又能够很容易的开发自己的插件。同时又因为PostCSS使用Javascript,Javascript本身及其繁荣的生态,为PostCSS提供了更加强大的能力。
    • PostCSS中,最为大家所熟知的就是Autoprefix,同时Autoprefix在大量的预处理中使用着。PostCSS依靠其现代的架构设计,甩掉了历史包袱,利用其插件系统,能够更加优雅强化CSS的健壮性

    PostCSS架构大致如下图:

    • 通过PostCSS将CSS转换成AST(抽象语法树),对应的是JavaScript对象;然后通过插件遍历AST,进行增加,删除,修改;最后再生成CSS文件,这就是整个流程,跟babel的架构非常相似。

    • 其中的CSS解析成AST及AST序列化生成CSS,PostCSS都帮我们做好了,我们只需要关注在插件开发,及如何操作AST。

    所以下面我们用一个简单的例子来说明如何开发PostCSS的插件。有这么一个样式:

    .content{
        color: red;
    }
    
    • 如果我们检测到某个样式中有"color: red"这样一条样式规则,需要自动增加"background-color: red"这样一条规则。

    PostCSS官方提供插件编写的模板,只需要将其下载下来:

    git clone git@github.com:postcss/postcss-plugin-boilerplate.git
    

    然后进入该文件夹的目录下,输入命令:

    node start
    

    这个时候会需要你输入一些信息,然后就会自动生成一个插件的模板文件夹,还是进入到这个对应的文件夹下,这个时候就能看到熟悉的package.json,index.js等文件,使用npm安装了依赖之后就可以在index.js文件中编写插件的逻辑了。同样的,我们可以在项目中通过npm安装任意依赖,然后在index.js中通过require引入,这就是背靠JavaScript的优势。

    index.js默认的内容是这样的:

    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
      };
    });
    
    

    编写出来的插件代码是这样的:

    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'
              });
            }
          });
        });
      };
    });     
    
    
    • 非常简单,解释一下:root.walkRules会遍历每一个CSS规则,可以通过rule.selector拿到每一组规则中的选择器,然后通过rule.walkDecls遍历每一组规则中的样式声明, 通过decl.prop,decl.value拿到样式声明中的属性和值。上面判断属性是否为'background-color'、值是否为'red',如果满足条件则向规则中插入一条新的样式声明(这里为了简单,没有考虑是否已经存在background-color声明)。

    插件编写完了,样板项目中还包含index.test.js来测试是否插件是否合理,下面是编写的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;}', {});
    });
    
    

    然后运行: npm run test接下来就可以打包发布,然后在项目中安装使用了,就像其他插件那样。PostCSS还提供了许多有用的函数,帮助你操作AST,轻松完成开发。

    相关文章

      网友评论

          本文标题:Sass、Less、Stylus和PostCSS

          本文链接:https://www.haomeiwen.com/subject/ixvcnctx.html