Unify various modules and helpers
See original GitHub issueWhat
Twind started as a single module that exported setup
and tw
. As the project has evolved we have added helpers like css
/apply
/style
and other features to support things like the shim, SSR and CLI. Now we are coming up to V1 we are looking to clean up and consolidate the Twind offering somewhat.
The general idea here is to create an interface something like this for the client and server respectively:
import { setup, tw, css, apply, style, sheets } from "twind";
import { setup, extract, sheets } from "twind/server";
Why
The aim is to make things a little more intuitive, improve discoverability and make documentation easier. We have witnessed developers struggling to know what to import where and wondering whether they are doing it right. The proposal aims to create just one way to do things.
Considerations
Bundle Size
Unifying modules like this is likely to increase the overall bundle size by a couple of KB (from ~14 to 16). Time taken to download/parse Twind is always a primary concern but we think the pros outweigh the cons here. Another thing to note is that the module will be tree-shakeable so if you are using a bundler and don’t use css
for example, then it won’t be bundled into your application.
Shim by default
You might notice that the shim is not exposed by the proposed interface. The more we have used Twind ourselves or seen it been used by others, we have noticed that the shim is the easiest and most popular mechanism for both server and client. Going forward we would like to make this the default behaviour and have things just work. This means that the go to way of adding styles to your app will be via class="bg-black text-white"
which comes with the advantage of aligning static HTML and virtual DOM implementations. We appreciate that not every application will require the runtime component which activates the MutationObserver (and allows for the better dev tools experience) so we are proposing that this can be disabled by config similarly to the autoprefixer and hashing setup({ runtime: false })
.
Tailwind Utilities
By default Twind includes Tailwind theme/variants/utilities definitions because it is a comprehensive and well documented preset. However this is where the vast majority of Twind’s filesize comes from. Something we have been considering lately is abstracting these out to twind/tailwind
which make Twind core offering very obvious and promote the idea of developers/companies to create their own design system that they can use with Twind much like we do by default with Tailwind as a basis today. This might also allow developers to create custom builds that only import the utilities they need resulting in smaller bundles for everyone.
The downside of this however is (quite contrary to the primary goal here) that it would requires something like this:
import { setup, tw, css, apply, style, sheets } from "twind";
import * as tailwind from "twind/tailwind"
setup({ presets: [{ ...tailwind }] });
With that said, this is probably something we could either a) do internally but still apply Tailwind presets by default or b) not do at all c) embrace the ideal wholly knowing that extra import only has to be done once during setup.
RFC
I’m creating this issue to gather feedback, thoughts and further considerations that we might not have been noted here. If you have and bright ideas or strong opinions on any aspect of this proposal then please comment below and I will update this description accordingly.
Issue Analytics
- State:
- Created 2 years ago
- Reactions:8
- Comments:16 (11 by maintainers)
Top GitHub Comments
Only just seen this reply so thanks for your patience. We are using
@twind/next
out of the box with no special configuration. It works absolutely great. The critical CSS is extracted to a style tag at the top of the page on first load SSR… after that the shim kicks in and handles dynamic styles at runtime (think a modal that was opened by the user after page load that requires styling). This comes with the added benefit of not shipping potentially unused styles to the client, but obviously this is a tradeoff.I’m still unsure of how valid the concern is of performance degradation when it comes to loading in Twind. On our netlify deploy environments we were seeing lighthouse perf scores ~90 and once we promoted the site to a production like environment we are seeing that go up to ~100 for desktop at least.
I do agree however that it would be interesting to see how we could make hydration more efficient. My initial approach would be to send down a map of already computed rules (essentially a serialized version of the cache that Twind would generate on the server) to prevent duplication of work by the browser. It sounds easy enough but I’m sure it comes with drawbacks/complications that I have not considered.
Thanks for your comments @danielweck I will follow the conversation on the thread you created (as this was not really the intended topic here) but just to let you know that I’m working on a “not so pet project” (a global ecommerce site) and we only have ~16KB of compressed twind imported at runtime (which includes tw/shim/css/style). All critical CSS is statically extracted during SSR with twind/next.
So in that sense I think that is is reasonably reasonable to state this kind of JS size, excluding of course all the additional “non-essential” modules like typography and forms.