🩹 Fixes
- interop: Only passthrough default if it is not a promise (#408)
📦 Build
- Revert to
terser-webpack-plugin(#407)❤️ Contributors
- Kricsleo (@kricsleo)
Does any of this look wrong? Please let us know.
See the full diff on Github. The new version differs by 6 commits:
- @depfu rebase
- Rebases against your default branch and redoes this update
- @depfu recreate
- Recreates this PR, overwriting any edits that you've made to it
- @depfu merge
- Merges this PR once your tests are passing and conflicts are resolved
- @depfu cancel merge
- Cancels automatic merging of this PR
- @depfu close
- Closes this PR and deletes the branch
- @depfu reopen
- Restores the branch and reopens this PR (if it's closed)
- @depfu pause
- Ignores all future updates for this dependency and closes this PR
- @depfu pause [minor|major]
- Ignores all future minor/major updates for this dependency and closes this PR
- @depfu resume
- Future versions of this dependency will create PRs again (leaves this PR as is)
(&mut self, filter: P) -> &mut WalkBuilder
where
P: Fn(&DirEntry) -> bool + Send + Sync + 'static,
@@ -902,6 +973,49 @@ impl WalkBuilder {
self.filter = Some(Filter(Arc::new(filter)));
self
}
+
+ /// Set the current working directory used for matching global gitignores.
+ ///
+ /// If this is not set, then this walker will attempt to discover the
+ /// correct path from the environment's current working directory. If
+ /// that fails, then global gitignore files will be ignored.
+ ///
+ /// Global gitignore files come from things like a user's git configuration
+ /// or from gitignore files added via [`WalkBuilder::add_ignore`].
+ pub fn current_dir(&mut self, cwd: impl Into Note This release is backporting bug fixes. It does
not include all pending features/changes on canary. Huge thanks to @mischnic for
helping! Note This release is backporting bug fixes. It does
not include all pending features/changes on canary. Huge thanks to @devjiwonchoi,
@ztanner, and
@icyJoseph
for helping! Does any of this look wrong? Please let us
know. See
the full diff on Github. The new version differs by 10 commits: Full Changelog: v1.5.0...v1.6.0 Does any of this look wrong? Please
let us know. See
the full diff on Github. The new version differs by 4 commits: Full Changelog: v1.5.0...v1.6.0 Does any of this look wrong? Please let us
know. See
the full diff on Github. The new version differs by 4 commits: Full Changelog: v1.5.0...v1.6.0 Does any of this look wrong? Please let
us know. See
the full diff on Github. The new version differs by 4 commits: No significant changes Does any of this look wrong? Please let
us know. See
the full diff on Github. The new version differs by 7 commits: SSR applications can now initialize HMR runtime by
importing Does any of this look wrong? Please
let us know. Does any of this look wrong? Please let us
know. See
the full diff on Github. The new version differs by 59 commits: Does any of this look wrong? Please let
us know. See
the full diff on Github. The new version differs by 6 commits: Does any of this look wrong? Please let us
know. See
the full diff on Github. The new version differs by more commits
than we can show here. Full Changelog: v1.7.0...v1.7.1 Full Changelog: v1.6.0...v1.7.0 Does any of this look wrong? Please
let us know. See
the full diff on Github. The new version differs by 6 commits: Does any of this look wrong? Please
let us know. Full Changelog: v1.7.0...v1.7.1 Full Changelog: v1.6.0...v1.7.0 Does any of this look wrong? Please let us
know. See
the full diff on Github. The new version differs by 6 commits: Does any of this look wrong? Please let us
know. See
the full diff on Github. The new version differs by 22 commits: In HTML reporter, there's a new tab we call
"Speedboard": It shows you all your executed tests sorted by
slowness, Starting with this release, Playwright switches from
Chromium, to using Chrome for
Testing builds. Both headed and headless browsers are subject to
this. Your tests should still be passing after upgrading to Playwright
1.57. We're expecting no functional changes to come from this
switch. The biggest change is the new icon and title in your
toolbar. If you still see an unexpected behaviour change, please file
an issue. On Arm64 Linux, Playwright continues to use Chromium. testConfig.webServer
added a If you include a named capture group into the expression,
then Playwright will provide the capture group contents via environment
variables: This is not just useful for capturing varying ports of dev
servers. You can also use it to wait for readiness of a service that
doesn't expose an HTTP readiness check, but instead prints a readiness
message to stdout or stderr. After 3 years of being deprecated, we removed Does any of this look wrong? Please
let us know. See
the full diff on Github. The new version differs by more commits
than we can show here. See
the full diff on Github. The new version differs by 6 commits: Does any of this look wrong? Please
let us know. See
the full diff on Github. The new version differs by 6 commits: Does any of this look wrong? Please let us
know. See
the full diff on Github. The new version differs by 6 commits: Does any of this look wrong? Please let us
know. See
the full diff on Github. The new version differs by 6 commits: Does any of this look wrong? Please let us
know. See
the full diff on Github. The new version differs by more commits
than we can show here. Does any of this look wrong? Please let us
know. See
the full diff on Github. The new version differs by 7 commits: Does any of this look wrong? Please let
us know. See
the full diff on Github. The new version differs by 3 commits: Does any of this look wrong? Please
let us know. See
the full diff on Github. The new version differs by 3 commits: See
the full diff on Github. The new version differs by 3 commits:
+ In a partial
+
+ In a partial using explicit local variables
+
+ In a partial using explicit local variables
+
On the current `main` branch, this looks like:
In this PR, the memory usage looks like:
The memory usage is stable, but to actually prove that we can still
track multiple design systems, let's track them all in a `Set` so
garbage collection cannot get rid of the unused design system objects:
Now we're sort of back to the current situation on `main`:
---
.../template/migrate-arbitrary-variants.ts | 18 +-
.../src/codemods/template/migrate.ts | 12 +-
.../src/canonicalize-candidates.ts | 911 +++++++++++++++---
packages/tailwindcss/src/design-system.ts | 7 +
.../src/expand-declaration.test.ts | 2 +-
.../tailwindcss/src/expand-declaration.ts | 2 +-
packages/tailwindcss/src/signatures.ts | 561 -----------
7 files changed, 799 insertions(+), 714 deletions(-)
delete mode 100644 packages/tailwindcss/src/signatures.ts
diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-variants.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-variants.ts
index c1b9669ac..58efb0421 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-variants.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-variants.ts
@@ -1,23 +1,23 @@
import { cloneCandidate } from '../../../../tailwindcss/src/candidate'
-import { createSignatureOptions } from '../../../../tailwindcss/src/canonicalize-candidates'
+import {
+ PRE_COMPUTED_VARIANTS_KEY,
+ prepareDesignSystemStorage,
+ VARIANT_SIGNATURE_KEY,
+} from '../../../../tailwindcss/src/canonicalize-candidates'
import type { Config } from '../../../../tailwindcss/src/compat/plugin-api'
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
-import {
- computeVariantSignature,
- preComputedVariants,
-} from '../../../../tailwindcss/src/signatures'
import type { Writable } from '../../../../tailwindcss/src/types'
import { replaceObject } from '../../../../tailwindcss/src/utils/replace-object'
import { walkVariants } from '../../utils/walk-variants'
export function migrateArbitraryVariants(
- designSystem: DesignSystem,
+ baseDesignSystem: DesignSystem,
_userConfig: Config | null,
rawCandidate: string,
): string {
- let signatureOptions = createSignatureOptions(designSystem)
- let signatures = computeVariantSignature.get(signatureOptions)
- let variants = preComputedVariants.get(signatureOptions)
+ let designSystem = prepareDesignSystemStorage(baseDesignSystem)
+ let signatures = designSystem.storage[VARIANT_SIGNATURE_KEY]
+ let variants = designSystem.storage[PRE_COMPUTED_VARIANTS_KEY]
for (let readonlyCandidate of designSystem.parseCandidate(rawCandidate)) {
// We are only interested in the variants
diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts
index 6060d8020..2b39094d4 100644
--- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts
+++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts
@@ -1,9 +1,12 @@
import fs from 'node:fs/promises'
import path, { extname } from 'node:path'
-import { createSignatureOptions } from '../../../../tailwindcss/src/canonicalize-candidates'
+import {
+ createSignatureOptions,
+ prepareDesignSystemStorage,
+ UTILITY_SIGNATURE_KEY,
+} from '../../../../tailwindcss/src/canonicalize-candidates'
import type { Config } from '../../../../tailwindcss/src/compat/plugin-api'
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
-import { computeUtilitySignature } from '../../../../tailwindcss/src/signatures'
import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map'
import { spliceChangesIntoString, type StringChange } from '../../utils/splice-changes-into-string'
import { extractRawCandidates } from './candidates'
@@ -40,7 +43,8 @@ export const DEFAULT_MIGRATIONS: Migration[] = [
migrateModernizeArbitraryValues,
]
-let migrateCached = new DefaultMap((designSystem: DesignSystem) => {
+let migrateCached = new DefaultMap((baseDesignSystem: DesignSystem) => {
+ let designSystem = prepareDesignSystemStorage(baseDesignSystem)
let options = createSignatureOptions(designSystem)
return new DefaultMap((userConfig: Config | null) => {
@@ -57,7 +61,7 @@ let migrateCached = new DefaultMap((designSystem: DesignSystem) => {
// Verify that the candidate actually makes sense at all. E.g.: `duration`
// is not a valid candidate, but it will parse because `duration-
Release Notes
15.5.6
Core Changes
Credits
15.5.5
Core Changes
experimental.middlewareClientMaxBodySize body
cloning limit (#84722)Misc Changes
Credits
Commits
v15.5.6Backport:
don't define `process.cwd()` in node_modules
(#84957)Fix
url-imports test on 15-5 (#84966)v15.5.5[backport]
fix: missing `next/link` types with `typedRoutes` (#82814)
(#84779)[backport]
docs: early October improvements and fixes (#84334)[backport]:
`experimental.middlewareClientMaxBodySize` (#84722)[backport]
fix: unstable_cache should perform blocking revalidation during ISR
revalidation (#84716)[backport]
Add deprecation warning to Runtime config (#84168)
(#84650)[backport]
Split code-frame into separate compiled package (#84174)
(#84238)All Depfu comment commands
Release Notes
1.6.0
What's Changed
Commits
All Depfu comment commands
Release Notes
1.6.0
What's Changed
Commits
All Depfu comment commands
Release Notes
1.6.0
What's Changed
Commits
All Depfu comment commands
Release Notes
0.30.21
View
changes on GitHub
Commits
All Depfu comment commands
Release Notes
5.1.0 (from changelog)
Add
@vitejs/plugin-react/preamble virtual module
for SSR HMR (#890)@vitejs/plugin-react/preamble
at the top of their client entry instead of manually calling transformIndexHtml. This simplifies SSR setup
for applications that don't use the transformIndexHtml API.All Depfu comment commands
All Depfu comment commands
Release Notes
9.39.1
Bug Fixes
Documentation
51b51f4 docs: add a section on when to
use extends vs cascading (#20268)
(Tanuj Kanti)b44d426 docs: Update README (GitHub
Actions Bot)Chores
92db329 chore: update @eslint/js version to 9.39.1 (#20284)
(Francesco Trotta)c7ebefc chore: package.json update for
@eslint/js release (Jenkins)61778f6 chore: update
eslint-config-eslint dependency @eslint/js to ^9.39.0 (#20275)
(renovate[bot])d9ca2fc ci: Add rangeStrategy to eslint
group in renovate config (#20266)
(唯然)009e507 test: fix version tests for
ESLint v10 (#20274)
(Milos Djermanovic)9.39.0
Features
cc57d87 feat: update error loc to key in
no-dupe-class-members (#20259)
(Tanuj Kanti)126552f feat: update error location in
for-direction and no-dupe-args (#20258)
(Tanuj Kanti)167d097 feat: update complexity rule to highlight only static
block header (#20245)
(jaymarvelz)Bug Fixes
15f5c7c fix: forward traversal step.args to visitors (#20253)
(jaymarvelz)5a1a534 fix: allow JSDoc comments in
object-shorthand rule (#20167)
(Nitin Kumar)e86b813 fix: Use more types from
@eslint/core (#20257)
(Nicholas C. Zakas)927272d fix: correct Scope typings (#20198)
(jaymarvelz)37f76d9 fix: use AST.Program type for Program node (#20244)
(Francesco Trotta)ae07f0b fix: unify timing report for
concurrent linting (#20188)
(jaymarvelz)b165d47 fix: correct Rule typings (#20199)
(jaymarvelz)fb97cda fix: improve error message for
missing fix function in suggestions (#20218)
(jaymarvelz)Documentation
d3e81e3 docs: Always recommend to include
a files property (#20158)
(Percy Ma)0f0385f docs: use consistent naming
recommendation (#20250)
(Alex M. Spieslechner)a3b1456 docs: Update README (GitHub
Actions Bot)cf5f2dd docs: fix correct tag of no-useless-constructor (#20255)
(Tanuj Kanti)10b995c docs: add TS options and examples
for nofunc in no-use-before-define (#20249)
(Tanuj Kanti)2584187 docs: remove repetitive word in
comment (#20242)
(reddaisyy)637216b docs: update CLI flags migration
instructions (#20238)
(jaymarvelz)e7cda3b docs: Update README (GitHub
Actions Bot)7b9446f docs: handle empty flags sections
on the feature flags page (#20222)
(sethamus)Chores
dfe3c1b chore: update @eslint/js version to 9.39.0 (#20270)
(Francesco Trotta)2375a6d chore: package.json update for
@eslint/js release (Jenkins)a1f4e52 chore: update @eslint dependencies (#20265)
(Francesco Trotta)c7d3229 chore: update dependency
@eslint/core to ^0.17.0 (#20256)
(renovate[bot])27549bc chore: update fuzz testing to not
error if code sample minimizer fails (#20252)
(Milos Djermanovic)a1370ee ci: bump actions/setup-node from
5 to 6 (#20230)
(dependabot[bot])9e7fad4 chore: add script to
auto-generate eslint:recommended configuration (#20208)
(唯然)9.38.0
Features
ce40f74 feat: update complexity rule to only highlight function
header (#20048)
(Atul Nair)e37e590 feat: correct no-loss-of-precision false positives with
e notation (#20187)
(Francesco Trotta)Bug Fixes
50c3dfd fix: improve type support for
isolated dependencies in pnpm (#20201)
(Francesco Trotta)a1f06a3 fix: correct SourceCode typings
(#20114)
(Pixel998)Documentation
462675a docs: improve web accessibility
by hiding non-semantic character (#20205)
(루밀LuMir)c070e65 docs: correct formatting in no-irregular-whitespace rule documentation
(#20203)
(루밀LuMir)b39e71a docs: Update README (GitHub
Actions Bot)cd39983 docs: move custom-formatters type descriptions to nodejs-api (#20190)
(Percy Ma)Chores
d17c795 chore: upgrade @eslint/js@9.38.0
(#20221)
(Milos Djermanovic)25d0e33 chore: package.json update for
@eslint/js release (Jenkins)c82b5ef refactor: Use types from
@eslint/core (#20168)
(Nicholas C. Zakas)ff31609 ci: add Node.js 25 to ci.yml (#20220)
(루밀LuMir)004577e ci: bump github/codeql-action
from 3 to 4 (#20211)
(dependabot[bot])eac71fb test: remove use of nodejsScope option of eslint-scope from tests
(#20206)
(Milos Djermanovic)4168a18 chore: fix typo in
legacy-eslint.js (#20202)
(Sweta Tanwar)205dbd2 chore: fix typos (#20200)
(ntnyq)dbb200e chore: use team member's username
when name is not available in data (#20194)
(Milos Djermanovic)8962089 chore: mark deprecated rules as
available until v11.0.0 (#20184)
(Pixel998)Commits
9.39.1Build:
changelog update for 9.39.1chore:
update `@eslint/js` version to 9.39.1 (#20284)chore:
package.json update for @eslint/js releasefix:
Only pass node to JS lang visitor methods (#20283)docs:
add a section on when to use extends vs cascading
(#20268)chore:
update eslint-config-eslint dependency @eslint/js to ^9.39.0
(#20275)ci:
Add rangeStrategy to eslint group in renovate config
(#20266)test:
fix version tests for ESLint v10 (#20274)docs:
Update README9.39.0Build:
changelog update for 9.39.0chore:
update `@eslint/js` version to 9.39.0 (#20270)chore:
package.json update for @eslint/js releasedocs:
Always recommend to include a files property (#20158)fix:
forward traversal `step.args` to visitors (#20253)fix:
allow JSDoc comments in object-shorthand rule (#20167)feat:
update error loc to key in `no-dupe-class-members`
(#20259)chore:
update `@eslint` dependencies (#20265)fix:
Use more types from @eslint/core (#20257)feat:
update error location in `for-direction` and `no-dupe-args`
(#20258)fix:
correct `Scope` typings (#20198)chore:
update dependency @eslint/core to ^0.17.0 (#20256)docs:
use consistent naming recommendation (#20250)docs:
Update READMEfix:
use `AST.Program` type for Program node (#20244)docs:
fix correct tag of `no-useless-constructor` (#20255)chore:
update fuzz testing to not error if code sample minimizer fails
(#20252)docs:
add TS options and examples for `nofunc` in `no-use-before-define`
(#20249)feat:
update `complexity` rule to highlight only static block header
(#20245)docs:
remove repetitive word in comment (#20242)fix:
unify timing report for concurrent linting (#20188)fix:
correct `Rule` typings (#20199)docs:
update CLI flags migration instructions (#20238)docs:
Update READMEci:
bump actions/setup-node from 5 to 6 (#20230)fix:
improve error message for missing fix function in suggestions
(#20218)chore:
add script to auto-generate eslint:recommended configuration
(#20208)docs:
handle empty flags sections on the feature flags page
(#20222)9.38.0Build:
changelog update for 9.38.0chore:
upgrade @eslint/js@9.38.0 (#20221)chore:
package.json update for @eslint/js releasefix:
improve type support for isolated dependencies in pnpm
(#20201)refactor:
Use types from @eslint/core (#20168)ci:
add Node.js 25 to `ci.yml` (#20220)feat:
update `complexity` rule to only highlight function header
(#20048)feat:
correct `no-loss-of-precision` false positives with `e` notation
(#20187)ci:
bump github/codeql-action from 3 to 4 (#20211)test:
remove use of `nodejsScope` option of eslint-scope from tests
(#20206)docs:
improve web accessibility by hiding non-semantic character
(#20205)docs:
correct formatting in `no-irregular-whitespace` rule documentation
(#20203)docs:
Update READMEchore:
fix typo in legacy-eslint.js (#20202)chore:
fix typos (#20200)fix:
correct SourceCode typings (#20114)chore:
use team member's username when name is not available in data
(#20194)docs:
move `custom-formatters` type descriptions to `nodejs-api`
(#20190)chore:
mark deprecated rules as available until v11.0.0
(#20184)All Depfu comment commands
Release Notes
10.4.22
stretch prefixes on new Can I
Use database.fraction.js.Commits
All Depfu comment commands
All Depfu comment commands
Release Notes
16.0.3
More info than we can show here.
16.0.2
More info than we can show here.
16.0.1
More info than we can show here.
16.0.0
More info than we can show here.
Commits
All Depfu comment commands
Release Notes
1.7.1
What's Changed
1.7.0
What's Changed
Commits
All Depfu comment commands
Release Notes
5.1.1 (from changelog)
Update code to support newer
rolldown-vite (#976)rolldown-vite will remove
optimizeDeps.rollupOptions in favor of
optimizeDeps.rolldownOptions soon. This
plugin now uses optimizeDeps.rolldownOptions to support newer
rolldown-vite. Please update rolldown-vite to the latest version if you
are using an older version.All Depfu comment commands
Release Notes
1.7.1
What's Changed
1.7.0
What's Changed
Commits
All Depfu comment commands
Release Notes
16.0.0
Breaking
gitignore option is enabled b9a9a84
{gitignore: true}, Globby now
searches for .gitignore files up to the Git repo root (if detected).
This matches Git's actual behavior where parent .gitignore patterns
apply to subdirectories. Previously, only .gitignore files within the
glob search path were considered. This change could break existing code
that relied on the previous, more limited behavior.Improvements
isGitIgnored functions e794ff9
cwd option 7535e6d
Fixes
{gitignore: true} performance
issue fcb07c3
gitignore option 11dd2df
stats option c10f600
Commits
16.0.0Fix
bundler compatibilityRespect
parent .gitignore files when `gitignore` option is
enabledAdd
test for `suppressErrors` option with file patternsAdd
test for ignore option with trailing slashesFix
parent directory patterns with `gitignore` optionAdd
tests for special characters in directory namesImprove
path check in gitignore logicImprove
error message for invalid `cwd` optionFix
types for `stats` optionFix
ignore patterns with parent directory referencesImprove
Windows path documentationSupport
negation-only patternsRespect
custom fs implementation from optionsImprove
docs for `convertPathToPattern`Add
test for .npmignore in subdirectoriesClarify
`gitignore` priority in documentationAdd
test for nested gitignore with negationAdd
some more options to isGitIgnored functionsFix
`{gitignore: true}` performance issueAdd
tests for some fast-glob limitationsAdd
test for gitignore patterns starting with `./` or `../`All Depfu comment commands
---
CHANGELOG.md | 1 +
.../src/canonicalize-candidates.test.ts | 77 ++++++---
.../src/canonicalize-candidates.ts | 151 +++++++++++++-----
packages/tailwindcss/src/cartesian.ts | 45 ++++++
4 files changed, 209 insertions(+), 65 deletions(-)
create mode 100644 packages/tailwindcss/src/cartesian.ts
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e9f52b544..8399fa1c2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Improve backwards compatibility for `content` theme key from JS configs ([#19381](https://github.com/tailwindlabs/tailwindcss/pull/19381))
- Upgrade: Handle `future` and `experimental` config keys ([#19344](https://github.com/tailwindlabs/tailwindcss/pull/19344))
- Try to canonicalize any arbitrary utility to a bare value ([#19379](https://github.com/tailwindlabs/tailwindcss/pull/19379))
+- Canonicalization: combine `text-*` and `leading-*` classes ([#19396](https://github.com/tailwindlabs/tailwindcss/pull/19396))
### Added
diff --git a/packages/tailwindcss/src/canonicalize-candidates.test.ts b/packages/tailwindcss/src/canonicalize-candidates.test.ts
index fd5826c4d..b8ca59d67 100644
--- a/packages/tailwindcss/src/canonicalize-candidates.test.ts
+++ b/packages/tailwindcss/src/canonicalize-candidates.test.ts
@@ -2,6 +2,7 @@ import fs from 'node:fs'
import path from 'node:path'
import { describe, expect, test } from 'vitest'
import { __unstable__loadDesignSystem } from '.'
+import { cartesian } from './cartesian'
import type { CanonicalizeOptions } from './intellisense'
import { DefaultMap } from './utils/default-map'
@@ -54,7 +55,7 @@ const DEFAULT_CANONICALIZATION_OPTIONS: CanonicalizeOptions = {
}
describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s', (strategy) => {
- let testName = '`%s` → `%s` (%#)'
+ let testName = '%s → %s (%#)'
if (strategy === 'with-variant') {
testName = testName.replaceAll('%s', 'focus:%s')
} else if (strategy === 'important') {
@@ -1025,37 +1026,69 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s',
})
})
- test.each([
- // 4 to 1
- ['mt-1 mr-1 mb-1 ml-1', 'm-1'],
+ describe('combine to shorthand utilities', () => {
+ test.each([
+ // 4 to 1
+ ['mt-1 mr-1 mb-1 ml-1', 'm-1'],
- // 2 to 1
- ['mt-1 mb-1', 'my-1'],
+ // 2 to 1
+ ['mt-1 mb-1', 'my-1'],
- // Different order as above
- ['mb-1 mt-1', 'my-1'],
+ // Different order as above
+ ['mb-1 mt-1', 'my-1'],
- // To completely different utility
- ['w-4 h-4', 'size-4'],
+ // To completely different utility
+ ['w-4 h-4', 'size-4'],
- // Do not touch if not operating on the same variants
- ['hover:w-4 h-4', 'hover:w-4 h-4'],
+ // Do not touch if not operating on the same variants
+ ['hover:w-4 h-4', 'hover:w-4 h-4'],
- // Arbitrary properties to combined class
- ['[width:_16px_] [height:16px]', 'size-4'],
+ // Arbitrary properties to combined class
+ ['[width:_16px_] [height:16px]', 'size-4'],
- // Arbitrary properties to combined class with modifier
- ['[font-size:14px] [line-height:1.625]', 'text-sm/relaxed'],
- ])(
- 'should canonicalize multiple classes `%s` into a shorthand `%s`',
- { timeout },
- async (candidates, expected) => {
+ // Arbitrary properties to combined class with modifier
+ ['[font-size:14px] [line-height:1.625]', 'text-sm/relaxed'],
+ ])(testName, { timeout }, async (candidates, expected) => {
let input = css`
@import 'tailwindcss';
`
await expectCombinedCanonicalization(input, candidates, expected)
- },
- )
+ })
+ })
+
+ describe('font-size/line-height to text-{x}/{y}', () => {
+ test.each([
+ ...Array.from(
+ cartesian(
+ ['[font-size:14px]', 'text-[14px]', 'text-[14px]/6', 'text-sm', 'text-sm/6'],
+ ['[line-height:28px]', 'leading-[28px]', 'leading-7'],
+ ),
+ ).map((classes) => [classes.join(' ').padEnd(40, ' '), 'text-sm/7']),
+ ...Array.from(
+ cartesian(
+ ['[font-size:15px]', 'text-[15px]', 'text-[15px]/6'],
+ ['[line-height:28px]', 'leading-[28px]', 'leading-7'],
+ ),
+ ).map((classes) => [classes.join(' ').padEnd(40, ' '), 'text-[15px]/7']),
+ ...Array.from(
+ cartesian(
+ ['[font-size:14px]', 'text-[14px]', 'text-[14px]/6', 'text-sm', 'text-sm/6'],
+ ['[line-height:28.5px]', 'leading-[28.5px]'],
+ ),
+ ).map((classes) => [classes.join(' ').padEnd(40, ' '), 'text-sm/[28.5px]']),
+ ...Array.from(
+ cartesian(
+ ['[font-size:15px]', 'text-[15px]', 'text-[15px]/6'],
+ ['[line-height:28.5px]', 'leading-[28.5px]'],
+ ),
+ ).map((classes) => [classes.join(' ').padEnd(40, ' '), 'text-[15px]/[28.5px]']),
+ ])(testName, { timeout }, async (candidates, expected) => {
+ let input = css`
+ @import 'tailwindcss';
+ `
+ await expectCombinedCanonicalization(input, candidates.trim(), expected)
+ })
+ })
})
describe('theme to var', () => {
diff --git a/packages/tailwindcss/src/canonicalize-candidates.ts b/packages/tailwindcss/src/canonicalize-candidates.ts
index 437ec60b4..612129985 100644
--- a/packages/tailwindcss/src/canonicalize-candidates.ts
+++ b/packages/tailwindcss/src/canonicalize-candidates.ts
@@ -107,7 +107,10 @@ interface DesignSystem extends BaseDesignSystem {
}
}
-export function prepareDesignSystemStorage(baseDesignSystem: BaseDesignSystem): DesignSystem {
+export function prepareDesignSystemStorage(
+ baseDesignSystem: BaseDesignSystem,
+ options?: CanonicalizeOptions,
+): DesignSystem {
let designSystem = baseDesignSystem as DesignSystem
designSystem.storage[SIGNATURE_OPTIONS_KEY] ??= createSignatureOptionsCache()
@@ -116,7 +119,7 @@ export function prepareDesignSystemStorage(baseDesignSystem: BaseDesignSystem):
designSystem.storage[CANONICALIZE_VARIANT_KEY] ??= createCanonicalizeVariantCache()
designSystem.storage[CANONICALIZE_UTILITY_KEY] ??= createCanonicalizeUtilityCache()
designSystem.storage[CONVERTER_KEY] ??= createConverterCache(designSystem)
- designSystem.storage[SPACING_KEY] ??= createSpacingCache(designSystem)
+ designSystem.storage[SPACING_KEY] ??= createSpacingCache(designSystem, options)
designSystem.storage[UTILITY_SIGNATURE_KEY] ??= createUtilitySignatureCache(designSystem)
designSystem.storage[STATIC_UTILITIES_KEY] ??= createStaticUtilitiesCache()
designSystem.storage[UTILITY_PROPERTIES_KEY] ??= createUtilityPropertiesCache(designSystem)
@@ -144,7 +147,7 @@ export function createSignatureOptions(
if (options?.collapse) features |= SignatureFeatures.ExpandProperties
if (options?.logicalToPhysical) features |= SignatureFeatures.LogicalToPhysical
- let designSystem = prepareDesignSystemStorage(baseDesignSystem)
+ let designSystem = prepareDesignSystemStorage(baseDesignSystem, options)
return designSystem.storage[SIGNATURE_OPTIONS_KEY].get(options?.rem ?? null).get(features)
}
@@ -255,6 +258,56 @@ function collapseCandidates(options: InternalCanonicalizeOptions, candidates: st
computeUtilitiesPropertiesLookup.get(candidate),
)
+ // Hard-coded optimization: if any candidate sets `line-height` and another
+ // candidate sets `font-size`, we pre-compute the `text-*` utilities with
+ // this line-height to try and collapse to those combined values.
+ if (candidatePropertiesValues.some((x) => x.has('line-height'))) {
+ let fontSizeNames = designSystem.theme.keysInNamespaces(['--text'])
+ if (fontSizeNames.length > 0) {
+ let interestingLineHeights = new Set
Release Notes
1.57.0
Speedboard
and can help you understand where your test suite is taking longer than
expected.
Take a look at yours - maybe you'll find some tests that are spending a
longer time waiting than they should!Chrome for Testing
Waiting for webserver output
wait field. Pass a regular
expression, and Playwright will wait until the webserver logs match
it.import { defineConfig } from '@playwright/test';
export default defineConfig({
webServer: {
command: 'npm run
start',
wait: {
stdout: '/Listening on
port (?<my_server_port>\\d+)/'
},
},
});
import { test, expect } from '@playwright/test';
test.use({ baseUrl: `http://localhost:${process.env.MY_SERVER_PORT ?? 3000}` });
test('homepage', async ({ page }) => {
await page.goto('/');
});
Breaking Change
Page#accessibility from our API. Please use
other libraries such as Axe if
you need to test page accessibility. See our Node.js guide for
integration with Axe.New APIs
Locator.toString() now uses the
description when available.steps in locator.click()
and locator.dragTo()
that configures the number of mousemove
events emitted while moving the mouse pointer to the target
element.PLAYWRIGHT_DISABLE_SERVICE_WORKER_NETWORK
environment variable.PLAYWRIGHT_DISABLE_SERVICE_WORKER_CONSOLE
environment variable.Browser Versions
Commits
All Depfu comment commands
Commits
All Depfu comment commands
All Depfu comment commands
All Depfu comment commands
All Depfu comment commands
All Depfu comment commands
All Depfu comment commands
Release Notes
5.18.4
Fixes
#/
file: schemaCommits
chore(release):
5.18.4fix:
support `file:` schema (#467)fix:
allow subpath imports that start with `#/` (#468)fix(AliasPlugin):
correct wildcard replacement in multi-target aliases
(#465)fix:
fix always-false condition (#464)fix:
note the conditions under which the request couldn't be found
(#460)All Depfu comment commands
Release Notes
19.2.3
React Server Components
19.2.2
React Server Components
react-server-dom-webpack/*.unbundled to
private react-server-dom-unbundled (@eps1lon #35290)Commits
Release Notes
19.2.3
React Server Components
19.2.2
React Server Components
react-server-dom-webpack/*.unbundled to
private react-server-dom-unbundled (@eps1lon #35290)Commits
All Depfu comment commands
Release Notes
16.1.0
More info than we can show here.
Commits
All Depfu comment commands
Release Notes
9.39.2
Bug Fixes
Build Related
Chores
Commits
All Depfu comment commands
Release Notes
10.4.23
Commits
All Depfu comment commands
All Depfu comment commands
All Depfu comment commands
Release Notes
0.5.11
Fixed
Commits
All Depfu comment commands
All Depfu comment commands
Commits
All Depfu comment commands
All Depfu comment commands
All Depfu comment commands
---
CHANGELOG.md | 1 +
.../extractor/pre_processors/pre_processor.rs | 30 +++++++++++-
.../src/extractor/pre_processors/ruby.rs | 47 ++++++++++++++++++-
3 files changed, 75 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bf75d28e3..5adb9f7f5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Allow whitespace around `@source inline()` argument ([#19461](https://github.com/tailwindlabs/tailwindcss/pull/19461))
- CLI: Emit comment when source maps are saved to files ([#19447](https://github.com/tailwindlabs/tailwindcss/pull/19447))
- Detect utilities when containing capital letters followed by numbers ([#19465](https://github.com/tailwindlabs/tailwindcss/pull/19465))
+- Fix class extraction for Rails' strict locals ([#19525](https://github.com/tailwindlabs/tailwindcss/pull/19525))
### Added
diff --git a/crates/oxide/src/extractor/pre_processors/pre_processor.rs b/crates/oxide/src/extractor/pre_processors/pre_processor.rs
index 8d42e744a..8e02b8f69 100644
--- a/crates/oxide/src/extractor/pre_processors/pre_processor.rs
+++ b/crates/oxide/src/extractor/pre_processors/pre_processor.rs
@@ -25,7 +25,33 @@ pub trait PreProcessor: Sized + Default {
}
#[cfg(test)]
- fn test_extract_contains(input: &str, items: Vec<&str>) {
+ fn test_extract_exact(input: &str, expected: Vec<&str>) {
+ use crate::extractor::{Extracted, Extractor};
+
+ let input = input.as_bytes();
+
+ let processor = Self::default();
+ let transformed = processor.process(input);
+
+ let extracted = Extractor::new(&transformed).extract();
+
+ // Extract all candidates and css variables.
+ let candidates = extracted
+ .iter()
+ .filter_map(|x| match x {
+ Extracted::Candidate(bytes) => std::str::from_utf8(bytes).ok(),
+ Extracted::CssVariable(bytes) => std::str::from_utf8(bytes).ok(),
+ })
+ .collect::
All Depfu comment commands
---
CHANGELOG.md | 1 +
packages/tailwindcss/src/index.test.ts | 35 +++++
packages/tailwindcss/src/utilities.test.ts | 43 ++++++
packages/tailwindcss/src/utilities.ts | 148 ++++++++++++++++++++-
4 files changed, 222 insertions(+), 5 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5adb9f7f5..b66ebaea0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- CLI: Emit comment when source maps are saved to files ([#19447](https://github.com/tailwindlabs/tailwindcss/pull/19447))
- Detect utilities when containing capital letters followed by numbers ([#19465](https://github.com/tailwindlabs/tailwindcss/pull/19465))
- Fix class extraction for Rails' strict locals ([#19525](https://github.com/tailwindlabs/tailwindcss/pull/19525))
+- Align `@utility` name validation with Oxide scanner rules ([#19524](https://github.com/tailwindlabs/tailwindcss/pull/19524))
### Added
diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts
index 2d6ab8daf..94b17714e 100644
--- a/packages/tailwindcss/src/index.test.ts
+++ b/packages/tailwindcss/src/index.test.ts
@@ -4660,6 +4660,41 @@ describe('@utility', () => {
`[Error: \`@utility my-*-utility\` defines an invalid utility name. The dynamic portion marked by \`-*\` must appear once at the end.]`,
)
})
+
+ // https://github.com/tailwindlabs/tailwindcss/issues/19505
+ test('@utility name cannot contain multiple `/` characters', async () => {
+ await expect(
+ compileCss(
+ css`
+ @utility ui/button {
+ display: inline-flex;
+ background: blue;
+ }
+ @tailwind utilities;
+ `,
+ ['ui/button'],
+ ),
+ ).resolves.toMatchInlineSnapshot(
+ `
+ ".ui\\/button {
+ background: #00f;
+ display: inline-flex;
+ }"
+ `,
+ )
+
+ await expect(
+ compileCss(css`
+ @utility ui/button/sm {
+ display: inline-flex;
+ background: blue;
+ font-size: 12px;
+ }
+ `),
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
+ `[Error: \`@utility ui/button/sm\` defines an invalid utility name. Utilities should be alphanumeric and start with a lowercase letter.]`,
+ )
+ })
})
test('addBase', async () => {
diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts
index 732e4ab9a..cff2284f4 100644
--- a/packages/tailwindcss/src/utilities.test.ts
+++ b/packages/tailwindcss/src/utilities.test.ts
@@ -1,6 +1,7 @@
import { describe, expect, test, vi } from 'vitest'
import { compile } from '.'
import { compileCss, optimizeCss, run } from './test-utils/run'
+import { isValidFunctionalUtilityName, isValidStaticUtilityName } from './utilities'
const css = String.raw
@@ -27207,6 +27208,48 @@ describe('spacing utilities', () => {
})
describe('custom utilities', () => {
+ test.each([
+ ['foo', true], // Simple name
+ ['foo-123', true], // Ending with a number is valid
+ ['foo-2.5', true], // Dots are valid when surrounded by numbers
+ ['-foo', true], // Simple name with negative sign
+ ['foo-bar', true], // With dashes
+ ['foo_bar', true], // With underscores
+ ['foo-50%', true], // Bare value with percentage
+ ['foo-1/2', true], // Bare value with fraction
+ ['foo-sm/8', true], // Bare value with number modifier
+ ['foo-4/snug', true], // Bare value with named modifier
+ ['foo_', true], // This is supported today, so let's not break it
+ ['foo/bar', true], // A slash to separate the modifier is valid.
+
+ ['Foo', false], // Starting with uppercase letter is invalid
+ ['-Foo', false], // Starting with uppercase letter is invalid (negative)
+ ['foo-', false], // Should not end with a dash
+ ['foo-1/', false], // Invalid fraction/modifier
+ ['foo-p%', false], // Invalid percentage
+ ['foo.bar', false], // Dots are only valid when surrounded by numbers
+ ['foo-1..5', false], // Double dots are invalid
+ ['foo..bar', false], // Double dots are invalid definitely without numbers
+ ['foo/bar/baz', false], // Multiple slashes are invalid
+ ])('valid static utility name "%s" (%s)', (name, valid) => {
+ expect(isValidStaticUtilityName(name)).toBe(valid)
+ })
+
+ test.each([
+ ['foo', false], // Simple name, missing '-*' suffix
+ ['foo-*', true], // Simple name
+ ['foo--*', false], // Root should not end in `-`
+ ['-foo-*', true], // Simple name (negative)
+ ['foo-bar-*', true], // With dashes
+ ['foo_bar-*', true], // With underscores
+ ['Foo-*', false], // Starting with uppercase letter is invalid
+ ['-Foo-*', false], // Starting with uppercase letter is invalid
+ ['foo!-*', false], // Invalid special character
+ ['foo-[…]', false], // Invalid special character
+ ])('valid functional name "%s" (%s)', (name, valid) => {
+ expect(isValidFunctionalUtilityName(name)).toBe(valid)
+ })
+
test('custom static utility', async () => {
let { build } = await compile(css`
@layer utilities {
diff --git a/packages/tailwindcss/src/utilities.ts b/packages/tailwindcss/src/utilities.ts
index 8ec903af6..e1c84ce00 100644
--- a/packages/tailwindcss/src/utilities.ts
+++ b/packages/tailwindcss/src/utilities.ts
@@ -28,9 +28,6 @@ import { segment } from './utils/segment'
import * as ValueParser from './value-parser'
import { walk, WalkAction } from './walk'
-const IS_VALID_STATIC_UTILITY_NAME = /^-?[a-z][a-zA-Z0-9/%._-]*$/
-const IS_VALID_FUNCTIONAL_UTILITY_NAME = /^-?[a-z][a-zA-Z0-9/%._-]*-\*$/
-
const DEFAULT_SPACING_SUGGESTIONS = [
'0',
'0.5',
@@ -5835,7 +5832,7 @@ export function createCssUtility(node: AtRule) {
let name = node.params
// Functional utilities. E.g.: `tab-size-*`
- if (IS_VALID_FUNCTIONAL_UTILITY_NAME.test(name)) {
+ if (isValidFunctionalUtilityName(name)) {
// API:
//
// - `--value('literal')` resolves a literal named value
@@ -6184,7 +6181,7 @@ export function createCssUtility(node: AtRule) {
}
}
- if (IS_VALID_STATIC_UTILITY_NAME.test(name)) {
+ if (isValidStaticUtilityName(name)) {
return (designSystem: DesignSystem) => {
designSystem.utilities.static(name, () => node.nodes.map(cloneAstNode))
}
@@ -6428,3 +6425,144 @@ function alphaReplacedDropShadowProperties(
return [decl(property, prefix + replacedValue)]
}
}
+
+const UTILITY_ROOT = /^-?[a-z][a-zA-Z0-9_-]*/
+
+const PERCENT = 37
+const SLASH = 47
+const DOT = 46
+const LOWER_A = 97
+const LOWER_Z = 122
+const UPPER_A = 65
+const UPPER_Z = 90
+const ZERO = 48
+const NINE = 57
+const UNDERSCORE = 95
+const DASH = 45
+
+export function isValidStaticUtilityName(name: string): boolean {
+ let match = UTILITY_ROOT.exec(name)
+ if (match === null) return false // Invalid root
+
+ let root = match[0]
+ let value = name.slice(root.length)
+
+ // Root should not end in `-` if there is no value
+ //
+ // `tab-size-`
+ // --------- Root
+ if (value.length === 0 && root.endsWith('-')) {
+ return false
+ }
+
+ // No remaining value is valid
+ //
+ // `tab-size`
+ // -------- Root
+ if (value.length === 0) {
+ return true
+ }
+
+ // Any valid (static) utility should be valid including:
+ // - Bare values with `.`: `p-1.5`
+ // - Bare values with `%`: `w-50%`
+ // - With an embedded modifier: `text-xs/8`
+
+ let seenSlash = false
+ for (let i = 0; i < value.length; i++) {
+ let charCode = value.charCodeAt(i)
+ switch (charCode) {
+ case PERCENT: {
+ // A percentage is only valid at the end of the value
+ if (i !== value.length - 1) return false
+
+ // A percent is only valid when preceded by a digit. E.g.: `w-%` is invalid
+ let previousChar = value[i - 1] || root[root.length - 1] || ''
+ let previousCharCode = previousChar.charCodeAt(0)
+ if (previousCharCode < ZERO || previousCharCode > NINE) return false
+ break
+ }
+
+ case SLASH: {
+ // A slash must be followed by at least 1 character. E.g.: `foo/` is invalid
+ if (i === value.length - 1) return false
+
+ // A slash can only appear once. E.g.: `foo/bar/baz` is invalid
+ if (seenSlash) return false
+ seenSlash = true
+ break
+ }
+
+ case DOT: {
+ // Dots are only allowed between digits. E.g.: `p-1.a` is invalid
+ let previousChar = value[i - 1] || root[root.length - 1] || ''
+ let previousCharCode = previousChar.charCodeAt(0)
+ if (previousCharCode < ZERO || previousCharCode > NINE) return false
+
+ let nextChar = value[i + 1] || ''
+ let nextCharCode = nextChar.charCodeAt(0)
+ if (nextCharCode < ZERO || nextCharCode > NINE) return false
+ break
+ }
+
+ // Allowed special characters
+ case UNDERSCORE:
+ case DASH: {
+ continue
+ }
+
+ default: {
+ if (
+ (charCode >= LOWER_A && charCode <= LOWER_Z) || // Allow a-z
+ (charCode >= UPPER_A && charCode <= UPPER_Z) || // Allow A-Z
+ (charCode >= ZERO && charCode <= NINE) // Allow 0-9
+ ) {
+ continue
+ }
+
+ // Everything else is invalid
+ return false
+ }
+ }
+ }
+
+ return true
+}
+
+export function isValidFunctionalUtilityName(name: string): boolean {
+ if (!name.endsWith('-*')) return false // Missing '-*' suffix
+ name = name.slice(0, -2)
+
+ let match = UTILITY_ROOT.exec(name)
+ if (match === null) return false // Invalid root
+
+ let root = match[0]
+ let value = name.slice(root.length)
+
+ // Root should not end in `-` if there is no value
+ //
+ // `tab-size--*`
+ // --------- Root
+ // -- Suffix
+ //
+ // Because with default values, this could match `tab-size-` which is invalid.
+ if (value.length === 0 && root.endsWith('-')) {
+ return false
+ }
+
+ // No remaining value is valid
+ //
+ // `tab-size-*`
+ // -------- Root
+ // -- Suffix
+ if (value.length === 0) {
+ return true
+ }
+
+ // But if there is a value remaining, it's invalid.
+ //
+ // E.g.: `tab-size-[…]-*`
+ //
+ // If we allow more characters, we can extend the validation here
+ return false
+}
From 1f9ac73daf07112a8efe43e140861fffe4468a99 Mon Sep 17 00:00:00 2001
From: Robin Malfait