elcrm 1.1.6 → 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @see minifyCssVars
|
|
4
4
|
*/
|
|
5
5
|
export type MinifyCssVarsOptions = {
|
|
6
|
-
/** Длина короткого `--xx` (`2` → `--ab`). По умолчанию
|
|
6
|
+
/** Длина короткого `--xx` (`2` → `--aa`, `--ab`). По умолчанию 2 */
|
|
7
7
|
var?: number;
|
|
8
8
|
/** Длина суффикса класса после prefix (`3` + prefix `x` → `xabc`) */
|
|
9
9
|
name?: number;
|
|
@@ -21,8 +21,32 @@ export type MinifyCssVarsOptions = {
|
|
|
21
21
|
css?: boolean;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
+
export declare function toIndex(value: unknown, fallback?: number): number;
|
|
25
|
+
|
|
24
26
|
export declare function encodeName(index: number, length: number): string;
|
|
25
27
|
|
|
28
|
+
export declare function namespaceSize(length: number): number;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Короткие имена без коллизий: пропускает `reserved`, не выдаёт одно `to` дважды.
|
|
32
|
+
*/
|
|
33
|
+
export declare function allocateUnique(
|
|
34
|
+
originals: string[],
|
|
35
|
+
reserved: Set<string>,
|
|
36
|
+
make: (index: number) => string,
|
|
37
|
+
start: number,
|
|
38
|
+
capacity: number,
|
|
39
|
+
kind: string,
|
|
40
|
+
): Map<string, string>;
|
|
41
|
+
|
|
42
|
+
export declare function replaceCssVarToken(
|
|
43
|
+
text: string,
|
|
44
|
+
from: string,
|
|
45
|
+
to: string,
|
|
46
|
+
): string;
|
|
47
|
+
|
|
48
|
+
export declare function isVendorJsChunk(code: string): boolean;
|
|
49
|
+
|
|
26
50
|
export declare function minifyCssVars(
|
|
27
51
|
options?: MinifyCssVarsOptions,
|
|
28
52
|
): import("vite").Plugin;
|
|
@@ -24,16 +24,22 @@ function collectJsVars(texts) {
|
|
|
24
24
|
let m;
|
|
25
25
|
while (m = VAR_IN_JS_RE.exec(text)) {
|
|
26
26
|
const name = m[1] ?? m[2];
|
|
27
|
-
if (name)
|
|
27
|
+
if (name && !/^--[a-zA-Z_]{1,3}$/.test(name))
|
|
28
28
|
set.add(name);
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
return set;
|
|
32
32
|
}
|
|
33
|
+
function toIndex(value, fallback = 0) {
|
|
34
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
35
|
+
if (!Number.isFinite(n) || n < 0)
|
|
36
|
+
return fallback;
|
|
37
|
+
return Math.floor(n);
|
|
38
|
+
}
|
|
33
39
|
function encodeName(index, length) {
|
|
34
40
|
const n = LETTERS.length;
|
|
35
41
|
const len = Math.max(1, Math.min(8, Math.floor(length)));
|
|
36
|
-
let i =
|
|
42
|
+
let i = toIndex(index, 0);
|
|
37
43
|
let s = "";
|
|
38
44
|
for (let k = 0;k < len; k++) {
|
|
39
45
|
s = LETTERS[i % n] + s;
|
|
@@ -55,30 +61,80 @@ function shouldMinifyClass(name, minLen, prefix) {
|
|
|
55
61
|
return false;
|
|
56
62
|
return /[-_]/.test(name);
|
|
57
63
|
}
|
|
64
|
+
function namespaceSize(length) {
|
|
65
|
+
const len = Math.max(1, Math.min(8, Math.floor(length)));
|
|
66
|
+
let n = 1;
|
|
67
|
+
for (let i = 0;i < len; i++)
|
|
68
|
+
n *= LETTERS.length;
|
|
69
|
+
return n;
|
|
70
|
+
}
|
|
71
|
+
function allocateUnique(originals, reserved, make, start, capacity, kind) {
|
|
72
|
+
const taken = new Set(reserved);
|
|
73
|
+
const out = new Map;
|
|
74
|
+
let cursor = start;
|
|
75
|
+
for (const from of originals) {
|
|
76
|
+
if (out.has(from))
|
|
77
|
+
continue;
|
|
78
|
+
let found;
|
|
79
|
+
for (let n = 0;n < capacity; n++) {
|
|
80
|
+
const candidate = make(cursor);
|
|
81
|
+
cursor++;
|
|
82
|
+
if (taken.has(candidate) || candidate === from)
|
|
83
|
+
continue;
|
|
84
|
+
found = candidate;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
if (!found) {
|
|
88
|
+
throw new Error(`[minify-css-vars] нет свободных имён для ${kind}: нужно ${originals.length}, ёмкость ${capacity}, занято ${taken.size}. Увеличьте var/name или сдвиньте start.`);
|
|
89
|
+
}
|
|
90
|
+
out.set(from, found);
|
|
91
|
+
taken.add(found);
|
|
92
|
+
}
|
|
93
|
+
const used = new Set;
|
|
94
|
+
for (const [from, to] of out) {
|
|
95
|
+
if (used.has(to)) {
|
|
96
|
+
throw new Error(`[minify-css-vars] коллизия ${kind}: «${from}» и ещё одно имя → «${to}»`);
|
|
97
|
+
}
|
|
98
|
+
used.add(to);
|
|
99
|
+
if (reserved.has(to)) {
|
|
100
|
+
throw new Error(`[minify-css-vars] «${from}» → «${to}» затирает существующее имя ${kind}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
function escapeRe(s) {
|
|
106
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
107
|
+
}
|
|
108
|
+
function replaceCssVarToken(text, from, to) {
|
|
109
|
+
return text.replace(new RegExp(`${escapeRe(from)}(?![\\w-])`, "g"), to);
|
|
110
|
+
}
|
|
58
111
|
function replaceVarsInCss(text, map) {
|
|
59
112
|
let out = text;
|
|
60
113
|
for (const [from, to] of map)
|
|
61
|
-
out = out
|
|
114
|
+
out = replaceCssVarToken(out, from, to);
|
|
62
115
|
return out;
|
|
63
116
|
}
|
|
64
117
|
function replaceVarsInJs(text, map) {
|
|
65
118
|
let out = text;
|
|
66
119
|
for (const [from, to] of map) {
|
|
67
|
-
const esc = from
|
|
68
|
-
out = out.replace(new RegExp(`var\\(\\s*${esc}\\
|
|
120
|
+
const esc = escapeRe(from);
|
|
121
|
+
out = out.replace(new RegExp(`var\\(\\s*${esc}(?=[\\s,)])`, "g"), (m) => m.replace(from, to));
|
|
69
122
|
out = out.replace(new RegExp(`(["'])${esc}\\1`, "g"), `$1${to}$1`);
|
|
70
123
|
}
|
|
71
124
|
return out;
|
|
72
125
|
}
|
|
126
|
+
function isVendorJsChunk(code) {
|
|
127
|
+
return /ZipFileWorker|\bJSZip\b|pako deflate|readable-stream/.test(code);
|
|
128
|
+
}
|
|
73
129
|
function replaceClasses(text, map, inCss) {
|
|
74
130
|
let out = text;
|
|
75
131
|
for (const [from, to] of map) {
|
|
132
|
+
const esc = escapeRe(from);
|
|
76
133
|
if (inCss) {
|
|
77
|
-
out = out.
|
|
134
|
+
out = out.replace(new RegExp(`\\.${esc}(?![\\w-])`, "g"), `.${to}`);
|
|
78
135
|
continue;
|
|
79
136
|
}
|
|
80
|
-
|
|
81
|
-
out = out.replace(re, `$1${to}`);
|
|
137
|
+
out = out.replace(new RegExp(`(^|[^\\w-])(${esc})(?=[^\\w-]|$)`, "g"), `$1${to}`);
|
|
82
138
|
}
|
|
83
139
|
return out;
|
|
84
140
|
}
|
|
@@ -99,14 +155,14 @@ function pct(before, after) {
|
|
|
99
155
|
}
|
|
100
156
|
function normalizeOpts(raw) {
|
|
101
157
|
const o = raw ?? {};
|
|
102
|
-
const varLen = o.var ??
|
|
158
|
+
const varLen = o.var ?? 2;
|
|
103
159
|
const nameLen = o.name ?? 2;
|
|
104
|
-
const start = o.start
|
|
160
|
+
const start = toIndex(o.start, 0);
|
|
105
161
|
return {
|
|
106
162
|
varLen,
|
|
107
163
|
nameLen,
|
|
108
|
-
varStart: o.varStart
|
|
109
|
-
nameStart: o.nameStart
|
|
164
|
+
varStart: toIndex(o.varStart, start),
|
|
165
|
+
nameStart: toIndex(o.nameStart, start),
|
|
110
166
|
vars: o.vars !== false,
|
|
111
167
|
classes: o.classes !== false,
|
|
112
168
|
minVar: o.minVar ?? Math.max(4, varLen + 2),
|
|
@@ -143,37 +199,44 @@ function minifyCssVars(options) {
|
|
|
143
199
|
...collect(cssTexts, VAR_RE),
|
|
144
200
|
...collectJsVars(jsTexts)
|
|
145
201
|
]);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
202
|
+
const remap = [];
|
|
203
|
+
const reserved = new Set;
|
|
204
|
+
for (const name of names) {
|
|
205
|
+
if (alreadyShortVar(name, opt.varLen) || /^--[a-zA-Z_]$/.test(name) || name.length < opt.minVar) {
|
|
206
|
+
reserved.add(name);
|
|
151
207
|
continue;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
varMap.set(name, `--${encodeName(opt.varStart + vi, opt.varLen)}`);
|
|
155
|
-
vi++;
|
|
208
|
+
}
|
|
209
|
+
remap.push(name);
|
|
156
210
|
}
|
|
211
|
+
const cap = namespaceSize(opt.varLen);
|
|
212
|
+
const allocated = allocateUnique([...remap].sort(), reserved, (i) => `--${encodeName(i, opt.varLen)}`, opt.varStart, cap, "vars");
|
|
213
|
+
for (const [k, v] of allocated)
|
|
214
|
+
varMap.set(k, v);
|
|
157
215
|
}
|
|
158
216
|
const classMap = new Map;
|
|
159
217
|
if (opt.classes) {
|
|
160
|
-
const
|
|
218
|
+
const allClasses = new Set;
|
|
161
219
|
for (const file of files) {
|
|
162
220
|
if (!file.isCss)
|
|
163
221
|
continue;
|
|
164
222
|
CLASS_SEL_RE.lastIndex = 0;
|
|
165
223
|
let m;
|
|
166
|
-
while (m = CLASS_SEL_RE.exec(file.raw))
|
|
167
|
-
|
|
168
|
-
classNames.add(m[1]);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
224
|
+
while (m = CLASS_SEL_RE.exec(file.raw))
|
|
225
|
+
allClasses.add(m[1]);
|
|
171
226
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
227
|
+
const remap = [];
|
|
228
|
+
const reserved = new Set;
|
|
229
|
+
for (const name of allClasses) {
|
|
230
|
+
if (shouldMinifyClass(name, opt.minClass, opt.classPrefix)) {
|
|
231
|
+
remap.push(name);
|
|
232
|
+
} else {
|
|
233
|
+
reserved.add(name);
|
|
234
|
+
}
|
|
176
235
|
}
|
|
236
|
+
const cap = namespaceSize(opt.nameLen);
|
|
237
|
+
const allocated = allocateUnique([...remap].sort(), reserved, (i) => `${opt.classPrefix}${encodeName(i, opt.nameLen)}`, opt.nameStart, cap, "classes");
|
|
238
|
+
for (const [k, v] of allocated)
|
|
239
|
+
classMap.set(k, v);
|
|
177
240
|
}
|
|
178
241
|
if (!varMap.size && !classMap.size) {
|
|
179
242
|
if (opt.report) {
|
|
@@ -181,17 +244,18 @@ function minifyCssVars(options) {
|
|
|
181
244
|
}
|
|
182
245
|
return;
|
|
183
246
|
}
|
|
184
|
-
const varEntries = [
|
|
247
|
+
const varEntries = byLongest(varMap.keys()).map((k) => [k, varMap.get(k)]);
|
|
185
248
|
let beforeAll = 0;
|
|
186
249
|
let afterAll = 0;
|
|
187
250
|
const rows = [];
|
|
188
251
|
for (const file of files) {
|
|
189
252
|
const before = bytes(file.raw);
|
|
190
253
|
let next = file.raw;
|
|
191
|
-
|
|
254
|
+
const skipJsVendor = file.isJs && isVendorJsChunk(next);
|
|
255
|
+
if (varMap.size && !skipJsVendor) {
|
|
192
256
|
next = file.isCss ? replaceVarsInCss(next, varEntries) : replaceVarsInJs(next, varEntries);
|
|
193
257
|
}
|
|
194
|
-
if (classMap.size) {
|
|
258
|
+
if (classMap.size && !skipJsVendor) {
|
|
195
259
|
next = replaceClasses(next, classMap, file.isCss);
|
|
196
260
|
}
|
|
197
261
|
const after = bytes(next);
|
|
@@ -218,6 +282,11 @@ function minifyCssVars(options) {
|
|
|
218
282
|
};
|
|
219
283
|
}
|
|
220
284
|
export {
|
|
285
|
+
toIndex,
|
|
286
|
+
replaceCssVarToken,
|
|
287
|
+
namespaceSize,
|
|
221
288
|
minifyCssVars,
|
|
222
|
-
|
|
289
|
+
isVendorJsChunk,
|
|
290
|
+
encodeName,
|
|
291
|
+
allocateUnique
|
|
223
292
|
};
|
package/package.json
CHANGED
|
@@ -90,6 +90,23 @@ elcrm test # статический анализ + scripts п
|
|
|
90
90
|
elcrm test --front|--server|--no-scripts
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
+
### Vite-плагины из пакета `elcrm`
|
|
94
|
+
|
|
95
|
+
Нужен **elcrm ≥ 1.1.6** в `devDependencies` приложения (не только глобальный `bun i -g`). Иначе Vite: `Missing "./vite/minify-css-vars" specifier`.
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
import { minifyCssVars } from "elcrm/vite/minify-css-vars";
|
|
99
|
+
|
|
100
|
+
// только command === "build"
|
|
101
|
+
minifyCssVars({ var: 2, name: 3, classPrefix: "x" })
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`nameStart` / `varStart` — **числа** (индекс алфавита), не `"x"`. Префикс класса — `classPrefix`. В JS плагин не трогает декремент `--n` (иначе `aqt is not defined`).
|
|
105
|
+
|
|
106
|
+
Короткие имена **уникальны и подряд**: `--aa`, `--ab`, `--ac`… Занятые слоты пропускаются; `--field` не затирает `--field-border`. Чанки JSZip не минифицируются (декремент `--n`). Если слотов нет — сборка падает.
|
|
107
|
+
|
|
108
|
+
Также: `elcrm/vite/plugin-css-scoped`, `discover-lib`, `concat-css`, `check-tokens`, `postbuild`.
|
|
109
|
+
|
|
93
110
|
### Прочее
|
|
94
111
|
|
|
95
112
|
```bash
|