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

Config hashing consumes 30% of total processsing time #14229

Closed
marvinhagemeister opened this issue Aug 21, 2024 · 4 comments
Closed

Config hashing consumes 30% of total processsing time #14229

marvinhagemeister opened this issue Aug 21, 2024 · 4 comments
Assignees

Comments

@marvinhagemeister
Copy link

marvinhagemeister commented Aug 21, 2024

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?

  • N/A

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.

export default function hashConfig(config) {
return hash(config, { ignoreUnknown: true })
}

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.

@thecrypticace thecrypticace self-assigned this Aug 21, 2024
@marvinhagemeister
Copy link
Author

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
Copy link

chenjiahan commented Nov 1, 2024

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

  • hash() 3.12s
Screenshot 2024-11-01 at 12 06 54
  • JSON.stringify() 0.41s
Screenshot 2024-11-01 at 12 09 11

@chenjiahan
Copy link

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:

  • If the user calls the plugin via tailwindcss() and does not pass in any parameters, tailwindcss will cache based on the path of tailwind.config.ts to avoid repeated hash() calls.

  • If the user calls the plugin via tailwindcss(configObject), tailwindcss will not cache this, causing each module to trigger hash() once, resulting in build performance degradation.

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

@adamwathan
Copy link
Member

Hey thanks for this! We dug around in the code a bit a couple months ago to try and remember why we had to use this approach and if I remember right, it's because you can use functions and stuff in Tailwind config files and we need to be able to compare the identity of those functions as part of our caching strategy, which we lose with JSON.stringify.

Thankfully v4.0 doesn't have any of these same problems because of the huge architectural changes we made in the rewrite 🙌🏻 So going to close this one as resolved by the v4 release. Thanks again man!

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

4 participants