zcompress-vite-plugin 0.1.3 → 0.1.5

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 CHANGED
@@ -8,7 +8,7 @@ Vite 生产构建资源压缩插件 — gzip / zstd / brotli,多线程并行
8
8
  npm install zcompress-vite-plugin --save-dev
9
9
  ```
10
10
 
11
- 安装后自动下载对应平台的 zcompress 二进制。如果下载失败,手动安装:
11
+ 安装后会自动下载对应平台的 `zcompress` 二进制。下载失败可手动安装:
12
12
 
13
13
  | 平台 | 命令 |
14
14
  |------|------|
@@ -35,17 +35,7 @@ export default {
35
35
  };
36
36
  ```
37
37
 
38
- `vite build` 后,`dist-compressed/` 目录下就是压缩后的文件:
39
-
40
- ```
41
- dist-compressed/
42
- ├── index.html.gz
43
- ├── style.css.gz
44
- ├── app.js.gz
45
- └── logo.svg.gz
46
- ```
47
-
48
- 配合 Nginx 的 `gzip_static on` 直接使用。
38
+ `vite build` 后,`dist-compressed/` 目录下就是压缩后的文件。
49
39
 
50
40
  ## 选项
51
41
 
@@ -56,13 +46,42 @@ dist-compressed/
56
46
  | `threads` | `number` | `0` | 线程数 (0=CPU 核心数) |
57
47
  | `verbose` | `boolean` | `false` | 显示详细输出 |
58
48
  | `cache` | `boolean` | `false` | 跳过未修改文件 |
59
- | `include` | `string[]` | `[]` | 额外压缩的扩展名 (如 `['.ts']`) |
60
- | `exclude` | `string[]` | `[]` | 排除的扩展名 (如 `['.map']`) |
49
+ | `include` | `string[]` | `[]` | 额外压缩扩展名 |
50
+ | `exclude` | `string[]` | `[]` | 排除扩展名 |
61
51
  | `binaryPath` | `string` | 自动 | 手动指定二进制路径 |
52
+ | `failOnError` | `boolean` | `true` | 压缩失败时让 `vite build` 失败 |
53
+
54
+ ## 故障排查
55
+
56
+ ### `postinstall` 下载二进制 404
57
+
58
+ 如果看到类似:
59
+
60
+ ```txt
61
+ https://github.com/luochuan2008/zcompress/releases/download/vX.Y.Z/zcompress-macos-arm64 → 404
62
+ ```
63
+
64
+ 说明 npm 版本已发布,但对应 GitHub Release 还没上传该平台二进制。
62
65
 
63
- ## 默认压缩的文件类型
66
+ 解决方式:
64
67
 
65
- `.js` `.mjs` `.cjs` `.css` `.html` `.htm` `.json` `.svg` `.png` `.jpg` `.jpeg` `.gif` `.ico` `.ttf` `.woff` `.woff2` `.xml` `.csv` `.wasm`
68
+ 1. 手动安装 CLI(上面安装表)
69
+ 2. 在插件里指定 `binaryPath`
70
+
71
+ ```js
72
+ zcompress({
73
+ binaryPath: '/absolute/path/to/zcompress',
74
+ })
75
+ ```
76
+
77
+ 也可用环境变量(不改代码):
78
+
79
+ ```bash
80
+ export ZCOMPRESS_BINARY=/absolute/path/to/zcompress
81
+ npm run build
82
+ ```
83
+
84
+ 默认 `failOnError: true`,失败会直接中断构建(不会静默跳过压缩)。
66
85
 
67
86
  ## License
68
87
 
@@ -80,11 +99,16 @@ High-performance multi-threaded asset compression for Vite production builds. 5-
80
99
  npm install zcompress-vite-plugin --save-dev
81
100
  ```
82
101
 
83
- The correct platform binary is downloaded automatically. If that fails:
102
+ The package auto-downloads a platform binary. If that fails, install/build manually:
84
103
 
85
104
  ```bash
86
- brew install zcompress # macOS
87
- zig build install # from source
105
+ # macOS
106
+ brew install zstd brotli
107
+ zig build -Doptimize=ReleaseFast
108
+
109
+ # Linux
110
+ sudo apt-get install -y libzstd-dev libbrotli-dev
111
+ zig build -Doptimize=ReleaseFast
88
112
  ```
89
113
 
90
114
  ## Usage
@@ -97,7 +121,7 @@ export default {
97
121
  plugins: [
98
122
  zcompress({
99
123
  algo: 'brotli', // 'gzip' | 'zstd' | 'brotli'
100
- level: 11, // 1-9
124
+ level: 9, // 1-9
101
125
  threads: 0, // 0 = auto
102
126
  cache: true, // skip unchanged files
103
127
  })
@@ -105,15 +129,7 @@ export default {
105
129
  };
106
130
  ```
107
131
 
108
- After `vite build`, find compressed assets in `dist-compressed/`:
109
-
110
- ```
111
- dist-compressed/
112
- ├── index.html.br
113
- ├── style.css.br
114
- ├── app.js.br
115
- └── logo.svg.br
116
- ```
132
+ After `vite build`, compressed assets are written to `dist-compressed/`.
117
133
 
118
134
  ## Options
119
135
 
@@ -127,6 +143,39 @@ dist-compressed/
127
143
  | `include` | `string[]` | `[]` | Extra extensions to compress |
128
144
  | `exclude` | `string[]` | `[]` | Extensions to skip |
129
145
  | `binaryPath` | `string` | auto | Override binary path |
146
+ | `failOnError` | `boolean` | `true` | Fail `vite build` when compression fails |
147
+
148
+ ## Troubleshooting
149
+
150
+ ### `postinstall` binary download returns 404
151
+
152
+ If you see:
153
+
154
+ ```txt
155
+ https://github.com/luochuan2008/zcompress/releases/download/vX.Y.Z/zcompress-macos-arm64 → 404
156
+ ```
157
+
158
+ that npm version exists, but the matching GitHub Release binary asset is missing.
159
+
160
+ Workarounds:
161
+
162
+ 1. Install/build the CLI manually
163
+ 2. Set `binaryPath` explicitly
164
+
165
+ ```js
166
+ zcompress({
167
+ binaryPath: '/absolute/path/to/zcompress',
168
+ })
169
+ ```
170
+
171
+ Or use env var (no code change):
172
+
173
+ ```bash
174
+ export ZCOMPRESS_BINARY=/absolute/path/to/zcompress
175
+ npm run build
176
+ ```
177
+
178
+ Default `failOnError: true` ensures builds fail loudly (instead of silently skipping compression).
130
179
 
131
180
  ## License
132
181
 
package/index.d.ts CHANGED
@@ -16,6 +16,8 @@ declare module 'zcompress-vite-plugin' {
16
16
  exclude?: string[];
17
17
  /** Override path to zcompress binary */
18
18
  binaryPath?: string;
19
+ /** Fail Vite build if compression fails (default: true) */
20
+ failOnError?: boolean;
19
21
  }
20
22
 
21
23
  import type { Plugin } from 'vite';
package/index.js CHANGED
@@ -15,7 +15,7 @@
15
15
  * };
16
16
  */
17
17
 
18
- import { execSync } from 'node:child_process';
18
+ import { execFileSync } from 'node:child_process';
19
19
  import { existsSync, statSync, readdirSync } from 'node:fs';
20
20
  import { join, dirname, isAbsolute } from 'node:path';
21
21
  import { fileURLToPath } from 'node:url';
@@ -32,6 +32,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
32
32
  * @property {string[]} [include] - Extra file extensions to include
33
33
  * @property {string[]} [exclude] - File extensions to exclude
34
34
  * @property {string} [binaryPath] - Path to zcompress binary
35
+ * @property {boolean} [failOnError=true] - Fail Vite build if compression fails
35
36
  */
36
37
 
37
38
  /** @type {ZCompressOptions} */
@@ -43,18 +44,23 @@ const DEFAULT_OPTIONS = {
43
44
  cache: false,
44
45
  include: [],
45
46
  exclude: [],
47
+ failOnError: true,
46
48
  };
47
49
 
48
50
  /**
49
51
  * Find the zcompress binary.
50
- * Order: 1) downloaded by postinstall 2) local zig build 3) PATH
52
+ * Order: 1) ZCOMPRESS_BINARY env 2) downloaded by postinstall 3) local zig build 4) PATH
51
53
  */
52
54
  function findBinary() {
53
- // 1) postinstall-downloaded binary inside package
55
+ // 1) explicit env override
56
+ const envBinary = process.env.ZCOMPRESS_BINARY;
57
+ if (envBinary) return envBinary;
58
+
59
+ // 2) postinstall-downloaded binary inside package
54
60
  const packagedBin = join(__dirname, 'bin', process.platform === 'win32' ? 'zcompress.exe' : 'zcompress');
55
61
  if (existsSync(packagedBin)) return packagedBin;
56
62
 
57
- // 2) local zig build
63
+ // 3) local zig build
58
64
  const zigPaths = [
59
65
  join(process.cwd(), 'zig-out', 'bin', 'zcompress'),
60
66
  join(process.cwd(), 'zig-out', 'bin', 'zcompress.exe'),
@@ -63,7 +69,7 @@ function findBinary() {
63
69
  if (existsSync(p)) return p;
64
70
  }
65
71
 
66
- // 3) PATH
72
+ // 4) PATH
67
73
  return 'zcompress';
68
74
  }
69
75
 
@@ -130,7 +136,7 @@ export default function zcompressPlugin(userOptions = {}) {
130
136
  const startTime = Date.now();
131
137
 
132
138
  try {
133
- execSync(cmd, { stdio: 'inherit' });
139
+ execFileSync(binary, args, { stdio: 'inherit' });
134
140
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
135
141
 
136
142
  const srcSize = getDirSize(outDir);
@@ -141,8 +147,18 @@ export default function zcompressPlugin(userOptions = {}) {
141
147
 
142
148
  console.log(`[zcompress] ✅ Compressed ${formatSize(srcSize)} → ${formatSize(destSize)} (saved ${savedPct}%) in ${elapsed}s`);
143
149
  } catch (err) {
144
- console.error(`[zcompress] Compression failed: ${err.message}`);
145
- console.error('[zcompress] Make sure zcompress is installed: zig build install');
150
+ const message = [
151
+ `[zcompress] Compression failed: ${err.message}`,
152
+ `[zcompress] Binary used: ${binary}`,
153
+ '[zcompress] If this is a 404 download issue, GitHub Release may be missing prebuilt binaries for this version.',
154
+ '[zcompress] Workaround: install CLI manually (`zig build -Doptimize=ReleaseFast`) and set `binaryPath` in plugin options.',
155
+ ].join('\n');
156
+
157
+ if (options.failOnError !== false) {
158
+ throw new Error(message);
159
+ }
160
+
161
+ console.error(message);
146
162
  }
147
163
  },
148
164
  };
package/install.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  /**
4
4
  * Postinstall script — downloads the prebuilt zcompress binary from GitHub Releases.
5
- * Falls back gracefully if the binary can't be downloaded (e.g. air-gapped, unsupported platform).
5
+ * Falls back gracefully if binary can't be fetched.
6
6
  */
7
7
 
8
8
  import { createWriteStream, existsSync, chmodSync, mkdirSync, unlinkSync, readFileSync } from 'node:fs';
@@ -20,10 +20,10 @@ const pkgJson = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf8')
20
20
  const VERSION = pkgJson.version;
21
21
 
22
22
  const PLATFORM_MAP = {
23
- 'darwin-arm64': 'zcompress-macos-arm64',
24
- 'darwin-x64': 'zcompress-macos-x64',
25
- 'linux-x64': 'zcompress-linux-x64',
26
- 'win32-x64': 'zcompress-windows-x64.exe',
23
+ 'darwin-arm64': 'zcompress-macos-arm64',
24
+ 'darwin-x64': 'zcompress-macos-x64',
25
+ 'linux-x64': 'zcompress-linux-x64',
26
+ 'win32-x64': 'zcompress-windows-x64.exe',
27
27
  };
28
28
 
29
29
  function getPlatformKey() {
@@ -33,8 +33,54 @@ function getPlatformKey() {
33
33
  }
34
34
 
35
35
  function getBinaryName() {
36
- const key = getPlatformKey();
37
- return PLATFORM_MAP[key] || null;
36
+ return PLATFORM_MAP[getPlatformKey()] || null;
37
+ }
38
+
39
+ function request(url) {
40
+ return new Promise((resolve, reject) => {
41
+ get(url, {
42
+ headers: {
43
+ 'User-Agent': 'zcompress-vite-plugin-install',
44
+ Accept: 'application/vnd.github+json',
45
+ },
46
+ }, (res) => resolve(res)).on('error', reject);
47
+ });
48
+ }
49
+
50
+ async function fetchJson(url) {
51
+ const res = await request(url);
52
+ if (res.statusCode !== 200) {
53
+ throw new Error(`HTTP ${res.statusCode}`);
54
+ }
55
+ const chunks = [];
56
+ for await (const chunk of res) chunks.push(chunk);
57
+ return JSON.parse(Buffer.concat(chunks).toString('utf8'));
58
+ }
59
+
60
+ async function downloadToFile(url, destPath) {
61
+ const res = await request(url);
62
+
63
+ // Follow redirect (GitHub release assets usually 302)
64
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
65
+ return downloadToFile(res.headers.location, destPath);
66
+ }
67
+
68
+ if (res.statusCode !== 200) {
69
+ throw new Error(`HTTP ${res.statusCode}`);
70
+ }
71
+
72
+ const file = createWriteStream(destPath);
73
+ await pipeline(res, file);
74
+ }
75
+
76
+ async function resolveFallbackAssetUrl(binaryName) {
77
+ // Fallback strategy: use latest release asset for this platform when current version asset is missing.
78
+ const latest = await fetchJson(`https://api.github.com/repos/${REPO}/releases/latest`);
79
+ const asset = (latest.assets || []).find((a) => a.name === binaryName);
80
+ if (!asset?.browser_download_url) {
81
+ throw new Error('Latest release has no matching asset');
82
+ }
83
+ return asset.browser_download_url;
38
84
  }
39
85
 
40
86
  async function downloadBinary() {
@@ -52,49 +98,55 @@ async function downloadBinary() {
52
98
  return true;
53
99
  }
54
100
 
55
- const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${binaryName}`;
56
- console.log(`[zcompress] ↓ Downloading ${url} ...`);
101
+ mkdirSync(BIN_DIR, { recursive: true });
57
102
 
103
+ const versionedUrl = `https://github.com/${REPO}/releases/download/v${VERSION}/${binaryName}`;
104
+ const tried = [];
105
+
106
+ // Attempt 1: exact npm version
58
107
  try {
59
- mkdirSync(BIN_DIR, { recursive: true });
60
-
61
- await new Promise((resolve, reject) => {
62
- const file = createWriteStream(destPath);
63
- get(url, (response) => {
64
- // Follow redirects
65
- if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
66
- get(response.headers.location, (redirectRes) => {
67
- pipeline(redirectRes, file).then(resolve).catch(reject);
68
- }).on('error', reject);
69
- return;
70
- }
71
- if (response.statusCode !== 200) {
72
- file.close();
73
- unlinkSync(destPath);
74
- reject(new Error(`HTTP ${response.statusCode}`));
75
- return;
76
- }
77
- pipeline(response, file).then(resolve).catch(reject);
78
- }).on('error', reject);
79
- });
80
-
81
- // Make executable on Unix
82
- if (process.platform !== 'win32') {
83
- chmodSync(destPath, 0o755);
84
- }
108
+ tried.push(versionedUrl);
109
+ console.log(`[zcompress] ↓ Downloading ${versionedUrl} ...`);
110
+ await downloadToFile(versionedUrl, destPath);
85
111
 
112
+ if (process.platform !== 'win32') chmodSync(destPath, 0o755);
86
113
  console.log(`[zcompress] ✓ Binary installed: ${destPath}`);
87
114
  return true;
88
115
  } catch (err) {
89
- console.log(`[zcompress] Could not download binary (${err.message}). Build from source: cd zcompress && zig build install`);
90
- // Clean up partial download
91
- try { unlinkSync(destPath); } catch (_) { /* ignore */ }
116
+ // cleanup partial
117
+ try { unlinkSync(destPath); } catch {}
118
+
119
+ // If 404 or similar, try latest release as fallback
120
+ const msg = String(err?.message || err);
121
+ if (!msg.includes('404')) {
122
+ console.log(`[zcompress] ⚠ Download failed (${msg}).`);
123
+ } else {
124
+ console.log(`[zcompress] ⚠ Asset missing for v${VERSION}: ${binaryName}`);
125
+ }
126
+ }
127
+
128
+ // Attempt 2: latest release fallback
129
+ try {
130
+ const latestAssetUrl = await resolveFallbackAssetUrl(binaryName);
131
+ tried.push(latestAssetUrl);
132
+ console.log(`[zcompress] ↓ Falling back to latest release asset: ${latestAssetUrl}`);
133
+ await downloadToFile(latestAssetUrl, destPath);
134
+
135
+ if (process.platform !== 'win32') chmodSync(destPath, 0o755);
136
+ console.log(`[zcompress] ✓ Binary installed from latest release: ${destPath}`);
137
+ return true;
138
+ } catch (err) {
139
+ try { unlinkSync(destPath); } catch {}
140
+ console.log(`[zcompress] ⚠ Fallback download failed (${err.message}).`);
141
+ console.log('[zcompress] Tried URLs:');
142
+ for (const u of tried) console.log(` - ${u}`);
143
+ console.log('[zcompress] ℹ Build from source: cd zcompress && zig build -Doptimize=ReleaseFast');
92
144
  return false;
93
145
  }
94
146
  }
95
147
 
96
148
  // Run download, but don't fail install if it doesn't work
97
- downloadBinary().catch(() => {
98
- console.log('[zcompress] Binary download skipped. The plugin requires the zcompress CLI.');
99
- console.log('[zcompress] ℹ Install it via: brew install zcompress OR zig build install');
149
+ downloadBinary().catch((err) => {
150
+ console.log(`[zcompress] Binary download skipped (${err.message}).`);
151
+ console.log('[zcompress] ℹ Install it manually and set plugin option `binaryPath`.');
100
152
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zcompress-vite-plugin",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "High-performance multi-threaded asset compression for Vite — powered by Zig",
5
5
  "type": "module",
6
6
  "main": "index.js",