Repository: tailwindlabs/tailwindcss (TypeScript)

Issue:
Vendor prefix can break the generated css
<!-- Please provide all of the information requested below. We're a small team and without all of this information it's not possible for us to help and your bug report will be closed. -->

**What version of Tailwind CSS are you using?**

v3.0.0

**What build tool (or framework if it abstracts the build tool) are you using?**

webpack 5.60.0 and play.tailwind.com

**What version of Node.js are you using?**

v16.13.0

**What browser are you using?**

Chrome

**What operating system are you using?**

macOS

**Reproduction URL**

Try this in any browser except firefox:
https://play.tailwindcss.com/YkD75GMXHs?file=css

**Describe your issue**

If I @apply border class inside my custom CSS, and it just happens to be a vendor-prefixed, and the current browser doesn't support it then that invalidates all the CSS after that vendor-prefix.

Any vendor-prefixed classes will need to be separated into their own blocks and not merged in the comma-separated list. This does happen in my webpack setup as well but I suspect that's not relevant.


Hints:
Ah great catch, this is an important one and will prioritize, thank you 👍🏻 

Patch:
diff --git a/src/lib/resolveDefaultsAtRules.js b/src/lib/resolveDefaultsAtRules.js
--- a/src/lib/resolveDefaultsAtRules.js
+++ b/src/lib/resolveDefaultsAtRules.js
@@ -71,6 +71,8 @@ function extractElementSelector(selector) {
 export default function resolveDefaultsAtRules({ tailwindConfig }) {
   return (root) => {
     let variableNodeMap = new Map()
+
+    /** @type {Set<import('postcss').AtRule>} */
     let universals = new Set()
 
     root.walkAtRules('defaults', (rule) => {
@@ -90,31 +92,50 @@ export default function resolveDefaultsAtRules({ tailwindConfig }) {
     })
 
     for (let universal of universals) {
-      let selectors = new Set()
+      /** @type {Map<string, Set<string>>} */
+      let selectorGroups = new Map()
 
       let rules = variableNodeMap.get(universal.params) ?? []
 
       for (let rule of rules) {
         for (let selector of extractElementSelector(rule.selector)) {
+          // If selector contains a vendor prefix after a pseudo element or class,
+          // we consider them separately because merging the declarations into
+          // a single rule will cause browsers that do not understand the
+          // vendor prefix to throw out the whole rule
+          let selectorGroupName =
+            selector.includes(':-') || selector.includes('::-') ? selector : '__DEFAULT__'
+
+          let selectors = selectorGroups.get(selectorGroupName) ?? new Set()
+          selectorGroups.set(selectorGroupName, selectors)
+
           selectors.add(selector)
         }
       }
 
-      if (selectors.size === 0) {
+      if (selectorGroups.size === 0) {
         universal.remove()
         continue
       }
 
-      let universalRule = postcss.rule()
-
       if (flagEnabled(tailwindConfig, 'optimizeUniversalDefaults')) {
-        universalRule.selectors = [...selectors]
+        for (let [, selectors] of selectorGroups) {
+          let universalRule = postcss.rule()
+
+          universalRule.selectors = [...selectors]
+
+          universalRule.append(universal.nodes.map((node) => node.clone()))
+          universal.before(universalRule)
+        }
       } else {
+        let universalRule = postcss.rule()
+
         universalRule.selectors = ['*', '::before', '::after']
+
+        universalRule.append(universal.nodes)
+        universal.before(universalRule)
       }
 
-      universalRule.append(universal.nodes)
-      universal.before(universalRule)
       universal.remove()
     }
   }


Test patch:
diff --git a/tests/apply.test.js b/tests/apply.test.js
--- a/tests/apply.test.js
+++ b/tests/apply.test.js
@@ -577,3 +577,42 @@ it('should throw when trying to apply an indirect circular dependency with a mod
     expect(err.reason).toBe('Circular dependency detected when using: `@apply a`')
   })
 })
+
+it('rules with vendor prefixes are still separate when optimizing defaults rules', () => {
+  let config = {
+    experimental: { optimizeUniversalDefaults: true },
+    content: [{ raw: html`<div class="border"></div>` }],
+    corePlugins: { preflight: false },
+  }
+
+  let input = css`
+    @tailwind base;
+    @tailwind components;
+    @tailwind utilities;
+
+    @layer components {
+      input[type='range']::-moz-range-thumb {
+        @apply border;
+      }
+    }
+  `
+
+  return run(input, config).then((result) => {
+    return expect(result.css).toMatchFormattedCss(css`
+      [type='range']::-moz-range-thumb {
+        --tw-border-opacity: 1;
+        border-color: rgb(229 231 235 / var(--tw-border-opacity));
+      }
+      .border {
+        --tw-border-opacity: 1;
+        border-color: rgb(229 231 235 / var(--tw-border-opacity));
+      }
+      input[type='range']::-moz-range-thumb {
+        border-width: 1px;
+      }
+      .border {
+        border-width: 1px;
+      }
+    `)
+  })
+})