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.
- package/README.md +33 -6
- package/build.mjs +111 -22
- 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**
|
|
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)
|
|
134
|
-
*
|
|
135
|
-
*
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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 (
|
|
54
|
-
else if (
|
|
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',
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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