构建轻巧的CSS格式化
#javascript #css #编程 #prettier

去年,我们向该网站介绍了一个预感,因为这是您在审核CSS时经常想做的事情之一。然后,不久之后,我们添加了在分析CSS之前对您的CSS进行漂亮的选项。因为我们现在在分析仪页面上有一个DevTools面板,所以以格式化的方式查看CSS使用情况很有意义。但这是有代价的:使CSS占据最高两倍的CSS分析CSS!是时候查看更具性能的替代方案了。

<img alt="Example output of formatted CSS that we need to audit" src="https://user-images.githubusercontent.com/1536852/244181349-9824fdf7-abfe-45f5-b2c4-859d87f7e8d1.png">
<figcaption>CSS that is being audited where the source code was originally minified, making the auditing process difficult, because those lines become pretty much unreadable.</figcaption>

天真的方式:更漂亮

我们的预期仪的第一次迭代使用了Prettier,这是一个非常受欢迎且受人尊敬的项目,可以相当印刷数百种不同的语言。甚至在测试之前,我已经很确定这将是一个缓慢的函数调用,因此我继续进行,并确保仅导入相关模块将函数放在网上工作人员中以将其运行UI线程。

// prettify-worker.js
import prettier from 'prettier/esm/standalone.mjs'
import cssParser from 'prettier/esm/parser-postcss.mjs'

// The `event` here is the message we send from the UI to
// the worker and `event.data` contains the string of CSS.
onmessage = function (event) {
    try {
        let result = prettier.format(event.data, {
            parser: 'css',
            plugins: [cssParser]
        })
        // Send result back to UI thread
        postMessage(result)
    } catch (error) {
        postMessage({ error })
    }
}

这效果很好,但是几周后,我经常注意到分析仪页面上的进度被卡在“ Premttifying CSS” 步骤上。这不一定是一件坏事,但是如果您像我一样分析CSS,一段时间后就会很烦人。而且由于Premintifying CSS甚至不是我们的核心业务(如果可以的话),则更令人沮丧。

CSSTREE进行营救

在考虑了一段时间的问题之后,我意识到我可以邀请CSSTree的帮助来完成一些工作。 CSS分析仪基于CSstree的AST,因此我知道事物的工作原理,并且依赖项已经在页面上,因此无需下载更多的依赖项。 Prettier + Postcss的费用几乎要下载340kb,这并不大,但是如果我们可以减少该金额,那就太好了。

那么,如何将(潜在缩小)CSS的字符串变成带有CSstree的大多数可读CSS的字符串?让我们从分析CSS开始,因此我们可以与AST合作。然后,使用该AST,我们应用我们对CSS结构的知识将它们变成可读的字符串,逐行。

快速CSS解析

csstree具有一些整洁的解析选择,可以加快速度。它使您可以跳过某些令牌,这将减少内存使用量以及所有这些。以下脚本创建了我们CSS的AST。可以在ASTExplorer上检查此类AST的一个例子。

let ast = parse(css, {
    positions: true,
    parseAtrulePrelude: false,
    parseCustomProperty: false,
    parseValue: false
})

我们可以跳过解析术语前列,自定义属性和值,因为我们只对其“原始”字符串值感兴趣,而不是其中的更深令牌。我们确实需要positions,因为这将使我们以后可以做很多css.substring(x, y)

创建可读的CSS字符串

使用AST,我们可以开始考虑如何将其转变为漂亮的CSS。我们需要CSS足够漂亮,以在我们的Devtools中以可读性的方式展示它,而不是比这更奇特的。经过一番思考,我提出了以下规则:

  1. 每个 aTrule 从新线开始
  2. 每个规则从新线开始
  3. 每个选择器从新线开始
  4. 在每个选择器之后都放置一个逗号
  5. 每个 block {})的缩进比以前的凹痕级别
  6. 多1个选项卡
  7. 每个声明从新线开始
  8. 每个声明以半隆(;)结尾
  9. 块之后放置一个空线,除非它是周围块中的最后一个
  10. 未知语法被渲染为IS

您可以从此列表中看到,我们在此处处理非常有限的CSS代币子集:Stylesheet,ontrule,ulule,selectorlist,selectorlist,selector,selector,block和声明。我们正在从样式表级别开始我们的格式:

function print(node, indent_level = 0, css) {
    let buffer = ''

    for (let child of node.children) {
        if (child.type === 'Rule') {
            buffer += print_rule(child, indent_level, css)
            buffer += '\n'
        } else if (child.type === 'Atrule') {
            buffer += print_atrule(child, indent_level, css)
            buffer += '\n'
        } else {
            buffer += print_unknown(child, indent_level, css)
        }
        buffer += '\n'
    }

    return buffer
}

我们对我们的启示,称为print_atruleprint_rule,看起来像这样:

function print_atrule(node, indent_level, css) {
    let buffer = indent(indent_level)
    buffer += '@' + node.name

    if (node.prelude) {
        buffer += ' ' + substr(node.prelude, css)
    }
    if (node.block && node.block.type === 'Block') {
        buffer += print_block(node.block, indent_level, css)
    } else {
        // `@import url(style.css);` has no block, neither does `@layer layer1;`
        buffer += ';'
    }

    return buffer
}

function print_rule(node, indent_level, css) {
    let buffer = ''

    if (node.prelude && node.prelude.type === 'SelectorList') {
        buffer += print_selectorlist(node.prelude, indent_level, css)
    }

    if (node.block && node.block.type === 'Block') {
        buffer += print_block(node.block, indent_level, css)
    }

    return buffer
}

这也对所有其他类型都进行了一些。这里甚至还有一些递归,因为可以嵌套嵌套(@media和CSS嵌套中,仅举几例),因此我们需要确保也考虑这些。

如果您想查看更多信息:可以找到source code,可以找到on GitHub,我目前正在将其作为独立的NPM包。

权衡

没有项目是完美的,这个小脚本也不是。有些事情以一种简单的方式做得很好,但是除了该包装的范围和必要性之外,我会考虑一些事情。没关系,因为我们只需要它来格式化您的CSS足够好即可轻松审核。

  • 超级快(>比漂亮的90%)
  • ''微小捆绑尺寸(比漂亮的99%)
  • 为我们的用例
  • ,足够漂亮
  • ð¶如果您的源CSS呈现多行(例如长选择器或值),它们将保持多行(我猜是很好)
  • ðº不像Prettier
  • 那样可配置和广泛

这些是我可以忍受的权衡。