Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
80a90e0
hoist regex
RobinMalfait Sep 26, 2025
33dd0f8
remove async
RobinMalfait Sep 30, 2025
a62d857
tmp: annotate migrations
RobinMalfait Sep 30, 2025
c82bc30
move `printCandidate` normalization tests to core
RobinMalfait Sep 30, 2025
8c6fe35
expose `canonicalizeCandidates` on the design system
RobinMalfait Sep 26, 2025
5d6ea74
canonicalize candidates at the end of upgrading your project
RobinMalfait Sep 30, 2025
44352e8
move `migrate-bg-gradient` to core
RobinMalfait Sep 30, 2025
afa3216
move `migrate-theme-to-var` to core
RobinMalfait Sep 30, 2025
852e288
cache the converter
RobinMalfait Sep 30, 2025
4c722c0
only prefix variable that are not in `--theme(…)`
RobinMalfait Sep 30, 2025
4bf93bf
move types to core
RobinMalfait Sep 30, 2025
5de55bd
move dimensions to core
RobinMalfait Sep 30, 2025
2c9a2c7
move signatures to core
RobinMalfait Sep 30, 2025
34cb8b8
ensure `canonicalizeCandidates` returns a unique list
RobinMalfait Sep 30, 2025
0c3d48e
move `migrate-arbitrary-utilities` to core
RobinMalfait Sep 30, 2025
f088459
move `migrate-bare-utilities` to core
RobinMalfait Sep 30, 2025
ae5909c
move `migrate-deprecated-utilities` to core
RobinMalfait Sep 30, 2025
e5352d3
move `replaceObject` to core
RobinMalfait Sep 30, 2025
2863c60
move `migrate-arbitrary-variants` to core
RobinMalfait Sep 30, 2025
0eaf373
move `migrate-drop-unnecessary-data-types` to core
RobinMalfait Sep 30, 2025
683ffc5
move `migrate-arbitrary-value-to-bare-value` to core
RobinMalfait Sep 30, 2025
1c02690
move `migrate-optimize-modifier` to core
RobinMalfait Sep 30, 2025
760aef8
remove `!` from test case
RobinMalfait Oct 2, 2025
d5ba933
handle `&` and `*` in selector parser as standalone selector
RobinMalfait Oct 3, 2025
80029cd
move parts of `migrate-modernize-arbitrary-values` to core
RobinMalfait Oct 3, 2025
02ba94f
ensure we canonicalize at the end
RobinMalfait Oct 3, 2025
c956b08
big refactor
RobinMalfait Oct 3, 2025
c281426
drop unnecessary calls
RobinMalfait Oct 4, 2025
368eb80
only stringify the value when something changed
RobinMalfait Oct 4, 2025
55cbbaf
remove migration annotations
RobinMalfait Oct 4, 2025
e7ce1ad
move `selector-parser` from `./src/compat` to just `./src`
RobinMalfait Oct 5, 2025
705476c
add attribute selector parser
RobinMalfait Oct 5, 2025
226ae47
use dedicated `AttributeSelectorParser`
RobinMalfait Oct 5, 2025
4441c44
remove intellisense features from `@tailwindcss/browser`
RobinMalfait Oct 6, 2025
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
16 changes: 16 additions & 0 deletions packages/@tailwindcss-browser/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,20 @@ export default defineConfig({
'process.env.NODE_ENV': '"production"',
'process.env.FEATURES_ENV': '"stable"',
},
esbuildPlugins: [
{
name: 'patch-intellisense-apis',
setup(build) {
build.onLoad({ filter: /intellisense.ts$/ }, () => {
return {
contents: `
export function getClassList() { return [] }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean these APIs aren't available when running the browser version?

Copy link
Contributor

@thecrypticace thecrypticace Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These APIs are internal and were already inaccessible in the @tailwindcss/browser package. They were on an object (i.e. not tree-shakable) which meant they still took up some space. This replaces those APIs so they take up much less space since they can't be used anyway.

basically this is just a code-size reduction and doesn't eliminate any actual functionality.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can still import the tailwindcss package directly — even in the browser — it'll work just fine and will have these APIs.

This is done to help eliminate some bundle size concerns with the CDN version (e.g. the one that scans class="…" attributes in HTML and compiles on the fly)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah that must be it great, thanks mate

export function getVariants() { return [] }
export function canonicalizeCandidates() { return [] }
`,
}
})
},
},
],
})
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { __unstable__loadDesignSystem } from '@tailwindcss/node'
import { describe, expect, test } from 'vitest'
import { expect, test } from 'vitest'
import { spliceChangesIntoString } from '../../utils/splice-changes-into-string'
import { extractRawCandidates } from './candidates'

Expand Down Expand Up @@ -82,116 +82,3 @@ test('replaces the right positions for a candidate', async () => {
"
`)
})

const candidates = [
// Arbitrary candidates
['[color:red]', '[color:red]'],
['[color:red]/50', '[color:red]/50'],
['[color:red]/[0.5]', '[color:red]/[0.5]'],
['[color:red]/50!', '[color:red]/50!'],
['![color:red]/50', '[color:red]/50!'],
['[color:red]/[0.5]!', '[color:red]/[0.5]!'],

// Static candidates
['box-border', 'box-border'],
['underline!', 'underline!'],
['!underline', 'underline!'],
['-inset-full', '-inset-full'],

// Functional candidates
['bg-red-500', 'bg-red-500'],
['bg-red-500/50', 'bg-red-500/50'],
['bg-red-500/[0.5]', 'bg-red-500/[0.5]'],
['bg-red-500!', 'bg-red-500!'],
['!bg-red-500', 'bg-red-500!'],
['bg-[#0088cc]/50', 'bg-[#0088cc]/50'],
['bg-[#0088cc]/[0.5]', 'bg-[#0088cc]/[0.5]'],
['bg-[#0088cc]!', 'bg-[#0088cc]!'],
['!bg-[#0088cc]', 'bg-[#0088cc]!'],
['bg-[var(--spacing)-1px]', 'bg-[var(--spacing)-1px]'],
['bg-[var(--spacing)_-_1px]', 'bg-[var(--spacing)-1px]'],
['bg-[var(--_spacing)]', 'bg-(--_spacing)'],
['bg-(--_spacing)', 'bg-(--_spacing)'],
['bg-[var(--\_spacing)]', 'bg-(--_spacing)'],
['bg-(--\_spacing)', 'bg-(--_spacing)'],
['bg-[-1px_-1px]', 'bg-[-1px_-1px]'],
['p-[round(to-zero,1px)]', 'p-[round(to-zero,1px)]'],
['w-1/2', 'w-1/2'],
['p-[calc((100vw-theme(maxWidth.2xl))_/_2)]', 'p-[calc((100vw-theme(maxWidth.2xl))/2)]'],

// Keep spaces in strings
['content-["hello_world"]', 'content-["hello_world"]'],
['content-[____"hello_world"___]', 'content-["hello_world"]'],

// Do not escape underscores for url() and CSS variable in var()
['bg-[no-repeat_url(/image_13.png)]', 'bg-[no-repeat_url(/image_13.png)]'],
[
'bg-[var(--spacing-0_5,_var(--spacing-1_5,_3rem))]',
'bg-(--spacing-0_5,var(--spacing-1_5,3rem))',
],
]

const variants = [
['', ''], // no variant
['*:', '*:'],
['focus:', 'focus:'],
['group-focus:', 'group-focus:'],

['hover:focus:', 'hover:focus:'],
['hover:group-focus:', 'hover:group-focus:'],
['group-hover:focus:', 'group-hover:focus:'],
['group-hover:group-focus:', 'group-hover:group-focus:'],

['min-[10px]:', 'min-[10px]:'],

// Normalize spaces
['min-[calc(1000px_+_12em)]:', 'min-[calc(1000px+12em)]:'],
['min-[calc(1000px_+12em)]:', 'min-[calc(1000px+12em)]:'],
['min-[calc(1000px+_12em)]:', 'min-[calc(1000px+12em)]:'],
['min-[calc(1000px___+___12em)]:', 'min-[calc(1000px+12em)]:'],

['peer-[&_p]:', 'peer-[&_p]:'],
['peer-[&_p]:hover:', 'peer-[&_p]:hover:'],
['hover:peer-[&_p]:', 'hover:peer-[&_p]:'],
['hover:peer-[&_p]:focus:', 'hover:peer-[&_p]:focus:'],
['peer-[&:hover]:peer-[&_p]:', 'peer-[&:hover]:peer-[&_p]:'],

['[p]:', '[p]:'],
['[_p_]:', '[p]:'],
['has-[p]:', 'has-[p]:'],
['has-[_p_]:', 'has-[p]:'],

// Simplify `&:is(p)` to `p`
['[&:is(p)]:', '[p]:'],
['[&:is(_p_)]:', '[p]:'],
['has-[&:is(p)]:', 'has-[p]:'],
['has-[&:is(_p_)]:', 'has-[p]:'],

// Handle special `@` variants. These shouldn't be printed as `@-`
['@xl:', '@xl:'],
['@[123px]:', '@[123px]:'],
]

let combinations: [string, string][] = []

for (let [inputVariant, outputVariant] of variants) {
for (let [inputCandidate, outputCandidate] of candidates) {
combinations.push([`${inputVariant}${inputCandidate}`, `${outputVariant}${outputCandidate}`])
}
}

describe('printCandidate()', () => {
test.each(combinations)('%s -> %s', async (candidate: string, result: string) => {
let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', {
base: __dirname,
})

let candidates = designSystem.parseCandidate(candidate)

// Sometimes we will have a functional and a static candidate for the same
// raw input string (e.g. `-inset-full`). Dedupe in this case.
let cleaned = new Set([...candidates].map((c) => designSystem.printCandidate(c)))

expect([...cleaned]).toEqual([result])
})
})
Loading