Skip to content

Add incremental rebuilds to @tailwindcss/cli #13169

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
104 changes: 50 additions & 54 deletions packages/@tailwindcss-cli/src/commands/build/index.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -99,25 +92,42 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
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<ReturnType<typeof options>>) {
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()
Expand Down Expand Up @@ -162,26 +172,14 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
// 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']
Expand All @@ -191,25 +189,19 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
`,
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)}`)
Expand Down Expand Up @@ -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())
Expand All @@ -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),
),
])
}