@@ -1901,10 +1901,15 @@ type CssUrlResolver = (
1901
1901
) =>
1902
1902
| [ url : string , id : string | undefined ]
1903
1903
| Promise < [ url : string , id : string | undefined ] >
1904
+ /**
1905
+ * replace URL references
1906
+ *
1907
+ * When returning `false`, it keeps the content as-is
1908
+ */
1904
1909
type CssUrlReplacer = (
1905
- url : string ,
1906
- importer ? : string ,
1907
- ) => string | Promise < string >
1910
+ unquotedUrl : string ,
1911
+ rawUrl : string ,
1912
+ ) => string | false | Promise < string | false >
1908
1913
// https://drafts.csswg.org/css-syntax-3/#identifier-code-point
1909
1914
export const cssUrlRE =
1910
1915
/ (?< ! @ i m p o r t \s + ) (?< = ^ | [ ^ \w \- \u0080 - \uffff ] ) u r l \( ( \s * ( ' [ ^ ' ] + ' | " [ ^ " ] + " ) \s * | [ ^ ' " ) ] + ) \) /
@@ -2034,12 +2039,12 @@ async function rewriteCssImageSet(
2034
2039
return url
2035
2040
} )
2036
2041
}
2037
- function skipUrlReplacer ( rawUrl : string ) {
2042
+ function skipUrlReplacer ( unquotedUrl : string ) {
2038
2043
return (
2039
- isExternalUrl ( rawUrl ) ||
2040
- isDataUrl ( rawUrl ) ||
2041
- rawUrl [ 0 ] === '#' ||
2042
- functionCallRE . test ( rawUrl )
2044
+ isExternalUrl ( unquotedUrl ) ||
2045
+ isDataUrl ( unquotedUrl ) ||
2046
+ unquotedUrl [ 0 ] === '#' ||
2047
+ functionCallRE . test ( unquotedUrl )
2043
2048
)
2044
2049
}
2045
2050
async function doUrlReplace (
@@ -2050,16 +2055,20 @@ async function doUrlReplace(
2050
2055
) {
2051
2056
let wrap = ''
2052
2057
const first = rawUrl [ 0 ]
2058
+ let unquotedUrl = rawUrl
2053
2059
if ( first === `"` || first === `'` ) {
2054
2060
wrap = first
2055
- rawUrl = rawUrl . slice ( 1 , - 1 )
2061
+ unquotedUrl = rawUrl . slice ( 1 , - 1 )
2062
+ }
2063
+ if ( skipUrlReplacer ( unquotedUrl ) ) {
2064
+ return matched
2056
2065
}
2057
2066
2058
- if ( skipUrlReplacer ( rawUrl ) ) {
2067
+ let newUrl = await replacer ( unquotedUrl , rawUrl )
2068
+ if ( newUrl === false ) {
2059
2069
return matched
2060
2070
}
2061
2071
2062
- let newUrl = await replacer ( rawUrl )
2063
2072
// The new url might need wrapping even if the original did not have it, e.g. if a space was added during replacement
2064
2073
if ( wrap === '' && newUrl !== encodeURI ( newUrl ) ) {
2065
2074
wrap = '"'
@@ -2083,16 +2092,22 @@ async function doImportCSSReplace(
2083
2092
) {
2084
2093
let wrap = ''
2085
2094
const first = rawUrl [ 0 ]
2095
+ let unquotedUrl = rawUrl
2086
2096
if ( first === `"` || first === `'` ) {
2087
2097
wrap = first
2088
- rawUrl = rawUrl . slice ( 1 , - 1 )
2098
+ unquotedUrl = rawUrl . slice ( 1 , - 1 )
2099
+ }
2100
+ if ( skipUrlReplacer ( unquotedUrl ) ) {
2101
+ return matched
2089
2102
}
2090
- if ( isExternalUrl ( rawUrl ) || isDataUrl ( rawUrl ) || rawUrl [ 0 ] === '#' ) {
2103
+
2104
+ const newUrl = await replacer ( unquotedUrl , rawUrl )
2105
+ if ( newUrl === false ) {
2091
2106
return matched
2092
2107
}
2093
2108
2094
2109
const prefix = matched . includes ( 'url(' ) ? 'url(' : ''
2095
- return `@import ${ prefix } ${ wrap } ${ await replacer ( rawUrl ) } ${ wrap } `
2110
+ return `@import ${ prefix } ${ wrap } ${ newUrl } ${ wrap } `
2096
2111
}
2097
2112
2098
2113
async function minifyCSS (
@@ -2392,14 +2407,24 @@ const makeModernScssWorker = (
2392
2407
return resolved ?? null
2393
2408
}
2394
2409
2410
+ const skipRebaseUrls = ( unquotedUrl : string , rawUrl : string ) => {
2411
+ const isQuoted = rawUrl [ 0 ] === '"' || rawUrl [ 0 ] === "'"
2412
+ // matches `url($foo)`
2413
+ if ( ! isQuoted && unquotedUrl [ 0 ] === '$' ) {
2414
+ return true
2415
+ }
2416
+ // matches `url(#{foo})` and `url('#{foo}')`
2417
+ return unquotedUrl . startsWith ( '#{' )
2418
+ }
2419
+
2395
2420
const internalLoad = async ( file : string , rootFile : string ) => {
2396
2421
const result = await rebaseUrls (
2397
2422
environment ,
2398
2423
file ,
2399
2424
rootFile ,
2400
2425
alias ,
2401
- '$' ,
2402
2426
resolvers . sass ,
2427
+ skipRebaseUrls ,
2403
2428
)
2404
2429
if ( result . contents ) {
2405
2430
return result . contents
@@ -2529,6 +2554,16 @@ const makeModernCompilerScssWorker = (
2529
2554
sassOptions . url = pathToFileURL ( options . filename )
2530
2555
sassOptions . sourceMap = options . enableSourcemap
2531
2556
2557
+ const skipRebaseUrls = ( unquotedUrl : string , rawUrl : string ) => {
2558
+ const isQuoted = rawUrl [ 0 ] === '"' || rawUrl [ 0 ] === "'"
2559
+ // matches `url($foo)`
2560
+ if ( ! isQuoted && unquotedUrl [ 0 ] === '$' ) {
2561
+ return true
2562
+ }
2563
+ // matches `url(#{foo})` and `url('#{foo}')`
2564
+ return unquotedUrl . startsWith ( '#{' )
2565
+ }
2566
+
2532
2567
const internalImporter : Sass . Importer < 'async' > = {
2533
2568
async canonicalize ( url , context ) {
2534
2569
const importer = context . containingUrl
@@ -2562,8 +2597,8 @@ const makeModernCompilerScssWorker = (
2562
2597
fileURLToPath ( canonicalUrl ) ,
2563
2598
options . filename ,
2564
2599
alias ,
2565
- '$' ,
2566
2600
resolvers . sass ,
2601
+ skipRebaseUrls ,
2567
2602
)
2568
2603
const contents =
2569
2604
result . contents ?? ( await fsp . readFile ( result . file , 'utf-8' ) )
@@ -2709,8 +2744,8 @@ async function rebaseUrls(
2709
2744
file : string ,
2710
2745
rootFile : string ,
2711
2746
alias : Alias [ ] ,
2712
- variablePrefix : string ,
2713
2747
resolver : ResolveIdFn ,
2748
+ ignoreUrl ?: ( unquotedUrl : string , rawUrl : string ) => boolean ,
2714
2749
) : Promise < { file : string ; contents ?: string } > {
2715
2750
file = path . resolve ( file ) // ensure os-specific flashes
2716
2751
// in the same dir, no need to rebase
@@ -2733,20 +2768,22 @@ async function rebaseUrls(
2733
2768
}
2734
2769
2735
2770
let rebased
2736
- const rebaseFn = async ( url : string ) => {
2737
- if ( url [ 0 ] === '/' ) return url
2738
- // ignore url's starting with variable
2739
- if ( url . startsWith ( variablePrefix ) ) return url
2771
+ const rebaseFn = async ( unquotedUrl : string , rawUrl : string ) => {
2772
+ if ( ignoreUrl ?.( unquotedUrl , rawUrl ) ) return false
2773
+ if ( unquotedUrl [ 0 ] === '/' ) return unquotedUrl
2740
2774
// match alias, no need to rewrite
2741
2775
for ( const { find } of alias ) {
2742
2776
const matches =
2743
- typeof find === 'string' ? url . startsWith ( find ) : find . test ( url )
2777
+ typeof find === 'string'
2778
+ ? unquotedUrl . startsWith ( find )
2779
+ : find . test ( unquotedUrl )
2744
2780
if ( matches ) {
2745
- return url
2781
+ return unquotedUrl
2746
2782
}
2747
2783
}
2748
2784
const absolute =
2749
- ( await resolver ( environment , url , file ) ) || path . resolve ( fileDir , url )
2785
+ ( await resolver ( environment , unquotedUrl , file ) ) ||
2786
+ path . resolve ( fileDir , unquotedUrl )
2750
2787
const relative = path . relative ( rootDir , absolute )
2751
2788
return normalizePath ( relative )
2752
2789
}
@@ -2778,6 +2815,13 @@ const makeLessWorker = (
2778
2815
alias : Alias [ ] ,
2779
2816
maxWorkers : number | undefined ,
2780
2817
) => {
2818
+ const skipRebaseUrls = ( unquotedUrl : string , _rawUrl : string ) => {
2819
+ // matches both
2820
+ // - interpolation: `url('@{foo}')`
2821
+ // - variable: `url(@foo)`
2822
+ return unquotedUrl [ 0 ] === '@'
2823
+ }
2824
+
2781
2825
const viteLessResolve = async (
2782
2826
filename : string ,
2783
2827
dir : string ,
@@ -2802,8 +2846,8 @@ const makeLessWorker = (
2802
2846
resolved ,
2803
2847
rootFile ,
2804
2848
alias ,
2805
- '@' ,
2806
2849
resolvers . less ,
2850
+ skipRebaseUrls ,
2807
2851
)
2808
2852
return {
2809
2853
resolved,
0 commit comments