diff --git a/CHANGELOG.md b/CHANGELOG.md index 54f6806d3de2..25f5e5b536f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Replace `--radius-none` and `--radius-full` theme values with static `rounded-none` and `rounded-full` utilities ([#13186](https://github.com/tailwindlabs/tailwindcss/pull/13186)) +### Added + +- Improve performance of incremental rebuilds for `@tailwindcss/cli` ([#13169](https://github.com/tailwindlabs/tailwindcss/pull/13169)) + ## [4.0.0-alpha.7] - 2024-03-08 ### Added diff --git a/packages/@tailwindcss-cli/src/commands/build/index.ts b/packages/@tailwindcss-cli/src/commands/build/index.ts index d9298485184d..e4f65055e39d 100644 --- a/packages/@tailwindcss-cli/src/commands/build/index.ts +++ b/packages/@tailwindcss-cli/src/commands/build/index.ts @@ -1,12 +1,5 @@ import watcher from '@parcel/watcher' -import { - IO, - Parsing, - clearCache, - scanDir, - scanFiles, - type ChangedContent, -} from '@tailwindcss/oxide' +import { IO, Parsing, scanDir, scanFiles, type ChangedContent } from '@tailwindcss/oxide' import { existsSync } from 'node:fs' import fs from 'node:fs/promises' import path from 'node:path' @@ -99,25 +92,42 @@ export async function handle(args: Result>) { args['--input'] ?? base, ) - // Compile the input - let { build } = compile(input) - let result = build(candidates) - - // Optimize the output - if (args['--minify'] || args['--optimize']) { - result = optimizeCss(result, { - file: args['--input'] ?? 'input.css', - minify: args['--minify'] ?? false, - }) + let previous = { + css: '', + optimizedCss: '', } - // Write the output - if (args['--output']) { - await outputFile(args['--output'], result) - } else { - println(result) + async function write(css: string, args: Result>) { + let output = css + + // Optimize the output + if (args['--minify'] || args['--optimize']) { + if (css !== previous.css) { + let optimizedCss = optimizeCss(css, { + file: args['--input'] ?? 'input.css', + minify: args['--minify'] ?? false, + }) + previous.css = css + previous.optimizedCss = optimizedCss + output = optimizedCss + } else { + output = previous.optimizedCss + } + } + + // Write the output + if (args['--output']) { + await outputFile(args['--output'], output) + } else { + println(output) + } } + // Compile the input + let { build } = compile(input) + + await write(build(candidates), args) + let end = process.hrtime.bigint() eprintln(header()) eprintln() @@ -162,26 +172,14 @@ export async function handle(args: Result>) { // Re-compile the input let start = process.hrtime.bigint() + // Track the compiled CSS + let compiledCss = '' + // Scan the entire `base` directory for full rebuilds. if (rebuildStrategy === 'full') { - // Clear the cache because we need to re-scan the entire directory. - clearCache() - // Re-scan the directory to get the new `candidates`. candidates = scanDir({ base }).candidates - } - - // Scan changed files only for incremental rebuilds. - else if (rebuildStrategy === 'incremental') { - let uniqueCandidates = new Set(candidates) - for (let candidate of scanFiles(changedFiles, IO.Sequential | Parsing.Sequential)) { - uniqueCandidates.add(candidate) - } - candidates = Array.from(uniqueCandidates) - } - // Resolve the input - if (rebuildStrategy === 'full') { // Collect the new `input` and `cssImportPaths`. ;[input, cssImportPaths] = await handleImports( args['--input'] @@ -191,25 +189,19 @@ export async function handle(args: Result>) { `, args['--input'] ?? base, ) + + build = compile(input).build + compiledCss = build(candidates) } - // Compile the input - result = compile(input).build(candidates) + // Scan changed files only for incremental rebuilds. + else if (rebuildStrategy === 'incremental') { + let newCandidates = scanFiles(changedFiles, IO.Sequential | Parsing.Sequential) - // Optimize the output - if (args['--minify'] || args['--optimize']) { - result = optimizeCss(result, { - file: args['--input'] ?? 'input.css', - minify: args['--minify'] ?? false, - }) + compiledCss = build(newCandidates) } - // Write the output - if (args['--output']) { - await outputFile(args['--output'], result) - } else { - println(result) - } + await write(compiledCss, args) let end = process.hrtime.bigint() eprintln(`Done in ${formatDuration(end - start)}`) @@ -244,7 +236,9 @@ function handleImports( // Relevant specification: // - CSS Import Resolve: https://csstools.github.io/css-import-resolve/ - if (!input.includes('@import')) return [input, []] + if (!input.includes('@import')) { + return [input, [file]] + } return postcss() .use(atImport()) @@ -254,6 +248,8 @@ function handleImports( // Use `result.messages` to get the imported files. This also includes the // current file itself. - result.messages.filter((msg) => msg.type === 'postcss-import').map((msg) => msg.file), + [file].concat( + result.messages.filter((msg) => msg.type === 'dependency').map((msg) => msg.file), + ), ]) }