From f0f7418785250c656ef1beb3078c9b6522b44215 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Wed, 21 Aug 2024 11:22:28 +0200 Subject: [PATCH] Simplify Vite plugin and handle all CSS transforms in pre-transform hook --- integrations/vite/index.test.ts | 514 +++++++++++++++--------- packages/@tailwindcss-vite/package.json | 8 +- packages/@tailwindcss-vite/src/index.ts | 237 ++++++----- playgrounds/vite/src/app.css | 1 + playgrounds/vite/src/bar.css | 3 + playgrounds/vite/src/bar.tsx | 2 +- pnpm-lock.yaml | 18 + 7 files changed, 469 insertions(+), 314 deletions(-) create mode 100644 playgrounds/vite/src/bar.css diff --git a/integrations/vite/index.test.ts b/integrations/vite/index.test.ts index c114828bc..5d7b08af6 100644 --- a/integrations/vite/index.test.ts +++ b/integrations/vite/index.test.ts @@ -1,5 +1,5 @@ import path from 'node:path' -import { expect } from 'vitest' +import { describe, expect } from 'vitest' import { candidate, css, @@ -12,206 +12,342 @@ import { ts, yaml, } from '../utils' +;['postcss', 'lightningcss'].forEach((transformer) => { + describe.concurrent(transformer, () => { + test( + `production build`, + { + 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": { + "vite": "^5.3.5" + } + } + `, + 'project-a/vite.config.ts': ts` + import tailwindcss from '@tailwindcss/vite' + import { defineConfig } from 'vite' -test( - 'production build', - { - fs: { - 'package.json': json`{}`, - 'pnpm-workspace.yaml': yaml` - # - packages: - - project-a + export default defineConfig({ + css: ${transformer === 'postcss' ? '{}' : "{ transformer: 'lightningcss' }"}, + build: { cssMinify: false }, + plugins: [tailwindcss()], + }) + `, + 'project-a/index.html': html` + + + + +
Hello, world!
+ `, - 'project-a/package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^5.3.5" - } - } - `, - 'project-a/vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' + 'project-a/src/index.css': css` + @import 'tailwindcss/theme' theme(reference); + @import 'tailwindcss/utilities'; + @source '../../project-b/src/**/*.js'; + `, + 'project-b/src/index.js': js` + const className = "content-['project-b/src/index.js']" + module.exports = { className } + `, + }, + }, + async ({ root, fs, exec }) => { + await exec('pnpm vite build', { cwd: path.join(root, 'project-a') }) - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss()], + let files = await fs.glob('project-a/dist/**/*.css') + expect(files).toHaveLength(1) + let [filename] = files[0] + + await fs.expectFileToContain(filename, [ + candidate`underline`, + candidate`m-2`, + candidate`content-['project-b/src/index.js']`, + ]) + }, + ) + + test( + `dev mode`, + { + 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": { + "vite": "^5.3.5" + } + } + `, + 'project-a/vite.config.ts': ts` + import tailwindcss from '@tailwindcss/vite' + import { defineConfig } from 'vite' + + export default defineConfig({ + css: ${transformer === 'postcss' ? '{}' : "{ transformer: 'lightningcss' }"}, + build: { cssMinify: false }, + plugins: [tailwindcss()], + }) + `, + 'project-a/index.html': html` + + + + +
Hello, world!
+ + `, + 'project-a/about.html': html` + + + + +
Tailwind Labs
+ + `, + 'project-a/src/index.css': css` + @import 'tailwindcss/theme' theme(reference); + @import 'tailwindcss/utilities'; + @source '../../project-b/src/**/*.js'; + `, + 'project-b/src/index.js': js` + const className = "content-['project-b/src/index.js']" + module.exports = { className } + `, + }, + }, + async ({ root, spawn, getFreePort, fs }) => { + let port = await getFreePort() + await spawn(`pnpm vite dev --port ${port}`, { + cwd: path.join(root, 'project-a'), }) - `, - 'project-a/index.html': html` - - - - -
Hello, world!
- - `, - 'project-a/src/index.css': css` - @import 'tailwindcss/theme' theme(reference); - @import 'tailwindcss/utilities'; - @source '../../project-b/src/**/*.js'; - `, - 'project-b/src/index.js': js` - const className = "content-['project-b/src/index.js']" - module.exports = { className } - `, - }, - }, - async ({ root, fs, exec }) => { - await exec('pnpm vite build', { cwd: path.join(root, 'project-a') }) - let files = await fs.glob('project-a/dist/**/*.css') - expect(files).toHaveLength(1) - let [filename] = files[0] - - await fs.expectFileToContain(filename, [ - candidate`underline`, - candidate`m-2`, - candidate`content-['project-b/src/index.js']`, - ]) - }, -) - -test( - 'dev mode', - { - 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": { - "vite": "^5.3.5" - } - } - `, - 'project-a/vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss()], + // Candidates are resolved lazily, so the first visit of index.html + // will only have candidates from this file. + await retryAssertion(async () => { + let css = await fetchStyles(port, '/index.html') + expect(css).toContain(candidate`underline`) + expect(css).not.toContain(candidate`font-bold`) }) - `, - 'project-a/index.html': html` + + // Going to about.html will extend the candidate list to include + // candidates from about.html. + await retryAssertion(async () => { + let css = await fetchStyles(port, '/about.html') + expect(css).toContain(candidate`underline`) + expect(css).toContain(candidate`font-bold`) + }) + + // Updates are additive and cause new candidates to be added. + await fs.write( + 'project-a/index.html', + html` - - - -
Hello, world!
- + + + +
Hello, world!
+ `, - 'project-a/about.html': html` - - - - -
Tailwind Labs
- - `, - 'project-a/src/index.css': css` - @import 'tailwindcss/theme' theme(reference); - @import 'tailwindcss/utilities'; - @source '../../project-b/src/**/*.js'; - `, - 'project-b/src/index.js': js` - const className = "content-['project-b/src/index.js']" - module.exports = { className } - `, - }, - }, - async ({ root, spawn, getFreePort, fs }) => { - let port = await getFreePort() - await spawn(`pnpm vite dev --port ${port}`, { - cwd: path.join(root, 'project-a'), - }) + ) + await retryAssertion(async () => { + let css = await fetchStyles(port) + expect(css).toContain(candidate`underline`) + expect(css).toContain(candidate`font-bold`) + expect(css).toContain(candidate`m-2`) + }) - // Candidates are resolved lazily, so the first visit of index.html - // will only have candidates from this file. - await retryAssertion(async () => { - let css = await fetchStyles(port, '/index.html') - expect(css).toContain(candidate`underline`) - expect(css).not.toContain(candidate`font-bold`) - }) + // Manually added `@source`s are watched and trigger a rebuild + await fs.write( + 'project-b/src/index.js', + js` + const className = "[.changed_&]:content-['project-b/src/index.js']" + module.exports = { className } + `, + ) + await retryAssertion(async () => { + let css = await fetchStyles(port) + expect(css).toContain(candidate`underline`) + expect(css).toContain(candidate`font-bold`) + expect(css).toContain(candidate`m-2`) + expect(css).toContain(candidate`[.changed_&]:content-['project-b/src/index.js']`) + }) - // Going to about.html will extend the candidate list to include - // candidates from about.html. - await retryAssertion(async () => { - let css = await fetchStyles(port, '/about.html') - expect(css).toContain(candidate`underline`) - expect(css).toContain(candidate`font-bold`) - }) + // After updates to the CSS file, all previous candidates should still be in + // the generated CSS + await fs.write( + 'project-a/src/index.css', + css` + ${await fs.read('project-a/src/index.css')} - // Updates are additive and cause new candidates to be added. - await fs.write( - 'project-a/index.html', - html` - - - - -
Hello, world!
- - `, + .red { + color: red; + } + `, + ) + await retryAssertion(async () => { + let css = await fetchStyles(port) + expect(css).toContain(candidate`red`) + expect(css).toContain(candidate`m-2`) + expect(css).toContain(candidate`underline`) + expect(css).toContain(candidate`[.changed_&]:content-['project-b/src/index.js']`) + expect(css).toContain(candidate`font-bold`) + }) + }, ) - await retryAssertion(async () => { - let css = await fetchStyles(port) - expect(css).toContain(candidate`underline`) - expect(css).toContain(candidate`font-bold`) - expect(css).toContain(candidate`m-2`) - }) - // Manually added `@source`s are watched and trigger a rebuild - await fs.write( - 'project-b/src/index.js', - js` - const className = "[.changed_&]:content-['project-b/src/index.js']" - module.exports = { className } - `, - ) - await retryAssertion(async () => { - let css = await fetchStyles(port) - expect(css).toContain(candidate`underline`) - expect(css).toContain(candidate`font-bold`) - expect(css).toContain(candidate`m-2`) - expect(css).toContain(candidate`[.changed_&]:content-['project-b/src/index.js']`) - }) + // test( + // 'watch mode', + // { + // 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": { + // "vite": "^5.3.5" + // } + // } + // `, + // 'project-a/vite.config.ts': ts` + // import tailwindcss from '@tailwindcss/vite' + // import { defineConfig } from 'vite' - // After updates to the CSS file, all previous candidates should still be in - // the generated CSS - await fs.write( - 'project-a/src/index.css', - css` - ${await fs.read('project-a/src/index.css')} + // export default defineConfig({ + // build: { cssMinify: false }, + // plugins: [tailwindcss()], + // }) + // `, + // 'project-a/index.html': html` + // + // + // + // + //
Hello, world!
+ // + // `, + // 'project-a/src/index.css': css` + // @import 'tailwindcss/theme' theme(reference); + // @import 'tailwindcss/utilities'; + // @source '../../project-b/src/**/*.js'; + // `, + // 'project-b/src/index.js': js` + // const className = "content-['project-b/src/index.js']" + // module.exports = { className } + // `, + // }, + // }, + // async ({ root, spawn, fs }) => { + // await spawn(`pnpm vite build --watch`, { + // cwd: path.join(root, 'project-a'), + // }) - .red { - color: red; - } - `, - ) - await retryAssertion(async () => { - let css = await fetchStyles(port) - expect(css).toContain(candidate`red`) - expect(css).toContain(candidate`m-2`) - expect(css).toContain(candidate`underline`) - expect(css).toContain(candidate`[.changed_&]:content-['project-b/src/index.js']`) - expect(css).toContain(candidate`font-bold`) - }) - }, -) + // let filename = '' + // await retryAssertion(async () => { + // let files = await fs.glob('project-a/dist/**/*.css') + // expect(files).toHaveLength(1) + // filename = files[0][0] + // }) + + // await fs.expectFileToContain(filename, [candidate`underline`]) + + // await new Promise((resolve) => setTimeout(resolve, 1000)) + + // // Updates are additive and cause new candidates to be added. + // await fs.write( + // 'project-a/index.html', + // html` + // + // + // + // + //
Hello, world!
+ // + // `, + // ) + // await retryAssertion(async () => { + // let files = await fs.glob('project-a/dist/**/*.css') + // let filenames = files.map(([filename]) => filename) + // // Expect a rebuild to have a different .css hash + // expect(filenames).not.toContain(filename) + // expect(filenames).toHaveLength(1) + // filename = filenames[0] + // }) + // await fs.expectFileToContain(filename, [candidate`underline`, candidate`m-2`]) + + // // Manually added `@source`s are watched and trigger a rebuild + // await fs.write( + // 'project-b/src/index.js', + // js` + // const className = "[.changed_&]:content-['project-b/src/index.js']" + // module.exports = { className } + // `, + // ) + // await fs.expectFileToContain(filename, [ + // candidate`underline`, + // candidate`m-2`, + // candidate`[.changed_&]:content-['project-b/src/index.js']`, + // ]) + + // // After updates to the CSS file, all previous candidates should still be in + // // the generated CSS + // await fs.write( + // 'project-a/src/index.css', + // css` + // ${await fs.read('project-a/src/index.css')} + + // .red { + // color: red; + // } + // `, + // ) + // await retryAssertion(async () => { + // let files = await fs.glob('project-a/dist/**/*.css') + // let filenames = files.map(([filename]) => filename) + // // Expect a rebuild to have a different .css hash + // expect(filenames).not.toContain(filename) + // expect(filenames).toHaveLength(1) + // filename = filenames[0] + // }) + // await fs.expectFileToContain(filename, [ + // candidate`red`, + // candidate`underline`, + // candidate`m-2`, + // candidate`[.changed_&]:content-['project-b/src/index.js']`, + // ]) + // }, + // ) + }) +}) diff --git a/packages/@tailwindcss-vite/package.json b/packages/@tailwindcss-vite/package.json index cd3aa761e..c032b4ee2 100644 --- a/packages/@tailwindcss-vite/package.json +++ b/packages/@tailwindcss-vite/package.json @@ -31,13 +31,15 @@ "@tailwindcss/node": "workspace:^", "@tailwindcss/oxide": "workspace:^", "lightningcss": "catalog:", - "postcss-load-config": "^6.0.1", + "postcss": "^8.4.41", + "postcss-import": "^16.1.0", "tailwindcss": "workspace:^" }, "devDependencies": { "@types/node": "catalog:", - "vite": "catalog:", - "internal-postcss-fix-relative-paths": "workspace:^" + "@types/postcss-import": "^14.0.3", + "internal-postcss-fix-relative-paths": "workspace:^", + "vite": "catalog:" }, "peerDependencies": { "vite": "^5.2.0" diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts index 9314f8cf0..235575b69 100644 --- a/packages/@tailwindcss-vite/src/index.ts +++ b/packages/@tailwindcss-vite/src/index.ts @@ -4,9 +4,11 @@ import { clearRequireCache } from '@tailwindcss/node/require-cache' import { Scanner } from '@tailwindcss/oxide' import fixRelativePathsPlugin, { normalizePath } from 'internal-postcss-fix-relative-paths' import { Features, transform } from 'lightningcss' +import fs from 'node:fs/promises' import { pathToFileURL } from 'node:url' import path from 'path' -import postcssrc from 'postcss-load-config' +import postcss from 'postcss' +import postcssImport from 'postcss-import' import { compile } from 'tailwindcss' import type { Plugin, ResolvedConfig, Rollup, Update, ViteDevServer } from 'vite' @@ -16,23 +18,19 @@ export default function tailwindcss(): Plugin[] { let scanner: Scanner | null = null let changedContent: { content: string; extension: string }[] = [] let candidates = new Set() - let fullRebuildPaths: string[] = [] + let fullRebuildPaths = new Set() + let rebuildStrategy: 'full' | 'incremental' = 'full' + let compiler: null | Awaited> = null // In serve mode this is treated as a set — the content doesn't matter. // In build mode, we store file contents to use them in renderChunk. - let cssModules: Record< - string, - { - content: string - handled: boolean - } - > = {} + let cssModules: Record = {} let isSSR = false let minify = false let cssPlugins: readonly Plugin[] = [] // Trigger update to all CSS modules - function updateCssModules(isSSR: boolean) { + function queueRebuild(isSSR: boolean) { // If we're building then we don't need to update anything if (!server) return @@ -82,56 +80,85 @@ export default function tailwindcss(): Plugin[] { return updated } + function onFileChange(src: string) { + if (fullRebuildPaths.has(idToPath(src))) { + rebuildStrategy = 'full' + return + } + } + async function generateCss(css: string, inputPath: string, addWatchFile: (file: string) => void) { await import('@tailwindcss/node/esm-cache-hook') let inputBasePath = path.dirname(path.resolve(inputPath)) - clearRequireCache(fullRebuildPaths) - fullRebuildPaths = [] - let { build, globs } = await compile(css, { - loadPlugin: async (pluginPath) => { - if (pluginPath[0] !== '.') { - return import(pluginPath).then((m) => m.default ?? m) - } - let resolvedPath = path.resolve(inputBasePath, pluginPath) - let [module, moduleDependencies] = await Promise.all([ - import(pathToFileURL(resolvedPath).href + '?id=' + Date.now()), - getModuleDependencies(resolvedPath), - ]) + if (compiler === null || rebuildStrategy === 'full') { + rebuildStrategy = 'incremental' + clearRequireCache(Array.from(fullRebuildPaths)) + fullRebuildPaths.clear() + fullRebuildPaths.add(idToPath(inputPath)) - addWatchFile(resolvedPath) - fullRebuildPaths.push(resolvedPath) - for (let file of moduleDependencies) { - addWatchFile(file) - fullRebuildPaths.push(file) - } - return module.default ?? module - }, + // Resolve `@import`s + let postcssCompiled = await postcss([ + postcssImport({ + load(path) { + fullRebuildPaths.add(path) + addWatchFile(path) + return fs.readFile(path, 'utf8') + }, + }), + fixRelativePathsPlugin(), + ]).process(css, { + from: inputPath, + to: inputPath, + }) + css = postcssCompiled.css - loadConfig: async (configPath) => { - if (configPath[0] !== '.') { - return import(configPath).then((m) => m.default ?? m) - } + compiler = await compile(css, { + loadPlugin: async (pluginPath) => { + if (pluginPath[0] !== '.') { + return import(pluginPath).then((m) => m.default ?? m) + } - let resolvedPath = path.resolve(inputBasePath, configPath) - let [module, moduleDependencies] = await Promise.all([ - import(pathToFileURL(resolvedPath).href + '?id=' + Date.now()), - getModuleDependencies(resolvedPath), - ]) + let resolvedPath = path.resolve(inputBasePath, pluginPath) + let [module, moduleDependencies] = await Promise.all([ + import(pathToFileURL(resolvedPath).href + '?id=' + Date.now()), + getModuleDependencies(resolvedPath), + ]) - addWatchFile(resolvedPath) - fullRebuildPaths.push(resolvedPath) - for (let file of moduleDependencies) { - addWatchFile(file) - fullRebuildPaths.push(file) - } - return module.default ?? module - }, - }) + addWatchFile(resolvedPath) + fullRebuildPaths.add(resolvedPath) + for (let file of moduleDependencies) { + addWatchFile(file) + fullRebuildPaths.add(file) + } + return module.default ?? module + }, + + loadConfig: async (configPath) => { + if (configPath[0] !== '.') { + return import(configPath).then((m) => m.default ?? m) + } + + let resolvedPath = path.resolve(inputBasePath, configPath) + let [module, moduleDependencies] = await Promise.all([ + import(pathToFileURL(resolvedPath).href + '?id=' + Date.now()), + getModuleDependencies(resolvedPath), + ]) + + addWatchFile(resolvedPath) + fullRebuildPaths.add(resolvedPath) + for (let file of moduleDependencies) { + addWatchFile(file) + fullRebuildPaths.add(file) + } + return module.default ?? module + }, + }) + } scanner = new Scanner({ - sources: globs.map((pattern) => ({ + sources: compiler.globs.map((pattern) => ({ base: inputBasePath, // Globs are relative to the input.css file pattern, })), @@ -170,7 +197,7 @@ export default function tailwindcss(): Plugin[] { addWatchFile(path.posix.join(relative, glob.pattern)) } - return build(Array.from(candidates)) + return compiler.build(Array.from(candidates)) } async function generateOptimizedCss( @@ -239,106 +266,57 @@ export default function tailwindcss(): Plugin[] { ...(config.command === 'build' ? ['vite:css-post'] : []), ] + console.log('plugins') + console.log(config.plugins) cssPlugins = config.plugins.filter((plugin) => { return allowedPlugins.includes(plugin.name) }) }, - // Append the postcss-fix-relative-paths plugin - async config(config) { - let postcssConfig = config.css?.postcss - - if (typeof postcssConfig === 'string') { - // We expand string configs to their PostCSS config object similar to - // how Vite does it. - // See: https://github.com/vitejs/vite/blob/440783953a55c6c63cd09ec8d13728dc4693073d/packages/vite/src/node/plugins/css.ts#L1580 - let searchPath = typeof postcssConfig === 'string' ? postcssConfig : config.root - let parsedConfig = await postcssrc({}, searchPath).catch((e: Error) => { - if (!e.message.includes('No PostCSS Config found')) { - if (e instanceof Error) { - let { name, message, stack } = e - e.name = 'Failed to load PostCSS config' - e.message = `Failed to load PostCSS config (searchPath: ${searchPath}): [${name}] ${message}\n${stack}` - e.stack = '' // add stack to message to retain stack - throw e - } else { - throw new Error(`Failed to load PostCSS config: ${e}`) - } - } - return null - }) - if (parsedConfig !== null) { - postcssConfig = { - options: parsedConfig.options, - plugins: parsedConfig.plugins, - } as any - } else { - postcssConfig = {} - } - config.css = { postcss: postcssConfig } - } - - // postcssConfig is no longer a string after the above. This test is to - // avoid TypeScript errors below. - if (typeof postcssConfig === 'string') { - return - } - - if (!postcssConfig || !postcssConfig?.plugins) { - config.css = config.css || {} - config.css.postcss = postcssConfig || {} - config.css.postcss.plugins = [fixRelativePathsPlugin() as any] - } else { - postcssConfig.plugins.push(fixRelativePathsPlugin() as any) - } - }, - // Scan index.html for candidates - transformIndexHtml(html) { - let updated = scan(html, 'html') + transformIndexHtml(html, context) { + onFileChange(context.filename) - // In serve mode, if the generated CSS contains a URL that causes the - // browser to load a page (e.g. an URL to a missing image), triggering a - // CSS update will cause an infinite loop. We only trigger if the - // candidates have been updated. + let updated = scan(html, 'html') if (updated) { - updateCssModules(isSSR) + queueRebuild(isSSR) } }, // Scan all non-CSS files for candidates transform(src, id, options) { + onFileChange(id) + if (id.includes('/.vite/')) return let extension = getExtension(id) if (extension === '' || extension === 'css') return - scan(src, extension) - updateCssModules(options?.ssr ?? false) + let updated = scan(src, extension) + if (updated) { + queueRebuild(options?.ssr ?? false) + } }, }, - /* - * The plugins that generate CSS must run after 'enforce: pre' so @imports - * are expanded in transform. - */ - { // Step 2 (serve mode): Generate CSS name: '@tailwindcss/vite:generate:serve', apply: 'serve', + enforce: 'pre', async transform(src, id, options) { if (!isTailwindCssFile(id, src)) return + onFileChange(id) // In serve mode, we treat cssModules as a set, ignoring the value. cssModules[id] = { content: '', handled: true } - if (!options?.ssr) { - // Wait until all other files have been processed, so we can extract - // all candidates before generating CSS. This must not be called - // during SSR or it will block the server. - await server?.waitForRequestsIdle?.(id) - } + // if (!options?.ssr) { + // // Wait until all other files have been processed, so we can extract + // // all candidates before generating CSS. This must not be called + // // during SSR or it will block the server. + // await server?.waitForRequestsIdle?.(id) + // } let code = await transformWithPlugins( this, @@ -353,10 +331,23 @@ export default function tailwindcss(): Plugin[] { // Step 2 (full build): Generate CSS name: '@tailwindcss/vite:generate:build', apply: 'build', + enforce: 'pre', - transform(src, id) { + async transform(src, id) { if (!isTailwindCssFile(id, src)) return + onFileChange(id) + cssModules[id] = { content: src, handled: false } + + // We do a first pass to generate valid CSS for the downstream plugins. + // However, it's likely that we need to regenerate this later once all + // candidates have been extracted. + let code = await transformWithPlugins( + this, + id, + await generateCss(src, id, (file) => this.addWatchFile(file)), + ) + return { code } }, // renderChunk runs in the bundle generation stage after all transforms. @@ -390,7 +381,7 @@ function getExtension(id: string) { function isTailwindCssFile(id: string, src: string) { let extension = getExtension(id) let isCssFile = extension === 'css' || (extension === 'vue' && id.includes('&lang.css')) - return isCssFile && src.includes('@tailwind') + return isCssFile } function optimizeCss( @@ -416,3 +407,7 @@ function optimizeCss( errorRecovery: true, }).code.toString() } + +function idToPath(id: string) { + return path.resolve(id.replace(/\?.*$/, '')) +} diff --git a/playgrounds/vite/src/app.css b/playgrounds/vite/src/app.css index adbd8e19f..ba7a2f60a 100644 --- a/playgrounds/vite/src/app.css +++ b/playgrounds/vite/src/app.css @@ -1,2 +1,3 @@ @import 'tailwindcss'; +@import './bar.css'; @plugin "./plugin.js"; diff --git a/playgrounds/vite/src/bar.css b/playgrounds/vite/src/bar.css new file mode 100644 index 000000000..c6dd2c88f --- /dev/null +++ b/playgrounds/vite/src/bar.css @@ -0,0 +1,3 @@ +.red { + color: red; +} diff --git a/playgrounds/vite/src/bar.tsx b/playgrounds/vite/src/bar.tsx index 1efa2c004..b4b9f70c1 100644 --- a/playgrounds/vite/src/bar.tsx +++ b/playgrounds/vite/src/bar.tsx @@ -1,7 +1,7 @@ export function Bar() { return (
-

Bar

+

Bar

) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d51514c73..f6a41b4b4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -283,10 +283,25 @@ importers: version: link:../../crates/node lightningcss: specifier: 'catalog:' +<<<<<<< HEAD version: 1.26.0(patch_hash=5hwfyehqvg5wjb7mwtdvubqbl4) postcss-load-config: specifier: ^6.0.1 version: 6.0.1(postcss@8.4.41) +||||||| parent of 9ab6a7c5 (Simplify Vite plugin and handle all CSS transforms in pre-transform hook) + version: 1.26.0 + postcss-load-config: + specifier: ^6.0.1 + version: 6.0.1(postcss@8.4.41) +======= + version: 1.26.0 + postcss: + specifier: ^8.4.41 + version: 8.4.41 + postcss-import: + specifier: ^16.1.0 + version: 16.1.0(postcss@8.4.41) +>>>>>>> 9ab6a7c5 (Simplify Vite plugin and handle all CSS transforms in pre-transform hook) tailwindcss: specifier: workspace:^ version: link:../tailwindcss @@ -294,6 +309,9 @@ importers: '@types/node': specifier: 'catalog:' version: 20.14.13 + '@types/postcss-import': + specifier: ^14.0.3 + version: 14.0.3 internal-postcss-fix-relative-paths: specifier: workspace:^ version: link:../internal-postcss-fix-relative-paths