LESS 变量 转换为 CSS 变量

412 阅读3分钟

背景

设计之初本想走在 web 前沿,跟随 antd-mobile@5.x 的脚步,求索 css var 的真谛,人算不如天算,万万没想到😭......(此处省略一万字)

为啥呢?从下图中可以看出 chrome49 才刚开始支持,然而实测安卓6.0正好是 chrome48,6.0以下有一万多用户,经讨论不能放弃,所以暂时停用了yui。

我期望的

我有 css 变量

:root {
  --a: red;
}

#page {
  --a: white;
  #header {
    color: var(--b, black);
  }
}

变成 👉🏻 Less 变量

@a: red;

#page {
  @a: white;
  #header {
    color: @a; // white
  }
}

好像替换下就行吧,,我用正则

😄 最初设想的替换

'var(--a, --c)'.replace(/var((.*),$/g, '$1')

🙂 实操后增加的用例

`.popup {
    width: var(--w);
    height: var(--w, --h);
  }
`.replace(/var(([^,)]+),?[\S\s]*?)+/g, '$1')

😶 两小时后,,,(我写不动了,让大佬同事帮写的)

`.popup {
    width: var(--w);
    width: var(--w, --w1); 
    // width: var(--w, --w1); 
    height: var(--h, var(--h1), var(--h2, --h3));
    padding: var(--pt) var(--pb);
    padding: var(--pt) var(--pb) !important; 
  }
`.replace(/var(([^,)]+),?[\S\s]*?)(?=[*]|\s|;|$)/img, '$1')

🙃 再加上注释里如果包含了关键字,但不希望被修改呢?

万能解法: AST

众所周知,在语法转换界有个东西叫 AST(abstract syntax tree)。像 babel 结构化 js 字符串一样,借助工具也能把 less、css 等语言结构化。关于 CSS AST 这里有三个概念需要定义一下:

  • AtRule:以 @ 开始的语句,就像 @charset "UTF-8"@media(screen){}
  • Rule: 一整条的 class 样式,包含 selectors declaration
  • Declaration: 键-值对,就像 color: black;
图例
如图就是转换后的结构,value 就是我们的目标

我们本次转译重点是 css var ,观察下 value,竟然还是一整串字符,,这怎么转呢?还用正则吗?当转换中遇到 var(--a, var(--b)) 的时候,也希望得到结构化的数据,这样只需要取 nodes 里第一个 type == 'word' 的 value,把 -- 替换成 @即可。

[
    {
        "type": "function",
        "value": "var",
        "nodes": [
            {
                "type": "word",
                "value": "--a"
            },
            {
                "type": "div",
                "value": ","
            },
            {
                "type": "function",
                "value": "var",
                "nodes": [
                    {
                        "type": "word",
                        "value": "--b"
                    }
                ]
            }
        ]
    }
]

分词

对啦,借助 postcss-value-parser 解析成那种结构的过程就叫做分词。也可以直接用基于它实现的 postcss-css-var-to-less-var 来做转换。

结合 gulp 对整个项目的所有文件批量操作,完整代码如下:

const gulp = require('gulp')
const Input = require('postcss/lib/input')
// !used patch-package
const { LessParser } = require('postcss-less')
const through = require('through2')
const varConvert = require('postcss-css-var-to-less-var')
const convertPip = () =>
  through.obj((file, enc, cb) => {
    const input = new Input(file.contents)
    const parser = new LessParser(input)
    
    parser.parse()
    varConvert()(parser.root)
    file.contents = Buffer.from(parser.root.toString(), enc)
    cb(null, file)
  })

gulp
  .src('src/**/*.less', { base: './' })
  .pipe(convertPip())
  .pipe(gulp.dest('./', { overwrite: true }))
  

运行这段代码的同学可能会发现,postcss-less 中并没有导出 LessParser,为何你能运行呢?原因就是使用了 patch-package,它的作用是把你对 node_modules 目录内容的修改记录到patches/**.patch,即使重新 npm install 之后,也能用 npx patch-package 应用上你之前的修改。

Less var VS css var

最后简单再说一下两者的区别:

  • css var 是真正意义上的变量,符合直觉,是动态的,甚至在运行时直接改变 ele.style.setProperty('--var-value', old + 10)
  • Less var 的变量查找规则是本地(同一个大括号里)找不到就去父级找,是静态的;

经验丰富的同学会想我用 context theme 方式、 css-in-js 也可以动态,但是会有运行时开销,组件要跟随重新渲染,而 css var 改变后剩下的交给浏览器。

参考链接: