@pz4l/tinyimg-unplugin 0.3.1 → 0.3.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.
- package/dist/index.d.mts +2 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +145 -121
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -2
package/dist/index.d.mts
CHANGED
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/filter.ts","../src/options.ts","../src/index.ts"],"mappings":";;;UAIiB,aAAA;EACf,OAAA;EACA,OAAA;AAAA;;;UCJe,sBAAA,SAA+B,aAAA;EAC9C,IAAA;EACA,KAAA;EACA,QAAA;EACA,MAAA;EACA,OAAA;AAAA;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/filter.ts","../src/options.ts","../src/index.ts"],"mappings":";;;UAIiB,aAAA;EACf,OAAA;EACA,OAAA;AAAA;;;UCJe,sBAAA,SAA+B,aAAA;EAC9C,IAAA;EACA,KAAA;EACA,QAAA;EACA,MAAA;EACA,KAAA;EDDO;ECGP,OAAA;AAAA;;;cCTqD,QAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import { compressImage, formatBytes, loadKeys } from "@pz4l/tinyimg-core";
|
|
4
|
+
import path from "pathe";
|
|
4
5
|
import { createUnplugin } from "unplugin";
|
|
5
|
-
import path from "node:path";
|
|
6
6
|
import micromatch from "micromatch";
|
|
7
|
+
import kleur from "kleur";
|
|
7
8
|
//#region src/filter.ts
|
|
8
9
|
const IMAGE_EXTENSIONS = new Set([
|
|
9
10
|
".png",
|
|
@@ -11,124 +12,20 @@ const IMAGE_EXTENSIONS = new Set([
|
|
|
11
12
|
".jpeg"
|
|
12
13
|
]);
|
|
13
14
|
function shouldProcessImage(id, options = {}) {
|
|
14
|
-
const
|
|
15
|
+
const normalizedId = path.normalize(id);
|
|
16
|
+
const ext = path.extname(normalizedId).toLowerCase();
|
|
15
17
|
if (!IMAGE_EXTENSIONS.has(ext)) return false;
|
|
16
18
|
if (options.include) {
|
|
17
19
|
const includePatterns = Array.isArray(options.include) ? options.include : [options.include];
|
|
18
|
-
if (!micromatch.isMatch(
|
|
20
|
+
if (!micromatch.isMatch(normalizedId, includePatterns)) return false;
|
|
19
21
|
}
|
|
20
22
|
if (options.exclude) {
|
|
21
23
|
const excludePatterns = Array.isArray(options.exclude) ? options.exclude : [options.exclude];
|
|
22
|
-
if (micromatch.isMatch(
|
|
24
|
+
if (micromatch.isMatch(normalizedId, excludePatterns)) return false;
|
|
23
25
|
}
|
|
24
26
|
return true;
|
|
25
27
|
}
|
|
26
28
|
//#endregion
|
|
27
|
-
//#region src/stats.ts
|
|
28
|
-
var CompressionStats = class {
|
|
29
|
-
compressedCount = 0;
|
|
30
|
-
cachedCount = 0;
|
|
31
|
-
originalSize = 0;
|
|
32
|
-
compressedSize = 0;
|
|
33
|
-
fileResults = [];
|
|
34
|
-
recordCompressed(path, originalSize, compressedSize) {
|
|
35
|
-
this.compressedCount++;
|
|
36
|
-
this.originalSize += originalSize;
|
|
37
|
-
this.compressedSize += compressedSize;
|
|
38
|
-
this.fileResults.push({
|
|
39
|
-
path,
|
|
40
|
-
originalSize,
|
|
41
|
-
compressedSize,
|
|
42
|
-
cached: false
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
recordCached(path, size) {
|
|
46
|
-
this.cachedCount++;
|
|
47
|
-
this.originalSize += size;
|
|
48
|
-
this.compressedSize += size;
|
|
49
|
-
this.fileResults.push({
|
|
50
|
-
path,
|
|
51
|
-
originalSize: size,
|
|
52
|
-
compressedSize: size,
|
|
53
|
-
cached: true
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
recordError(path, error) {
|
|
57
|
-
this.fileResults.push({
|
|
58
|
-
path,
|
|
59
|
-
originalSize: 0,
|
|
60
|
-
cached: false,
|
|
61
|
-
error
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
getSummary() {
|
|
65
|
-
return {
|
|
66
|
-
compressedCount: this.compressedCount,
|
|
67
|
-
cachedCount: this.cachedCount,
|
|
68
|
-
originalSize: this.originalSize,
|
|
69
|
-
compressedSize: this.compressedSize,
|
|
70
|
-
bytesSaved: this.originalSize - this.compressedSize,
|
|
71
|
-
fileCount: this.fileResults.length
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
formatSummary() {
|
|
75
|
-
const summary = this.getSummary();
|
|
76
|
-
const lines = [];
|
|
77
|
-
if (summary.compressedCount === 0 && summary.cachedCount > 0) lines.push(`[tinyimg] All images cached (0 compressed, ${summary.cachedCount} cached)`);
|
|
78
|
-
else {
|
|
79
|
-
lines.push(`✓ [tinyimg] Compressed ${summary.fileCount} images (${summary.cachedCount} cached, ${summary.compressedCount} compressed)`);
|
|
80
|
-
lines.push(`✓ [tinyimg] Saved ${formatBytes(summary.bytesSaved)} (original: ${formatBytes(summary.originalSize)} → compressed: ${formatBytes(summary.compressedSize)})`);
|
|
81
|
-
}
|
|
82
|
-
return lines;
|
|
83
|
-
}
|
|
84
|
-
getFileResults() {
|
|
85
|
-
return this.fileResults;
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
//#endregion
|
|
89
|
-
//#region src/logger.ts
|
|
90
|
-
var TinyimgLogger = class {
|
|
91
|
-
stats;
|
|
92
|
-
verbose;
|
|
93
|
-
strict;
|
|
94
|
-
constructor(options = {}) {
|
|
95
|
-
this.verbose = options.verbose ?? false;
|
|
96
|
-
this.strict = options.strict ?? false;
|
|
97
|
-
this.stats = new CompressionStats();
|
|
98
|
-
}
|
|
99
|
-
logCompressing(path) {
|
|
100
|
-
if (this.verbose) console.log(`[tinyimg] Compressing ${path}...`);
|
|
101
|
-
}
|
|
102
|
-
logCompressed(path, originalSize, compressedSize) {
|
|
103
|
-
this.stats.recordCompressed(path, originalSize, compressedSize);
|
|
104
|
-
if (this.verbose) {
|
|
105
|
-
const saved = ((1 - compressedSize / originalSize) * 100).toFixed(1);
|
|
106
|
-
console.log(`[tinyimg] ✓ Compressed: ${formatBytes(originalSize)} → ${formatBytes(compressedSize)} (${saved}% saved)`);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
logCacheHit(path, size) {
|
|
110
|
-
this.stats.recordCached(path, size);
|
|
111
|
-
if (this.verbose) console.log(`[tinyimg] Cache hit: ${path}`);
|
|
112
|
-
}
|
|
113
|
-
logError(path, error) {
|
|
114
|
-
this.stats.recordError(path, error);
|
|
115
|
-
if (this.strict) console.error(`[tinyimg] ✖ Failed to compress ${path}: ${error}. Build failed.`);
|
|
116
|
-
else console.warn(`[tinyimg] ⚠ Failed to compress ${path}: ${error}. Using original file.`);
|
|
117
|
-
}
|
|
118
|
-
logSummary() {
|
|
119
|
-
this.stats.formatSummary().forEach((line) => console.log(line));
|
|
120
|
-
}
|
|
121
|
-
getStats() {
|
|
122
|
-
return this.stats;
|
|
123
|
-
}
|
|
124
|
-
shouldThrowOnError() {
|
|
125
|
-
return this.strict;
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
function createLogger(options = {}) {
|
|
129
|
-
return new TinyimgLogger(options);
|
|
130
|
-
}
|
|
131
|
-
//#endregion
|
|
132
29
|
//#region src/options.ts
|
|
133
30
|
const VALID_MODES = new Set([
|
|
134
31
|
"random",
|
|
@@ -138,26 +35,150 @@ const VALID_MODES = new Set([
|
|
|
138
35
|
function normalizeOptions(options = {}) {
|
|
139
36
|
if (options.mode !== void 0 && !VALID_MODES.has(options.mode)) throw new TypeError(`Invalid mode: "${options.mode}". Must be one of: random, round-robin, priority`);
|
|
140
37
|
if (options.parallel !== void 0 && options.parallel <= 0) throw new RangeError(`Invalid parallel: ${options.parallel}. Must be a positive number`);
|
|
38
|
+
const level = options.level ?? (options.verbose === true ? "verbose" : "normal");
|
|
141
39
|
return {
|
|
142
40
|
mode: options.mode ?? "random",
|
|
143
41
|
cache: options.cache ?? true,
|
|
144
42
|
parallel: options.parallel ?? 8,
|
|
145
43
|
strict: options.strict ?? false,
|
|
146
|
-
|
|
44
|
+
level,
|
|
147
45
|
include: options.include ? Array.isArray(options.include) ? options.include : [options.include] : void 0,
|
|
148
46
|
exclude: options.exclude ? Array.isArray(options.exclude) ? options.exclude : [options.exclude] : void 0
|
|
149
47
|
};
|
|
150
48
|
}
|
|
151
49
|
//#endregion
|
|
50
|
+
//#region src/utils/logger.ts
|
|
51
|
+
/**
|
|
52
|
+
* 统一的终端日志类(unplugin 专用)
|
|
53
|
+
* 支持 quiet/normal/verbose 三级输出级别
|
|
54
|
+
* 内置统计功能,与 CLI TerminalLogger 对齐
|
|
55
|
+
* 使用统一配色主题(THM-01)
|
|
56
|
+
*/
|
|
57
|
+
var TerminalLogger = class {
|
|
58
|
+
level = "normal";
|
|
59
|
+
stats = {
|
|
60
|
+
processed: 0,
|
|
61
|
+
compressed: 0,
|
|
62
|
+
cached: 0,
|
|
63
|
+
errors: 0,
|
|
64
|
+
originalSize: 0,
|
|
65
|
+
compressedSize: 0
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* 构造函数
|
|
69
|
+
* @param level - 日志级别,默认为 'normal'
|
|
70
|
+
*/
|
|
71
|
+
constructor(level = "normal") {
|
|
72
|
+
this.level = level;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 设置日志级别
|
|
76
|
+
*/
|
|
77
|
+
setLevel(level) {
|
|
78
|
+
this.level = level;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* 获取当前日志级别
|
|
82
|
+
*/
|
|
83
|
+
getLevel() {
|
|
84
|
+
return this.level;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 记录压缩成功
|
|
88
|
+
* 格式:✓ {path} {originalSize} → {compressedSize} -{percent}%
|
|
89
|
+
*/
|
|
90
|
+
successCompress(path, originalSize, compressedSize) {
|
|
91
|
+
if (this.level === "quiet") return;
|
|
92
|
+
this.stats.processed++;
|
|
93
|
+
this.stats.compressed++;
|
|
94
|
+
this.stats.originalSize += originalSize;
|
|
95
|
+
this.stats.compressedSize += compressedSize;
|
|
96
|
+
const savedPercent = originalSize === 0 ? 0 : (1 - compressedSize / originalSize) * 100;
|
|
97
|
+
console.log(`${kleur.green("✓")} ${path} ${formatBytes(originalSize)} → ${formatBytes(compressedSize)} -${savedPercent.toFixed(1)}%`);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 记录缓存命中
|
|
101
|
+
* 格式:✓ {path} {size} cached
|
|
102
|
+
*/
|
|
103
|
+
cacheHit(path, size) {
|
|
104
|
+
if (this.level === "quiet") return;
|
|
105
|
+
this.stats.processed++;
|
|
106
|
+
this.stats.cached++;
|
|
107
|
+
this.stats.originalSize += size;
|
|
108
|
+
this.stats.compressedSize += size;
|
|
109
|
+
console.log(`${kleur.green("✓")} ${path} ${formatBytes(size)} cached`);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 记录压缩错误
|
|
113
|
+
* strict=true: 使用 console.error 输出 ✗ 前缀(红色)
|
|
114
|
+
* strict=false: 使用 console.warn 输出 ⚠ 前缀(黄色)
|
|
115
|
+
*/
|
|
116
|
+
errorCompress(path, message, strict) {
|
|
117
|
+
this.stats.processed++;
|
|
118
|
+
this.stats.errors++;
|
|
119
|
+
if (strict) console.error(`${kleur.red("✗")} ${path} ${message}`);
|
|
120
|
+
else console.warn(`${kleur.yellow("⚠")} ${path} ${message}`);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* 信息日志 - normal/verbose 模式输出
|
|
124
|
+
* 配色:青色 + ℹ 前缀
|
|
125
|
+
*/
|
|
126
|
+
info(message) {
|
|
127
|
+
if (this.level === "quiet") return;
|
|
128
|
+
console.log(`${kleur.cyan("ℹ")} ${message}`);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* 详细日志 - 仅 verbose 模式输出
|
|
132
|
+
* 配色:灰色
|
|
133
|
+
*/
|
|
134
|
+
verbose(message) {
|
|
135
|
+
if (this.level === "verbose") console.log(kleur.gray(message));
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* 输出汇总信息
|
|
139
|
+
* 格式与 CLI compress 汇总一致:
|
|
140
|
+
* ✓ Compression complete
|
|
141
|
+
* Files: N processed, M compressed, K cached
|
|
142
|
+
* [Errors: E](如果有错误)
|
|
143
|
+
* Savings: X.X% avg, Y.Z total
|
|
144
|
+
*/
|
|
145
|
+
summary() {
|
|
146
|
+
if (this.level === "quiet" || this.stats.processed === 0) return;
|
|
147
|
+
const savedBytes = this.stats.originalSize - this.stats.compressedSize;
|
|
148
|
+
const savedPercent = this.stats.originalSize === 0 ? 0 : savedBytes / this.stats.originalSize * 100;
|
|
149
|
+
console.log(`${kleur.green("✓")} Compression complete`);
|
|
150
|
+
console.log(` Files: ${this.stats.processed} processed, ${this.stats.compressed} compressed, ${this.stats.cached} cached`);
|
|
151
|
+
if (this.stats.errors > 0) console.log(` Errors: ${this.stats.errors}`);
|
|
152
|
+
console.log(` Savings: ${savedPercent.toFixed(1)}% avg, ${formatBytes(savedBytes)} total`);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* 获取当前统计信息
|
|
156
|
+
*/
|
|
157
|
+
getStats() {
|
|
158
|
+
return { ...this.stats };
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* 重置统计信息
|
|
162
|
+
*/
|
|
163
|
+
resetStats() {
|
|
164
|
+
this.stats = {
|
|
165
|
+
processed: 0,
|
|
166
|
+
compressed: 0,
|
|
167
|
+
cached: 0,
|
|
168
|
+
errors: 0,
|
|
169
|
+
originalSize: 0,
|
|
170
|
+
compressedSize: 0
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
new TerminalLogger();
|
|
175
|
+
//#endregion
|
|
152
176
|
//#region src/index.ts
|
|
153
177
|
const IMAGE_REGEX = /\.(png|jpg|jpeg|gif|webp|svg)$/i;
|
|
154
178
|
var src_default = createUnplugin((options = {}) => {
|
|
155
179
|
const normalized = normalizeOptions(options);
|
|
156
180
|
if (loadKeys().length === 0) throw new Error("TINYPNG_KEYS environment variable is required");
|
|
157
|
-
const logger =
|
|
158
|
-
verbose: normalized.verbose,
|
|
159
|
-
strict: normalized.strict
|
|
160
|
-
});
|
|
181
|
+
const logger = new TerminalLogger(normalized.level);
|
|
161
182
|
return {
|
|
162
183
|
name: "tinyimg-unplugin",
|
|
163
184
|
enforce: "post",
|
|
@@ -166,26 +187,29 @@ var src_default = createUnplugin((options = {}) => {
|
|
|
166
187
|
if (!isProductionBuild(this)) return null;
|
|
167
188
|
const buffer = Buffer.from(code);
|
|
168
189
|
const relativePath = getRelativePath(id);
|
|
169
|
-
logger.logCompressing(relativePath);
|
|
170
190
|
try {
|
|
171
|
-
const compressed = await compressImage(buffer, {
|
|
191
|
+
const { buffer: compressed, meta } = await compressImage(buffer, {
|
|
172
192
|
projectCacheOnly: true,
|
|
173
193
|
cache: normalized.cache,
|
|
174
194
|
mode: normalized.mode
|
|
175
195
|
});
|
|
176
|
-
logger.
|
|
196
|
+
if (meta.cached) logger.cacheHit(relativePath, meta.originalSize);
|
|
197
|
+
else {
|
|
198
|
+
logger.successCompress(relativePath, meta.originalSize, meta.compressedSize);
|
|
199
|
+
if (normalized.level === "verbose" && meta.compressorName) logger.verbose(` compressor: ${meta.compressorName}`);
|
|
200
|
+
}
|
|
177
201
|
return {
|
|
178
202
|
code: compressed,
|
|
179
203
|
map: null
|
|
180
204
|
};
|
|
181
205
|
} catch (error) {
|
|
182
|
-
logger.
|
|
183
|
-
if (
|
|
206
|
+
logger.errorCompress(relativePath, error.message, normalized.strict);
|
|
207
|
+
if (normalized.strict) throw error;
|
|
184
208
|
return null;
|
|
185
209
|
}
|
|
186
210
|
},
|
|
187
211
|
buildEnd() {
|
|
188
|
-
logger.
|
|
212
|
+
logger.summary();
|
|
189
213
|
}
|
|
190
214
|
};
|
|
191
215
|
});
|
|
@@ -196,7 +220,7 @@ function isProductionBuild(context) {
|
|
|
196
220
|
}
|
|
197
221
|
function getRelativePath(id) {
|
|
198
222
|
const root = process.cwd();
|
|
199
|
-
return
|
|
223
|
+
return path.relative(root, id).replace(IMAGE_REGEX, "");
|
|
200
224
|
}
|
|
201
225
|
//#endregion
|
|
202
226
|
export { src_default as default };
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/filter.ts","../src/stats.ts","../src/logger.ts","../src/options.ts","../src/index.ts"],"sourcesContent":["import path from 'node:path'\n// @ts-expect-error - micromatch doesn't have types\nimport micromatch from 'micromatch'\n\nexport interface FilterOptions {\n include?: string | string[]\n exclude?: string | string[]\n}\n\nexport const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg'])\n\nexport function shouldProcessImage(id: string, options: FilterOptions = {}): boolean {\n // 1. Check extension first (fast path)\n const ext = path.extname(id).toLowerCase()\n if (!IMAGE_EXTENSIONS.has(ext)) {\n return false\n }\n\n // 2. Check include pattern if provided\n if (options.include) {\n const includePatterns = Array.isArray(options.include) ? options.include : [options.include]\n const isInclude = micromatch.isMatch(id, includePatterns)\n if (!isInclude)\n return false\n }\n\n // 3. Check exclude pattern if provided\n if (options.exclude) {\n const excludePatterns = Array.isArray(options.exclude) ? options.exclude : [options.exclude]\n const isExclude = micromatch.isMatch(id, excludePatterns)\n if (isExclude)\n return false\n }\n\n return true\n}\n","import { formatBytes } from '@pz4l/tinyimg-core'\n\nexport interface FileResult {\n path: string\n originalSize: number\n compressedSize?: number\n cached: boolean\n error?: string\n}\n\nexport class CompressionStats {\n private compressedCount = 0\n private cachedCount = 0\n private originalSize = 0\n private compressedSize = 0\n private fileResults: FileResult[] = []\n\n recordCompressed(path: string, originalSize: number, compressedSize: number): void {\n this.compressedCount++\n this.originalSize += originalSize\n this.compressedSize += compressedSize\n this.fileResults.push({ path, originalSize, compressedSize, cached: false })\n }\n\n recordCached(path: string, size: number): void {\n this.cachedCount++\n this.originalSize += size\n this.compressedSize += size\n this.fileResults.push({ path, originalSize: size, compressedSize: size, cached: true })\n }\n\n recordError(path: string, error: string): void {\n this.fileResults.push({ path, originalSize: 0, cached: false, error })\n }\n\n getSummary() {\n return {\n compressedCount: this.compressedCount,\n cachedCount: this.cachedCount,\n originalSize: this.originalSize,\n compressedSize: this.compressedSize,\n bytesSaved: this.originalSize - this.compressedSize,\n fileCount: this.fileResults.length,\n }\n }\n\n formatSummary(): string[] {\n const summary = this.getSummary()\n const lines: string[] = []\n\n if (summary.compressedCount === 0 && summary.cachedCount > 0) {\n // All cached (D-11)\n lines.push(`[tinyimg] All images cached (0 compressed, ${summary.cachedCount} cached)`)\n }\n else {\n // Normal summary (D-09)\n lines.push(`✓ [tinyimg] Compressed ${summary.fileCount} images (${summary.cachedCount} cached, ${summary.compressedCount} compressed)`)\n lines.push(`✓ [tinyimg] Saved ${formatBytes(summary.bytesSaved)} (original: ${formatBytes(summary.originalSize)} → compressed: ${formatBytes(summary.compressedSize)})`)\n }\n\n return lines\n }\n\n getFileResults(): readonly FileResult[] {\n return this.fileResults\n }\n}\n","import { formatBytes } from '@pz4l/tinyimg-core'\nimport { CompressionStats } from './stats'\n\nexport interface LoggerOptions {\n verbose?: boolean\n strict?: boolean\n}\n\nexport class TinyimgLogger {\n private stats: CompressionStats\n private verbose: boolean\n private strict: boolean\n\n constructor(options: LoggerOptions = {}) {\n this.verbose = options.verbose ?? false\n this.strict = options.strict ?? false\n this.stats = new CompressionStats()\n }\n\n logCompressing(path: string): void {\n if (this.verbose) {\n console.log(`[tinyimg] Compressing ${path}...`)\n }\n }\n\n logCompressed(path: string, originalSize: number, compressedSize: number): void {\n this.stats.recordCompressed(path, originalSize, compressedSize)\n\n if (this.verbose) {\n const saved = ((1 - compressedSize / originalSize) * 100).toFixed(1)\n console.log(`[tinyimg] ✓ Compressed: ${formatBytes(originalSize)} → ${formatBytes(compressedSize)} (${saved}% saved)`)\n }\n }\n\n logCacheHit(path: string, size: number): void {\n this.stats.recordCached(path, size)\n\n if (this.verbose) {\n console.log(`[tinyimg] Cache hit: ${path}`)\n }\n }\n\n logError(path: string, error: string): void {\n this.stats.recordError(path, error)\n\n if (this.strict) {\n // Strict mode (D-13)\n console.error(`[tinyimg] ✖ Failed to compress ${path}: ${error}. Build failed.`)\n }\n else {\n // Non-strict mode (D-12)\n console.warn(`[tinyimg] ⚠ Failed to compress ${path}: ${error}. Using original file.`)\n }\n }\n\n logSummary(): void {\n const lines = this.stats.formatSummary()\n lines.forEach(line => console.log(line))\n }\n\n getStats(): CompressionStats {\n return this.stats\n }\n\n shouldThrowOnError(): boolean {\n return this.strict\n }\n}\n\nexport function createLogger(options: LoggerOptions = {}): TinyimgLogger {\n return new TinyimgLogger(options)\n}\n","import type { FilterOptions } from './filter'\n\nexport interface TinyimgUnpluginOptions extends FilterOptions {\n mode?: 'random' | 'round-robin' | 'priority'\n cache?: boolean\n parallel?: number\n strict?: boolean\n verbose?: boolean\n}\n\nexport interface NormalizedOptions {\n mode: 'random' | 'round-robin' | 'priority'\n cache: boolean\n parallel: number\n strict: boolean\n verbose: boolean\n include?: string[]\n exclude?: string[]\n}\n\nconst VALID_MODES = new Set(['random', 'round-robin', 'priority'])\n\nexport function normalizeOptions(options: TinyimgUnpluginOptions = {}): NormalizedOptions {\n // Validate mode\n if (options.mode !== undefined && !VALID_MODES.has(options.mode)) {\n throw new TypeError(`Invalid mode: \"${options.mode}\". Must be one of: random, round-robin, priority`)\n }\n\n // Validate parallel\n if (options.parallel !== undefined && options.parallel <= 0) {\n throw new RangeError(`Invalid parallel: ${options.parallel}. Must be a positive number`)\n }\n\n return {\n mode: options.mode ?? 'random',\n cache: options.cache ?? true,\n parallel: options.parallel ?? 8,\n strict: options.strict ?? false,\n verbose: options.verbose ?? false,\n include: options.include ? (Array.isArray(options.include) ? options.include : [options.include]) : undefined,\n exclude: options.exclude ? (Array.isArray(options.exclude) ? options.exclude : [options.exclude]) : undefined,\n }\n}\n","import type { TinyimgUnpluginOptions } from './options'\nimport { Buffer } from 'node:buffer'\nimport process from 'node:process'\nimport { compressImage, loadKeys } from '@pz4l/tinyimg-core'\nimport { createUnplugin } from 'unplugin'\nimport { shouldProcessImage } from './filter'\nimport { createLogger } from './logger'\nimport { normalizeOptions } from './options'\n\n// Regex for matching image file extensions\nconst IMAGE_REGEX = /\\.(png|jpg|jpeg|gif|webp|svg)$/i\n\nexport default createUnplugin((options: TinyimgUnpluginOptions = {}): any => {\n // Normalize options\n const normalized = normalizeOptions(options)\n\n // Validate TINYPNG_KEYS (D-15, D-16)\n const keys = loadKeys()\n if (keys.length === 0) {\n throw new Error('TINYPNG_KEYS environment variable is required')\n }\n\n // Create logger\n const logger = createLogger({\n verbose: normalized.verbose,\n strict: normalized.strict,\n })\n\n return {\n name: 'tinyimg-unplugin',\n enforce: 'post', // Run after other transformations (D-02)\n\n async transform(code: any, id: any) {\n // Filter non-image files\n const shouldProcess = shouldProcessImage(id, normalized)\n if (!shouldProcess) {\n return null\n }\n\n // Check production build (D-01)\n const isProd = isProductionBuild(this)\n if (!isProd) {\n return null\n }\n\n // Convert to Buffer\n const buffer = Buffer.from(code)\n\n // Get relative path for logging\n const relativePath = getRelativePath(id)\n\n // Log compression start\n logger.logCompressing(relativePath)\n\n try {\n // Compress image\n const compressed = await compressImage(buffer, {\n projectCacheOnly: true, // Only project cache (D-17)\n cache: normalized.cache,\n mode: normalized.mode as any,\n })\n\n // Log success\n logger.logCompressed(relativePath, buffer.length, compressed.length)\n\n return { code: compressed, map: null }\n }\n catch (error: any) {\n // Log error\n logger.logError(relativePath, error.message)\n\n // Check strict mode\n if (logger.shouldThrowOnError()) {\n throw error\n }\n\n // Non-strict: return null to use original file\n return null\n }\n },\n\n buildEnd() {\n logger.logSummary()\n },\n }\n})\n\n// Helper functions\nfunction isProductionBuild(context: any): boolean {\n // Vite: check config.isBuild (D-01)\n if (context?.config?.isBuild !== undefined) {\n return context.config.isBuild\n }\n\n // Webpack: check mode (D-01)\n if (context?.mode !== undefined) {\n return context.mode === 'production'\n }\n\n // Fallback: check NODE_ENV\n return process.env.NODE_ENV === 'production'\n}\n\nfunction getRelativePath(id: string): string {\n // Convert absolute path to relative for logging\n const root = process.cwd()\n return id.replace(root, '').replace(IMAGE_REGEX, '')\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/filter.ts","../src/options.ts","../src/utils/logger.ts","../src/index.ts"],"sourcesContent":["// @ts-expect-error - micromatch doesn't have types\nimport micromatch from 'micromatch'\nimport path from 'pathe'\n\nexport interface FilterOptions {\n include?: string | string[]\n exclude?: string | string[]\n}\n\nexport const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg'])\n\nexport function shouldProcessImage(id: string, options: FilterOptions = {}): boolean {\n // Normalize path for cross-platform compatibility (per D-06, D-07)\n const normalizedId = path.normalize(id)\n\n // 1. Check extension first (fast path)\n const ext = path.extname(normalizedId).toLowerCase()\n if (!IMAGE_EXTENSIONS.has(ext)) {\n return false\n }\n\n // 2. Check include pattern if provided\n if (options.include) {\n const includePatterns = Array.isArray(options.include) ? options.include : [options.include]\n const isInclude = micromatch.isMatch(normalizedId, includePatterns)\n if (!isInclude)\n return false\n }\n\n // 3. Check exclude pattern if provided\n if (options.exclude) {\n const excludePatterns = Array.isArray(options.exclude) ? options.exclude : [options.exclude]\n const isExclude = micromatch.isMatch(normalizedId, excludePatterns)\n if (isExclude)\n return false\n }\n\n return true\n}\n","import type { FilterOptions } from './filter'\n\nexport interface TinyimgUnpluginOptions extends FilterOptions {\n mode?: 'random' | 'round-robin' | 'priority'\n cache?: boolean\n parallel?: number\n strict?: boolean\n level?: 'quiet' | 'normal' | 'verbose'\n /** @deprecated use `level: 'verbose'` instead */\n verbose?: boolean\n}\n\nexport interface NormalizedOptions {\n mode: 'random' | 'round-robin' | 'priority'\n cache: boolean\n parallel: number\n strict: boolean\n level: 'quiet' | 'normal' | 'verbose'\n include?: string[]\n exclude?: string[]\n}\n\nconst VALID_MODES = new Set(['random', 'round-robin', 'priority'])\n\nexport function normalizeOptions(options: TinyimgUnpluginOptions = {}): NormalizedOptions {\n // Validate mode\n if (options.mode !== undefined && !VALID_MODES.has(options.mode)) {\n throw new TypeError(`Invalid mode: \"${options.mode}\". Must be one of: random, round-robin, priority`)\n }\n\n // Validate parallel\n if (options.parallel !== undefined && options.parallel <= 0) {\n throw new RangeError(`Invalid parallel: ${options.parallel}. Must be a positive number`)\n }\n\n // 解析日志级别:优先使用 level,兼容旧版 verbose\n const level = options.level ?? (options.verbose === true ? 'verbose' : 'normal')\n\n return {\n mode: options.mode ?? 'random',\n cache: options.cache ?? true,\n parallel: options.parallel ?? 8,\n strict: options.strict ?? false,\n level,\n include: options.include ? (Array.isArray(options.include) ? options.include : [options.include]) : undefined,\n exclude: options.exclude ? (Array.isArray(options.exclude) ? options.exclude : [options.exclude]) : undefined,\n }\n}\n","import { formatBytes } from '@pz4l/tinyimg-core'\nimport kleur from 'kleur'\n\nexport type LogLevel = 'quiet' | 'normal' | 'verbose'\n\n/**\n * 统一的终端日志类(unplugin 专用)\n * 支持 quiet/normal/verbose 三级输出级别\n * 内置统计功能,与 CLI TerminalLogger 对齐\n * 使用统一配色主题(THM-01)\n */\nexport class TerminalLogger {\n private level: LogLevel = 'normal'\n private stats = {\n processed: 0,\n compressed: 0,\n cached: 0,\n errors: 0,\n originalSize: 0,\n compressedSize: 0,\n }\n\n /**\n * 构造函数\n * @param level - 日志级别,默认为 'normal'\n */\n constructor(level: LogLevel = 'normal') {\n this.level = level\n }\n\n /**\n * 设置日志级别\n */\n setLevel(level: LogLevel): void {\n this.level = level\n }\n\n /**\n * 获取当前日志级别\n */\n getLevel(): LogLevel {\n return this.level\n }\n\n /**\n * 记录压缩成功\n * 格式:✓ {path} {originalSize} → {compressedSize} -{percent}%\n */\n successCompress(path: string, originalSize: number, compressedSize: number): void {\n if (this.level === 'quiet') {\n return\n }\n\n // 更新统计\n this.stats.processed++\n this.stats.compressed++\n this.stats.originalSize += originalSize\n this.stats.compressedSize += compressedSize\n\n // 计算节省百分比\n const savedPercent = originalSize === 0 ? 0 : ((1 - compressedSize / originalSize) * 100)\n\n // 输出格式:✓ path original → compressed -percent%\n console.log(\n `${kleur.green('✓')} ${path} ${formatBytes(originalSize)} → ${formatBytes(compressedSize)} -${savedPercent.toFixed(1)}%`,\n )\n }\n\n /**\n * 记录缓存命中\n * 格式:✓ {path} {size} cached\n */\n cacheHit(path: string, size: number): void {\n if (this.level === 'quiet') {\n return\n }\n\n // 更新统计\n this.stats.processed++\n this.stats.cached++\n this.stats.originalSize += size\n this.stats.compressedSize += size\n\n // 输出格式:✓ path size cached\n console.log(`${kleur.green('✓')} ${path} ${formatBytes(size)} cached`)\n }\n\n /**\n * 记录压缩错误\n * strict=true: 使用 console.error 输出 ✗ 前缀(红色)\n * strict=false: 使用 console.warn 输出 ⚠ 前缀(黄色)\n */\n errorCompress(path: string, message: string, strict: boolean): void {\n // 更新统计(错误总是记录)\n this.stats.processed++\n this.stats.errors++\n\n if (strict) {\n // Strict mode: 红色错误前缀\n console.error(`${kleur.red('✗')} ${path} ${message}`)\n }\n else {\n // Non-strict mode: 黄色警告前缀\n console.warn(`${kleur.yellow('⚠')} ${path} ${message}`)\n }\n }\n\n /**\n * 信息日志 - normal/verbose 模式输出\n * 配色:青色 + ℹ 前缀\n */\n info(message: string): void {\n if (this.level === 'quiet') {\n return\n }\n console.log(`${kleur.cyan('ℹ')} ${message}`)\n }\n\n /**\n * 详细日志 - 仅 verbose 模式输出\n * 配色:灰色\n */\n verbose(message: string): void {\n if (this.level === 'verbose') {\n console.log(kleur.gray(message))\n }\n }\n\n /**\n * 输出汇总信息\n * 格式与 CLI compress 汇总一致:\n * ✓ Compression complete\n * Files: N processed, M compressed, K cached\n * [Errors: E](如果有错误)\n * Savings: X.X% avg, Y.Z total\n */\n summary(): void {\n if (this.level === 'quiet' || this.stats.processed === 0) {\n return\n }\n\n const savedBytes = this.stats.originalSize - this.stats.compressedSize\n const savedPercent = this.stats.originalSize === 0\n ? 0\n : (savedBytes / this.stats.originalSize * 100)\n\n // 输出汇总\n console.log(`${kleur.green('✓')} Compression complete`)\n console.log(` Files: ${this.stats.processed} processed, ${this.stats.compressed} compressed, ${this.stats.cached} cached`)\n\n // 如果有错误,显示错误数量\n if (this.stats.errors > 0) {\n console.log(` Errors: ${this.stats.errors}`)\n }\n\n console.log(` Savings: ${savedPercent.toFixed(1)}% avg, ${formatBytes(savedBytes)} total`)\n }\n\n /**\n * 获取当前统计信息\n */\n getStats() {\n return { ...this.stats }\n }\n\n /**\n * 重置统计信息\n */\n resetStats(): void {\n this.stats = {\n processed: 0,\n compressed: 0,\n cached: 0,\n errors: 0,\n originalSize: 0,\n compressedSize: 0,\n }\n }\n}\n\n/**\n * 全局单例 logger 实例\n */\nexport const logger = new TerminalLogger()\n","import type { TinyimgUnpluginOptions } from './options'\nimport { Buffer } from 'node:buffer'\nimport process from 'node:process'\nimport { compressImage, loadKeys } from '@pz4l/tinyimg-core'\nimport path from 'pathe'\nimport { createUnplugin } from 'unplugin'\nimport { shouldProcessImage } from './filter'\nimport { normalizeOptions } from './options'\nimport { TerminalLogger } from './utils/logger'\n\n// Regex for matching image file extensions\nconst IMAGE_REGEX = /\\.(png|jpg|jpeg|gif|webp|svg)$/i\n\nexport default createUnplugin((options: TinyimgUnpluginOptions = {}): any => {\n // Normalize options\n const normalized = normalizeOptions(options)\n\n // Validate TINYPNG_KEYS (D-15, D-16)\n const keys = loadKeys()\n if (keys.length === 0) {\n throw new Error('TINYPNG_KEYS environment variable is required')\n }\n\n // Create logger\n const logger = new TerminalLogger(normalized.level)\n\n return {\n name: 'tinyimg-unplugin',\n enforce: 'post', // Run after other transformations (D-02)\n\n async transform(code: any, id: any) {\n // Filter non-image files\n const shouldProcess = shouldProcessImage(id, normalized)\n if (!shouldProcess) {\n return null\n }\n\n // Check production build (D-01)\n const isProd = isProductionBuild(this)\n if (!isProd) {\n return null\n }\n\n // Convert to Buffer\n const buffer = Buffer.from(code)\n\n // Get relative path for logging\n const relativePath = getRelativePath(id)\n\n try {\n // Compress image\n const { buffer: compressed, meta } = await compressImage(buffer, {\n projectCacheOnly: true, // Only project cache (D-17)\n cache: normalized.cache,\n mode: normalized.mode as any,\n })\n\n // Log based on cache status\n if (meta.cached) {\n logger.cacheHit(relativePath, meta.originalSize)\n }\n else {\n logger.successCompress(relativePath, meta.originalSize, meta.compressedSize)\n // Verbose mode: log compressor name\n if (normalized.level === 'verbose' && meta.compressorName) {\n logger.verbose(` compressor: ${meta.compressorName}`)\n }\n }\n\n return { code: compressed, map: null }\n }\n catch (error: any) {\n // Log error\n logger.errorCompress(relativePath, error.message, normalized.strict)\n\n // Check strict mode\n if (normalized.strict) {\n throw error\n }\n\n // Non-strict: return null to use original file\n return null\n }\n },\n\n buildEnd() {\n logger.summary()\n },\n }\n})\n\n// Helper functions\nfunction isProductionBuild(context: any): boolean {\n // Vite: check config.isBuild (D-01)\n if (context?.config?.isBuild !== undefined) {\n return context.config.isBuild\n }\n\n // Webpack: check mode (D-01)\n if (context?.mode !== undefined) {\n return context.mode === 'production'\n }\n\n // Fallback: check NODE_ENV\n return process.env.NODE_ENV === 'production'\n}\n\nfunction getRelativePath(id: string): string {\n // Convert absolute path to relative for logging using pathe (per D-08)\n const root = process.cwd()\n const relativePath = path.relative(root, id)\n return relativePath.replace(IMAGE_REGEX, '')\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pz4l/tinyimg-unplugin",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.6",
|
|
5
5
|
"description": "unplugin for automatic image compression during build (Vite, Webpack, Rolldown)",
|
|
6
6
|
"author": "pzehrel",
|
|
7
7
|
"license": "MIT",
|
|
@@ -60,9 +60,11 @@
|
|
|
60
60
|
}
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
+
"kleur": "^4.1.5",
|
|
63
64
|
"micromatch": "^4.0.8",
|
|
65
|
+
"pathe": "^2.0.3",
|
|
64
66
|
"unplugin": "^1.0.0",
|
|
65
|
-
"@pz4l/tinyimg-core": "0.3.
|
|
67
|
+
"@pz4l/tinyimg-core": "0.3.6"
|
|
66
68
|
},
|
|
67
69
|
"devDependencies": {
|
|
68
70
|
"fast-glob": "^3.3.3",
|