[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
This commit is contained in:
Robin Malfait 2024-03-08 18:36:07 +01:00 • committed by GitHub
parent 7132875d02
commit c550a62be4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 51 additions and 16 deletions

View file

@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Add Android builds ([#13115](https://github.com/tailwindlabs/tailwindcss/pull/13115))
- Make CSS optimization and minification configurable ([#13130](https://github.com/tailwindlabs/tailwindcss/pull/13130))
## [4.0.0-alpha.6] - 2024-03-07

View file

@ -46,9 +46,13 @@ export function options() {
},
'--minify': {
type: 'boolean',
description: 'Minify the output',
description: 'Optimize and minify the output',
alias: '-m',
},
'--optimize': {
type: 'boolean',
description: 'Optimize the output without minifying',
},
'--cwd': {
type: 'string',
description: 'The current working directory',
@ -96,10 +100,15 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
)
// Compile the input
let result = optimizeCss(compile(input, candidates), {
file: args['--input'] ?? 'input.css',
minify: args['--minify'],
})
let result = compile(input, candidates)
// Optimize the output
if (args['--minify'] || args['--optimize']) {
result = optimizeCss(result, {
file: args['--input'] ?? 'input.css',
minify: args['--minify'] ?? false,
})
}
// Write the output
if (args['--output']) {
@ -184,10 +193,15 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
}
// Compile the input
let result = optimizeCss(compile(input, candidates), {
file: args['--input'] ?? 'input.css',
minify: args['--minify'],
})
let result = compile(input, candidates)
// Optimize the output
if (args['--minify'] || args['--optimize']) {
result = optimizeCss(result, {
file: args['--input'] ?? 'input.css',
minify: args['--minify'] ?? false,
})
}
// Write the output
if (args['--output']) {

View file

@ -18,7 +18,9 @@ beforeEach(async () => {
})
test("`@import 'tailwindcss'` is replaced with the generated CSS", async () => {
let processor = postcss([tailwindcss({ base: `${__dirname}/fixtures/example-project` })])
let processor = postcss([
tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
])
let result = await processor.process(`@import 'tailwindcss'`, { from: INPUT_CSS_PATH })
@ -47,7 +49,9 @@ test("`@import 'tailwindcss'` is replaced with the generated CSS", async () => {
})
test('output is optimized by Lightning CSS', async () => {
let processor = postcss([tailwindcss({ base: `${__dirname}/fixtures/example-project` })])
let processor = postcss([
tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
])
// `@apply` is used because Lightning is skipped if neither `@tailwind` nor
// `@apply` is used.
@ -82,7 +86,9 @@ test('output is optimized by Lightning CSS', async () => {
})
test('@apply can be used without emitting the theme in the CSS file', async () => {
let processor = postcss([tailwindcss({ base: `${__dirname}/fixtures/example-project` })])
let processor = postcss([
tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
])
// `@apply` is used because Lightning is skipped if neither `@tailwind` nor
// `@apply` is used.
@ -112,7 +118,7 @@ describe('processing without specifying a base path', () => {
afterEach(() => unlink(filepath))
test('the current working directory is used by default', async () => {
let processor = postcss([tailwindcss()])
let processor = postcss([tailwindcss({ optimize: { minify: false } })])
let result = await processor.process(`@import "tailwindcss"`, { from: INPUT_CSS_PATH })

View file

@ -6,10 +6,14 @@ 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',
@ -38,7 +42,13 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
function replaceCss(css: string) {
root.removeAll()
root.append(postcss.parse(optimizeCss(css), result.opts))
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

View file

@ -61,7 +61,11 @@ export default function tailwindcss(): Plugin[] {
}
function generateCss(css: string) {
return optimizeCss(compile(css, Array.from(candidates)), { minify })
return compile(css, Array.from(candidates))
}
function generateOptimizedCss(css: string) {
return optimizeCss(generateCss(css), { minify })
}
// In dev mode, there isn't a hook to signal that we've seen all files. We use
@ -168,7 +172,7 @@ export default function tailwindcss(): Plugin[] {
rawSource instanceof Uint8Array ? new TextDecoder().decode(rawSource) : rawSource
if (source.includes('@tailwind')) {
item.source = generateCss(source)
item.source = generateOptimizedCss(source)
}
}
},