diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ccd72524320..6bcd4928543d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Skip creating a compiler for CSS files that should not be processed ([#15340](https://github.com/tailwindlabs/tailwindcss/pull/15340)) - Fix missing `shadow-none` suggestion in IntelliSense ([#15342](https://github.com/tailwindlabs/tailwindcss/pull/15342)) - Optimize AST before printing for IntelliSense ([#15347](https://github.com/tailwindlabs/tailwindcss/pull/15347)) +- Improve debug logs to get better insights ([#15303](https://github.com/tailwindlabs/tailwindcss/pull/15303)) ### Changed diff --git a/crates/oxide/src/lib.rs b/crates/oxide/src/lib.rs index f7da2be772ce..4631cabc843c 100644 --- a/crates/oxide/src/lib.rs +++ b/crates/oxide/src/lib.rs @@ -24,7 +24,7 @@ pub mod paths; pub mod scanner; static SHOULD_TRACE: sync::LazyLock = sync::LazyLock::new( - || matches!(std::env::var("DEBUG"), Ok(value) if value.eq("*") || value.eq("1") || value.eq("true") || value.contains("tailwind")), + || matches!(std::env::var("DEBUG"), Ok(value) if value.eq("*") || (value.contains("tailwindcss:oxide") && !value.contains("-tailwindcss:oxide"))), ); fn init_tracing() { diff --git a/packages/@tailwindcss-cli/src/commands/build/index.ts b/packages/@tailwindcss-cli/src/commands/build/index.ts index b04c779dd548..2a711eae354a 100644 --- a/packages/@tailwindcss-cli/src/commands/build/index.ts +++ b/packages/@tailwindcss-cli/src/commands/build/index.ts @@ -1,5 +1,5 @@ import watcher from '@parcel/watcher' -import { compile, env } from '@tailwindcss/node' +import { compile, env, Instrumentation } from '@tailwindcss/node' import { clearRequireCache } from '@tailwindcss/node/require-cache' import { Scanner, type ChangedContent } from '@tailwindcss/oxide' import { Features, transform } from 'lightningcss' @@ -19,6 +19,7 @@ import { import { drainStdin, outputFile } from './utils' const css = String.raw +const DEBUG = env.DEBUG export function options() { return { @@ -66,6 +67,9 @@ async function handleError(fn: () => T): Promise { } export async function handle(args: Result>) { + using I = new Instrumentation() + DEBUG && I.start('[@tailwindcss/cli] (initial build)') + let base = path.resolve(args['--cwd']) // Resolve the output as an absolute path. @@ -103,18 +107,18 @@ export async function handle(args: Result>) { optimizedCss: '', } - async function write(css: string, args: Result>) { + async function write(css: string, args: Result>, I: Instrumentation) { let output = css // Optimize the output if (args['--minify'] || args['--optimize']) { if (css !== previous.css) { - env.DEBUG && console.time('[@tailwindcss/cli] Optimize CSS') + DEBUG && I.start('Optimize CSS') let optimizedCss = optimizeCss(css, { file: args['--input'] ?? 'input.css', minify: args['--minify'] ?? false, }) - env.DEBUG && console.timeEnd('[@tailwindcss/cli] Optimize CSS') + DEBUG && I.end('Optimize CSS') previous.css = css previous.optimizedCss = optimizedCss output = optimizedCss @@ -124,13 +128,13 @@ export async function handle(args: Result>) { } // Write the output - env.DEBUG && console.time('[@tailwindcss/cli] Write output') + DEBUG && I.start('Write output') if (args['--output']) { await outputFile(args['--output'], output) } else { println(output) } - env.DEBUG && console.timeEnd('[@tailwindcss/cli] Write output') + DEBUG && I.end('Write output') } let inputFilePath = @@ -140,8 +144,8 @@ export async function handle(args: Result>) { let fullRebuildPaths: string[] = inputFilePath ? [inputFilePath] : [] - async function createCompiler(css: string) { - env.DEBUG && console.time('[@tailwindcss/cli] Setup compiler') + async function createCompiler(css: string, I: Instrumentation) { + DEBUG && I.start('Setup compiler') let compiler = await compile(css, { base: inputBasePath, onDependency(path) { @@ -165,12 +169,12 @@ export async function handle(args: Result>) { })().concat(compiler.globs) let scanner = new Scanner({ sources }) - env.DEBUG && console.timeEnd('[@tailwindcss/cli] Setup compiler') + DEBUG && I.end('Setup compiler') return [compiler, scanner] as const } - let [compiler, scanner] = await handleError(() => createCompiler(input)) + let [compiler, scanner] = await handleError(() => createCompiler(input, I)) // Watch for changes if (args['--watch']) { @@ -182,6 +186,12 @@ export async function handle(args: Result>) { // trigger a rebuild because that will result in an infinite loop. if (files.length === 1 && files[0] === args['--output']) return + using I = new Instrumentation() + DEBUG && I.start('[@tailwindcss/cli] (watcher)') + + // Re-compile the input + let start = process.hrtime.bigint() + let changedFiles: ChangedContent[] = [] let rebuildStrategy: 'incremental' | 'full' = 'incremental' @@ -206,9 +216,6 @@ export async function handle(args: Result>) { } satisfies ChangedContent) } - // Re-compile the input - let start = process.hrtime.bigint() - // Track the compiled CSS let compiledCss = '' @@ -226,32 +233,36 @@ export async function handle(args: Result>) { fullRebuildPaths = inputFilePath ? [inputFilePath] : [] // Create a new compiler, given the new `input` - ;[compiler, scanner] = await createCompiler(input) + ;[compiler, scanner] = await createCompiler(input, I) // Scan the directory for candidates - env.DEBUG && console.time('[@tailwindcss/cli] Scan for candidates') + DEBUG && I.start('Scan for candidates') let candidates = scanner.scan() - env.DEBUG && console.timeEnd('[@tailwindcss/cli] Scan for candidates') + DEBUG && I.end('Scan for candidates') // Setup new watchers + DEBUG && I.start('Setup new watchers') let newCleanupWatchers = await createWatchers(watchDirectories(scanner), handle) + DEBUG && I.end('Setup new watchers') // Clear old watchers + DEBUG && I.start('Cleanup old watchers') await cleanupWatchers() + DEBUG && I.end('Cleanup old watchers') cleanupWatchers = newCleanupWatchers // Re-compile the CSS - env.DEBUG && console.time('[@tailwindcss/cli] Build CSS') + DEBUG && I.start('Build CSS') compiledCss = compiler.build(candidates) - env.DEBUG && console.timeEnd('[@tailwindcss/cli] Build CSS') + DEBUG && I.end('Build CSS') } // Scan changed files only for incremental rebuilds. else if (rebuildStrategy === 'incremental') { - env.DEBUG && console.time('[@tailwindcss/cli] Scan for candidates') + DEBUG && I.start('Scan for candidates') let newCandidates = scanner.scanFiles(changedFiles) - env.DEBUG && console.timeEnd('[@tailwindcss/cli] Scan for candidates') + DEBUG && I.end('Scan for candidates') // No new candidates found which means we don't need to write to // disk, and can return early. @@ -261,12 +272,12 @@ export async function handle(args: Result>) { return } - env.DEBUG && console.time('[@tailwindcss/cli] Build CSS') + DEBUG && I.start('Build CSS') compiledCss = compiler.build(newCandidates) - env.DEBUG && console.timeEnd('[@tailwindcss/cli] Build CSS') + DEBUG && I.end('Build CSS') } - await write(compiledCss, args) + await write(compiledCss, args, I) let end = process.hrtime.bigint() eprintln(`Done in ${formatDuration(end - start)}`) @@ -295,13 +306,13 @@ export async function handle(args: Result>) { process.stdin.resume() } - env.DEBUG && console.time('[@tailwindcss/cli] Scan for candidates') + DEBUG && I.start('Scan for candidates') let candidates = scanner.scan() - env.DEBUG && console.timeEnd('[@tailwindcss/cli] Scan for candidates') - env.DEBUG && console.time('[@tailwindcss/cli] Build CSS') + DEBUG && I.end('Scan for candidates') + DEBUG && I.start('Build CSS') let output = await handleError(() => compiler.build(candidates)) - env.DEBUG && console.timeEnd('[@tailwindcss/cli] Build CSS') - await write(output, args) + DEBUG && I.end('Build CSS') + await write(output, args, I) let end = process.hrtime.bigint() eprintln(header()) diff --git a/packages/@tailwindcss-node/src/index.cts b/packages/@tailwindcss-node/src/index.cts index da67709545fb..2e79e25c7566 100644 --- a/packages/@tailwindcss-node/src/index.cts +++ b/packages/@tailwindcss-node/src/index.cts @@ -2,6 +2,7 @@ import * as Module from 'node:module' import { pathToFileURL } from 'node:url' import * as env from './env' export * from './compile' +export * from './instrumentation' export * from './normalize-path' export { env } diff --git a/packages/@tailwindcss-node/src/index.ts b/packages/@tailwindcss-node/src/index.ts index c4b88e981650..4ee2fe80587d 100644 --- a/packages/@tailwindcss-node/src/index.ts +++ b/packages/@tailwindcss-node/src/index.ts @@ -2,6 +2,7 @@ import * as Module from 'node:module' import { pathToFileURL } from 'node:url' import * as env from './env' export { __unstable__loadDesignSystem, compile, compileAst, Features } from './compile' +export * from './instrumentation' export * from './normalize-path' export { env } diff --git a/packages/@tailwindcss-node/src/instrumentation.test.ts b/packages/@tailwindcss-node/src/instrumentation.test.ts new file mode 100644 index 000000000000..1f06796fdd3e --- /dev/null +++ b/packages/@tailwindcss-node/src/instrumentation.test.ts @@ -0,0 +1,61 @@ +import { stripVTControlCharacters } from 'util' +import { expect, it } from 'vitest' +import { Instrumentation } from './instrumentation' + +it('should add instrumentation', () => { + let I = new Instrumentation() + + I.start('Foo') + let x = 1 + for (let i = 0; i < 100; i++) { + I.start('Bar') + x **= 2 + I.end('Bar') + } + I.end('Foo') + + I.hit('Potato') + I.hit('Potato') + I.hit('Potato') + I.hit('Potato') + + expect.assertions(1) + + I.report((output) => { + expect(stripVTControlCharacters(output).replace(/\[.*\]/g, '[0.xxms]')).toMatchInlineSnapshot(` + " + Hits: + Potato × 4 + + Timers: + [0.xxms] Foo + [0.xxms] ↳ Bar × 100 + " + `) + }) +}) + +it('should auto end pending timers when reporting', () => { + let I = new Instrumentation() + + I.start('Foo') + let x = 1 + for (let i = 0; i < 100; i++) { + I.start('Bar') + x **= 2 + I.end('Bar') + } + I.start('Baz') + + expect.assertions(1) + + I.report((output) => { + expect(stripVTControlCharacters(output).replace(/\[.*\]/g, '[0.xxms]')).toMatchInlineSnapshot(` + " + [0.xxms] Foo + [0.xxms] ↳ Bar × 100 + [0.xxms] ↳ Baz + " + `) + }) +}) diff --git a/packages/@tailwindcss-node/src/instrumentation.ts b/packages/@tailwindcss-node/src/instrumentation.ts new file mode 100644 index 000000000000..c408c4db786f --- /dev/null +++ b/packages/@tailwindcss-node/src/instrumentation.ts @@ -0,0 +1,105 @@ +import { DefaultMap } from '../../tailwindcss/src/utils/default-map' +import * as env from './env' + +export class Instrumentation implements Disposable { + #hits = new DefaultMap(() => ({ value: 0 })) + #timers = new DefaultMap(() => ({ value: 0n })) + #timerStack: { id: string; label: string; namespace: string; value: bigint }[] = [] + + constructor(private defaultFlush = (message: string) => process.stderr.write(`${message}\n`)) {} + + hit(label: string) { + this.#hits.get(label).value++ + } + + start(label: string) { + let namespace = this.#timerStack.map((t) => t.label).join('//') + let id = `${namespace}${namespace.length === 0 ? '' : '//'}${label}` + + this.#hits.get(id).value++ + + // Create the timer if it doesn't exist yet + this.#timers.get(id) + + this.#timerStack.push({ id, label, namespace, value: process.hrtime.bigint() }) + } + + end(label: string) { + let end = process.hrtime.bigint() + + if (this.#timerStack[this.#timerStack.length - 1].label !== label) { + throw new Error( + `Mismatched timer label: \`${label}\`, expected \`${ + this.#timerStack[this.#timerStack.length - 1].label + }\``, + ) + } + + let parent = this.#timerStack.pop()! + let elapsed = end - parent.value + this.#timers.get(parent.id).value += elapsed + } + + reset() { + this.#hits.clear() + this.#timers.clear() + this.#timerStack.splice(0) + } + + report(flush = this.defaultFlush) { + let output: string[] = [] + let hasHits = false + + // Auto end any pending timers + for (let i = this.#timerStack.length - 1; i >= 0; i--) { + this.end(this.#timerStack[i].label) + } + + for (let [label, { value: count }] of this.#hits.entries()) { + if (this.#timers.has(label)) continue + if (output.length === 0) { + hasHits = true + output.push('Hits:') + } + + let depth = label.split('//').length + output.push(`${' '.repeat(depth)}${label} ${dim(blue(`× ${count}`))}`) + } + + if (this.#timers.size > 0 && hasHits) { + output.push('\nTimers:') + } + + let max = -Infinity + let computed = new Map() + for (let [label, { value }] of this.#timers) { + let x = `${(Number(value) / 1e6).toFixed(2)}ms` + computed.set(label, x) + max = Math.max(max, x.length) + } + + for (let label of this.#timers.keys()) { + let depth = label.split('//').length + output.push( + `${dim(`[${computed.get(label)!.padStart(max, ' ')}]`)}${' '.repeat(depth - 1)}${depth === 1 ? ' ' : dim(' ↳ ')}${label.split('//').pop()} ${ + this.#hits.get(label).value === 1 ? '' : dim(blue(`× ${this.#hits.get(label).value}`)) + }`.trimEnd(), + ) + } + + flush(`\n${output.join('\n')}\n`) + this.reset() + } + + [Symbol.dispose]() { + env.DEBUG && this.report() + } +} + +function dim(input: string) { + return `\u001b[2m${input}\u001b[22m` +} + +function blue(input: string) { + return `\u001b[34m${input}\u001b[39m` +} diff --git a/packages/@tailwindcss-postcss/src/index.ts b/packages/@tailwindcss-postcss/src/index.ts index 52a1e2efac1b..c49c53ecf1ec 100644 --- a/packages/@tailwindcss-postcss/src/index.ts +++ b/packages/@tailwindcss-postcss/src/index.ts @@ -1,15 +1,17 @@ import QuickLRU from '@alloc/quick-lru' -import { compileAst, env, Features } from '@tailwindcss/node' +import { compileAst, env, Features, Instrumentation } from '@tailwindcss/node' import { clearRequireCache } from '@tailwindcss/node/require-cache' import { Scanner } from '@tailwindcss/oxide' import { Features as LightningCssFeatures, transform } from 'lightningcss' import fs from 'node:fs' -import path from 'node:path' +import path, { relative } from 'node:path' import postcss, { type AcceptedPlugin, type PluginCreator } from 'postcss' import { toCss, type AstNode } from '../../tailwindcss/src/ast' import { cssAstToPostCssAst, postCssAstToCssAst } from './ast' import fixRelativePathsPlugin from './postcss-fix-relative-paths' +const DEBUG = env.DEBUG + interface CacheEntry { mtimes: Map compiler: null | Awaited> @@ -19,7 +21,7 @@ interface CacheEntry { optimizedPostCssAst: postcss.Root fullRebuildPaths: string[] } -let cache = new QuickLRU({ maxSize: 50 }) +const cache = new QuickLRU({ maxSize: 50 }) function getContextFromCache(inputFile: string, opts: PluginOptions): CacheEntry { let key = `${inputFile}:${opts.base ?? ''}:${JSON.stringify(opts.optimize)}` @@ -62,10 +64,15 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin { { postcssPlugin: 'tailwindcss', async Once(root, { result }) { - env.DEBUG && console.time('[@tailwindcss/postcss] Total time in @tailwindcss/postcss') + using I = new Instrumentation() + + let inputFile = result.opts.from ?? '' + + DEBUG && I.start(`[@tailwindcss/postcss] ${relative(base, inputFile)}`) // Bail out early if this is guaranteed to be a non-Tailwind CSS file. { + DEBUG && I.start('Quick bail check') let canBail = true root.walkAtRules((node) => { if ( @@ -79,33 +86,35 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin { return false } }) - if (canBail) { - env.DEBUG && - console.timeEnd('[@tailwindcss/postcss] Total time in @tailwindcss/postcss') - return - } + if (canBail) return + DEBUG && I.end('Quick bail check') } - let inputFile = result.opts.from ?? '' let context = getContextFromCache(inputFile, opts) let inputBasePath = path.dirname(path.resolve(inputFile)) async function createCompiler() { - env.DEBUG && console.time('[@tailwindcss/postcss] Setup compiler') + DEBUG && I.start('Setup compiler') if (context.fullRebuildPaths.length > 0 && !isInitialBuild) { clearRequireCache(context.fullRebuildPaths) } context.fullRebuildPaths = [] - let compiler = await compileAst(postCssAstToCssAst(root), { + DEBUG && I.start('PostCSS AST -> Tailwind CSS AST') + let ast = postCssAstToCssAst(root) + DEBUG && I.end('PostCSS AST -> Tailwind CSS AST') + + DEBUG && I.start('Create compiler') + let compiler = await compileAst(ast, { base: inputBasePath, onDependency: (path) => { context.fullRebuildPaths.push(path) }, }) + DEBUG && I.end('Create compiler') - env.DEBUG && console.timeEnd('[@tailwindcss/postcss] Setup compiler') + DEBUG && I.end('Setup compiler') return compiler } @@ -124,6 +133,7 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin { let rebuildStrategy: 'full' | 'incremental' = 'incremental' // Track file modification times to CSS files + DEBUG && I.start('Register full rebuild paths') { for (let file of context.fullRebuildPaths) { result.messages.push({ @@ -156,6 +166,7 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin { context.mtimes.set(file, changedTime) } } + DEBUG && I.end('Register full rebuild paths') if ( rebuildStrategy === 'full' && @@ -167,6 +178,7 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin { } if (context.scanner === null || rebuildStrategy === 'full') { + DEBUG && I.start('Setup scanner') let sources = (() => { // Disable auto source detection if (context.compiler.root === 'none') { @@ -184,14 +196,16 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin { // Look for candidates used to generate the CSS context.scanner = new Scanner({ sources }) + DEBUG && I.end('Setup scanner') } - env.DEBUG && console.time('[@tailwindcss/postcss] Scan for candidates') + DEBUG && I.start('Scan for candidates') let candidates = context.compiler.features & Features.Utilities ? context.scanner.scan() : [] - env.DEBUG && console.timeEnd('[@tailwindcss/postcss] Scan for candidates') + DEBUG && I.end('Scan for candidates') if (context.compiler.features & Features.Utilities) { + DEBUG && I.start('Register dependency messages') // Add all found files as direct dependencies for (let file of context.scanner.files) { result.messages.push({ @@ -230,34 +244,43 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin { }) } } + DEBUG && I.end('Register dependency messages') } - env.DEBUG && console.time('[@tailwindcss/postcss] Build AST') + DEBUG && I.start('Build utilities') let tailwindCssAst = context.compiler.build(candidates) - env.DEBUG && console.timeEnd('[@tailwindcss/postcss] Build AST') + DEBUG && I.end('Build utilities') if (context.tailwindCssAst !== tailwindCssAst) { if (optimize) { - env.DEBUG && console.time('[@tailwindcss/postcss] Optimize CSS') - context.optimizedPostCssAst = postcss.parse( - optimizeCss(toCss(tailwindCssAst), { - minify: typeof optimize === 'object' ? optimize.minify : true, - }), - result.opts, - ) - env.DEBUG && console.timeEnd('[@tailwindcss/postcss] Optimize CSS') + DEBUG && I.start('Optimization') + + DEBUG && I.start('AST -> CSS') + let css = toCss(tailwindCssAst) + DEBUG && I.end('AST -> CSS') + + DEBUG && I.start('Lightning CSS') + let ast = optimizeCss(css, { + minify: typeof optimize === 'object' ? optimize.minify : true, + }) + DEBUG && I.end('Lightning CSS') + + DEBUG && I.start('CSS -> PostCSS AST') + context.optimizedPostCssAst = postcss.parse(ast, result.opts) + DEBUG && I.end('CSS -> PostCSS AST') + + DEBUG && I.end('Optimization') } else { // Convert our AST to a PostCSS AST - env.DEBUG && console.time('[@tailwindcss/postcss] Transform CSS AST into PostCSS AST') + DEBUG && I.start('Transform Tailwind CSS AST into PostCSS AST') context.cachedPostCssAst = cssAstToPostCssAst(tailwindCssAst, root.source) - env.DEBUG && - console.timeEnd('[@tailwindcss/postcss] Transform CSS AST into PostCSS AST') + DEBUG && I.end('Transform Tailwind CSS AST into PostCSS AST') } } context.tailwindCssAst = tailwindCssAst - env.DEBUG && console.time('[@tailwindcss/postcss] Update PostCSS AST') + DEBUG && I.start('Update PostCSS AST') root.removeAll() root.append( optimize @@ -268,9 +291,9 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin { // Trick PostCSS into thinking the indent is 2 spaces, so it uses that // as the default instead of 4. root.raws.indent = ' ' + DEBUG && I.end('Update PostCSS AST') - env.DEBUG && console.timeEnd('[@tailwindcss/postcss] Update PostCSS AST') - env.DEBUG && console.timeEnd('[@tailwindcss/postcss] Total time in @tailwindcss/postcss') + DEBUG && I.end(`[@tailwindcss/postcss] ${relative(base, inputFile)}`) }, }, ], diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts index a6ba5db3f2bb..b85538b9f259 100644 --- a/packages/@tailwindcss-vite/src/index.ts +++ b/packages/@tailwindcss-vite/src/index.ts @@ -1,4 +1,4 @@ -import { compile, env, Features, normalizePath } from '@tailwindcss/node' +import { compile, env, Features, Instrumentation, normalizePath } from '@tailwindcss/node' import { clearRequireCache } from '@tailwindcss/node/require-cache' import { Scanner } from '@tailwindcss/oxide' import { Features as LightningCssFeatures, transform } from 'lightningcss' @@ -6,6 +6,7 @@ import fs from 'node:fs/promises' import path from 'node:path' import type { Plugin, ResolvedConfig, Rollup, Update, ViteDevServer } from 'vite' +const DEBUG = env.DEBUG const SPECIAL_QUERY_RE = /[?&](raw|url)\b/ export default function tailwindcss(): Plugin[] { @@ -110,15 +111,19 @@ export default function tailwindcss(): Plugin[] { } } - async function regenerateOptimizedCss(root: Root, addWatchFile: (file: string) => void) { + async function regenerateOptimizedCss( + root: Root, + addWatchFile: (file: string) => void, + I: Instrumentation, + ) { let content = root.lastContent - let generated = await root.generate(content, addWatchFile) + let generated = await root.generate(content, addWatchFile, I) if (generated === false) { return } - env.DEBUG && console.time('[@tailwindcss/vite] Optimize CSS') + DEBUG && I.start('Optimize CSS') let result = optimizeCss(generated, { minify }) - env.DEBUG && console.timeEnd('[@tailwindcss/vite] Optimize CSS') + DEBUG && I.end('Optimize CSS') return result } @@ -215,6 +220,9 @@ export default function tailwindcss(): Plugin[] { async transform(src, id, options) { if (!isPotentialCssRootFile(id)) return + using I = new Instrumentation() + I.start('[@tailwindcss/vite] Generate CSS (serve)') + let root = roots.get(id) // If the root was built outside of the transform hook (e.g. in the @@ -241,7 +249,7 @@ export default function tailwindcss(): Plugin[] { await Promise.all(servers.map((server) => server.waitForRequestsIdle(id))) } - let generated = await root.generate(src, (file) => this.addWatchFile(file)) + let generated = await root.generate(src, (file) => this.addWatchFile(file), I) if (!generated) { roots.delete(id) return src @@ -259,6 +267,9 @@ export default function tailwindcss(): Plugin[] { async transform(src, id) { if (!isPotentialCssRootFile(id)) return + using I = new Instrumentation() + I.start('[@tailwindcss/vite] Generate CSS (build)') + let root = roots.get(id) // If the root was built outside of the transform hook (e.g. in the @@ -277,7 +288,7 @@ export default function tailwindcss(): Plugin[] { // We do a first pass to generate valid CSS for the downstream plugins. // However, since not all candidates are guaranteed to be extracted by // this time, we have to re-run a transform for the root later. - let generated = await root.generate(src, (file) => this.addWatchFile(file)) + let generated = await root.generate(src, (file) => this.addWatchFile(file), I) if (!generated) { roots.delete(id) return src @@ -289,6 +300,9 @@ export default function tailwindcss(): Plugin[] { // We must run before `enforce: post` so the updated chunks are picked up // by vite:css-post. async renderStart() { + using I = new Instrumentation() + I.start('[@tailwindcss/vite] (render start)') + for (let [id, root] of roots.entries()) { // Do not do a second render pass on Svelte `