-
-
Notifications
You must be signed in to change notification settings - Fork 284
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
proposal: deprecate CSS components in favour of 3rd party CSS pre-processors #740
Comments
For me personally, this is something I'd really like to avoid. The main reason I picked Templ over Svelte for my new app was to avoid the JS ecosystem. I'd prefer a built-in solution, even if it had less features, but of course I understand the constraints on developer time and know you have a lot of competing priorities. Templ is awesome overall, thanks for sharing your thoughts with us in real-time! |
I agree with the proposal, as I don't think CSS should be a scope for templ at all. I would also like to opt out of the JS ecosystem (@jimafisk ) but I think it's almost impossible right now. We migrated a medium sized go+react project completely to templ. As we swapped away from React components, we also needed interactivity. We looked into web-components, especially things like stencil or lit but this would ultimately bring back JS dependencies and build processes back into the project. We ended up using HTMX + Alpine.js which is an awesome combination that really drives the idea of locality of behavior and HATEOAS, but it definitely is not a silver-bullet and I'd be open to better solutions. |
I started building with templ precisely because I don't want to setup JS tooling. Integrating with them is fine as long as it's optional. |
im not advertising what ive done just a mere food for thought. Im using temple and very quickly found I needed more than the css provided so started our own way of generating css from go. very early days and still working out the best way to use it and what we need from it. |
I love what you're doing there @stuartaccent. Couple of ideas (not sure if you've already of thought of this)... If you wanted to, you could update the func (s *Style) Render(ctx context.Context, w io.Writer) error {
//TODO: Get a CSP nonce from the context.
//TODO: Check errors etc.
io.WriteString(w, "<style type=\"text/css\">\n")
s.CSS(w)
io.WriteString(w, "\n</style>")
}
func (s *Style) CSS(w io.Writer) error {
// Existing
} You could also create a type Stylesheet []Style
func (ss Stylesheet) Render(ctx context.Context, w io.Writer) error {
//TODO: Get a CSP nonce from the context.
//TODO: Check errors etc.
io.WriteString(w, "<style type=\"text/css\">\n")
for _, s := range ss {
s.CSS(w)
}
io.WriteString(w, "\n</style>")
} Doing this would mean that So, defining a set of styles as a
Then, using those CSS classes as components in templ: package components
templ Button(name string) {
@deps.ButtonCSS
<button class="button">{name}</button>
}
templ ButtonPrimary(name string) {
@deps.ButtonCSS
<button class="button-primary">{name}</button>
} If you then use But, in this case the CSS would only be loaded into the page as a Obviously, I'm not aware of Either way, it looks very great. |
thanks, @a-h that looks like a solid idea i will take a look. cheers stu. |
As a Gopress creator, all I can say is - CSS in template engines is a huge topic. It is important to answer the questions: what architecture are we creating for, where and how will we embed styles, what do we want to share/reuse or what should be global, how will we introduce changes (e.g. after changing from the UI designer in Figma). Some challenges:
Gopress has a very unique approach in that we have sets of Tailwind classes that are transferred one-to-one from Golang to, for example, Vue, because we use an atomic design approach. These sets create variants based on attributes. Instead of classes you can use just pure css (it will be worse DX, but the effect can be more or less the same). DX is very important - you can see immediately what styles the class gives (syntax suggestions from Tailwind). The component is isolated and the only dependency is the Tailwind configuration file. Because Tailwind is also not perfect i'm working on alternative solution (without Tailwind) and one more thing - by design, Gopress was built in such a way that components could be generated and changed from an application designed for this purpose. This is related, on the one hand, to AI and, on the other hand, to the problem of technology adoption. Gcss is interesting because it has no dependency and in some cases it can be good solution, definitely worth attention. |
This is very much needed. I am trying to use Templ for HTML Emails, and am finding that this undocumented restriction on I think that not baking this in from the start - or at the absolute minimum, documenting it - was a mistake. I know that the design philosophy behind Templ is to be helpful, sometimes erring on the side of being "hand holdy", but these sorts of seemingly arbitrary restrictions can lead to a lot of disillusionment from developers just trying to ship product. Oh, and one last thing I'll mention is I agree with @jimafisk that I too do everything I can to avoid having a For contextThis is all I want to be able to do in an HTML Email. I want to keep my color scheme and theme information the same so I want to use some templ emailLayout() {
<!-- Don't worry about the rest -->
<body style={ "margin:0; padding:0; background-color:"+common.BackgroundColor+";" }>
<!-- Don't worry about the rest -->
} One very last thing...I have a similar issue with the |
I think keeping a minimal in-house solution and allowing the user to plug in something else if they want is a good approach. |
@a-h I think we are circling a decision here. It may be worth us splitting this into a few different features...
|
In my opinion, using build system should be optional but even when we use templ, we have a build step that generates go files. So what we can do is getting the best solution (performance and compability) into the build system in order to achieve faster builds and better developer experience. My suggestion would be using lightningcss[repo]. |
Just started playing around with Templ today. Really great project! This was one of the first questions I bumped up against, "how do I style more than one element at a time?" I'd really like Templ to provide a native solution, similar to how Next.js supports styled-jsx as an out-of-the-box CSS solution, but you can supplement with Tailwind, etc. I'm still new to Templ so I'm not quite sure about solutions yet, but I maintain https://github.com/matthewmueller/css and https://github.com/matthewmueller/styledjsx, which is basically styled-jsx but in pure Go. Something like this coupled with ESBuild's CSS support for browserlist, I do feel like we have all the pieces built out in the Go ecosystem. |
Interesting! @matthewmueller - we should probably get together and chat about it, if you're interested in chasing down an implementation. I have an example of using tdewolff's parser to add in support for interpolated variables, see https://github.com/a-h/templ-css/blob/7cb95993e63562ff6aa2c84f69feb1a9ff51dd68/parse_test.go#L29-L40 Perhaps there's a way to use your css parser and do something similar? The steps would be something like:
|
Definitely down to chat more about the design! But here's some initial thoughts:
I just tested this in this commit. So Would you be open to different syntax? We could also do something custom for Templ if
This is possible today. After you manipulate the AST, you can print it back out via
This should also be possible, you could even translate from a CSS AST to a Go AST, then print out the Go AST. Stepping back a sec, one of the big challenges in the component world is making sure styles from the parents don't leak into the child components. Unfortunately nested CSS doesn't help with this: .parent {
a {
background: red;
}
} <div class="parent">
@Child()
</div> <div class="child">
<a>some link</a> <!-- oh noz, I'm red -->
</div> Personally, I'd love for Templ to take care of this issue for us. Spitballing on solutions, I think what I'd like to see is a remix on: handler := NewCSSMiddleware(httpRoutes, c1) But instead of middleware taking classes one-by-one, it's a handler that takes a filesystem, something like: //go:embed view/**/*.templ
var fsys embed.FS
router.Get("/templ.css", templ.Stylesheet(fsys)) Then upon request, it traverses the You'd need to transform the Before transform templ Story(story *hackernews.Story) {
<div class="story">
<div>
<a>
{ story.Title }
</a>
if story.URL != "" {
<a class="url" href={ templ.URL(story.URL) }>({ formatURL(story.URL) })</a>
}
</div>
<div class="meta">
{ strconv.Itoa(story.Points) } points by { story.Author } • { " " }
@Time(story.CreatedAt)
</div>
<style scoped>
.story {
background: red;
}
.url {
text-transform: none;
}
.meta {
padding: 10px;
}
</style>
</div>
} After transform templ Story(story *hackernews.Story) {
<div class="css-123abc story">
<div class="css-123abc">
<a class="css-123abc">
{ story.Title }
</a>
if story.URL != "" {
<a class="css-123abc url" href={ templ.URL(story.URL) }>({ formatURL(story.URL) })</a>
}
</div>
<div class="css-123abc meta">
{ strconv.Itoa(story.Points) } points by { story.Author } • { " " }
@Time(story.CreatedAt)
</div>
</div>
} Then the served CSS ends up being something like: .css-123abc.story {
background: red;
}
.css-123abc.url {
text-transform: none;
}
.css-123abc.meta {
padding: 10px;
} Sorry, this ended up being longer than I expected. Hope this helps! Also happy to sync on this. Thanks for entertaining the idea of having batteries-included CSS in Templ! Update So for fun I stuck the component above into https://github.com/matthewmueller/styledjsx and it mostly works! Here's the draft PR: matthewmueller/styledjsx#1. If there's interest, I'll look into making |
This is very interesting, and your demo looks great! I'm keen to have a scoped CSS capability in the templ ecosystem to meet exactly the challenge you described - the ability to ship components that are not affected by inheriting scope. templ has a parser in it, you can parse a templ file into nodes using the templ/parser/v2/templatefile.go Line 13 in 3ac3c9d
templ/parser/v2/diagnostics.go Lines 15 to 33 in 3ac3c9d
This capability doesn't have to be within templ itself, but great if it is. In terms of syntax, I'm flexible on what that could / should look like. What I'm hearing from templ users is that they want to be able to:
I'm really keen that templ should nudge users towards security - i.e. make it harder to do the wrong thing, e.g. templ's existing CSS components protect against unsafe CSS property values. I'm also keen that it's something we can maintain with a small number of people over a long period of time. I'd like to be conservative in features - not box ourselves in, but give ourselves scope to add features later. Are you on the Gopher's Slack? If so, should we schedule a Slack call and kick about some ideas? Ideally, we'd end up with a proposal doc that outlines the target features, the expected syntax, and the expected output Go code. |
Please, remember about few things:
|
Would you be up for joining a conversation about this topic @Riu? Your real-time input would be very valued. |
@a-h Awesome!
Great to hear this is coming from user feedback! At the risk of going against this feedback, I'm on the fence about interpolation within CSS for two reasons:
So my personal opinion is that this is a reasonable place to be conservative on features. At the very least for the initial release.
Cool, I was just learning more about Templ these last couple days. I'll look into contributing next!
Big fan of this approach!
Yep! Just dropped a message and will drop follow-up questions when I start the implementation. Also down to sync. I'm free most evenings (US timezone) and weekends. @Riu thanks for the call outs. +1 it would be good to learn more about these. Let me try and answer a few:
I'm really curious about this. Since the scoping applies to the HTML, I'm unsure why you'd want to support scoped block outside of any HTML tag.
This is mostly true. Since you're hashing the CSS contents, you can dedupe <style> tags that have the same contents, but not the declarations, so you will end up with e.g.
Can you elaborate?
Reset files would just be additional And yah, we'd probably want support for the global pseudo-class, e.g.
I think I responded to this when talking about dynamic styles. Let me know if you mean something different! |
@a-h I'm available from next week, so let's talk.
|
Is this true @Riu ? If the resulting name was a hash of the css contents the likelihood of a clash could be reduced, and if there was a clash the likely reason is that the classes have the same contents. Maybe this is a naive view. |
In this example: #740 (comment) hash is |
Hey @a-hm, @matthewmueller Did this discussion ever go anywhere? Is a feature planned? I really dislike what using Tailwind does to the HTML... classes littered everywhere. Would love to implement my current project using some JSX / Svelte style components. |
I really like how there is no real build step outside of templ. I'd hate it if an entire extra build process with something like postcss and esbuild became the recommended practice. What I need that's not really covered by templ is for example to be able to write a metaprogram of sorts to detect if my combination of css classes is valid, or check if my html element name conforms to accessibility norms. A plugin system could be made that would allow hooking into templ's behavior like described to allow for that extra layer of extensibility. If people want, they can call the however many css preprocessors the cool kids have nowadays from there. Such adapters will be written once for most of the popular tooling and then everyone can use it. Then everyone will be happy forever. I agree css and script blocks should be gone. It's scope creep in my eyes. That's how I would do this. |
Hey @zimdo, thanks for the prompt! I stepped away from working on UI stuff for a bit, but still planning on coming back to this! If anyone feels like picking it up in the meantime feel free! @a-h, one thing that could be nice is a way to add plugins to Templ. This implementation could then start outside of core initially and be one of many experiments. There might already be a way to do this (if there is, please let me know!) but after briefly reading through the code and docs, I didn't see anything. Two ideas along this vein of preprocessors: 1. Allow users to programmatically generate Templ So you could basically create your own package main
func main() {
t := templ.New(dir)
t.Use(scopedcss.New())
err := t.Generate(ctx, "**/*.tmpl")
} 2. Spawn plugins from the CLI templ generate --plugin=./scopedcss ./... where |
templ css
CSS components are very basic. They don't support a range of use cases #262 including media queries, and the interpolation features are minimal. I have built a PoC of interpolating CSS values within a standard<style>
element at https://github.com/a-h/templ-css but before jumping to implementation, I took a step back.People are already using pre-processors
Many users of templ are using Tailwind, or other CSS pre-processors already. They are essentially templating libraries for CSS, but can also do thing like include browser specific properties.
Pre-processors are popular and mature
The PostCSS NPM package gets around 69.3 million downloads per week, while SASS gets around 13.5 million per week, and tailwind gets 8M. They're well established projects, with large user bases.
templ css features are covered by existing pre-processors
templ css
was designed so that it was possible to include classes for specific templ components, and only render the CSS if required. It dynamically generates class names to provide basic scoping. But... its design has severe limits.Given the range of available pre-processors, their popularity, and the limited resources of the templ project, I think it might be better to spend time on improving the DX around using existing pre-processors in templ, rather than to attempt to create one, alongside everything else.
PostCSS has a plugin for scoping at https://github.com/madyankin/postcss-modules - which allows the scoping behaviour to be achieved easily, and there's a plugin for PostCSS which outputs a list of classes - https://www.npmjs.com/package/postcss-ts-classnames - in that particular case, in TypeScript format.
We can get compile-time errors about missing CSS classes
If, for example, we created a version of
postcss-ts-classnames
PostCSS plugin which created a Go package calledclass
containing a load of constants, we'd then be able to import the package into our templates, and get a strongly defined list of classes. When you write<div class={ class.
in templ, the Go LSP would provide a list of classes in the IDE autocomplete.This would give us the benefits of a strongly typed list of classes, for minimal development effort.
This proposal fits well with #739
For JavaScript handling, I've proposed an alternative focus on to using JSON
script
elements, alongsideesbuild
to bundle up scripts. It solves the problems of transpiling TypeScript so you can use async/await, sorts out minification etc. you can see what that looks like at #739 - esbuild is written in Go, and is very fast.templ.OncePerRequest
To ensure that links to stylesheets, or
<style>
content itself is only rendered once, a newtempl.OncePerRequest
component could ensure that children of the component are only rendered once per request, using thectx
parameter.Build process
I think the way forward on CSS is similar - with the result that you run
templ generate
to get your HTML templates,esbuild
to covert your modern JS/TS to browser JS, andpostcss
(or whatever CSS pre-processor you want to use) to get output CSS, and get the Go web server to host the outputs atdist
/public
.Automatic (opt-in) migration for users of
templ css
andtempl script
componentsThere could be an automated migration path away from
templ css
andtempl script
. Atempl migrate
command could bring all the CSS together and outputscss
file(s) instead, while the scripts would be converted into JS functions in a*.js
file.Consideration of project setup
These tools require a more complex setup in that you have to have
node
installed, and will need an esbuild configmjs
file, apackage.json
and apostcss
config, but I think that can be solved by using a tool like cookiecutter (but not cookiecutter) to create some initial app templates, e.g. basic templ hello world, templ, HTMX with typescript and postcss, or templ, HTMX with JS, tailwind etc.style
attributeCurrently, the style attribute can't contain templ expressions. I think this could be relaxed, and any CSS within the style attribute could be pushed through a CSS sanitization process. This would allow the use of
style={ fmt.Sprintf("background-image: url(%v)", url) }
or the use of a dynamic builder (not implemented, just an example)style={ css.BackgroundImage(url).PaddingPx(10, 10, 10, 10) }
.Summary
I think this would be a smart use of time, and would allow us to spend more time on the LSP and DX areas of templ.
Thoughts?
The text was updated successfully, but these errors were encountered: