Skip to content

Commit f9c60da

Browse files
refactor(loader): v1.0.0
1 parent dad63b9 commit f9c60da

File tree

8 files changed

+318
-4
lines changed

8 files changed

+318
-4
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ To disable `url()` resolving by `css-loader` set the option to `false`
103103

104104
#### `{Function}`
105105

106+
106107
**webpack.config.js**
107108
```js
108109
{

src/lib/Error.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
class SyntaxError extends Error {
2+
constructor(err) {
3+
super(err);
4+
5+
this.name = 'Syntax Error';
6+
this.message = '';
7+
8+
if (err.line) {
9+
this.message += `\n\n[${err.line}:${err.column}] ${err.reason}`;
10+
}
11+
12+
if (err.input.source) {
13+
this.message += `\n\n${err.showSourceCode()}\n`;
14+
}
15+
16+
Error.captureStackTrace(this, this.constructor);
17+
}
18+
}
19+
20+
export default SyntaxError;

src/lib/plugins/import.js

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/* eslint-disable */
2+
import postcss from 'postcss';
3+
import valueParser from 'postcss-value-parser';
4+
// ICSS {String}
5+
// import { createICSSRules } from "icss-utils";
6+
7+
const plugin = 'postcss-icss-import';
8+
9+
const getArg = nodes =>
10+
(nodes.length !== 0 && nodes[0].type === 'string'
11+
? nodes[0].value
12+
: valueParser.stringify(nodes));
13+
14+
const getUrl = (node) => {
15+
if (node.type === 'function' && node.value === 'url') {
16+
return getArg(node.nodes);
17+
}
18+
if (node.type === 'string') {
19+
return node.value;
20+
}
21+
return '';
22+
};
23+
24+
const parseImport = (params) => {
25+
const { nodes } = valueParser(params);
26+
27+
if (nodes.length === 0) {
28+
return null;
29+
}
30+
31+
const url = getUrl(nodes[0]);
32+
33+
if (url.trim().length === 0) {
34+
return null;
35+
}
36+
37+
return {
38+
url,
39+
media: valueParser.stringify(nodes.slice(1)).trim(),
40+
};
41+
};
42+
43+
const isExternalUrl = url => /^\w+:\/\//.test(url) || url.startsWith('//');
44+
45+
const walkImports = (css, callback) => {
46+
css.each((node) => {
47+
if (node.type === 'atrule' && node.name.toLowerCase() === 'import') {
48+
callback(node);
49+
}
50+
});
51+
};
52+
53+
export default postcss.plugin(plugin, () => (css, result) => {
54+
let idx = 0;
55+
const imports = {};
56+
57+
walkImports(css, (atrule) => {
58+
if (atrule.nodes) {
59+
return result.warn(
60+
'It looks like you didn\'t end your @import statement correctly.\nChild nodes are attached to it.',
61+
{ node: atrule },
62+
);
63+
}
64+
65+
const parsed = parseImport(atrule.params);
66+
67+
if (parsed === null) {
68+
return result.warn(`Unable to find uri in '${atrule.toString()}'`, {
69+
node: atrule,
70+
});
71+
}
72+
73+
if (!isExternalUrl(parsed.url)) {
74+
atrule.remove();
75+
76+
imports[`CSS__IMPORT__${idx}`] = `${parsed.url}`;
77+
78+
idx++;
79+
80+
// ICSS {String}
81+
// imports[`'${parsed.url}'`] = {
82+
// import: "default" +
83+
// (parsed.media.length === 0 ? "" : ` ${parsed.media}`)
84+
// };
85+
}
86+
});
87+
88+
result.messages.push({ imports });
89+
90+
// result.messages.push({
91+
// type: 'dependency',
92+
// plugin: 'postcss-icss-import',
93+
// imports
94+
// })
95+
96+
// ICSS {String}
97+
// css.prepend(createICSSRules(imports, {}));
98+
});

src/lib/plugins/url.js

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/* eslint-disable */
2+
import postcss from 'postcss';
3+
import valueParser from 'postcss-value-parser';
4+
// ICSS {String}
5+
// import { createICSSRules } from "icss-utils";
6+
7+
const walkUrls = (parsed, callback) => {
8+
parsed.walk((node) => {
9+
if (node.type === 'function' && node.value === 'url') {
10+
const content = node.nodes.length !== 0 && node.nodes[0].type === 'string'
11+
? node.nodes[0].value
12+
: valueParser.stringify(node.nodes);
13+
14+
if (content.trim().length !== 0) {
15+
callback(node, content);
16+
}
17+
18+
// do not traverse inside url
19+
return false;
20+
}
21+
});
22+
};
23+
24+
const mapUrls = (parsed, map) => {
25+
walkUrls(parsed, (node, content) => {
26+
node.nodes = [{ type: 'word', value: map(content) }];
27+
});
28+
};
29+
30+
const filterUrls = (parsed, filter) => {
31+
const result = [];
32+
33+
walkUrls(parsed, (node, content) => {
34+
if (filter(content)) {
35+
result.push(content);
36+
}
37+
});
38+
39+
return result;
40+
};
41+
42+
const walkDeclsWithUrl = (css, filter) => {
43+
const result = [];
44+
45+
css.walkDecls((decl) => {
46+
if (decl.value.includes('url(')) {
47+
const parsed = valueParser(decl.value);
48+
const values = filterUrls(parsed, filter);
49+
50+
if (values.length) {
51+
result.push({
52+
decl,
53+
parsed,
54+
values,
55+
});
56+
}
57+
}
58+
});
59+
60+
return result;
61+
};
62+
63+
const filterValues = value => !/^\w+:\/\//.test(value) &&
64+
!value.startsWith('//') &&
65+
!value.startsWith('#') &&
66+
!value.startsWith('data:');
67+
68+
const flatten = arr => arr.reduce((acc, d) => [...acc, ...d], []);
69+
const uniq = arr => arr.reduce(
70+
(acc, d) => (acc.indexOf(d) === -1 ? [...acc, d] : acc),
71+
[],
72+
);
73+
74+
module.exports = postcss.plugin('postcss-icss-url', () => (css, result) => {
75+
const traversed = walkDeclsWithUrl(css, filterValues);
76+
const paths = uniq(flatten(traversed.map(item => item.values)));
77+
78+
// ICSS imports {String}
79+
const urls = {};
80+
const aliases = {};
81+
82+
paths.forEach((path, index) => {
83+
// ICSS Placeholder
84+
const alias = '${' + `CSS__URL__${index}` + '}';
85+
86+
// console.log(alias)
87+
88+
// ICSS {String}
89+
// imports[`'${path}'`] = {
90+
// [alias]: "default"
91+
// };
92+
93+
urls[`CSS__URL__${index}`] = `${path}`;
94+
95+
aliases[path] = alias;
96+
});
97+
98+
traversed.forEach((item) => {
99+
mapUrls(item.parsed, value => aliases[value]);
100+
101+
item.decl.value = item.parsed.toString();
102+
});
103+
104+
result.messages.push({ urls });
105+
106+
// result.messages.push({
107+
// type: 'dependency',
108+
// plugin: 'postcss-icss-url',
109+
// urls
110+
// })
111+
112+
// ICSS {String}
113+
// css.prepend(createICSSRules(imports, {}));
114+
});

src/lib/runtime.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/* eslint-disable */
2+
3+
// CSS (Loader) Runtime
4+
// TODO Update to ESM (if needed)
5+
// @see css-loader/new-loader
6+
module.exports = function (sourceMap) {
7+
var list = [];
8+
9+
// return the list of modules as css string
10+
list.toString = function toString() {
11+
return this.map(function (item) {
12+
var css = cssWithMappingToString(item, sourceMap);
13+
14+
if (item[2]) return "@media " + item[2] + "{" + css + "}";
15+
16+
return css;
17+
}).join("");
18+
};
19+
20+
// import a list of modules into the list
21+
list.i = function (modules, mediaQuery) {
22+
if(typeof modules === "string") modules = [[null, modules, ""]];
23+
24+
var isImported = {};
25+
26+
for (var i = 0; i < this.length; i++) {
27+
var id = this[i][0];
28+
29+
if (typeof id === "number") isImported[id] = true;
30+
}
31+
32+
for (i = 0; i < modules.length; i++) {
33+
var item = modules[i];
34+
// skip already imported module
35+
// this implementation is not 100% perfect for weird media query combos
36+
// when a module is imported multiple times with different media queries.
37+
// I hope this will never occur (Hey this way we have smaller bundles)
38+
if (typeof item[0] !== "number" || !isImported[item[0]]) {
39+
if (mediaQuery && !item[2]) item[2] = mediaQuery;
40+
else if (mediaQuery) {
41+
item[2] = "(" + item[2] + ") and (" + mediaQuery + ")";
42+
}
43+
44+
list.push(item);
45+
}
46+
}
47+
};
48+
49+
return list;
50+
};
51+
52+
function cssWithMappingToString (item, sourceMap) {
53+
var css = item[1] || '';
54+
var map = item[3];
55+
56+
if (!map) {
57+
return css;
58+
}
59+
60+
if (sourceMap && typeof btoa === 'function') {
61+
var sourceMapping = toComment(map);
62+
63+
var sourceURLs = map.sources.map(function (source) {
64+
return '/*# sourceURL=' + map.sourceRoot + source + ' */'
65+
});
66+
67+
return [css].concat(sourceURLs).concat([sourceMapping]).join('\n');
68+
}
69+
70+
return [css].join('\n');
71+
}
72+
73+
// Adapted from convert-source-map (MIT)
74+
function toComment (sourceMap) {
75+
// eslint-disable-next-line no-undef
76+
var base64 = btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap))));
77+
var data = 'sourceMappingURL=data:application/json;charset=utf-8;base64,' + base64;
78+
79+
return '/*# ' + data + ' */';
80+
}

test/options/minimize.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ describe('Options', () => {
2222
});
2323
});
2424
});
25+

test/options/sourceMap.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable
2-
prefer-destructuring,
32
no-param-reassign,
43
no-underscore-dangle,
4+
prefer-destructuring,
55
*/
66
import path from 'path';
77
import webpack from '../helpers/compiler';

test/runtime.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ describe('Runtime', () => {
6060

6161
expect(m.toString()).toEqual(
6262
'body { b: 2; }' +
63-
'body { c: 3; }' +
64-
'@media print{body { d: 4; }}' +
65-
'@media screen{body { a: 1; }}'
63+
'body { c: 3; }' +
64+
'@media print{body { d: 4; }}' +
65+
'@media screen{body { a: 1; }}'
6666
);
6767
});
6868

0 commit comments

Comments
 (0)