Repository: tailwindlabs/tailwindcss
Pull request #5587 (2021-09-24 14:03:35)

Support URL in arbitrary values
This PR allows you to use `bg-[url('...')]` or `stroke-[url(#...)]`

It adds a new regex to detect the `bg` case, and it will also prevent adding spaces to operators like `-`. 
This is important, otherwise this happens:

```css
.bg-\[url\(\'\.\/path-to-file\.png\'\)\] {
  background-image: url('./path - to - file.png')
}
```

Fixes: #5197
Fixes: #5398


Patch:
diff --git a/src/corePlugins.js b/src/corePlugins.js
index f89f923a..e801e462 100644
--- a/src/corePlugins.js
+++ b/src/corePlugins.js
@@ -17,6 +17,7 @@ import {
   transformAllClasses,
   transformLastClasses,
   asLength,
+  asURL,
   asLookupValue,
 } from './util/pluginUtils'
 import packageJson from '../package.json'
@@ -1361,7 +1362,7 @@ export let backgroundOpacity = createUtilityPlugin('backgroundOpacity', [
 export let backgroundImage = createUtilityPlugin(
   'backgroundImage',
   [['bg', ['background-image']]],
-  { resolveArbitraryValue: asLookupValue }
+  { resolveArbitraryValue: [asLookupValue, asURL] }
 )
 export let gradientColorStops = (() => {
   function transparentTo(value) {
@@ -1482,7 +1483,7 @@ export let stroke = ({ matchUtilities, theme }) => {
 }
 
 export let strokeWidth = createUtilityPlugin('strokeWidth', [['stroke', ['stroke-width']]], {
-  resolveArbitraryValue: asLength,
+  resolveArbitraryValue: [asLength, asURL],
 })
 
 export let objectFit = ({ addUtilities }) => {
diff --git a/src/lib/expandTailwindAtRules.js b/src/lib/expandTailwindAtRules.js
index 4ed2c5cf..3ecdcb3b 100644
--- a/src/lib/expandTailwindAtRules.js
+++ b/src/lib/expandTailwindAtRules.js
@@ -7,6 +7,8 @@ let env = sharedState.env
 let contentMatchCache = sharedState.contentMatchCache
 
 const PATTERNS = [
+  /([^<>"'`\s]*\[\w*\('[^"'`\s]*'\)\])/.source, // bg-[url('...')]
+  /([^<>"'`\s]*\[\w*\("[^"'`\s]*"\)\])/.source, // bg-[url("...")]
   /([^<>"'`\s]*\['[^"'`\s]*'\])/.source, // `content-['hello']` but not `content-['hello']']`
   /([^<>"'`\s]*\["[^"'`\s]*"\])/.source, // `content-["hello"]` but not `content-["hello"]"]`
   /([^<>"'`\s]*\[[^"'`\s]+\])/.source, // `fill-[#bada55]`
diff --git a/src/util/createUtilityPlugin.js b/src/util/createUtilityPlugin.js
index 3fffc390..4a9dadc5 100644
--- a/src/util/createUtilityPlugin.js
+++ b/src/util/createUtilityPlugin.js
@@ -1,11 +1,12 @@
 import transformThemeValue from './transformThemeValue'
-import { asValue, asColor, asAngle, asLength, asLookupValue } from '../util/pluginUtils'
+import { asValue, asColor, asAngle, asLength, asURL, asLookupValue } from '../util/pluginUtils'
 
 let asMap = new Map([
   [asValue, 'any'],
   [asColor, 'color'],
   [asAngle, 'angle'],
   [asLength, 'length'],
+  [asURL, 'url'],
   [asLookupValue, 'lookup'],
 ])
 
@@ -38,7 +39,9 @@ export default function createUtilityPlugin(
                 Object.entries(theme(themeKey) ?? {}).filter(([modifier]) => modifier !== 'DEFAULT')
               )
             : theme(themeKey),
-          type: asMap.get(resolveArbitraryValue) ?? 'any',
+          type: Array.isArray(resolveArbitraryValue)
+            ? resolveArbitraryValue.map((typeResolver) => asMap.get(typeResolver) ?? 'any')
+            : asMap.get(resolveArbitraryValue) ?? 'any',
         }
       )
     }
diff --git a/src/util/pluginUtils.js b/src/util/pluginUtils.js
index bdbc3278..7252314f 100644
--- a/src/util/pluginUtils.js
+++ b/src/util/pluginUtils.js
@@ -175,6 +175,9 @@ export function asValue(modifier, lookup = {}, { validate = () => true } = {}) {
     .replace(/^_/g, ' ')
     .replace(/\\_/g, '_')
 
+  // Keep raw strings if it starts with `url(`
+  if (value.startsWith('url(')) return value
+
   // add spaces around operators inside calc() that do not follow an operator or (
   return value.replace(
     /(-?\d*\.?\d(?!\b-.+[,)](?![^+\-/*])\D)(?:%|[a-z]+)?|\))([+\-/*])/g,
@@ -236,6 +239,12 @@ export function asAngle(modifier, lookup = {}) {
   return asUnit(modifier, ['deg', 'grad', 'rad', 'turn'], lookup)
 }
 
+export function asURL(modifier, lookup = {}) {
+  return asValue(modifier, lookup, {
+    validate: (value) => value.startsWith('url('),
+  })
+}
+
 export function asLength(modifier, lookup = {}) {
   return asUnit(
     modifier,
@@ -271,6 +280,7 @@ let typeMap = {
   color: asColor,
   angle: asAngle,
   length: asLength,
+  url: asURL,
   lookup: asLookupValue,
 }
 


Test patch:
diff --git a/tests/arbitrary-values.test.css b/tests/arbitrary-values.test.css
index 2b81c782..6d915765 100644
--- a/tests/arbitrary-values.test.css
+++ b/tests/arbitrary-values.test.css
@@ -275,6 +275,12 @@
 .bg-opacity-\[var\(--value\)\] {
   --tw-bg-opacity: var(--value);
 }
+.bg-\[url\(\'\/path-to-image\.png\'\)\] {
+  background-image: url('/path-to-image.png');
+}
+.bg-\[url\:var\(--url\)\] {
+  background-image: var(--url);
+}
 .from-\[\#da5b66\] {
   --tw-gradient-from: #da5b66;
   --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgb(218 91 102 / 0));
@@ -305,6 +311,9 @@
 .stroke-\[\#da5b66\] {
   stroke: #da5b66;
 }
+.stroke-\[url\(\#icon-gradient\)\] {
+  stroke-width: url(#icon-gradient);
+}
 .object-\[50\%\2c 50\%\] {
   object-position: 50% 50%;
 }
diff --git a/tests/arbitrary-values.test.html b/tests/arbitrary-values.test.html
index 505f96bd..bab407ed 100644
--- a/tests/arbitrary-values.test.html
+++ b/tests/arbitrary-values.test.html
@@ -11,6 +11,7 @@
     <div class="bg-[#0f0] bg-[#ff0000] bg-[#0000ffcc]"></div>
     <div class="bg-[rgb(123,123,123)] bg-[rgba(123,123,123,0.5)]"></div>
     <div class="bg-[hsl(0,100%,50%)] bg-[hsla(0,100%,50%,0.3)]"></div>
+    <div class="bg-[url('/path-to-image.png')] bg-[url:var(--url)]"></div>
     <div class="bg-opacity-[0.11]"></div>
     <div class="bg-opacity-[var(--value)]"></div>
     <div class="border-[#f00]"></div>
@@ -116,6 +117,7 @@
     <div class="object-[top,right]"></div>
     <div class="object-[var(--position)]"></div>
     <div class="stroke-[#da5b66]"></div>
+    <div class="stroke-[url(#icon-gradient)]"></div>
     <div class="leading-[var(--leading)]"></div>
     <div class="tracking-[var(--tracking)]"></div>
     <div class="placeholder-[var(--placeholder)]"></div>