vaderjs 2.3.13 → 2.3.15

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/main.ts ADDED
@@ -0,0 +1,535 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { build, serve } from "bun";
4
+ import fs from "fs/promises";
5
+ import fsSync from "fs";
6
+ import path from "path";
7
+ import { initProject } from "vaderjs/cli";
8
+
9
+ const colors = {
10
+ reset: "\x1b[0m",
11
+ red: "\x1b[31m",
12
+ green: "\x1b[32m",
13
+ yellow: "\x1b[33m",
14
+ blue: "\x1b[34m",
15
+ magenta: "\x1b[35m",
16
+ cyan: "\x1b[36m",
17
+ };
18
+
19
+ globalThis.isDev = process.argv[2] === "dev";
20
+
21
+ const logger = {
22
+ _log: (color, ...args) => console.log(color, ...args, colors.reset),
23
+ info: (...args) => logger._log(colors.cyan, "ℹ", ...args),
24
+ success: (...args) => logger._log(colors.green, "✅", ...args),
25
+ warn: (...args) => logger._log(colors.yellow, "⚠️", ...args),
26
+ error: (...args) => logger._log(colors.red, "❌", ...args),
27
+ step: (...args) => logger._log(colors.magenta, "\n🚀", ...args),
28
+ };
29
+
30
+ async function timedStep(name, fn) {
31
+ logger.step(`${name}...`);
32
+ const start = performance.now();
33
+ try {
34
+ await fn();
35
+ const duration = (performance.now() - start).toFixed(2);
36
+ logger.success(`Finished '${name}' in ${duration}ms`);
37
+ } catch (e) {
38
+ logger.error(`Error during '${name}':`, e);
39
+ if (!isDev) process.exit(1);
40
+ }
41
+ }
42
+
43
+ // --- CONSTANTS ---
44
+
45
+ const PROJECT_ROOT = process.cwd();
46
+ const PUBLIC_DIR = path.join(PROJECT_ROOT, "public");
47
+ const DIST_DIR = path.join(PROJECT_ROOT, "dist");
48
+ const SRC_DIR = path.join(PROJECT_ROOT, "src");
49
+ const APP_DIR = path.join(PROJECT_ROOT, "app");
50
+
51
+ const VADER_SRC_PATH = path.join(
52
+ PROJECT_ROOT,
53
+ "node_modules",
54
+ "vaderjs",
55
+ "index.ts"
56
+ );
57
+
58
+ const TEMP_SRC_DIR = path.join(PROJECT_ROOT, ".vader_temp_src");
59
+
60
+ let config: any = {};
61
+ let htmlInjections: string[] = [];
62
+
63
+ // --- JSConfig Setup ---
64
+
65
+ async function ensureJSConfig() {
66
+ const jsconfigPath = path.join(PROJECT_ROOT, "jsconfig.json");
67
+
68
+ // Check if jsconfig.json already exists
69
+ let existingConfig = {};
70
+ if (fsSync.existsSync(jsconfigPath)) {
71
+ try {
72
+ const content = await fs.readFile(jsconfigPath, "utf8");
73
+ existingConfig = JSON.parse(content);
74
+ } catch (e) {
75
+ logger.warn("Existing jsconfig.json is invalid, will overwrite");
76
+ }
77
+ }
78
+
79
+ // Define the required VaderJS configuration
80
+ const vaderConfig = {
81
+ compilerOptions: {
82
+ jsx: "react",
83
+ jsxFactory: "Vader.createElement",
84
+ jsxFragmentFactory: "Fragment"
85
+ }
86
+ };
87
+
88
+ // Merge with existing config (preserve other settings)
89
+ const mergedConfig = {
90
+ ...existingConfig,
91
+ compilerOptions: {
92
+ ...(existingConfig.compilerOptions || {}),
93
+ ...vaderConfig.compilerOptions
94
+ }
95
+ };
96
+
97
+ // Write the config
98
+ await fs.writeFile(jsconfigPath, JSON.stringify(mergedConfig, null, 2));
99
+ logger.success(`jsconfig.json created/updated at ${jsconfigPath}`);
100
+ }
101
+
102
+ // --- FILE WATCHER ---
103
+
104
+ class FileWatcher {
105
+ watchers = new Map<string, any>();
106
+ callbacks: ((file: string) => void)[] = [];
107
+
108
+ watch(dir: string) {
109
+ if (!fsSync.existsSync(dir)) return;
110
+
111
+ const watcher = fsSync.watch(dir, { recursive: true }, (_, filename) => {
112
+ if (!filename) return;
113
+
114
+ const file = path.join(dir, filename);
115
+
116
+ if (
117
+ file.includes("node_modules") ||
118
+ file.includes("dist") ||
119
+ file.includes(".git")
120
+ )
121
+ return;
122
+
123
+ this.callbacks.forEach((cb) => cb(file));
124
+ });
125
+
126
+ this.watchers.set(dir, watcher);
127
+ }
128
+
129
+ onChange(cb: (file: string) => void) {
130
+ this.callbacks.push(cb);
131
+ }
132
+
133
+ clear() {
134
+ this.watchers.forEach((w) => w.close());
135
+ this.watchers.clear();
136
+ }
137
+ }
138
+
139
+ const watcher = new FileWatcher();
140
+
141
+ // --- CONFIG ---
142
+
143
+ export async function loadConfig() {
144
+ try {
145
+ const mod = await import(path.join(PROJECT_ROOT, "vaderjs.config.js"));
146
+ return mod.default || mod;
147
+ } catch {
148
+ return {};
149
+ }
150
+ }
151
+
152
+ export function defineConfig(cfg: any) {
153
+ return cfg;
154
+ }
155
+
156
+ // --- BUILD HELPERS ---
157
+
158
+ function patchHooksUsage(code: string) {
159
+ return code.replace(
160
+ /import\s+{[^}]*use(State|Effect|Memo)[^}]*}\s+from\s+['"]vaderjs['"];?\n?/g,
161
+ ""
162
+ );
163
+ }
164
+
165
+ async function preprocessSources(src: string, dest: string) {
166
+ await fs.mkdir(dest, { recursive: true });
167
+
168
+ const entries = await fs.readdir(src, { withFileTypes: true });
169
+
170
+ for (const entry of entries) {
171
+ const srcPath = path.join(src, entry.name);
172
+ const destPath = path.join(dest, entry.name);
173
+
174
+ if (entry.isDirectory()) {
175
+ await preprocessSources(srcPath, destPath);
176
+ } else if (/\.(ts|tsx|js|jsx)$/.test(entry.name)) {
177
+ let content = await fs.readFile(srcPath, "utf8");
178
+ content = patchHooksUsage(content);
179
+ await fs.writeFile(destPath, content);
180
+ } else {
181
+ await fs.copyFile(srcPath, destPath);
182
+ }
183
+ }
184
+ }
185
+
186
+ async function buildVaderCore() {
187
+ if (!fsSync.existsSync(VADER_SRC_PATH)) {
188
+ throw new Error("Missing vaderjs dependency");
189
+ }
190
+
191
+ await build({
192
+ entrypoints: [VADER_SRC_PATH],
193
+ outdir: path.join(DIST_DIR, "src", "vader"),
194
+ target: "browser",
195
+ jsxFactory: "e",
196
+ jsxFragment: "Fragment",
197
+ jsxImportSource: "vaderjs",
198
+ sourcemap: "external",
199
+ });
200
+ }
201
+
202
+ async function buildSrc() {
203
+ if (!fsSync.existsSync(SRC_DIR)) return;
204
+
205
+ if (fsSync.existsSync(TEMP_SRC_DIR)) {
206
+ await fs.rm(TEMP_SRC_DIR, { recursive: true, force: true });
207
+ }
208
+
209
+ await preprocessSources(SRC_DIR, TEMP_SRC_DIR);
210
+
211
+ const entrypoints: string[] = [];
212
+
213
+ function collect(dir: string) {
214
+ const files = fsSync.readdirSync(dir, { withFileTypes: true });
215
+
216
+ for (const f of files) {
217
+ const full = path.join(dir, f.name);
218
+
219
+ if (f.isDirectory()) collect(full);
220
+ else if (/\.(ts|tsx|js|jsx)$/.test(f.name)) entrypoints.push(full);
221
+ }
222
+ }
223
+
224
+ collect(TEMP_SRC_DIR);
225
+
226
+ if (!entrypoints.length) return;
227
+
228
+ await build({
229
+ entrypoints,
230
+ outdir: path.join(DIST_DIR, "src"),
231
+ root: TEMP_SRC_DIR,
232
+ naming: { entry: "[dir]/[name].js" },
233
+ jsxFactory: "e",
234
+ jsxFragment: "Fragment",
235
+ jsxImportSource: "vaderjs",
236
+ target: "browser",
237
+ external: ["vaderjs"],
238
+ });
239
+ }
240
+
241
+ async function copyPublicAssets() {
242
+ if (!fsSync.existsSync(PUBLIC_DIR)) return;
243
+
244
+ const items = await fs.readdir(PUBLIC_DIR);
245
+
246
+ for (const item of items) {
247
+ await fs.cp(
248
+ path.join(PUBLIC_DIR, item),
249
+ path.join(DIST_DIR, item),
250
+ { recursive: true }
251
+ );
252
+ }
253
+ }
254
+
255
+ // Helper function to find App file
256
+ function findAppFile(): string | null {
257
+ const possiblePaths = [
258
+ path.join(PROJECT_ROOT, "App.tsx"),
259
+ path.join(PROJECT_ROOT, "App.jsx"),
260
+ path.join(PROJECT_ROOT, "App.ts"),
261
+ path.join(PROJECT_ROOT, "App.js"),
262
+ path.join(APP_DIR, "index.tsx"),
263
+ path.join(APP_DIR, "index.jsx"),
264
+ ];
265
+
266
+ for (const appPath of possiblePaths) {
267
+ if (fsSync.existsSync(appPath)) {
268
+ return appPath;
269
+ }
270
+ }
271
+
272
+ return null;
273
+ }
274
+
275
+ async function buildAppEntrypoints() {
276
+ // First check for root App file (VaderJS standard)
277
+ const appFile = findAppFile();
278
+
279
+ if (appFile) {
280
+ logger.info(`Building App from: ${appFile}`);
281
+
282
+ await build({
283
+ entrypoints: [appFile],
284
+ outdir: DIST_DIR,
285
+ target: "browser",
286
+ jsxFactory: "e",
287
+ jsxFragment: "Fragment",
288
+ jsxImportSource: "vaderjs",
289
+ naming: "index.js",
290
+ external: [], // Bundle everything for website
291
+ });
292
+
293
+ const html = `<!DOCTYPE html>
294
+ <html>
295
+ <head>
296
+ <meta charset="UTF-8"/>
297
+ <meta name="viewport" content="width=device-width,initial-scale=1"/>
298
+ <title>Vader App</title>
299
+ </head>
300
+ <body>
301
+ <div id="app"></div>
302
+ <script type="module" src="/index.js"></script>
303
+ </body>
304
+ </html>`;
305
+
306
+ await fs.writeFile(path.join(DIST_DIR, "index.html"), html);
307
+ return;
308
+ }
309
+
310
+ // Fallback to app directory structure
311
+ if (!fsSync.existsSync(APP_DIR)) {
312
+ logger.warn("No App.tsx or app directory found");
313
+ return;
314
+ }
315
+
316
+ const entries = fsSync
317
+ .readdirSync(APP_DIR, { recursive: true })
318
+ .filter((f) => /index\.(tsx|jsx)$/.test(f as string))
319
+ .map((f) => ({
320
+ name:
321
+ path.dirname(f as string) === "."
322
+ ? "index"
323
+ : path.dirname(f as string),
324
+ path: path.join(APP_DIR, f as string),
325
+ }));
326
+
327
+ for (const entry of entries) {
328
+ var outDir = path.join(DIST_DIR, entry.name === "index" ? "" : entry.name);
329
+
330
+ await fs.mkdir(outDir, { recursive: true });
331
+ console.log("Building entrypoint:", entry.path);
332
+ await build({
333
+ entrypoints: [entry.path],
334
+ outdir: outDir,
335
+ target: "browser",
336
+ jsxFactory: "e",
337
+ jsxFragment: "Fragment",
338
+ jsxImportSource: "vaderjs",
339
+ external: [], // Bundle everything for website
340
+ });
341
+
342
+ const html = `<!DOCTYPE html>
343
+ <html>
344
+ <head>
345
+ <meta charset="UTF-8"/>
346
+ <meta name="viewport" content="width=device-width,initial-scale=1"/>
347
+ <title>Vader App</title>
348
+ </head>
349
+ <body>
350
+ <div id="app"></div>
351
+ <script type="module" src="/index.js"></script>
352
+ </body>
353
+ </html>`;
354
+
355
+ await fs.writeFile(path.join(outDir, "index.html"), html);
356
+ }
357
+ }
358
+
359
+ async function buildAll(dev = false) {
360
+ const start = performance.now();
361
+
362
+ // Ensure jsconfig.json exists before building
363
+ await ensureJSConfig();
364
+
365
+ if (fsSync.existsSync(DIST_DIR)) {
366
+ await fs.rm(DIST_DIR, { recursive: true, force: true });
367
+ }
368
+
369
+ await fs.mkdir(DIST_DIR, { recursive: true });
370
+
371
+ await timedStep("Build Vader Core", buildVaderCore);
372
+ await timedStep("Build Src", buildSrc);
373
+ await timedStep("Copy Public", copyPublicAssets);
374
+ await timedStep("Build App", buildAppEntrypoints);
375
+
376
+ logger.success(
377
+ `Build finished in ${(performance.now() - start).toFixed(1)}ms`
378
+ );
379
+ }
380
+
381
+ // --- DEV SERVER ---
382
+
383
+ async function runDevServer() {
384
+ await buildAll(true);
385
+
386
+ const clients = new Set<any>();
387
+
388
+ const port = config.port || 3000;
389
+
390
+ const server = serve({
391
+ port,
392
+
393
+ fetch(req, server) {
394
+ const url = new URL(req.url);
395
+
396
+ if (url.pathname === "/__hmr" && server.upgrade(req)) return;
397
+
398
+ let filePath = path.join(DIST_DIR, url.pathname);
399
+
400
+ if (!path.extname(filePath)) {
401
+ filePath = path.join(filePath, "index.html");
402
+ }
403
+
404
+ // Ensure we're serving from dist directory
405
+ if (url.pathname === "/" || url.pathname === "") {
406
+ filePath = path.join(DIST_DIR, "index.html");
407
+ }
408
+
409
+ console.log("Serving:", filePath);
410
+
411
+ const file = Bun.file(filePath);
412
+
413
+ return file.exists().then((exists) =>
414
+ exists
415
+ ? new Response(file)
416
+ : new Response("Not Found", { status: 404 })
417
+ );
418
+ },
419
+
420
+ websocket: {
421
+ open(ws) {
422
+ clients.add(ws);
423
+ },
424
+ close(ws) {
425
+ clients.delete(ws);
426
+ },
427
+ },
428
+ });
429
+
430
+ watcher.watch(APP_DIR);
431
+ watcher.watch(SRC_DIR);
432
+ watcher.watch(PUBLIC_DIR);
433
+
434
+ // Also watch for root App file
435
+ const rootAppDir = path.dirname(findAppFile() || "");
436
+ if (rootAppDir && rootAppDir !== PROJECT_ROOT) {
437
+ watcher.watch(rootAppDir);
438
+ }
439
+
440
+ watcher.onChange(async () => {
441
+ logger.info("Changes detected, rebuilding...");
442
+ await buildAll(true);
443
+
444
+ for (const c of clients) c.send("reload");
445
+ });
446
+
447
+ logger.success(`Dev server running http://localhost:${port}`);
448
+ }
449
+
450
+ // --- PROD SERVER ---
451
+
452
+ async function runProdServer() {
453
+ const port = config.port || 3000;
454
+
455
+ const server = serve({
456
+ port,
457
+ fetch(req) {
458
+ const url = new URL(req.url);
459
+ let filePath = path.join(DIST_DIR, url.pathname);
460
+
461
+ if (!path.extname(filePath)) {
462
+ filePath = path.join(filePath, "index.html");
463
+ }
464
+
465
+ if (url.pathname === "/" || url.pathname === "") {
466
+ filePath = path.join(DIST_DIR, "index.html");
467
+ }
468
+
469
+ const file = Bun.file(filePath);
470
+
471
+ return file.exists().then((exists) =>
472
+ exists
473
+ ? new Response(file)
474
+ : new Response("Not Found", { status: 404 })
475
+ );
476
+ },
477
+ });
478
+
479
+ logger.success(`Serving production http://localhost:${port}`);
480
+ }
481
+
482
+ // --- CLI ENTRY ---
483
+
484
+ async function main() {
485
+ console.log(`${colors.magenta}
486
+ __ __ ____ ____ _______ __
487
+ | | / |/ __ \\ / __ \\ / ____/ |/ /
488
+ | | / / / / // /_/ // /___ | /
489
+ | | / / /_/ / \\____// /___ / |
490
+ |____/____/_____/ /_____/ |_| |_|
491
+ ${colors.reset}`);
492
+
493
+ const cmd = process.argv[2];
494
+
495
+ if (cmd === "init") {
496
+ await initProject(process.argv[3]);
497
+ // Also create jsconfig.json when initializing a new project
498
+ await ensureJSConfig();
499
+ return;
500
+ }
501
+
502
+ config = await loadConfig();
503
+ config.port ||= 3000;
504
+
505
+ switch (cmd) {
506
+ case "dev":
507
+ await runDevServer();
508
+ break;
509
+
510
+ case "build":
511
+ await buildAll(false);
512
+ break;
513
+
514
+ case "serve":
515
+ await buildAll(false);
516
+ await runProdServer();
517
+ break;
518
+
519
+ default:
520
+ logger.info(`
521
+ Commands:
522
+ dev Start development server with hot reload
523
+ build Build for production
524
+ serve Build and serve production build
525
+ init Initialize a new VaderJS project
526
+
527
+ Make sure you have:
528
+ - App.tsx or App.jsx in your project root
529
+ - OR an app/ directory with index.tsx/jsx files
530
+ - vaderjs installed as a dependency
531
+ `);
532
+ }
533
+ }
534
+
535
+ main();
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
- {
2
- "name": "vaderjs",
3
- "version": "2.3.13",
4
- "description": "A simple and powerful JavaScript library for building modern web applications.",
5
- "bin": {
6
- "vaderjs": "./main.js"
7
- },
8
- "repository": {
9
- "type": "git",
10
- "url": "https://github.com/Postr-Inc/Vader.js"
11
- },
12
- "license": "MIT",
13
- "dependencies": {
14
- "ansi-colors": "latest"
15
- }
16
- }
1
+ {
2
+ "name": "vaderjs",
3
+ "version": "2.3.15",
4
+ "description": "A simple and powerful JavaScript library for building modern web applications.",
5
+ "bin": {
6
+ "vaderjs": "./main.ts"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/Postr-Inc/Vader.js"
11
+ },
12
+ "license": "MIT",
13
+ "dependencies": {
14
+ "ansi-colors": "latest"
15
+ }
16
+ }
package/plugins/index.ts CHANGED
@@ -1,72 +1,72 @@
1
- // vaderPlugin.d.ts
2
-
3
- /** The API object passed to plugin hooks */
4
- export interface VaderAPI {
5
- /** Run a shell command (string or string[]) and wait for it to finish */
6
- runCommand(cmd: string | string[]): Promise<void>;
7
-
8
- /** Inject arbitrary HTML into the <head> of generated index.html files */
9
- injectHTML(content: string): void;
10
-
11
- /** Log a message prefixed with [Vader Plugin] */
12
- log : {
13
- warn: (msg: any) => void,
14
- info: (msg: any) => void,
15
- error: (msg: any) => void,
16
- success: (msg: any) => void,
17
- step: (msg: any) => void
18
- }
19
-
20
- /** Get absolute path to the project root */
21
- getProjectRoot(): string;
22
-
23
- /** Get absolute path to the dist output directory */
24
- getDistDir(): string;
25
-
26
- /** Get absolute path to the public assets directory */
27
- getPublicDir(): string;
28
- }
29
-
30
- /** Supported plugin hook names */
31
- export type PluginHookName =
32
- | "onBuildStart"
33
- | "onBuildFinish"
34
- | "onWatchStart"
35
- | "onWatchStop"
36
- | "onServeStart"
37
- | "onServeStop"
38
- | "onFileChange";
39
-
40
- /** Interface for a single plugin */
41
- export interface VaderPlugin {
42
- name: string;
43
- description: string;
44
- version: string;
45
- /** Called before build starts */
46
- onBuildStart?(api: VaderAPI): Promise<void> | void;
47
-
48
- /** Called after build finishes */
49
- onBuildFinish?(api: VaderAPI): Promise<void> | void;
50
-
51
- /** Called when watcher starts (dev mode) */
52
- onWatchStart?(api: VaderAPI): Promise<void> | void;
53
-
54
- /** Called when watcher stops (dev mode) */
55
- onWatchStop?(api: VaderAPI): Promise<void> | void;
56
-
57
- /** Called when dev server starts */
58
- onServeStart?(api: VaderAPI): Promise<void> | void;
59
-
60
- /** Called when dev server stops */
61
- onServeStop?(api: VaderAPI): Promise<void> | void;
62
-
63
- /** Called on file change during watch, with changed file path */
64
- onFileChange?(api: VaderAPI, filePath: string): Promise<void> | void;
65
- }
66
-
67
- /** User config type */
68
- export interface VaderConfig {
69
- port?: number;
70
- host_provider?: string;
71
- plugins?: VaderPlugin[];
72
- }
1
+ // vaderPlugin.d.ts
2
+
3
+ /** The API object passed to plugin hooks */
4
+ export interface VaderAPI {
5
+ /** Run a shell command (string or string[]) and wait for it to finish */
6
+ runCommand(cmd: string | string[]): Promise<void>;
7
+
8
+ /** Inject arbitrary HTML into the <head> of generated index.html files */
9
+ injectHTML(content: string): void;
10
+
11
+ /** Log a message prefixed with [Vader Plugin] */
12
+ log : {
13
+ warn: (msg: any) => void,
14
+ info: (msg: any) => void,
15
+ error: (msg: any) => void,
16
+ success: (msg: any) => void,
17
+ step: (msg: any) => void
18
+ }
19
+
20
+ /** Get absolute path to the project root */
21
+ getProjectRoot(): string;
22
+
23
+ /** Get absolute path to the dist output directory */
24
+ getDistDir(): string;
25
+
26
+ /** Get absolute path to the public assets directory */
27
+ getPublicDir(): string;
28
+ }
29
+
30
+ /** Supported plugin hook names */
31
+ export type PluginHookName =
32
+ | "onBuildStart"
33
+ | "onBuildFinish"
34
+ | "onWatchStart"
35
+ | "onWatchStop"
36
+ | "onServeStart"
37
+ | "onServeStop"
38
+ | "onFileChange";
39
+
40
+ /** Interface for a single plugin */
41
+ export interface VaderPlugin {
42
+ name: string;
43
+ description: string;
44
+ version: string;
45
+ /** Called before build starts */
46
+ onBuildStart?(api: VaderAPI): Promise<void> | void;
47
+
48
+ /** Called after build finishes */
49
+ onBuildFinish?(api: VaderAPI): Promise<void> | void;
50
+
51
+ /** Called when watcher starts (dev mode) */
52
+ onWatchStart?(api: VaderAPI): Promise<void> | void;
53
+
54
+ /** Called when watcher stops (dev mode) */
55
+ onWatchStop?(api: VaderAPI): Promise<void> | void;
56
+
57
+ /** Called when dev server starts */
58
+ onServeStart?(api: VaderAPI): Promise<void> | void;
59
+
60
+ /** Called when dev server stops */
61
+ onServeStop?(api: VaderAPI): Promise<void> | void;
62
+
63
+ /** Called on file change during watch, with changed file path */
64
+ onFileChange?(api: VaderAPI, filePath: string): Promise<void> | void;
65
+ }
66
+
67
+ /** User config type */
68
+ export interface VaderConfig {
69
+ port?: number;
70
+ host_provider?: string;
71
+ plugins?: VaderPlugin[];
72
+ }