tailwindlabs / tailwindcss

A utility-first CSS framework for rapid UI development.
https://tailwindcss.com/
MIT License
83.4k stars 4.22k forks source link

Config hashing consumes 30% of total processsing time #14229

Open marvinhagemeister opened 3 months ago

marvinhagemeister commented 3 months ago

What version of Tailwind CSS are you using?

v3.4.10

What build tool (or framework if it abstracts the build tool) are you using?

postcss 8.4.41

What version of Node.js are you using?

v22.6.0 (also Deno 1.46.0-rc.3+dd8a9c5)

What browser are you using?

What operating system are you using?

macOS

Reproduction URL

https://github.com/marvinhagemeister/tw-config-hash-repro

Steps to reproduce:

  1. Clone https://github.com/marvinhagemeister/tw-config-hash-repro
  2. Run npm i
  3. Run node foo.mjs -> observe time printed to console
  4. Edit node_modules/tailwindcss/lib/util/hashConfig.js and replace the contents of the hashing function with JSON.stringify(config)
     function hashConfig(config) {
    -   return (0, _objecthash.default)(config, {
    -     ignoreUnknown: true
    -   });
    +   return JSON.stringify(config);
     }
  5. Run node foo.mjs again -> observe much faster time

Describe your issue

I noticed that hashing the tailwind configuration takes up roughly a third of the time in my project. That seemed a bit much. It looks like the object-hash package isn't the fastest.

https://github.com/tailwindlabs/tailwindcss/blob/f07dbff2a7f78fd75c53c6cfe01b58b6c0419f22/src/util/hashConfig.js#L3-L5

Given that the config is mostly json, switching to JSON.stringify would be much faster. If we do want to treat some values in a special way when the config isn't pure json, we could pass a custom replacer function to JSON.stringify for those.

Here is a quick diff:

  function hashConfig(config) {
-   return hash(config, { ignoreUnknown: true });
+   return JSON.stringify(config);
  }

Before 84ms:

Screenshot 2024-08-21 at 13 02 06

After 6.33ms:

Screenshot 2024-08-21 at 13 02 13

Whilst this is just a synthetic example repository, I can reproduce the same results in real world projects of mine.

Project Before After
Reproduction repo 84ms 6.33ms
Project A 207ms 19ms
Project B 301ms 24ms

It might not seem like much in the grander scheme of thing, but in my projects where I do tailwind processing during development this makes HMR updates feel a tiny bit snappier. There the time hashing the configuration easily takes up 30-35% of the total tailwind processing time. Would be nice if that could be reduced.

marvinhagemeister commented 3 months ago

EDIT: Updated the numbers gathered from the repro code. I accidentally left in the ones from another project which didn't make it match up with the screenshots. Apologies for that I didn't notice that error when filing the issue.

chenjiahan commented 3 weeks ago

I tried to replace hash() with JSON.stringify() in a real project (Rspack + postcss-loader + tailwindcss) and this makes tailwindcss several times faster:

Screenshot 2024-11-01 at 12 06 54 Screenshot 2024-11-01 at 12 09 11
chenjiahan commented 3 weeks ago

I found that the performance of hash() depends on how the user uses the tailwindcss postcss plugin. Tailwindcss only caches hash() internally for some situations:

See: https://github.com/tailwindlabs/tailwindcss/blob/3.4/src/lib/setupTrackingContext.js#L31-L71