quickjs-zig 1.0.1 → 1.0.3

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.
Files changed (3) hide show
  1. package/README.md +33 -6
  2. package/build.mjs +111 -22
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -5,9 +5,10 @@ A high-performance build system for **QuickJS**, powered by the **Zig** compiler
5
5
  ## Features
6
6
 
7
7
  * **Zero Config Cross-Compilation**: Build for Windows, Linux, and macOS from any host (Intel or Apple Silicon).
8
- * **Zig-Powered**: Uses **Zig 0.15.2** as a C compiler for modern, safe, and highly optimized binaries.
8
+ * **Zig-Powered Optimization**: Uses **Zig 0.15.2** with LTO (Link Time Optimization) to produce small binaries.
9
9
  * **Custom C Modules**: Easily inject and register your own C modules into the QuickJS engine.
10
10
  * **Native Windows Support**: Includes a custom `exec` implementation for Windows, bypassing typical QuickJS POSIX limitations.
11
+ * **Platform-Specific Swapping**: Automatically replaces generic JS files with platform-specific ones (e.g., `index.mjs` → `index.darwin.mjs`) during build.
11
12
  * **Clean Source Management**: Automatically patches and restores QuickJS source files to keep the core library pristine.
12
13
 
13
14
  ---
@@ -35,17 +36,40 @@ npm link
35
36
 
36
37
  ```
37
38
 
39
+ ---
40
+
41
+ ## Platform-Specific File Swapping
42
+
43
+ The build system supports platform-specific file resolution. This is useful when you need different JS logic for different operating systems while maintaining a single development entry point for IDE completion.
44
+
45
+ ### How it works
46
+
47
+ 1. **Generic file**: Create a base file (e.g., `index.mjs` or `dialogs.mjs`). This is your reference for IDE completion and IntelliSense.
48
+ 2. **Specific files**: Create files with the platform suffix:
49
+ * `filename.win32.mjs`
50
+ * `filename.darwin.mjs`
51
+ * `filename.linux.mjs`
52
+
53
+
54
+ 3. **Build Logic**:
55
+ * The `build/` folder is cleaned at the start of each execution.
56
+ * If a platform-specific version exists, the generic version is **excluded** from the build folder to avoid duplicates.
57
+ * The script automatically rewrites `import` statements in your code to point to the correct suffix during the build process.
58
+
59
+
60
+
38
61
  ---
39
62
 
40
63
  ## Configuration
41
64
 
42
- Configure your entry point and custom C modules in your project's `package.json`:
65
+ Configure your entry point, optimization, and custom C modules in your project's `package.json`:
43
66
 
44
67
  ```json
45
68
  {
46
69
  "name": "my-app",
47
70
  "quickJs": {
48
71
  "input": "app/index.mjs",
72
+ "optimization": true,
49
73
  "modules": {
50
74
  "my_module": "src/my_module.c"
51
75
  }
@@ -55,8 +79,11 @@ Configure your entry point and custom C modules in your project's `package.json`
55
79
  ```
56
80
 
57
81
  * **`input`**: The entry point of your JavaScript application (defaults to `app/index.mjs`).
82
+ * **`optimization`**: When `true`, enables **LTO**, aggressive inlining (`-O3`), and strips unused features like `eval` or `Promises` via `qjsc` flags.
58
83
  * **`modules`**: A key-value map of custom C modules (Module Name -> C Source Path).
59
84
 
85
+ **Note**: The system always applies `-s` (strip symbols) regardless of the optimization flag to ensure no `.pdb` or debug tables are generated.
86
+
60
87
  ---
61
88
 
62
89
  ## Example Usage
@@ -130,9 +157,9 @@ The build system generates binaries for the following platforms in the `dist/` f
130
157
 
131
158
  Zig is not just a language; it's a powerful C/C++ toolchain. It allows `quickjs-zig` to:
132
159
 
133
- * Cross-compile to Windows (MinGW) from macOS/Linux without installing complex toolchains.
134
- * Provide a consistent `libc` environment across different platforms.
135
- * Produce small, fast, and statically linked binaries.
160
+ * **Cross-compile** to Windows (MinGW) or Linux from any host without installing complex toolchains.
161
+ * **Size matters**: By using `-flto` and `-O3`, Zig can discard unused parts of the engine, bringing the footprint down by nearly 95% compared to Node.js standalone binaries.
162
+ * **macOS LTO Support**: On Apple targets, we automatically force `-fuse-ld=lld` when optimized to ensure the LLVM Linker handles the bitcode correctly.
136
163
 
137
164
  ### Windows Patching
138
165
 
@@ -144,4 +171,4 @@ QuickJS is designed for POSIX systems. This tool automatically patches `quickjs-
144
171
 
145
172
  MIT - Created by **eid-app**.
146
173
 
147
- ---
174
+ ---
package/build.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { execSync } from 'child_process';
2
- import { writeFileSync, mkdirSync, unlinkSync, existsSync, readdirSync, cpSync, readFileSync, renameSync } from 'fs';
2
+ import { writeFileSync, mkdirSync, unlinkSync, existsSync, readdirSync, cpSync, readFileSync, renameSync, statSync, rmSync } from 'fs';
3
3
  import path from 'path';
4
4
  import os from 'os';
5
5
  import { fileURLToPath } from 'url';
@@ -7,6 +7,13 @@ import { fileURLToPath } from 'url';
7
7
  // --- PATH CONFIGURATION ---
8
8
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
9
  const USER_CWD = process.cwd();
10
+ const PLATFORM = os.platform();
11
+
12
+ // Clean global build directory at the very beginning
13
+ const GLOBAL_BUILD_DIR = path.join(USER_CWD, 'build');
14
+ if (existsSync(GLOBAL_BUILD_DIR)) {
15
+ rmSync(GLOBAL_BUILD_DIR, { recursive: true });
16
+ }
10
17
 
11
18
  // Load the user project's package.json
12
19
  const USER_PKG_PATH = path.join(USER_CWD, 'package.json');
@@ -19,13 +26,71 @@ const userPackageJson = JSON.parse(readFileSync(USER_PKG_PATH, 'utf8'));
19
26
  const customModules = userPackageJson.quickJs?.modules || {};
20
27
  const APP_NAME = userPackageJson.name || 'app';
21
28
 
29
+ // Optimization flag from package.json
30
+ const IS_OPTIMIZED = userPackageJson.quickJs?.optimization === true;
31
+
22
32
  // Input file from package.json or default to app/index.mjs
23
33
  const INPUT_FILE_RELATIVE = userPackageJson.quickJs?.input || 'app/index.mjs';
24
- const INPUT_FILE_ABS = path.resolve(USER_CWD, INPUT_FILE_RELATIVE);
25
34
 
26
- if (!existsSync(INPUT_FILE_ABS)) {
27
- console.error(`❌ Error: Input file not found at ${INPUT_FILE_ABS}`);
28
- process.exit(1);
35
+ /**
36
+ * Recursively scan and transform imports to platform-specific ones.
37
+ * Filters out files from other platforms and ensures specific versions
38
+ * replace generic ones (like index.mjs) in the build folder.
39
+ */
40
+ function processDirectory(currentDir, targetDir, targetPlat) {
41
+ if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true });
42
+
43
+ const filesInSource = readdirSync(currentDir);
44
+
45
+ filesInSource.forEach(file => {
46
+ const fullPath = path.join(currentDir, file);
47
+ const destPath = path.join(targetDir, file);
48
+
49
+ if (statSync(fullPath).isDirectory()) {
50
+ return processDirectory(fullPath, destPath, targetPlat);
51
+ }
52
+
53
+ const isMjs = file.endsWith('.mjs') || file.endsWith('.js');
54
+ const knownPlats = ['win32', 'darwin', 'linux'];
55
+
56
+ if (isMjs) {
57
+ const parts = file.split('.');
58
+ const filePlat = parts.length > 2 ? parts[parts.length - 2] : null;
59
+
60
+ // 1. Skip files belonging to OTHER platforms
61
+ if (filePlat && knownPlats.includes(filePlat) && filePlat !== targetPlat) {
62
+ return;
63
+ }
64
+
65
+ // 2. Logic for generic files (like index.mjs):
66
+ // If a specific version (index.darwin.mjs) exists, we skip the generic one.
67
+ if (!filePlat || !knownPlats.includes(filePlat)) {
68
+ const specFile = file.replace(/\.mjs$/, `.${targetPlat}.mjs`);
69
+ if (filesInSource.includes(specFile)) {
70
+ return;
71
+ }
72
+ }
73
+
74
+ let content = readFileSync(fullPath, 'utf8');
75
+
76
+ // 3. Transform generic imports to platform-specific ones if they exist physically
77
+ content = content.replace(/(import\s+.+?\s+from\s+['"])(.+?)\.mjs(['"])/g, (match, before, importPath, after) => {
78
+ const platFile = `${importPath}.${targetPlat}.mjs`;
79
+ const platformFullPath = path.resolve(currentDir, platFile);
80
+
81
+ if (existsSync(platformFullPath)) {
82
+ console.log(`✨ [${targetPlat}] Swapping import: ${importPath}.mjs -> ${platFile}`);
83
+ return `${before}${importPath}.${targetPlat}.mjs${after}`;
84
+ }
85
+ return match;
86
+ });
87
+
88
+ writeFileSync(destPath, content);
89
+ } else {
90
+ // Copy assets and other files as is
91
+ cpSync(fullPath, destPath);
92
+ }
93
+ });
29
94
  }
30
95
 
31
96
  const QUICKJS_DIR = path.resolve(__dirname, 'quickjs');
@@ -46,13 +111,11 @@ if (!existsSync(BIN_DIR)) mkdirSync(BIN_DIR, { recursive: true });
46
111
  if (!existsSync(DIST_DIR)) mkdirSync(DIST_DIR, { recursive: true });
47
112
 
48
113
  // Detect the host architecture to find the correct qjsc binary
49
- const platform = os.platform();
50
114
  const arch = os.arch();
51
115
  let hostQjscName = '';
52
-
53
- if (platform === 'darwin') hostQjscName = (arch === 'arm64') ? 'qjsc_mac_arm' : 'qjsc_mac_intel';
54
- else if (platform === 'linux') hostQjscName = (arch === 'arm64' || arch === 'aarch64') ? 'qjsc_linux_arm' : 'qjsc_linux64';
55
- else if (platform === 'win32') hostQjscName = 'qjsc_win64.exe';
116
+ if (PLATFORM === 'darwin') hostQjscName = (arch === 'arm64') ? 'qjsc_mac_arm' : 'qjsc_mac_intel';
117
+ else if (PLATFORM === 'linux') hostQjscName = (arch === 'arm64' || arch === 'aarch64') ? 'qjsc_linux_arm' : 'qjsc_linux64';
118
+ else if (PLATFORM === 'win32') hostQjscName = 'qjsc_win64.exe';
56
119
 
57
120
  const hostQjscPath = path.join(BIN_DIR, hostQjscName);
58
121
 
@@ -114,13 +177,13 @@ const baseSources = ['quickjs.c', 'libregexp.c', 'libunicode.c', 'cutils.c', 'qu
114
177
  .join(' ');
115
178
 
116
179
  const targets = [
117
- { id: 'x86_64-windows-gnu', qjs: 'qjs_win64.exe', qjsc: 'qjsc_win64.exe', app: `${APP_NAME}_win64.exe`, libs: '-lm', cflags: '-D_GNU_SOURCE' },
118
- { id: 'x86-windows-gnu', qjs: 'qjs_win32.exe', qjsc: 'qjsc_win32.exe', app: `${APP_NAME}_win32.exe`, libs: '-lm', cflags: '-D_GNU_SOURCE' },
119
- { id: 'x86_64-linux-gnu', qjs: 'qjs_linux64', qjsc: 'qjsc_linux64', app: `${APP_NAME}_linux64`, libs: '-lm -lpthread -ldl', cflags: '-D_GNU_SOURCE -DCONFIG_PTHREAD' },
120
- { id: 'x86-linux-gnu', qjs: 'qjs_linux32', qjsc: 'qjsc_linux32', app: `${APP_NAME}_linux32`, libs: '-lm -lpthread -ldl', cflags: '-D_GNU_SOURCE -DCONFIG_PTHREAD' },
121
- { id: 'aarch64-linux-gnu', qjs: 'qjs_linux_arm64', qjsc: 'qjsc_linux_arm64', app: `${APP_NAME}_linux_arm64`, libs: '-lm -lpthread -ldl', cflags: '-D_GNU_SOURCE -DCONFIG_PTHREAD' },
122
- { id: 'aarch64-macos', qjs: 'qjs_mac_arm', qjsc: 'qjsc_mac_arm', app: `${APP_NAME}_mac_arm`, libs: '-lm -lpthread -ldl', cflags: '-D_GNU_SOURCE -DCONFIG_PTHREAD' },
123
- { id: 'x86_64-macos', qjs: 'qjs_mac_intel', qjsc: 'qjsc_mac_intel', app: `${APP_NAME}_mac_intel`, libs: '-lm -lpthread -ldl', cflags: '-D_GNU_SOURCE -DCONFIG_PTHREAD' }
180
+ { id: 'x86_64-windows-gnu', qjs: 'qjs_win64.exe', qjsc: 'qjsc_win64.exe', app: `${APP_NAME}_win64.exe`, libs: '-lm', cflags: '-D_GNU_SOURCE', plat: 'win32' },
181
+ { id: 'x86-windows-gnu', qjs: 'qjs_win32.exe', qjsc: 'qjsc_win32.exe', app: `${APP_NAME}_win32.exe`, libs: '-lm', cflags: '-D_GNU_SOURCE', plat: 'win32' },
182
+ { id: 'x86_64-linux-gnu', qjs: 'qjs_linux64', qjsc: 'qjsc_linux64', app: `${APP_NAME}_linux64`, libs: '-lm -lpthread -ldl', cflags: '-D_GNU_SOURCE -DCONFIG_PTHREAD', plat: 'linux' },
183
+ { id: 'x86-linux-gnu', qjs: 'qjs_linux32', qjsc: 'qjsc_linux32', app: `${APP_NAME}_linux32`, libs: '-lm -lpthread -ldl', cflags: '-D_GNU_SOURCE -DCONFIG_PTHREAD', plat: 'linux' },
184
+ { id: 'aarch64-linux-gnu', qjs: 'qjs_linux_arm64', qjsc: 'qjsc_linux_arm64', app: `${APP_NAME}_linux_arm64`, libs: '-lm -lpthread -ldl', cflags: '-D_GNU_SOURCE -DCONFIG_PTHREAD', plat: 'linux' },
185
+ { id: 'aarch64-macos', qjs: 'qjs_mac_arm', qjsc: 'qjsc_mac_arm', app: `${APP_NAME}_mac_arm`, libs: '-lm -lpthread -ldl', cflags: '-D_GNU_SOURCE -DCONFIG_PTHREAD', plat: 'darwin' },
186
+ { id: 'x86_64-macos', qjs: 'qjs_mac_intel', qjsc: 'qjsc_mac_intel', app: `${APP_NAME}_mac_intel`, libs: '-lm -lpthread -ldl', cflags: '-D_GNU_SOURCE -DCONFIG_PTHREAD', plat: 'darwin' }
124
187
  ];
125
188
 
126
189
  // ==========================================================
@@ -129,9 +192,33 @@ const targets = [
129
192
  const stubPath = path.join(QUICKJS_DIR, 'repl_stub.c');
130
193
  writeFileSync(stubPath, `const unsigned char qjsc_repl[] = {0}; const unsigned int qjsc_repl_size = 0;`);
131
194
 
195
+ // Feature optimization flags for qjsc
196
+ const qjscFlags = IS_OPTIMIZED ? '-fno-eval -fno-regexp -fno-proxy -fno-map -fno-typedarray -fno-promise' : '';
197
+
132
198
  targets.forEach(t => {
133
199
  console.log(`\n--- Compiling for: ${t.id} ---`);
134
- const cmdBase = `${ZIG_PATH} cc -target ${t.id} -I${QUICKJS_DIR} -O2 ${t.cflags} -Wno-ignored-attributes -DCONFIG_VERSION=\\"${VERSION}\\" ${t.libs} -s`;
200
+
201
+ // --- PLATFORM SPECIFIC BUILD RESOLUTION ---
202
+ const PLATFORM_BUILD_DIR = path.join(USER_CWD, 'build', t.id);
203
+ const inputBaseDir = path.dirname(path.resolve(USER_CWD, INPUT_FILE_RELATIVE));
204
+
205
+ if (existsSync(PLATFORM_BUILD_DIR)) {
206
+ rmSync(PLATFORM_BUILD_DIR, { recursive: true });
207
+ }
208
+
209
+ processDirectory(inputBaseDir, PLATFORM_BUILD_DIR, t.plat);
210
+
211
+ const TARGET_INPUT_ABS = path.join(PLATFORM_BUILD_DIR, path.basename(INPUT_FILE_RELATIVE));
212
+
213
+ // Dynamic Optimization Flags
214
+ let optFlags = IS_OPTIMIZED ? '-O3 -flto' : '-O2';
215
+
216
+ // -fuse-ld=lld is mandatory for macOS LTO, but causes warnings on other platforms
217
+ if (IS_OPTIMIZED && t.plat === 'darwin') {
218
+ optFlags += ' -fuse-ld=lld';
219
+ }
220
+
221
+ const cmdBase = `${ZIG_PATH} cc -target ${t.id} -I${QUICKJS_DIR} ${optFlags} ${t.cflags} -Wno-ignored-attributes -DCONFIG_VERSION=\\"${VERSION}\\" ${t.libs} -s`;
135
222
 
136
223
  try {
137
224
  execSync(`${cmdBase} -o "${path.join(BIN_DIR, t.qjs)}" ${baseSources} ${stubPath} "${path.join(QUICKJS_DIR, 'qjs.c')}"`);
@@ -139,12 +226,12 @@ targets.forEach(t => {
139
226
  console.log(`✅ Build tools generated.`);
140
227
 
141
228
  const tempC = path.join(BIN_DIR, `${t.id}_app.c`);
142
- const relativeInput = path.relative(USER_CWD, INPUT_FILE_ABS);
143
229
 
144
- execSync(`"${hostQjscPath}" -e -o "${tempC}" "${relativeInput}"`, { cwd: USER_CWD });
230
+ // Use host qjsc to compile the platform-resolved source
231
+ execSync(`"${hostQjscPath}" ${qjscFlags} -e -o "${tempC}" "${TARGET_INPUT_ABS}"`, { cwd: USER_CWD });
145
232
 
146
233
  execSync(`${cmdBase} -o "${path.join(DIST_DIR, t.app)}" "${tempC}" ${baseSources} -I${QUICKJS_DIR}`);
147
- console.log(`✅ Binary built: ${t.app}`);
234
+ console.log(`✅ Binary built${IS_OPTIMIZED ? ' and optimized' : ''}: ${t.app}`);
148
235
  } catch (e) {
149
236
  console.error(`❌ Compilation failed for ${t.id}`);
150
237
  console.error(e.stderr?.toString() || e.message);
@@ -172,4 +259,6 @@ binFiles.forEach(file => {
172
259
  }
173
260
  });
174
261
  if (existsSync(stubPath)) unlinkSync(stubPath);
175
- console.log("🚀 Build process complete.");
262
+
263
+ console.log(`🚀 Build process complete. (Optimization: ${IS_OPTIMIZED ? 'ON' : 'OFF'})`);
264
+ console.log("Platform sources kept in build/ subfolders.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quickjs-zig",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "A generic build engine to compile QuickJS scripts into standalone native binaries using Zig for multi-platform cross-compilation.",
5
5
  "type": "module",
6
6
  "main": "index.mjs",