-
-
Notifications
You must be signed in to change notification settings - Fork 104
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
Unify various modules and helpers #157
Comments
Unless I am mistaken, I think that the advertized 14-16KB footprint is slightly misleading. There are several other CSS-in-JS libs that claim "near-zero" / "tiny" runtime, sometimes optimistically. In contrast, Twind offers complete Tailwind CSS support (and more) for a relatively small price, which I believe is worth documenting more openly. Let's break it down: I believe the currently-advertized bundle size corresponds to the "minimal" Twind runtime which excludes the https://bundlephobia.com/[email protected] https://unpkg.com/browse/[email protected]/ Now, in my pet project (i.e. a small performance-oriented Preact WMR website), the Rollup-bundled and Terser-minified JS chunk dedicated to Twind weighs in at 35KB without any plugins. The actual practical size is 53KB with the "typography" and "forms" plugins (and I plan to use other ones too). These KB figures are on-disk sizes, before gzip compression. Note that I use The 50+KB (pre-compression) Twind-dedicated JS bundle is actually never loaded in the frontend of my production builds, but I suspect that I am an exception to the rule: the vast majority of Twind users / developers will ship the runtime, which is; all things considered; relatively lightweight given Twind's rich feature set. I just don't think 16KB (~30KB pre-compression) is achievable in real-word use. EDIT: added Lines 42 to 84 in 7d9cc19
|
Yes please 👍 :) |
That would be great! I only had a superficial look at the core lib's source code, so I don't have a clear picture of how costly it would be to factor out the Tailwind definitions (in terms of development effort). But if you guys feel you can do it, I will totally root for you ;) I wonder if Twind plugins could be harmonized based on the same architectural principles (i.e. declarative CSS definitions + small integration API), and whether or not this could help power the typescript language service (see tw-in-js/typescript-plugin#8 ) It would be nice if there was a default Regarding tree shaking / dead code detection: I don't have a clear idea of how granular the TW definitions are / can be in Twind's code, but once factored out it should be possible to identify atoms of functionality that can safely be excluded from production JS bundles. Seems like a ton of work, but if you guys can do it :) 👍 |
@danielweck We are talking about compressed file sizes here. In one of the latest commits I've updated size-limit to show some more stats (the sub-modules do not include twind as dependency): I believe that these are the stats we should talk about:
How much raw JS there is not important most of the time. What matters are how much must be parsed and how much is evaluated when importing the module. These things are reflected in the running time. I know that this is not what people are used to but that is what matters. That is why we should talk/mention total time (load +eval) and size (minified & brotli). |
Of course. I thought this was clear in my comment, sorry if it wasn't. Looking at Bundlephobia numbers, and then looking at Twind's bundle ESM code in the "core lib", I love your updated Brotli figures by the way! :) As expected, better than gzip. Can you please verify that |
I couldn't agree more. Lighthouse screenshots with vanilla HTML+Twind integration would illustrate this quite well (I mean: no (P)React, Vue, Solid, etc.) Naturally, in a web app with a giant DOM, lots of inline SVG, non-lazy image loading, non-deferred scripts, analytics, ads, etc., the impact of Twind runtime will be relatively minimal :) |
These results are to be expected because the shim kicks in after DOMContentLoaded. For production sites we recommend critical CSS extraction. I'm currently experimenting with one or two ways to optimize this. One of them is: <script>
// Inline https://unpkg.com/twind/twind.umd.js
// Inline https://unpkg.com/twind/observe/observe.umd.js
twindObserve.observe(document.documentElement)
</script> Could you try that one? |
Sure. My current test is with: <!-- script type="module" src="https://cdn.skypack.dev/twind/shim"></script -->
<!-- LOCALHOST COPY: -->
<script type="module" src="./shim.js"></script> |
@danielweck This is quite intersting. Could we move that into discussion or discord. |
I will do. |
I created a separate GitHub "discussion": #161 |
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. |
Thanks Luke, good to hear about Twind being used in production websites. I don't use Next.js, and my use case is certainly different. I use Preact WMR (crucially: static SSR, not dynamic route prerendering), so I generate an inline critical / minimal CSS style element for pre-hydration initial render, as well as a separate async external stylesheet for other styles used post-hydration. This way, I am able to completely negate the need for the Twind runtime / just-in-time resolver. The Twind CLI which extracts styles ahead of time is very useful in many cases, but there are significant caveats (by design, not bugs) which stop me from using this utility. Plus, the WMR pre/post processing plugin pipeline and Preact's VNode interceptor allow me to more accurately extract used Twind CSS. If you don't mind me asking, do you use Twind's open source Next.js integration, or some in-house solution? Cheers, Daniel |
Only just seen this reply so thanks for your patience. We are using 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. |
I've been using Twind in a Deno environment. There's one problem related to this issue in that case which you might want to consider. At https://unpkg.com/[email protected]/twind.js, there's a dependency on style-vendorizer (i.e. Although this works, it fails when you want to use Twind within a web worker as import maps aren't supported there yet (denoland/deno#6675). If you re-architect this portion of Twind, maybe a good option here would be to allow the consumers to inject style-vendorizer somehow or at least have it as an optional dependency for the project. |
Implemented in twind v1. Please give it a try. Here are some links to get you started:
Closing this for now. Feel free to re-open. |
What
Twind started as a single module that exported
setup
andtw
. As the project has evolved we have added helpers likecss
/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:
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 hashingsetup({ 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:
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.
/cc @tw-in-js/contributors
The text was updated successfully, but these errors were encountered: