Skip to content

Commit f5f21ea

Browse files
perf: improve parse performance for @import at-rules
1 parent bc63911 commit f5f21ea

File tree

7 files changed

+194
-176
lines changed

7 files changed

+194
-176
lines changed

src/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ export default function loader(content, map, meta) {
138138
esModule
139139
);
140140

141-
return callback(null, [importCode, moduleCode, exportCode].join(''));
141+
return callback(null, `${importCode}${moduleCode}${exportCode}`);
142142
})
143143
.catch((error) => {
144144
callback(

src/plugins/postcss-import-parser.js

+73-81
Original file line numberDiff line numberDiff line change
@@ -6,111 +6,103 @@ import { normalizeUrl } from '../utils';
66

77
const pluginName = 'postcss-import-parser';
88

9-
function getParsedValue(node) {
10-
if (node.type === 'function' && node.value.toLowerCase() === 'url') {
11-
const { nodes } = node;
12-
const isStringValue = nodes.length !== 0 && nodes[0].type === 'string';
13-
const url = isStringValue ? nodes[0].value : valueParser.stringify(nodes);
14-
15-
return { url, isStringValue };
16-
}
17-
18-
if (node.type === 'string') {
19-
const url = node.value;
20-
21-
return { url, isStringValue: true };
22-
}
23-
24-
return null;
25-
}
26-
27-
function parseImport(params) {
28-
const { nodes } = valueParser(params);
29-
30-
if (nodes.length === 0) {
31-
return null;
32-
}
33-
34-
const value = getParsedValue(nodes[0]);
35-
36-
if (!value) {
37-
return null;
38-
}
39-
40-
let { url } = value;
41-
42-
if (url.trim().length === 0) {
43-
return null;
44-
}
45-
46-
if (isUrlRequest(url)) {
47-
const { isStringValue } = value;
48-
49-
url = normalizeUrl(url, isStringValue);
50-
}
51-
52-
return {
53-
url,
54-
media: valueParser
55-
.stringify(nodes.slice(1))
56-
.trim()
57-
.toLowerCase(),
58-
};
59-
}
60-
61-
function walkAtRules(css, result, filter) {
62-
const items = [];
63-
9+
export default postcss.plugin(pluginName, (options) => (css, result) => {
6410
css.walkAtRules(/^import$/i, (atRule) => {
6511
// Convert only top-level @import
6612
if (atRule.parent.type !== 'root') {
6713
return;
6814
}
6915

16+
// Nodes do not exists - `@import url('http://') :root {}`
7017
if (atRule.nodes) {
7118
result.warn(
72-
"It looks like you didn't end your @import statement correctly. " +
73-
'Child nodes are attached to it.',
19+
"It looks like you didn't end your @import statement correctly. Child nodes are attached to it.",
7420
{ node: atRule }
7521
);
22+
7623
return;
7724
}
7825

79-
const parsed = parseImport(atRule.params);
26+
const { nodes } = valueParser(atRule.params);
8027

81-
if (!parsed) {
82-
// eslint-disable-next-line consistent-return
83-
return result.warn(`Unable to find uri in '${atRule.toString()}'`, {
28+
// No nodes - `@import ;`
29+
// Invalid type - `@import foo-bar;`
30+
if (
31+
nodes.length === 0 ||
32+
(nodes[0].type !== 'string' && nodes[0].type !== 'function')
33+
) {
34+
result.warn(`Unable to find uri in "${atRule.toString()}"`, {
8435
node: atRule,
8536
});
86-
}
8737

88-
if (filter && !filter(parsed)) {
8938
return;
9039
}
9140

92-
atRule.remove();
41+
let isStringValue;
42+
let url;
43+
44+
if (nodes[0].type === 'string') {
45+
isStringValue = true;
46+
url = nodes[0].value;
47+
} else if (nodes[0].type === 'function') {
48+
// Invalid function - `@import nourl(test.css);`
49+
if (nodes[0].value.toLowerCase() !== 'url') {
50+
result.warn(`Unable to find uri in "${atRule.toString()}"`, {
51+
node: atRule,
52+
});
9353

94-
items.push(parsed);
95-
});
54+
return;
55+
}
56+
57+
isStringValue =
58+
nodes[0].nodes.length !== 0 && nodes[0].nodes[0].type === 'string';
59+
url = isStringValue
60+
? nodes[0].nodes[0].value
61+
: valueParser.stringify(nodes[0].nodes);
62+
}
63+
64+
// Empty url - `@import "";` or `@import url();`
65+
if (url.trim().length === 0) {
66+
result.warn(`Unable to find uri in "${atRule.toString()}"`, {
67+
node: atRule,
68+
});
9669

97-
return items;
98-
}
70+
return;
71+
}
9972

100-
export default postcss.plugin(
101-
pluginName,
102-
(options) =>
103-
function process(css, result) {
104-
const items = walkAtRules(css, result, options.filter);
73+
const isRequestable = isUrlRequest(url);
10574

106-
items.forEach((item) => {
107-
const { url, media } = item;
75+
if (isRequestable) {
76+
url = normalizeUrl(url, isStringValue);
10877

109-
result.messages.push({
110-
pluginName,
111-
type: 'import',
112-
value: { type: '@import', url, media },
78+
// Empty url after normalize - `@import '\
79+
// \
80+
// \
81+
// ';
82+
if (url.trim().length === 0) {
83+
result.warn(`Unable to find uri in "${atRule.toString()}"`, {
84+
node: atRule,
11385
});
114-
});
86+
87+
return;
88+
}
11589
}
116-
);
90+
91+
const media = valueParser
92+
.stringify(nodes.slice(1))
93+
.trim()
94+
.toLowerCase();
95+
96+
if (options.filter && !options.filter({ url, media })) {
97+
return;
98+
}
99+
100+
atRule.remove();
101+
102+
result.messages.push({
103+
pluginName,
104+
type: 'import',
105+
value: { type: '@import', isRequestable, url, media },
106+
});
107+
});
108+
});

src/utils.js

+33-49
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@
44
*/
55
import path from 'path';
66

7-
import loaderUtils, {
8-
isUrlRequest,
9-
stringifyRequest,
10-
urlToRequest,
11-
} from 'loader-utils';
7+
import { stringifyRequest, urlToRequest, interpolateName } from 'loader-utils';
128
import normalizePath from 'normalize-path';
139
import cssesc from 'cssesc';
1410
import modulesValues from 'postcss-modules-values';
@@ -65,8 +61,7 @@ function getLocalIdent(loaderContext, localIdentName, localName, options) {
6561
// Using `[path]` placeholder outputs `/` we need escape their
6662
// Also directories can contains invalid characters for css we need escape their too
6763
return cssesc(
68-
loaderUtils
69-
.interpolateName(loaderContext, localIdentName, options)
64+
interpolateName(loaderContext, localIdentName, options)
7065
// For `[hash]` placeholder
7166
.replace(/^((-?[0-9])|--)/, '_$1')
7267
.replace(filenameReservedRegex, '-')
@@ -216,16 +211,15 @@ function getImportCode(
216211
let importPrefix;
217212

218213
if (exportType === 'full') {
214+
const apiUrl = stringifyRequest(
215+
loaderContext,
216+
require.resolve('./runtime/api')
217+
);
218+
219219
importItems.push(
220220
esModule
221-
? `import ___CSS_LOADER_API_IMPORT___ from ${stringifyRequest(
222-
loaderContext,
223-
require.resolve('./runtime/api')
224-
)};`
225-
: `var ___CSS_LOADER_API_IMPORT___ = require(${stringifyRequest(
226-
loaderContext,
227-
require.resolve('./runtime/api')
228-
)});`
221+
? `import ___CSS_LOADER_API_IMPORT___ from ${apiUrl};`
222+
: `var ___CSS_LOADER_API_IMPORT___ = require(${apiUrl});`
229223
);
230224
codeItems.push(
231225
esModule
@@ -239,10 +233,10 @@ function getImportCode(
239233
switch (item.type) {
240234
case '@import':
241235
{
242-
const { url, media } = item;
236+
const { isRequestable, url, media } = item;
243237
const preparedMedia = media ? `, ${JSON.stringify(media)}` : '';
244238

245-
if (!isUrlRequest(url)) {
239+
if (!isRequestable) {
246240
codeItems.push(
247241
`exports.push([module.id, ${JSON.stringify(
248242
`@import url(${url});`
@@ -259,17 +253,16 @@ function getImportCode(
259253
importPrefix = getImportPrefix(loaderContext, importLoaders);
260254
}
261255

256+
const importUrl = stringifyRequest(
257+
loaderContext,
258+
importPrefix + url
259+
);
260+
262261
importName = `___CSS_LOADER_AT_RULE_IMPORT_${atRuleImportNames.size}___`;
263262
importItems.push(
264263
esModule
265-
? `import ${importName} from ${stringifyRequest(
266-
loaderContext,
267-
importPrefix + url
268-
)};`
269-
: `var ${importName} = require(${stringifyRequest(
270-
loaderContext,
271-
importPrefix + url
272-
)});`
264+
? `import ${importName} from ${importUrl};`
265+
: `var ${importName} = require(${importUrl});`
273266
);
274267

275268
atRuleImportNames.set(url, importName);
@@ -281,16 +274,15 @@ function getImportCode(
281274
case 'url':
282275
{
283276
if (urlImportNames.size === 0) {
277+
const helperUrl = stringifyRequest(
278+
loaderContext,
279+
require.resolve('./runtime/getUrl.js')
280+
);
281+
284282
importItems.push(
285283
esModule
286-
? `import ___CSS_LOADER_GET_URL_IMPORT___ from ${stringifyRequest(
287-
loaderContext,
288-
require.resolve('./runtime/getUrl.js')
289-
)};`
290-
: `var ___CSS_LOADER_GET_URL_IMPORT___ = require(${stringifyRequest(
291-
loaderContext,
292-
require.resolve('./runtime/getUrl.js')
293-
)});`
284+
? `import ___CSS_LOADER_GET_URL_IMPORT___ from ${helperUrl};`
285+
: `var ___CSS_LOADER_GET_URL_IMPORT___ = require(${helperUrl});`
294286
);
295287
}
296288

@@ -299,17 +291,13 @@ function getImportCode(
299291
let importName = urlImportNames.get(url);
300292

301293
if (!importName) {
294+
const importUrl = stringifyRequest(loaderContext, url);
295+
302296
importName = `___CSS_LOADER_URL_IMPORT_${urlImportNames.size}___`;
303297
importItems.push(
304298
esModule
305-
? `import ${importName} from ${stringifyRequest(
306-
loaderContext,
307-
url
308-
)};`
309-
: `var ${importName} = require(${stringifyRequest(
310-
loaderContext,
311-
url
312-
)});`
299+
? `import ${importName} from ${importUrl};`
300+
: `var ${importName} = require(${importUrl});`
313301
);
314302

315303
urlImportNames.set(url, importName);
@@ -335,16 +323,12 @@ function getImportCode(
335323
importPrefix = getImportPrefix(loaderContext, importLoaders);
336324
}
337325

326+
const importUrl = stringifyRequest(loaderContext, importPrefix + url);
327+
338328
importItems.push(
339329
esModule
340-
? `import ${importName} from ${stringifyRequest(
341-
loaderContext,
342-
importPrefix + url
343-
)};`
344-
: `var ${importName} = require(${stringifyRequest(
345-
loaderContext,
346-
importPrefix + url
347-
)});`
330+
? `import ${importName} from ${importUrl};`
331+
: `var ${importName} = require(${importUrl});`
348332
);
349333

350334
if (exportType === 'full') {

0 commit comments

Comments
 (0)