elcrm 1.1.5 → 1.1.6
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.
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite plugin: короткие CSS-переменные и классы после сборки.
|
|
3
|
+
* @see minifyCssVars
|
|
4
|
+
*/
|
|
5
|
+
export type MinifyCssVarsOptions = {
|
|
6
|
+
/** Длина короткого `--xx` (`2` → `--ab`). По умолчанию 3 */
|
|
7
|
+
var?: number;
|
|
8
|
+
/** Длина суффикса класса после prefix (`3` + prefix `x` → `xabc`) */
|
|
9
|
+
name?: number;
|
|
10
|
+
/** Общий стартовый индекс кодировки */
|
|
11
|
+
start?: number;
|
|
12
|
+
varStart?: number;
|
|
13
|
+
nameStart?: number;
|
|
14
|
+
vars?: boolean;
|
|
15
|
+
classes?: boolean;
|
|
16
|
+
minVar?: number;
|
|
17
|
+
minClass?: number;
|
|
18
|
+
classPrefix?: string;
|
|
19
|
+
report?: boolean;
|
|
20
|
+
js?: boolean;
|
|
21
|
+
css?: boolean;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export declare function encodeName(index: number, length: number): string;
|
|
25
|
+
|
|
26
|
+
export declare function minifyCssVars(
|
|
27
|
+
options?: MinifyCssVarsOptions,
|
|
28
|
+
): import("vite").Plugin;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
// src/vite/minify-css-vars.ts
|
|
2
|
+
var VAR_RE = /--[a-zA-Z_][\w-]*/g;
|
|
3
|
+
var VAR_IN_JS_RE = /var\(\s*(--[a-zA-Z_][\w-]*)|["'](--[a-zA-Z_][\w-]*)["']/g;
|
|
4
|
+
var CLASS_SEL_RE = /\.(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)/g;
|
|
5
|
+
var LETTERS = "abcdefghijklmnopqrstuvwxyz";
|
|
6
|
+
function collect(texts, re) {
|
|
7
|
+
const set = new Set;
|
|
8
|
+
for (const text of texts) {
|
|
9
|
+
if (!text)
|
|
10
|
+
continue;
|
|
11
|
+
re.lastIndex = 0;
|
|
12
|
+
let m;
|
|
13
|
+
while (m = re.exec(text))
|
|
14
|
+
set.add(m[1] ?? m[0]);
|
|
15
|
+
}
|
|
16
|
+
return set;
|
|
17
|
+
}
|
|
18
|
+
function collectJsVars(texts) {
|
|
19
|
+
const set = new Set;
|
|
20
|
+
for (const text of texts) {
|
|
21
|
+
if (!text)
|
|
22
|
+
continue;
|
|
23
|
+
VAR_IN_JS_RE.lastIndex = 0;
|
|
24
|
+
let m;
|
|
25
|
+
while (m = VAR_IN_JS_RE.exec(text)) {
|
|
26
|
+
const name = m[1] ?? m[2];
|
|
27
|
+
if (name)
|
|
28
|
+
set.add(name);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return set;
|
|
32
|
+
}
|
|
33
|
+
function encodeName(index, length) {
|
|
34
|
+
const n = LETTERS.length;
|
|
35
|
+
const len = Math.max(1, Math.min(8, Math.floor(length)));
|
|
36
|
+
let i = Math.max(0, Math.floor(index));
|
|
37
|
+
let s = "";
|
|
38
|
+
for (let k = 0;k < len; k++) {
|
|
39
|
+
s = LETTERS[i % n] + s;
|
|
40
|
+
i = Math.floor(i / n);
|
|
41
|
+
}
|
|
42
|
+
return s;
|
|
43
|
+
}
|
|
44
|
+
function byLongest(names) {
|
|
45
|
+
return [...names].sort((a, b) => b.length - a.length);
|
|
46
|
+
}
|
|
47
|
+
function alreadyShortVar(name, len) {
|
|
48
|
+
return new RegExp(`^--[a-z]{${len}}$`).test(name);
|
|
49
|
+
}
|
|
50
|
+
function shouldMinifyClass(name, minLen, prefix) {
|
|
51
|
+
if (name.length < minLen)
|
|
52
|
+
return false;
|
|
53
|
+
const esc = prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
54
|
+
if (new RegExp(`^${esc}[a-z0-9]+$`).test(name))
|
|
55
|
+
return false;
|
|
56
|
+
return /[-_]/.test(name);
|
|
57
|
+
}
|
|
58
|
+
function replaceVarsInCss(text, map) {
|
|
59
|
+
let out = text;
|
|
60
|
+
for (const [from, to] of map)
|
|
61
|
+
out = out.split(from).join(to);
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
function replaceVarsInJs(text, map) {
|
|
65
|
+
let out = text;
|
|
66
|
+
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));
|
|
69
|
+
out = out.replace(new RegExp(`(["'])${esc}\\1`, "g"), `$1${to}$1`);
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
function replaceClasses(text, map, inCss) {
|
|
74
|
+
let out = text;
|
|
75
|
+
for (const [from, to] of map) {
|
|
76
|
+
if (inCss) {
|
|
77
|
+
out = out.split(`.${from}`).join(`.${to}`);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const re = new RegExp(`(^|[^\\w-])(${from.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})(?=[^\\w-]|$)`, "g");
|
|
81
|
+
out = out.replace(re, `$1${to}`);
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
function bytes(s) {
|
|
86
|
+
return Buffer.byteLength(s, "utf8");
|
|
87
|
+
}
|
|
88
|
+
function fmtKb(n) {
|
|
89
|
+
if (n < 1024)
|
|
90
|
+
return `${n} B`;
|
|
91
|
+
return `${(n / 1024).toFixed(2)} kB`;
|
|
92
|
+
}
|
|
93
|
+
function pct(before, after) {
|
|
94
|
+
if (!before)
|
|
95
|
+
return "0%";
|
|
96
|
+
const p = (before - after) / before * 100;
|
|
97
|
+
const sign = p > 0 ? "−" : p < 0 ? "+" : "";
|
|
98
|
+
return `${sign}${Math.abs(p).toFixed(1)}%`;
|
|
99
|
+
}
|
|
100
|
+
function normalizeOpts(raw) {
|
|
101
|
+
const o = raw ?? {};
|
|
102
|
+
const varLen = o.var ?? 3;
|
|
103
|
+
const nameLen = o.name ?? 2;
|
|
104
|
+
const start = o.start ?? 0;
|
|
105
|
+
return {
|
|
106
|
+
varLen,
|
|
107
|
+
nameLen,
|
|
108
|
+
varStart: o.varStart ?? start,
|
|
109
|
+
nameStart: o.nameStart ?? start,
|
|
110
|
+
vars: o.vars !== false,
|
|
111
|
+
classes: o.classes !== false,
|
|
112
|
+
minVar: o.minVar ?? Math.max(4, varLen + 2),
|
|
113
|
+
minClass: o.minClass ?? 5,
|
|
114
|
+
classPrefix: o.classPrefix ?? "x",
|
|
115
|
+
report: o.report !== false,
|
|
116
|
+
js: o.js !== false,
|
|
117
|
+
css: o.css !== false
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function minifyCssVars(options) {
|
|
121
|
+
const opt = normalizeOpts(options);
|
|
122
|
+
return {
|
|
123
|
+
name: "elcrm-minify-css-vars",
|
|
124
|
+
apply: "build",
|
|
125
|
+
enforce: "post",
|
|
126
|
+
generateBundle(_opts, bundle) {
|
|
127
|
+
const files = [];
|
|
128
|
+
for (const [fileName, item] of Object.entries(bundle)) {
|
|
129
|
+
if (item.type !== "asset" && item.type !== "chunk")
|
|
130
|
+
continue;
|
|
131
|
+
const isCss = opt.css && fileName.endsWith(".css");
|
|
132
|
+
const isJs = opt.js && fileName.endsWith(".js");
|
|
133
|
+
if (!isCss && !isJs)
|
|
134
|
+
continue;
|
|
135
|
+
const raw = item.type === "chunk" ? item.code : typeof item.source === "string" ? item.source : item.source ? Buffer.from(item.source).toString("utf8") : "";
|
|
136
|
+
files.push({ item, fileName, raw, isCss, isJs });
|
|
137
|
+
}
|
|
138
|
+
const cssTexts = files.filter((f) => f.isCss).map((f) => f.raw);
|
|
139
|
+
const jsTexts = files.filter((f) => f.isJs).map((f) => f.raw);
|
|
140
|
+
const varMap = new Map;
|
|
141
|
+
if (opt.vars) {
|
|
142
|
+
const names = new Set([
|
|
143
|
+
...collect(cssTexts, VAR_RE),
|
|
144
|
+
...collectJsVars(jsTexts)
|
|
145
|
+
]);
|
|
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))
|
|
151
|
+
continue;
|
|
152
|
+
if (name.length < opt.minVar)
|
|
153
|
+
continue;
|
|
154
|
+
varMap.set(name, `--${encodeName(opt.varStart + vi, opt.varLen)}`);
|
|
155
|
+
vi++;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const classMap = new Map;
|
|
159
|
+
if (opt.classes) {
|
|
160
|
+
const classNames = new Set;
|
|
161
|
+
for (const file of files) {
|
|
162
|
+
if (!file.isCss)
|
|
163
|
+
continue;
|
|
164
|
+
CLASS_SEL_RE.lastIndex = 0;
|
|
165
|
+
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
|
+
}
|
|
171
|
+
}
|
|
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++;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (!varMap.size && !classMap.size) {
|
|
179
|
+
if (opt.report) {
|
|
180
|
+
console.log("[minify-css-vars] нечего сокращать");
|
|
181
|
+
}
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const varEntries = [...varMap.entries()];
|
|
185
|
+
let beforeAll = 0;
|
|
186
|
+
let afterAll = 0;
|
|
187
|
+
const rows = [];
|
|
188
|
+
for (const file of files) {
|
|
189
|
+
const before = bytes(file.raw);
|
|
190
|
+
let next = file.raw;
|
|
191
|
+
if (varMap.size) {
|
|
192
|
+
next = file.isCss ? replaceVarsInCss(next, varEntries) : replaceVarsInJs(next, varEntries);
|
|
193
|
+
}
|
|
194
|
+
if (classMap.size) {
|
|
195
|
+
next = replaceClasses(next, classMap, file.isCss);
|
|
196
|
+
}
|
|
197
|
+
const after = bytes(next);
|
|
198
|
+
beforeAll += before;
|
|
199
|
+
afterAll += after;
|
|
200
|
+
if (next !== file.raw) {
|
|
201
|
+
if (file.item.type === "chunk") {
|
|
202
|
+
file.item.code = next;
|
|
203
|
+
} else {
|
|
204
|
+
file.item.source = next;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (opt.report && (next !== file.raw || before !== after)) {
|
|
208
|
+
rows.push(` ${file.fileName} ${fmtKb(before)} → ${fmtKb(after)} (${pct(before, after)})`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (opt.report) {
|
|
212
|
+
console.log(`[minify-css-vars] vars: ${varMap.size} (пример --${encodeName(opt.varStart, opt.varLen)}, start=${opt.varStart}) classes: ${classMap.size} (пример ${opt.classPrefix}${encodeName(opt.nameStart, opt.nameLen)}, start=${opt.nameStart})`);
|
|
213
|
+
for (const row of rows)
|
|
214
|
+
console.log(row);
|
|
215
|
+
console.log(`[minify-css-vars] итого ${fmtKb(beforeAll)} → ${fmtKb(afterAll)} (${pct(beforeAll, afterAll)})`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
export {
|
|
221
|
+
minifyCssVars,
|
|
222
|
+
encodeName
|
|
223
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "elcrm",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"description": "CLI @elcrm/*: update --fix --test, css (prune imports), docs, cursor, migrate, audit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,6 +33,11 @@
|
|
|
33
33
|
"types": "./dist/vite/postbuild.d.ts",
|
|
34
34
|
"import": "./dist/vite/postbuild.js",
|
|
35
35
|
"default": "./dist/vite/postbuild.js"
|
|
36
|
+
},
|
|
37
|
+
"./vite/minify-css-vars": {
|
|
38
|
+
"types": "./dist/vite/minify-css-vars.d.ts",
|
|
39
|
+
"import": "./dist/vite/minify-css-vars.js",
|
|
40
|
+
"default": "./dist/vite/minify-css-vars.js"
|
|
36
41
|
}
|
|
37
42
|
},
|
|
38
43
|
"files": [
|
|
@@ -44,10 +49,10 @@
|
|
|
44
49
|
"node": ">=18"
|
|
45
50
|
},
|
|
46
51
|
"scripts": {
|
|
47
|
-
"build": "bun build ./src/index.ts --outdir ./dist --target bun --format esm && bun build ./src/vite/plugin-css-scoped.ts --outfile ./dist/vite/plugin-css-scoped.js --target node --format esm --packages external && bun build ./src/vite/discover-lib.ts --outfile ./dist/vite/discover-lib.js --target node --format esm --packages external && bun build ./src/vite/concat-css.ts --outfile ./dist/vite/concat-css.js --target node --format esm --packages external && bun build ./src/vite/check-tokens.ts --outfile ./dist/vite/check-tokens.js --target node --format esm --packages external && bun build ./src/vite/postbuild.ts --outfile ./dist/vite/postbuild.js --target node --format esm --packages external && node ./scripts/chmod-bin.mjs && node ./scripts/copy-vite-dts.mjs",
|
|
52
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target bun --format esm && bun build ./src/vite/plugin-css-scoped.ts --outfile ./dist/vite/plugin-css-scoped.js --target node --format esm --packages external && bun build ./src/vite/discover-lib.ts --outfile ./dist/vite/discover-lib.js --target node --format esm --packages external && bun build ./src/vite/concat-css.ts --outfile ./dist/vite/concat-css.js --target node --format esm --packages external && bun build ./src/vite/check-tokens.ts --outfile ./dist/vite/check-tokens.js --target node --format esm --packages external && bun build ./src/vite/postbuild.ts --outfile ./dist/vite/postbuild.js --target node --format esm --packages external && bun build ./src/vite/minify-css-vars.ts --outfile ./dist/vite/minify-css-vars.js --target node --format esm --packages external && node ./scripts/chmod-bin.mjs && node ./scripts/copy-vite-dts.mjs",
|
|
48
53
|
"dev": "bun run ./src/index.ts",
|
|
49
54
|
"elcrm": "bun run ./src/index.ts",
|
|
50
|
-
"test": "bun test src/lib/migrate",
|
|
55
|
+
"test": "bun test src/lib/migrate src/vite",
|
|
51
56
|
"link:local": "bun run build && bun link",
|
|
52
57
|
"prepublishOnly": "bun run test && bun run build",
|
|
53
58
|
"publish:npm": "npm publish"
|