Skip to content

Commit f1b4136

Browse files
author
David Liu
authored
Merge branch 'master' into master
2 parents 723f3d9 + fe0e6c9 commit f1b4136

File tree

8 files changed

+271
-307
lines changed

8 files changed

+271
-307
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/plugins/postcss-url-parser.js

+58-106
Original file line numberDiff line numberDiff line change
@@ -59,128 +59,80 @@ function walkUrls(parsed, callback) {
5959
});
6060
}
6161

62-
function getUrlsFromValue(value, result, filter, decl) {
63-
if (!needParseDecl.test(value)) {
64-
return;
65-
}
66-
67-
const parsed = valueParser(value);
68-
const urls = [];
69-
70-
walkUrls(parsed, (node, url, needQuotes, isStringValue) => {
71-
if (url.trim().replace(/\\[\r\n]/g, '').length === 0) {
72-
result.warn(`Unable to find uri in '${decl ? decl.toString() : value}'`, {
73-
node: decl,
74-
});
75-
76-
return;
77-
}
62+
export default postcss.plugin(pluginName, (options) => (css, result) => {
63+
const importsMap = new Map();
64+
const replacersMap = new Map();
7865

79-
if (filter && !filter(url)) {
66+
css.walkDecls((decl) => {
67+
if (!needParseDecl.test(decl.value)) {
8068
return;
8169
}
8270

83-
const splittedUrl = url.split(/(\?)?#/);
84-
const [urlWithoutHash, singleQuery, hashValue] = splittedUrl;
85-
const hash =
86-
singleQuery || hashValue
87-
? `${singleQuery ? '?' : ''}${hashValue ? `#${hashValue}` : ''}`
88-
: '';
89-
90-
const normalizedUrl = normalizeUrl(urlWithoutHash, isStringValue);
91-
92-
urls.push({ node, url: normalizedUrl, hash, needQuotes });
93-
});
94-
95-
// eslint-disable-next-line consistent-return
96-
return { parsed, urls };
97-
}
98-
99-
function walkDecls(css, result, filter) {
100-
const items = [];
71+
const parsed = valueParser(decl.value);
10172

102-
css.walkDecls((decl) => {
103-
const item = getUrlsFromValue(decl.value, result, filter, decl);
73+
walkUrls(parsed, (node, url, needQuotes, isStringValue) => {
74+
if (url.trim().replace(/\\[\r\n]/g, '').length === 0) {
75+
result.warn(
76+
`Unable to find uri in '${decl ? decl.toString() : decl.value}'`,
77+
{ node: decl }
78+
);
10479

105-
if (!item || item.urls.length === 0) {
106-
return;
107-
}
80+
return;
81+
}
10882

109-
items.push({ decl, parsed: item.parsed, urls: item.urls });
110-
});
83+
if (options.filter && !options.filter(url)) {
84+
return;
85+
}
11186

112-
return items;
113-
}
87+
const splittedUrl = url.split(/(\?)?#/);
88+
const [urlWithoutHash, singleQuery, hashValue] = splittedUrl;
89+
const hash =
90+
singleQuery || hashValue
91+
? `${singleQuery ? '?' : ''}${hashValue ? `#${hashValue}` : ''}`
92+
: '';
11493

115-
function flatten(array) {
116-
return array.reduce((a, b) => a.concat(b), []);
117-
}
94+
const normalizedUrl = normalizeUrl(urlWithoutHash, isStringValue);
11895

119-
function collectUniqueUrlsWithNodes(array) {
120-
return array.reduce((accumulator, currentValue) => {
121-
const { url, needQuotes, hash, node } = currentValue;
122-
const found = accumulator.find(
123-
(item) =>
124-
url === item.url && needQuotes === item.needQuotes && hash === item.hash
125-
);
126-
127-
if (!found) {
128-
accumulator.push({ url, hash, needQuotes, nodes: [node] });
129-
} else {
130-
found.nodes.push(node);
131-
}
96+
const importKey = normalizedUrl;
97+
let importName = importsMap.get(importKey);
13298

133-
return accumulator;
134-
}, []);
135-
}
99+
if (!importName) {
100+
importName = `___CSS_LOADER_URL_IMPORT_${importsMap.size}___`;
101+
importsMap.set(importKey, importName);
136102

137-
export default postcss.plugin(
138-
pluginName,
139-
(options) =>
140-
function process(css, result) {
141-
const traversed = walkDecls(css, result, options.filter);
142-
const flattenTraversed = flatten(traversed.map((item) => item.urls));
143-
const urlsWithNodes = collectUniqueUrlsWithNodes(flattenTraversed);
144-
const replacers = new Map();
145-
146-
urlsWithNodes.forEach((urlWithNodes, index) => {
147-
const { url, hash, needQuotes, nodes } = urlWithNodes;
148-
const replacementName = `___CSS_LOADER_URL_REPLACEMENT_${index}___`;
149-
150-
result.messages.push(
151-
{
152-
pluginName,
153-
type: 'import',
154-
value: { type: 'url', replacementName, url, needQuotes, hash },
103+
result.messages.push({
104+
pluginName,
105+
type: 'import',
106+
value: {
107+
type: 'url',
108+
importName,
109+
url: normalizedUrl,
155110
},
156-
{
157-
pluginName,
158-
type: 'replacer',
159-
value: { type: 'url', replacementName },
160-
}
161-
);
162-
163-
nodes.forEach((node) => {
164-
replacers.set(node, replacementName);
165111
});
166-
});
112+
}
167113

168-
traversed.forEach((item) => {
169-
walkUrls(item.parsed, (node) => {
170-
const replacementName = replacers.get(node);
114+
const replacerKey = JSON.stringify({ importKey, hash, needQuotes });
171115

172-
if (!replacementName) {
173-
return;
174-
}
116+
let replacerName = replacersMap.get(replacerKey);
175117

176-
// eslint-disable-next-line no-param-reassign
177-
node.type = 'word';
178-
// eslint-disable-next-line no-param-reassign
179-
node.value = replacementName;
118+
if (!replacerName) {
119+
replacerName = `___CSS_LOADER_URL_REPLACEMENT_${replacersMap.size}___`;
120+
replacersMap.set(replacerKey, replacerName);
121+
122+
result.messages.push({
123+
pluginName,
124+
type: 'replacer',
125+
value: { type: 'url', replacerName, importName, hash, needQuotes },
180126
});
127+
}
181128

182-
// eslint-disable-next-line no-param-reassign
183-
item.decl.value = item.parsed.toString();
184-
});
185-
}
186-
);
129+
// eslint-disable-next-line no-param-reassign
130+
node.type = 'word';
131+
// eslint-disable-next-line no-param-reassign
132+
node.value = replacerName;
133+
});
134+
135+
// eslint-disable-next-line no-param-reassign
136+
decl.value = parsed.toString();
137+
});
138+
});

0 commit comments

Comments
 (0)