mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Add incremental rebuilds to @tailwindcss/postcss (#13170)
* rename PostCSS plugin name from `tailwindcss-v4` to `@tailwindcss/postcss` * add incremental rebuilds to `@tailwindcss/postcss` * improve comment * update changelog * Simplify nested conditionals * Simplify more conditionals * Refactor file modification tracking --------- Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
This commit is contained in:
parent
a458e5ddda
commit
172bc5d822
@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Added
|
||||
|
||||
- Improve performance of incremental rebuilds for `@tailwindcss/cli` ([#13169](https://github.com/tailwindlabs/tailwindcss/pull/13169))
|
||||
- Improve performance of incremental rebuilds for `@tailwindcss/postcss` ([#13170](https://github.com/tailwindlabs/tailwindcss/pull/13170))
|
||||
|
||||
## [4.0.0-alpha.7] - 2024-03-08
|
||||
|
||||
|
||||
@ -1,8 +1,30 @@
|
||||
import { scanDir } from '@tailwindcss/oxide'
|
||||
import fs from 'fs'
|
||||
import postcss, { type AcceptedPlugin, type PluginCreator } from 'postcss'
|
||||
import postcssImport from 'postcss-import'
|
||||
import { compile, optimizeCss } from 'tailwindcss'
|
||||
|
||||
/**
|
||||
* A Map that can generate default values for keys that don't exist.
|
||||
* Generated default values are added to the map to avoid recomputation.
|
||||
*/
|
||||
class DefaultMap<T = string, V = any> extends Map<T, V> {
|
||||
constructor(private factory: (key: T, self: DefaultMap<T, V>) => V) {
|
||||
super()
|
||||
}
|
||||
|
||||
get(key: T): V {
|
||||
let value = super.get(key)
|
||||
|
||||
if (value === undefined) {
|
||||
value = this.factory(key, this)
|
||||
this.set(key, value)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
type PluginOptions = {
|
||||
// The base directory to scan for class candidates.
|
||||
base?: string
|
||||
@ -15,13 +37,51 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
|
||||
let base = opts.base ?? process.cwd()
|
||||
let optimize = opts.optimize ?? process.env.NODE_ENV === 'production'
|
||||
|
||||
let cache = new DefaultMap(() => {
|
||||
return {
|
||||
mtimes: new Map<string, number>(),
|
||||
build: null as null | ReturnType<typeof compile>['build'],
|
||||
css: '',
|
||||
optimizedCss: '',
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
postcssPlugin: 'tailwindcss-v4',
|
||||
postcssPlugin: '@tailwindcss/postcss',
|
||||
plugins: [
|
||||
// We need to run `postcss-import` first to handle `@import` rules.
|
||||
postcssImport(),
|
||||
|
||||
(root, result) => {
|
||||
let inputFile = result.opts.from ?? ''
|
||||
let context = cache.get(inputFile)
|
||||
|
||||
let rebuildStrategy: 'full' | 'incremental' = 'incremental'
|
||||
|
||||
// Track file modification times to CSS files
|
||||
{
|
||||
let files = result.messages.flatMap((message) => {
|
||||
if (message.type !== 'dependency') return []
|
||||
return message.file
|
||||
})
|
||||
files.push(inputFile)
|
||||
for (let file of files) {
|
||||
let changedTime = fs.statSync(file, { throwIfNoEntry: false })?.mtimeMs ?? null
|
||||
if (changedTime === null) {
|
||||
if (file === inputFile) {
|
||||
rebuildStrategy = 'full'
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
let prevTime = context.mtimes.get(file)
|
||||
if (prevTime === changedTime) continue
|
||||
|
||||
rebuildStrategy = 'full'
|
||||
context.mtimes.set(file, changedTime)
|
||||
}
|
||||
}
|
||||
|
||||
let hasApply = false
|
||||
let hasTailwind = false
|
||||
|
||||
@ -40,22 +100,7 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
|
||||
// Do nothing if neither `@tailwind` nor `@apply` is used
|
||||
if (!hasTailwind && !hasApply) return
|
||||
|
||||
function replaceCss(css: string) {
|
||||
root.removeAll()
|
||||
let output = css
|
||||
if (optimize) {
|
||||
output = optimizeCss(output, {
|
||||
minify: typeof optimize === 'object' ? optimize.minify : true,
|
||||
})
|
||||
}
|
||||
root.append(postcss.parse(output, result.opts))
|
||||
}
|
||||
|
||||
// No `@tailwind` means we don't have to look for candidates
|
||||
if (!hasTailwind) {
|
||||
replaceCss(compile(root.toString()).build([]))
|
||||
return
|
||||
}
|
||||
let css = ''
|
||||
|
||||
// Look for candidates used to generate the CSS
|
||||
let { candidates, files, globs } = scanDir({ base, globs: true })
|
||||
@ -64,7 +109,7 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
|
||||
for (let file of files) {
|
||||
result.messages.push({
|
||||
type: 'dependency',
|
||||
plugin: 'tailwindcss-v4',
|
||||
plugin: '@tailwindcss/postcss',
|
||||
file,
|
||||
parent: result.opts.from,
|
||||
})
|
||||
@ -76,14 +121,30 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
|
||||
for (let { base, glob } of globs) {
|
||||
result.messages.push({
|
||||
type: 'dir-dependency',
|
||||
plugin: 'tailwindcss-v4',
|
||||
plugin: '@tailwindcss/postcss',
|
||||
dir: base,
|
||||
glob,
|
||||
parent: result.opts.from,
|
||||
})
|
||||
}
|
||||
|
||||
replaceCss(compile(root.toString()).build(candidates))
|
||||
if (rebuildStrategy === 'full') {
|
||||
let { build } = compile(root.toString())
|
||||
context.build = build
|
||||
css = build(hasTailwind ? candidates : [])
|
||||
} else if (rebuildStrategy === 'incremental') {
|
||||
css = context.build!(candidates)
|
||||
}
|
||||
|
||||
// Replace CSS
|
||||
if (css !== context.css && optimize) {
|
||||
context.optimizedCss = optimizeCss(css, {
|
||||
minify: typeof optimize === 'object' ? optimize.minify : true,
|
||||
})
|
||||
}
|
||||
context.css = css
|
||||
root.removeAll()
|
||||
root.append(postcss.parse(optimize ? context.optimizedCss : context.css, result.opts))
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user