hadars 0.4.2 → 0.4.3-rc.1
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 +53 -14
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/{chunk-2TMQUXFL.js → chunk-FZOSW2PU.js} +80 -18
- package/dist/{chunk-NYLXE7T7.js → chunk-W5ZSLBP3.js} +1 -1
- package/dist/cli.js +332 -226
- package/dist/cloudflare.cjs +85 -34
- package/dist/cloudflare.js +3 -2
- package/dist/index.js +2 -0
- package/dist/lambda.cjs +85 -34
- package/dist/lambda.js +3 -2
- package/dist/slim-react/index.cjs +105 -45
- package/dist/slim-react/index.d.cts +24 -2
- package/dist/slim-react/index.d.ts +24 -2
- package/dist/slim-react/index.js +4 -1
- package/dist/slim-react/jsx-runtime.js +1 -0
- package/dist/ssr-render-worker.js +88 -37
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -147,25 +147,8 @@ var SLIM_ELEMENT = Symbol.for("react.element");
|
|
|
147
147
|
var REACT19_ELEMENT = Symbol.for("react.transitional.element");
|
|
148
148
|
var FRAGMENT_TYPE = Symbol.for("react.fragment");
|
|
149
149
|
var SUSPENSE_TYPE = Symbol.for("react.suspense");
|
|
150
|
-
// src/slim-react/jsx.ts
|
|
151
|
-
function createElement(type, props, ...children) {
|
|
152
|
-
const normalizedProps = { ...props || {} };
|
|
153
|
-
if (children.length === 1) {
|
|
154
|
-
normalizedProps.children = children[0];
|
|
155
|
-
} else if (children.length > 1) {
|
|
156
|
-
normalizedProps.children = children;
|
|
157
|
-
}
|
|
158
|
-
const key = normalizedProps.key ?? null;
|
|
159
|
-
delete normalizedProps.key;
|
|
160
|
-
return {
|
|
161
|
-
$$typeof: SLIM_ELEMENT,
|
|
162
|
-
type,
|
|
163
|
-
props: normalizedProps,
|
|
164
|
-
key
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
150
|
// src/slim-react/renderContext.ts
|
|
151
|
+
import { createRequire as _nodeCreateRequire } from "node:module";
|
|
169
152
|
var MAP_KEY = "__slimReactContextMap";
|
|
170
153
|
var _g = globalThis;
|
|
171
154
|
if (!("__slimReactContextMap" in _g))
|
|
@@ -189,8 +172,7 @@ function getContextValue(context) {
|
|
|
189
172
|
const map = _g[MAP_KEY];
|
|
190
173
|
if (map && map.has(context))
|
|
191
174
|
return map.get(context);
|
|
192
|
-
|
|
193
|
-
return "_defaultValue" in c ? c._defaultValue : c._currentValue;
|
|
175
|
+
return context._currentValue;
|
|
194
176
|
}
|
|
195
177
|
function pushContextValue(context, value) {
|
|
196
178
|
let map = _g[MAP_KEY];
|
|
@@ -198,8 +180,7 @@ function pushContextValue(context, value) {
|
|
|
198
180
|
map = new Map;
|
|
199
181
|
_g[MAP_KEY] = map;
|
|
200
182
|
}
|
|
201
|
-
const
|
|
202
|
-
const prev = map.has(context) ? map.get(context) : ("_defaultValue" in c) ? c._defaultValue : c._currentValue;
|
|
183
|
+
const prev = map.has(context) ? map.get(context) : context._currentValue;
|
|
203
184
|
map.set(context, value);
|
|
204
185
|
return prev;
|
|
205
186
|
}
|
|
@@ -304,14 +285,54 @@ function getTreeId() {
|
|
|
304
285
|
const stripped = (id & ~(1 << 31 - Math.clz32(id))).toString(32);
|
|
305
286
|
return stripped + overflow;
|
|
306
287
|
}
|
|
288
|
+
var _detectReact = () => {
|
|
289
|
+
if (typeof __HADARS_REACT_MAJOR__ !== "undefined") {
|
|
290
|
+
const major = parseInt(String(__HADARS_REACT_MAJOR__), 10);
|
|
291
|
+
return {
|
|
292
|
+
major,
|
|
293
|
+
version: major < 19 ? "18.3.1" : "19.1.1"
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
const parse = (ver) => ({ major: parseInt(ver.split(".")[0], 10), version: ver });
|
|
297
|
+
try {
|
|
298
|
+
return parse(__require("react").version);
|
|
299
|
+
} catch {}
|
|
300
|
+
try {
|
|
301
|
+
const req = _nodeCreateRequire(process.cwd() + "/__hadars__.js");
|
|
302
|
+
return parse(req("react").version);
|
|
303
|
+
} catch {}
|
|
304
|
+
return { major: 19, version: "19.1.1" };
|
|
305
|
+
};
|
|
306
|
+
var _react = _detectReact();
|
|
307
|
+
var REACT_MAJOR = _react.major;
|
|
308
|
+
var REACT_VERSION = _react.version;
|
|
307
309
|
function makeId() {
|
|
308
310
|
const st = s();
|
|
309
311
|
const treeId = getTreeId();
|
|
310
312
|
const n = st.localIdCounter++;
|
|
311
|
-
|
|
312
|
-
if (
|
|
313
|
-
|
|
314
|
-
|
|
313
|
+
const suffix = n > 0 ? "H" + n.toString(32) : "";
|
|
314
|
+
if (REACT_MAJOR < 19) {
|
|
315
|
+
return ":" + st.idPrefix + "R" + treeId + suffix + ":";
|
|
316
|
+
}
|
|
317
|
+
return "_R_" + st.idPrefix + treeId + suffix + "_";
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/slim-react/jsx.ts
|
|
321
|
+
function createElement(type, props, ...children) {
|
|
322
|
+
const normalizedProps = { ...props || {} };
|
|
323
|
+
if (children.length === 1) {
|
|
324
|
+
normalizedProps.children = children[0];
|
|
325
|
+
} else if (children.length > 1) {
|
|
326
|
+
normalizedProps.children = children;
|
|
327
|
+
}
|
|
328
|
+
const key = normalizedProps.key ?? null;
|
|
329
|
+
delete normalizedProps.key;
|
|
330
|
+
return {
|
|
331
|
+
$$typeof: SLIM_ELEMENT,
|
|
332
|
+
type,
|
|
333
|
+
props: normalizedProps,
|
|
334
|
+
key
|
|
335
|
+
};
|
|
315
336
|
}
|
|
316
337
|
|
|
317
338
|
// src/slim-react/hooks.ts
|
|
@@ -364,8 +385,24 @@ function use(usable) {
|
|
|
364
385
|
}
|
|
365
386
|
// src/slim-react/dispatcher.ts
|
|
366
387
|
import * as ReactNS from "react";
|
|
367
|
-
var
|
|
368
|
-
var
|
|
388
|
+
var _r19;
|
|
389
|
+
var _r18;
|
|
390
|
+
var _detected = false;
|
|
391
|
+
var _k19 = "__CLIENT_INTERNALS_DO_NOT_USE" + "_OR_WARN_USERS_THEY_CANNOT_UPGRADE";
|
|
392
|
+
var _k18 = "__SECRET_INTERNALS_DO_NOT_USE" + "_OR_YOU_WILL_BE_FIRED";
|
|
393
|
+
function _detect() {
|
|
394
|
+
if (_detected)
|
|
395
|
+
return;
|
|
396
|
+
_detected = true;
|
|
397
|
+
const r19 = ReactNS[_k19];
|
|
398
|
+
if (r19) {
|
|
399
|
+
_r19 = r19;
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
const raw = ReactNS[_k18];
|
|
403
|
+
if (raw?.ReactCurrentDispatcher)
|
|
404
|
+
_r18 = raw;
|
|
405
|
+
}
|
|
369
406
|
var slimDispatcher = {
|
|
370
407
|
useId: makeId,
|
|
371
408
|
readContext: (ctx) => getContextValue(ctx),
|
|
@@ -391,15 +428,25 @@ var slimDispatcher = {
|
|
|
391
428
|
useHostTransitionStatus: () => false
|
|
392
429
|
};
|
|
393
430
|
function installDispatcher() {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
431
|
+
_detect();
|
|
432
|
+
if (_r19) {
|
|
433
|
+
const prev = _r19.H;
|
|
434
|
+
_r19.H = slimDispatcher;
|
|
435
|
+
return prev;
|
|
436
|
+
}
|
|
437
|
+
if (_r18) {
|
|
438
|
+
const prev = _r18.ReactCurrentDispatcher.current;
|
|
439
|
+
_r18.ReactCurrentDispatcher.current = slimDispatcher;
|
|
440
|
+
return prev;
|
|
441
|
+
}
|
|
442
|
+
return null;
|
|
399
443
|
}
|
|
400
444
|
function restoreDispatcher(prev) {
|
|
401
|
-
|
|
402
|
-
|
|
445
|
+
_detect();
|
|
446
|
+
if (_r19)
|
|
447
|
+
_r19.H = prev;
|
|
448
|
+
else if (_r18)
|
|
449
|
+
_r18.ReactCurrentDispatcher.current = prev;
|
|
403
450
|
}
|
|
404
451
|
|
|
405
452
|
// src/slim-react/render.ts
|
|
@@ -660,7 +707,7 @@ function writeAttributes(writer, props, isSvg, skip) {
|
|
|
660
707
|
if (isSvg && key in SVG_ATTR_MAP) {
|
|
661
708
|
attrName = SVG_ATTR_MAP[key];
|
|
662
709
|
} else {
|
|
663
|
-
attrName = key === "className" ? "class" : key === "htmlFor" ? "for" : key === "tabIndex" ? "tabindex" : key === "defaultValue" ? "value" : key === "defaultChecked" ? "checked" : key;
|
|
710
|
+
attrName = key === "className" ? "class" : key === "htmlFor" ? "for" : key === "tabIndex" ? "tabindex" : key === "defaultValue" ? "value" : key === "defaultChecked" ? "checked" : key === "contentEditable" && REACT_MAJOR < 19 ? "contenteditable" : key;
|
|
664
711
|
}
|
|
665
712
|
if (value === false || value == null) {
|
|
666
713
|
if (value === false && (attrName.charCodeAt(0) === 97 && attrName.startsWith("aria-") || attrName.charCodeAt(0) === 100 && attrName.startsWith("data-"))) {
|
|
@@ -871,7 +918,8 @@ function renderComponent(type, props, writer, isSvg, _suspenseRetries = 0) {
|
|
|
871
918
|
const LazyComp = resolved?.default ?? resolved;
|
|
872
919
|
return renderComponent(LazyComp, props, writer, isSvg);
|
|
873
920
|
}
|
|
874
|
-
|
|
921
|
+
const isConsumer = typeOf === REACT_CONSUMER || typeOf === REACT_CONTEXT && "_context" in type && !("value" in props);
|
|
922
|
+
if (isConsumer) {
|
|
875
923
|
const ctx2 = type._context;
|
|
876
924
|
const value = ctx2 ? getContextValue(ctx2) : undefined;
|
|
877
925
|
const result2 = typeof props.children === "function" ? props.children(value) : null;
|
|
@@ -2448,6 +2496,20 @@ var HadarsFolder = "./.hadars";
|
|
|
2448
2496
|
var StaticPath = `${HadarsFolder}/static`;
|
|
2449
2497
|
var HADARS_TMP_DIR = pathMod2.join(os.tmpdir(), "hadars");
|
|
2450
2498
|
var ensureHadarsTmpDir = () => fs.mkdir(HADARS_TMP_DIR, { recursive: true });
|
|
2499
|
+
var readReactMajor = async () => {
|
|
2500
|
+
let dir = process.cwd();
|
|
2501
|
+
while (true) {
|
|
2502
|
+
try {
|
|
2503
|
+
const pkgPath = pathMod2.join(dir, "node_modules", "react", "package.json");
|
|
2504
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, "utf-8"));
|
|
2505
|
+
return parseInt(pkg.version.split(".")[0], 10);
|
|
2506
|
+
} catch {}
|
|
2507
|
+
const parent = pathMod2.dirname(dir);
|
|
2508
|
+
if (parent === dir)
|
|
2509
|
+
return 19;
|
|
2510
|
+
dir = parent;
|
|
2511
|
+
}
|
|
2512
|
+
};
|
|
2451
2513
|
var validateOptions = (options) => {
|
|
2452
2514
|
if (!options.entry) {
|
|
2453
2515
|
throw new Error("Entry file is required");
|
|
@@ -2567,6 +2629,8 @@ var dev = async (options) => {
|
|
|
2567
2629
|
});
|
|
2568
2630
|
const workerCmd = resolveWorkerCmd(packageDir2);
|
|
2569
2631
|
console.log("Spawning SSR worker:", workerCmd.join(" "), "entry:", entry);
|
|
2632
|
+
const reactMajor = await readReactMajor();
|
|
2633
|
+
const ssrDefine = { __HADARS_REACT_MAJOR__: String(reactMajor), ...options.define };
|
|
2570
2634
|
const child = spawn(workerCmd[0], [
|
|
2571
2635
|
...workerCmd.slice(1),
|
|
2572
2636
|
`--entry=${entry}`,
|
|
@@ -2574,7 +2638,7 @@ var dev = async (options) => {
|
|
|
2574
2638
|
`--outFile=${SSR_FILENAME}`,
|
|
2575
2639
|
`--base=${baseURL}`,
|
|
2576
2640
|
...options.swcPlugins ? [`--swcPlugins=${JSON.stringify(options.swcPlugins)}`] : [],
|
|
2577
|
-
|
|
2641
|
+
`--define=${JSON.stringify(ssrDefine)}`,
|
|
2578
2642
|
...options.moduleRules ? [`--moduleRules=${JSON.stringify(options.moduleRules, (_k, v) => v instanceof RegExp ? { __re: v.source, __flags: v.flags } : v)}`] : []
|
|
2579
2643
|
], { stdio: "pipe" });
|
|
2580
2644
|
child.stdin?.end();
|
|
@@ -2748,6 +2812,7 @@ var build = async (options) => {
|
|
|
2748
2812
|
const tmpFilePath = pathMod2.join(HADARS_TMP_DIR, `client-${Date.now()}.tsx`);
|
|
2749
2813
|
await fs.writeFile(tmpFilePath, clientScript);
|
|
2750
2814
|
const resolvedHtmlTemplate = options.htmlTemplate ? await processHtmlTemplate(pathMod2.resolve(__dirname3, options.htmlTemplate)) : undefined;
|
|
2815
|
+
const reactMajor = await readReactMajor();
|
|
2751
2816
|
console.log("Building client and server bundles in parallel...");
|
|
2752
2817
|
await Promise.all([
|
|
2753
2818
|
compileEntry(tmpFilePath, {
|
|
@@ -2779,7 +2844,7 @@ var build = async (options) => {
|
|
|
2779
2844
|
target: "node",
|
|
2780
2845
|
mode: "production",
|
|
2781
2846
|
swcPlugins: options.swcPlugins,
|
|
2782
|
-
define: options.define,
|
|
2847
|
+
define: { __HADARS_REACT_MAJOR__: String(reactMajor), ...options.define },
|
|
2783
2848
|
moduleRules: options.moduleRules,
|
|
2784
2849
|
plugins: options.plugins,
|
|
2785
2850
|
postcssPlugins: options.postcssPlugins
|
|
@@ -3184,8 +3249,166 @@ Deploy instructions:`);
|
|
|
3184
3249
|
await unlink(shimPath).catch(() => {});
|
|
3185
3250
|
}
|
|
3186
3251
|
}
|
|
3187
|
-
var
|
|
3188
|
-
|
|
3252
|
+
var _R = "\x1B[0m";
|
|
3253
|
+
var _B = "\x1B[1m";
|
|
3254
|
+
var _D = "\x1B[2m";
|
|
3255
|
+
var _C = "\x1B[36m";
|
|
3256
|
+
var _G = "\x1B[32m";
|
|
3257
|
+
var _UP_KEY = "\x1B[A";
|
|
3258
|
+
var _DOWN_KEY = "\x1B[B";
|
|
3259
|
+
var _HIDE = "\x1B[?25l";
|
|
3260
|
+
var _SHOW = "\x1B[?25h";
|
|
3261
|
+
var _cl = () => "\r\x1B[2K";
|
|
3262
|
+
var _up = (n) => n > 0 ? `\x1B[${n}A` : "";
|
|
3263
|
+
function _readKeys(handler) {
|
|
3264
|
+
return new Promise((resolve2) => {
|
|
3265
|
+
const { stdin } = process;
|
|
3266
|
+
const wasRaw = stdin.isTTY && stdin.isRaw;
|
|
3267
|
+
if (stdin.isTTY)
|
|
3268
|
+
stdin.setRawMode(true);
|
|
3269
|
+
stdin.resume();
|
|
3270
|
+
stdin.setEncoding("utf-8");
|
|
3271
|
+
const onData = (key) => {
|
|
3272
|
+
if (key === "\x03") {
|
|
3273
|
+
cleanup();
|
|
3274
|
+
process.stdout.write(_SHOW);
|
|
3275
|
+
process.exit(130);
|
|
3276
|
+
}
|
|
3277
|
+
if (handler(key))
|
|
3278
|
+
cleanup();
|
|
3279
|
+
};
|
|
3280
|
+
const cleanup = () => {
|
|
3281
|
+
stdin.removeListener("data", onData);
|
|
3282
|
+
if (stdin.isTTY && !wasRaw)
|
|
3283
|
+
stdin.setRawMode(false);
|
|
3284
|
+
stdin.pause();
|
|
3285
|
+
resolve2();
|
|
3286
|
+
};
|
|
3287
|
+
stdin.on("data", onData);
|
|
3288
|
+
});
|
|
3289
|
+
}
|
|
3290
|
+
async function promptRadio(question, options) {
|
|
3291
|
+
const out = process.stdout;
|
|
3292
|
+
let cursor = 0;
|
|
3293
|
+
const total = 1 + options.length;
|
|
3294
|
+
const render = (redraw) => {
|
|
3295
|
+
if (redraw)
|
|
3296
|
+
out.write(_up(total));
|
|
3297
|
+
out.write(`${_cl()} ${_B}${question}${_R}
|
|
3298
|
+
`);
|
|
3299
|
+
for (let i = 0;i < options.length; i++) {
|
|
3300
|
+
const arrow = i === cursor ? `${_C}❯${_R}` : " ";
|
|
3301
|
+
const text = i === cursor ? `${_B}${options[i]}${_R}` : `${_D}${options[i]}${_R}`;
|
|
3302
|
+
out.write(`${_cl()} ${arrow} ${text}
|
|
3303
|
+
`);
|
|
3304
|
+
}
|
|
3305
|
+
};
|
|
3306
|
+
out.write(_HIDE);
|
|
3307
|
+
render(false);
|
|
3308
|
+
await _readKeys((key) => {
|
|
3309
|
+
if (key === _UP_KEY && cursor > 0) {
|
|
3310
|
+
cursor--;
|
|
3311
|
+
render(true);
|
|
3312
|
+
} else if (key === _DOWN_KEY && cursor < options.length - 1) {
|
|
3313
|
+
cursor++;
|
|
3314
|
+
render(true);
|
|
3315
|
+
} else if (key === "\r") {
|
|
3316
|
+
return true;
|
|
3317
|
+
}
|
|
3318
|
+
return false;
|
|
3319
|
+
});
|
|
3320
|
+
out.write(_up(total));
|
|
3321
|
+
out.write(`${_cl()} ${question} ${_C}${_B}${options[cursor]}${_R}
|
|
3322
|
+
`);
|
|
3323
|
+
for (let i = 0;i < options.length; i++)
|
|
3324
|
+
out.write(`${_cl()}
|
|
3325
|
+
`);
|
|
3326
|
+
out.write(_up(options.length));
|
|
3327
|
+
out.write(_SHOW);
|
|
3328
|
+
return cursor;
|
|
3329
|
+
}
|
|
3330
|
+
async function promptMultiSelect(question, options) {
|
|
3331
|
+
const out = process.stdout;
|
|
3332
|
+
let cursor = 0;
|
|
3333
|
+
const selected = new Set;
|
|
3334
|
+
const total = 2 + options.length;
|
|
3335
|
+
const render = (redraw) => {
|
|
3336
|
+
if (redraw)
|
|
3337
|
+
out.write(_up(total));
|
|
3338
|
+
out.write(`${_cl()} ${_B}${question}${_R}
|
|
3339
|
+
`);
|
|
3340
|
+
out.write(`${_cl()} ${_D}↑↓ navigate · Space select · Enter confirm${_R}
|
|
3341
|
+
`);
|
|
3342
|
+
for (let i = 0;i < options.length; i++) {
|
|
3343
|
+
const arrow = i === cursor ? `${_C}❯${_R}` : " ";
|
|
3344
|
+
const box = selected.has(i) ? `${_G}●${_R}` : `${_D}○${_R}`;
|
|
3345
|
+
const text = i === cursor ? `${_B}${options[i]}${_R}` : `${_D}${options[i]}${_R}`;
|
|
3346
|
+
out.write(`${_cl()} ${arrow} ${box} ${text}
|
|
3347
|
+
`);
|
|
3348
|
+
}
|
|
3349
|
+
};
|
|
3350
|
+
out.write(_HIDE);
|
|
3351
|
+
render(false);
|
|
3352
|
+
await _readKeys((key) => {
|
|
3353
|
+
if (key === _UP_KEY && cursor > 0) {
|
|
3354
|
+
cursor--;
|
|
3355
|
+
render(true);
|
|
3356
|
+
} else if (key === _DOWN_KEY && cursor < options.length - 1) {
|
|
3357
|
+
cursor++;
|
|
3358
|
+
render(true);
|
|
3359
|
+
} else if (key === " ") {
|
|
3360
|
+
selected.has(cursor) ? selected.delete(cursor) : selected.add(cursor);
|
|
3361
|
+
render(true);
|
|
3362
|
+
} else if (key === "\r") {
|
|
3363
|
+
return true;
|
|
3364
|
+
}
|
|
3365
|
+
return false;
|
|
3366
|
+
});
|
|
3367
|
+
const picked = [...selected].sort((a, b) => a - b);
|
|
3368
|
+
const summary = picked.length > 0 ? picked.map((i) => options[i]).join(", ") : "none";
|
|
3369
|
+
out.write(_up(total));
|
|
3370
|
+
out.write(`${_cl()} ${question} ${_C}${_B}${summary}${_R}
|
|
3371
|
+
`);
|
|
3372
|
+
for (let i = 0;i < total - 1; i++)
|
|
3373
|
+
out.write(`${_cl()}
|
|
3374
|
+
`);
|
|
3375
|
+
out.write(_up(total - 1));
|
|
3376
|
+
out.write(_SHOW);
|
|
3377
|
+
return picked;
|
|
3378
|
+
}
|
|
3379
|
+
var PLUGINS = [
|
|
3380
|
+
{ pkg: "@swc/plugin-emotion", version: "12.0.0", label: "Emotion (CSS-in-JS)" },
|
|
3381
|
+
{ pkg: "@swc/plugin-styled-components", version: "10.0.0", label: "styled-components" },
|
|
3382
|
+
{ pkg: "@swc/plugin-relay", version: "10.0.0", label: "Relay (GraphQL)" },
|
|
3383
|
+
{ pkg: "@swc/plugin-styled-jsx", version: "11.0.0", label: "styled-jsx" },
|
|
3384
|
+
{ pkg: "@swc/plugin-transform-imports", version: "10.0.0", label: "transform-imports" },
|
|
3385
|
+
{ pkg: "@swc/plugin-loadable-components", version: "9.0.0", label: "Loadable Components" },
|
|
3386
|
+
{ pkg: "@swc/plugin-formatjs", version: "7.0.0", label: "FormatJS (i18n)" }
|
|
3387
|
+
];
|
|
3388
|
+
function renderSwcPluginsConfig(plugins) {
|
|
3389
|
+
if (plugins.length === 0)
|
|
3390
|
+
return "";
|
|
3391
|
+
const lines = plugins.map((p) => {
|
|
3392
|
+
if (p.pkg === "@swc/plugin-relay") {
|
|
3393
|
+
return ` ['${p.pkg}', { rootDir: process.cwd(), artifactDirectory: 'src/__generated__' }],`;
|
|
3394
|
+
}
|
|
3395
|
+
return ` ['${p.pkg}', {}],`;
|
|
3396
|
+
});
|
|
3397
|
+
return `
|
|
3398
|
+
swcPlugins: [
|
|
3399
|
+
${lines.join(`
|
|
3400
|
+
`)}
|
|
3401
|
+
],`;
|
|
3402
|
+
}
|
|
3403
|
+
function buildTemplates(name, opts) {
|
|
3404
|
+
const { useTypeScript, plugins } = opts;
|
|
3405
|
+
const appExt = useTypeScript ? "tsx" : "jsx";
|
|
3406
|
+
const cfgExt = useTypeScript ? "ts" : "js";
|
|
3407
|
+
const tsOrJs = useTypeScript ? "tsconfig.json" : "jsconfig.json";
|
|
3408
|
+
const pluginDeps = {};
|
|
3409
|
+
for (const p of plugins)
|
|
3410
|
+
pluginDeps[p.pkg] = p.version;
|
|
3411
|
+
const packageJson = JSON.stringify({
|
|
3189
3412
|
name,
|
|
3190
3413
|
version: "0.1.0",
|
|
3191
3414
|
type: "module",
|
|
@@ -3199,19 +3422,28 @@ var TEMPLATES = {
|
|
|
3199
3422
|
hadars: "latest",
|
|
3200
3423
|
react: "^19.0.0",
|
|
3201
3424
|
"react-dom": "^19.0.0"
|
|
3202
|
-
}
|
|
3425
|
+
},
|
|
3426
|
+
...Object.keys(pluginDeps).length > 0 ? { devDependencies: pluginDeps } : {}
|
|
3203
3427
|
}, null, 2) + `
|
|
3204
|
-
|
|
3205
|
-
|
|
3428
|
+
`;
|
|
3429
|
+
const swcSection = renderSwcPluginsConfig(plugins);
|
|
3430
|
+
const hadarsConfig = useTypeScript ? `import type { HadarsOptions } from 'hadars';
|
|
3206
3431
|
|
|
3207
3432
|
const config: HadarsOptions = {
|
|
3208
3433
|
entry: 'src/App.tsx',
|
|
3209
|
-
port: 3000
|
|
3434
|
+
port: 3000,${swcSection}
|
|
3210
3435
|
};
|
|
3211
3436
|
|
|
3212
3437
|
export default config;
|
|
3213
|
-
|
|
3214
|
-
|
|
3438
|
+
` : `/** @type {import('hadars').HadarsOptions} */
|
|
3439
|
+
const config = {
|
|
3440
|
+
entry: 'src/App.jsx',
|
|
3441
|
+
port: 3000,${swcSection}
|
|
3442
|
+
};
|
|
3443
|
+
|
|
3444
|
+
export default config;
|
|
3445
|
+
`;
|
|
3446
|
+
const tsConfigContent = useTypeScript ? JSON.stringify({
|
|
3215
3447
|
compilerOptions: {
|
|
3216
3448
|
lib: ["ESNext", "DOM"],
|
|
3217
3449
|
target: "ESNext",
|
|
@@ -3226,215 +3458,89 @@ export default config;
|
|
|
3226
3458
|
skipLibCheck: true
|
|
3227
3459
|
}
|
|
3228
3460
|
}, null, 2) + `
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
}
|
|
3246
|
-
|
|
3247
|
-
.nav {
|
|
3248
|
-
display: flex;
|
|
3249
|
-
align-items: center;
|
|
3250
|
-
justify-content: space-between;
|
|
3251
|
-
padding: 1rem 2rem;
|
|
3252
|
-
border-bottom: 1px solid #1e1e2e;
|
|
3253
|
-
}
|
|
3254
|
-
.nav-brand { font-weight: 700; font-size: 1.1rem; color: #a78bfa; letter-spacing: -0.02em; }
|
|
3255
|
-
.nav-links { display: flex; gap: 1.5rem; }
|
|
3256
|
-
.nav-links a { color: #94a3b8; text-decoration: none; font-size: 0.9rem; }
|
|
3257
|
-
.nav-links a:hover { color: #e2e8f0; }
|
|
3258
|
-
|
|
3259
|
-
.hero {
|
|
3260
|
-
text-align: center;
|
|
3261
|
-
padding: 5rem 1rem 4rem;
|
|
3262
|
-
max-width: 680px;
|
|
3263
|
-
margin: 0 auto;
|
|
3264
|
-
}
|
|
3265
|
-
.hero-badge {
|
|
3266
|
-
display: inline-block;
|
|
3267
|
-
background: #1e1a2e;
|
|
3268
|
-
border: 1px solid #4c1d95;
|
|
3269
|
-
color: #a78bfa;
|
|
3270
|
-
font-size: 0.75rem;
|
|
3271
|
-
font-weight: 600;
|
|
3272
|
-
letter-spacing: 0.05em;
|
|
3273
|
-
text-transform: uppercase;
|
|
3274
|
-
padding: 0.3rem 0.8rem;
|
|
3275
|
-
border-radius: 999px;
|
|
3276
|
-
margin-bottom: 1.5rem;
|
|
3277
|
-
}
|
|
3278
|
-
.hero h1 {
|
|
3279
|
-
font-size: clamp(2rem, 5vw, 3.25rem);
|
|
3280
|
-
font-weight: 800;
|
|
3281
|
-
letter-spacing: -0.03em;
|
|
3282
|
-
line-height: 1.15;
|
|
3283
|
-
margin-bottom: 1rem;
|
|
3284
|
-
}
|
|
3285
|
-
.hero h1 span { color: #a78bfa; }
|
|
3286
|
-
.hero p {
|
|
3287
|
-
font-size: 1.1rem;
|
|
3288
|
-
color: #94a3b8;
|
|
3289
|
-
line-height: 1.7;
|
|
3290
|
-
margin-bottom: 2.5rem;
|
|
3291
|
-
}
|
|
3292
|
-
.hero-actions { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; }
|
|
3293
|
-
.btn {
|
|
3294
|
-
display: inline-flex;
|
|
3295
|
-
align-items: center;
|
|
3296
|
-
gap: 0.4rem;
|
|
3297
|
-
padding: 0.65rem 1.4rem;
|
|
3298
|
-
border-radius: 8px;
|
|
3299
|
-
font-size: 0.9rem;
|
|
3300
|
-
font-weight: 600;
|
|
3301
|
-
cursor: pointer;
|
|
3302
|
-
border: none;
|
|
3303
|
-
transition: opacity 0.15s, transform 0.1s;
|
|
3304
|
-
text-decoration: none;
|
|
3305
|
-
}
|
|
3306
|
-
.btn:hover { opacity: 0.85; transform: translateY(-1px); }
|
|
3307
|
-
.btn:active { transform: translateY(0); }
|
|
3308
|
-
.btn-primary { background: #7c3aed; color: #fff; }
|
|
3309
|
-
.btn-ghost { background: #1e1e2e; color: #e2e8f0; border: 1px solid #2d2d3e; }
|
|
3310
|
-
|
|
3311
|
-
.features {
|
|
3312
|
-
display: grid;
|
|
3313
|
-
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
3314
|
-
gap: 1rem;
|
|
3315
|
-
max-width: 900px;
|
|
3316
|
-
margin: 0 auto 4rem;
|
|
3317
|
-
padding: 0 1.5rem;
|
|
3318
|
-
}
|
|
3319
|
-
.card {
|
|
3320
|
-
background: #16161f;
|
|
3321
|
-
border: 1px solid #1e1e2e;
|
|
3322
|
-
border-radius: 12px;
|
|
3323
|
-
padding: 1.5rem;
|
|
3324
|
-
}
|
|
3325
|
-
.card-icon { font-size: 1.5rem; margin-bottom: 0.75rem; }
|
|
3326
|
-
.card h3 { font-size: 0.95rem; font-weight: 700; margin-bottom: 0.4rem; }
|
|
3327
|
-
.card p { font-size: 0.85rem; color: #64748b; line-height: 1.6; }
|
|
3328
|
-
|
|
3329
|
-
.demo {
|
|
3330
|
-
max-width: 480px;
|
|
3331
|
-
margin: 0 auto 4rem;
|
|
3332
|
-
padding: 0 1.5rem;
|
|
3333
|
-
text-align: center;
|
|
3334
|
-
}
|
|
3335
|
-
.demo-box {
|
|
3336
|
-
background: #16161f;
|
|
3337
|
-
border: 1px solid #1e1e2e;
|
|
3338
|
-
border-radius: 12px;
|
|
3339
|
-
padding: 2rem;
|
|
3340
|
-
}
|
|
3341
|
-
.demo-box h2 { font-size: 0.8rem; font-weight: 600; color: #64748b; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 1.25rem; }
|
|
3342
|
-
.counter { font-size: 3.5rem; font-weight: 800; color: #a78bfa; letter-spacing: -0.04em; margin-bottom: 1.25rem; }
|
|
3343
|
-
.demo-actions { display: flex; gap: 0.75rem; justify-content: center; }
|
|
3344
|
-
|
|
3345
|
-
\`;
|
|
3461
|
+
` : JSON.stringify({
|
|
3462
|
+
compilerOptions: {
|
|
3463
|
+
lib: ["ESNext", "DOM"],
|
|
3464
|
+
target: "ESNext",
|
|
3465
|
+
module: "ESNext",
|
|
3466
|
+
moduleResolution: "bundler",
|
|
3467
|
+
jsx: "react-jsx",
|
|
3468
|
+
checkJs: true,
|
|
3469
|
+
noEmit: true,
|
|
3470
|
+
skipLibCheck: true
|
|
3471
|
+
}
|
|
3472
|
+
}, null, 2) + `
|
|
3473
|
+
`;
|
|
3474
|
+
const appImports = useTypeScript ? `import React from 'react';
|
|
3475
|
+
import { HadarsHead, type HadarsApp } from 'hadars';` : `import React from 'react';
|
|
3476
|
+
import { HadarsHead } from 'hadars';`;
|
|
3477
|
+
const appSignature = useTypeScript ? `const App: HadarsApp<{}> = () => {` : `const App = () => {`;
|
|
3478
|
+
const appContent = `${appImports}
|
|
3346
3479
|
|
|
3347
|
-
|
|
3480
|
+
${appSignature}
|
|
3348
3481
|
const [count, setCount] = React.useState(0);
|
|
3349
3482
|
|
|
3350
3483
|
return (
|
|
3351
3484
|
<>
|
|
3352
3485
|
<HadarsHead status={200}>
|
|
3353
|
-
<title
|
|
3486
|
+
<title>${name}</title>
|
|
3354
3487
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
3355
|
-
<style data-id="app-styles" dangerouslySetInnerHTML={{ __html: css }} />
|
|
3356
3488
|
</HadarsHead>
|
|
3357
3489
|
|
|
3358
|
-
<
|
|
3359
|
-
<
|
|
3360
|
-
<
|
|
3361
|
-
<
|
|
3362
|
-
</div>
|
|
3363
|
-
</nav>
|
|
3364
|
-
|
|
3365
|
-
<section className="hero">
|
|
3366
|
-
<div className="hero-badge">built with hadars</div>
|
|
3367
|
-
<h1>Ship <span>React apps</span><br />at full speed</h1>
|
|
3368
|
-
<p>
|
|
3369
|
-
SSR out of the box, zero config, instant hot-reload.
|
|
3370
|
-
Edit <code>src/App.tsx</code> to get started.
|
|
3490
|
+
<main style={{ fontFamily: 'sans-serif', maxWidth: 480, margin: '4rem auto', padding: '0 1rem', textAlign: 'center' }}>
|
|
3491
|
+
<h1>${name}</h1>
|
|
3492
|
+
<p style={{ color: '#666', margin: '1rem 0 2rem' }}>
|
|
3493
|
+
Edit <code>src/App.${appExt}</code> to get started.
|
|
3371
3494
|
</p>
|
|
3372
|
-
<
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3495
|
+
<p style={{ fontSize: '3rem', margin: '1rem 0' }}>{count}</p>
|
|
3496
|
+
<div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'center' }}>
|
|
3497
|
+
<button onClick={() => setCount(c => c - 1)}>−</button>
|
|
3498
|
+
<button onClick={() => setCount(c => c + 1)}>+</button>
|
|
3376
3499
|
</div>
|
|
3377
|
-
</
|
|
3378
|
-
|
|
3379
|
-
<div className="features">
|
|
3380
|
-
<div className="card">
|
|
3381
|
-
<div className="card-icon">⚡</div>
|
|
3382
|
-
<h3>Server-side rendering</h3>
|
|
3383
|
-
<p>Pages render on the server and hydrate on the client — great for SEO and first paint.</p>
|
|
3384
|
-
</div>
|
|
3385
|
-
<div className="card">
|
|
3386
|
-
<div className="card-icon">\uD83D\uDD25</div>
|
|
3387
|
-
<h3>Hot module reload</h3>
|
|
3388
|
-
<p>Changes in <code>src/App.tsx</code> reflect instantly in the browser during development.</p>
|
|
3389
|
-
</div>
|
|
3390
|
-
<div className="card">
|
|
3391
|
-
<div className="card-icon">\uD83D\uDCE6</div>
|
|
3392
|
-
<h3>Zero config</h3>
|
|
3393
|
-
<p>One config file. Export a React component, run <code>hadars dev</code>, done.</p>
|
|
3394
|
-
</div>
|
|
3395
|
-
<div className="card">
|
|
3396
|
-
<div className="card-icon">\uD83D\uDDC4️</div>
|
|
3397
|
-
<h3>Server data hooks</h3>
|
|
3398
|
-
<p>Use <code>useServerData</code> to fetch data on the server without extra round-trips.</p>
|
|
3399
|
-
</div>
|
|
3400
|
-
</div>
|
|
3401
|
-
|
|
3402
|
-
<div className="demo">
|
|
3403
|
-
<div className="demo-box">
|
|
3404
|
-
<h2>Client interactivity works</h2>
|
|
3405
|
-
<div className="counter">{count}</div>
|
|
3406
|
-
<div className="demo-actions">
|
|
3407
|
-
<button className="btn btn-ghost" onClick={() => setCount(c => c - 1)}>− dec</button>
|
|
3408
|
-
<button className="btn btn-primary" onClick={() => setCount(c => c + 1)}>+ inc</button>
|
|
3409
|
-
</div>
|
|
3410
|
-
</div>
|
|
3411
|
-
</div>
|
|
3412
|
-
|
|
3500
|
+
</main>
|
|
3413
3501
|
</>
|
|
3414
3502
|
);
|
|
3415
3503
|
};
|
|
3416
3504
|
|
|
3417
3505
|
export default App;
|
|
3418
|
-
|
|
3419
|
-
|
|
3506
|
+
`;
|
|
3507
|
+
return {
|
|
3508
|
+
"package.json": packageJson,
|
|
3509
|
+
[`hadars.config.${cfgExt}`]: hadarsConfig,
|
|
3510
|
+
[tsOrJs]: tsConfigContent,
|
|
3511
|
+
".gitignore": `node_modules/
|
|
3512
|
+
.hadars/
|
|
3513
|
+
dist/
|
|
3514
|
+
`,
|
|
3515
|
+
[`src/App.${appExt}`]: appContent
|
|
3516
|
+
};
|
|
3517
|
+
}
|
|
3420
3518
|
async function createProject(name, cwd) {
|
|
3421
3519
|
const dir = resolve(cwd, name);
|
|
3422
3520
|
if (existsSync3(dir)) {
|
|
3423
3521
|
console.error(`Directory already exists: ${dir}`);
|
|
3424
3522
|
process.exit(1);
|
|
3425
3523
|
}
|
|
3426
|
-
|
|
3524
|
+
const useTypeScript = await promptRadio("Language?", ["TypeScript", "JavaScript"]) === 0;
|
|
3525
|
+
const selectedIndices = await promptMultiSelect("Select SWC plugins to enable (optional):", PLUGINS.map((p) => p.label));
|
|
3526
|
+
const plugins = selectedIndices.map((i) => PLUGINS[i]);
|
|
3527
|
+
console.log(`
|
|
3528
|
+
Creating hadars project in ${dir}`);
|
|
3427
3529
|
await mkdir2(join2(dir, "src"), { recursive: true });
|
|
3428
|
-
|
|
3429
|
-
|
|
3530
|
+
const files = buildTemplates(name, { useTypeScript, plugins });
|
|
3531
|
+
for (const [file, content] of Object.entries(files)) {
|
|
3430
3532
|
await writeFile2(join2(dir, file), content, "utf-8");
|
|
3431
3533
|
console.log(` created ${file}`);
|
|
3432
3534
|
}
|
|
3535
|
+
const installHint = plugins.length > 0 ? `
|
|
3536
|
+
# Also install selected SWC plugins:
|
|
3537
|
+
npm install -D ${plugins.map((p) => `${p.pkg}@${p.version}`).join(" ")}
|
|
3538
|
+
` : "";
|
|
3433
3539
|
console.log(`
|
|
3434
3540
|
Done! Next steps:
|
|
3435
3541
|
|
|
3436
3542
|
cd ${name}
|
|
3437
|
-
npm install # or: bun install / pnpm install
|
|
3543
|
+
npm install # or: bun install / pnpm install${installHint}
|
|
3438
3544
|
npm run dev # or: bun run dev
|
|
3439
3545
|
`);
|
|
3440
3546
|
}
|