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

Tailwind 2.0 poor performance as part of a webpack PostCSS build system #2820

Closed
khalwat opened this issue Nov 19, 2020 · 56 comments
Closed
Assignees

Comments

@khalwat
Copy link

khalwat commented Nov 19, 2020

Description of the problem

Tailwind CSS 2.0 builds slowly as part of a HMR webpack build system. It is slower than the release, but that could just be due to the amount of CSS generated increasing.

I filed a very similar issue a month ago #2544 and worked around the problem by splitting the CSS up into separate chunks. Global @apply still worked, and everything was great.

It's all written up in the article Speeding Up Tailwind CSS Builds

However with Tailwind CSS 2.0, the technique described in the article breaks.

Desired solution

I'm hoping to have the DX of working with Tailwind CSS 2.0 be improved from a HMR build time perspective.

I realize that you can only optimize your generation of CSS so much, and a PR is in the works to do just that with esbuild. I also realize that part of the slowness here is simply webpack and the surrounding ecosystem being slow when dealing with massive amounts of CSS (though I've optimized that quite a bit here).

However, the paradigm that Tailwind CSS is using is what generates a massive amount of CSS, and webpack is a very widely used tool that many things are built upon.

Some way to address the DX here would be great; huge gains can be made using the CSS splitting technique described in the article, maybe there could be a way to restore that functionality to Tailwind 2.x?

Link to a minimal reproduction:

I made a new branch with Tailwind 2.0, webpack 5, PostCSS 8, and all the goodness:

https://github.com/nystudio107/tailwind-css-performance/tree/problem-tailwind-2.x

webpack_1  | ℹ 「wdm」: Compiled successfully.
webpack_1  | ℹ 「wdm」: Compiling...
webpack_1  | ℹ 「wdm」: assets by status 72.8 KiB [cached] 2 assets
webpack_1  | assets by status 9.21 MiB [emitted]
webpack_1  |   assets by info 3.62 MiB [immutable]
webpack_1  |     asset app.53801fed2ee43ce850f5.hot-update.js 3.62 MiB [emitted] [immutable] [hmr] (name: app)
webpack_1  |     asset runtime.53801fed2ee43ce850f5.hot-update.js 881 bytes [emitted] [immutable] [hmr] (name: runtime)
webpack_1  |     asset 53801fed2ee43ce850f5.hot-update.json 37 bytes [emitted] [immutable] [hmr]
webpack_1  |   assets by path js/*.js 5.6 MiB
webpack_1  |     asset js/app.js 5.55 MiB [emitted] (name: app)
webpack_1  |     asset js/runtime.js 43.9 KiB [emitted] (name: runtime)
webpack_1  |   asset manifest.json 303 bytes [emitted]
webpack_1  | Entrypoint app 9.21 MiB = js/runtime.js 43.9 KiB runtime.53801fed2ee43ce850f5.hot-update.js 881 bytes js/app.js 5.55 MiB app.53801fed2ee43ce850f5.hot-update.js 3.62 MiB
webpack_1  | cached modules 711 KiB [cached] 61 modules
webpack_1  | runtime modules 28.9 KiB 14 modules
webpack_1  | ./node_modules/css-loader/dist/cjs.js??clonedRuleSet-4.use[1]!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-4.use[2]!../src/css/app.pcss 3.61 MiB [built] [code generated]
webpack_1  | example-project (webpack 5.4.0) compiled successfully in 2527 ms

While 2.5s isn't awful, this is in a pretty minimal CSS setup, where I was getting about 1.2s for the equivalent builds in Tailwind 1.x, and using the technique described in the article, the builds were around 182ms

@adamwathan
Copy link
Member

However with Tailwind CSS 2.0, the technique described in the article breaks.

Can you explain specifically what you mean by this? Does the build fail with an error or something? I'm surprised anything is different here as there were basically no internal changes between 1.9 and 2.0.

@khalwat
Copy link
Author

khalwat commented Nov 19, 2020

Ah yes, sorry, I pushed up a new branch that demonstrates the problem: https://github.com/nystudio107/tailwind-css-performance/tree/broken-tailwind-2.x

So if I try to do this in global.pcss:

.my-cool-class {
  @apply pt-4 mb-2 text-lg
}

body {
  background-color: orange;
  @apply my-cool-class
}

I end up with this:

webpack_1  | ERROR in ../src/css/app-components.pcss (./node_modules/css-loader/dist/cjs.js??clonedRuleSet-4.use[1]!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-4.use[2]!../src/css/app-components.pcss)
webpack_1  | Module build failed (from ./node_modules/postcss-loader/dist/cjs.js):
webpack_1  | SyntaxError
webpack_1  |
webpack_1  | (9:10) /var/www/project/src/css/components/global.pcss The `pt-4` class does not exist. If you're sure that `pt-4` exists, make sure that any `@import` statements are being properly processed before Tailwind CSS sees your CSS, as `@apply` can only be used for classes in the same CSS tree.
webpack_1  |
webpack_1  |    7 |
webpack_1  |    8 | .my-cool-class {
webpack_1  | >  9 |   @apply pt-4 mb-2 text-lg
webpack_1  |      |          ^
webpack_1  |   10 | }
webpack_1  |   11 |
webpack_1  |
webpack_1  |  @ ../src/css/app-components.pcss 47:4-60:5 2:12-216 9:17-24 13:7-21 45:20-34 49:6-59:7 50:38-52 56:26-40 58:21-28 68:15-29
webpack_1  |
webpack_1  | example-project (webpack 5.4.0) compiled with 1 error in 207 ms

The above works fine when using Tailwind 1.x, despite being broken up into separately imported files.

I assumed this was a result of the various internal @apply changes that were made.

@khalwat
Copy link
Author

khalwat commented Nov 19, 2020

I made a branch that shows the same code working in Tailwind 1.x: https://github.com/nystudio107/tailwind-css-performance/tree/works-tailwind-1.x

Here's the diff between the two branches:

nystudio107/tailwind-css-performance@broken-tailwind-2.x...works-tailwind-1.x

It's just changing the Tailwind CSS semver from ^2.0.0 to ^1.0.0, and then the changes in the package-lock.json as a result of the npm install but that's it.

It works with Tailwind CSS 1.x, and it breaks with Tailwind CSS 2.x, with the aforementioned error:

webpack_1  | (9:10) /var/www/project/src/css/components/global.pcss The `pt-4` class does not exist. If you're sure that `pt-4` exists, make sure that any `@import` statements are being properly processed before Tailwind CSS sees your CSS, as `@apply` can only be used for classes in the same CSS tree.

@acoyfellow
Copy link

acoyfellow commented Nov 20, 2020

I'm not sure if it's related, I have a hunch it might be.. But upgradng a Sapper/Svelte (using postcss with rollup though, not webpack) project to Tailwind 2.0 has brought me a new headache. Sapper's compile time has 10-100x'd, and I can't make changes on the fly. I get "Killed 137" messages. Trying to debug now, I'll update this comment with any findings.

Edit: I found that

  1. It's seems something related to Tailwind 2.0, or one of it's dependencies. At least in my case (sapper project using svelte preprocessor)
  2. Simply doubling the CPU on my dev machine did nothing. still crashing.
  3. Removing the preprocessor entirely bypasses the crashing
  4. Removing the preprocessor build step from rollup in the server section, but leavign it in the client (this is the "trick" i'm unsure about. my project seems to be working as expected still, and it compiles). I question if we need the svelte preprocessor in both client/server? The same project, but simply updating deps, brought my dev experience to a screetching hault. Edit2: this isn't a real solution. I've introduced a flicker on ssr load while the css is loading from the client, I think.

@ambethia
Copy link

ambethia commented Nov 22, 2020

I'm going to add to this. I just upgraded to tailwind 2.0 and I'm getting brief beach-balling on my browser whenever HMR happens in a svelte app on snowpacker. PostCSS plugins are just tailwind, postcss-nested, and autoprefixer.

@acoyfellow
Copy link

acoyfellow commented Nov 22, 2020

Autoprefixer was my problem, I think. Still investigating. I had to roll it back to an older version than what TW 2 uses. My dev experience and build times are back to “normal”. TW has^10.0.2, and i rolled back to ^9.8.6.

@Cherry
Copy link
Contributor

Cherry commented Nov 26, 2020

We're also experiencing very slow build times with our setup. Here's another reproduction repo which I hope will help resolve the problem:

https://github.com/Cherry/postcss-tailwind-slowdown

In this stripped down repo, we have a very basic PostCSS workflow setup, with postcss-preset-env, a simple JS workflow with just babel-loader (defaults for example sake), and 2 entrypoints, both requiring tailwind. On my Ryzen 3700x system, the npm run dev step takes almost 50 seconds for the first build, and then if I alter the tailwind.config.js file, the rebuild takes another 50s. For some members on my team with older systems, this rebuild process can take multiple minutes, which is as you can imagine quite disruptive to their workflow.

Other system info:

OS: Windows 10.0.19041 Build 19041
Node: v14.15.1 LTS

If we can provide any more information to make debugging this easier, please let me know and I'd be happy to do so.

@RobinMalfait
Copy link
Contributor

Hi!

I've tested your setup @Cherry and when I run webpack --progress it takes about 1min20s for me.
But using that same setup, when I run yarn add postcss@latest and then webpack --progress again, it takes 15s. (essentially upgrading to postcss 8 instead of postcss 7)

Is it possible to migrate to postcss 8?

I'll keep doing some research to see if I can find other culprits.

@Cherry
Copy link
Contributor

Cherry commented Nov 27, 2020

Hi!

I've tested your setup @Cherry and when I run webpack --progress it takes about 1min20s for me.
But using that same setup, when I run yarn add postcss@latest and then webpack --progress again, it takes 15s. (essentially upgrading to postcss 8 instead of postcss 7)

Is it possible to migrate to postcss 8?

I'll keep doing some research to see if I can find other culprits.

Thanks so much for taking a look @RobinMalfait. Unfortunately we're not able to upgrade just yet. postcss-preset-env is a key component in our build process today, so we're stuck on PostCSS 7 until that sees an update. A jump from 1m20s to 15s is definitely significant, though 15s still seems like a rather long time for a simple development build.

@iuscare
Copy link

iuscare commented Nov 29, 2020

I can confirm, that the switch from tailwind 1.9.6 to 2.0.1 with postCSS8 in place increased our compilation time during development no less than 33% and sadly breaks the workaround by @khalwat

Please let us know, if additional information can be helpful to classify or to debug the issue.

@michtio
Copy link

michtio commented Nov 29, 2020

I can confirm the same thing as what @khalwat mentioned, we reverted back to 1.9, I hope this issue gets some priority, as TW2.0 slow down our dev environment a lot, HMR takes much too long, and our compilation time ( after changing out postcss-preset-env , to leverage autoprefixer directly, and abandoning nested postcss ) still increases depending on machines by 35-55%, which brings in a rather complicated ( but necessary ) setup build times to ~2.8s.

@gremo
Copy link

gremo commented Dec 2, 2020

I can confirm I have the same performance problems. Quite a decent PC here.

Webpack 4 + TailwindCSS 1.9 with live reloading:

Cattura1

Webpack 5 + TailwindCSS 2.0 with live reloading:

Cattura2

@ahtik
Copy link

ahtik commented Dec 2, 2020

@gremo Could you also try a simple postcss build and check the speed of initial build and rebuild on file change without webpack being involved? Something like postcss src/styles/tailwind.css -o src/styles/main.css --verbose -w.

Btw, I have purge enabled: true in tailwind.config.js, so also the HRM is getting the small version without a significant slowdown.

@gremo
Copy link

gremo commented Dec 2, 2020

@ahtik sure

PostCSS 8 + TailwindCSS 2:

postcss "src/style.css" "-o" "temp.css" "--verbose"

Processing src\style.css...
Finished src\style.css in 2.7 s

PostCSS 7 + TailwindCSS 1.9:

postcss "src/style.css" "-o" "temp.css" "--verbose"

Processing src\style.css...
Finished src\style.css in 2.21 s

Config is pretty the same in the two cases:

module.exports = {
  theme: {},
  variants: {},
  plugins: [],
  purge: {
    content: [
      './src/**/*.html',
      './src/**/*.{js,jsx}',
    ],
    options: {
      keyframes: true,
      fontFace: true,
      variables: true,
    },
  }
}

So what's wrong with Webpack 5 + postcss-loader?

@ahtik
Copy link

ahtik commented Dec 2, 2020

@gremo thanks! Could you also try with the -w option that is watching for the file changes? Rebuild is also often much faster and would be interesting to see the comparison.

For my setup, I've started to use a bit of unconventional method with the package.json commands (although using snowpack instead of webpack, but the idea is the same):

    "start": "yarn npm-run-all --parallel watch:styles snowpack:dev",
    "snowpack:dev": "snowpack dev",
    "watch:styles": "postcss src/styles/tailwind.css -o src/styles/main.css --verbose -w",
    "build:styles": "postcss src/styles/tailwind.css -o src/styles/main.css --verbose",

My tailwind.config.js:

module.exports = {
  purge: {
    enabled: true,
    content: ['./src/**/*.{js,tsx}', './public/index.html'],
  },
.....
}

@gremo
Copy link

gremo commented Dec 2, 2020

PostCSS 8 + TailwindCSS 2:

postcss "src/style.css" "-o" "temp.css" "--verbose" "-w"

A few changes show this:

Processing src\style.css...
Finished src\style.css in 2.67 s

Waiting for file changes...
Processing src\style.css...
Finished src\style.css in 1.16 s

Waiting for file changes...
Processing src\style.css...
Finished src\style.css in 1.09 s

Waiting for file changes...
Processing src\style.css...
Finished src\style.css in 1.06 s

PostCSS 7 + TailwindCSS 1.9:

postcss "src/style.css" "-o" "temp.css" "--verbose" "-w"

Processing src\style.css...
Finished src\style.css in 2.2 s

Waiting for file changes...
Processing src\style.css...
Finished src\style.css in 878 ms

Waiting for file changes...
Processing src\style.css...
Finished src\style.css in 834 ms

Waiting for file changes...
Processing src\style.css...
Finished src\style.css in 849 ms

@khalwat
Copy link
Author

khalwat commented Dec 2, 2020

@gremo Look at the difference in the size of the bundles there... 8.8mb for webpack 4/Tailwind 1.x, and 14.1mb for webpack 5/Tailwind 2.x

A large part of the issue with performance in a HMR scenario is just the sheer quantity of data that's having to be processed, wrapped in JS, injected into the DOM, and then the browser has to re-parse it all to rebuild the CSSOM.

I'd recommend cutting down on the size as much as possible, by removing CSS maps and using a more performant devtool as in:

https://github.com/nystudio107/annotated-webpack-config/blob/webpack-5/buildchain/webpack-configs/postcss-loader.config.js

&

https://github.com/nystudio107/annotated-webpack-config/blob/webpack-5/buildchain/webpack-configs/dev-server.config.js#L53

All of this aside, even fully optimized, the real issue is we need to be able to rebuild only what is has to be rebuilt. The way Tailwind CSS 2.x is currently built requires that everything is rebuilt every time.

No optimizations in the world are going to make that performant in a HMR scenario through the entire pipeline, regardless of the tool used. And the amount of CSS being generated will only get larger as features are added.

Some way to do "CSS splitting" so Tailwind CSS can rebuild only the necessary bits—but keep a global state to allow @apply to work—is likely going to be needed.

This worked with Tailwind CSS 1.x, so I'm hoping the folks at Tailwind Labs can figure out a way to restore the functionality as described in the article in my initial post.

@lustrousgorilla
Copy link

lustrousgorilla commented Dec 3, 2020

I don't know enough about Tailwind's internals, but perhaps something like a static cache that just returns the utilities rather than regenerating them if the config hasn’t changed would be ideal.

@khalwat this was a great suggestion in #2544. I'd try my hand at making a PR but am in the same boat of having zero familiarity with the Tailwind codebase. @adamwathan how much time/effort do you think it'd take one of us get up to speed enough to implement such a cache?

FWIW I've just resigned myself to tearing out my custom classes with @apply and duplicating the utility classes everywhere in my templates; less painful IMO than these slow re-builds (still ~3 seconds for me after turning off source maps).

@khalwat
Copy link
Author

khalwat commented Dec 4, 2020

@lustrousgorilla you can also roll back to Tailwind 1.9.x -- because the CSS splitting mentioned in the OP here works with Tailwind 1.x, so you'll get super fast HMR times and global @apply still works.

It just doesn't work with Tailwind 2.x, either due to internal changes in Tailwind CSS, or some other part of the package tree they use.

Here's the article again: Speeding Up Tailwind CSS Builds

@RobinMalfait
Copy link
Contributor

Hey all!

I'm looking further into this, and while Tailwind is currently not the fastest, I believe that there are other issues as well. (This has also been mentioned before, but wanted to clarify it anyway)

github.com/tailwindlabs/webpack-tailwind on  master [!?] is 📦 v1.0.0 via ⬢ v15.2.0webpack
config changed: 1
TAILWIND(): 8.353s
[webpack-cli] Compilation finished
asset bundle.js 17.2 MiB [emitted] [minimized] [big] (name: main)
runtime modules 657 bytes 3 modules
orphan modules 89 bytes [orphan] 1 module
cacheable modules 17.2 MiB
  ./src/index.js + 1 modules 159 bytes [built] [code generated]
  ./src/file.css 17.2 MiB [built] [code generated]
  ./node_modules/css-loader/dist/runtime/api.js 1.57 KiB [built] [code generated]

WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
This can impact web performance.
Assets:
  bundle.js (17.2 MiB)

WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
Entrypoints:
  main (17.2 MiB)
      bundle.js


WARNING in webpack performance recommendations:
You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.
For more info visit https://webpack.js.org/guides/code-splitting/

webpack 5.9.0 compiled with 3 warnings in 30110 ms

I timed the actual time spent inside Tailwind, which results in 8.353s (TAILWIND(): 8.353s)
The total time of my webpack build is 30s (30110 ms)


Another funny observation is that when you run webpack --watch that it apparently runs the build twice, this could be something wrong with my setup though...

github.com/tailwindlabs/webpack-tailwind on  master [!?] is 📦 v1.0.0 via ⬢ v15.2.0webpack --watch
[webpack-cli] Compilation starting...
config changed: 1
TAILWIND(): 8.004s
[webpack-cli] Compilation finished
asset bundle.js 17.2 MiB [compared for emit] [minimized] [big] (name: main)
runtime modules 657 bytes 3 modules
orphan modules 89 bytes [orphan] 1 module
cacheable modules 17.2 MiB
  ./src/index.js + 1 modules 159 bytes [built] [code generated]
  ./src/file.css 17.2 MiB [built] [code generated]
  ./node_modules/css-loader/dist/runtime/api.js 1.57 KiB [built] [code generated]

WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
This can impact web performance.
Assets:
  bundle.js (17.2 MiB)

WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
Entrypoints:
  main (17.2 MiB)
      bundle.js


WARNING in webpack performance recommendations:
You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.
For more info visit https://webpack.js.org/guides/code-splitting/

webpack 5.9.0 compiled with 3 warnings in 29644 ms
[webpack-cli] watching files for updates...
[webpack-cli] Compilation starting...
TAILWIND(): 3.262s
[webpack-cli] Compilation finished
asset bundle.js 17.2 MiB [emitted] [minimized] [big] (name: main)
runtime modules 657 bytes 3 modules
orphan modules 89 bytes [orphan] 1 module
cacheable modules 17.2 MiB
  ./src/index.js + 1 modules 159 bytes [built] [code generated]
  ./src/file.css 17.2 MiB [built] [code generated]
  ./node_modules/css-loader/dist/runtime/api.js 1.57 KiB [built] [code generated]

WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
This can impact web performance.
Assets:
  bundle.js (17.2 MiB)

WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
Entrypoints:
  main (17.2 MiB)
      bundle.js


WARNING in webpack performance recommendations:
You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.
For more info visit https://webpack.js.org/guides/code-splitting/

webpack 5.9.0 compiled with 3 warnings in 24589 ms
[webpack-cli] watching files for updates...

What you will notice is that the config only changed once, and we already have some caching in place. This results in:

  • TAILWIND(): 8.353s (First build, at the top)
  • TAILWIND(): 3.262s (The second build, for some reason)

Next, I made a change inside in my css file where I have some @apply rules setup, but here is the interesting part:

[webpack-cli] Compilation starting...
TAILWIND(): 3.856s
[webpack-cli] Compilation finished
asset bundle.js 17.2 MiB [emitted] [minimized] [big] (name: main)
runtime modules 657 bytes 3 modules
orphan modules 89 bytes [orphan] 1 module
cacheable modules 17.2 MiB
  ./src/index.js + 1 modules 159 bytes [built] [code generated]
  ./src/file.css 17.2 MiB [built] [code generated]
  ./node_modules/css-loader/dist/runtime/api.js 1.57 KiB [built] [code generated]

WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
This can impact web performance.
Assets:
  bundle.js (17.2 MiB)

WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
Entrypoints:
  main (17.2 MiB)
      bundle.js


WARNING in webpack performance recommendations:
You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.
For more info visit https://webpack.js.org/guides/code-splitting/

webpack 5.9.0 compiled with 3 warnings in 26862 ms
[webpack-cli] watching files for updates...

The time spent in tailwind itself is TAILWIND(): 3.856s, again, while this is not the fastest, the total time spent in webpack is 26862 ms. Therefore I think that Tailwind CSS is not the bottleneck. (It does generate a lot of css though 😅).

So, @lustrousgorilla implementing (more) caching in Tailwind itself won't fix any of these bigger problems.


That said, I am going to look into why the proposed solution by @khalwat for 1.9 (#2820 (comment)) doesn't work anymore.

@khalwat
Copy link
Author

khalwat commented Dec 4, 2020

@RobinMalfait specifically if you could look into the times it takes when you do:

webpack serve

...and then not on the initial build, but rather on rebuild times when you change something in one of your CSS files.

That is the HMR timing that uses webpack-dev-server that we're discussing.

Thanks for looking into this!

@gremo
Copy link

gremo commented Dec 4, 2020

Well, I'm not an expert about bundlers and build systems, but here my two cents . Considering that:

  • The time of re-building Tailwind CSS is quite the same with PostCSS 7 and TW 1.9 or PostCSS 8 + TW 2 (max 1 sec on my machine)
  • I did a little experiement with Snowpack (https://github.com/gremo/snowpack-playground) and it seems.... really pretty fast 😎

... the problem has to be the build chain or the PostCSS loader of Webpack.

@ahtik
Copy link

ahtik commented Dec 4, 2020

In addition to getting the build&rebuild fast, worth experimenting with purge always enabled and compare the results with unpurged.

It looks like in general HMR is faster with less code to push through and making up the purge time.
image

@khalwat
Copy link
Author

khalwat commented Dec 4, 2020

@ahtik in my tests, purging in development only helped if you also didn't disable CSS sourcemaps... because then you're cutting out a good amount of file size with your purging.

But it's better to disable CSS sourcemaps in local dev when using Tailwind CSS and a HMR system imo.

@khalwat
Copy link
Author

khalwat commented Dec 4, 2020

@gremo we're talking specifically about the HMR time. In your Snowpack setup, if you start the dev server up, and make a change to your CSS, what is the resulting rebuild time that results from it rebuilding all of the CSS and injecting it dynamically into the DOM?

I've seen some colleagues of mine working with Snowpack, and they had similarly slow times. Are we missing something?

@gremo
Copy link

gremo commented Dec 4, 2020

@khalwat I just learned snowpack yesterday sorry 😄 I can't figure out how to print stats about the time to rebuilt + inject. It seems way faster that my previously posted setup (Webpack 5, PostCSS 8, TW 2). About 3 secs I would say.

@adamwathan
Copy link
Member

Going to close this at this point since we are back to v1 behavior and there's nothing left we can do in this codebase to improve things. Any additional improvements will come in the form of improvements to css-loader or a new loader designed with large CSS files in mind.

Glad we were at least able to get the old workaround working 👍🏻

@khalwat
Copy link
Author

khalwat commented Dec 16, 2020

Been heads-down on another project, but will verify shortly!

alwaysblank added a commit to 11in/elfin that referenced this issue Dec 19, 2020
https://nystudio107.com/blog/speeding-up-tailwind-css-builds

tailwindlabs/tailwindcss#2820

Basically, the splits the CSS up into a bunch of separate files so that
Webpack only has to rebuild the *whole* tailwind thing rarely. HMR
reloading when the site is served is much faster!
@AndyOGo
Copy link

AndyOGo commented Dec 21, 2020

Regarding the build of tailwind itself, is it incremental?
To me it looks like that the whole stuff is rebuild every time, instead of granular updates.

Ideally it would build the whole thing initially, and then only apply partial updates, e.g. when the tailwind config file changes.
If I change some CSS with @apply, nothing of tailwind itself should actually rebuild, since there is no change, it should just get a memoized result from a cache.

@jcbpl
Copy link

jcbpl commented Jan 8, 2021

In case it's helpful to anyone else, I'm seeing dramatically improved build times (30s to 15s) by including Tailwind via JS import rather than PostCSS @import, in a Webpacker v4 project with Tailwind 2, PostCSS 8, and postcss-import 12.0.1.

Posted details in #3321!

@peterpeterparker
Copy link

peterpeterparker commented Jan 9, 2021

In an Angular app using currently tailwindcss v2.0.2, many pages and components, we removed all empty scss files. The build went down from 12min to 6min.

Still really slow. Specially as new components were added recently. The build take actually > 10 minutes.

@peterpeterparker
Copy link

I found the issue while upgrading ngneat/tailwind from v3 to latest in the Angular application.

Previously, the following purge configuration was slowing the build:

purge: {
    content: ['**/*.html', '**/*.ts', '**/*.scss'],
  },

The new default config provided by ngneat is the following and fixed the build time:

purge: {
    content: ['./src/**/*.{html,ts}'],
  },

See this PR for the details. The build time went down to 2 min.

@hiepxanh
Copy link

woa, that so much much good. Thank you a lot @peterpeterparker
build time reduce from 170s to 106s
Yahooooooooooooo!
image
image

@Hecatron
Copy link

Hecatron commented Jan 26, 2021

One thing I've discovered is that extract-css-chunks-webpack-plugin I think is a quicker option than style-loader
when using tailwind

style-loader
initial build: 18993 ms
rebuild 3299 ms

extract-css-chunks-webpack-plugin
initial build: 11321 ms
rebuild: 567 ms

In the rules I'm now using

const ExtractCssChunks = require('extract-css-chunks-webpack-plugin');

...

{ test: /\.css$/, use: isDevBuild ?

    // Development
    [ExtractCssChunks.loader,
        { loader: 'css-loader', options: { importLoaders: 1, sourceMap: true } },
        { loader: 'postcss-loader' }
    ] :

    // Production
    [ExtractCssChunks.loader,
        { loader: 'css-loader', options: { importLoaders: 1 } },
        { loader: 'postcss-loader' }
    ]
},

In the plugins

const ExtractCssChunks = require('extract-css-chunks-webpack-plugin');
...
new ExtractCssChunks({ filename: 'styles/site.css' }),

Another option is to try the webpack file cache
although this only seems to make much of a difference when using style-loader instead of extract css chunks

  cache: {
    type: 'filesystem',
  },

I'd be tempted to leave that off though as sometimes the cache needs to be manually deleted and it seems to slow things down a bit with extract css chunks

Edit
Maybe there's some downside to using ExtractCssChunks I'm not aware of (since I'm still new to frontend vuejs) but rebuilds are super duper lightning fast now.

@khalwat
Copy link
Author

khalwat commented Jan 26, 2021

@grbd it's likely due to the sourcemaps -- disable them:

nystudio107/tailwind-css-performance@259a998

...and disable URL resolving since PostCSS will do that for us

@Hecatron
Copy link

By disabling source maps

Style Loader

Initial build: 18 vs 12 seconds
webpack dev server rebuild: 3.2 vs 1.6 seconds

ExtractCssChunks

Initial build: 11 vs 8 seconds
webpack dev server rebuild: 300ms regardless

I'd imagine on a much larger site ExtractCssChunks may have a much bigger speed impact
since anything not unique to a specific vue control is being offloaded into a separate css file which is why the rebuild speed is so quick since it doesn't have to rebuild the site.css file, just the stuff that's unique to the vue control

@khalwat
Copy link
Author

khalwat commented Jan 26, 2021

I think ExtractCssChunks hasn't been updated to work with webpack 5... Isn't ExtractCssChunks merged with MiniCssExtractPlugin?

Also the cache settings you mention should only apply to production builds... by default, if webpack-dev-server is running, the cache is set to memory

@Hecatron
Copy link

Like I said I would ignore the file cache setting if using the css extraction plugins
It only has an impact if using style-loader and sometimes glitches the cache which needs to be deleted
It just speeds up the initial first build in some cases.

I think for extractcsschunks there may be issues with webpack 5 if setting it up to generate multiple css files
In my case I'm using it to just generate a single file which is why it probably works

I just had a quick go with mini-css-extract-plugin
it seems to be just as fast as extractcsschunks so I'll use that from now on

@khalwat
Copy link
Author

khalwat commented Jan 26, 2021

@grbd inspired by your timings, I did some test last night with MiniCssExtractPlugin but unfortunately I didn't see any speed gains. This is using webpack 5 -- I'm not sure what the differences in our setups might be, I was hoping I'd see similar results, but it wasn't any quicker than using style-loader for me.

@Hecatron
Copy link

Hecatron commented Jan 26, 2021

I've put up an example here
https://github.com/Hecatron/Hecatron.WebTemplates/tree/master/frontends/tailwind

under clientjs\webpack\site.rules.js
switching MiniCssExtractPlugin.loader with 'style-loader'
results in a significant speed decrease on my machine

One thing I tend to do is split the webpack build into two parts, vendor and site
with vendor being the stuff from node_modules and site being the local site js
although in this case tailwind is part of the site bundle due to the fact it's needed by the site related code.
So the separate bundling shouldn't impact the speed in so far as tailwind

# Do a yarn V2 install
yarn install

# Build webpack files
yarn build:dev:vendor
yarn build:dev:site

# run webpack dev server
yarn build:dev:server

@khalwat
Copy link
Author

khalwat commented Feb 27, 2021

Man sorry this is about 2 months late @adamwathan & @RobinMalfait but I've verified the changes made, and everything works great with "CSS splitting" again (which I know you already know... just finally getting around to verifying it).

Updated the article: Speeding Up Tailwind CSS Builds

This technique is used in this real-world project for the devMode.fm website in case anyone wants to try out a live example.

@hiepxanh
Copy link

hiepxanh commented Mar 1, 2021

@khalwat
image

There is an improvement to the speed to my system after using your article:
image

increment change from 57 to 52 (first time), 24 to 21 (second time)

image
image

total build reduce from 49 to 35

@et-hh
Copy link

et-hh commented Jul 7, 2021

对于无法升级到最新版本tailwind的可以通过webpack插件解决这个问题:
https://juejin.cn/post/6956424977184718856

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