zcompress-vite-plugin 0.1.5 → 0.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.
- package/README.md +21 -0
- package/index.js +110 -2
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -83,6 +83,8 @@ npm run build
|
|
|
83
83
|
|
|
84
84
|
默认 `failOnError: true`,失败会直接中断构建(不会静默跳过压缩)。
|
|
85
85
|
|
|
86
|
+
从 `v0.1.6` 开始:若找不到二进制,会自动启用 Node 内置 fallback(支持 `gzip` / `brotli`)。`zstd` 仍需 CLI 二进制。
|
|
87
|
+
|
|
86
88
|
## License
|
|
87
89
|
|
|
88
90
|
MIT
|
|
@@ -177,6 +179,25 @@ npm run build
|
|
|
177
179
|
|
|
178
180
|
Default `failOnError: true` ensures builds fail loudly (instead of silently skipping compression).
|
|
179
181
|
|
|
182
|
+
Since `v0.1.6`: if the binary is missing, the plugin automatically falls back to Node built-in compression for `gzip` / `brotli`. `zstd` still requires the CLI binary.
|
|
183
|
+
|
|
184
|
+
## Maintainer Release Checklist
|
|
185
|
+
|
|
186
|
+
Before running `npm publish`:
|
|
187
|
+
|
|
188
|
+
1. Build and upload these assets to GitHub Release `vX.Y.Z`:
|
|
189
|
+
- `zcompress-macos-arm64`
|
|
190
|
+
- `zcompress-macos-x64`
|
|
191
|
+
- `zcompress-linux-x64`
|
|
192
|
+
- `zcompress-windows-x64.exe`
|
|
193
|
+
2. Run:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
npm run verify:release-assets
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`prepublishOnly` enforces this automatically, so broken releases are blocked.
|
|
200
|
+
|
|
180
201
|
## License
|
|
181
202
|
|
|
182
203
|
MIT
|
package/index.js
CHANGED
|
@@ -16,12 +16,18 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { execFileSync } from 'node:child_process';
|
|
19
|
-
import { existsSync, statSync, readdirSync } from 'node:fs';
|
|
20
|
-
import { join, dirname, isAbsolute } from 'node:path';
|
|
19
|
+
import { existsSync, statSync, readdirSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
20
|
+
import { join, dirname, isAbsolute, extname } from 'node:path';
|
|
21
21
|
import { fileURLToPath } from 'node:url';
|
|
22
|
+
import { gzipSync, brotliCompressSync, constants as zlibConstants } from 'node:zlib';
|
|
22
23
|
|
|
23
24
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
24
25
|
|
|
26
|
+
const DEFAULT_EXTENSIONS = [
|
|
27
|
+
'.js', '.mjs', '.cjs', '.css', '.html', '.htm', '.json', '.svg',
|
|
28
|
+
'.png', '.jpg', '.jpeg', '.gif', '.ico', '.ttf', '.woff', '.woff2', '.xml', '.csv', '.wasm',
|
|
29
|
+
];
|
|
30
|
+
|
|
25
31
|
/**
|
|
26
32
|
* @typedef {Object} ZCompressOptions
|
|
27
33
|
* @property {string} [algo='gzip'] - Compression algorithm: gzip, zstd, brotli
|
|
@@ -147,6 +153,33 @@ export default function zcompressPlugin(userOptions = {}) {
|
|
|
147
153
|
|
|
148
154
|
console.log(`[zcompress] ✅ Compressed ${formatSize(srcSize)} → ${formatSize(destSize)} (saved ${savedPct}%) in ${elapsed}s`);
|
|
149
155
|
} catch (err) {
|
|
156
|
+
// Binary missing: fallback to built-in Node compression for gzip/brotli.
|
|
157
|
+
if (isBinaryNotFoundError(err)) {
|
|
158
|
+
try {
|
|
159
|
+
const fb = fallbackCompressAssets(outDir, compressedDir, options);
|
|
160
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
161
|
+
const savedPct = fb.srcSize > 0
|
|
162
|
+
? ((1 - fb.destSize / fb.srcSize) * 100).toFixed(1)
|
|
163
|
+
: '0.0';
|
|
164
|
+
|
|
165
|
+
console.warn('[zcompress] ⚠ zcompress binary not found, using built-in Node fallback compressor.');
|
|
166
|
+
console.log(`[zcompress] ✅ Compressed ${formatSize(fb.srcSize)} → ${formatSize(fb.destSize)} (saved ${savedPct}%) in ${elapsed}s`);
|
|
167
|
+
if (fb.skipped > 0) console.log(`[zcompress] 💾 ${fb.skipped} file(s) skipped (cache)`);
|
|
168
|
+
return;
|
|
169
|
+
} catch (fallbackErr) {
|
|
170
|
+
const fallbackMessage = [
|
|
171
|
+
`[zcompress] ❌ Compression failed: ${err.message}`,
|
|
172
|
+
`[zcompress] Binary used: ${binary}`,
|
|
173
|
+
`[zcompress] Fallback also failed: ${fallbackErr.message}`,
|
|
174
|
+
'[zcompress] Workaround: install CLI manually (`zig build -Doptimize=ReleaseFast`) and set `binaryPath`.',
|
|
175
|
+
].join('\n');
|
|
176
|
+
|
|
177
|
+
if (options.failOnError !== false) throw new Error(fallbackMessage);
|
|
178
|
+
console.error(fallbackMessage);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
150
183
|
const message = [
|
|
151
184
|
`[zcompress] ❌ Compression failed: ${err.message}`,
|
|
152
185
|
`[zcompress] Binary used: ${binary}`,
|
|
@@ -164,6 +197,81 @@ export default function zcompressPlugin(userOptions = {}) {
|
|
|
164
197
|
};
|
|
165
198
|
}
|
|
166
199
|
|
|
200
|
+
function isBinaryNotFoundError(err) {
|
|
201
|
+
return err && (err.code === 'ENOENT' || String(err.message || '').includes('ENOENT'));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function shouldCompressFile(filePath, options) {
|
|
205
|
+
const ext = extname(filePath).toLowerCase();
|
|
206
|
+
const include = options.include.length > 0
|
|
207
|
+
? options.include.map((e) => e.toLowerCase())
|
|
208
|
+
: DEFAULT_EXTENSIONS;
|
|
209
|
+
const exclude = options.exclude.map((e) => e.toLowerCase());
|
|
210
|
+
|
|
211
|
+
if (!include.includes(ext)) return false;
|
|
212
|
+
if (exclude.includes(ext)) return false;
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function compressionSuffix(algo) {
|
|
217
|
+
if (algo === 'gzip') return '.gz';
|
|
218
|
+
if (algo === 'brotli') return '.br';
|
|
219
|
+
throw new Error('Node fallback currently supports only gzip and brotli. Use CLI binary for zstd.');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function compressBuffer(buf, options) {
|
|
223
|
+
if (options.algo === 'gzip') {
|
|
224
|
+
const level = Math.max(1, Math.min(9, Number(options.level) || 6));
|
|
225
|
+
return gzipSync(buf, { level });
|
|
226
|
+
}
|
|
227
|
+
if (options.algo === 'brotli') {
|
|
228
|
+
const quality = Math.max(1, Math.min(11, Number(options.level) || 6));
|
|
229
|
+
return brotliCompressSync(buf, {
|
|
230
|
+
params: {
|
|
231
|
+
[zlibConstants.BROTLI_PARAM_QUALITY]: quality,
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
throw new Error('Node fallback currently supports only gzip and brotli.');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function fallbackCompressAssets(outDir, compressedDir, options) {
|
|
239
|
+
const allFiles = [];
|
|
240
|
+
walkDir(outDir, (file) => allFiles.push(file));
|
|
241
|
+
|
|
242
|
+
const suffix = compressionSuffix(options.algo);
|
|
243
|
+
let srcSize = 0;
|
|
244
|
+
let destSize = 0;
|
|
245
|
+
let skipped = 0;
|
|
246
|
+
|
|
247
|
+
for (const file of allFiles) {
|
|
248
|
+
if (!shouldCompressFile(file, options)) continue;
|
|
249
|
+
|
|
250
|
+
const rel = file.slice(outDir.length + 1);
|
|
251
|
+
const dest = join(compressedDir, `${rel}${suffix}`);
|
|
252
|
+
|
|
253
|
+
const srcStat = statSync(file);
|
|
254
|
+
if (options.cache && existsSync(dest)) {
|
|
255
|
+
const dstStat = statSync(dest);
|
|
256
|
+
if (dstStat.mtimeMs >= srcStat.mtimeMs) {
|
|
257
|
+
skipped++;
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const data = readFileSync(file);
|
|
263
|
+
const compressed = compressBuffer(data, options);
|
|
264
|
+
|
|
265
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
266
|
+
writeFileSync(dest, compressed);
|
|
267
|
+
|
|
268
|
+
srcSize += srcStat.size;
|
|
269
|
+
destSize += compressed.length;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return { srcSize, destSize, skipped };
|
|
273
|
+
}
|
|
274
|
+
|
|
167
275
|
/**
|
|
168
276
|
* Calculate total directory size in bytes.
|
|
169
277
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zcompress-vite-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "High-performance multi-threaded asset compression for Vite — powered by Zig",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
15
|
"postinstall": "node install.js",
|
|
16
|
-
"test": "node test.js"
|
|
16
|
+
"test": "node test.js",
|
|
17
|
+
"verify:release-assets": "node verify-release-assets.js",
|
|
18
|
+
"prepublishOnly": "node verify-release-assets.js --warn-only"
|
|
17
19
|
},
|
|
18
20
|
"keywords": [
|
|
19
21
|
"vite",
|