-
-
Notifications
You must be signed in to change notification settings - Fork 407
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make scoped CSS the default in 'template-tags' #1037
base: master
Are you sure you want to change the base?
Conversation
(I wrote glimmer-scoped-css so in general I think it's a good pattern.) I think the important thing this RFC needs to clarify is what we're committing to in published addons. Addons are not supposed to publish template tag, they're supposed to turn it into valid javascript first. One way that could work is essentially what apps already do now in their own build when using the addon: // === GJS Input ===
const MyComponent = <template>
<style>
h1 { color: red }
</style>
<h1>Hello</h1>
</template>
// === JS Output ===
import { template } from '@ember/template-compiler';
/*
This would be a real file in the published addon containing:
h1[data-scopedcss-5f78d2d716-402c934e4d] { color: red }
*/
import "./scoped-css-5f78d2d716.css";
const MyComponent = template(`
<h1 data-scopedcss-5f78d2d716-402c934e4d>Hello</h1>
`); If that's how we want it to work, we're not committing to anything new in addons, because they're already allowed to import paths ending in However, one downside of that strategy is that the CSS is eager, in the sense that it goes in the DOM when you import the component, not when you render the component. A component that's imported but never rendered still has its CSS in the DOM forever. Does this matter? Personally I suspect no. But there is an alternative design like: // === GJS Input ===
const MyComponent = <template>
<style>
h1 { color: red }
</style>
<h1>Hello</h1>
</template>
// === JS Output ===
import { template } from '@ember/template-compiler';
// this utility would do a reference-counted insert/remove of the constructible stylesheet on the document
import { ensureAppended } from 'glimmer-scoped-css/runtime';
const __stylesheet0__ = new CSSStyleSheet();
__stylesheet0__.replaceSync(`
h1[data-scopedcss-5f78d2d716-402c934e4d] { color: red }
`);
const MyComponent = template(`
{{ (ensureAppended __stylessheet0__) }}
<h1 data-scopedcss-5f78d2d716-402c934e4d>Hello</h1>
`, {
scope: () => ({ __stylesheet0__ })
}
); In one sense, this RFC doesn't really have to decide between these two alternatives, because both only use features that addons are already allowed to use. But it would probably be good for us to offer solid guidance to addon authors so they aren't each picking their own solution. |
Separate topic: I think this RFC needs to actually write out what people are allowed to say in their CSS and exactly how it differs from standard CSS. For example:
This can lean on the documentation for Vue's scoped CSS feature, because that is the implementation I started from. |
|
Making scoped-CSS the default will improve the developer experience by not having to fight spill CSS that is affecting other components.
Changed the placeholder, which was 'xyz', for rfc number in the rfc file's name to the actual rfc number which is 1037.
I'm also a friend of plain CSS as it is become sooo good lately. However, what about CSS pre-processors? I could very much imagine the case, where people do use CSS modules (or similar) and want to use compose, or to use some looping behavior from a given preprocessor (SASS or PostCSS). I think the RFC should answer one of the two questions:
|
I don't think addons should publish non-native CSS. Like how we require their JS to be compiled, so should we require their CSS be compiled. If they want variables/etc, they should be native CSS variables.
Since the compile target is ultimately an import of CSS, and since how that import is interpreted must be determined by the app, and thus follow the conventions of whatever packager/app system an app is using (webpack, vite, etc). Webpack uses loaders, so you could configure all CSS to use sass or postcss: https://webpack.js.org/loaders/postcss-loader/ However, via their For Vite, they automatically apply postcss by default: https://vitejs.dev/guide/features.html#css (if detected) In anycase, if folks want to use CSS modules, there is nothing that this RFC needs to cover. Both Webpack and Vite support css-modules, and to use them, you'd I don't think it makes sense for any of these features to be implemented in co-located components. If webpack or vite are configured to handle multiple file extensions, then we could probably support that with little effort. <template>
<style lang="sass">...</sytle>
</template> becomes import './styles.sass'; once compiled. Since different tools support different features via query parameters, I think we can use the <style> tag's attributes to add values to the import's query parameters. for example, using Vite's "inline": https://vitejs.dev/guide/features.html#disabling-css-injection-into-the-page <template>
<style inline>...</sytle>
</template> becomes import './styles.css?inline'; once compiled. |
When one uses a CSS preprocessor, one will probably be practicing scalable and modular architecture for CSS (SMACSS) and the like.
This means one will have the style for each component in its own So, putting the SCSS in the component's style tags, might not be the best. At the same, what is suggested by @NullVoxPopuli; passing of the preprocessor as a language attribute of the style tag is a good one.
|
A possible amendment that came out of real usage issues at Cardstack: Because it's easy to unintentionally apply your transforms to third-party code, it's not great that the current implementation repurposes the meaning of |
The above comment -- making scoping opt-in instead of opt-out -- is now implemented in glimmer-scoped-css 0.6.0. |
I haven't followed all the discussion of scoped css, so forgive me if this has been discussed already: CSS
|
I think the proposed CSS glimmer-scoped-css doesn't let styles leak down into components that you |
|
@kategengler scoped CSS, in this case, it is scoping to component rather than element, which should be the default. If there is an agreement, that a component is a self-contained UI unit with its style. |
@ef4, What are the technical constraints around this? The idea of requiring an attribute to make socped implies that we want the default to be global and that's what we want folks to be doing in their components (by default) Is it possible to default to scoped (requiring |
Before 0.6.0 Regardless, with the trends over the years toward explicitness vs “magic”, it also felt fitting to require |
Is it not possible to fix whatever situation makes app-transforms apply to addons?
It just means we think people should be writing global CSS by default -- which I think I might strongly disagree with. :-\ At least, I want the pit of Success to be scoped, because that's what folks really want. Global CSS, afaict, is the exception |
I strongly disagree with making the plain The |
I agree with @kategengler about not taking over normal browser behavior. I think a lint rule would be sufficient to nudge everyone in the right direction while preserving default browser behavior. Regarding the concern of using |
Are ya'll using strict mode/gjs/gts when you put |
This is a sample import { concat } from '@ember/helper';
import tippyTip from '../modifiers/tippyTip';
const tooltip = (transactionType) => {
if (transactionType === 'fob') transactionType = 'FOB';
return `Select the currency in which the ${transactionType} is denominated`;
};
<template>
<div class="brand-text" >
<label for={{( concat @transactionType "Currency") }}></label>
<select
id={{( concat @transactionType "Currency") }}
name={{( concat @transactionType "Currency") }}
class="currency-selected" autofocus="" required=""
{{tippyTip 'mouseenter' 'top' (tooltip @transactionType)}}>
<option value=''>CUR</option>
{{#each @currencies as |currency|}}
<option id={{currency}} name={{currency}} value={{currency}}>{{currency}}</option>
{{/each}}
</select>
</div>
<style>
.currency-selected {
padding-left: 0.1rem;
font-size: 0.7rem;
min-height: 2.0rem;
}
</style>
</template> |
I believe that styles in the component should not pollute the global CSS |
aye, same |
I'd like to block on on using data attributes for classes instead of classes. <3 styles aren't data, and the closer we stick the the intended semantics the better. |
Nobody disagrees that polluting global CSS from a component is bad style. Just like manipulating javascript global variables from a component is bad style. But it would be very bad if we broke the possibility of manipulating javascript global variables from a component, because we'd be breaking Javascript. And I would argue this is the same kind of problem, and making it impossible to manipulate global styles from a component would be breaking HTML.
|
Before making scoped CSS the default, I think there needs to be a first-class support from Currently, it's up to each codemod author to come up with their own implementation. For example, I think we may head towards a path where migrations become costly, if we don't first solve how to easily write programs that can modify template(s) and class(es) in a single file, before we add styles to the equation. |
I think this has to do with implementation, but RFC is about having a picture of how things should be; a desired state. Policy (RFC) is a picture that guides implementation. So, for the emberjs component to meet the definition of a component—a unit of user interface with its style and behavior—scoped CSS is needed. Some of the goodness of scoped CSS:
|
I was thinking more about the question of whether scoped should be the default, and I realized we haven't discussed an important aspect of the problem: if we went with scoped-by-default it's a breaking change and would require a fairly extensive rollout plan across the ecosystem. Lots of ember templates already exist and contain Also, we would need an explanation for what's supposed to happen, for example, when people are using the runtime template compiler. I think |
Another topic we need this RFC to cover: what happens with bound values inside the style tag. Currently |
that's another case for requiring a |
Alternative possibility: capitalized |
I'd like to mention another possibility so that we can consider all options and arrive at a good solution. Since the purpose mentioned in this RFC was to avoid "spill[ing] CSS [...] affecting other components," we could consider CSS modules as a default for The benefits are:
The downsides are:
|
Another syntax possibility would be to separate the A downside of this is that it might be surprising that Variants on this idea could use a different name for this new top-level tag, such as |
as a syntax / grammar author of multiple implementations, I must say "nay!" 🙈 It also implies that all |
I can respect this.
|
I understand the hesitancy to have a top-level |
top level |
Actually, from a DX perspective, I would naturally think that nested would be scoped and top-level not. 🤷♂️ Either way, sounds like this is shot down anyway?? And is this what we're talking about? <template>
<h1>Scoped CSS</h1>
<style>
h1 { font-family: Roboto};
</style>
</template> Versus <style>
h1 { font-family: Roboto};
</style>
<template>
<h1>Global CSS</h1>
</template> If so, what happens in this case? import Component from '@glimmer/component';
export default class MyComponent extends Component {
<style>
h1 { font-family: Roboto};
</style>
<template>
<h1>Unknown CSS</h1>
</template>
} FWIW, I could see that either way, but might assume scoped since it's within the Component definition. Whereas this would be global: import Component from '@glimmer/component';
<style>
h1 { font-family: Roboto};
</style>
export default class MyComponent extends Component {
<template>
<h1>Global CSS</h1>
</template>
} Styles anywhere! 🥳🤮 |
In the last RFC meeting we had discussed I think we'd need to lint against bound values inside, as well. |
Personally, I would hope that if ember standardized an in-file authoring syntax for styles that it would get first class treatment: class extends Component {
<style></style>
<template></template>
} This of course leads to the oddball situation where template-only components don't have a natural way to have styles, which the in-tag solution does account for: export default <template></template> However, I think a number of us (myself included) don't love the template-only shorthand and would prefer to always wrap template in a component and have the class compile away if it wasn't needed, which also immediately solves for this. |
One of the reasons glimmer-scoped-css went with style *inside* template is
that you get syntax support for free since all html tooling knows what to
do with css in that position.
Style outside template may be more work, similarly to what we had to do to
get syntax highlighting for gjs. Or maybe it looks enough like JSX that
we’ll find environments support it.
…On Sun, Nov 10, 2024 at 1:05 AM Chris Thoburn ***@***.***> wrote:
Personally, I would hope that if ember standardized an in-file authoring
syntax for styles that it would get first class treatment:
class extends Component {
<style></style>
<template></template>}
This of course leads to the oddball situation where template-only
components don't have a natural way to have styles, which the in-tag
solution does account for:
export default <template></template>
However, I think a number of us (myself included) don't love the
template-only shorthand and would prefer to always wrap template in a
component and have the class compile away if it wasn't needed, which also
immediately solves for this.
—
Reply to this email directly, view it on GitHub
<#1037 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AACN6MQ5T6VCL2RE4JRCRILZ73ZRFAVCNFSM6AAAAABKSQ7F7WVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDINRWGYYDEMZRHA>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
a bit off topic but what's the expected behaviour of the following? import Component from '@glimmer/component';
import my_css from './styles.css';
export default class MyComponent extends Component {
<template>
<link href={{my_css}} rel="stylesheet" />
<h1>Some CSS</h1>
</template>
} |
That line is a static error. Because we standardized on css imports meaning side-effectful inclusion of the stylesheet. So there's no default export to import. Buf if you obtained a URL to a valid stylesheet some other way, you can make a normal |
sidestepping the fact I can customize the loader rule of the build tool to give me the url, I can also make it interpret that with the raw loader to give me the content of the file which I could then just make everyones nightmare come true and do import Component from '@glimmer/component';
import my_css from './styles.css';
export default class MyComponent extends Component {
<template>
<style>
{{my_css}}
</style>
<h1>Some CSS</h1>
</template>
} my real question is how do I hook the content inside the <style> tag (that doesn't do any of the weirdness with import statements) into a pre/post processor before it is made global or local to the component whichever the rfc lands on |
I'm feeling a little naive here but what is the outcome of these scoped ideas? Does it wrap the style in a unique selector and add it to the top level CSS? I don't know if this counts as there seems to be a lot of back bending to get this idea across but for me I manually scope my CSS: <template>
<style>
.my-component-selector {
...Any selectors nested under...
}
</style>
<div class="my-component-selector">
...
</div>
</template> Using specific selectors like |
Making scoped-CSS the default will improve the developer experience by not having to fight spill CSS that is affecting other components.
Make Scoped CSS the default in 'template-tags
Rendered
Summary
Based on my observation while using
template-tags
to author components, the style tag insidetemplate-tags
is global, which I think should not be because it may affect other components. Also, libraries or imported components might have used the same style name, so the issue will become nested and, therefore won't be easy to trace. Even if one works around this using a unique class wrapper for each component, the possibility of collision is still present, and uniqueness is not guaranteed project-wide.This pull request is proposing a new RFC.
To succeed, it will need to pass into the Exploring Stage), followed by the Accepted Stage.
A Proposed or Exploring RFC may also move to the Closed Stage if it is withdrawn by the author or if it is rejected by the Ember team. This requires an "FCP to Close" period.
An FCP is required before merging this PR to advance to Accepted.
Upon merging this PR, automation will open a draft PR for this RFC to move to the Ready for Released Stage.
Exploring Stage Description
This stage is entered when the Ember team believes the concept described in the RFC should be pursued, but the RFC may still need some more work, discussion, answers to open questions, and/or a champion before it can move to the next stage.
An RFC is moved into Exploring with consensus of the relevant teams. The relevant team expects to spend time helping to refine the proposal. The RFC remains a PR and will have an
Exploring
label applied.An Exploring RFC that is successfully completed can move to Accepted with an FCP is required as in the existing process. It may also be moved to Closed with an FCP.
Accepted Stage Description
To move into the "accepted stage" the RFC must have complete prose and have successfully passed through an "FCP to Accept" period in which the community has weighed in and consensus has been achieved on the direction. The relevant teams believe that the proposal is well-specified and ready for implementation. The RFC has a champion within one of the relevant teams.
If there are unanswered questions, we have outlined them and expect that they will be answered before Ready for Release.
When the RFC is accepted, the PR will be merged, and automation will open a new PR to move the RFC to the Ready for Release stage. That PR should be used to track implementation progress and gain consensus to move to the next stage.
Checklist to move to Exploring
S-Proposed
is removed from the PR and the labelS-Exploring
is added.Checklist to move to Accepted
Final Comment Period
label has been added to start the FCP