Skip to content

Instantly share code, notes, and snippets.

@ef4
Last active February 27, 2023 15:21
Show Gist options
  • Save ef4/d5347562b433b251f32b004315fd3c70 to your computer and use it in GitHub Desktop.
Save ef4/d5347562b433b251f32b004315fd3c70 to your computer and use it in GitHub Desktop.

Feedback on scoped CSS design

  1. I would expect this to be cleaner to implement in a way that works in both classic and embroider if we first do embroider-build/ember-auto-import#565 and I'm curious how you avoid needing that. I'm saying this because I'm hoping your implementation ultimately emits imports for CSS, ultimately feeding the CSS into whatever prevailing CSS pipeline exists without making assumptions about that pipeline.

  2. Does your implementation allow the CSS to split alongside the Javascript, if people use either import() or Embroider's splitAtRoutes? (This is another way to ask the same question as above, because if the styles are getting emitted within the module graph it will Just Work and if they're not, it's very much not OK.)

  3. I would strongly suggest against using classes to do the scoping. Adding dedicated data attributes is better because you can leave the user's classes entirely alone. Instead of changing the selector .foo to .foo-123, you can change it to .foo[data-scopedcss=123]. h1 becomes h1[data-scopedcss=456]. .main > span becomes .main > span[data-scopedcss=789]. This is how Vue does CSS scoping and it works out great. It also results in a very simple AST transform on the template side -- you can add the data-scopedcss attribute to every HTML element in a given template, and that keeps your scoping perfectly aligned with the boundaries of the template, even when people use arbitrarily complicated selectors.

    (Globally namespaced CSS features like keyframes do still need mangling and I agree with that part.)

  4. If you instead put <style></style> inside the <template></template> tags in GJS, you get

    • out-of-the-box correct syntax highlighting
    • scoping per-component instead of per-file. Scoping per-file should be a total non-starter given the way Ember is trying to stop forcing components and files to be one-to-one.
    • a single interface that works for both GJS and traditional HBS files (you can put <style> in those too, and thus should not even bother implementing standalone co-located CSS files).
    • An easy place in the AST to grab the unprocessed CSS, with no extra parsing step to find it.
@ef4
Copy link
Author

ef4 commented Feb 27, 2023

I think my main problem with this is that it clashes with how the web works. If I want a <style> tag to actually exist now I can't do it without us introducing some mechanism to have the processor ignore it.

We already differ from the spec. For example, the spec says <Div> is a regular element but Glimmer says it's a component invocation because it's capitalized. You could say the same thing here with <Style> instead of <style> and you haven't differed from the spec in any way that's new from what glimmer already does. Personally I think that's unnecessarily subtle and <style scoped> vs <style> would be clearer to readers. (Especially since the capitalization rule will eventually be less important to learn as people adopt strict mode templates.)

I think you're underestimating how big a pain it will be to make <style>-in-Javascript play nicely with glint, eslint, typescript, prettier. It already syntax highlights correctly if you put it inside <template> instead (as long as you've already setup GJS support). As for:

parents needing to pass classes down.

This is only a problem when you're mangling the user's classes. If you leave them alone, users can pass them down normally. If they want to associate styles with those classes they can either (1) use a global stylesheet like app/styles/app.css instead of a scoped one inside a component or (2) we can explicitly support both <style> and <style scoped>.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment