diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ae894e4f..e40421238 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add support for prefixes ([#14501](https://github.com/tailwindlabs/tailwindcss/pull/14501)) +- Expose timing information in debug mode ([#14553](https://github.com/tailwindlabs/tailwindcss/pull/14553)) - _Experimental_: Add template codemods for migrating `bg-gradient-*` utilities to `bg-linear-*` ([#14537](https://github.com/tailwindlabs/tailwindcss/pull/14537])) - _Experimental_: Migrate `@import "tailwindcss/tailwind.css"` to `@import "tailwindcss"` ([#14514](https://github.com/tailwindlabs/tailwindcss/pull/14514)) diff --git a/packages/@tailwindcss-cli/src/commands/build/index.ts b/packages/@tailwindcss-cli/src/commands/build/index.ts index cf2fb9060..d937caf64 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 } from '@tailwindcss/node' +import { compile, env } from '@tailwindcss/node' import { clearRequireCache } from '@tailwindcss/node/require-cache' import { Scanner, type ChangedContent } from '@tailwindcss/oxide' import { Features, transform } from 'lightningcss' @@ -98,10 +98,12 @@ export async function handle(args: Result>) { // Optimize the output if (args['--minify'] || args['--optimize']) { if (css !== previous.css) { + env.DEBUG && console.time('[@tailwindcss/cli] Optimize CSS') let optimizedCss = optimizeCss(css, { file: args['--input'] ?? 'input.css', minify: args['--minify'] ?? false, }) + env.DEBUG && console.timeEnd('[@tailwindcss/cli] Optimize CSS') previous.css = css previous.optimizedCss = optimizedCss output = optimizedCss @@ -111,11 +113,13 @@ export async function handle(args: Result>) { } // Write the output + env.DEBUG && console.time('[@tailwindcss/cli] Write output') if (args['--output']) { await outputFile(args['--output'], output) } else { println(output) } + env.DEBUG && console.timeEnd('[@tailwindcss/cli] Write output') } let inputBasePath = @@ -206,18 +210,24 @@ export async function handle(args: Result>) { }) // Scan the directory for candidates + env.DEBUG && console.time('[@tailwindcss/cli] Scan for candidates') let candidates = scanner.scan() + env.DEBUG && console.timeEnd('[@tailwindcss/cli] Scan for candidates') // Setup new watchers cleanupWatchers = await createWatchers(watchDirectories(base, scanner), handle) // Re-compile the CSS + env.DEBUG && console.time('[@tailwindcss/cli] Build CSS') compiledCss = compiler.build(candidates) + env.DEBUG && console.timeEnd('[@tailwindcss/cli] Build CSS') } // Scan changed files only for incremental rebuilds. else if (rebuildStrategy === 'incremental') { + env.DEBUG && console.time('[@tailwindcss/cli] Scan for candidates') let newCandidates = scanner.scanFiles(changedFiles) + env.DEBUG && console.timeEnd('[@tailwindcss/cli] Scan for candidates') // No new candidates found which means we don't need to write to // disk, and can return early. @@ -227,7 +237,9 @@ export async function handle(args: Result>) { return } + env.DEBUG && console.time('[@tailwindcss/cli] Build CSS') compiledCss = compiler.build(newCandidates) + env.DEBUG && console.timeEnd('[@tailwindcss/cli] Build CSS') } await write(compiledCss, args) @@ -257,7 +269,13 @@ export async function handle(args: Result>) { process.stdin.resume() } - await write(compiler.build(scanner.scan()), args) + env.DEBUG && console.time('[@tailwindcss/cli] Scan for candidates') + let candidates = scanner.scan() + env.DEBUG && console.timeEnd('[@tailwindcss/cli] Scan for candidates') + env.DEBUG && console.time('[@tailwindcss/cli] Build CSS') + let output = compiler.build(candidates) + env.DEBUG && console.timeEnd('[@tailwindcss/cli] Build CSS') + await write(output, args) let end = process.hrtime.bigint() eprintln(header()) diff --git a/packages/@tailwindcss-node/src/env.ts b/packages/@tailwindcss-node/src/env.ts new file mode 100644 index 000000000..517ccc6c5 --- /dev/null +++ b/packages/@tailwindcss-node/src/env.ts @@ -0,0 +1,40 @@ +export const DEBUG = resolveDebug(process.env.DEBUG) + +function resolveDebug(debug: typeof process.env.DEBUG) { + if (debug === undefined) { + return false + } + + // Environment variables are strings, so convert to boolean + if (debug === 'true' || debug === '1') { + return true + } + + if (debug === 'false' || debug === '0') { + return false + } + + // Keep the debug convention into account: + // DEBUG=* -> This enables all debug modes + // DEBUG=projectA,projectB,projectC -> This enables debug for projectA, projectB and projectC + // DEBUG=projectA:* -> This enables all debug modes for projectA (if you have sub-types) + // DEBUG=projectA,-projectB -> This enables debug for projectA and explicitly disables it for projectB + + if (debug === '*') { + return true + } + + let debuggers = debug.split(',').map((d) => d.split(':')[0]) + + // Ignoring tailwindcss + if (debuggers.includes('-tailwindcss')) { + return false + } + + // Including tailwindcss + if (debuggers.includes('tailwindcss')) { + return true + } + + return false +} diff --git a/packages/@tailwindcss-node/src/index.cts b/packages/@tailwindcss-node/src/index.cts index ee0de7ff5..da6770954 100644 --- a/packages/@tailwindcss-node/src/index.cts +++ b/packages/@tailwindcss-node/src/index.cts @@ -1,7 +1,9 @@ import * as Module from 'node:module' import { pathToFileURL } from 'node:url' +import * as env from './env' export * from './compile' export * from './normalize-path' +export { env } // In Bun, ESM modules will also populate `require.cache`, so the module hook is // not necessary. diff --git a/packages/@tailwindcss-node/src/index.ts b/packages/@tailwindcss-node/src/index.ts index f42c4ff4e..523079b08 100644 --- a/packages/@tailwindcss-node/src/index.ts +++ b/packages/@tailwindcss-node/src/index.ts @@ -1,7 +1,9 @@ import * as Module from 'node:module' import { pathToFileURL } from 'node:url' +import * as env from './env' export * from './compile' export * from './normalize-path' +export { env } // In Bun, ESM modules will also populate `require.cache`, so the module hook is // not necessary. diff --git a/packages/@tailwindcss-postcss/src/index.ts b/packages/@tailwindcss-postcss/src/index.ts index 881445a80..7d818b3d1 100644 --- a/packages/@tailwindcss-postcss/src/index.ts +++ b/packages/@tailwindcss-postcss/src/index.ts @@ -1,4 +1,4 @@ -import { compile } from '@tailwindcss/node' +import { compile, env } from '@tailwindcss/node' import { clearRequireCache } from '@tailwindcss/node/require-cache' import { Scanner } from '@tailwindcss/oxide' import fs from 'fs' @@ -61,21 +61,26 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin { { postcssPlugin: 'tailwindcss', async OnceExit(root, { result }) { + env.DEBUG && console.time('[@tailwindcss/postcss] Total time in @tailwindcss/postcss') let inputFile = result.opts.from ?? '' let context = cache.get(inputFile) let inputBasePath = path.dirname(path.resolve(inputFile)) async function createCompiler() { + env.DEBUG && console.time('[@tailwindcss/postcss] Setup compiler') clearRequireCache(context.fullRebuildPaths) context.fullRebuildPaths = [] - return compile(root.toString(), { + let compiler = compile(root.toString(), { base: inputBasePath, onDependency: (path) => { context.fullRebuildPaths.push(path) }, }) + + env.DEBUG && console.timeEnd('[@tailwindcss/postcss] Setup compiler') + return compiler } // Setup the compiler if it doesn't exist yet. This way we can @@ -126,7 +131,9 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin { sources: context.compiler.globs, }) + env.DEBUG && console.time('[@tailwindcss/postcss] Scan for candidates') let candidates = scanner.scan() + env.DEBUG && console.timeEnd('[@tailwindcss/postcss] Scan for candidates') // Add all found files as direct dependencies for (let file of scanner.files) { @@ -154,17 +161,26 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin { if (rebuildStrategy === 'full') { context.compiler = await createCompiler() } + + env.DEBUG && console.time('[@tailwindcss/postcss] Build CSS') css = context.compiler.build(candidates) + env.DEBUG && console.timeEnd('[@tailwindcss/postcss] Build CSS') // Replace CSS if (css !== context.css && optimize) { + env.DEBUG && console.time('[@tailwindcss/postcss] Optimize CSS') context.optimizedCss = optimizeCss(css, { minify: typeof optimize === 'object' ? optimize.minify : true, }) + env.DEBUG && console.timeEnd('[@tailwindcss/postcss] Optimize CSS') } context.css = css + + env.DEBUG && console.time('[@tailwindcss/postcss] Update PostCSS AST') root.removeAll() root.append(postcss.parse(optimize ? context.optimizedCss : context.css, result.opts)) + env.DEBUG && console.timeEnd('[@tailwindcss/postcss] Update PostCSS AST') + env.DEBUG && console.timeEnd('[@tailwindcss/postcss] Total time in @tailwindcss/postcss') }, }, ], diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts index 53f2aecff..d64dca6ff 100644 --- a/packages/@tailwindcss-vite/src/index.ts +++ b/packages/@tailwindcss-vite/src/index.ts @@ -1,6 +1,5 @@ -import { compile, normalizePath } from '@tailwindcss/node' +import { compile, env, normalizePath } from '@tailwindcss/node' import { clearRequireCache } from '@tailwindcss/node/require-cache' - import { Scanner } from '@tailwindcss/oxide' import { Features, transform } from 'lightningcss' import path from 'path' @@ -92,7 +91,10 @@ export default function tailwindcss(): Plugin[] { if (generated === false) { return } - return optimizeCss(generated, { minify }) + env.DEBUG && console.time('[@tailwindcss/vite] Optimize CSS') + let result = optimizeCss(generated, { minify }) + env.DEBUG && console.timeEnd('[@tailwindcss/vite] Optimize CSS') + return result } // Manually run the transform functions of non-Tailwind plugins on the given CSS @@ -378,9 +380,11 @@ class Root { // This should not be here, but right now the Vite plugin is setup where we // setup a new scanner and compiler every time we request the CSS file // (regardless whether it actually changed or not). + env.DEBUG && console.time('[@tailwindcss/vite] Scan for candidates') for (let candidate of this.scanner.scan()) { this.candidates.add(candidate) } + env.DEBUG && console.timeEnd('[@tailwindcss/vite] Scan for candidates') // Watch individual files found via custom `@source` paths for (let file of this.scanner.files) { @@ -404,6 +408,10 @@ class Root { this.requiresRebuild = true - return this.compiler.build([...this.getSharedCandidates(), ...this.candidates]) + env.DEBUG && console.time('[@tailwindcss/vite] Build CSS') + let result = this.compiler.build([...this.getSharedCandidates(), ...this.candidates]) + env.DEBUG && console.timeEnd('[@tailwindcss/vite] Build CSS') + + return result } }