-
-
Notifications
You must be signed in to change notification settings - Fork 596
/
one-letter-css.js
209 lines (167 loc) · 4.68 KB
/
one-letter-css.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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
const { interpolateName } = require('loader-utils');
/**
* Change css-classes to 64-bit prefix (by class position at file) + hash postfix (by file path)
*/
// Parse encoding string, or get default values
const getRule = (externalRule) => {
let iRule = {
type: 'hash',
rule: 'base64',
hashLen: 8,
val: '',
};
iRule.val = `[${iRule.type}:${iRule.rule}:${iRule.hashLen}]`;
const matchHashRule =
externalRule
.replace(/_/g, '')
.match(/^(?:\[local])*\[([a-z\d]+):([a-z\d]+):(\d+)]$/) || [];
if (matchHashRule.length >= 4) {
const [, type, rule, hashLen] = matchHashRule;
iRule = {
type,
rule,
hashLen,
val: `[${type}:${rule}:${hashLen}]`,
};
}
return iRule;
};
export default class OneLetterCssClasses {
constructor() {
// Save separators points from ascii-table
this.a = 'a'.charCodeAt(0);
this.A = 'A'.charCodeAt(0);
this.zero = '0'.charCodeAt(0);
this.files = {};
// [a-zA-Z\d_-]
this.encoderSize = 64;
this.symbolsArea = {
// a-z
az: 26,
// A-Z
AZ: 52,
// _
under: 53,
// 0-9 | \d
digit: 63,
// -
// dash: 64
};
// prevent loop hell
this.maxLoop = 5;
this.rootPathLen = process.cwd().length;
}
getSingleSymbol(n) {
const {
a,
A,
zero,
encoderSize,
symbolsArea: { az, AZ, under, digit },
} = this;
if (!n) {
// console.error(`!n, n=${n}`);
return '';
}
if (n > encoderSize) {
// console.error(`n > ${encoderSize}, n=${n}`);
return '';
}
// work with 1 <= n <= 64
if (n <= az) {
return String.fromCharCode(n - 1 + a);
}
if (n <= AZ) {
return String.fromCharCode(n - 1 - az + A);
}
if (n <= under) {
return '_';
}
if (n <= digit) {
return String.fromCharCode(n - 1 - under + zero);
}
return '-';
}
/** Encode classname by position at file, 0 - a, 1 - b, etc */
getNamePrefix(num) {
const { maxLoop, encoderSize } = this;
if (!num) {
return '';
}
let loopCount = 0;
let n = num;
let res = '';
// Divide at loop for 64
// For example, from 1 to 64 - 1 step, from 65 to 4096 (64*64) - 2 steps, etc
while (n && loopCount < maxLoop) {
// Remainder of division, for 1-64 encode
let tail = n % encoderSize;
const origTail = tail;
// Check limits, n === encoderSize. 64 % 64 = 0, but encoding for 64
if (tail === 0) {
tail = encoderSize;
}
// Concat encoding result
res = this.getSingleSymbol(tail) + res;
// Check for new loop
if (Math.floor((n - 1) / encoderSize)) {
// Find the number of bits for next encoding cycle
n = (n - origTail) / encoderSize;
// At limit value (64), go to a new circle,
// -1 to avoid (we have already encoded this)
//
if (origTail === 0) {
n -= 1;
}
} else {
n = 0;
}
loopCount += 1;
}
return res;
}
/**
* Make hash
*/
getLocalIdentWithFileHash(context, localIdentName, localName) {
const { resourcePath } = context;
const { files, rootPathLen } = this;
// To fix difference between stands, work with file path from project root
const resPath = resourcePath.substr(rootPathLen);
// Filename at list, take his name
let fileShort = files[resPath];
// Filename not at list, generate new name, save
if (!fileShort) {
// parse encoding rule
const localIdentRule = getRule(localIdentName);
const fileShortName = interpolateName(context, localIdentRule.val, {
content: resPath,
});
fileShort = { name: fileShortName, lastUsed: 0, ruleNames: {} };
files[resPath] = fileShort;
}
// Take rulename, if exists at current file
let newRuleName = fileShort.ruleNames[localName];
// If rulename not exist - generate new, and save
if (!newRuleName) {
// Increase rules count
fileShort.lastUsed += 1;
// Generate new rulename
newRuleName = this.getNamePrefix(fileShort.lastUsed) + fileShort.name;
// Save rulename
fileShort.ruleNames[localName] = newRuleName;
}
// Check encoding settings for local development (save original rulenames)
const hasLocal = /\[local]/.test(localIdentName);
// If develop mode - add prefix
const res = hasLocal ? `${localName}__${newRuleName}` : newRuleName;
// Add prefix '_' for classes with '-' or digit '\d'
// or '_' (for fix collision)
return /^[\d_-]/.test(res) ? `_${res}` : res;
}
getStat() {
const stat = { ...this.files };
this.files = {};
return stat;
}
}