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

Generated CSS that creates warnings can cause infinite loops in generateCodeFrame #15076

Closed
7 tasks done
Spice-King opened this issue Nov 21, 2023 · 0 comments · Fixed by #15093
Closed
7 tasks done

Generated CSS that creates warnings can cause infinite loops in generateCodeFrame #15076

Spice-King opened this issue Nov 21, 2023 · 0 comments · Fixed by #15093
Labels
p4-important Violate documented behavior or significantly improves performance (priority)

Comments

@Spice-King
Copy link

Describe the bug

This took me the better part of two days to pin down enough to make what I'd call a minimal reproduction, though you might disagree with how many dependencies I dragged into this.

Renovate attempted to update Vite from 4.5.0 to 5.0.0 (for the record tried 5.0.2 too) over the weekend, and our CI pipeline on a on-prem GitLab instance dutifully attempted to build everything for a staging deployment for human evaluation. Build job fails with ERROR: Job failed: execution took longer than 1h0m0s seconds when we expect it to take 3 minutes best case with caching, ~8-9 minutes at absolute worst without. Since it just gets stuck with no useful errors/warnings/logs on CI (non-interactive TTY), I pull it down and try a build manually, which seems to get stuck on random files, ours or NPM packages even. And thus starts the madness of finding why.

I've run debugging on Vite while running on my repo it gets stuck inside this loop here.

for (let j = i - range; j <= i + range || end > count; j++) {
if (j < 0 || j >= lines.length) continue

When I was poking at it, j got up to something like 1.4 trillion and change (if that was index into a file, that would be around the 1.3 GB mark). While j <= i + range was false, the other half of the logical or check end > count was always true with nothing to change it in that loop. Running a bisect, I did find a commit Git wants to blame (2b4e793, from #14984) and, from an outsiders view, I'd agree with it.

Eventually, by taking a machete to a copy of our code to hack out random chunks of code/imports, I found that either removing the @tailwind rules and/or removing our most of our Vue components it would go away. More hacking away at the code around just TailwindCSS to try and pin it down. Turned out a custom plugin was adding a CSS property of color-adjust for a utility and it was never updated to print-color-adjust to stop Autoprefixer from throwing warnings (oops!).

Here is where my understanding of this all breaks down. Manually generating the CSS with the rule was fine, though I'd get the expected warning from Autoprefixer. Creating a new utility function (with the plugin gone for testing) for it with a unique name also did not trigger it. Spent a good amount of time bashing my head on it, went home for the day, then came back fresh for another look. Came up with the idea to check where said utility was being used, and saw that there was a use with a variant (print of all things, probably because work was being put into make a given page printable but still have some useful bits of color). That was finally what I could use to create a minimal reproduction. Funnily enough, manually creating that CSS rule with the variant does not trigger it.

This kinda leaves the cause to a generated rule in a media query. I've tested a few of Tailwind's variants, dark mode and responsive design also trigger it, not just print, but hover, focus, active, first, last, odd, even, before and after don't. This helps pin it more and more on media based varants, not pseudo-element or pseudo-class based ones.

Hacking around while looking for answers, I tried to patch Vite locally as a test, swapping continue for a break here. Just found generateCodeFrame in the compiled copy and swapped it.

for (let j = i - range; j <= i + range || end > count; j++) {
if (j < 0 || j >= lines.length) continue

It does work, but i doubt that it's the proper fix. Figured it was worth mentioning as a point to dig around.

Hope this all helps with getting a fix for it. I'm just going to fix our code so I can get on with work proper, but this still should be fixed in Vite so no others get lost in this pothole.

Thanks!

Reproduction

https://stackblitz.com/edit/vitejs-vite-dnqqxn

Steps to reproduce

This is largely the same as setting up TailwindCSS with Vite like you would on a new project.

  1. npm create vite@latest -- --template vanilla bug
  2. cd bug
  3. npm install -D tailwindcss postcss autoprefixer
  4. npx tailwindcss init -p
  5. Set content to [ "./index.html", "./src/**/*.{js,ts,jsx,tsx}"] in tailwind.config.js
  6. Add the following block to src/style.css:
@tailwind base;
@tailwind components;
@tailwind utilities;

/* this finally starts setting up the bug */
@layer utilities {
  .vite-bug {
    color-adjust: exact; /* Replace color-adjust to print-color-adjust. The color-adjust shorthand is currently deprecated. */
  }
}
  1. Add lg:vite-bug to anywhere inside index.html or one of the included JS files in src. A comment works just fine for it.
  2. Run ./node_modules/.bin/vite build
  3. Wait until heat death of the universe.

System Info

System:
    OS: Linux 6.5 Alpine Linux
    CPU: (12) x64 AMD Ryzen 5 3600 6-Core Processor
    Memory: 11.94 GB / 62.69 GB
    Container: Yes
    Shell: 1.36.1 - /bin/ash
  Binaries:
    Node: 20.9.0 - /usr/local/bin/node
    Yarn: 1.22.19 - /usr/local/bin/yarn
    npm: 10.1.0 - /usr/local/bin/npm
  npmPackages:
    @vitejs/plugin-vue: ^4.0.0 => 4.5.0 
    vite: ^5.0.0 => 5.0.0

Used Package Manager

npm

Logs

Click to expand!
./node_modules/.bin/vite build --debug
  vite:config no config file found. +0ms
  vite:config using resolved config: {
  vite:config   root: '/home/projects/vitejs-vite-dnqqxn',
  vite:config   base: '/',
  vite:config   mode: 'production',
  vite:config   configFile: undefined,
  vite:config   logLevel: undefined,
  vite:config   clearScreen: undefined,
  vite:config   optimizeDeps: {
  vite:config     disabled: 'build',
  vite:config     force: undefined,
  vite:config     esbuildOptions: { preserveSymlinks: false }
  vite:config   },
  vite:config   build: {
  vite:config     target: [ 'es2020', 'edge88', 'firefox78', 'chrome87', 'safari14' ],
  vite:config     cssTarget: [ 'es2020', 'edge88', 'firefox78', 'chrome87', 'safari14' ],
  vite:config     outDir: 'dist',
  vite:config     assetsDir: 'assets',
  vite:config     assetsInlineLimit: 4096,
  vite:config     cssCodeSplit: true,
  vite:config     sourcemap: false,
  vite:config     rollupOptions: {},
  vite:config     minify: 'esbuild',
  vite:config     terserOptions: {},
  vite:config     write: true,
  vite:config     emptyOutDir: null,
  vite:config     copyPublicDir: true,
  vite:config     manifest: false,
  vite:config     lib: false,
  vite:config     ssr: false,
  vite:config     ssrManifest: false,
  vite:config     ssrEmitAssets: false,
  vite:config     reportCompressedSize: true,
  vite:config     chunkSizeWarningLimit: 500,
  vite:config     watch: null,
  vite:config     commonjsOptions: { include: [Array], extensions: [Array] },
  vite:config     dynamicImportVarsOptions: { warnOnError: true, exclude: [Array] },
  vite:config     modulePreload: { polyfill: true },
  vite:config     cssMinify: true
  vite:config   },
  vite:config   configFileDependencies: [],
  vite:config   inlineConfig: {
  vite:config     root: undefined,
  vite:config     base: undefined,
  vite:config     mode: undefined,
  vite:config     configFile: undefined,
  vite:config     logLevel: undefined,
  vite:config     clearScreen: undefined,
  vite:config     optimizeDeps: { force: undefined },
  vite:config     build: {}
  vite:config   },
  vite:config   rawBase: '/',
  vite:config   resolve: {
  vite:config     mainFields: [ 'browser', 'module', 'jsnext:main', 'jsnext' ],
  vite:config     conditions: [],
  vite:config     extensions: [
  vite:config       '.mjs',  '.js',
  vite:config       '.mts',  '.ts',
  vite:config       '.jsx',  '.tsx',
  vite:config       '.json'
  vite:config     ],
  vite:config     dedupe: [],
  vite:config     preserveSymlinks: false,
  vite:config     alias: [ [Object], [Object] ]
  vite:config   },
  vite:config   publicDir: '/home/projects/vitejs-vite-dnqqxn/public',
  vite:config   cacheDir: '/home/projects/vitejs-vite-dnqqxn/node_modules/.vite',
  vite:config   command: 'build',
  vite:config   ssr: {
  vite:config     target: 'node',
  vite:config     optimizeDeps: { disabled: true, esbuildOptions: [Object] }
  vite:config   },
  vite:config   isWorker: false,
  vite:config   mainConfig: null,
  vite:config   isProduction: true,
  vite:config   plugins: [
  vite:config     'vite:build-metadata',
  vite:config     'vite:watch-package-data',
  vite:config     'vite:pre-alias',
  vite:config     'alias',
  vite:config     'vite:modulepreload-polyfill',
  vite:config     'vite:resolve',
  vite:config     'vite:html-inline-proxy',
  vite:config     'vite:css',
  vite:config     'vite:esbuild',
  vite:config     'vite:json',
  vite:config     'vite:wasm-helper',
  vite:config     'vite:worker',
  vite:config     'vite:asset',
  vite:config     'vite:wasm-fallback',
  vite:config     'vite:define',
  vite:config     'vite:css-post',
  vite:config     'vite:build-html',
  vite:config     'vite:worker-import-meta-url',
  vite:config     'vite:asset-import-meta-url',
  vite:config     'vite:force-systemjs-wrap-complete',
  vite:config     'commonjs',
  vite:config     'vite:data-uri',
  vite:config     'vite:dynamic-import-vars',
  vite:config     'vite:import-glob',
  vite:config     'vite:build-import-analysis',
  vite:config     'vite:esbuild-transpile',
  vite:config     'vite:terser',
  vite:config     'vite:reporter',
  vite:config     'vite:load-fallback'
  vite:config   ],
  vite:config   css: { lightningcss: undefined },
  vite:config   esbuild: { jsxDev: false },
  vite:config   server: {
  vite:config     preTransformRequests: true,
  vite:config     sourcemapIgnoreList: [Function: isInNodeModules$1],
  vite:config     middlewareMode: false,
  vite:config     fs: { strict: true, allow: [Array], deny: [Array] }
  vite:config   },
  vite:config   preview: {
  vite:config     port: undefined,
  vite:config     strictPort: undefined,
  vite:config     host: undefined,
  vite:config     https: undefined,
  vite:config     open: undefined,
  vite:config     proxy: undefined,
  vite:config     cors: undefined,
  vite:config     headers: undefined
  vite:config   },
  vite:config   envDir: '/home/projects/vitejs-vite-dnqqxn',
  vite:config   env: { BASE_URL: '/', MODE: 'production', DEV: false, PROD: true },
  vite:config   assetsInclude: [Function: assetsInclude],
  vite:config   logger: {
  vite:config     hasWarned: false,
  vite:config     info: [Function: info],
  vite:config     warn: [Function: warn],
  vite:config     warnOnce: [Function: warnOnce],
  vite:config     error: [Function: error],
  vite:config     clearScreen: [Function: clearScreen],
  vite:config     hasErrorLogged: [Function: hasErrorLogged]
  vite:config   },
  vite:config   packageCache: Map(1) {
  vite:config     'fnpd_/home/projects/vitejs-vite-dnqqxn' => {
  vite:config       dir: '/home/projects/vitejs-vite-dnqqxn',
  vite:config       data: [Object],
  vite:config       hasSideEffects: [Function: hasSideEffects],
  vite:config       webResolvedImports: {},
  vite:config       nodeResolvedImports: {},
  vite:config       setResolvedCache: [Function: setResolvedCache],
  vite:config       getResolvedCache: [Function: getResolvedCache]
  vite:config     },
  vite:config     set: [Function (anonymous)]
  vite:config   },
  vite:config   createResolver: [Function: createResolver],
  vite:config   worker: { format: 'iife', plugins: '() => plugins', rollupOptions: {} },
  vite:config   appType: 'spa',
  vite:config   experimental: { importGlobRestoreExtension: false, hmrPartialAccept: false },
  vite:config   getSortedPlugins: [Function: getSortedPlugins],
  vite:config   getSortedPluginHooks: [Function: getSortedPluginHooks]
  vite:config } +10ms
vite v5.0.2 building for production...
transforming (1) index.html **hangs here forever**

Validations

@sapphi-red sapphi-red added the p4-important Violate documented behavior or significantly improves performance (priority) label Nov 22, 2023
@github-actions github-actions bot locked and limited conversation to collaborators Dec 7, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
p4-important Violate documented behavior or significantly improves performance (priority)
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants