1
1
import postcss from 'postcss' ;
2
2
import valueParser from 'postcss-value-parser' ;
3
- import { isUrlRequest } from 'loader-utils' ;
3
+ import { isUrlRequest , stringifyRequest , urlToRequest } from 'loader-utils' ;
4
4
5
5
const pluginName = 'postcss-css-loader-icss-url' ;
6
6
7
+ function normalizeUrl ( url ) {
8
+ return url . split ( / ( \? ) ? # / ) ;
9
+ }
10
+
7
11
const walkUrls = ( parsed , callback ) => {
8
12
parsed . walk ( ( node ) => {
9
13
if ( node . type !== 'function' || node . value . toLowerCase ( ) !== 'url' ) {
10
14
return ;
11
15
}
12
16
13
- const content =
17
+ const url =
14
18
node . nodes . length !== 0 && node . nodes [ 0 ] . type === 'string'
15
19
? node . nodes [ 0 ] . value
16
20
: valueParser . stringify ( node . nodes ) ;
17
21
18
- if ( content . trim ( ) . replace ( / \\ [ \r \n ] / g, '' ) . length !== 0 ) {
19
- callback ( node , content ) ;
22
+ if ( url . trim ( ) . replace ( / \\ [ \r \n ] / g, '' ) . length !== 0 ) {
23
+ callback ( node , url ) ;
20
24
}
21
25
22
26
// Do not traverse inside url
@@ -57,7 +61,8 @@ const walkDeclsWithUrl = (css, filter) => {
57
61
result . push ( {
58
62
decl,
59
63
parsed,
60
- values,
64
+ // Remove `#hash` and `?#hash` to avoid duplicate require for assets with `#hash` and `?#hash`
65
+ values : values . map ( ( value ) => normalizeUrl ( value ) [ 0 ] ) ,
61
66
} ) ;
62
67
} ) ;
63
68
@@ -79,26 +84,69 @@ const mapUrls = (parsed, map) => {
79
84
export default postcss . plugin (
80
85
pluginName ,
81
86
( ) =>
82
- function process ( css ) {
87
+ function process ( css , result ) {
83
88
const traversed = walkDeclsWithUrl ( css , ( value ) => isUrlRequest ( value ) ) ;
84
89
const paths = uniq ( flatten ( traversed . map ( ( item ) => item . values ) ) ) ;
85
- const imports = { } ;
86
- const aliases = { } ;
87
90
88
- paths . forEach ( ( path , index ) => {
89
- const alias = `__url__${ index } __` ;
91
+ if ( paths . length === 0 ) {
92
+ return ;
93
+ }
90
94
91
- imports [ `" ${ path } "` ] = {
92
- [ alias ] : 'default' ,
93
- } ;
94
- aliases [ path ] = alias ;
95
+ const urls = { } ;
96
+
97
+ paths . forEach ( ( path , index ) => {
98
+ urls [ path ] = `___CSS_LOADER_URL___ ${ index } ___` ;
95
99
} ) ;
96
100
97
101
traversed . forEach ( ( item ) => {
98
- mapUrls ( item . parsed , ( value ) => aliases [ value ] ) ;
102
+ mapUrls ( item . parsed , ( url ) => {
103
+ const [ normalizedUrl , singleQuery , hashValue ] = normalizeUrl ( url ) ;
104
+
105
+ // Return `#hash` and `?#hash` in css
106
+ return `${ urls [ normalizedUrl ] } ${ singleQuery || '' } ${
107
+ hashValue ? `#${ hashValue } ` : ''
108
+ } `;
109
+ } ) ;
99
110
100
111
// eslint-disable-next-line no-param-reassign
101
112
item . decl . value = item . parsed . toString ( ) ;
102
113
} ) ;
114
+
115
+ let hasURLEscapeRuntime = false ;
116
+
117
+ Object . keys ( urls ) . forEach ( ( url ) => {
118
+ result . messages . push ( {
119
+ pluginName,
120
+ type : 'module' ,
121
+ modify ( moduleObj , loaderContext ) {
122
+ if ( ! hasURLEscapeRuntime ) {
123
+ moduleObj . imports . push (
124
+ `var escape = require(${ stringifyRequest (
125
+ loaderContext ,
126
+ require . resolve ( '../runtime/escape' )
127
+ ) } );`
128
+ ) ;
129
+
130
+ hasURLEscapeRuntime = true ;
131
+ }
132
+
133
+ const placeholder = urls [ url ] ;
134
+ const [ normalizedUrl ] = normalizeUrl ( url ) ;
135
+
136
+ moduleObj . imports . push (
137
+ `var ${ placeholder } = escape(require(${ stringifyRequest (
138
+ loaderContext ,
139
+ urlToRequest ( normalizedUrl )
140
+ ) } ));`
141
+ ) ;
142
+ moduleObj . module = moduleObj . module . replace (
143
+ new RegExp ( placeholder , 'g' ) ,
144
+ `" + ${ placeholder } + "`
145
+ ) ;
146
+
147
+ return moduleObj ;
148
+ } ,
149
+ } ) ;
150
+ } ) ;
103
151
}
104
152
) ;
0 commit comments