Use @layer properties for @property polyfills (#17506)

This PR changes how polyfills for `@property` are inserted. The main
motivation is to remove the need to rely on the correct placement of
`@layer base;`—Something that's not really required right not in
Tailwind CSS v4 and we'd like to keep it this way.

The idea is that the polyfills are inserted for you automatically. To
ensure they always take precedence, we insert an empty `@layer
properties;` at the top of the CSS file so that later, when we emit all
`@property` rules and their fallback, we can use this new named layer to
ensure the rules have a higher order.

Unfortunately, just putting `@layer properties;` at the beginning of a
file would not work as `lightningcss` incorrectly hoists all content
into the first occurrence of a layer name meaning these rules might be
inserted _before_ eventual external imports:


![image](https://github.com/user-attachments/assets/c5a1694d-1549-47ed-ad0f-266807be4730)

To work around this, we have to insert that layer name after any
eventual remaining external `@imports` for now.

## Test plan

- Updated snapshot tests
- Deployed a new version of the website with the patch applied to ensure
it works across browsers:
https://tailwindcss-com-git-legacy-browsers-tailwindlabs.vercel.app/.
Tested on: Safari on iOS 15.5, Safari on iOS 16.0, Firefox 127, Firefox
128, Chrome 110, Chrome latest, Safari latest, Firefox latest
This commit is contained in:
Philipp Spiess 2025-04-02 18:16:28 +02:00 • committed by GitHub
parent 6a0a3ec0fa
commit 4484192ca3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 320 additions and 301 deletions

View file

@ -713,13 +713,7 @@ test(
expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(`
"
--- ./dist/out.css ---
@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-content: "";
}
}
}
@layer properties;
.content-\\[\\"components\\/my-component\\.tsx\\"\\] {
--tw-content: "components/my-component.tsx";
content: var(--tw-content);
@ -761,6 +755,13 @@ test(
inherits: false;
initial-value: "";
}
@layer properties {
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
*, ::before, ::after, ::backdrop {
--tw-content: "";
}
}
}
"
`)
},
@ -951,13 +952,7 @@ test(
expect(await fs.dumpFiles('./project-a/dist/*.css')).toMatchInlineSnapshot(`
"
--- ./project-a/dist/out.css ---
@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-content: "";
}
}
}
@layer properties;
.content-\\[\\'project-a\\/node_modules\\/my-lib-1\\/src\\/index\\.html\\'\\] {
--tw-content: 'project-a/node modules/my-lib-1/src/index.html';
content: var(--tw-content);
@ -1011,6 +1006,13 @@ test(
inherits: false;
initial-value: "";
}
@layer properties {
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
*, ::before, ::after, ::backdrop {
--tw-content: "";
}
}
}
"
`)
@ -1195,13 +1197,7 @@ test(
expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(`
"
--- ./dist/out.css ---
@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-content: "";
}
}
}
@layer properties;
.content-\\[\\"pages\\/foo\\.html\\"\\] {
--tw-content: "pages/foo.html";
content: var(--tw-content);
@ -1219,6 +1215,13 @@ test(
inherits: false;
initial-value: "";
}
@layer properties {
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
*, ::before, ::after, ::backdrop {
--tw-content: "";
}
}
}
"
`)
},
@ -1588,27 +1591,8 @@ test(
"
--- ./dist/out.css ---
@import url('https://fonts.googleapis.com');
@layer properties;
@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',
@ -1839,6 +1823,26 @@ test(
inherits: false;
initial-value: 0 0 #0000;
}
@layer properties {
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
*, ::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;
}
}
}
"
`)
},