use incremental builds in PostCSS
This commit is contained in:
parent
316190fe5a
commit
a99bcfba60
1 changed files with 100 additions and 21 deletions
|
|
@ -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,6 +37,15 @@ 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>(),
|
||||
rebuild: null as null | ReturnType<typeof compile>['rebuild'],
|
||||
previousCss: '',
|
||||
previousOptimizedCss: '',
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
postcssPlugin: '@tailwindcss/postcss',
|
||||
plugins: [
|
||||
|
|
@ -22,6 +53,38 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
|
|||
postcssImport(),
|
||||
|
||||
(root, result) => {
|
||||
let from = result.opts.from ?? ''
|
||||
let context = cache.get(from)
|
||||
|
||||
let rebuildStrategy: 'full' | 'incremental' = 'incremental'
|
||||
|
||||
// Bookkeeping — track file modification times to CSS files
|
||||
{
|
||||
let changedTime = fs.statSync(from, { throwIfNoEntry: false })?.mtimeMs ?? null
|
||||
if (changedTime !== null) {
|
||||
let prevTime = context.mtimes.get(from)
|
||||
if (prevTime !== changedTime) {
|
||||
rebuildStrategy = 'full'
|
||||
context.mtimes.set(from, changedTime)
|
||||
}
|
||||
} else {
|
||||
rebuildStrategy = 'full'
|
||||
}
|
||||
for (let message of result.messages) {
|
||||
if (message.type === 'dependency') {
|
||||
let file = message.file as string
|
||||
let changedTime = fs.statSync(file, { throwIfNoEntry: false })?.mtimeMs ?? null
|
||||
if (changedTime !== null) {
|
||||
let prevTime = context.mtimes.get(file)
|
||||
if (prevTime !== changedTime) {
|
||||
rebuildStrategy = 'full'
|
||||
context.mtimes.set(file, changedTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let hasApply = false
|
||||
let hasTailwind = false
|
||||
|
||||
|
|
@ -40,24 +103,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 : false,
|
||||
})
|
||||
}
|
||||
root.append(postcss.parse(output, result.opts))
|
||||
}
|
||||
|
||||
let compileResult = compile(root.toString(), [])
|
||||
|
||||
// No `@tailwind` means we don't have to look for candidates
|
||||
if (!hasTailwind) {
|
||||
replaceCss(compileResult.css)
|
||||
return
|
||||
}
|
||||
let css = ''
|
||||
|
||||
// Look for candidates used to generate the CSS
|
||||
let { candidates, files, globs } = scanDir({ base, globs: true })
|
||||
|
|
@ -85,9 +131,42 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
|
|||
})
|
||||
}
|
||||
|
||||
console.time('Compile')
|
||||
replaceCss(compileResult.rebuild(candidates))
|
||||
console.timeEnd('Compile')
|
||||
if (rebuildStrategy === 'full') {
|
||||
if (hasTailwind) {
|
||||
let compileResult = compile(root.toString(), candidates)
|
||||
css = compileResult.css
|
||||
context.rebuild = compileResult.rebuild.bind(compileResult)
|
||||
} else {
|
||||
css = compile(root.toString(), []).css
|
||||
}
|
||||
} else if (rebuildStrategy === 'incremental') {
|
||||
css = context.rebuild!(candidates)
|
||||
}
|
||||
|
||||
function replaceCss(css: string) {
|
||||
root.removeAll()
|
||||
root.append(postcss.parse(css, result.opts))
|
||||
}
|
||||
|
||||
// Replace CSS
|
||||
if (css === context.previousCss) {
|
||||
if (optimize) {
|
||||
replaceCss(context.previousOptimizedCss)
|
||||
} else {
|
||||
replaceCss(css)
|
||||
}
|
||||
} else {
|
||||
if (optimize) {
|
||||
let optimizedCss = optimizeCss(css, {
|
||||
minify: typeof optimize === 'object' ? optimize.minify : false,
|
||||
})
|
||||
replaceCss(optimizedCss)
|
||||
context.previousOptimizedCss = optimizedCss
|
||||
} else {
|
||||
replaceCss(css)
|
||||
}
|
||||
context.previousCss = css
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue