Repository: tailwindlabs/tailwindcss-jit
Pull request #175 (2021-03-28 13:10:32)

Add support for selectors in `important` option
This PR adds support for using a selector in the `important` option to increase specificity without actually making things important.

This is an existing feature in Tailwind that hadn't yet been ported to the JIT engine — [see the documentation to learn more about how it works](https://tailwindcss.com/docs/configuration#selector-strategy).


Patch:
diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js
index 046be38..fe48a0d 100644
--- a/src/lib/generateRules.js
+++ b/src/lib/generateRules.js
@@ -261,18 +261,8 @@ function* resolveMatches(candidate, context) {
   }
 }
 
-function inKeyframes(d) {
-  return (
-    d.parent.parent && d.parent.parent.type === 'atrule' && d.parent.parent.name === 'keyframes'
-  )
-}
-
-function makeImportant(rule) {
-  rule.walkDecls((d) => {
-    if (d.parent.type === 'rule' && !inKeyframes(d)) {
-      d.important = true
-    }
-  })
+function inKeyframes(rule) {
+  return rule.parent && rule.parent.type === 'atrule' && rule.parent.name === 'keyframes'
 }
 
 function generateRules(candidates, context) {
@@ -300,8 +290,26 @@ function generateRules(candidates, context) {
   }
 
   return allRules.flat(1).map(([{ sort, layer, options }, rule]) => {
-    if (context.tailwindConfig.important === true && options.respectImportant) {
-      makeImportant(rule)
+    if (options.respectImportant) {
+      if (context.tailwindConfig.important === true) {
+        rule.walkDecls((d) => {
+          if (d.parent.type === 'rule' && !inKeyframes(d.parent)) {
+            d.important = true
+          }
+        })
+      } else if (typeof context.tailwindConfig.important === 'string') {
+        let container = postcss.root({ nodes: [rule] })
+        container.walkRules((r) => {
+          if (inKeyframes(r)) {
+            return
+          }
+
+          r.selectors = r.selectors.map((selector) => {
+            return `${context.tailwindConfig.important} ${selector}`
+          })
+        })
+        rule = container.nodes[0]
+      }
     }
     return [sort | context.layerOrder[layer], rule]
   })


Test patch:
diff --git a/tests/important-selector.test.css b/tests/important-selector.test.css
new file mode 100644
index 0000000..a631401
--- /dev/null
+++ b/tests/important-selector.test.css
@@ -0,0 +1,85 @@
+* {
+  --tw-shadow: 0 0 #0000;
+  --tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
+  --tw-ring-offset-width: 0px;
+  --tw-ring-offset-color: #fff;
+  --tw-ring-color: rgba(59, 130, 246, 0.5);
+  --tw-ring-offset-shadow: 0 0 #0000;
+  --tw-ring-shadow: 0 0 #0000;
+}
+.container {
+  width: 100%;
+}
+@media (min-width: 640px) {
+  .container {
+    max-width: 640px;
+  }
+}
+@media (min-width: 768px) {
+  .container {
+    max-width: 768px;
+  }
+}
+@media (min-width: 1024px) {
+  .container {
+    max-width: 1024px;
+  }
+}
+@media (min-width: 1280px) {
+  .container {
+    max-width: 1280px;
+  }
+}
+@media (min-width: 1536px) {
+  .container {
+    max-width: 1536px;
+  }
+}
+#app .btn {
+  button: yes;
+}
+@font-face {
+  font-family: Inter;
+}
+@page {
+  margin: 1cm;
+}
+.custom-component {
+  font-weight: 700;
+}
+.custom-important-component {
+  text-align: center !important;
+}
+@keyframes spin {
+  to {
+    transform: rotate(360deg);
+  }
+}
+#app .animate-spin {
+  animation: spin 1s linear infinite;
+}
+#app .font-bold {
+  font-weight: 700;
+}
+.custom-util {
+  button: no;
+}
+#app .group:hover .group-hover\:focus-within\:text-left:focus-within {
+  text-align: left;
+}
+#app [dir='rtl'] .rtl\:active\:text-center:active {
+  text-align: center;
+}
+@media (prefers-reduced-motion: no-preference) {
+  #app .motion-safe\:hover\:text-center:hover {
+    text-align: center;
+  }
+}
+#app .dark .dark\:focus\:text-left:focus {
+  text-align: left;
+}
+@media (min-width: 768px) {
+  #app .md\:hover\:text-right:hover {
+    text-align: right;
+  }
+}
diff --git a/tests/important-selector.test.html b/tests/important-selector.test.html
new file mode 100644
index 0000000..5668043
--- /dev/null
+++ b/tests/important-selector.test.html
@@ -0,0 +1,12 @@
+<div class="container"></div>
+<div class="btn"></div>
+<div class="animate-spin"></div>
+<div class="custom-util"></div>
+<div class="custom-component"></div>
+<div class="custom-important-component"></div>
+<div class="font-bold"></div>
+<div class="md:hover:text-right"></div>
+<div class="motion-safe:hover:text-center"></div>
+<div class="dark:focus:text-left"></div>
+<div class="group-hover:focus-within:text-left"></div>
+<div class="rtl:active:text-center"></div>
diff --git a/tests/important-selector.test.js b/tests/important-selector.test.js
new file mode 100644
index 0000000..c4349bf
--- /dev/null
+++ b/tests/important-selector.test.js
@@ -0,0 +1,70 @@
+const postcss = require('postcss')
+const tailwind = require('../src/index.js')
+const fs = require('fs')
+const path = require('path')
+
+function run(input, config = {}) {
+  return postcss([tailwind(config)]).process(input, { from: path.resolve(__filename) })
+}
+
+test('important selector', () => {
+  let config = {
+    important: '#app',
+    darkMode: 'class',
+    purge: [path.resolve(__dirname, './important-selector.test.html')],
+    corePlugins: { preflight: false },
+    theme: {},
+    plugins: [
+      function ({ addComponents, addUtilities }) {
+        addComponents(
+          {
+            '.btn': {
+              button: 'yes',
+            },
+          },
+          { respectImportant: true }
+        )
+        addComponents(
+          {
+            '@font-face': {
+              'font-family': 'Inter',
+            },
+            '@page': {
+              margin: '1cm',
+            },
+          },
+          { respectImportant: true }
+        )
+        addUtilities(
+          {
+            '.custom-util': {
+              button: 'no',
+            },
+          },
+          { respectImportant: false }
+        )
+      },
+    ],
+  }
+
+  let css = `
+    @tailwind base;
+    @tailwind components;
+    @layer components {
+      .custom-component {
+        @apply font-bold;
+      }
+      .custom-important-component {
+        @apply text-center !important;
+      }
+    }
+    @tailwind utilities;
+  `
+
+  return run(css, config).then((result) => {
+    let expectedPath = path.resolve(__dirname, './important-selector.test.css')
+    let expected = fs.readFileSync(expectedPath, 'utf8')
+
+    expect(result.css).toMatchCss(expected)
+  })
+})