tailwindlabs / tailwindcss-intellisense

Intelligent Tailwind CSS tooling for Visual Studio Code
2.75k stars 183 forks source link

JavaScript heap out of memory when using tailwindCSS.experimental.classRegex #893

Closed mfb-davidmay closed 5 months ago

mfb-davidmay commented 6 months ago

What version of VS Code are you using?

Version: 1.85.1 (user setup)

What version of Tailwind CSS IntelliSense are you using?

v0.10.4 also tried v0.11.36

What version of Tailwind CSS are you using?

^3.3.6

What package manager are you using?

yarn berry v3.7.0

What operating system are you using?

Windows 10

Tailwind config

import { withTV } from 'tailwind-variants/transformer';

/** @type {import('tailwindcss').Config} */
export default withTV({
  content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
  theme: {
    extend: {
      colors: {
        mfb: {
          brand: {
            100: 'rgba(var(--colors-mfb-primary-100-value), <alpha-value>)',
            200: 'rgba(var(--colors-mfb-primary-200-value), <alpha-value>)',
            300: 'rgba(var(--colors-mfb-primary-300-value), <alpha-value>)',
            400: 'rgba(var(--colors-mfb-primary-400-value), <alpha-value>)',
            500: 'rgba(var(--colors-mfb-primary-500-value), <alpha-value>)',
            600: 'rgba(var(--colors-mfb-primary-600-value), <alpha-value>)',
            700: 'rgba(var(--colors-mfb-primary-700-value), <alpha-value>)',
            800: 'rgba(var(--colors-mfb-primary-800-value), <alpha-value>)',
            900: 'rgba(var(--colors-mfb-primary-900-value), <alpha-value>)',
          },
          secondary: {
            100: 'rgba(var(--colors-mfb-secondary-100-value), <alpha-value>)',
            200: 'rgba(var(--colors-mfb-secondary-200-value), <alpha-value>)',
            300: 'rgba(var(--colors-mfb-secondary-300-value), <alpha-value>)',
            400: 'rgba(var(--colors-mfb-secondary-400-value), <alpha-value>)',
            500: 'rgba(var(--colors-mfb-secondary-500-value), <alpha-value>)',
            600: 'rgba(var(--colors-mfb-secondary-600-value), <alpha-value>)',
            700: 'rgba(var(--colors-mfb-secondary-700-value), <alpha-value>)',
            800: 'rgba(var(--colors-mfb-secondary-800-value), <alpha-value>)',
            900: 'rgba(var(--colors-mfb-secondary-900-value), <alpha-value>)',
          },
          support: {
            100: 'rgba(var(--colors-mfb-support-100-value), <alpha-value>)',
            200: 'rgba(var(--colors-mfb-support-200-value), <alpha-value>)',
            300: 'rgba(var(--colors-mfb-support-300-value), <alpha-value>)',
            400: 'rgba(var(--colors-mfb-support-400-value), <alpha-value>)',
            500: 'rgba(var(--colors-mfb-support-500-value), <alpha-value>)',
            600: 'rgba(var(--colors-mfb-support-600-value), <alpha-value>)',
            700: 'rgba(var(--colors-mfb-support-700-value), <alpha-value>)',
            800: 'rgba(var(--colors-mfb-support-800-value), <alpha-value>)',
            900: 'rgba(var(--colors-mfb-support-900-value), <alpha-value>)',
          },
          neutral: {
            100: 'rgba(var(--colors-mfb-neutral-100-value), <alpha-value>)',
            200: 'rgba(var(--colors-mfb-neutral-200-value), <alpha-value>)',
            300: 'rgba(var(--colors-mfb-neutral-300-value), <alpha-value>)',
            400: 'rgba(var(--colors-mfb-neutral-400-value), <alpha-value>)',
            500: 'rgba(var(--colors-mfb-neutral-500-value), <alpha-value>)',
            600: 'rgba(var(--colors-mfb-neutral-600-value), <alpha-value>)',
            700: 'rgba(var(--colors-mfb-neutral-700-value), <alpha-value>)',
            800: 'rgba(var(--colors-mfb-neutral-800-value), <alpha-value>)',
            900: 'rgba(var(--colors-mfb-neutral-900-value), <alpha-value>)',
          }
        },
        bb: {
          brand: {
            100: 'rgba(var(--colors-bb-primary-100-value), <alpha-value>)',
            200: 'rgba(var(--colors-bb-primary-200-value), <alpha-value>)',
            300: 'rgba(var(--colors-bb-primary-300-value), <alpha-value>)',
            400: 'rgba(var(--colors-bb-primary-400-value), <alpha-value>)',
            500: 'rgba(var(--colors-bb-primary-500-value), <alpha-value>)',
            600: 'rgba(var(--colors-bb-primary-600-value), <alpha-value>)',
            700: 'rgba(var(--colors-bb-primary-700-value), <alpha-value>)',
            800: 'rgba(var(--colors-bb-primary-800-value), <alpha-value>)',
            900: 'rgba(var(--colors-bb-primary-900-value), <alpha-value>)',
          },
          secondary: {
            100: 'rgba(var(--colors-bb-secondary-100-value), <alpha-value>)',
            200: 'rgba(var(--colors-bb-secondary-200-value), <alpha-value>)',
            300: 'rgba(var(--colors-bb-secondary-300-value), <alpha-value>)',
            400: 'rgba(var(--colors-bb-secondary-400-value), <alpha-value>)',
            500: 'rgba(var(--colors-bb-secondary-500-value), <alpha-value>)',
            600: 'rgba(var(--colors-bb-secondary-600-value), <alpha-value>)',
            700: 'rgba(var(--colors-bb-secondary-700-value), <alpha-value>)',
            800: 'rgba(var(--colors-bb-secondary-800-value), <alpha-value>)',
            900: 'rgba(var(--colors-bb-secondary-900-value), <alpha-value>)',
          },
          support: {
            100: 'rgba(var(--colors-bb-support-100-value), <alpha-value>)',
            200: 'rgba(var(--colors-bb-support-200-value), <alpha-value>)',
            300: 'rgba(var(--colors-bb-support-300-value), <alpha-value>)',
            400: 'rgba(var(--colors-bb-support-400-value), <alpha-value>)',
            500: 'rgba(var(--colors-bb-support-500-value), <alpha-value>)',
            600: 'rgba(var(--colors-bb-support-600-value), <alpha-value>)',
            700: 'rgba(var(--colors-bb-support-700-value), <alpha-value>)',
            800: 'rgba(var(--colors-bb-support-800-value), <alpha-value>)',
            900: 'rgba(var(--colors-bb-support-900-value), <alpha-value>)',
          },
          neutral: {
            100: 'rgba(var(--colors-bb-neutral-100-value), <alpha-value>)',
            200: 'rgba(var(--colors-bb-neutral-200-value), <alpha-value>)',
            300: 'rgba(var(--colors-bb-neutral-300-value), <alpha-value>)',
            400: 'rgba(var(--colors-bb-neutral-400-value), <alpha-value>)',
            500: 'rgba(var(--colors-bb-neutral-500-value), <alpha-value>)',
            600: 'rgba(var(--colors-bb-neutral-600-value), <alpha-value>)',
            700: 'rgba(var(--colors-bb-neutral-700-value), <alpha-value>)',
            800: 'rgba(var(--colors-bb-neutral-800-value), <alpha-value>)',
            900: 'rgba(var(--colors-bb-neutral-900-value), <alpha-value>)',
          }
        },
        fs: {
          brand: {
            100: 'rgba(var(--colors-fs-primary-100-value), <alpha-value>)',
            200: 'rgba(var(--colors-fs-primary-200-value), <alpha-value>)',
            300: 'rgba(var(--colors-fs-primary-300-value), <alpha-value>)',
            400: 'rgba(var(--colors-fs-primary-400-value), <alpha-value>)',
            500: 'rgba(var(--colors-fs-primary-500-value), <alpha-value>)',
            600: 'rgba(var(--colors-fs-primary-600-value), <alpha-value>)',
            700: 'rgba(var(--colors-fs-primary-700-value), <alpha-value>)',
            800: 'rgba(var(--colors-fs-primary-800-value), <alpha-value>)',
            900: 'rgba(var(--colors-fs-primary-900-value), <alpha-value>)',
          },
          secondary: {
            100: 'rgba(var(--colors-fs-secondary-100-value), <alpha-value>)',
            200: 'rgba(var(--colors-fs-secondary-200-value), <alpha-value>)',
            300: 'rgba(var(--colors-fs-secondary-300-value), <alpha-value>)',
            400: 'rgba(var(--colors-fs-secondary-400-value), <alpha-value>)',
            500: 'rgba(var(--colors-fs-secondary-500-value), <alpha-value>)',
            600: 'rgba(var(--colors-fs-secondary-600-value), <alpha-value>)',
            700: 'rgba(var(--colors-fs-secondary-700-value), <alpha-value>)',
            800: 'rgba(var(--colors-fs-secondary-800-value), <alpha-value>)',
            900: 'rgba(var(--colors-fs-secondary-900-value), <alpha-value>)',
          },
          support: {
            100: 'rgba(var(--colors-fs-support-100-value), <alpha-value>)',
            200: 'rgba(var(--colors-fs-support-200-value), <alpha-value>)',
            300: 'rgba(var(--colors-fs-support-300-value), <alpha-value>)',
            400: 'rgba(var(--colors-fs-support-400-value), <alpha-value>)',
            500: 'rgba(var(--colors-fs-support-500-value), <alpha-value>)',
            600: 'rgba(var(--colors-fs-support-600-value), <alpha-value>)',
            700: 'rgba(var(--colors-fs-support-700-value), <alpha-value>)',
            800: 'rgba(var(--colors-fs-support-800-value), <alpha-value>)',
            900: 'rgba(var(--colors-fs-support-900-value), <alpha-value>)',
          },
          neutral: {
            100: 'rgba(var(--colors-fs-neutral-100-value), <alpha-value>)',
            200: 'rgba(var(--colors-fs-neutral-200-value), <alpha-value>)',
            300: 'rgba(var(--colors-fs-neutral-300-value), <alpha-value>)',
            400: 'rgba(var(--colors-fs-neutral-400-value), <alpha-value>)',
            500: 'rgba(var(--colors-fs-neutral-500-value), <alpha-value>)',
            600: 'rgba(var(--colors-fs-neutral-600-value), <alpha-value>)',
            700: 'rgba(var(--colors-fs-neutral-700-value), <alpha-value>)',
            800: 'rgba(var(--colors-fs-neutral-800-value), <alpha-value>)',
            900: 'rgba(var(--colors-fs-neutral-900-value), <alpha-value>)',
          }
        }
      },
    },
  },
  plugins: [],
  corePlugins: {
    // preflight: true,
  },
  prefix: 'tw-'
});

VS Code settings

{
  "files.associations": {
    "plyconfig.json": "jsonc"
  },

  // Yarn 3.8+ Setup
  "search.exclude": {
    "**/.yarn": true,
    "**/.pnp.*": true
  },
  "eslint.nodePath": ".yarn/sdks",

  // Typescript Setup
  // "typescript.tsdk": ".yarn/sdks/typescript/lib",
  "typescript.tsdk": "node_modules\\typescript\\lib",
  "typescript.enablePromptUseWorkspaceTsdk": true,

  // Tailwind Setup
  "css.customData": [".vscode/tailwind.json"],
  "scss.lint.unknownAtRules": "ignore",
  "tailwindCSS.experimental.classRegex": [
    ["(?<=tv\\()[^]*?(?=}\\))", "(?<=['\"])[\\w\\-\\s:\\[\\]\\.]*?(?=['\"])"]
  ],
  "editor.quickSuggestions": {
    "strings": "on"
  }
}

Reproduction URL

https://github.com/mfb-davidmay/tw-intellisense-timeout/blob/main/src/components/Button/index.tsx

Describe your issue

When attempting to load Intellisense for the aforementioned file, Visual Studio will consume large amounts of memory and CPU to the point that the Tailwind Extension runs out of memory.

I've pinpointed the issue to be an experimental regex that I've added which was recommended by the author of the tailwind-variants library. The regex shown here is my attempt to improve the one suggested to simplify the matching.

I believe the memory exception might be because there's too many classes matched within the container? but unsure as the logging of the extension isn't telling me much.

Removing the classRegex returns the extension back to normal, and I can use it with standard out of the box support. Just wanting some help to understand what I can do to enable support for the tv(...) functions in my components.

Here's the extension output.

[Global] Creating projects: [{"folder":"c:/dev/Experiments/onionui","configPath":"c:/dev/Experiments/onionui/tailwind.config.js","isUserConfigured":false,"documentSelector":[{"pattern":"c:/dev/Experiments/onionui/tailwind.config.js","priority":0},{"pattern":"c:/dev/Experiments/onionui/**","priority":3}]}]
[Global] Adding watch patterns: c:/dev/Experiments/onionui/tailwind.config.js, c:/dev/Experiments/onionui, c:/dev/Experiments/onionui/tailwind.config.js, c:/dev/Experiments/onionui
[tailwind.config.js] Initializing...
[tailwind.config.js] Loaded Tailwind CSS config file: c:/dev/Experiments/onionui/tailwind.config.js
[tailwind.config.js] Loaded postcss v8.4.32: c:\dev\Experiments\onionui\node_modules\postcss
[tailwind.config.js] Loaded tailwindcss v3.4.0: c:\dev\Experiments\onionui\node_modules\tailwindcss
[tailwind.config.js] Building...

<--- Last few GCs --->

[24556:0000128C00328000]    41079 ms: Mark-Compact 3963.9 (4094.9) -> 3961.6 (4094.9) MB, 2705.69 / 0.00 ms  (average mu = 0.116, current mu = 0.001) allocation failure; scavenge might not succeed
[24556:0000128C00328000]    43728 ms: Mark-Compact 3964.2 (4095.4) -> 3962.1 (4095.4) MB, 2646.47 / 0.00 ms  (average mu = 0.062, current mu = 0.001) allocation failure; scavenge might not succeed

<--- JS stacktrace --->

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
 1: 00007FF7A1873376 node::SetTracingController+80086
 2: 00007FF7A1873603 node::OnFatalError+595
 3: 00007FF7A4562D33 v8::Function::NewInstance+835
 4: 00007FF7A4562CC3 v8::Function::NewInstance+723
 5: 00007FF7A462B6E7 v8::CppHeap::CollectGarbageInYoungGenerationForTesting+74791
 6: 00007FF7A06433EE v8::CppHeap::Terminate+89774
 7: 00007FF7A064000F v8::CppHeap::Terminate+76495
 8: 00007FF7A3171A99 v8::MicrotasksScope::PerformCheckpoint+541961
 9: 00007FF7A316D4DD v8::MicrotasksScope::PerformCheckpoint+524109
10: 00007FF7A315D8D7 v8::MicrotasksScope::PerformCheckpoint+459591
11: 00007FF7A33689C3 v8::MicrotasksScope::PerformCheckpoint+2602035
12: 00007FF7A36860BA v8_inspector::String16::String16+2535018
13: 00007FF7E038F8CA 
[Info  - 12:34:52 PM] Connection to server got closed. Server will restart.
mfb-davidmay commented 6 months ago

Some additional information from my machine that might help.

Version: 1.85.1 (user setup)
Commit: 0ee08df0cf4527e40edc9aa28f4b5bd38bbff2b2
Date: 2023-12-13T09:49:37.021Z
Electron: 25.9.7
ElectronBuildId: 25551756
Chromium: 114.0.5735.289
Node.js: 18.15.0
V8: 11.4.183.29-electron.0
OS: Windows_NT x64 10.0.19045
mfb-davidmay commented 6 months ago

Just following up with some notes from tinkering around with the regex container, I believe the issue lies with the amount of data captured with the container regex (?<=tv\\()[^]*?(?=}\\)) which is is a capture group of 3,558 characters.

When I change the capture group to something much smaller, and general (it's basically matching any, and every string in the file now) the extension is working as expected, and not timing out anymore. This change results in container capture groups of 200-400 characters.

Here's what I changed it to.

{
  "tailwindCSS.experimental.classRegex": [
    ["([\"'`][^\"'`]*.*?[\"'`])", "[\"'`]([^\"'`]*).*?[\"'`]"]
  ]
}
mfb-davidmay commented 6 months ago

Ideally, having the ability to specify (?<=tv\([^]*?)([\"'`][^\"'`]*.*?[\"'`])(?=[^]*?}\);?$) which contains both a positive lookbehind, and positive lookahead to scope matches within the tv function would be the ultimate setup. But I think the regex library you're using doesn't support these regex features, or maybe I'm doing something wrong because intellisense is turned off with this configuration.

I experimented with the becke-ch--regex--s0-0-v1--base--pl--lib using this regex and it returns matches with gm flags so unsure what's going on.

bitabs commented 6 months ago

I had the same issue. I was surprised considering its a Macbook pro 16" M3 Max with 64GB memory 🤦🏻

mfb-davidmay commented 6 months ago

I had the same issue. I was surprised considering its a Macbook pro 16" M3 Max with 64GB memory 🤦🏻

Ha, not surprised tbh. There's a lot of code that gets executed with the container match regex.

"([\"'`][^\"'`]*.*?[\"'`])", "[\"'`]([^\"'`]*).*?[\"'`]" while overly general in terms of what it matches in a file will at least stop turning your laptop into a portable heater.

thecrypticace commented 6 months ago

The problem is with (?<=['\"])[\\w\\-\\s:\\[\\]\\.]*?(?=['\"]). It matches a zero-length string at a specific position.

Here, a very simplified, non-escaped form: /(?<=')\w*?(?=')/ — this regex basically says match anything zero characters or more between single quotes but not the quotes themselves. Given the string let x = '' it'll match the position between the quotes. And because it's zero length, the exec call in becke-ch--regex--s0-0-v1--base--pl--lib appears to never advance an internal pointer or something resulting in an infinite loop which ends up allocating more and more memory.

I have a fix for this but I'm not yet sure if it's the right fix. I need to do some more testing today.

thecrypticace commented 6 months ago

Turns out my fix wasn't that good so I ended up rewriting things, discovered new JS APIs in the process, and rewrote things some more.

I have a little bit more testing to do but I think things are in a better spot for this now. Should have at least a fix in the pre-release version of the extension tomorrow.

thecrypticace commented 6 months ago

Was hoping to get a pre-release out today but I've run into something odd — I think it's a v8 bug but I'm not 100% sure yet.

thecrypticace commented 5 months ago

I just merged #897 which fixes this. It'll be available in the pre-release version of the extension. If you could give it a test and report back to me that would be 💯

dgknca commented 2 months ago

I'm using pre-release version but I'm suffering from the same issue.

thecrypticace commented 1 month ago

@dgknca can you open a separate issue with a reproduction that I can take a look at?

dgknca commented 1 month ago

@dgknca can you open a separate issue with a reproduction that I can take a look at?

I was using this regex https://www.tailwind-variants.org/docs/getting-started#intellisense-setup-optional

{
  "tailwindCSS.experimental.classRegex": [
    ["tv\\((([^()]*|\\([^()]*\\))*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
  ]
}

replaced it with:

["([\"'`][^\"'`]*.*?[\"'`])", "[\"'`]([^\"'`]*).*?[\"'`]"]

works fine now.