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