1
+ import { promisify } from 'util' ;
2
+
1
3
import postcss from 'postcss' ;
2
4
import valueParser from 'postcss-value-parser' ;
3
5
4
- import { normalizeUrl } from '../utils' ;
6
+ import { normalizeUrl , resolveRequests } from '../utils' ;
5
7
6
8
const pluginName = 'postcss-url-parser' ;
7
9
8
10
const isUrlFunc = / u r l / i;
9
11
const isImageSetFunc = / ^ (?: - w e b k i t - ) ? i m a g e - s e t $ / i;
10
12
const needParseDecl = / (?: u r l | (?: - w e b k i t - ) ? i m a g e - s e t ) \( / i;
11
13
14
+ const walkCssAsync = promisify ( walkCss ) ;
15
+
12
16
function getNodeFromUrlFunc ( node ) {
13
17
return node . nodes && node . nodes [ 0 ] ;
14
18
}
15
19
16
- function walkUrls ( parsed , callback ) {
17
- parsed . walk ( ( node ) => {
18
- if ( node . type !== 'function' ) {
19
- return ;
20
- }
20
+ function ruleValidate ( rule , decl , result , options ) {
21
+ // https://www.w3.org/TR/css-syntax-3/#typedef-url-token
22
+ if ( rule . url . replace ( / ^ [ \s ] + | [ \s ] + $ / g, '' ) . length === 0 ) {
23
+ result . warn (
24
+ `Unable to find uri in '${ decl ? decl . toString ( ) : decl . value } '` ,
25
+ { node : decl }
26
+ ) ;
21
27
22
- if ( isUrlFunc . test ( node . value ) ) {
23
- const { nodes } = node ;
24
- const isStringValue = nodes . length !== 0 && nodes [ 0 ] . type === 'string' ;
25
- const url = isStringValue ? nodes [ 0 ] . value : valueParser . stringify ( nodes ) ;
28
+ return false ;
29
+ }
26
30
27
- callback ( getNodeFromUrlFunc ( node ) , url , false , isStringValue ) ;
31
+ if ( options . filter && ! options . filter ( rule . url ) ) {
32
+ return false ;
33
+ }
28
34
29
- // Do not traverse inside `url`
30
- // eslint-disable-next-line consistent-return
31
- return false ;
32
- }
35
+ return true ;
36
+ }
33
37
34
- if ( isImageSetFunc . test ( node . value ) ) {
35
- for ( const nNode of node . nodes ) {
36
- const { type, value } = nNode ;
38
+ function walkCss ( css , result , options , callback ) {
39
+ const accumulator = [ ] ;
37
40
38
- if ( type === 'function' && isUrlFunc . test ( value ) ) {
39
- const { nodes } = nNode ;
41
+ css . walkDecls ( ( decl ) => {
42
+ if ( ! needParseDecl . test ( decl . value ) ) {
43
+ return ;
44
+ }
40
45
41
- const isStringValue =
42
- nodes . length !== 0 && nodes [ 0 ] . type === 'string' ;
43
- const url = isStringValue
44
- ? nodes [ 0 ] . value
45
- : valueParser . stringify ( nodes ) ;
46
+ const parsed = valueParser ( decl . value ) ;
46
47
47
- callback ( getNodeFromUrlFunc ( nNode ) , url , false , isStringValue ) ;
48
- }
48
+ parsed . walk ( ( node ) => {
49
+ if ( node . type !== 'function' ) {
50
+ return ;
51
+ }
49
52
50
- if ( type === 'string' ) {
51
- callback ( nNode , value , true , true ) ;
53
+ if ( isUrlFunc . test ( node . value ) ) {
54
+ const { nodes } = node ;
55
+ const isStringValue = nodes . length !== 0 && nodes [ 0 ] . type === 'string' ;
56
+ const url = isStringValue
57
+ ? nodes [ 0 ] . value
58
+ : valueParser . stringify ( nodes ) ;
59
+
60
+ const rule = {
61
+ node : getNodeFromUrlFunc ( node ) ,
62
+ url,
63
+ needQuotes : false ,
64
+ isStringValue,
65
+ } ;
66
+
67
+ if ( ruleValidate ( rule , decl , result , options ) ) {
68
+ accumulator . push ( {
69
+ decl,
70
+ rule,
71
+ parsed,
72
+ } ) ;
52
73
}
74
+
75
+ // Do not traverse inside `url`
76
+ // eslint-disable-next-line consistent-return
77
+ return false ;
53
78
}
54
79
55
- // Do not traverse inside `image-set`
56
- // eslint-disable-next-line consistent-return
57
- return false ;
58
- }
80
+ if ( isImageSetFunc . test ( node . value ) ) {
81
+ for ( const nNode of node . nodes ) {
82
+ const { type, value } = nNode ;
83
+
84
+ if ( type === 'function' && isUrlFunc . test ( value ) ) {
85
+ const { nodes } = nNode ;
86
+ const isStringValue =
87
+ nodes . length !== 0 && nodes [ 0 ] . type === 'string' ;
88
+ const url = isStringValue
89
+ ? nodes [ 0 ] . value
90
+ : valueParser . stringify ( nodes ) ;
91
+
92
+ const rule = {
93
+ node : getNodeFromUrlFunc ( nNode ) ,
94
+ url,
95
+ needQuotes : false ,
96
+ isStringValue,
97
+ } ;
98
+
99
+ if ( ruleValidate ( rule , decl , result , options ) ) {
100
+ accumulator . push ( {
101
+ decl,
102
+ rule,
103
+ parsed,
104
+ } ) ;
105
+ }
106
+ }
107
+
108
+ if ( type === 'string' ) {
109
+ const rule = {
110
+ node : nNode ,
111
+ url : value ,
112
+ needQuotes : true ,
113
+ isStringValue : true ,
114
+ } ;
115
+
116
+ if ( ruleValidate ( rule , decl , result , options ) ) {
117
+ accumulator . push ( {
118
+ decl,
119
+ rule,
120
+ parsed,
121
+ } ) ;
122
+ }
123
+ }
124
+ }
125
+
126
+ // Do not traverse inside `image-set`
127
+ // eslint-disable-next-line consistent-return
128
+ return false ;
129
+ }
130
+ } ) ;
59
131
} ) ;
132
+
133
+ callback ( null , accumulator ) ;
60
134
}
61
135
62
136
export default postcss . plugin ( pluginName , ( options ) => ( css , result ) => {
63
- const importsMap = new Map ( ) ;
64
- const replacementsMap = new Map ( ) ;
137
+ return new Promise ( async ( resolve , reject ) => {
138
+ const importsMap = new Map ( ) ;
139
+ const replacementsMap = new Map ( ) ;
140
+ const urlToHelper = require . resolve ( '../runtime/getUrl.js' ) ;
65
141
66
- let hasHelper = false ;
142
+ let parsedResults ;
67
143
68
- let index = 0 ;
144
+ try {
145
+ parsedResults = await walkCssAsync ( css , result , options ) ;
146
+ } catch ( error ) {
147
+ reject ( error ) ;
148
+ }
149
+
150
+ if ( parsedResults . length === 0 ) {
151
+ resolve ( ) ;
69
152
70
- css . walkDecls ( ( decl ) => {
71
- if ( ! needParseDecl . test ( decl . value ) ) {
72
153
return ;
73
154
}
74
155
75
- const parsed = valueParser ( decl . value ) ;
156
+ const tasks = [ ] ;
76
157
77
- walkUrls ( parsed , ( node , url , needQuotes , isStringValue ) => {
78
- // https://www.w3.org/TR/css-syntax-3/#typedef-url-token
79
- if ( url . replace ( / ^ [ \s ] + | [ \s ] + $ / g, '' ) . length === 0 ) {
80
- result . warn (
81
- `Unable to find uri in '${ decl ? decl . toString ( ) : decl . value } '` ,
82
- { node : decl }
83
- ) ;
158
+ let index = 0 ;
159
+ let hasHelper = false ;
84
160
85
- return ;
86
- }
161
+ for ( const parsedResult of parsedResults ) {
162
+ index += 1 ;
87
163
88
- if ( options . filter && ! options . filter ( url ) ) {
89
- return ;
164
+ if ( ! hasHelper ) {
165
+ result . messages . push ( {
166
+ pluginName,
167
+ type : 'import' ,
168
+ value : {
169
+ // 'CSS_LOADER_GET_URL_IMPORT'
170
+ order : 2 ,
171
+ importName : '___CSS_LOADER_GET_URL_IMPORT___' ,
172
+ url : options . urlHandler
173
+ ? options . urlHandler ( urlToHelper )
174
+ : urlToHelper ,
175
+ index,
176
+ } ,
177
+ } ) ;
178
+
179
+ hasHelper = true ;
90
180
}
91
181
182
+ const { decl, rule } = parsedResult ;
183
+ const { node, url, needQuotes, isStringValue } = rule ;
92
184
const splittedUrl = url . split ( / ( \? ) ? # / ) ;
93
185
const [ urlWithoutHash , singleQuery , hashValue ] = splittedUrl ;
94
186
const hash =
95
187
singleQuery || hashValue
96
188
? `${ singleQuery ? '?' : '' } ${ hashValue ? `#${ hashValue } ` : '' } `
97
189
: '' ;
98
190
99
- const normalizedUrl = normalizeUrl ( urlWithoutHash , isStringValue ) ;
191
+ let normalizedUrl = normalizeUrl (
192
+ urlWithoutHash ,
193
+ isStringValue ,
194
+ options . rootContext
195
+ ) ;
196
+
197
+ let prefixSuffix = '' ;
198
+
199
+ const queryParts = normalizedUrl . split ( '!' ) ;
200
+
201
+ if ( queryParts . length > 1 ) {
202
+ normalizedUrl = queryParts . pop ( ) ;
203
+ prefixSuffix = queryParts . join ( '!' ) ;
204
+ }
100
205
101
206
const importKey = normalizedUrl ;
102
- let importName = importsMap . get ( importKey ) ;
103
207
104
- index += 1 ;
208
+ let importName = importsMap . get ( importKey ) ;
105
209
106
210
if ( ! importName ) {
107
211
importName = `___CSS_LOADER_URL_IMPORT_${ importsMap . size } ___` ;
108
212
importsMap . set ( importKey , importName ) ;
109
213
110
- if ( ! hasHelper ) {
111
- const urlToHelper = require . resolve ( '../runtime/getUrl.js' ) ;
112
-
113
- result . messages . push ( {
114
- pluginName ,
115
- type : 'import' ,
116
- value : {
117
- // 'CSS_LOADER_GET_URL_IMPORT'
118
- order : 2 ,
119
- importName : '___CSS_LOADER_GET_URL_IMPORT___' ,
120
- url : options . urlHandler
121
- ? options . urlHandler ( urlToHelper )
122
- : urlToHelper ,
123
- index ,
124
- } ,
125
- } ) ;
126
-
127
- hasHelper = true ;
128
- }
129
-
130
- result . messages . push ( {
131
- pluginName ,
132
- type : 'import' ,
133
- value : {
134
- // 'CSS_LOADER_URL_IMPORT'
135
- order : 3 ,
136
- importName ,
137
- url : options . urlHandler
138
- ? options . urlHandler ( normalizedUrl )
139
- : normalizedUrl ,
140
- index ,
141
- } ,
142
- } ) ;
214
+ tasks . push (
215
+ Promise . resolve ( index ) . then ( async ( currentIndex ) => {
216
+ const { resolver , context } = options ;
217
+
218
+ let resolvedUrl ;
219
+
220
+ try {
221
+ resolvedUrl = await resolveRequests ( resolver , context , [
222
+ ... new Set ( [ normalizedUrl , url ] ) ,
223
+ ] ) ;
224
+ } catch ( error ) {
225
+ throw error ;
226
+ }
227
+
228
+ if ( prefixSuffix ) {
229
+ resolvedUrl = ` ${ prefixSuffix } ! ${ resolvedUrl } ` ;
230
+ }
231
+
232
+ result . messages . push ( {
233
+ pluginName ,
234
+ type : 'import' ,
235
+ value : {
236
+ // 'CSS_LOADER_URL_IMPORT'
237
+ order : 3 ,
238
+ importName ,
239
+ url : options . urlHandler
240
+ ? options . urlHandler ( resolvedUrl )
241
+ : resolvedUrl ,
242
+ index : currentIndex ,
243
+ } ,
244
+ } ) ;
245
+ } )
246
+ ) ;
143
247
}
144
248
145
- const replacementKey = JSON . stringify ( { importKey, hash, needQuotes } ) ;
249
+ const replacementKey = JSON . stringify ( {
250
+ importKey,
251
+ hash,
252
+ needQuotes,
253
+ } ) ;
146
254
let replacementName = replacementsMap . get ( replacementKey ) ;
147
255
148
256
if ( ! replacementName ) {
@@ -168,9 +276,13 @@ export default postcss.plugin(pluginName, (options) => (css, result) => {
168
276
node . type = 'word' ;
169
277
// eslint-disable-next-line no-param-reassign
170
278
node . value = replacementName ;
171
- } ) ;
279
+ // eslint-disable-next-line no-param-reassign
280
+ decl . value = parsedResult . parsed . toString ( ) ;
281
+ }
172
282
173
- // eslint-disable-next-line no-param-reassign
174
- decl . value = parsed . toString ( ) ;
283
+ Promise . all ( tasks ) . then (
284
+ ( ) => resolve ( ) ,
285
+ ( error ) => reject ( error )
286
+ ) ;
175
287
} ) ;
176
288
} ) ;
0 commit comments