-
-
Notifications
You must be signed in to change notification settings - Fork 8k
Generated CSS that creates warnings can cause infinite loops in generateCodeFrame #15076
Description
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.
vite/packages/vite/src/node/utils.ts
Lines 516 to 517 in f576d98
| 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.
vite/packages/vite/src/node/utils.ts
Lines 516 to 517 in f576d98
| 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
Steps to reproduce
This is largely the same as setting up TailwindCSS with Vite like you would on a new project.
npm create vite@latest -- --template vanilla bugcd bugnpm install -D tailwindcss postcss autoprefixernpx tailwindcss init -p- Set
contentto[ "./index.html", "./src/**/*.{js,ts,jsx,tsx}"]intailwind.config.js - 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. */
}
}- Add
lg:vite-bugto anywhere insideindex.htmlor one of the included JS files insrc. A comment works just fine for it. - Run
./node_modules/.bin/vite build - 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.0Used 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
- Follow our Code of Conduct
- Read the Contributing Guidelines.
- Read the docs.
- Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
- Make sure this is a Vite issue and not a framework-specific issue. For example, if it's a Vue SFC related bug, it should likely be reported to vuejs/core instead.
- Check that this is a concrete bug. For Q&A open a GitHub Discussion or join our Discord Chat Server.
- The provided reproduction is a minimal reproducible example of the bug.