w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.42k stars 649 forks source link

[cssom] Add `toString` method on `CSSStyleSheet` #7171

Open lucacasonato opened 2 years ago

lucacasonato commented 2 years ago

CSSOM allows programmatic construction and modification of CSSStyleSheets. These created sheets can be attached to a HTML document on the client so users can directly use them. It is however not currently trivial to "export" programmatic style sheets back into a text representation.

This is unfortunate, because the CSSOM API would be great for the generation of style sheets server side, if the server side runtime were to support the API. This is something that we would like to do in Deno (https://github.com/denoland/deno/issues/13898). In Deno, there is no DOM, so the style sheet could never directly be used. Instead you would build a style sheet with the CSSOM API, and then export it to a string and send it to a client.

It is possible to turn a CSSStyleSheet into a string my manually concatenating the string representations of each rule. This is what the stringify-css-stylesheet npm package does for example. A simplified example:

function stringifyStyleSheet(stylesheet) {
  return Array.from(stylesheet.cssRules)
    .map(rule => rule.cssText || "")
    .join("\n")
}

It would be awesome if CSSStyleSheet.prototype.toString() would exist to do this natively, without requiring any helper code.

tabatkins commented 2 years ago

Yes, this seems like a lack purely due to apathy, not due to any actual problems, since authors can trivially serialize an entire sheet anyway, as demonstrated. I think this would be completely fine to add.

I couldn't find any discussions about this in the past with a quick search thru my archives, but that doesn't guarantee there wasn't any. Is anyone aware of us discussing this in the past?

timotius02 commented 2 years ago

Hello, I'm currently taking this issue after some guidance from @tabatkins (thanks a lot for your guidance btw).

I wanted to know whether the stringifier should be the toString method of CSSStyleSheet like this issue suggests or if it should be exposed as a string property just like in CSSRule. Would love some opinions on this.

tabatkins commented 2 years ago

For consistency, we probably should expose it as cssText, but we can make it better by making it a stringifier attribute.

Loirooriol commented 2 years ago

CSSStyleRule doesn't have a stringifier, so I would keep CSSStyleSheet consistent.

lucacasonato commented 2 years ago

Or we add a stringifier to both?

timotius02 commented 2 years ago

I would be open to changing both, but what are the chances that there's something out there that relies on the current stringified behavior of CSSStyleRule?

Also is there a difference spec-wise other than adding stringifier to the attribute to make something a stringifier?

tabatkins commented 2 years ago

Nope, that keyword is all you need.

(If you don't have an attribute already containing the text you have to write the definition slightly different to have an independent stringifier, but it's literally just a few words difference in invocation.)

And yeah, adding a stringifier to both would be ideal (but it increases the testing cost slightly). Relying on the current stringification seems not too likely. (In theory someone could be using it for dirty type-testing since it currently stringifies to "[Object CSSRule]", but doing that generically requires you to specifically use Object.prototype.toString.call() precisely because a lot of objects have stringifiers, so it's likely such uses are relying on that and won't be affected.)

Loirooriol commented 2 years ago

It should also be consistent with other interfaces like CSSStyleDeclaration. And given that most things don't have stringifiers, I wouldn't add them here.

timotius02 commented 2 years ago

On the other side however, it seems like MediaList interface has a stringifier, though it seems like it's the only one in cssom and I can't seem to track the reasoning behind that.

We can choose add a stringifier to CSSStyleDeclaration as well but if we do want to move in the path of least resistance, just forgoing stringifiers is best. Can anyone provide context on the state of stringifiers as a whole in w3c spec, like how common are they currently and any specs that heavily uses stringifiers as major features?

trusktr commented 9 months ago

Here's a use case for this:

In the second snippet there, the #shared-stylesheet could have a CSS OM and no textContent, so to-stringing it would be handy.

But iterating the rules is also not that difficult.

TheJaredWilcurt commented 9 months ago

Use case: Visual Regression (screenshot) testing

There is a problem with visual regression tests where they aren't cross-browser or cross-platform compatible due to subtle differences in rendering, especially fonts. So taking a screenshot of the app in a specific state, and then comparing the pixels against a previous known-good screenshot will fail just because the screenshot was done on a different OS or slightly different browser version.

If you could serialize the entire DOM and CSSOM and get hashes of each, then store those hashes next to the screenshot, you'd be able to inform a CI tool to update the snapshots when it re-runs if the hashes do not match. Allowing you to locally update the hashes when you want a new known good, without having to commit a screenshot from your machine that will only work for your machine. Then when the CI runs, it validates the screenshots, or replaces them if the hashes don't match.

This would work best if you could serialize just the part of the DOM and CSSOM that applies to a specific selector, like [data-test="myWidget"]. So the screenshot does not need to be of the entire page, but just the part related to that specific test.

mayank99 commented 1 month ago

Ran into a super weird issue today when using the cssText approach shown in the original description. Apparently it can produce invalid CSS when using custom properties (https://issues.chromium.org/issues/40804066). Obviously this bug should be fixed, but a .toString() function could help avoid the problem altogether.

Edit: Actually .toString() would have the same issue since that's how it's specced to be serialized. I think the spec should probably be fixed if we're wanting to serialize entire stylesheets?