tailwindcss/packages/@tailwindcss-postcss/src/index.ts
Robin Malfait c550a62be4
[v4] Make CSS optimization and minification configurable (#13130)
* only run Lightning CSS when passing `--minify` to the CLI

* only optimize the CSS when creating a production build

* add `optimize` option to PostCSS plugin

- The optimize option can be set to `true`, which will optimize
  (unnesting, adding browser prefixes, lowering values) and minify
- The optimize option can also be set to `{ minify: false }`, which will
  optimize but not minify.

* default `optimize` option to the true for `NODE_ENV=production`

* add `--optimize` flag to CLI

This will only optimize the CSS output without minification.

* update `--minify` description

* update changelog
2024-03-08 18:36:07 +01:00

92 lines
2.8 KiB
TypeScript

import { scanDir } from '@tailwindcss/oxide'
import postcss, { type AcceptedPlugin, type PluginCreator } from 'postcss'
import postcssImport from 'postcss-import'
import { compile, optimizeCss } from 'tailwindcss'
type PluginOptions = {
// The base directory to scan for class candidates.
base?: string
// Optimize the output CSS.
optimize?: boolean | { minify?: boolean }
}
function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
let base = opts.base ?? process.cwd()
let optimize = opts.optimize ?? process.env.NODE_ENV === 'production'
return {
postcssPlugin: 'tailwindcss-v4',
plugins: [
// We need to run `postcss-import` first to handle `@import` rules.
postcssImport(),
(root, result) => {
let hasApply = false
let hasTailwind = false
root.walkAtRules((rule) => {
if (rule.name === 'apply') {
hasApply = true
} else if (rule.name === 'tailwind') {
hasApply = true
hasTailwind = true
// If we've found `@tailwind` then we already
// know we have to run a "full" build
return false
}
})
// 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))
}
// No `@tailwind` means we don't have to look for candidates
if (!hasTailwind) {
replaceCss(compile(root.toString(), []))
return
}
// Look for candidates used to generate the CSS
let { candidates, files, globs } = scanDir({ base, globs: true })
// Add all found files as direct dependencies
for (let file of files) {
result.messages.push({
type: 'dependency',
plugin: 'tailwindcss-v4',
file,
parent: result.opts.from,
})
}
// Register dependencies so changes in `base` cause a rebuild while
// giving tools like Vite or Parcel a glob that can be used to limit
// the files that cause a rebuild to only those that match it.
for (let { base, glob } of globs) {
result.messages.push({
type: 'dir-dependency',
plugin: 'tailwindcss-v4',
dir: base,
glob,
parent: result.opts.from,
})
}
replaceCss(compile(root.toString(), candidates))
},
],
}
}
export default Object.assign(tailwindcss, { postcss: true }) as PluginCreator<PluginOptions>