mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2026-01-25 16:44:12 +00:00
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:
parent
2cbc915193
commit
bcf70990a7
@ -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
|
||||
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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 }
|
||||
|
||||
|
||||
@ -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 }
|
||||
|
||||
|
||||
61
packages/@tailwindcss-node/src/instrumentation.test.ts
Normal file
61
packages/@tailwindcss-node/src/instrumentation.test.ts
Normal 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
|
||||
"
|
||||
`)
|
||||
})
|
||||
})
|
||||
105
packages/@tailwindcss-node/src/instrumentation.ts
Normal file
105
packages/@tailwindcss-node/src/instrumentation.ts
Normal 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`
|
||||
}
|
||||
@ -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)}`)
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"target": "ESNext",
|
||||
"target": "es2022",
|
||||
"lib": ["es2022", "esnext.disposable", "dom"],
|
||||
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"allowJs": true,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user