Skip to content
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

CSS Tailwind slow build time even with different files... #275

Closed
frederikhors opened this issue Oct 31, 2020 · 20 comments
Closed

CSS Tailwind slow build time even with different files... #275

frederikhors opened this issue Oct 31, 2020 · 20 comments
Assignees

Comments

@frederikhors
Copy link
Contributor

I'm using Tailwind with default Svelte template and svelte-preprocess.

When I modify a single css the build is very slow (~10 seconds).

I read this: https://nystudio107.com/blog/speeding-up-tailwind-css-builds but I can't make it work with svelte-preprocess.

Also with different .css files imported in one App.svelte file when modifying just one of it (the lighter one, not the one with tailwindcss/utilities in it) the build is of the same time.

Why?

Thanks for your precious work.

@acoyfellow
Copy link

Tailwind 2.0 has crushed my development flow. Startup time has 50x'd, and my dev server is running out of memory and crashing in new ways I've yet to see.

@kaisermann
Copy link
Member

Hey @frederikhors 👋 Could you provide a minimal repro? Would help a lot 😁

@kaisermann kaisermann self-assigned this Nov 20, 2020
@natecorkish
Copy link

natecorkish commented Nov 30, 2020

Hey @frederikhors 👋 Could you provide a minimal repro? Would help a lot 😁

@kaisermann

https://github.com/natecorkish/frontend-svelte/

Checkout to 7b7f13e7dea17e6421c38fc7e8b51a02197e2493 which has an inline tailwind stylesheet. You should get the same issue as the original poster.

@dionysiusmarquis
Copy link

dionysiusmarquis commented Dec 4, 2020

I tested some scenarios always copy pasted the generated css coming from @tailwind base; … into a <style global>…:
Default sapper template with rollup: "vanilla" ~2s with prebuilt tailwind css ~26s
Typescript sapper template with rollup: "vanilla" ~4s with prebuilt tailwind css ~39s

When also using postcss and not a prebuilt css I'll have ~1m

I'd say one of the main problems is that svelte already processes the whole tailwind css as it where "handwritten". An option to skip the whole processing part for a specific <script> inside a .svelte file could help e.g.

@dionysiusmarquis
Copy link

dionysiusmarquis commented Dec 8, 2020

After I investigated this deeper in the context of sapper. I found out that it's not wrong to consider it as bad practice to import styles inside <style> rather than <script> (following: sveltejs/svelte#5745).
My conclusion is that you should most likely never add heavy "third party" css to svelte's <style> content. It's preferable to import them inside <script> and add the corresponding rollup plugins or webpack loaders to handle .css, .pcss imports.
This way you have better control of which styles are actually svelte component's css that should be processed by svelte and which styles are (in terms of Tailwind even pretty heavy) "third party" styles, that aren't bound to any specific svelte component. You just have to remember to also add a css minifier and enable tailwind purge to prod postcss

❗ (even though it's done this way in quite a lot blog posts regarding Tailwind in svelte)

<styles global lang="postcss">
  @import 'src/styles/tailwind.pcss'
…

<script>
  import 'src/styles/tailwind.pcss'
…

@frederikhors
Copy link
Contributor Author

@dionysiusmarquis does this solve the problem?

@dionysiusmarquis
Copy link

dionysiusmarquis commented Dec 8, 2020

Only tested it in the context of sapper and I'm down to ~5s per tailwind.pcss change after moving the imports to script. With style imports it was at >1m.
So for me it was a huge build time decrease. And that makes total sense, as svelte and svelte-preprocess don't need to "mess with" the Tailwind styles at all anymore.

@frederikhors
Copy link
Contributor Author

@dionysiusmarquis what rollup's plugin are you using for .pcss?

@frederikhors
Copy link
Contributor Author

@dionysiusmarquis can you share a gist for us, please?

@non25
Copy link

non25 commented Dec 31, 2020

This has nothing to do with svelte-preprocess.
Here's the wasted computation pipeline with default svelte-preprocess & tailwind postcss config:

  1. svelte-preprocess launches postcss with its default config on any style tag, which in this case makes tailwind AST for @apply and friends, which are not going to be used in every component < FIX: it makes it only with <style lang="postcss"> and with svelte-preprocess postcss: true option if tailwind is present in postcss.config.js
  2. resulting css goes to the svelte compiler
  3. svelte-loader or rollup-plugin-svelte stores all component styles from the compiler in memory cache
  4. every compiled svelte component loads this style from cache through import statements
  5. for webpack, due to how copypasted webpack-virtual-modules work, svelte-loader with emitCss: true reloads that virtual css file for every component on any change applying postcss transformations from default postcss config for example, making tailwind AST for the second time < FIX: that is only the case if you have a rule for .css that launches postcss with its config

So, make separate tailwind config for postcss working only for lang="tailwind" explicitly, that way you will be only making tailwind's AST where it's needed.

If you are using same global postcss config with tailwind and autoprefixer for svelte-preprocess and *.css files, then you are making tailwind's AST twice, one for svelte-preprocess, and second is for loading compiled css from compiled svelte component making it slow as hell.

So I would advise you to make separate configs for tailwind and other postcss stuff like autoprefixer for svelte components and external files (something like *.tcss).

I believe the smoothest DX with hmr (and this is important for style tags) can be achieved with webpack 5 for now. So...

Tired of slow tailwind?

Get this template: non25/svelte-tailwind-template

This issue can be closed, because it belongs in corresponding rollup-plugin-svelte and svelte-loader repos.

@non25
Copy link

non25 commented Jan 19, 2021

@frederikhors what do you think about the template above?

@benmccann
Copy link
Member

I've been reading through the issues in the Tailwind repo. This definitely isn't a problem unique to Svelte. A few things I found there that I'm wondering if people have tried:

@non25
Copy link

non25 commented Jan 22, 2021

@benmccann The article you found describes stuff better. 😁

My above post's main focus is currently irrelevant.
I've done tests and came to such conclusions because I was using one guy's config, which had:

  • bug from svelte-loader, reloading every component's virtual css on any project change (irrelevant today)
  • .css rule running postcss with tailwind, making reloading bug noticeable
  • huge imports of tailwind base stuff
  • purgecss & cssnano working in both modes

Here's main takeaways I see now

Include huge css from tailwind into your bundle only in production

These ones:

@tailwind base;
@tailwind components;
@tailwind utilities;

Prebuild them to be loaded in index.html for devmode and import & purge them in production.

In webpack, I achieve it with the help of null-loader, which outputs nothing from imports, and does so conditionally.

Launch postcss with tailwind only on css where you will write @applys, etc.

Avoid making rules for .css that launch postcss with tailwind. With emitCss: true it will spin it's @applys for preprocess, and then for imported virtual css.

This can go unnoticed because most guides recommend making one postcss.config.js for every postcss task and put tailwind plugin there.

I'd argue that people using tailwind would also use third-party svelte components and would want to run autoprefixer on them too, but without tailwind's overhead.

That's when explicit lang=tailwind, or tailwind files (.tcss for example) come in handy.

Sidenotes

Would be cool if there was an option for postcss/webpack, that saved spinned up stuff for @applys in memory after getting done with one component and reusing it on the next. Cold build times would skyrocket.

Or if we could somehow collect all css from every component, preprocess it in one batch with tailwind and then do regular processing without launching tailwind plugin anymore.

@dionysiusmarquis
Copy link

dionysiusmarquis commented Jan 22, 2021

I did a quite deep dive into the svelte css part. This preprocessor will add global(…) (see https://svelte.dev/docs#style) to every tailwind generated css definitions. Svelte then has an internal process to determine unused css parts etc. This processes can get quite heavy when executed on bigger generated, non hand written css like Tailwind. This processes are also 100% unnecessary computations for generated css like Tailwind. As already stated, the only vanilla way atm to bypass this unnecessary processing is to import css in script and not style.

You can find the breakdown and example REPL here (it shifted to sapper but is still relevant in this context): sveltejs/svelte#5745
tldr: Svelte css preprocessing/processing treads Tailwind css as "hand written" component css. As war as I can tell the most feasible solution on svelte level could be to add a way to tell svelte that the heavy, non hand written css parts shouldn't be processed by svelte at all

@non25
Copy link

non25 commented Jan 22, 2021

@dionysiusmarquis I don't believe that is the case unless you use something like @tailwind utilities or @import tailwindcss/utilities.css in svelte component's style tag, which is completely unnecessary.

Also I don't think importing anything that is not mixins, vars, functions in svelte component's style tag is a viable choice.

Tailwind works in svelte components by replacing directives with css code. There shouldn't be any extraneous classes whatsoever.

I don't understand why would anyone use svelte component's style tag for anything beyond component-specific scoped css.

If you need to import something global - import it in script tag or main.js.

@dionysiusmarquis
Copy link

dionysiusmarquis commented Jan 22, 2021

I don't believe that is the case unless you use something like @tailwind utilities or @import tailwindcss/utilities.css in svelte component's style tag, which is completely unnecessary.

But that's exactly the way everyone seem to use css libraries like Tailwind in svelte intuitively. That is most likely also the reason why nearly every blog post will tell you to use tailwind in svelte this way. And to match this intuition a way to write or add plain css could be a logical next step, imo

@non25
Copy link

non25 commented Jan 22, 2021

That is most likely also the reason why nearly every blog post will tell you to use tailwind in svelte this way.

This is outrageous.

So for every component where you want to slap some tailwind's classes such articles will instruct you to import the whole thing ?
And in the end you will have like ~component_count dups of whole tailwind's ~3mb csses ? Cool stuff.

Can you give links to such articles ?

<style plain></style> even if added in svelte-preprocess, should just remove that stuff and move imports to the script tag.

@dionysiusmarquis
Copy link

In svelte terms you are pretty much always in "component territory", i'd say. The current way of dealing with general css definitions seems to be adding a <style global … inside App.svelte or Layout.svelte ("components"). Which is understandable and will generate only one global definition for all components, but svelte will kick in all css processing routines nevertheless. There is currently no way to tell svelte that incoming css definitions inside a <style> don't need any additional processing and should just added to the global definitions unchanged.
The only way would be to do it via the <scripts> pipeline with corresponding loader or plugin. But this will also create an additional css file you have to hash and apply to your main template as well.

@dionysiusmarquis
Copy link

dionysiusmarquis commented Jan 25, 2021

@dionysiusmarquis can you share a gist for us, please?

@frederikhors Here is a simple example. It's not really the way you should organise the css files when working with Tailwind, but it outlines my point. You should get expected "fast" build times and you should also be able to use all the Tailwind features as expected.

Here are the changes applied to the current svelte-template:
dionysiusmarquis/svelte-tailwind-template@0a0c828

@kaisermann
Copy link
Member

As @dionysiusmarquis described, the preprocessor always treats your CSS considering a component context, so having the preprocessor putting those :global and svelte parsing and ignoring all of Tailwind's declarations will definitely take a toll on one's machine. I'd suggest importing the base of your Tailwind CSS through JS or some other mean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants