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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance improvements + memory leak fix #3032

merged 7 commits into from Dec 11, 2020


Copy link

@RobinMalfait RobinMalfait commented Dec 8, 2020

This PR will improve some of the performance issues people have seen. But even more importantly it also fixes a bad memory leak where if you use tailwindcss in a running process (like a webpack watch), it could happen that once in a while you get a "JavaScript heap out of memory" error and everything crashes. This has been fixed, and also improves a bit of performance.

Another issue that has been pointed out in #2544 and #2820 is that when you split up files, that you get an error that some utilities don't exist when using @apply. While splitting up is not a perfect solution, this PR will at least fix that issue.

Let's go in a bit more detail here:

Broken @apply

When you split up files (splitting up @tailwind base;, @tailwind utilities; and @tailwind components;), and one of your files contains something like this:

/* my-base.css */
@tailwind base;

body { @apply bg-gray-100; }

Then we made the assumption that if we found a @tailwind rule, that we don't have to re-compute all the utilities so that you can apply them. However, in this case only the base is included in the full css. Therefore you will get an error that bg-gray-100 could not be found.

This PR fixes that, by looking if it contains a @tailwind utilities; rule. If it does, then we don't have to compute the all the utilities again because it already exists. If it doesn't exist then we will generate the tree so that you can apply those utilities.

One of the main reasons for this is for example in Vue you can define css per component:

.something { @apply text-xl }

But it would be very annoying to write the following code in every single component:

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

.something { @apply text-xl }

Memory leak

We have an optimization in the codebase with useMemo (not this is not React, yes I used the same name 馃檲) so that we can cache certain calculations if we already did a calculation for the same key. However, every time the config changes (in a running process) we start re-calculating things. For example, imagine that you enabled darkMode: 'class' we have to make sure that we create new utilities for darkMode. However, if you remove darkMode again we didn't cleanup all those things.

Long story short, in this PR we also make sure to clear all the caches when the config changes.

Why splitting up is not that ideal, however, it's fast with webpack

In development the css output file of Tailwind can be easily +3 MB. Just to be clear, this is NOT an issue in production, purging is amazing!

The main reason for this is the @tailwind utilities; being huge.

Some people started splitting up each part of @tailwind ... in separate chunks. This works, because @tailwind utilities; won't change as long as the config file doesn't change. This means that webpack can cache this file perfectly. When you have a separate file for your css, webpack only has to worry about that part.

However there is a catch...

In Tailwind we have the concept of @layer's ( This means that when you use something like:

@tailwind base;
@tailwind components; /* (1) will be inserted in this spot */
@tailwind utilities;

@layer components { /* (1) this */
  .my-component { @apply text-xl; }

This means that we need a single css file so that we can insert custom css in the correct spot.
This won't work anymore once you start splitting up each @tailwind at rule in separate files, because now everything is separated. This can lead to incorrect behaviour and specificity because the order matters.

Fixes: #2986, #3024, #2544 and #2820

Copy link

codecov-io commented Dec 9, 2020

Codecov Report

Merging #3032 (78b17d2) into master (f12458a) will increase coverage by 0.00%.
The diff coverage is 95.55%.

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #3032   +/-   ##
  Coverage   93.28%   93.28%           
  Files         177      178    +1     
  Lines        1801     1831   +30     
  Branches      318      324    +6     
+ Hits         1680     1708   +28     
- Misses        104      105    +1     
- Partials       17       18    +1     
Impacted Files Coverage 螖
src/util/disposables.js 83.33% <83.33%> (酶)
src/lib/substituteClassApplyAtRules.js 95.12% <100.00%> (+0.41%) 猬嗭笍
src/processTailwindFeatures.js 94.11% <100.00%> (+0.36%) 猬嗭笍
src/util/useMemo.js 100.00% <100.00%> (酶)

Continue to review full report at Codecov.

Legend - Click here to learn more
螖 = absolute <relative> (impact), 酶 = not affected, ? = missing data
Powered by Codecov. Last update f12458a...78b17d2. Read the comment docs.

@RobinMalfait RobinMalfait marked this pull request as ready for review December 11, 2020 13:22
@RobinMalfait RobinMalfait merged commit eac11cf into master Dec 11, 2020
@RobinMalfait RobinMalfait deleted the performance-improvements branch December 11, 2020 14:03
Copy link

I've upgraded to v2.0.2 and I'm still running into heap memory crashes...

<i> [webpack-dev-middleware] Compiling...
<i> [webpack-dev-middleware] wait until bundle finished: /dist/manifest.json
<i> [webpack-dev-middleware] wait until bundle finished: /dist/manifest.json
<i> [webpack-dev-middleware] assets by info 7.26 MiB [immutable]
<i> assets by path *.js 7.26 MiB
<i> asset 7.26 MiB [emitted] [immutable] [hmr] (name: site)
<i> asset 323 bytes [emitted] [immutable] [hmr] (name: app)
<i> assets by path *.json 55 bytes
<i> asset 28 bytes [emitted] [immutable] [hmr]
<i> asset 27 bytes [emitted] [immutable] [hmr]
<i> assets by path js/*.js 19.2 MiB
<i> asset js/app.js 10 MiB [emitted] (name: app)
<i> asset js/site.js 9.16 MiB [emitted] (name: site)
<i> asset manifest.json 66 bytes [emitted]
<i> Entrypoint app 10 MiB = js/app.js 10 MiB 323 bytes
<i> Entrypoint site 16.4 MiB = js/site.js 9.16 MiB 7.26 MiB
<i> cached modules 9.35 MiB [cached] 1010 modules
<i> runtime modules 50.1 KiB 27 modules
<i> ../node_modules/css-loader/dist/cjs.js??clonedRuleSet-2[0].rules[0].use[1]!../node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-2[0].rules[0].use[2]!../node_modules/less-loader/dist/cjs.js!./css/site/main.less 7.26 MiB [built] [code generated]
<i> webpack 5.9.0 compiled successfully in 33051 ms
<i> [webpack-dev-middleware] Compiled successfully.
<i> [webpack-dev-middleware] Compiling...
<--- Last few GCs --->
[42:0x55fe0761f200] 9293250 ms: Mark-sweep 2036.2 (2052.8) -> 2036.2 (2054.0) MB, 2546.2 / 0.0 ms (average mu = 0.107, current mu = 0.004) allocation failure scavenge might not succeed
<--- JS stacktrace --->
==== JS stack trace =========================================
0: ExitFrame [pc: 0x55fe04745939]
1: StubFrame [pc: 0x55fe0474680d]
Security context: 0x2c4a5c2808d1 <JSObject>
2: /* anonymous */ [0x2c4a5c2bfd01] [/app/node_modules/webpack/node_modules/acorn/dist/acorn.js:~4979] [pc=0x2c338e624607](this=0x21f0ec15f5d1 <Parser map = 0x3fb77cf777a9>,34)
3: /* anonymous */ [0x2c4a5c2be421] [/app/node_modules/webpack/node_modules/acorn/dist/acorn.js:4772] [bytecode=0x22e8c0ca0f91 offset=775...
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
[nodemon] app crashed - waiting for file changes before starting...

Is there anything I should have to change in my build process to take advantage of the fixes in this PR @RobinMalfait?

Copy link
Contributor Author

@jalendport Hey! Can you create a reproduction case for this? Also are you sure that you are on the latest version?

Copy link

@RobinMalfait I'm seeing the same thing as @jalendport. Version of tailwind: 2.0.2.

鉅 Re-building development bundle

<--- Last few GCs --->

[17037:0x108008000]  1754081 ms: Scavenge 1943.9 (2010.0) -> 1943.9 (2011.0) MB, 4.6 / 0.0 ms  (average mu = 0.378, current mu = 0.466) allocation failure
[17037:0x108008000]  1754153 ms: Scavenge 1944.8 (2011.0) -> 1944.8 (2012.0) MB, 5.0 / 0.0 ms  (average mu = 0.378, current mu = 0.466) allocation failure
[17037:0x108008000]  1754205 ms: Scavenge 1954.0 (2020.8) -> 1952.8 (2019.8) MB, 4.0 / 0.0 ms  (average mu = 0.378, current mu = 0.466) allocation failure

<--- JS stacktrace --->

==== JS stack trace =========================================

    0: ExitFrame [pc: 0x100ea9f02]
Security context: 0x09f6cea1a2f1 <JSObject>
    1: /* anonymous */(aka /* anonymous */) [0x9f6b0d52409] [/Users/abe/Projects/generalpiston/padfever/content/node_modules/ink/node_modules/slice-ansi/index.js:~50] [pc=0x233a4fc75617](this=0x09f6a16804d1 <undefined>,0x09f61778d5c9 <String[118]:                                                                                                                    ...

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
 1: 0x100075bd5 node::Abort() [/Users/abe/.nodenv/versions/12.4.0/bin/node]
 2: 0x100076316 node::errors::TryCatchScope::~TryCatchScope() [/Users/abe/.nodenv/versions/12.4.0/bin/node]
 3: 0x1001697d7 v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/Users/abe/.nodenv/versions/12.4.0/bin/node]
 4: 0x10016976c v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/Users/abe/.nodenv/versions/12.4.0/bin/node]
 5: 0x1005480d5 v8::internal::Heap::FatalProcessOutOfMemory(char const*) [/Users/abe/.nodenv/versions/12.4.0/bin/node]
 6: 0x1005491c3 v8::internal::Heap::CheckIneffectiveMarkCompact(unsigned long, double) [/Users/abe/.nodenv/versions/12.4.0/bin/node]
 7: 0x100546bc3 v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [/Users/abe/.nodenv/versions/12.4.0/bin/node]
 8: 0x10054487f v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/Users/abe/.nodenv/versions/12.4.0/bin/node]
 9: 0x1005435b1 v8::internal::Heap::HandleGCRequest() [/Users/abe/.nodenv/versions/12.4.0/bin/node]
10: 0x1004f59be v8::internal::StackGuard::HandleInterrupts() [/Users/abe/.nodenv/versions/12.4.0/bin/node]
11: 0x1007ce861 v8::internal::Runtime_StackGuard(int, unsigned long*, v8::internal::Isolate*) [/Users/abe/.nodenv/versions/12.4.0/bin/node]
12: 0x100ea9f02 Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit [/Users/abe/.nodenv/versions/12.4.0/bin/node]
13: 0x233a4fc75617

In my case, it's while I'm using Gatsby development mode. Underneath they're using webpack I believe.

Copy link

matthewmcvickar commented Dec 19, 2020

Here鈥檚 the error I鈥檓 getting, in case it鈥檚 helpful. Running npx postcss static/css/main.css -o static/css/main-compiled.css -w --verbose --no-map. tailwindcss version 2.0.2.

Processing static/css/main.css...

<--- Last few GCs --->

[90264:0x10284b000]   928955 ms: Scavenge 1369.1 (1423.8) -> 1368.7 (1424.8) MB, 3.9 / 0.0 ms  (average mu = 0.158, current mu = 0.017) allocation failure
[90264:0x10284b000]   930589 ms: Mark-sweep 1369.5 (1424.8) -> 1369.0 (1423.8) MB, 1630.0 / 0.0 ms  (average mu = 0.076, current mu = 0.015) allocation failure scavenge might not succeed
[90264:0x10284b000]   930597 ms: Scavenge 1369.7 (1423.8) -> 1369.3 (1424.8) MB, 3.8 / 0.0 ms  (average mu = 0.076, current mu = 0.015) allocation failure

<--- JS stacktrace --->

==== JS stack trace =========================================

    0: ExitFrame [pc: 0x398b70a4fc7d]
    1: StubFrame [pc: 0x398b70a0fcbc]
Security context: 0x234d0a71d9f1 <JSObject>
    2: handle(aka handle) [0x234dffc728e1] [/Users/matthewmcvickar/Sites/work/foobarbaz/node_modules/tailwindcss/lib/lib/substituteClassApplyAtRules.js:~123] [pc=0x398b71eca8ab](this=0x234dfc1822a1 <null>,0x234dffc72869 <JSFunction css.walkRules.handle.bind.rule (sfi = 0x234d16566689)>,0x234d7b594289 <Node map = 0x234d3255ccf...

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory

Writing Node.js report to file: report.20201218.213844.90264.0.001.json
Node.js report completed
 1: 0x100069fdc node::Abort() [/usr/local/bin/node]
 2: 0x10006a72c node::errors::TryCatchScope::~TryCatchScope() [/usr/local/bin/node]
 3: 0x1001b7127 v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/usr/local/bin/node]
 4: 0x1001b70c4 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/usr/local/bin/node]
 5: 0x1005bdd92 v8::internal::Heap::FatalProcessOutOfMemory(char const*) [/usr/local/bin/node]
 6: 0x1005c02c3 v8::internal::Heap::CheckIneffectiveMarkCompact(unsigned long, double) [/usr/local/bin/node]
 7: 0x1005bc7f8 v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [/usr/local/bin/node]
 8: 0x1005ba9b5 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/usr/local/bin/node]
 9: 0x1005c725c v8::internal::Heap::AllocateRawWithLightRetry(int, v8::internal::AllocationSpace, v8::internal::AllocationAlignment) [/usr/local/bin/node]
10: 0x1005c72df v8::internal::Heap::AllocateRawWithRetryOrFail(int, v8::internal::AllocationSpace, v8::internal::AllocationAlignment) [/usr/local/bin/node]
11: 0x1005964d4 v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationSpace) [/usr/local/bin/node]
12: 0x100848d74 v8::internal::Runtime_AllocateInNewSpace(int, v8::internal::Object**, v8::internal::Isolate*) [/usr/local/bin/node]
13: 0x398b70a4fc7d
14: 0x398b70a0fcbc
15: 0x398b71eca8ab
npm run postcss exited with code SIGABRT

Copy link

Can it be used in 1.9.x version, we need ie11 support

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

Successfully merging this pull request may close these issues.

"JavaScript heap out of memory" problems with PostCSS 7 compatibility build
6 participants