@@ -2,131 +2,166 @@ import postcss from 'postcss';
22import valueParser from 'postcss-value-parser' ;
33import { isUrlRequest } from 'loader-utils' ;
44
5- import { normalizeUrl } from '../utils' ;
5+ import { normalizeUrl , resolveRequests } from '../utils' ;
66
77const pluginName = 'postcss-import-parser' ;
88
99export default postcss . plugin ( pluginName , ( options ) => ( css , result ) => {
10- const importsMap = new Map ( ) ;
11-
12- css . walkAtRules ( / ^ i m p o r t $ / i, ( atRule ) => {
13- // Convert only top-level @import
14- if ( atRule . parent . type !== 'root' ) {
15- return ;
16- }
17-
18- // Nodes do not exists - `@import url('http://') :root {}`
19- if ( atRule . nodes ) {
20- result . warn (
21- "It looks like you didn't end your @import statement correctly. Child nodes are attached to it." ,
22- { node : atRule }
23- ) ;
10+ return new Promise ( ( resolve , reject ) => {
11+ const importsMap = new Map ( ) ;
12+ const tasks = [ ] ;
2413
25- return ;
26- }
27-
28- const { nodes } = valueParser ( atRule . params ) ;
29-
30- // No nodes - `@import ;`
31- // Invalid type - `@import foo-bar;`
32- if (
33- nodes . length === 0 ||
34- ( nodes [ 0 ] . type !== 'string' && nodes [ 0 ] . type !== 'function' )
35- ) {
36- result . warn ( `Unable to find uri in "${ atRule . toString ( ) } "` , {
37- node : atRule ,
38- } ) ;
39-
40- return ;
41- }
42-
43- let isStringValue ;
44- let url ;
45-
46- if ( nodes [ 0 ] . type === 'string' ) {
47- isStringValue = true ;
48- url = nodes [ 0 ] . value ;
49- } else if ( nodes [ 0 ] . type === 'function' ) {
50- // Invalid function - `@import nourl(test.css);`
51- if ( nodes [ 0 ] . value . toLowerCase ( ) !== 'url' ) {
52- result . warn ( `Unable to find uri in "${ atRule . toString ( ) } "` , {
53- node : atRule ,
54- } ) ;
14+ // A counter is used instead of an index in callback css.walkAtRules because we mutate AST (atRule.remove())
15+ let index = 0 ;
5516
17+ css . walkAtRules ( / ^ i m p o r t $ / i, ( atRule ) => {
18+ // Convert only top-level @import
19+ if ( atRule . parent . type !== 'root' ) {
5620 return ;
5721 }
5822
59- isStringValue =
60- nodes [ 0 ] . nodes . length !== 0 && nodes [ 0 ] . nodes [ 0 ] . type === 'string' ;
61- url = isStringValue
62- ? nodes [ 0 ] . nodes [ 0 ] . value
63- : valueParser . stringify ( nodes [ 0 ] . nodes ) ;
64- }
23+ // Nodes do not exists - `@import url('http://') :root {}`
24+ if ( atRule . nodes ) {
25+ result . warn (
26+ "It looks like you didn't end your @import statement correctly. Child nodes are attached to it." ,
27+ { node : atRule }
28+ ) ;
29+
30+ return ;
31+ }
6532
66- // Empty url - `@import "";` or `@import url();`
67- if ( url . trim ( ) . length === 0 ) {
68- result . warn ( `Unable to find uri in "${ atRule . toString ( ) } "` , {
69- node : atRule ,
70- } ) ;
33+ const { nodes } = valueParser ( atRule . params ) ;
7134
72- return ;
73- }
35+ // No nodes - `@import ;`
36+ // Invalid type - `@import foo-bar;`
37+ if (
38+ nodes . length === 0 ||
39+ ( nodes [ 0 ] . type !== 'string' && nodes [ 0 ] . type !== 'function' )
40+ ) {
41+ result . warn ( `Unable to find uri in "${ atRule . toString ( ) } "` , {
42+ node : atRule ,
43+ } ) ;
7444
75- const isRequestable = isUrlRequest ( url ) ;
45+ return ;
46+ }
7647
77- if ( isRequestable ) {
78- url = normalizeUrl ( url , isStringValue ) ;
48+ let isStringValue ;
49+ let url ;
50+
51+ if ( nodes [ 0 ] . type === 'string' ) {
52+ isStringValue = true ;
53+ url = nodes [ 0 ] . value ;
54+ } else if ( nodes [ 0 ] . type === 'function' ) {
55+ // Invalid function - `@import nourl(test.css);`
56+ if ( nodes [ 0 ] . value . toLowerCase ( ) !== 'url' ) {
57+ result . warn ( `Unable to find uri in "${ atRule . toString ( ) } "` , {
58+ node : atRule ,
59+ } ) ;
60+
61+ return ;
62+ }
63+
64+ isStringValue =
65+ nodes [ 0 ] . nodes . length !== 0 && nodes [ 0 ] . nodes [ 0 ] . type === 'string' ;
66+ url = isStringValue
67+ ? nodes [ 0 ] . nodes [ 0 ] . value
68+ : valueParser . stringify ( nodes [ 0 ] . nodes ) ;
69+ }
7970
80- // Empty url after normalize - `@import '\
81- // \
82- // \
83- // ';
71+ // Empty url - `@import "";` or `@import url();`
8472 if ( url . trim ( ) . length === 0 ) {
8573 result . warn ( `Unable to find uri in "${ atRule . toString ( ) } "` , {
8674 node : atRule ,
8775 } ) ;
8876
8977 return ;
9078 }
91- }
92-
93- const media = valueParser . stringify ( nodes . slice ( 1 ) ) . trim ( ) . toLowerCase ( ) ;
94-
95- if ( options . filter && ! options . filter ( { url, media } ) ) {
96- return ;
97- }
9879
99- atRule . remove ( ) ;
80+ const isRequestable = isUrlRequest ( url ) ;
10081
101- if ( isRequestable ) {
102- const importKey = url ;
103- let importName = importsMap . get ( importKey ) ;
82+ if ( isRequestable ) {
83+ url = normalizeUrl ( url , isStringValue ) ;
10484
105- if ( ! importName ) {
106- importName = `___CSS_LOADER_AT_RULE_IMPORT_${ importsMap . size } ___` ;
107- importsMap . set ( importKey , importName ) ;
85+ // Empty url after normalize - `@import '\
86+ // \
87+ // \
88+ // ';
89+ if ( url . trim ( ) . length === 0 ) {
90+ result . warn ( `Unable to find uri in "${ atRule . toString ( ) } "` , {
91+ node : atRule ,
92+ } ) ;
10893
109- result . messages . push ( {
110- type : 'import' ,
111- value : {
112- importName,
113- url : options . urlHandler ? options . urlHandler ( url ) : url ,
114- } ,
115- } ) ;
94+ return ;
95+ }
11696 }
11797
118- result . messages . push ( {
119- type : 'api-import' ,
120- value : { type : 'internal' , importName, media } ,
121- } ) ;
98+ const media = valueParser . stringify ( nodes . slice ( 1 ) ) . trim ( ) . toLowerCase ( ) ;
12299
123- return ;
124- }
100+ if ( options . filter && ! options . filter ( { url, media } ) ) {
101+ return ;
102+ }
125103
126- result . messages . push ( {
127- pluginName,
128- type : 'api-import' ,
129- value : { type : 'external' , url, media } ,
104+ atRule . remove ( ) ;
105+
106+ index += 1 ;
107+
108+ tasks . push (
109+ Promise . resolve ( index ) . then ( async ( currentIndex ) => {
110+ if ( isRequestable ) {
111+ const importKey = url ;
112+ let importName = importsMap . get ( importKey ) ;
113+
114+ if ( ! importName ) {
115+ importName = `___CSS_LOADER_AT_RULE_IMPORT_${ importsMap . size } ___` ;
116+ importsMap . set ( importKey , importName ) ;
117+
118+ const { resolver, context } = options ;
119+
120+ let resolvedUrl ;
121+
122+ try {
123+ resolvedUrl = await resolveRequests ( resolver , context , [ url ] ) ;
124+ } catch ( error ) {
125+ throw error ;
126+ }
127+
128+ result . messages . push ( {
129+ type : 'import' ,
130+ value : {
131+ importName,
132+ url : options . urlHandler
133+ ? options . urlHandler ( resolvedUrl )
134+ : resolvedUrl ,
135+ index : currentIndex ,
136+ } ,
137+ } ) ;
138+ }
139+
140+ result . messages . push ( {
141+ type : 'api-import' ,
142+ value : {
143+ type : 'internal' ,
144+ importName,
145+ media,
146+ index : currentIndex ,
147+ } ,
148+ } ) ;
149+
150+ return ;
151+ }
152+
153+ result . messages . push ( {
154+ pluginName,
155+ type : 'api-import' ,
156+ value : { type : 'external' , url, media, index : currentIndex } ,
157+ } ) ;
158+ } )
159+ ) ;
130160 } ) ;
161+
162+ Promise . all ( tasks ) . then (
163+ ( ) => resolve ( ) ,
164+ ( error ) => reject ( error )
165+ ) ;
131166 } ) ;
132167} ) ;
0 commit comments