Improve debug logs (#15303)

This PR improves the debug logs for the `@tailwindcss/postcss`
integration. It uses custom instrumentation to provide a nested but
detailed overview of where time is spent during the build process.

The updated logs look like this:
```
[0.15ms] [@tailwindcss/postcss] src/app/geistsans_9fc57718.module.css
[0.14ms]   ↳ Quick bail check
[0.02ms] [@tailwindcss/postcss] src/app/geistsans_9fc57718.module.css
[0.01ms]   ↳ Quick bail check

[0.03ms] [@tailwindcss/postcss] src/app/geistmono_b9f59162.module.css
[0.02ms]   ↳ Quick bail check
[0.12ms] [@tailwindcss/postcss] src/app/geistmono_b9f59162.module.css
[0.11ms]   ↳ Quick bail check

[42.09ms] [@tailwindcss/postcss] src/app/globals.css
[ 0.01ms]   ↳ Quick bail check
[12.12ms]   ↳ Setup compiler
[ 0.11ms]     ↳ PostCSS AST -> Tailwind CSS AST
[11.99ms]     ↳ Create compiler
[ 0.07ms]   ↳ Register full rebuild paths
[ 0.06ms]   ↳ Setup scanner
[ 7.51ms]   ↳ Scan for candidates
[ 5.86ms]   ↳ Register dependency messages
[ 5.88ms]   ↳ Build utilities
[ 8.34ms]   ↳ Optimization
[ 0.23ms]     ↳ AST -> CSS
[ 4.20ms]     ↳ Lightning CSS
[ 3.89ms]     ↳ CSS -> PostCSS AST
[ 1.97ms]   ↳ Update PostCSS AST
```
This commit is contained in:
Robin Malfait 2024-12-11 15:27:20 +01:00 committed by GitHub
parent 2cbc915193
commit bcf70990a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 301 additions and 77 deletions

View File

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

View File

@ -24,7 +24,7 @@ pub mod paths;
pub mod scanner;
static SHOULD_TRACE: sync::LazyLock<bool> = 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() {

View File

@ -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<T>(fn: () => T): Promise<T> {
}
export async function handle(args: Result<ReturnType<typeof options>>) {
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<ReturnType<typeof options>>) {
optimizedCss: '',
}
async function write(css: string, args: Result<ReturnType<typeof options>>) {
async function write(css: string, args: Result<ReturnType<typeof options>>, 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<ReturnType<typeof options>>) {
}
// 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<ReturnType<typeof options>>) {
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<ReturnType<typeof options>>) {
})().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<ReturnType<typeof options>>) {
// 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<ReturnType<typeof options>>) {
} 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<ReturnType<typeof options>>) {
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<ReturnType<typeof options>>) {
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<ReturnType<typeof options>>) {
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())

View File

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

View File

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

View File

@ -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
"
`)
})
})

View File

@ -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<string, string>()
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`
}

View File

@ -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<string, number>
compiler: null | Awaited<ReturnType<typeof compileAst>>
@ -19,7 +21,7 @@ interface CacheEntry {
optimizedPostCssAst: postcss.Root
fullRebuildPaths: string[]
}
let cache = new QuickLRU<string, CacheEntry>({ maxSize: 50 })
const cache = new QuickLRU<string, CacheEntry>({ 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)}`)
},
},
],

View File

@ -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 `<style>` tags.
if (isSvelteStyle(id)) continue
@ -301,6 +315,7 @@ export default function tailwindcss(): Plugin[] {
// before and the dependencies should not be changed (only the
// candidate list might have)
() => {},
I,
)
if (!generated) {
roots.delete(id)
@ -454,6 +469,7 @@ class Root {
public async generate(
content: string,
addWatchFile: (file: string) => void,
I: Instrumentation,
): Promise<string | false> {
this.lastContent = content
@ -464,7 +480,7 @@ class Root {
clearRequireCache(Array.from(this.dependencies))
this.dependencies = new Set([idToPath(inputPath)])
env.DEBUG && console.time('[@tailwindcss/vite] Setup compiler')
DEBUG && I.start('Setup compiler')
this.compiler = await compile(content, {
base: inputBase,
shouldRewriteUrls: true,
@ -476,7 +492,7 @@ class Root {
customCssResolver: this.customCssResolver,
customJsResolver: this.customJsResolver,
})
env.DEBUG && console.timeEnd('[@tailwindcss/vite] Setup compiler')
DEBUG && I.end('Setup compiler')
let sources = (() => {
// Disable auto source detection
@ -509,11 +525,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')
DEBUG && I.start('Scan for candidates')
for (let candidate of this.scanner.scan()) {
this.candidates.add(candidate)
}
env.DEBUG && console.timeEnd('[@tailwindcss/vite] Scan for candidates')
DEBUG && I.end('Scan for candidates')
}
if (this.compiler.features & Features.Utilities) {
@ -561,13 +577,13 @@ class Root {
this.requiresRebuild = true
env.DEBUG && console.time('[@tailwindcss/vite] Build CSS')
DEBUG && I.start('Build CSS')
let result = this.compiler.build(
this.overwriteCandidates
? this.overwriteCandidates
: [...this.sharedCandidates(), ...this.candidates],
)
env.DEBUG && console.timeEnd('[@tailwindcss/vite] Build CSS')
DEBUG && I.end('Build CSS')
return result
}
@ -634,6 +650,8 @@ function svelteProcessor(roots: DefaultMap<string, Root>): Plugin {
markup: string
}) {
if (!filename) return
using I = new Instrumentation()
DEBUG && I.start('[@tailwindcss/vite] Preprocess svelte')
// Create the ID used by Vite to identify the `<style>` contents. This
// way, the Vite `transform` hook can find the right root and thus
@ -660,8 +678,10 @@ function svelteProcessor(roots: DefaultMap<string, Root>): Plugin {
{ content: markup, file: filename, extension: 'svelte' },
])
let generated = await root.generate(content, (file) =>
root.builtBeforeTransform?.push(file),
let generated = await root.generate(
content,
(file) => root.builtBeforeTransform?.push(file),
I,
)
if (!generated) {

View File

@ -1,7 +1,8 @@
{
"compilerOptions": {
"lib": ["ESNext", "DOM"],
"target": "ESNext",
"target": "es2022",
"lib": ["es2022", "esnext.disposable", "dom"],
"module": "ESNext",
"moduleDetection": "force",
"allowJs": true,