zcompress-vite-plugin 0.1.2 → 0.1.4

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,35 @@ 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` 失败 |
62
53
 
63
- ## 默认压缩的文件类型
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
+ ```
64
63
 
65
- `.js` `.mjs` `.cjs` `.css` `.html` `.htm` `.json` `.svg` `.png` `.jpg` `.jpeg` `.gif` `.ico` `.ttf` `.woff` `.woff2` `.xml` `.csv` `.wasm`
64
+ 说明 npm 版本已发布,但对应 GitHub Release 还没上传该平台二进制。
65
+
66
+ 解决方式:
67
+
68
+ 1. 手动安装 CLI(上面安装表)
69
+ 2. 在插件里指定 `binaryPath`
70
+
71
+ ```js
72
+ zcompress({
73
+ binaryPath: '/absolute/path/to/zcompress',
74
+ })
75
+ ```
76
+
77
+ 默认 `failOnError: true`,失败会直接中断构建(不会静默跳过压缩)。
66
78
 
67
79
  ## License
68
80
 
@@ -80,11 +92,16 @@ High-performance multi-threaded asset compression for Vite production builds. 5-
80
92
  npm install zcompress-vite-plugin --save-dev
81
93
  ```
82
94
 
83
- The correct platform binary is downloaded automatically. If that fails:
95
+ The package auto-downloads a platform binary. If that fails, install/build manually:
84
96
 
85
97
  ```bash
86
- brew install zcompress # macOS
87
- zig build install # from source
98
+ # macOS
99
+ brew install zstd brotli
100
+ zig build -Doptimize=ReleaseFast
101
+
102
+ # Linux
103
+ sudo apt-get install -y libzstd-dev libbrotli-dev
104
+ zig build -Doptimize=ReleaseFast
88
105
  ```
89
106
 
90
107
  ## Usage
@@ -97,7 +114,7 @@ export default {
97
114
  plugins: [
98
115
  zcompress({
99
116
  algo: 'brotli', // 'gzip' | 'zstd' | 'brotli'
100
- level: 11, // 1-9
117
+ level: 9, // 1-9
101
118
  threads: 0, // 0 = auto
102
119
  cache: true, // skip unchanged files
103
120
  })
@@ -105,15 +122,7 @@ export default {
105
122
  };
106
123
  ```
107
124
 
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
- ```
125
+ After `vite build`, compressed assets are written to `dist-compressed/`.
117
126
 
118
127
  ## Options
119
128
 
@@ -127,6 +136,32 @@ dist-compressed/
127
136
  | `include` | `string[]` | `[]` | Extra extensions to compress |
128
137
  | `exclude` | `string[]` | `[]` | Extensions to skip |
129
138
  | `binaryPath` | `string` | auto | Override binary path |
139
+ | `failOnError` | `boolean` | `true` | Fail `vite build` when compression fails |
140
+
141
+ ## Troubleshooting
142
+
143
+ ### `postinstall` binary download returns 404
144
+
145
+ If you see:
146
+
147
+ ```txt
148
+ https://github.com/luochuan2008/zcompress/releases/download/vX.Y.Z/zcompress-macos-arm64 → 404
149
+ ```
150
+
151
+ that npm version exists, but the matching GitHub Release binary asset is missing.
152
+
153
+ Workarounds:
154
+
155
+ 1. Install/build the CLI manually
156
+ 2. Set `binaryPath` explicitly
157
+
158
+ ```js
159
+ zcompress({
160
+ binaryPath: '/absolute/path/to/zcompress',
161
+ })
162
+ ```
163
+
164
+ Default `failOnError: true` ensures builds fail loudly (instead of silently skipping compression).
130
165
 
131
166
  ## License
132
167
 
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
@@ -2,7 +2,7 @@
2
2
  * zcompress Vite Plugin
3
3
  *
4
4
  * A Vite plugin that compresses build output using the zcompress CLI.
5
- * Provides multi-threaded gzip/zstd compression for production builds.
5
+ * Provides multi-threaded gzip/zstd/brotli compression for production builds.
6
6
  *
7
7
  * @example
8
8
  * // vite.config.js
@@ -15,9 +15,12 @@
15
15
  * };
16
16
  */
17
17
 
18
- import { execSync } from 'node:child_process';
19
- import { existsSync, statSync } from 'node:fs';
20
- import { join } from 'node:path';
18
+ import { execFileSync } from 'node:child_process';
19
+ import { existsSync, statSync, readdirSync } from 'node:fs';
20
+ import { join, dirname, isAbsolute } from 'node:path';
21
+ import { fileURLToPath } from 'node:url';
22
+
23
+ const __dirname = dirname(fileURLToPath(import.meta.url));
21
24
 
22
25
  /**
23
26
  * @typedef {Object} ZCompressOptions
@@ -29,6 +32,7 @@ import { join } from 'node:path';
29
32
  * @property {string[]} [include] - Extra file extensions to include
30
33
  * @property {string[]} [exclude] - File extensions to exclude
31
34
  * @property {string} [binaryPath] - Path to zcompress binary
35
+ * @property {boolean} [failOnError=true] - Fail Vite build if compression fails
32
36
  */
33
37
 
34
38
  /** @type {ZCompressOptions} */
@@ -40,19 +44,19 @@ const DEFAULT_OPTIONS = {
40
44
  cache: false,
41
45
  include: [],
42
46
  exclude: [],
47
+ failOnError: true,
43
48
  };
44
49
 
45
50
  /**
46
51
  * Find the zcompress binary.
47
- * Order: 1) downloaded by postinstall 2) PATH 3) local zig build
52
+ * Order: 1) downloaded by postinstall 2) local zig build 3) PATH
48
53
  */
49
54
  function findBinary() {
50
- // 1. Check for postinstall-downloaded binary
51
- const binDir = join(__dirname, 'bin');
52
- const localBin = join(binDir, process.platform === 'win32' ? 'zcompress.exe' : 'zcompress');
53
- if (existsSync(localBin)) return localBin;
55
+ // 1) postinstall-downloaded binary inside package
56
+ const packagedBin = join(__dirname, 'bin', process.platform === 'win32' ? 'zcompress.exe' : 'zcompress');
57
+ if (existsSync(packagedBin)) return packagedBin;
54
58
 
55
- // 2. Check local zig build
59
+ // 2) local zig build
56
60
  const zigPaths = [
57
61
  join(process.cwd(), 'zig-out', 'bin', 'zcompress'),
58
62
  join(process.cwd(), 'zig-out', 'bin', 'zcompress.exe'),
@@ -61,7 +65,7 @@ function findBinary() {
61
65
  if (existsSync(p)) return p;
62
66
  }
63
67
 
64
- // 3. Fall back to PATH
68
+ // 3) PATH
65
69
  return 'zcompress';
66
70
  }
67
71
 
@@ -74,20 +78,29 @@ function findBinary() {
74
78
  export default function zcompressPlugin(userOptions = {}) {
75
79
  const options = { ...DEFAULT_OPTIONS, ...userOptions };
76
80
 
81
+ // ESM-safe plugin state (do not use `this`)
82
+ let active = false;
83
+ let resolvedOutDir = 'dist';
84
+
77
85
  return {
78
86
  name: 'zcompress',
79
87
  apply: 'build',
80
88
  enforce: 'post',
81
89
 
82
90
  configResolved(config) {
83
- // Only run in production build mode
84
- this.active = config.command === 'build';
91
+ active = config.command === 'build';
92
+
93
+ // Save outDir from Vite config (Bug #2 fix)
94
+ const configuredOutDir = config.build?.outDir || 'dist';
95
+ resolvedOutDir = isAbsolute(configuredOutDir)
96
+ ? configuredOutDir
97
+ : join(config.root || process.cwd(), configuredOutDir);
85
98
  },
86
99
 
87
100
  closeBundle() {
88
- if (!this.active) return;
101
+ if (!active) return;
89
102
 
90
- const outDir = this.outDir || 'dist';
103
+ const outDir = resolvedOutDir;
91
104
  const compressedDir = `${outDir}-compressed`;
92
105
 
93
106
  if (!existsSync(outDir)) {
@@ -103,18 +116,12 @@ export default function zcompressPlugin(userOptions = {}) {
103
116
  '-l', String(options.level),
104
117
  ];
105
118
 
106
- if (options.threads > 0) {
107
- args.push('-t', String(options.threads));
108
- }
119
+ if (options.threads > 0) args.push('-t', String(options.threads));
109
120
  if (options.verbose) args.push('--verbose');
110
121
  if (options.cache) args.push('--cache');
111
122
 
112
- for (const ext of options.include) {
113
- args.push(`--include=${ext}`);
114
- }
115
- for (const ext of options.exclude) {
116
- args.push(`--exclude=${ext}`);
117
- }
123
+ for (const ext of options.include) args.push(`--include=${ext}`);
124
+ for (const ext of options.exclude) args.push(`--exclude=${ext}`);
118
125
 
119
126
  const cmd = `${binary} ${args.join(' ')}`;
120
127
 
@@ -125,10 +132,9 @@ export default function zcompressPlugin(userOptions = {}) {
125
132
  const startTime = Date.now();
126
133
 
127
134
  try {
128
- execSync(cmd, { stdio: 'inherit' });
135
+ execFileSync(binary, args, { stdio: 'inherit' });
129
136
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
130
137
 
131
- // Calculate size stats
132
138
  const srcSize = getDirSize(outDir);
133
139
  const destSize = getDirSize(compressedDir);
134
140
  const savedPct = srcSize > 0
@@ -137,8 +143,18 @@ export default function zcompressPlugin(userOptions = {}) {
137
143
 
138
144
  console.log(`[zcompress] ✅ Compressed ${formatSize(srcSize)} → ${formatSize(destSize)} (saved ${savedPct}%) in ${elapsed}s`);
139
145
  } catch (err) {
140
- console.error(`[zcompress] Compression failed: ${err.message}`);
141
- console.error('[zcompress] Make sure zcompress is installed: zig build install');
146
+ const message = [
147
+ `[zcompress] Compression failed: ${err.message}`,
148
+ `[zcompress] Binary used: ${binary}`,
149
+ '[zcompress] If this is a 404 download issue, GitHub Release may be missing prebuilt binaries for this version.',
150
+ '[zcompress] Workaround: install CLI manually (`zig build -Doptimize=ReleaseFast`) and set `binaryPath` in plugin options.',
151
+ ].join('\n');
152
+
153
+ if (options.failOnError !== false) {
154
+ throw new Error(message);
155
+ }
156
+
157
+ console.error(message);
142
158
  }
143
159
  },
144
160
  };
@@ -152,17 +168,19 @@ export default function zcompressPlugin(userOptions = {}) {
152
168
  */
153
169
  function getDirSize(dirPath) {
154
170
  if (!existsSync(dirPath)) return 0;
155
- // Simple approximation — list files and sum their sizes
171
+
156
172
  let total = 0;
157
173
  try {
158
- const { readdirSync } = require('node:fs');
159
- const { join } = require('node:path');
160
174
  walkDir(dirPath, (filePath) => {
161
175
  try {
162
176
  total += statSync(filePath).size;
163
- } catch (_) { /* ignore */ }
177
+ } catch {
178
+ // ignore unreadable files
179
+ }
164
180
  });
165
- } catch (_) { /* ignore */ }
181
+ } catch {
182
+ // ignore traversal failures
183
+ }
166
184
  return total;
167
185
  }
168
186
 
@@ -173,8 +191,6 @@ function getDirSize(dirPath) {
173
191
  * @param {(path: string) => void} fn
174
192
  */
175
193
  function walkDir(dir, fn) {
176
- const { readdirSync } = require('node:fs');
177
- const { join } = require('node:path');
178
194
  try {
179
195
  const entries = readdirSync(dir, { withFileTypes: true });
180
196
  for (const entry of entries) {
@@ -185,7 +201,9 @@ function walkDir(dir, fn) {
185
201
  fn(full);
186
202
  }
187
203
  }
188
- } catch (_) { /* ignore */ }
204
+ } catch {
205
+ // ignore traversal failures
206
+ }
189
207
  }
190
208
 
191
209
  /**
package/install.js CHANGED
@@ -5,8 +5,7 @@
5
5
  * Falls back gracefully if the binary can't be downloaded (e.g. air-gapped, unsupported platform).
6
6
  */
7
7
 
8
- import { execSync } from 'node:child_process';
9
- import { createWriteStream, existsSync, chmodSync, mkdirSync, unlinkSync } from 'node:fs';
8
+ import { createWriteStream, existsSync, chmodSync, mkdirSync, unlinkSync, readFileSync } from 'node:fs';
10
9
  import { get } from 'node:https';
11
10
  import { join, dirname } from 'node:path';
12
11
  import { fileURLToPath } from 'node:url';
@@ -14,8 +13,11 @@ import { pipeline } from 'node:stream/promises';
14
13
 
15
14
  const __dirname = dirname(fileURLToPath(import.meta.url));
16
15
  const BIN_DIR = join(__dirname, 'bin');
17
- const VERSION = '0.1.0'; // Update on each release
18
- const REPO = 'luochuan2008/zcompress'; // Change to your GitHub username/repo
16
+ const REPO = 'luochuan2008/zcompress';
17
+
18
+ // Read package version dynamically to avoid manual sync bugs.
19
+ const pkgJson = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf8'));
20
+ const VERSION = pkgJson.version;
19
21
 
20
22
  const PLATFORM_MAP = {
21
23
  'darwin-arm64': 'zcompress-macos-arm64',
@@ -84,7 +86,14 @@ async function downloadBinary() {
84
86
  console.log(`[zcompress] ✓ Binary installed: ${destPath}`);
85
87
  return true;
86
88
  } catch (err) {
87
- console.log(`[zcompress] ⚠ Could not download binary (${err.message}). Build from source: cd zcompress && zig build install`);
89
+ if (String(err.message).includes('HTTP 404')) {
90
+ console.log(`[zcompress] ⚠ Release asset not found for v${VERSION}: ${binaryName}`);
91
+ console.log(`[zcompress] ⚠ Expected URL: ${url}`);
92
+ console.log('[zcompress] ⚠ This usually means the npm version was published before uploading binary assets to GitHub Releases.');
93
+ } else {
94
+ console.log(`[zcompress] ⚠ Could not download binary (${err.message}).`);
95
+ }
96
+ console.log('[zcompress] ℹ Build from source: cd zcompress && zig build -Doptimize=ReleaseFast');
88
97
  // Clean up partial download
89
98
  try { unlinkSync(destPath); } catch (_) { /* ignore */ }
90
99
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zcompress-vite-plugin",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "High-performance multi-threaded asset compression for Vite — powered by Zig",
5
5
  "type": "module",
6
6
  "main": "index.js",