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`). По умолчанию 3 */
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 = Math.max(0, Math.floor(index));
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.split(from).join(to);
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.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
68
- out = out.replace(new RegExp(`var\\(\\s*${esc}\\b`, "g"), (m) => m.replace(from, to));
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.split(`.${from}`).join(`.${to}`);
134
+ out = out.replace(new RegExp(`\\.${esc}(?![\\w-])`, "g"), `.${to}`);
78
135
  continue;
79
136
  }
80
- const re = new RegExp(`(^|[^\\w-])(${from.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})(?=[^\\w-]|$)`, "g");
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 ?? 3;
158
+ const varLen = o.var ?? 2;
103
159
  const nameLen = o.name ?? 2;
104
- const start = o.start ?? 0;
160
+ const start = toIndex(o.start, 0);
105
161
  return {
106
162
  varLen,
107
163
  nameLen,
108
- varStart: o.varStart ?? start,
109
- nameStart: o.nameStart ?? start,
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
- let vi = 0;
147
- for (const name of byLongest(names)) {
148
- if (alreadyShortVar(name, opt.varLen))
149
- continue;
150
- if (/^--[a-zA-Z_]$/.test(name))
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
- if (name.length < opt.minVar)
153
- continue;
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 classNames = new Set;
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
- if (shouldMinifyClass(m[1], opt.minClass, opt.classPrefix)) {
168
- classNames.add(m[1]);
169
- }
170
- }
224
+ while (m = CLASS_SEL_RE.exec(file.raw))
225
+ allClasses.add(m[1]);
171
226
  }
172
- let ci = 0;
173
- for (const name of byLongest(classNames)) {
174
- classMap.set(name, `${opt.classPrefix}${encodeName(opt.nameStart + ci, opt.nameLen)}`);
175
- ci++;
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 = [...varMap.entries()];
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
- if (varMap.size) {
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
- encodeName
289
+ isVendorJsChunk,
290
+ encodeName,
291
+ allocateUnique
223
292
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "elcrm",
3
- "version": "1.1.6",
3
+ "version": "1.1.8",
4
4
  "description": "CLI @elcrm/*: update --fix --test, css (prune imports), docs, cursor, migrate, audit",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -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