@ziex/cli 0.1.0-dev.865
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 +195 -0
- package/app.d.ts +112 -0
- package/aws-lambda/index.d.ts +96 -0
- package/aws-lambda/index.js +126 -0
- package/bin/ziex +61 -0
- package/build.zig +5 -0
- package/build.zig.zon +7 -0
- package/cloudflare/app.d.ts +2 -0
- package/cloudflare/do.d.ts +48 -0
- package/cloudflare/index.d.ts +4 -0
- package/cloudflare/index.js +1707 -0
- package/cloudflare/kv.d.ts +2 -0
- package/cloudflare/worker.d.ts +3 -0
- package/index.d.ts +2 -0
- package/index.js +739 -0
- package/install.cjs +218 -0
- package/kv.d.ts +27 -0
- package/package.json +60 -0
- package/react/dom.d.ts +21 -0
- package/react/index.d.ts +2 -0
- package/react/index.js +144 -0
- package/react/types.d.ts +190 -0
- package/runtime.d.ts +70 -0
- package/vercel/index.d.ts +26 -0
- package/vercel/index.js +18 -0
- package/wasi.d.ts +61 -0
- package/wasm/core.d.ts +78 -0
- package/wasm/dom.d.ts +4 -0
- package/wasm/index.d.ts +46 -0
- package/wasm/index.js +895 -0
- package/wasm/init.d.ts +1 -0
- package/wasm/init.js +873 -0
- package/wasm/types.d.ts +1 -0
- package/wasm/wasi.d.ts +28 -0
- package/zx.d.ts +1 -0
package/install.cjs
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// Postinstall script for ziex npm package
|
|
2
|
+
// Resolves the native binary from platform-specific optionalDependencies
|
|
3
|
+
// or falls back to downloading from GitHub releases.
|
|
4
|
+
|
|
5
|
+
const { existsSync, mkdirSync, copyFileSync, chmodSync, createWriteStream, unlinkSync } = require("fs");
|
|
6
|
+
const { execSync } = require("child_process");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
const https = require("https");
|
|
9
|
+
const { createGunzip } = require("zlib");
|
|
10
|
+
|
|
11
|
+
const PLATFORM_MAP = {
|
|
12
|
+
darwin: {
|
|
13
|
+
arm64: { pkg: "@ziex/cli-darwin-arm64", target: "macos-aarch64" },
|
|
14
|
+
x64: { pkg: "@ziex/cli-darwin-x64", target: "macos-x64" },
|
|
15
|
+
},
|
|
16
|
+
linux: {
|
|
17
|
+
x64: { pkg: "@ziex/cli-linux-x64", target: "linux-x64" },
|
|
18
|
+
arm64: { pkg: "@ziex/cli-linux-arm64", target: "linux-aarch64" },
|
|
19
|
+
},
|
|
20
|
+
win32: {
|
|
21
|
+
x64: { pkg: "@ziex/cli-win32-x64", target: "windows-x64" },
|
|
22
|
+
arm64: { pkg: "@ziex/cli-win32-arm64", target: "windows-aarch64" },
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const GITHUB_REPO = "ziex-dev/ziex";
|
|
27
|
+
|
|
28
|
+
function getPlatformInfo() {
|
|
29
|
+
const os = process.platform;
|
|
30
|
+
const arch = process.arch;
|
|
31
|
+
const info = PLATFORM_MAP[os]?.[arch];
|
|
32
|
+
if (!info) {
|
|
33
|
+
throw new Error(`Unsupported platform: ${os}-${arch}`);
|
|
34
|
+
}
|
|
35
|
+
return info;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getVersion() {
|
|
39
|
+
const pkg = require("./package.json");
|
|
40
|
+
return pkg.version;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getBinaryName(target) {
|
|
44
|
+
const isWindows = process.platform === "win32";
|
|
45
|
+
return `zx-${target}${isWindows ? ".exe" : ""}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getOutputPath() {
|
|
49
|
+
const binDir = path.join(__dirname, "bin");
|
|
50
|
+
const isWindows = process.platform === "win32";
|
|
51
|
+
return path.join(binDir, isWindows ? "zx.exe" : "zx");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Try to resolve binary from optionalDependencies
|
|
55
|
+
function tryResolveFromOptionalDep(pkgName) {
|
|
56
|
+
try {
|
|
57
|
+
const pkgPath = require.resolve(`${pkgName}/package.json`);
|
|
58
|
+
const pkgDir = path.dirname(pkgPath);
|
|
59
|
+
const pkgJson = require(pkgPath);
|
|
60
|
+
const binName = pkgJson.bin?.zx || pkgJson.bin?.ziex;
|
|
61
|
+
if (binName) {
|
|
62
|
+
const binPath = path.join(pkgDir, binName);
|
|
63
|
+
if (existsSync(binPath)) {
|
|
64
|
+
return binPath;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Fallback: look for the binary directly
|
|
68
|
+
const isWindows = process.platform === "win32";
|
|
69
|
+
const possibleNames = isWindows ? ["zx.exe"] : ["zx"];
|
|
70
|
+
for (const name of possibleNames) {
|
|
71
|
+
const candidate = path.join(pkgDir, "bin", name);
|
|
72
|
+
if (existsSync(candidate)) return candidate;
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
// Package not installed
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Download from GitHub releases
|
|
81
|
+
function downloadFromGitHub(target, version) {
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
const isWindows = process.platform === "win32";
|
|
84
|
+
const ext = isWindows ? "zip" : "tar.gz";
|
|
85
|
+
const tag = version.startsWith("0.") ? `zx-v${version}` : `zx-v${version}`;
|
|
86
|
+
const url = `https://github.com/${GITHUB_REPO}/releases/download/${tag}/zx-${target}.${ext}`;
|
|
87
|
+
const latestUrl = `https://github.com/${GITHUB_REPO}/releases/latest/download/zx-${target}.${ext}`;
|
|
88
|
+
|
|
89
|
+
const binDir = path.join(__dirname, "bin");
|
|
90
|
+
mkdirSync(binDir, { recursive: true });
|
|
91
|
+
const archivePath = path.join(binDir, `zx-${target}.${ext}`);
|
|
92
|
+
|
|
93
|
+
console.log(`Downloading ziex binary for ${target}...`);
|
|
94
|
+
|
|
95
|
+
function download(downloadUrl, isRetry) {
|
|
96
|
+
const followRedirect = (url) => {
|
|
97
|
+
const proto = url.startsWith("https") ? https : require("http");
|
|
98
|
+
proto
|
|
99
|
+
.get(url, { headers: { "User-Agent": "ziex-npm" } }, (res) => {
|
|
100
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
101
|
+
followRedirect(res.headers.location);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (res.statusCode !== 200) {
|
|
105
|
+
if (!isRetry) {
|
|
106
|
+
// Retry with latest
|
|
107
|
+
download(latestUrl, true);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
reject(new Error(`Download failed with status ${res.statusCode}: ${downloadUrl}`));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const file = createWriteStream(archivePath);
|
|
115
|
+
res.pipe(file);
|
|
116
|
+
file.on("finish", () => {
|
|
117
|
+
file.close(() => resolve(archivePath));
|
|
118
|
+
});
|
|
119
|
+
})
|
|
120
|
+
.on("error", reject);
|
|
121
|
+
};
|
|
122
|
+
followRedirect(downloadUrl);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
download(url, false);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function extractTarGz(archivePath, targetBinaryName, outputPath) {
|
|
130
|
+
const binDir = path.dirname(outputPath);
|
|
131
|
+
// Use tar command (available on macOS and Linux)
|
|
132
|
+
execSync(`tar -xzf "${archivePath}" -C "${binDir}"`, { stdio: "pipe" });
|
|
133
|
+
const extractedPath = path.join(binDir, targetBinaryName);
|
|
134
|
+
if (existsSync(extractedPath)) {
|
|
135
|
+
copyFileSync(extractedPath, outputPath);
|
|
136
|
+
unlinkSync(extractedPath);
|
|
137
|
+
}
|
|
138
|
+
unlinkSync(archivePath);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function extractZip(archivePath, targetBinaryName, outputPath) {
|
|
142
|
+
const binDir = path.dirname(outputPath);
|
|
143
|
+
execSync(`unzip -o "${archivePath}" -d "${binDir}"`, { stdio: "pipe" });
|
|
144
|
+
const extractedPath = path.join(binDir, targetBinaryName);
|
|
145
|
+
if (existsSync(extractedPath)) {
|
|
146
|
+
copyFileSync(extractedPath, outputPath);
|
|
147
|
+
unlinkSync(extractedPath);
|
|
148
|
+
}
|
|
149
|
+
unlinkSync(archivePath);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function main() {
|
|
153
|
+
const { pkg: pkgName, target } = getPlatformInfo();
|
|
154
|
+
const outputPath = getOutputPath();
|
|
155
|
+
const binDir = path.dirname(outputPath);
|
|
156
|
+
mkdirSync(binDir, { recursive: true });
|
|
157
|
+
|
|
158
|
+
// Step 1: Try to resolve from optional dependency
|
|
159
|
+
const resolvedPath = tryResolveFromOptionalDep(pkgName);
|
|
160
|
+
if (resolvedPath) {
|
|
161
|
+
console.log(`Found ziex binary from ${pkgName}`);
|
|
162
|
+
copyFileSync(resolvedPath, outputPath);
|
|
163
|
+
chmodSync(outputPath, 0o755);
|
|
164
|
+
|
|
165
|
+
// Create ziex alias (symlink or copy)
|
|
166
|
+
createAliases(outputPath);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Step 2: Download from GitHub releases
|
|
171
|
+
console.log(`Platform package ${pkgName} not found, downloading from GitHub...`);
|
|
172
|
+
try {
|
|
173
|
+
const version = getVersion();
|
|
174
|
+
const archivePath = await downloadFromGitHub(target, version);
|
|
175
|
+
const binaryName = getBinaryName(target);
|
|
176
|
+
const isWindows = process.platform === "win32";
|
|
177
|
+
|
|
178
|
+
if (isWindows) {
|
|
179
|
+
extractZip(archivePath, binaryName, outputPath);
|
|
180
|
+
} else {
|
|
181
|
+
extractTarGz(archivePath, binaryName, outputPath);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
chmodSync(outputPath, 0o755);
|
|
185
|
+
createAliases(outputPath);
|
|
186
|
+
console.log("ziex binary installed successfully!");
|
|
187
|
+
} catch (err) {
|
|
188
|
+
console.error(`Failed to install ziex binary: ${err.message}`);
|
|
189
|
+
console.error("You can install it manually from: https://ziex.dev/install");
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function createAliases(zxPath) {
|
|
195
|
+
const binDir = path.dirname(zxPath);
|
|
196
|
+
const isWindows = process.platform === "win32";
|
|
197
|
+
const ziexPath = path.join(binDir, isWindows ? "ziex.exe" : "ziex");
|
|
198
|
+
|
|
199
|
+
// The main binary is `zx`, create `ziex` as a copy/symlink
|
|
200
|
+
try {
|
|
201
|
+
if (existsSync(ziexPath) && ziexPath !== zxPath) unlinkSync(ziexPath);
|
|
202
|
+
if (ziexPath !== zxPath) {
|
|
203
|
+
try {
|
|
204
|
+
const fs = require("fs");
|
|
205
|
+
fs.symlinkSync(path.basename(zxPath), ziexPath);
|
|
206
|
+
} catch {
|
|
207
|
+
copyFileSync(zxPath, ziexPath);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
} catch {
|
|
211
|
+
// Non-critical
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
main().catch((err) => {
|
|
216
|
+
console.error(err.message);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
});
|
package/kv.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface KVNamespace {
|
|
2
|
+
get(key: string): Promise<string | null>;
|
|
3
|
+
put(key: string, value: string, options?: {
|
|
4
|
+
expiration?: number;
|
|
5
|
+
expirationTtl?: number;
|
|
6
|
+
}): Promise<void>;
|
|
7
|
+
delete(key: string): Promise<void>;
|
|
8
|
+
list(options?: {
|
|
9
|
+
prefix?: string;
|
|
10
|
+
}): Promise<{
|
|
11
|
+
keys: {
|
|
12
|
+
name: string;
|
|
13
|
+
}[];
|
|
14
|
+
}>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* In-memory KV namespace. Used as the default shim on platforms that don't
|
|
18
|
+
* provide a real KV binding (e.g. Vercel). Data lives only for the lifetime
|
|
19
|
+
* of the isolate instance.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createMemoryKV(): KVNamespace;
|
|
22
|
+
/**
|
|
23
|
+
* Create a `__zx_kv` import object for use with `run({ kv: ... })`.
|
|
24
|
+
* Always returns a valid import object. When JSPI is unavailable all KV
|
|
25
|
+
* operations are stubbed (get → not-found, put/delete → success, list → []).
|
|
26
|
+
*/
|
|
27
|
+
export declare function createKVImports(bindings: Record<string, KVNamespace>, getMemory: () => WebAssembly.Memory): Record<string, unknown>;
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ziex/cli",
|
|
3
|
+
"version": "0.1.0-dev.865",
|
|
4
|
+
"description": "ZX is a framework for building web applications with Zig.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ziex": "bin/ziex",
|
|
9
|
+
"zx": "bin/ziex"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./index.js",
|
|
13
|
+
"./react": "./react/index.js",
|
|
14
|
+
"./wasm": "./wasm/index.js",
|
|
15
|
+
"./wasm/init": "./wasm/init.js",
|
|
16
|
+
"./cloudflare": "./cloudflare/index.js",
|
|
17
|
+
"./aws-lambda": "./aws-lambda/index.js",
|
|
18
|
+
"./vercel": "./vercel/index.js"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://ziex.dev",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/ziex-dev/ziex.git"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"zx",
|
|
27
|
+
"zig",
|
|
28
|
+
"web framework",
|
|
29
|
+
"jsx",
|
|
30
|
+
"react",
|
|
31
|
+
"server components",
|
|
32
|
+
"client components",
|
|
33
|
+
"server actions",
|
|
34
|
+
"client actions",
|
|
35
|
+
"server actions"
|
|
36
|
+
],
|
|
37
|
+
"author": "Nurul Huda (Apon) <me@nurulhudaapon.com>",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"scripts": {
|
|
40
|
+
"postinstall": "node install.js"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"react": {
|
|
44
|
+
"optional": true
|
|
45
|
+
},
|
|
46
|
+
"react-dom": {
|
|
47
|
+
"optional": true
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"optionalDependencies": {
|
|
51
|
+
"@ziex/cli-darwin-arm64": "0.1.0-dev.805",
|
|
52
|
+
"@ziex/cli-darwin-x64": "0.1.0-dev.805",
|
|
53
|
+
"@ziex/cli-linux-x64": "0.1.0-dev.805",
|
|
54
|
+
"@ziex/cli-linux-arm64": "0.1.0-dev.805",
|
|
55
|
+
"@ziex/cli-win32-x64": "0.1.0-dev.805",
|
|
56
|
+
"@ziex/cli-win32-arm64": "0.1.0-dev.805"
|
|
57
|
+
},
|
|
58
|
+
"module": "index.js",
|
|
59
|
+
"types": "index.d.ts"
|
|
60
|
+
}
|
package/react/dom.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ComponentMetadata } from "./types";
|
|
2
|
+
export type PreparedComponent = {
|
|
3
|
+
domNode: HTMLElement;
|
|
4
|
+
props: Record<string, any> & {
|
|
5
|
+
dangerouslySetInnerHTML?: {
|
|
6
|
+
__html: string;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
Component: (props: any) => React.ReactElement;
|
|
10
|
+
};
|
|
11
|
+
export declare function prepareComponent(component: ComponentMetadata): Promise<PreparedComponent>;
|
|
12
|
+
export declare function filterComponents(components: ComponentMetadata[]): ComponentMetadata[];
|
|
13
|
+
export type DiscoveredComponent = {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
props: Record<string, any>;
|
|
17
|
+
container: HTMLElement;
|
|
18
|
+
};
|
|
19
|
+
export declare function discoverComponents(): DiscoveredComponent[];
|
|
20
|
+
export type ComponentRegistry = Record<string, () => Promise<(props: any) => React.ReactElement>>;
|
|
21
|
+
export declare function hydrateAll(registry: ComponentRegistry, render: (container: HTMLElement, Component: (props: any) => React.ReactElement, props: Record<string, any>) => void): Promise<void>;
|
package/react/index.d.ts
ADDED
package/react/index.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, {
|
|
5
|
+
get: all[name],
|
|
6
|
+
enumerable: true,
|
|
7
|
+
configurable: true,
|
|
8
|
+
set: (newValue) => all[name] = () => newValue
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/react/dom.ts
|
|
13
|
+
function findCommentMarker(id) {
|
|
14
|
+
const startPrefix = `$${id} `;
|
|
15
|
+
const endMarker = `/$${id}`;
|
|
16
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT, null);
|
|
17
|
+
let startComment = null;
|
|
18
|
+
let endComment = null;
|
|
19
|
+
let name = "";
|
|
20
|
+
let props = {};
|
|
21
|
+
let node;
|
|
22
|
+
while (node = walker.nextNode()) {
|
|
23
|
+
const text = node.textContent?.trim() || "";
|
|
24
|
+
if (text.startsWith(startPrefix)) {
|
|
25
|
+
startComment = node;
|
|
26
|
+
const content = text.slice(startPrefix.length);
|
|
27
|
+
const jsonStart = content.indexOf("{");
|
|
28
|
+
if (jsonStart !== -1) {
|
|
29
|
+
name = content.slice(0, jsonStart).trim();
|
|
30
|
+
const jsonStr = content.slice(jsonStart);
|
|
31
|
+
try {
|
|
32
|
+
props = JSON.parse(jsonStr);
|
|
33
|
+
} catch {}
|
|
34
|
+
} else {
|
|
35
|
+
name = content.trim();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (text === endMarker) {
|
|
39
|
+
endComment = node;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (startComment && endComment) {
|
|
44
|
+
return { startComment, endComment, name, props };
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
function createContainerBetweenMarkers(startComment, endComment) {
|
|
49
|
+
const container = document.createElement("div");
|
|
50
|
+
container.style.display = "contents";
|
|
51
|
+
let current = startComment.nextSibling;
|
|
52
|
+
while (current && current !== endComment) {
|
|
53
|
+
const next = current.nextSibling;
|
|
54
|
+
container.appendChild(current);
|
|
55
|
+
current = next;
|
|
56
|
+
}
|
|
57
|
+
endComment.parentNode?.insertBefore(container, endComment);
|
|
58
|
+
return container;
|
|
59
|
+
}
|
|
60
|
+
async function prepareComponent(component) {
|
|
61
|
+
const marker = findCommentMarker(component.id);
|
|
62
|
+
if (!marker) {
|
|
63
|
+
throw new Error(`Comment marker for ${component.id} not found`, { cause: component });
|
|
64
|
+
}
|
|
65
|
+
const props = marker.props;
|
|
66
|
+
const domNode = createContainerBetweenMarkers(marker.startComment, marker.endComment);
|
|
67
|
+
const Component = await component.import();
|
|
68
|
+
return { domNode, props, Component };
|
|
69
|
+
}
|
|
70
|
+
function filterComponents(components) {
|
|
71
|
+
const currentPath = window.location.pathname;
|
|
72
|
+
return components.filter((component) => component.route === currentPath || !component.route);
|
|
73
|
+
}
|
|
74
|
+
function discoverComponents() {
|
|
75
|
+
const components = [];
|
|
76
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT, null);
|
|
77
|
+
const markers = [];
|
|
78
|
+
let node;
|
|
79
|
+
while (node = walker.nextNode()) {
|
|
80
|
+
const text = node.textContent?.trim() || "";
|
|
81
|
+
if (text.startsWith("$") && !text.startsWith("/$")) {
|
|
82
|
+
const spaceIdx = text.indexOf(" ");
|
|
83
|
+
if (spaceIdx !== -1) {
|
|
84
|
+
const id = text.slice(1, spaceIdx);
|
|
85
|
+
const content = text.slice(spaceIdx + 1);
|
|
86
|
+
if (content.startsWith("[")) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const jsonStart = content.indexOf("{");
|
|
90
|
+
let name = "";
|
|
91
|
+
let props = {};
|
|
92
|
+
if (jsonStart !== -1) {
|
|
93
|
+
name = content.slice(0, jsonStart).trim();
|
|
94
|
+
try {
|
|
95
|
+
props = JSON.parse(content.slice(jsonStart));
|
|
96
|
+
} catch {}
|
|
97
|
+
} else {
|
|
98
|
+
name = content.trim();
|
|
99
|
+
}
|
|
100
|
+
markers.push({ id, name, props, startComment: node, endComment: null });
|
|
101
|
+
}
|
|
102
|
+
} else if (text.startsWith("/$")) {
|
|
103
|
+
const id = text.slice(2);
|
|
104
|
+
const marker = markers.find((m) => m.id === id && !m.endComment);
|
|
105
|
+
if (marker) {
|
|
106
|
+
marker.endComment = node;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
for (const marker of markers) {
|
|
111
|
+
if (!marker.endComment)
|
|
112
|
+
continue;
|
|
113
|
+
const container = createContainerBetweenMarkers(marker.startComment, marker.endComment);
|
|
114
|
+
components.push({
|
|
115
|
+
id: marker.id,
|
|
116
|
+
name: marker.name,
|
|
117
|
+
props: marker.props,
|
|
118
|
+
container
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return components;
|
|
122
|
+
}
|
|
123
|
+
async function hydrateAll(registry, render) {
|
|
124
|
+
const components = discoverComponents();
|
|
125
|
+
await Promise.all(components.map(async ({ name, props, container }) => {
|
|
126
|
+
const importer = registry[name];
|
|
127
|
+
if (!importer) {
|
|
128
|
+
console.warn(`Component "${name}" not found in registry`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const Component = await importer();
|
|
133
|
+
render(container, Component, props);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.error(`Failed to hydrate "${name}":`, error);
|
|
136
|
+
}
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
export {
|
|
140
|
+
prepareComponent,
|
|
141
|
+
hydrateAll,
|
|
142
|
+
filterComponents,
|
|
143
|
+
discoverComponents
|
|
144
|
+
};
|
package/react/types.d.ts
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata for a client-side component used within a ZX file.
|
|
3
|
+
*
|
|
4
|
+
* This type represents the metadata for components that are marked with the `@rendering` attribute
|
|
5
|
+
* in ZX files. When a component is declared with `@rendering={.react}` or `@rendering={.client}` in a
|
|
6
|
+
* `.zx` file, the ZX transpiler generates a `ComponentMetadata` entry that is included in the
|
|
7
|
+
* generated `components` array.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* // In a ZX file (page.zx):
|
|
12
|
+
* <CounterComponent @rendering={.react} max_count={10} />
|
|
13
|
+
*
|
|
14
|
+
* // Generated components array (components.ts):
|
|
15
|
+
* export const components: ComponentMetadata[] = [
|
|
16
|
+
* {
|
|
17
|
+
* name: "CounterComponent",
|
|
18
|
+
* path: "./components/CounterComponent.tsx",
|
|
19
|
+
* id: "zx-dcde04c415da9d1b15ca2690d8b497ae",
|
|
20
|
+
* type: "react",
|
|
21
|
+
* import: () => import('./components/CounterComponent.tsx')
|
|
22
|
+
* }
|
|
23
|
+
* ];
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```tsx
|
|
28
|
+
* // Using ComponentMetadata with prepareComponent:
|
|
29
|
+
* import { prepareComponent, type ComponentMetadata } from "ziex";
|
|
30
|
+
*
|
|
31
|
+
* for (const component of components) {
|
|
32
|
+
* const { domNode, props, Component } = await prepareComponent(component);
|
|
33
|
+
* // Render component to DOM node
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export type ComponentMetadata = {
|
|
38
|
+
/**
|
|
39
|
+
* The name of the component as declared in the ZX file.
|
|
40
|
+
*
|
|
41
|
+
* This is the tag name used in JSX syntax within `.zx` files. It corresponds to the component
|
|
42
|
+
* identifier used in the component declaration (e.g., `<CounterComponent />`).
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```tsx
|
|
46
|
+
* // In ZX file:
|
|
47
|
+
* <CounterComponent @rendering={.react} />
|
|
48
|
+
*
|
|
49
|
+
* // name will be: "CounterComponent"
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
name: string;
|
|
53
|
+
/**
|
|
54
|
+
* The file path to the component module.
|
|
55
|
+
*
|
|
56
|
+
* This is the relative or absolute path to the component file that will be dynamically imported
|
|
57
|
+
* at runtime. For CSR components, this typically points to a `.tsx` or `.jsx` file. For Client
|
|
58
|
+
* components, this points to a Zig component file.
|
|
59
|
+
*
|
|
60
|
+
* The path is determined from the `@jsImport` directive in the ZX file, or defaults to
|
|
61
|
+
* `./{componentName}.tsx` if not explicitly specified.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```tsx
|
|
65
|
+
* // In ZX file:
|
|
66
|
+
* const CounterComponent = @jsImport("components/Counter.tsx");
|
|
67
|
+
* <CounterComponent @rendering={.react} />
|
|
68
|
+
*
|
|
69
|
+
* // path will be: "components/Counter.tsx"
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```tsx
|
|
74
|
+
* // Without explicit @jsImport:
|
|
75
|
+
* <MyComponent @rendering={.react} />
|
|
76
|
+
*
|
|
77
|
+
* // path will default to: "./MyComponent.tsx"
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
path: string;
|
|
81
|
+
/**
|
|
82
|
+
* The route of of the component in which page the component was used,
|
|
83
|
+
* null in case the component was used in other non-page/layout context.
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
route: string | null;
|
|
87
|
+
/**
|
|
88
|
+
* A unique identifier for the component's hydration boundary.
|
|
89
|
+
*
|
|
90
|
+
* This ID is generated by hashing the component's path and name using MD5, then formatting it
|
|
91
|
+
* as a hex string with the "zx-" prefix and a counter suffix. The ID is used to locate the
|
|
92
|
+
* component's comment markers in the DOM during client-side hydration.
|
|
93
|
+
*
|
|
94
|
+
* The ID format is: `zx-{32 hex characters}-{counter}` (e.g., `zx-dcde04c415da9d1b15ca2690d8b497ae-0`)
|
|
95
|
+
*
|
|
96
|
+
* The ZX runtime renders components with comment markers in the format:
|
|
97
|
+
* `<!--$id name props-->...<!--/$id-->`
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```tsx
|
|
101
|
+
* // Component metadata:
|
|
102
|
+
* {
|
|
103
|
+
* name: "CounterComponent",
|
|
104
|
+
* path: "./components/Counter.tsx",
|
|
105
|
+
* id: "zx-dcde04c415da9d1b15ca2690d8b497ae-0"
|
|
106
|
+
* }
|
|
107
|
+
*
|
|
108
|
+
* // Generated HTML with comment markers:
|
|
109
|
+
* <!--$zx-dcde04c415da9d1b15ca2690d8b497ae-0 CounterComponent {"max_count":10}-->
|
|
110
|
+
* <button>0</button>
|
|
111
|
+
* <!--/$zx-dcde04c415da9d1b15ca2690d8b497ae-0-->
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
id: string;
|
|
115
|
+
/**
|
|
116
|
+
* The rendering type of the component, determining how it will be rendered on the client.
|
|
117
|
+
*
|
|
118
|
+
* - **"react"** (Client Side React): The component is a React component that will be rendered
|
|
119
|
+
* using React's client-side rendering. The component file should export a default React
|
|
120
|
+
* component function. This is the most common type for interactive UI components.
|
|
121
|
+
*
|
|
122
|
+
* - **"client"** (Client Side Zig): The component is a Zig component that will be compiled to
|
|
123
|
+
* WebAssembly and rendered on the client side. This allows you to use Zig's performance
|
|
124
|
+
* and type safety for client-side components.
|
|
125
|
+
*
|
|
126
|
+
* The type is determined by the `@rendering` attribute value in the ZX file (`.react` or `.client`).
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```tsx
|
|
130
|
+
* // CSR component (React):
|
|
131
|
+
* <CounterComponent @rendering={.react} max_count={10} />
|
|
132
|
+
* // type: "react"
|
|
133
|
+
*
|
|
134
|
+
* // Component file (CounterComponent.tsx):
|
|
135
|
+
* export default function CounterComponent({ max_count }: { max_count: number }) {
|
|
136
|
+
* return <div>Count: {max_count}</div>;
|
|
137
|
+
* }
|
|
138
|
+
* ```
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```tsx
|
|
142
|
+
* // Client component (Zig/WASM):
|
|
143
|
+
* <CounterComponent @rendering={.client} />
|
|
144
|
+
* // type: "client"
|
|
145
|
+
*
|
|
146
|
+
* // Component file (CounterComponent.zig):
|
|
147
|
+
* pub fn CounterComponent(allocator: zx.Allocator) zx.Component {
|
|
148
|
+
* return (<div @allocator={allocator}>Counter</div>);
|
|
149
|
+
* }
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
type: "react" | "client";
|
|
153
|
+
/**
|
|
154
|
+
* A lazy-loading function that dynamically imports the component module.
|
|
155
|
+
*
|
|
156
|
+
* This function returns a Promise that resolves to the component function. It enables
|
|
157
|
+
* code-splitting and lazy loading of components, improving initial page load performance
|
|
158
|
+
* by only loading components when they are needed.
|
|
159
|
+
*
|
|
160
|
+
* For CSR components, the imported module should export a default React component.
|
|
161
|
+
* For Client components, the import mechanism depends on the WASM module structure.
|
|
162
|
+
*
|
|
163
|
+
* The function is called during client-side hydration to load and render the component
|
|
164
|
+
* into its corresponding DOM container element.
|
|
165
|
+
*
|
|
166
|
+
* @returns A Promise that resolves to a component function that accepts props and returns a React element.
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* ```tsx
|
|
170
|
+
* // Component metadata:
|
|
171
|
+
* {
|
|
172
|
+
* import: () => import('./components/CounterComponent.tsx')
|
|
173
|
+
* }
|
|
174
|
+
*
|
|
175
|
+
* // Usage:
|
|
176
|
+
* const Component = await component.import();
|
|
177
|
+
* // Component is now the default export from CounterComponent.tsx
|
|
178
|
+
* ```
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```tsx
|
|
182
|
+
* // With prepareComponent helper:
|
|
183
|
+
* import { prepareComponent } from "ziex";
|
|
184
|
+
*
|
|
185
|
+
* const { Component, props, domNode } = await prepareComponent(component);
|
|
186
|
+
* // Component is loaded and ready to render with props
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
import: () => Promise<(props: any) => React.ReactElement>;
|
|
190
|
+
};
|