首页 > 解决方案 > 将 CSS 结构解析为对象

问题描述

我正在尝试解析一些 CSS 的结构以用作 Node.js 中 renderkid 的输入,有点(但不一样)就像Parsing CSS in JavaScript / jQuery一样。

例如,我想要这个 CSS

body { font-size: 10px; }
html { font-size: 11px; }
html, body { font-size: 12px; }
@media only screen and (max-width: 600px) {
  body {
    background-color: lightblue;
  }
}

被解析为这个对象:

{
  body: {
    "font-size": "12px"
  },
  html: {
    "font-size": "12px"
  },
  "@media only screen and (max-width: 600px)": {
    body: {
      "font-size": "12px"
    },
  }
}

为了避免通过编写 CSS 解析器重新发明轮子,我使用了流行的css包,它返回提供的 CSS 的 AST,在这种情况下看起来像这样:

{
    "type": "stylesheet",
    "stylesheet": {
        "rules": [{
            "type": "rule",
            "selectors": ["body"],
            "declarations": [{
                "type": "declaration",
                "property": "font-size",
                "value": "10px",
                "position": {
                    "start": {
                        "line": 1,
                        "column": 8
                    },
                    "end": {
                        "line": 1,
                        "column": 23
                    }
                }
            }],
            "position": {
                "start": {
                    "line": 1,
                    "column": 1
                },
                "end": {
                    "line": 1,
                    "column": 26
                }
            }
        }, {
            "type": "rule",
            "selectors": ["html"],
            "declarations": [{
                "type": "declaration",
                "property": "font-size",
                "value": "11px",
                "position": {
                    "start": {
                        "line": 2,
                        "column": 8
                    },
                    "end": {
                        "line": 2,
                        "column": 23
                    }
                }
            }],
            "position": {
                "start": {
                    "line": 2,
                    "column": 1
                },
                "end": {
                    "line": 2,
                    "column": 26
                }
            }
        }, {
            "type": "rule",
            "selectors": ["html", "body"],
            "declarations": [{
                "type": "declaration",
                "property": "font-size",
                "value": "12px",
                "position": {
                    "start": {
                        "line": 3,
                        "column": 14
                    },
                    "end": {
                        "line": 3,
                        "column": 29
                    }
                }
            }],
            "position": {
                "start": {
                    "line": 3,
                    "column": 1
                },
                "end": {
                    "line": 3,
                    "column": 32
                }
            }
        }, {
            "type": "media",
            "media": "only screen and (max-width: 600px)",
            "rules": [{
                "type": "rule",
                "selectors": ["body"],
                "declarations": [{
                    "type": "declaration",
                    "property": "background-color",
                    "value": "lightblue",
                    "position": {
                        "start": {
                            "line": 6,
                            "column": 5
                        },
                        "end": {
                            "line": 6,
                            "column": 32
                        }
                    }
                }],
                "position": {
                    "start": {
                        "line": 5,
                        "column": 3
                    },
                    "end": {
                        "line": 7,
                        "column": 4
                    }
                }
            }],
            "position": {
                "start": {
                    "line": 4,
                    "column": 1
                },
                "end": {
                    "line": 8,
                    "column": 2
                }
            }
        }],
        "parsingErrors": []
    }
}

目前,我设法想出了这段代码:

"use strict"

const {
    parse: parseCSS,
} = require("css")
const _ = require("lodash")

const pickToObject = (array, ...keys) => _.fromPairs(array.map((val) => [val[keys[0]], val[keys[1]]]))

module.exports = (css) => _.merge(...parseCSS(css).stylesheet.rules.map(({
    declarations,
    selectors,
    type,
    media,
    rules,
}) => {
    if (type === "rule") return _.fromPairs(selectors.map((selector) => [selector, pickToObject(declarations, "property", "value")]))
    if (type === "media") {
        return _.fromPairs([
            [`@media ${media}`, _.merge(...rules.map(({
                selectors,
                declarations,
            }) => _.fromPairs(selectors.map((selector) => [selector, pickToObject(declarations, "property", "value")]))))],
        ])
    }

    return undefined
}))

但是,我不确定如何进一步优化它。

澄清一下:我需要创建一个可以处理任何有效 CSS 的规范函数——这意味着不能简单地从 AST 中获取值。

标签: javascriptcssparsingabstract-syntax-tree

解决方案


使用reduce解决:

let styles = {
    "type": "stylesheet",
    "stylesheet": {
        "rules": [{
            "type": "rule",
            "selectors": ["body"],
            "declarations": [{
                "type": "declaration",
                "property": "background-color",
                "value": "black",
                "position": {
                    "start": {
                        "line": 1,
                        "column": 8
                    },
                    "end": {
                        "line": 1,
                        "column": 32
                    }
                }
            }],
            "position": {
                "start": {
                    "line": 1,
                    "column": 1
                },
                "end": {
                    "line": 1,
                    "column": 33
                }
            }
        }],
        "parsingErrors": []
    }
}

let parsedStyle = styles.stylesheet.rules.reduce((parsed, rule) => {
    parsed[rule['selectors']] = rule['declarations'].reduce((rr, r) => {
    rr[r['property']] = r['value'];return rr
    }, {})
    return parsed
}, {})

console.log(parsedStyle)


推荐阅读