import { candidate, css, fetchStyles, html, json, retryAssertion, test, ts, txt } from '../utils' test( 'dev mode', { fs: { 'package.json': json` { "type": "module", "dependencies": { "preact": "^10" }, "devDependencies": { "@preact/preset-vite": "^2", "@tailwindcss/vite": "workspace:^", "tailwindcss": "workspace:^", "vite": "^8" } } `, 'vite.config.ts': ts` import fs from 'node:fs' import path from 'node:path' import preact from '@preact/preset-vite' import tailwindcss from '@tailwindcss/vite' import { defineConfig } from 'vite' export default defineConfig({ plugins: [ tailwindcss(), preact(), { // Log all HMR payloads to a file so the test can assert on them name: 'hmr-wiretap', configureServer(server) { let logFile = path.resolve('hmr.log') fs.writeFileSync(logFile, '') for (let environment of Object.values(server.environments)) { let send = environment.hot.send.bind(environment.hot) environment.hot.send = (payload) => { fs.appendFileSync(logFile, JSON.stringify(payload) + '\\n') return send(payload) } } }, }, ], }) `, 'index.html': html`
`, 'src/main.tsx': ts` import { render } from 'preact' import { App } from './app' render(, document.getElementById('app')!) `, 'src/app.tsx': ts` import { useState } from 'preact/hooks' export function App() { const [count, setCount] = useState(0) return ( ) } `, 'src/index.css': css`@import 'tailwindcss';`, }, }, async ({ fs, spawn, expect }) => { let process = await spawn('pnpm vite dev') await process.onStdout((m) => m.includes('ready in')) let url = '' await process.onStdout((m) => { let match = /Local:\s*(http.*)\//.exec(m) if (match) url = match[1] return Boolean(url) }) await retryAssertion(async () => { let styles = await fetchStyles(url) expect(styles).toContain(candidate`underline`) }) // Load the component modules, like a browser visiting the page would await fetch(`${url}/src/main.tsx`) await fetch(`${url}/src/app.tsx`) // Editing a component keeps HMR intact: new classes are delivered through // a regular update, not a full page reload (which would lose all state) { await fs.write( 'src/app.tsx', ts` import { useState } from 'preact/hooks' export function App() { const [count, setCount] = useState(0) return ( ) } `, ) await retryAssertion(async () => { let styles = await fetchStyles(url) expect(styles).toContain(candidate`underline`) expect(styles).toContain(candidate`flex`) }) expect(await fs.read('hmr.log')).toContain('"type":"update"') expect(await fs.read('hmr.log')).not.toContain('full-reload') } // Changing a scanned file that is not part of the module graph (e.g. // `package.json`, which package managers and other tooling write to while // the dev server is running) should not trigger a full reload either — // that would destroy client state. New candidates should still be picked // up because the file is a watch dependency of the CSS root, so the CSS // hot-updates through Vite's regular pipeline. // // https://github.com/tailwindlabs/tailwindcss/issues/20411 { await fs.write( 'package.json', txt` { "type": "module", "description": "content-['package.json']", "dependencies": { "preact": "^10" }, "devDependencies": { "@preact/preset-vite": "^2", "@tailwindcss/vite": "workspace:^", "tailwindcss": "workspace:^", "vite": "^8" } } `, ) await retryAssertion(async () => { let styles = await fetchStyles(url) expect(styles).toContain(candidate`content-['package.json']`) }) expect(await fs.read('hmr.log')).not.toContain('full-reload') } }, )