From 3efedd50fb33052de888a9e2775ab77fbf248437 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 1 Apr 2025 23:00:27 +0200 Subject: [PATCH 1/5] inject polyfills after `@import` and body-less `@layer` --- packages/tailwindcss/src/ast.ts | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/tailwindcss/src/ast.ts b/packages/tailwindcss/src/ast.ts index 3a086a193968..436b22d021bf 100644 --- a/packages/tailwindcss/src/ast.ts +++ b/packages/tailwindcss/src/ast.ts @@ -582,10 +582,26 @@ export function optimizeAst( } if (fallbackAst.length > 0) { - let firstNonCommentIndex = newAst.findIndex((item) => item.kind !== 'comment') - if (firstNonCommentIndex === -1) firstNonCommentIndex = 0 + let firstValidNodeIndex = newAst.findIndex((node) => { + // License comments + if (node.kind === 'comment') return false + + if (node.kind === 'at-rule') { + // Charset + if (node.name === '@charset') return false + + // External imports + if (node.name === '@import') return false + + // Body-less `@layer …;` + if (node.name === '@layer' && node.nodes.length === 0) return false + } + + return true + }) + newAst.splice( - firstNonCommentIndex, + Math.max(firstValidNodeIndex, 0), 0, atRule( '@supports', From 989f39d989daa6e916fbaf5033863e27d0b3a4da Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 1 Apr 2025 23:01:37 +0200 Subject: [PATCH 2/5] add integration test --- integrations/cli/index.test.ts | 281 +++++++++++++++++++++++++++++++++ 1 file changed, 281 insertions(+) diff --git a/integrations/cli/index.test.ts b/integrations/cli/index.test.ts index 8a6924583667..df691b41983a 100644 --- a/integrations/cli/index.test.ts +++ b/integrations/cli/index.test.ts @@ -1563,6 +1563,287 @@ test( }, ) +test( + 'polyfills should be imported after external `@import url(…)` statements', + { + fs: { + 'package.json': json` + { + "dependencies": { + "tailwindcss": "workspace:^", + "@tailwindcss/cli": "workspace:^" + } + } + `, + 'index.css': css` + @import url('https://fonts.googleapis.com'); + @import 'tailwindcss'; + `, + 'index.html': html`
`, + }, + }, + async ({ exec, fs, expect }) => { + await exec('pnpm tailwindcss --input ./index.css --output ./dist/out.css') + expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(` + " + --- ./dist/out.css --- + @import url('https://fonts.googleapis.com'); + @layer theme, base, components, utilities; + @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) { + @layer base { + *, ::before, ::after, ::backdrop { + --tw-shadow: 0 0 #0000; + --tw-shadow-color: initial; + --tw-shadow-alpha: 100%; + --tw-inset-shadow: 0 0 #0000; + --tw-inset-shadow-color: initial; + --tw-inset-shadow-alpha: 100%; + --tw-ring-color: initial; + --tw-ring-shadow: 0 0 #0000; + --tw-inset-ring-color: initial; + --tw-inset-ring-shadow: 0 0 #0000; + --tw-ring-inset: initial; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-offset-shadow: 0 0 #0000; + } + } + } + @layer theme { + :root, :host { + --font-sans: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', + 'Noto Color Emoji'; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', + monospace; + --color-red-500: oklch(63.7% 0.237 25.331); + --default-font-family: var(--font-sans); + --default-mono-font-family: var(--font-mono); + } + } + @layer base { + *, ::after, ::before, ::backdrop, ::file-selector-button { + box-sizing: border-box; + margin: 0; + padding: 0; + border: 0 solid; + } + html, :host { + line-height: 1.5; + -webkit-text-size-adjust: 100%; + tab-size: 4; + font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'); + font-feature-settings: var(--default-font-feature-settings, normal); + font-variation-settings: var(--default-font-variation-settings, normal); + -webkit-tap-highlight-color: transparent; + } + hr { + height: 0; + color: inherit; + border-top-width: 1px; + } + abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + } + h1, h2, h3, h4, h5, h6 { + font-size: inherit; + font-weight: inherit; + } + a { + color: inherit; + -webkit-text-decoration: inherit; + text-decoration: inherit; + } + b, strong { + font-weight: bolder; + } + code, kbd, samp, pre { + font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace); + font-feature-settings: var(--default-mono-font-feature-settings, normal); + font-variation-settings: var(--default-mono-font-variation-settings, normal); + font-size: 1em; + } + small { + font-size: 80%; + } + sub, sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; + } + sub { + bottom: -0.25em; + } + sup { + top: -0.5em; + } + table { + text-indent: 0; + border-color: inherit; + border-collapse: collapse; + } + :-moz-focusring { + outline: auto; + } + progress { + vertical-align: baseline; + } + summary { + display: list-item; + } + ol, ul, menu { + list-style: none; + } + img, svg, video, canvas, audio, iframe, embed, object { + display: block; + vertical-align: middle; + } + img, video { + max-width: 100%; + height: auto; + } + button, input, select, optgroup, textarea, ::file-selector-button { + font: inherit; + font-feature-settings: inherit; + font-variation-settings: inherit; + letter-spacing: inherit; + color: inherit; + border-radius: 0; + background-color: transparent; + opacity: 1; + } + :where(select:is([multiple], [size])) optgroup { + font-weight: bolder; + } + :where(select:is([multiple], [size])) optgroup option { + padding-inline-start: 20px; + } + ::file-selector-button { + margin-inline-end: 4px; + } + ::placeholder { + opacity: 1; + } + @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) { + ::placeholder { + color: color-mix(in oklab, currentColor 50%, transparent); + } + } + textarea { + resize: vertical; + } + ::-webkit-search-decoration { + -webkit-appearance: none; + } + ::-webkit-date-and-time-value { + min-height: 1lh; + text-align: inherit; + } + ::-webkit-datetime-edit { + display: inline-flex; + } + ::-webkit-datetime-edit-fields-wrapper { + padding: 0; + } + ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field { + padding-block: 0; + } + :-moz-ui-invalid { + box-shadow: none; + } + button, input:where([type='button'], [type='reset'], [type='submit']), ::file-selector-button { + appearance: button; + } + ::-webkit-inner-spin-button, ::-webkit-outer-spin-button { + height: auto; + } + [hidden]:where(:not([hidden='until-found'])) { + display: none !important; + } + } + @layer utilities { + .bg-red-500\\/50 { + background-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 50%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-red-500) 50%, transparent); + } + } + .shadow-md { + --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + } + @property --tw-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; + } + @property --tw-shadow-color { + syntax: "*"; + inherits: false; + } + @property --tw-shadow-alpha { + syntax: ""; + inherits: false; + initial-value: 100%; + } + @property --tw-inset-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; + } + @property --tw-inset-shadow-color { + syntax: "*"; + inherits: false; + } + @property --tw-inset-shadow-alpha { + syntax: ""; + inherits: false; + initial-value: 100%; + } + @property --tw-ring-color { + syntax: "*"; + inherits: false; + } + @property --tw-ring-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; + } + @property --tw-inset-ring-color { + syntax: "*"; + inherits: false; + } + @property --tw-inset-ring-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; + } + @property --tw-ring-inset { + syntax: "*"; + inherits: false; + } + @property --tw-ring-offset-width { + syntax: ""; + inherits: false; + initial-value: 0px; + } + @property --tw-ring-offset-color { + syntax: "*"; + inherits: false; + initial-value: #fff; + } + @property --tw-ring-offset-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; + } + " + `) + }, +) + function withBOM(text: string): string { return '\uFEFF' + text } From 66090e835f10b46d7d72f0b8ab88939961746d5e Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 1 Apr 2025 23:03:06 +0200 Subject: [PATCH 3/5] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd426a2c63f4..f0ac519a9cd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Disable padding in `@source inline(…)` brace expansion ([#17491](https://github.com/tailwindlabs/tailwindcss/pull/17491)) +- Inject polyfills after `@import` and body-less `@layer` ([#17493](https://github.com/tailwindlabs/tailwindcss/pull/17493)) ## [4.1.0] - 2025-04-01 From 4954b1f1b878e5b561bdfb8214b747501eb6527d Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 1 Apr 2025 23:38:39 +0200 Subject: [PATCH 4/5] inject at the end in case no valid rule was found --- packages/tailwindcss/src/ast.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tailwindcss/src/ast.ts b/packages/tailwindcss/src/ast.ts index 436b22d021bf..e207e3ddfb15 100644 --- a/packages/tailwindcss/src/ast.ts +++ b/packages/tailwindcss/src/ast.ts @@ -601,7 +601,7 @@ export function optimizeAst( }) newAst.splice( - Math.max(firstValidNodeIndex, 0), + firstValidNodeIndex < 0 ? newAst.length : firstValidNodeIndex, 0, atRule( '@supports', From 748bbbea6075bdb2625a38271ab367fe0f7215bc Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 1 Apr 2025 23:43:31 +0200 Subject: [PATCH 5/5] update snapshots These snapshots look a bit confusing, but Lightning CSS is optimizing the body-less `@layer` and moving things around a bit. --- .../src/__snapshots__/index.test.ts.snap | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap b/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap index a4d9a400c243..4591a130cd44 100644 --- a/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap +++ b/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap @@ -1,15 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`\`@import 'tailwindcss'\` is replaced with the generated CSS 1`] = ` -"@supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { - @layer base { - *, :before, :after, ::backdrop { - --tw-font-weight: initial; - } - } -} - -@layer theme { +"@layer theme { :root, :host { --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; @@ -289,6 +281,14 @@ exports[`\`@import 'tailwindcss'\` is replaced with the generated CSS 1`] = ` } } +@supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { + @layer base { + *, :before, :after, ::backdrop { + --tw-font-weight: initial; + } + } +} + @property --tw-font-weight { syntax: "*"; inherits: false