Sergio
Sergio

Reputation: 28837

Use browser to parse CSS without touching the DOM/styles

I have something like:

const someCSS = `
    .foo {
        padding: 20px;
        background-color: #ddf;
        width: 100px;
    }
    .bar {
        height: 100px;
    }
    .foo {
        padding-top: 30px; /* this overrides the previous one */
    }
`;

I can add this do the DOM and get back all selectors with each rule like this (jsFiddle):

const style = document.createElement('style');
style.innerHTML = someCSS;
document.head.append(style);

const styleSheet = Array.from(document.styleSheets).find(ss => ss.ownerNode == style);
const rules = Array.from(styleSheet.rules).map(rule => rule.cssText);

function styleToObject(rules, mergeWith = {}) {
    return [...rules].reduce(
        (obj, rule) => (obj[rule] = rules[rule], obj), mergeWith
    );
}

const styleObject = Array.from(styleSheet.rules).reduce(
    (obj, rule) => (obj[rule.selectorText] = styleToObject(rule.style, obj[rule.selectorText]), obj), {}
);

document.querySelector('pre').appendChild(
    document.createTextNode(JSON.stringify(styleObject, null, '\t'))
);

and get something like this:

{
    ".foo": {
        "padding-top": "30px",
        "padding-right": "20px",
        "padding-bottom": "20px",
        "padding-left": "20px",
        "background-color": "rgb(221, 221, 255)",
        "width": "100px"
    },
    ".bar": {
        "height": "100px"
    }
}

Is there another way to have the browser do this, without touching the DOM? I mean have a CSS text parsed by the browser (no regex) without actually styling anything in the page.

I though about adding it to a iFrame, but before its appended to the DOM the document is not available...

Upvotes: 5

Views: 610

Answers (1)

Rogier Spieker
Rogier Spieker

Reputation: 4187

The short answer is no, you can't without changing the DOM.

If your concern is the added element triggering a redraw or the loaded style influencing the page in any way, you could add a "never-matching" media rule to the <style>-element you create.

For example:

style.setAttribute('media', '(max-width: 0)');

Working fiddle

EDIT Was working on an example utilising this trick/hack/solution, you can find it here. Only now noticed the update to the question which is rather similar in mechanics (although my sample will work in less green browsers (not part of the question, I know)).

I've checked some sources which I've come across when I was trying to do a similar thing, most notably MDN - CSSStylesheets is very thorough and states:

A CSSStyleSheet object is created and inserted into the document's styleSheets list automatically by the browser, when a style sheet is loaded for a document. As the document.styleSheets list cannot be modified directly, there's no useful way to create a new CSSStyleSheet object manually (although Constructable Stylesheet Objects might get added to the Web APIs at some point). To create a new stylesheet, insert a or element into the document.

(Emphasis mine. CSO already mentioned by @Ouroborus)

I haven't done a lot of testing on various browsers, but I haven't seen redraws and/or reflows by adding the (media queried) style node to the <head>, unlike adding an <iframe>.

I'm curious if someone out here knows of a solution which relies on the (cross-)browser for processing CSS without hitting the DOM, as I haven't found it and ended up building a Tokenizer/Lexer to create such a tree).

Upvotes: 7

Related Questions