Closed jordwalke closed 2 years ago
This feature could also be used at development time to get auto-completion of all possible Tailwind / Twind CSS classe names directly in the web browser / web inspector.
Now, I must say that I wouldn't personally use this development workflow, because I use a build system with near-instant refresh (Preact WMR). But in cases where the DX is slower, the ability to try out styles directly in the web inspector (i.e. without any compile step whatsoever) may be desirable. Caveat: once the classes are entered in the live DOM, they have to somehow be copy/pasted back into the source JSX/whatever ... that's one more reason why I wouldn't personally bother relying on this workflow, but perhaps it would help some developers out there :)
My workflow doesn't use a build, watcher, or server so that's why it's useful to me (I hope to others as well).
Caveat: once the classes are entered in the live DOM, they have to somehow be copy/pasted back into the source JSX/whatever ...
I'm not sure what you meant, and it might be that I miscommunicated. My goal is to just get a single css stylesheet for all the tailwind classes (including dark mode), along with my customizations to the theme. Once I obtain that, I would inject it into the doc as a <style>
tag, and then manually reference those classes as in <div class="bg-blue-100">
etc. Did I communicate that part effectively?
The shim seems to generate that css file - but only for the classnames that are currently referenced in the current DOM. I want to generate it "offline" without knowing what will end up in the DOM, and then use twind without the twind runtime in production, which also gives me the ability to generate multiple themes ahead of time and switch between them by replacing the <style>
tag. I assume twind has the set of all valid classes somewhere since it is able to warn/error when referencing an unknown class. It stands to reason it could simply generate all of them based on that information into a style sheet.
My goal is to just get a single css stylesheet for all the tailwind classes...
Sorry, my bad for not being clear: I meant that being able to generate a complete stylesheet of all possible outcomes based on a given Twind configuration (i.e. preflight, plugins, theme etc.) would open another useful development-time feature.
and then use twind without the twind runtime in production,
That's also my goal in a side project / experiment with Preact WMR static SSR + hydration (see my discussion thread in this repo, it is actually quite a complex issue, depending on the dev/prod framework).
However I would never want to ship a full unpruned stylesheet in my webapp, as this would negate all the byte savings I am aiming for everywhere else :) And introducing a pruning stage would basically be like working with TailwindCSS (before they introduced their Just In Time compiler), so not really in line with Twind's design principles.
In your use case, do you really intend to ship all TailwindCSS classes in your final production build, or just at development time? (sorry if I misunderstand your goals)
@danielweck I would probably ship all of the css classes, and only later prune them if it became a bottleneck. With dynamically rendered apps it's very hard to determine which classes will certainly not be rendered. From the root of the app all classes are theoretically "reachable". There are framework level solutions which probably do a good job, but I'm creating a new framework so existing out of the box ones won't work for me.
Is there a way to do what I want with tailwind entirely in the browser? I was interested in twind because it appeared to offer a browser-only solution where themes can also be built in browser, and then also some of the "escape hatches" twind has to use JS when necessary (if I ever need it).
just as a point of reference relating to the dev time feature i was describing, see:
https://github.com/windicss/vite-plugin-windicss#design-in-devtools and https://twitter.com/antfu7/status/1372368366179028996?s=21
(Twind already provides the mutation observer aka shim, all that’s needed is the “full” stylesheet so that class names can be auto completed directly from the web inspector)
Is there a way to do what I want with tailwind entirely in the browser?
Have you looked into Tailwind CSS Just In Time compiler? Sounds like this would hit the sweet spot for you. Although I’m not sure about escape hatches allowing for custom styling (Twind provides a pretty flexible APi, which I value in equal measure to Tailwind’s opinionated design system)
What I am struggling to understand fully is the reason why you are seeking to eliminate Twind runtime whilst you seem ok with including a large stylesheet. Is this motivated by <noscript>
?
I am starting to think/realise that I am going off-tangent with my inquisitive comments about Twind runtime :) If I understand correctly, you just want to collect all the styles in your utility app, but performance isn’t your primary concern?
If you do not know which classes will be used the twind runtime and maybe observe/shim are the right way to go.
That leaves us with the implicit global state. Could you render the themed parts of your site within a web component? That would prevent leaking of global styles. Each component would have its own twind instance and stylesheet.
The example in the docs uses tw but twind/observe should work just as well. It would create styles for all elements with that component.
https://twind.dev/docs/handbook/recipes/use-with/web-components.html
@danielweck Please do not forget about @twind/cli. Our tool to generate a CSS file - works just like the new tailwindcss jit, but without postcss
@danielweck Please do not forget about @twind/cli. Our tool to generate a CSS file - works just like the new tailwindcss jit, but without postcss
On that point:
https://github.com/tw-in-js/twind-cli/issues/1
;)
Please do not forget about @twind/cli. Our tool to generate a CSS file
Sure, but @jordwalke stated:
The problem is that the generated stylesheet will only render classes that are actually used in the document. Is there a way to tell the setup() to render all classes even if they're not used yet?
That leaves us with the implicit global state. Could you render the themed parts of your site within a web component? That would prevent leaking of global styles. Each component would have its own twind instance and stylesheet.
That is pretty much what I am doing with an iframe. I'm just wondering why there is even global state in twind.
I'll look into the cli, but it's idea to do this in the browser. (And there doesn't appear to be a way to have the cli generate all class names for markup that is not yet generated) The reason why I am okay with having a large css file is that I can generate the entire thing at dev time with no build step or dev server (designer friendly), and then later trim out dead css after rendering the page.
I'm just wondering why there is even global state in twind.
There can be several twind instances (tw
) but we expose one as the default via import {tw} from 'twind'
. This one can be configured via the setup
export. This way you can write use components from different packages without worrying about passing the right instance around. Component libraries use the default tw
import and apps use setup
for customization.
The instance is responsible for generating the styles and keep them in the right order within its style sheet. It not simply appending styles that would not work with most utilities. Because the order within the stylesheet is important.
Could please help me understand your problem better:
Okay, I think that makes sense. I tried instances but it didn't work and I thought somehow instances were "broken" when customizing the theme but now I think that's not the case - it's just that I didn't have any DOM nodes in the page that used the classes for twind to discover which is why it wasn't generating the necessary classes.
I tried minifying the entire tailwind generated css file, and after compressing with gzip it's only 300kb. That doesn't seem extreme for many use cases. In some cases, the simplicity of not having to run a build step, watcher, server would be worth it imho.
Then imagine adding a couple of config values in setup()
where you can strip out certain feature you are pretty sure you won't need (either a blacklist or whitelist approach). Wouldn't be surprised if most people could quickly get the file size down to about 150kb without any other worry about dev servers/watchers or build steps. Perhaps not suitable for the fastest site in the world, but universally compatible with any framework, existing or to-be-invented, and very low friction.
For comparison one full res image (when you click on it and open it) on Twitter desktop website is about 600kb compressed. 200kb cached isn't the end of the world for most websites, and there's a ton that can be done after the fact to trim out unneeded css when someone wants to optimize. How many projects even get past the prototyping/iteration stage (maybe 20-30%?)
Edit: I realized that my tailwind generated css with all of the classes didn't include dark mode variants. Maybe that invalidates my analysis above? (I'm not sure how much that adds to the file size, but I imagine the additional size compresses even better than the initial kb)
Could please help me understand your problem better:
Do you know which classes should be generated?
No, I do not know at the point of generating the css.
Should all possible classes including variants permutations be generated?
I would say yes - at least for this workflow that accommodates the lowest friction use case. I'm not advocating dropping the size optimization features of twind (those are awesome make-or-break features important for many production cases). I'm just suggesting a very low friction workflow for starting projects. I was surprised at how much friction tailwind css required to setup. I think it's an opportunity for twind to offer an easier on-ramp, while being more flexible, and without sacrificing the more advanced performance sensitive use cases.
Then my question again: Why not use the shim or observe with a custom stylesheet? That would allow generating all styles needed.
Why do you need more styles than being used on the page? I'm struggling to understand what you are trying to achieve.
Why not use the shim or observe with a custom stylesheet? That would allow generating all styles needed.
The shim would generate all styles even if they are not present on the page at the time of generation? I need to capture the generated css and store it in a variable, then eliminate the twind runtime. The reason is that I need to do this multiple times for multiple different themes. I tried using the shim, and it only generated style rules for elements that were currently on the page. Sure, if I later add the class names to the dom it will update the stylesheet, but that's not what my use case is. I also need to customize the setup in a way that can't be configured with an inline <script type=twind-config>
because I programmatically generate the color pallet.
Why do you need more styles than being used on the page? I'm struggling to understand what you are trying to achieve.
Because I want to eliminate the twind runtime, and I can't anticipate which classes will be necessary ahead of time.
Why not use the shim or observe with a custom stylesheet? That would allow generating all styles needed.
The shim would generate all styles even if they are not present on the page at the time of generation?
No. Only those that are used.
I need to capture the generated css and store it in a variable. The reason is that I need to do this multiple times for multiple different themes. I tried using the shim, and it only generated style rules for elements that were currently on the page. I also need to customize the setup in a way that can't be configured with an inline
<script type=twind-config>
because I programmatically generate the color pallet.
import { create } from 'twind'
import { virtualSheet, getStyleTag, getStyleTagProperties } from 'twind/sheets'
const sheet = virtualSheet()
const { tw } = create({
sheet,
theme: { /* .. */ }
})
// generate all styles here - see next section
tw(allTheStylesArray)
// retrieve the style
getStyleTag(sheet)
// => '<style>...</style>'
const { textContent } = getStyleTagProperties(sheet)
// => just the styles
How to get a list of all style rules? In the typescript plugin we do something like that. Maybe we could extract that as an API. Or at least generate a JSON file with all the classes.
Because I want to eliminate the twind runtime, and I can't anticipate which classes will be necessary ahead of time.
You want to replace a 13kb runtime with 300kb of CSS?
You want to replace a 13kb runtime with 300kb of CSS?
Yes, because the runtime is something that costs cpu (correct if I'm wrong) and the css bundle is something that is cached by the browser and can be trimmed with a variety of methods.
The script can be cached by the browser. You are assuming the a 300kb CSS files has benefits over a 13kb JS file?
Who does the trimming when?
Have you done any performance tests that may indicate a problem?
The script also executes additional instructions, no? Maybe my understanding is incorrect but I believed that the twind runtime would execute code when the page starts up or when elements are appended to the DOM. (Compared to a css file which is cached by the browser, and wouldn't block the execution of other JS instructions).
How to get a list of all style rules? In the typescript plugin we do something like that. Maybe we could extract that as an API.
This seems to be the missing piece. If I had that list I wouldn't need anything else from twind in the first place because I would just make a <div>
with all those classes before setting up tailwind, then grab all the generated styles.
Do you have a link to where the list of all the style rules (or at least the list of class names that twind will generate style rules for?)
The script also executes additional instructions, no? Maybe my understanding is incorrect but I believed that the twind runtime would execute code when the page starts up or when elements are appended to the DOM. (Compared to a css file which is cached by the browser, and wouldn't block the execution of other JS instructions).
Have you done any performance tests that may indicate a problem?
The CSS file must be parsed as well! A full Tailwind CSS file has blown up several browser tabs already.
Who does the trimming and when?
Do you have a link to where the list of all the style rules (or at least the list of class names that twind will generate style rules for?)
Which variants do you need? All? In which combinations? :hover:focus:disabled
?
@sastan It would be nice to pass a list of variants, and have the set of classnames for those variants. tw(classesForVariants('hover', 'focus', 'focus-in', 'dark', 'md'))
etc.
But I'd even take all possible variants to start if that's the only option.
The CSS file must be parsed as well! A full Tailwind CSS file has blown up several browser tabs already.
That is true. But I was thinking that trimming unneeded css is a solved problem in general (not even framework specific). trim(cssFile, myHTML) == trimmedCSS
.
My argument for the tradeoff in file size was not "I'd rather have the entire tailwind css file instead of the runtime of twind because it's smaller and faster" it was "I want the simplest possible developer workflow with no build step for multiple themes that I can switch between (computed in the browser without any cli dev tools or build steps), and I'll take a file size hit in development mode because it's not that bad for most use cases and trimming the unneeded style rules out later has many solutions and should be a solved problem in general that I assume is solved somewhere in a css-framework agnostic way"
Do I understand correctly that this would be the flow:
Why not use the twind + shim in development? We can figure out the theming stuff.
And for production use the shim to extract all required rules to generate a static CSS file.
It would be nice to pass a list of variants, and have the set of classnames for those variants.
tw(classesForVariants('hover', 'focus', 'focus-in', 'dark', 'md'))
etc.
That would be quite restrictive.
But I'd even take all possible variants to start if that's the only option.
That would be mean several 10.000 CSS rules!
I want the simplest possible developer workflow with no build step for multiple themes
And that is the twind/shim. Could you show me some performance stats that show an impact?
Regarding concerns for file size: For development mode: A 6mb css file takes 100ms to parse by the browser and is served "locally" off my file system in about 30ms. The size is not a concern for development mode imho.
How about parsing and executing twind?
I really do not get what the problem is. There is simply an existing solution why not use it?
Why not use the twind + shim in development? We can figure out the theming stuff.
And for production use the shim to extract all required rules to generate a static CSS file.
It wasn't clear how to do this in a way that allows:
Maybe it's documented but it wasn't clear to me.
extract all of the styles that will possibly be necessary depending on which interactions will be performed
If this is so highly dynamic why not use the shim in production and be done with it.
It seems to me that if one wants to pre-render a stylesheet, the only way to do so with 100% accuracy and not knowing which interactions/frameworks might use which css classes is to take the entire stylesheet with all the variants, which brings me back to the original problem. The only solutions here seem to be: A) to include the twind runtime even in production, or B) to render out all possible classes. Some in this thread have suggested some other third alternative - which is to use twind in development mode, but then somehow take pre-computed stylesheets for production. But that's what I was after in the first place. That third option is the missing feature/use case which I am after.
You talked about a simple no build step process. But want to create a large css file with thousands of rules and then purge it.
Compare that with import twind/shim. Could you show me some performance stats that show an impact?
Edit: We have a twind/shim/server module that parses HTML string and generates all styles within it.
You talked about a simple no build step process.
For development mode.
For production mode the build step would be done automatically when the user does Chrome "Save As". It's a very different workflow, so I understand why this might be a round peg in a square hole for twind.
But then use them shim in development mode. And then twind/shim/server to parse the HTML to generate the styles.
Edit: It is called server but works in the browser
But then use them shim in development mode. And then twind/shim/server to parse the HTML to generate the styles.
I would like to do that but it wasn't clear it was even possible to generate multiple themes using only the shim, and toggle between those themes. Edit: I seemed to have run into some global state issues. Maybe I was just using it incorrectly?
With https://twind.dev/docs/modules/twind_observe.html you can do that. That is the base module for the shim.
Even with the server mode, I think I run into issues where there is markup not yet rendered on the page, so it won't generate all the necessary rules.
How would then purged the styles?
I was thinking of some kind of a config where I would "opt in" to various segments of tailwind features. Like "spacing"(includes flex), "colors", and variants ("focus", "focus-in") etc. And it would notify me at dev time with an error telling me I'm using classes that I haven't opted into.
I still recommend the shim for production you could inline these UMD files
<scrip src="https://unpkg.com/twind/twind.umd.js"></script>
<script src="https://unpkg.com/twind/observe/observe.umd.js"></script>
<script>
twind.setup({ theme: {}})
twindObserver.observe(document.documentElement)
</script>
Or create a bundle. The would be executed in the head. We are already doing something like this. And the lighthouse score is 100.
@sastan Is there a way with the shim to be able to later reconfigure the setup/theme? I tried this and my naive approach did not work. The second setup/theme did nothing. The use case is that I wanted to have an entirely different theme depending on whether the user was in dark mode or not (I would use that to switch the theme). The issue with tailwind is that the color pallets aren't really easily customizable based on whether or not you are in dark mode. The requirements for normalizing color contrast in different dark/light modes are different.
Hi, I'm building a utility that will allow you to use twind to render multiple different versions of the style sheet (I'm doing so in an iframe as I have found that twind is pretty "global" in nature - yes, I've read all of the examples about
.reset()
). What I'm doing right now with rendering twind in an iframe to force isolation works fine. I render thedomSheet()
in an iframe and report back the generated stylesheet to the main document. This allows me to non-destructively/statelessly try out different configs (almost like hot reloading). The problem is that the generated stylesheet will only render classes that are actually used in the document. Is there a way to tell thesetup()
to render all classes even if they're not used yet?I have also tried using the "non shim" version of the module to no avail. Here's my example: