diff --git a/integrations/vite/index.test.ts b/integrations/vite/index.test.ts index 28c218b81227..b54d06018ae1 100644 --- a/integrations/vite/index.test.ts +++ b/integrations/vite/index.test.ts @@ -584,6 +584,174 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { }, ) + ;(transformer === 'postcss' ? test : test.skip)( + 'css-like scanned file changes do not force a full reload when another plugin handles CSS HMR', + { + fs: { + 'package.json': json`{}`, + 'pnpm-workspace.yaml': yaml` + # + packages: + - project-a + `, + 'project-a/package.json': json` + { + "type": "module", + "dependencies": { + "@tailwindcss/vite": "workspace:^", + "tailwindcss": "workspace:^" + }, + "devDependencies": { + ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} + "vite": "^8" + } + } + `, + 'project-a/vite.config.ts': ts` + import fs from 'node:fs' + import fsp from 'node:fs/promises' + import path from 'node:path' + import tailwindcss from '@tailwindcss/vite' + import { fileURLToPath } from 'node:url' + import { defineConfig, normalizePath } from 'vite' + + function appendLog(file, payload) { + fs.appendFileSync(file, JSON.stringify(payload) + '\\n', 'utf8') + } + + function hmrWiretap(logFile) { + return { + name: 'hmr-wiretap', + configureServer(server) { + fs.writeFileSync(logFile, '', 'utf8') + + const originalWsSend = server.ws.send.bind(server.ws) + server.ws.send = ((payload, ...args) => { + appendLog(logFile, { source: 'server.ws.send', payload }) + return originalWsSend(payload, ...args) + }) as typeof server.ws.send + + for (const [environmentName, environment] of Object.entries(server.environments)) { + const originalHotSend = environment.hot.send.bind(environment.hot) + environment.hot.send = ((payload) => { + appendLog(logFile, { + source: 'environment.hot.send', + environmentName, + payload, + }) + return originalHotSend(payload) + }) as typeof environment.hot.send + } + }, + } + } + + function componentStylePlugin() { + let probeFile = '' + let wrapperFile = '' + + return { + name: 'component-style-plugin', + enforce: 'pre', + configResolved(config) { + probeFile = normalizePath(path.resolve(config.root, 'src/probe.component.css')) + wrapperFile = normalizePath(path.resolve(config.root, 'src/component-wrapper.css')) + }, + async transform(_, id) { + if (normalizePath(id.split('?')[0]) !== wrapperFile) return + + this.addWatchFile(probeFile) + const content = await fsp.readFile(probeFile, 'utf8') + return [ + "@import 'tailwindcss';", + "@source './probe.component.css';", + content, + ].join('\\n') + }, + hotUpdate({ file, timestamp }) { + if (normalizePath(file) !== probeFile) return + + this.environment.hot.send({ + type: 'update', + updates: [ + { + type: 'css-update', + path: '/src/component-wrapper.css', + acceptedPath: '/src/component-wrapper.css', + timestamp, + }, + ], + }) + + return [] + }, + } + } + + export default defineConfig({ + css: ${transformer === 'postcss' ? '{}' : "{ transformer: 'lightningcss' }"}, + build: { cssMinify: false }, + logLevel: 'info', + plugins: [ + tailwindcss(), + componentStylePlugin(), + hmrWiretap(path.resolve(path.dirname(fileURLToPath(import.meta.url)), 'hmr.log')), + ], + }) + `, + 'project-a/index.html': html` + +
+ + + +