Expose timing information in debug mode (#14553)

This PR exposes when using the the `DEBUG` environment variable. This
follows the `DEBUG` conventions where:

- `DEBUG=1`
- `DEBUG=true`
- `DEBUG=*`
- `DEBUG=tailwindcss`

Will enable the debug information, but when using:

- `DEBUG=0`
- `DEBUG=false`
- `DEBUG=-tailwindcss`

It will not.

This currently only exposes some timings related to:

1. Scanning for candidates
2. Building the CSS
3. Optimizing the CSS

We can implement a more advanced version of this where we also expose
more fine grained information such as the files we scanned, the amount
of candidates we found and so on. But I believe that this will be enough
to start triaging performance related issues.
This commit is contained in:
Robin Malfait 2024-09-30 16:39:21 +02:00 committed by GitHub
parent 4f8ca556cf
commit ab82efab7d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 95 additions and 8 deletions

View File

@ -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))

View File

@ -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<ReturnType<typeof options>>) {
// 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<ReturnType<typeof options>>) {
}
// 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<ReturnType<typeof options>>) {
})
// 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<ReturnType<typeof options>>) {
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<ReturnType<typeof options>>) {
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())

View File

@ -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
}

View File

@ -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.

View File

@ -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.

View File

@ -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')
},
},
],

View File

@ -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
}
}