背景
设计之初本想走在 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
;
我们本次转译重点是 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 改变后剩下的交给浏览器。
参考链接:
- Less to CSS:调试less的特性,用它 www.cssportal.com/less-to-css…
- Less/SCSS 变量与 CSS custom properties 的区别