/
githubmirror
/
tailwindcss
Обзор
Документация
Войти
/
githubmirror
/
tailwindcss
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
integrations/vite/bundled-dev.test.ts
193 строки
7 KB
Robin Malfait
Attempt to fix flaky integration test (#20384)
05 авг 2026, 13:39
Не верифицирован
05 авг 2026, 13:39
3524b45
Код
Авторство
О чём код?
import path from 'node:path' import { candidate, css, html, json, retryAssertion, test, ts, txt, yaml } from '../utils' // Vite's experimental `bundledDev` mode invokes the `hotUpdate` hook without a // `server`, which used to crash the plugin on every file edit. // // - https://github.com/tailwindlabs/tailwindcss/issues/20378 // - https://vite.dev/blog/announcing-vite8-1#experimental-bundled-dev-mode test( 'dev mode (experimental `bundledDev`)', { fs: { 'package.json': json`{}`, 'pnpm-workspace.yaml': yaml` # packages: - project-a `, 'project-a/package.json': txt` { "type": "module", "dependencies": { "@tailwindcss/vite": "workspace:^", "tailwindcss": "workspace:^" }, "devDependencies": { "vite": "^8.1" } } `, 'project-a/vite.config.ts': ts` import tailwindcss from '@tailwindcss/vite' import { defineConfig } from 'vite' export default defineConfig({ experimental: { bundledDev: true, }, plugins: [tailwindcss()], }) `, 'project-a/index.html': html` <head> <link rel="stylesheet" href="./src/index.css" /> </head> <body> <div class="underline">Hello, world!</div> </body> `, 'project-a/src/index.css': css` @reference 'tailwindcss/theme'; @import 'tailwindcss/utilities'; @source '../../project-b/src/**/*.html'; `, 'project-b/src/index.html': html` <div class="flex" /> `, }, }, async ({ root, spawn, fs, expect }) => { let process = await spawn('pnpm vite dev', { cwd: path.join(root, 'project-a'), env: { // Some CI machines have an upstream rolldown bug where its file watcher // never delivers a single event. The debug output is the only way to // tell that apart from a real regression (see below). DEBUG: 'vite:full-bundle-mode', }, }) // Any debug output beyond the "INITIAL:" startup lines proves rolldown's // watcher delivered at least one file event. let watcherAlive = false // `hotUpdate` errors don't kill the dev server, they are only printed to // stderr. Track them explicitly so a crash fails the test even if the // rebuild happens to succeed anyway. let pluginErrors: string[] = [] process.onStderr((message) => { if (message.includes('vite:full-bundle-mode')) { if (!message.includes('INITIAL:')) watcherAlive = true } else if (message.includes('@tailwindcss/vite')) { pluginErrors.push(message) } return false }) let url = '' await process.onStdout((m) => { let match = /Local:\s*(http.*)\//.exec(m) if (match) url = match[1] return Boolean(url) }) // In `bundledDev` mode the stylesheet is not served separately. Instead the // generated CSS is embedded in the bundled JS and injected at runtime, so // extract the bundle from the served HTML. While the bundle is being built, // Vite serves a temporary fallback page instead. // // The bundle can be split into multiple chunks (e.g. the HMR client runtime // and the app itself), and the chunk containing the CSS is not always the // first one, so fetch every referenced script and stylesheet. async function fetchBundledStyles(): Promise<string> { let index = await fetch(`${url}/`) let html = await index.text() if (html.includes('__vite_is_fallback_page__')) { throw new Error('Bundling still in progress') } let sources = [ ...html.matchAll(/<script[^>]*\ssrc="([^"]+)"/g), ...html.matchAll(/<link[^>]*\srel="stylesheet"[^>]*\shref="([^"]+)"/g), ].map((match) => match[1]) if (sources.length === 0) throw new Error(`No scripts or stylesheets found in:\n\n${html}`) let contents = await Promise.all( sources.map(async (src) => { let response = await fetch(new URL(src, `${url}/`)) return await response.text() }), ) return contents.join('\n') } await retryAssertion( async () => { let styles = await fetchBundledStyles() expect(styles).toContain(candidate`underline`) expect(styles).toContain(candidate`flex`) }, { timeout: 10_000, delay: 100 }, ) // Edit a module file and a manually `@source`d file. Without a connected // HMR websocket client (this test only uses HTTP fetches), a file change // does not trigger an eager rebuild — it only marks the bundle as stale, // and the next fetch kicks off the rebuild. So poll with fetches without // touching the files again: rewriting would re-mark the bundle stale right // before each fetch and the rebuild could never be observed. await fs.write( 'project-a/index.html', html` <head> <link rel="stylesheet" href="./src/index.css" /> </head> <body> <div class="underline m-2">Hello, world!</div> </body> `, ) await fs.write('project-b/src/index.html', html` <div class="flex font-bold" /> `) try { await retryAssertion( async () => { let styles = await fetchBundledStyles() expect(styles).toContain(candidate`underline`) expect(styles).toContain(candidate`flex`) expect(styles).toContain(candidate`m-2`) expect(styles).toContain(candidate`font-bold`) }, { timeout: 15_000, delay: 100 }, ) } catch (error) { // If rolldown's watcher delivered any file event, the updates were // genuinely dropped somewhere along the way — a real failure. if (watcherAlive) throw error // Otherwise the watcher never delivered a single event, so the updates // can never be observed no matter how long we wait — an upstream // rolldown bug on some CI machines, not a plugin regression. The crash // regression this test guards is still covered: `hotUpdate` is driven // by Vite's own (working) chokidar watcher and asserted via // `pluginErrors` below. // // Skipping is only sound while the server still healthily serves the // original bundle. If it stopped serving (crashed, or wedged on the // fallback page), that's real breakage a silent watcher must not mask. console.log(error) let styles = await fetchBundledStyles() expect(styles).toContain(candidate`underline`) expect(styles).toContain(candidate`flex`) console.warn( 'Skipping update assertions: rolldown’s file watcher delivered no events on this machine.', ) } expect(pluginErrors).toEqual([]) }, )