-
Notifications
You must be signed in to change notification settings - Fork 0
/
loader.js
168 lines (139 loc) · 5.54 KB
/
loader.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
let babel = require("babel-core");
let loaderUtils = require('loader-utils');
let fs = require('fs');
let beautify = require('js-beautify').js_beautify;
let minify = require("babel-minify");
// Scrolls up and down on the same level as the codeblock with 'klass'
// Tests for 'className' and if found will delete the className attribute
// and return its value
let findClassNames = (lineno, spaces, lines) => {
let spaceRE = new RegExp('^' + spaces);
let classNameRe = new RegExp('^' + spaces + "className: (.*)");
// uncomment to visualise
// console.log('-----')
// for (let i = lineno - 7; i < lineno + 7; i++) {
// console.log('SNIP findClassNames:' + (i == lineno ? '-->' : ' '), lines[i]);
// }
// this loop is intentional
// go forward in lines until we come accross shallower indentation
do {} while (spaceRE.test(lines[++lineno]));
// go backwards in lines so we go through all properties in the object
while (spaceRE.test(lines[--lineno])) {
// if we find an additional classname attribute in the object
if (classNameRe.test(lines[lineno])) {
// return the expression found in it and return it without the comma
return lines[lineno].replace(classNameRe, (line, expr) => {
// if expression is multiline, get it as below
if (!expr.endsWith(',')) {
expr += getRestOfExpression(lineno, spaces, lines);
}
lines[lineno] = ''; // delete the line containing the remaining classes
return expr.replace(/,$/, '');
});
}
}
return '""';
};
// if the expression is more complicated, such as a function returning a string,
// then the beautifier will put it on more than one line. We can check for this
// by testing if the source line doesn't end in a comma and get the rest of the expression
// by adding all proceeding source lines which are on a deeper or equal indentation level
let getRestOfExpression = (lineno, spaces, lines) => {
let spaceRE = new RegExp('^' + spaces);
let spaceREExact = new RegExp('^' + spaces + '[^ ]');
let rest = '';
// uncomment to visualise
// console.log('-----')
// for (let i = lineno - 7; i < lineno + 7; i++) {
// console.log('SNIP getRestOfExpression:' + (i == lineno ? '-->' : ' '), lines[i]);
// }
// if the next line has less spaces in it, then we are just at the end of the object
if (spaceRE.test(lines[++lineno])) {
do {
// append the whole line, since is is just the next part of the unterminated expression
rest = rest + lines[lineno];
// and delete line
lines[lineno] = '';
} while (typeof lines[lineno++] !== 'undefined' && !spaceREExact.test(lines[lineno]));
// loop until the line ends with a comma,
// then add it:
rest += lines[lineno].replace(/,$/, '');
lines[lineno] = '';
}
return rest;
}
let buildReplacementSourceLine = (expr, ctx, lineno, spaces, lines, isDev, customAttr, noWarningFor) => {
expr = '(' + expr.replace(/,$/, "") + ')';
let warning = (item) => {
if (!isDev) return '';
return `
if (c.length && typeof __k_styles[c] === "undefined" && c !== '${noWarningFor}') {
console.error(
'Warning: [klass-loader] no matching \`${ customAttr }\` attribute` +
` for \\'' + ${ item } + '\\' in ${ ctx.resourcePath.replace(/.*src/, 'src') }'
)
}
`
};
let splitExpr = `
${expr}.split(/ +/).map(function(c) {
${ warning('c') };
return __k_styles[c];
}).join(' ')
`;
return `${spaces}className: (${ splitExpr } + ' ' + ${ findClassNames(lineno, spaces, lines) }),`
}
let replaceKlassAttributes = (s, ctx, isDev, customAttr, noWarningFor) => {
// if the file is not minified, we can assume we're on development mode
if (isDev === 'auto') {
isDev = s.split(/\r?\n/).length > 3;
} else {
isDev = isDev === 'true';
}
let lines = beautify(s).split(/\r?\n/);
let attrRE = new RegExp("( *)" + customAttr + ": (.*)$");
for (let i = 0; i < lines.length; i++) {
if (attrRE.test(lines[i])) {
lines[i] = lines[i].replace(attrRE, (line, spaces, expr) => {
if (!expr.endsWith(',')) {
expr += getRestOfExpression(i, spaces, lines);
}
return buildReplacementSourceLine(expr, ctx, i, spaces, lines, isDev, customAttr, noWarningFor)
});
}
}
// if the code does not appear to be minified, return unminified code
if (isDev) {
return lines.join('\n');
}
// otherwise return it minified
return minify(lines.join('\n'), {
mangle: {
keepClassName: true
}
}).code;
}
module.exports = function(source, map) {
//return source;
const options = loaderUtils.getOptions(this);
let extension = (options && options.extension) ? options.extension : 'css';
let isDev = (options && options.isDev) ? options.isDev : 'auto';
let customAttr = (options && options.customAttr) ? options.customAttr : 'klass';
let noWarningFor = (options && options.noWarningFor) ? options.noWarningFor : '';
if (source.indexOf(`${customAttr}:`) !== -1) {
if (fs.existsSync(this.context + "/styles." + extension)) {
//console.log(source);
source = replaceKlassAttributes(source, this, isDev, customAttr, noWarningFor);
source = "var __k_styles = require('./styles." + extension + "');\n" + source;
//console.log(source);
} else {
throw new Error(`
klass-loader error:
-> ${ this.context }/styles.${ extension } not found when \`${ customAttr }\` keyword used in associated file`);
}
}
if (this.callback)
return this.callback(null, source, map)
else
return source;
};