Skip to content
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
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- Nothing yet!
### Fixed

- Don't scan source files for utilities unless `@tailwind utilities` is present in the CSS in `@tailwindcss/postcss` and `@tailwindcss/vite` ([#15226](https://github.com/tailwindlabs/tailwindcss/pull/15226))
- Skip reserializing CSS files that don't use Tailwind features in `@tailwindcss/postcss` and `@tailwindcss/vite` ([#15226](https://github.com/tailwindlabs/tailwindcss/pull/15226))

## [4.0.0-beta.3] - 2024-11-27

Expand Down
3 changes: 3 additions & 0 deletions packages/@tailwindcss-node/src/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ import { pathToFileURL } from 'node:url'
import {
__unstable__loadDesignSystem as ___unstable__loadDesignSystem,
compile as _compile,
Features,
} from 'tailwindcss'
import { getModuleDependencies } from './get-module-dependencies'
import { rewriteUrls } from './urls'

export { Features }

export type Resolver = (id: string, base: string) => Promise<string | false | undefined>

export async function compile(
Expand Down
2 changes: 1 addition & 1 deletion packages/@tailwindcss-node/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as Module from 'node:module'
import { pathToFileURL } from 'node:url'
import * as env from './env'
export { __unstable__loadDesignSystem, compile } from './compile'
export { __unstable__loadDesignSystem, compile, Features } from './compile'
export * from './normalize-path'
export { env }

Expand Down
1 change: 1 addition & 0 deletions packages/@tailwindcss-postcss/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"devDependencies": {
"@types/node": "catalog:",
"@types/postcss-import": "14.0.3",
"dedent": "1.5.3",
"internal-example-plugin": "workspace:*",
"postcss-import": "^16.1.0"
}
Expand Down
50 changes: 38 additions & 12 deletions packages/@tailwindcss-postcss/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import dedent from 'dedent'
import { unlink, writeFile } from 'node:fs/promises'
import postcss from 'postcss'
import { afterEach, beforeEach, describe, expect, test } from 'vitest'
Expand All @@ -9,16 +10,20 @@ import tailwindcss from './index'
// We place it in packages/ because Vitest runs in the monorepo root,
// and packages/tailwindcss must be a sub-folder for
// @import 'tailwindcss' to work.
const INPUT_CSS_PATH = `${__dirname}/fixtures/example-project/input.css`
function inputCssFilePath() {
// Including the current test name to ensure that the cache is invalidated per
// test otherwise the cache will be used across tests.
return `${__dirname}/fixtures/example-project/input.css?test=${expect.getState().currentTestName}`
}

const css = String.raw
const css = dedent

test("`@import 'tailwindcss'` is replaced with the generated CSS", async () => {
let processor = postcss([
tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
])

let result = await processor.process(`@import 'tailwindcss'`, { from: INPUT_CSS_PATH })
let result = await processor.process(`@import 'tailwindcss'`, { from: inputCssFilePath() })

expect(result.css.trim()).toMatchSnapshot()

Expand Down Expand Up @@ -49,8 +54,6 @@ test('output is optimized by Lightning CSS', async () => {
tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
])

// `@apply` is used because Lightning is skipped if neither `@tailwind` nor
// `@apply` is used.
let result = await processor.process(
css`
@layer utilities {
Expand All @@ -65,7 +68,7 @@ test('output is optimized by Lightning CSS', async () => {
}
}
`,
{ from: INPUT_CSS_PATH },
{ from: inputCssFilePath() },
)

expect(result.css.trim()).toMatchInlineSnapshot(`
Expand All @@ -86,16 +89,14 @@ test('@apply can be used without emitting the theme in the CSS file', async () =
tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
])

// `@apply` is used because Lightning is skipped if neither `@tailwind` nor
// `@apply` is used.
let result = await processor.process(
css`
@import 'tailwindcss/theme.css' theme(reference);
.foo {
@apply text-red-500;
}
`,
{ from: INPUT_CSS_PATH },
{ from: inputCssFilePath() },
)

expect(result.css.trim()).toMatchInlineSnapshot(`
Expand All @@ -116,7 +117,7 @@ describe('processing without specifying a base path', () => {
test('the current working directory is used by default', async () => {
let processor = postcss([tailwindcss({ optimize: { minify: false } })])

let result = await processor.process(`@import "tailwindcss"`, { from: INPUT_CSS_PATH })
let result = await processor.process(`@import "tailwindcss"`, { from: inputCssFilePath() })

expect(result.css).toContain(
".md\\:\\[\\&\\:hover\\]\\:content-\\[\\'testing_default_base_path\\'\\]",
Expand All @@ -142,7 +143,7 @@ describe('plugins', () => {
@import 'tailwindcss/utilities';
@plugin './plugin.js';
`,
{ from: INPUT_CSS_PATH },
{ from: inputCssFilePath() },
)

expect(result.css.trim()).toMatchInlineSnapshot(`
Expand Down Expand Up @@ -202,7 +203,7 @@ describe('plugins', () => {
@import 'tailwindcss/utilities';
@plugin 'internal-example-plugin';
`,
{ from: INPUT_CSS_PATH },
{ from: inputCssFilePath() },
)

expect(result.css.trim()).toMatchInlineSnapshot(`
Expand All @@ -222,3 +223,28 @@ describe('plugins', () => {
`)
})
})

test('bail early when Tailwind is not used', async () => {
let processor = postcss([
tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
])

let result = await processor.process(
css`
.custom-css {
color: red;
}
`,
{ from: inputCssFilePath() },
)

// `fixtures/example-project` includes an `underline` candidate. But since we
// didn't use `@tailwind utilities` we didn't scan for utilities.
expect(result.css).not.toContain('.underline {')

expect(result.css.trim()).toMatchInlineSnapshot(`
".custom-css {
color: red;
}"
`)
})
83 changes: 46 additions & 37 deletions packages/@tailwindcss-postcss/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import QuickLRU from '@alloc/quick-lru'
import { compile, env } from '@tailwindcss/node'
import { compile, env, Features } from '@tailwindcss/node'
import { clearRequireCache } from '@tailwindcss/node/require-cache'
import { Scanner } from '@tailwindcss/oxide'
import { Features, transform } from 'lightningcss'
import { Features as LightningCssFeatures, transform } from 'lightningcss'
import fs from 'node:fs'
import path from 'node:path'
import postcss, { type AcceptedPlugin, type PluginCreator } from 'postcss'
Expand Down Expand Up @@ -63,7 +63,9 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {

async function createCompiler() {
env.DEBUG && console.time('[@tailwindcss/postcss] Setup compiler')
clearRequireCache(context.fullRebuildPaths)
if (context.fullRebuildPaths.length > 0 && !isInitialBuild) {
clearRequireCache(context.fullRebuildPaths)
}

context.fullRebuildPaths = []

Expand All @@ -86,6 +88,10 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
// guarantee a `build()` function is available.
context.compiler ??= await createCompiler()

if (context.compiler.features === Features.None) {
return
}

let rebuildStrategy: 'full' | 'incremental' = 'incremental'

// Track file modification times to CSS files
Expand Down Expand Up @@ -154,46 +160,49 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
}

env.DEBUG && console.time('[@tailwindcss/postcss] Scan for candidates')
let candidates = context.scanner.scan()
let candidates =
context.compiler.features & Features.Utilities ? context.scanner.scan() : []
env.DEBUG && console.timeEnd('[@tailwindcss/postcss] Scan for candidates')

// Add all found files as direct dependencies
for (let file of context.scanner.files) {
result.messages.push({
type: 'dependency',
plugin: '@tailwindcss/postcss',
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: globBase, pattern } of context.scanner.globs) {
// Avoid adding a dependency on the base directory itself, since it
// causes Next.js to start an endless recursion if the `distDir` is
// configured to anything other than the default `.next` dir.
if (pattern === '*' && base === globBase) {
continue
}

if (pattern === '') {
if (context.compiler.features & Features.Utilities) {
// Add all found files as direct dependencies
for (let file of context.scanner.files) {
result.messages.push({
type: 'dependency',
plugin: '@tailwindcss/postcss',
file: globBase,
parent: result.opts.from,
})
} else {
result.messages.push({
type: 'dir-dependency',
plugin: '@tailwindcss/postcss',
dir: globBase,
glob: pattern,
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: globBase, pattern } of context.scanner.globs) {
// Avoid adding a dependency on the base directory itself, since it
// causes Next.js to start an endless recursion if the `distDir` is
// configured to anything other than the default `.next` dir.
if (pattern === '*' && base === globBase) {
continue
}

if (pattern === '') {
result.messages.push({
type: 'dependency',
plugin: '@tailwindcss/postcss',
file: globBase,
parent: result.opts.from,
})
} else {
result.messages.push({
type: 'dir-dependency',
plugin: '@tailwindcss/postcss',
dir: globBase,
glob: pattern,
parent: result.opts.from,
})
}
}
}

env.DEBUG && console.time('[@tailwindcss/postcss] Build CSS')
Expand Down Expand Up @@ -237,8 +246,8 @@ function optimizeCss(
nonStandard: {
deepSelectorCombinator: true,
},
include: Features.Nesting,
exclude: Features.LogicalProperties,
include: LightningCssFeatures.Nesting,
exclude: LightningCssFeatures.LogicalProperties,
targets: {
safari: (16 << 16) | (4 << 8),
ios_saf: (16 << 16) | (4 << 8),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { toKeyPath } from '../../../../tailwindcss/src/utils/to-key-path'
import * as ValueParser from '../../../../tailwindcss/src/value-parser'
import { printCandidate } from '../candidates'

export enum Convert {
export const enum Convert {
All = 0,
MigrateModifier = 1 << 0,
MigrateThemeOnly = 1 << 1,
Expand Down
2 changes: 1 addition & 1 deletion packages/@tailwindcss-upgrade/src/utils/walk.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export enum WalkAction {
export const enum WalkAction {
// Continue walking the tree. Default behavior.
Continue,

Expand Down
Loading