Skip to content

Commit 6cf8a2b

Browse files
refactor: plugins (#817)
1 parent 21254c8 commit 6cf8a2b

8 files changed

+260
-180
lines changed

lib/loader.js

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,6 @@ module.exports = function loader(content, map) {
5050

5151
const alreadyImported = {};
5252
const importJs = result.importItems
53-
.map((imp) => {
54-
// fixes #781 when importing `url(filename.css )`
55-
// eslint-disable-next-line no-param-reassign
56-
imp.url = imp.url.trim();
57-
return imp;
58-
})
5953
.filter((imp) => {
6054
if (!imp.mediaQuery) {
6155
if (alreadyImported[imp.url]) {
@@ -98,7 +92,11 @@ module.exports = function loader(content, map) {
9892
// helper for ensuring valid CSS strings from requires
9993
let urlEscapeHelper = '';
10094

101-
if (options.url !== false && result.urlItems.length > 0) {
95+
if (
96+
options.url !== false &&
97+
result.urlItems &&
98+
result.urlItems.length > 0
99+
) {
102100
urlEscapeHelper = `var escape = require(${loaderUtils.stringifyRequest(
103101
this,
104102
require.resolve('./runtime/escape.js')

lib/plugins/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const importParser = require('./postcss-import-parser');
2+
const parser = require('./postcss-parser');
3+
const urlParser = require('./postcss-url-parser');
4+
5+
module.exports = {
6+
importParser,
7+
parser,
8+
urlParser,
9+
};

lib/plugins/postcss-import-parser.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
const postcss = require('postcss');
2+
const loaderUtils = require('loader-utils');
3+
const Tokenizer = require('css-selector-tokenizer');
4+
5+
const pluginName = 'postcss-import-parser';
6+
7+
module.exports = postcss.plugin(
8+
pluginName,
9+
(options) =>
10+
function process(css, result) {
11+
const importItems = [];
12+
13+
css.walkAtRules(/^import$/i, (atrule) => {
14+
// Convert only top-level @import
15+
if (atrule.parent.type !== 'root') {
16+
return;
17+
}
18+
19+
if (atrule.nodes) {
20+
result.warn(
21+
"It looks like you didn't end your @import statement correctly. " +
22+
'Child nodes are attached to it.',
23+
{ node: atrule }
24+
);
25+
return;
26+
}
27+
28+
const values = Tokenizer.parseValues(atrule.params);
29+
let [url] = values.nodes[0].nodes;
30+
31+
if (url && url.type === 'url') {
32+
({ url } = url);
33+
} else if (url && url.type === 'string') {
34+
url = url.value;
35+
} else {
36+
result.warn(`Unable to find uri in '${atrule.toString()}'`, {
37+
node: atrule,
38+
});
39+
40+
return;
41+
}
42+
43+
if (!url.replace(/\s/g, '').length) {
44+
result.warn(`Unable to find uri in '${atrule.toString()}'`, {
45+
node: atrule,
46+
});
47+
48+
return;
49+
}
50+
51+
values.nodes[0].nodes.shift();
52+
53+
const mediaQuery = Tokenizer.stringifyValues(values);
54+
55+
url = url.trim();
56+
57+
if (loaderUtils.isUrlRequest(url)) {
58+
url = loaderUtils.urlToRequest(url);
59+
}
60+
61+
importItems.push({
62+
url,
63+
mediaQuery,
64+
});
65+
66+
atrule.remove();
67+
});
68+
69+
// eslint-disable-next-line no-param-reassign
70+
options.importItems = importItems;
71+
}
72+
);

lib/plugins/postcss-parser.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
const postcss = require('postcss');
2+
const valueParser = require('postcss-value-parser');
3+
const icssUtils = require('icss-utils');
4+
const Tokenizer = require('css-selector-tokenizer');
5+
const loaderUtils = require('loader-utils');
6+
7+
module.exports = postcss.plugin(
8+
'postcss-parser',
9+
(options) =>
10+
function process(css) {
11+
const imports = {};
12+
let exports = {};
13+
const importItems = options.importItems || [];
14+
const urlItems = options.urlItems || [];
15+
16+
function replaceImportsInString(str) {
17+
if (options.import) {
18+
const tokens = valueParser(str);
19+
20+
tokens.walk((node) => {
21+
if (node.type !== 'word') {
22+
return;
23+
}
24+
25+
const token = node.value;
26+
const importIndex = imports[`$${token}`];
27+
28+
if (typeof importIndex === 'number') {
29+
// eslint-disable-next-line no-param-reassign
30+
node.value = `___CSS_LOADER_IMPORT___${importIndex}___`;
31+
}
32+
});
33+
34+
return tokens.toString();
35+
}
36+
return str;
37+
}
38+
39+
const icss = icssUtils.extractICSS(css);
40+
41+
exports = icss.icssExports;
42+
43+
Object.keys(icss.icssImports).forEach((key) => {
44+
const url = loaderUtils.parseString(key);
45+
46+
Object.keys(icss.icssImports[key]).forEach((prop) => {
47+
imports[`$${prop}`] = importItems.length;
48+
importItems.push({
49+
url,
50+
export: icss.icssImports[key][prop],
51+
});
52+
});
53+
});
54+
55+
Object.keys(exports).forEach((exportName) => {
56+
exports[exportName] = replaceImportsInString(exports[exportName]);
57+
});
58+
59+
function processNode(item) {
60+
switch (item.type) {
61+
case 'value':
62+
item.nodes.forEach(processNode);
63+
break;
64+
case 'nested-item':
65+
item.nodes.forEach(processNode);
66+
break;
67+
case 'item': {
68+
const importIndex = imports[`$${item.name}`];
69+
if (typeof importIndex === 'number') {
70+
// eslint-disable-next-line no-param-reassign
71+
item.name = `___CSS_LOADER_IMPORT___${importIndex}___`;
72+
}
73+
break;
74+
}
75+
// no default
76+
}
77+
}
78+
79+
css.walkDecls((decl) => {
80+
const values = Tokenizer.parseValues(decl.value);
81+
82+
values.nodes.forEach((value) => {
83+
value.nodes.forEach(processNode);
84+
});
85+
86+
// eslint-disable-next-line no-param-reassign
87+
decl.value = Tokenizer.stringifyValues(values);
88+
});
89+
90+
css.walkAtRules((atrule) => {
91+
if (typeof atrule.params === 'string') {
92+
// eslint-disable-next-line no-param-reassign
93+
atrule.params = replaceImportsInString(atrule.params);
94+
}
95+
});
96+
97+
/* eslint-disable no-param-reassign */
98+
options.importItems = importItems;
99+
options.urlItems = urlItems;
100+
options.exports = exports;
101+
/* eslint-enable no-param-reassign */
102+
}
103+
);

lib/plugins/postcss-url-parser.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
const postcss = require('postcss');
2+
const Tokenizer = require('css-selector-tokenizer');
3+
const loaderUtils = require('loader-utils');
4+
5+
const pluginName = 'postcss-url-parser';
6+
7+
module.exports = postcss.plugin(
8+
pluginName,
9+
(options) =>
10+
function process(css) {
11+
const urlItems = [];
12+
13+
function processNode(item) {
14+
switch (item.type) {
15+
case 'value':
16+
item.nodes.forEach(processNode);
17+
break;
18+
case 'nested-item':
19+
item.nodes.forEach(processNode);
20+
break;
21+
case 'url':
22+
if (
23+
item.url.replace(/\s/g, '').length &&
24+
!/^#/.test(item.url) &&
25+
loaderUtils.isUrlRequest(item.url)
26+
) {
27+
// Strip quotes, they will be re-added if the module needs them
28+
/* eslint-disable no-param-reassign */
29+
item.stringType = '';
30+
delete item.innerSpacingBefore;
31+
delete item.innerSpacingAfter;
32+
const { url } = item;
33+
item.url = `___CSS_LOADER_URL___${urlItems.length}___`;
34+
/* eslint-enable no-param-reassign */
35+
urlItems.push({
36+
url,
37+
});
38+
}
39+
break;
40+
// no default
41+
}
42+
}
43+
44+
css.walkDecls((decl) => {
45+
const values = Tokenizer.parseValues(decl.value);
46+
values.nodes.forEach((value) => {
47+
value.nodes.forEach(processNode);
48+
});
49+
// eslint-disable-next-line no-param-reassign
50+
decl.value = Tokenizer.stringifyValues(values);
51+
});
52+
53+
// eslint-disable-next-line no-param-reassign
54+
options.urlItems = urlItems;
55+
}
56+
);

0 commit comments

Comments
 (0)