vaderjs 2.3.4 → 2.3.6

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/config/index.ts CHANGED
@@ -1,6 +1,9 @@
1
- type Config = {
1
+ export type Config = {
2
2
  port: number,
3
3
  host?: string,
4
+ plugin_config?: {
5
+ [key: string]: any
6
+ },
4
7
  plugins?: any[],
5
8
  generateTypes?: boolean,
6
9
  host_provider?: 'vercel' | 'netlify' | 'aws' | 'gcp' | 'azure' | 'heroku' | 'custom' | 'apache' | 'none',
package/index.ts CHANGED
@@ -346,29 +346,38 @@ function performUnitOfWork(fiber: Fiber): Fiber | null {
346
346
  * Updates a function component fiber.
347
347
  * @param {Fiber} fiber - The function component fiber to update.
348
348
  */
349
- function updateFunctionComponent(fiber: Fiber) {
349
+ function updateFunctionComponent(fiber: Fiber) {
350
350
  wipFiber = fiber;
351
351
  hookIndex = 0;
352
352
  fiber.hooks = fiber.alternate?.hooks || [];
353
353
 
354
- // Directly call the component function without memoization
355
- // The 'createComponent' call is removed.
356
- const children = [(fiber.type as Function)(fiber.props)]
357
- .flat()
358
- .filter(child => child != null && typeof child !== 'boolean')
359
- .map(child => typeof child === 'object' ? child : createTextElement(child));
354
+ const rawChildren = (fiber.type as Function)(fiber.props);
355
+ const children = normalizeChildren(rawChildren, fiber);
360
356
 
361
357
  reconcileChildren(fiber, children);
362
358
  }
359
+ function normalizeChildren(children: any, parentFiber: Fiber): VNode[] {
360
+ if (!children) return [];
361
+ let arr = Array.isArray(children) ? children : [children];
362
+
363
+ return arr.flatMap((child, index) => {
364
+ if (child == null || typeof child === "boolean") return [];
365
+ if (typeof child === "string" || typeof child === "number") return [createTextElement(String(child))];
366
+
367
+ // Ensure every child has a stable key
368
+ const key = child.key ?? child.props?.id ?? `${parentFiber.key ?? "root"}-${index}`;
369
+ return [{ ...child, key }];
370
+ });
371
+ }
363
372
  /**
364
373
  * Updates a host component fiber (DOM element).
365
374
  * @param {Fiber} fiber - The host component fiber to update.
366
375
  */
367
376
  function updateHostComponent(fiber: Fiber): void {
368
- if (!fiber.dom) {
369
- fiber.dom = createDom(fiber);
370
- }
371
- reconcileChildren(fiber, fiber.props.children);
377
+ if (!fiber.dom) fiber.dom = createDom(fiber);
378
+
379
+ const children = normalizeChildren(fiber.props.children, fiber);
380
+ reconcileChildren(fiber, children);
372
381
  }
373
382
 
374
383
  /**
@@ -376,12 +385,11 @@ function updateHostComponent(fiber: Fiber): void {
376
385
  * @param {Fiber} wipFiber - The work-in-progress fiber.
377
386
  * @param {VNode[]} elements - The new child elements.
378
387
  */
379
- function reconcileChildren(wipFiber: Fiber, elements: VNode[]) {
388
+ function reconcileChildren(wipFiber: Fiber, elements: VNode[]) {
380
389
  let index = 0;
381
390
  let oldFiber = wipFiber.alternate?.child;
382
391
  let prevSibling: Fiber | null = null;
383
-
384
- // Create map of existing fibers by key
392
+
385
393
  const existingFibers = new Map<string | number | null, Fiber>();
386
394
  while (oldFiber) {
387
395
  const key = oldFiber.key ?? index;
@@ -393,23 +401,21 @@ function updateHostComponent(fiber: Fiber): void {
393
401
  index = 0;
394
402
  for (; index < elements.length; index++) {
395
403
  const element = elements[index];
396
- const key = element?.key ?? index;
404
+ const key = element.key ?? index;
397
405
  const oldFiber = existingFibers.get(key);
398
-
406
+
399
407
  const sameType = oldFiber && element && element.type === oldFiber.type;
400
408
  let newFiber: Fiber | null = null;
401
409
 
402
410
  if (sameType) {
403
- // Reuse the fiber
411
+ // Reuse old fiber for same type + key
404
412
  newFiber = {
405
- type: oldFiber.type,
413
+ ...oldFiber,
406
414
  props: element.props,
407
- dom: oldFiber.dom,
408
415
  parent: wipFiber,
409
416
  alternate: oldFiber,
410
417
  effectTag: "UPDATE",
411
- hooks: oldFiber.hooks,
412
- key
418
+ _needsUpdate: false,
413
419
  };
414
420
  existingFibers.delete(key);
415
421
  } else if (element) {
@@ -421,7 +427,8 @@ function updateHostComponent(fiber: Fiber): void {
421
427
  parent: wipFiber,
422
428
  alternate: null,
423
429
  effectTag: "PLACEMENT",
424
- key
430
+ key,
431
+ _needsUpdate: true,
425
432
  };
426
433
  }
427
434
 
@@ -436,9 +443,7 @@ function updateHostComponent(fiber: Fiber): void {
436
443
  prevSibling.sibling = newFiber;
437
444
  }
438
445
 
439
- if (newFiber) {
440
- prevSibling = newFiber;
441
- }
446
+ if (newFiber) prevSibling = newFiber;
442
447
  }
443
448
 
444
449
  // Mark remaining old fibers for deletion
@@ -455,23 +460,17 @@ function updateHostComponent(fiber: Fiber): void {
455
460
  * @param {...any} children - The element's children.
456
461
  * @returns {VNode} The created virtual DOM element.
457
462
  */
458
- export function createElement(
459
- type: string | Function,
460
- props?: object,
461
- ...children: any[]
462
- ): VNode {
463
+ export function createElement(type: string | Function, props?: any, ...children: any[]): VNode {
464
+ const rawChildren = children.flat().filter(c => c != null && typeof c !== "boolean");
465
+ const normalizedChildren = rawChildren.map((child, i) => {
466
+ if (typeof child === "object") return child;
467
+ return createTextElement(String(child));
468
+ });
469
+
463
470
  return {
464
471
  type,
465
- props: {
466
- ...props,
467
- children: children
468
- .flat()
469
- .filter(child => child != null && typeof child !== "boolean")
470
- .map(child =>
471
- typeof child === "object" ? child : createTextElement(child)
472
- ),
473
- },
474
- key: props?.key ?? null,
472
+ props: { ...props, children: normalizedChildren },
473
+ key: props?.key ?? props?.id ?? null,
475
474
  };
476
475
  }
477
476
 
@@ -598,10 +597,36 @@ export function Switch({ children }: { children: VNode[] }): VNode | null {
598
597
  export function Match({ when, children }: { when: boolean, children: VNode[] }): VNode | null {
599
598
  return when ? children : null;
600
599
  }
601
-
600
+ /**
601
+ * Wraps a functional component into a VNode for Vader.js rendering.
602
+ * @param {Function} fn - The function component to wrap.
603
+ * @returns {Function} A function that creates a VNode when called with props.
604
+ */
605
+ export function component<P extends object>(
606
+ fn: (props: P) => VNode | VNode[]
607
+ ): (props: P & { key?: string | number }) => VNode {
608
+ return (props: P & { key?: string | number }) => {
609
+ return createElement(fn, props);
610
+ };
611
+ }
602
612
  export function Show({ when, children }: { when: boolean, children: VNode[] }): VNode | null {
603
613
  return when ? children : null;
604
614
  }
615
+
616
+ /**
617
+ * For TypeScript to recognize your JSX factory,
618
+ * you often need the global namespace declaration as well:
619
+ */
620
+ declare global {
621
+ namespace JSX {
622
+ interface IntrinsicElements {
623
+ [elemName: string]: any;
624
+ }
625
+ interface Element {
626
+ [key: string]: any;
627
+ }
628
+ }
629
+ }
605
630
 
606
631
  /**
607
632
  * A React-like useRef hook for mutable references.
package/main.js CHANGED
@@ -1,15 +1,5 @@
1
1
  #!/usr/bin/env bun
2
- /**
3
- * VaderJS Build & Development Script
4
- *
5
- * This script handles building the VaderJS framework, your application code,
6
- * and serving it in a local development environment with live reloading.
7
- *
8
- * Commands:
9
- * bun run vaderjs build - Builds the project for production.
10
- * bun run vaderjs dev - Starts the dev server with HMR and file watching.
11
- * bun run vaderjs serve - Builds and serves the production output.
12
- */
2
+
13
3
 
14
4
  import { build, serve } from "bun";
15
5
  import fs from "fs/promises";
@@ -85,7 +75,13 @@ const vaderAPI = {
85
75
  await p.exited;
86
76
  },
87
77
  injectHTML: (content) => htmlInjections.push(content),
88
- log: (msg) => logger.info(`[Plugin] ${msg}`),
78
+ log: {
79
+ warn: (msg) => logger.warn(msg),
80
+ info: (msg) => logger.info(msg),
81
+ error: (msg) => logger.error(msg),
82
+ success: (msg) => logger.success(msg),
83
+ step: (msg) => logger.step(msg)
84
+ },
89
85
  getProjectRoot: () => PROJECT_ROOT,
90
86
  getDistDir: () => DIST_DIR,
91
87
  getPublicDir: () => PUBLIC_DIR,
@@ -93,9 +89,10 @@ const vaderAPI = {
93
89
 
94
90
  async function loadConfig() {
95
91
  try {
96
- const configModule = await import(path.join(PROJECT_ROOT, "vaderjs.config.js"));
92
+ const configModule = await import(path.join(PROJECT_ROOT, "vaderjs.config.js"));
97
93
  return configModule.default || configModule;
98
94
  } catch {
95
+ console.log(path.join(PROJECT_ROOT, "vaderjs.config.js"))
99
96
  logger.warn("No 'vader.config.js' found, using defaults.");
100
97
  return {};
101
98
  }
@@ -149,10 +146,43 @@ async function buildVaderCore() {
149
146
  function patchHooksUsage(code) {
150
147
  return code.replace(/import\s+{[^}]*use(State|Effect|Memo|Navigation)[^}]*}\s+from\s+['"]vaderjs['"];?\n?/g, "");
151
148
  }
149
+ function publicAssetPlugin() {
150
+ return {
151
+ name: "public-asset-replacer",
152
+ setup(build) {
153
+ build.onLoad({ filter: /\.(js|ts|jsx|tsx|html)$/ }, async (args) => {
154
+ let code = await fs.readFile(args.path, "utf8");
155
+
156
+ code = code.replace(/\{\{public:(.+?)\}\}/g, (_, relPath) => {
157
+ const absPath = path.join(PUBLIC_DIR, relPath.trim());
158
+ if (fsSync.existsSync(absPath)) {
159
+ return "/" + relPath.trim().replace(/\\/g, "/");
160
+ }
161
+ logger.warn(`Public asset not found: ${relPath}`);
162
+ return relPath;
163
+ });
164
+
165
+ return {
166
+ contents: code,
167
+ loader: args.path.endsWith(".html")
168
+ ? "text"
169
+ : args.path.endsWith(".tsx")
170
+ ? "tsx"
171
+ : args.path.endsWith(".jsx")
172
+ ? "jsx"
173
+ : args.path.endsWith(".ts")
174
+ ? "ts"
175
+ : "js",
176
+ };
177
+ });
178
+ },
179
+ };
180
+ }
152
181
 
153
182
  /**
154
183
  * Step 3: Pre-processes all files in `/src` into a temporary directory.
155
184
  */
185
+
156
186
  async function preprocessSources(srcDir, tempDir) {
157
187
  await fs.mkdir(tempDir, { recursive: true });
158
188
  for (const entry of await fs.readdir(srcDir, { withFileTypes: true })) {
@@ -163,7 +193,7 @@ async function preprocessSources(srcDir, tempDir) {
163
193
  await preprocessSources(srcPath, destPath);
164
194
  } else if (/\.(tsx|jsx|ts|js)$/.test(entry.name)) {
165
195
  let content = await fs.readFile(srcPath, "utf8");
166
- content = patchHooksUsage(content);
196
+ content = patchHooksUsage(content);
167
197
  await fs.writeFile(destPath, content);
168
198
  } else {
169
199
  await fs.copyFile(srcPath, destPath);
@@ -201,6 +231,9 @@ async function buildSrc() {
201
231
  jsxImportSource: "vaderjs",
202
232
  target: "browser",
203
233
  minify: false,
234
+ plugins: [
235
+ publicAssetPlugin(),
236
+ ],
204
237
  external: ["vaderjs"],
205
238
  });
206
239
  }
@@ -222,57 +255,61 @@ async function copyPublicAssets() {
222
255
  return;
223
256
  }
224
257
 
225
- // Ensure the dist directory exists
226
258
  if (!fsSync.existsSync(DIST_DIR)) {
227
259
  await fs.mkdir(DIST_DIR, { recursive: true });
228
260
  }
229
261
 
230
- const devClientScript = isDev ? `
231
- <script>
232
- new WebSocket("ws://" + location.host + "/__hmr").onmessage = (msg) => {
233
- if (msg.data === "reload") location.reload();
234
- };
235
- </script>` : "";
262
+ const devClientScript = isDev
263
+ ? `<script>
264
+ new WebSocket("ws://" + location.host + "/__hmr").onmessage = (msg) => {
265
+ if (msg.data === "reload") location.reload();
266
+ };
267
+ </script>`
268
+ : "";
236
269
 
237
270
  const entries = fsSync.readdirSync(APP_DIR, { recursive: true })
238
271
  .filter(file => /index\.(jsx|tsx)$/.test(file))
239
272
  .map(file => ({
240
- name: path.dirname(file) === '.' ? 'index' : path.dirname(file).replace(/\\/g, '/'),
241
- path: path.join(APP_DIR, file)
273
+ name: path.dirname(file) === '.' ? 'index' : path.dirname(file).replace(/\\/g, '/'),
274
+ path: path.join(APP_DIR, file)
242
275
  }));
243
276
 
277
+ // Helper to resolve any asset path from /public
278
+ function resolvePublicPath(p) {
279
+ const assetPath = p.replace(/^(\.\/|\/)/, ""); // strip leading ./ or /
280
+ const absPath = path.join(PUBLIC_DIR, assetPath);
281
+ if (fsSync.existsSync(absPath)) {
282
+ return "/" + assetPath.replace(/\\/g, "/");
283
+ }
284
+ return p; // leave unchanged if not in public
285
+ }
286
+
244
287
  for (const { name, path: entryPath } of entries) {
245
- // Check for the specific case where 'name' could be 'index.js' and prevent duplication
246
288
  const outDir = path.join(DIST_DIR, name === 'index' ? '' : name);
247
- const outJsPath = path.join(outDir, 'index.js'); // Output JavaScript file path
248
-
249
- // Ensure the output directory exists
289
+ const outJsPath = path.join(outDir, 'index.js');
250
290
  await fs.mkdir(outDir, { recursive: true });
251
291
 
252
- // **FIXED CSS HANDLING**: Find, copy, and correctly link CSS files
292
+ // --- CSS HANDLING ---
253
293
  const cssLinks = [];
254
- const cssContent = await fs.readFile(entryPath, "utf8");
255
- const cssImports = [...cssContent.matchAll(/import\s+['"](.*\.css)['"]/g)];
256
-
294
+ let content = await fs.readFile(entryPath, "utf8");
295
+ const cssImports = [...content.matchAll(/import\s+['"](.*\.css)['"]/g)];
257
296
  for (const match of cssImports) {
258
- const cssImportPath = match[1]; // e.g., './styles.css'
259
- const sourceCssPath = path.resolve(path.dirname(entryPath), cssImportPath);
260
- if (fsSync.existsSync(sourceCssPath)) {
261
- const relativeCssPath = path.relative(APP_DIR, sourceCssPath);
262
- const destCssPath = path.join(DIST_DIR, relativeCssPath);
263
-
264
- await fs.mkdir(path.dirname(destCssPath), { recursive: true });
265
- await fs.copyFile(sourceCssPath, destCssPath);
266
-
267
- const htmlRelativePath = path.relative(outDir, destCssPath).replace(/\\/g, '/');
268
- cssLinks.push(`<link rel="stylesheet" href="${htmlRelativePath}">`);
269
- } else {
270
- logger.warn(`CSS file not found: ${sourceCssPath}`);
271
- }
297
+ const cssImportPath = match[1];
298
+ const sourceCssPath = path.resolve(path.dirname(entryPath), cssImportPath);
299
+ if (fsSync.existsSync(sourceCssPath)) {
300
+ const relativeCssPath = path.relative(APP_DIR, sourceCssPath);
301
+ const destCssPath = path.join(DIST_DIR, relativeCssPath);
302
+ await fs.mkdir(path.dirname(destCssPath), { recursive: true });
303
+ await fs.copyFile(sourceCssPath, destCssPath);
304
+ const htmlRelativePath = path.relative(outDir, destCssPath).replace(/\\/g, '/');
305
+ cssLinks.push(`<link rel="stylesheet" href="${htmlRelativePath}">`);
306
+ } else {
307
+ logger.warn(`CSS file not found: ${sourceCssPath}`);
308
+ }
272
309
  }
273
310
 
274
- // Update the script tag to use relative paths for index.js
275
- const htmlContent = `<!DOCTYPE html>
311
+ // --- HTML GENERATION ---
312
+ let htmlContent = `<!DOCTYPE html>
276
313
  <html lang="en">
277
314
  <head>
278
315
  <meta charset="UTF-8" />
@@ -286,37 +323,54 @@ async function copyPublicAssets() {
286
323
  <script type="module">
287
324
  import App from '${name !== 'index' ? "/" + name : ''}/index.js';
288
325
  import * as Vader from '/src/vader/index.js';
289
- window.Vader = Vader
326
+ window.Vader = Vader;
290
327
  Vader.render(Vader.createElement(App, null), document.getElementById("app"));
291
328
  </script>
292
329
  ${devClientScript}
293
330
  </body>
294
331
  </html>`;
295
332
 
296
- await fs.writeFile(path.join(outDir, "index.html"), htmlContent);
333
+ // --- FIX ASSET PATHS IN HTML ---
334
+ htmlContent = htmlContent.replace(
335
+ /(["'(])([^"'()]+?\.(png|jpe?g|gif|svg|webp|ico))(["')])/gi,
336
+ (match, p1, assetPath, ext, p4) => p1 + resolvePublicPath(assetPath) + p4
337
+ );
297
338
 
298
- // Log for debugging
339
+ await fs.writeFile(path.join(outDir, "index.html"), htmlContent);
299
340
 
300
- // Build the JavaScript file and ensure it uses the correct paths
341
+ // --- JS BUILD ---
301
342
  await build({
302
343
  entrypoints: [entryPath],
303
- outdir: outDir, // Pass the directory path to outdir
344
+ outdir: outDir,
304
345
  target: "browser",
305
346
  minify: false,
306
347
  sourcemap: "external",
307
348
  external: ["vaderjs"],
308
349
  jsxFactory: "e",
309
350
  jsxFragment: "Fragment",
351
+ plugins: [
352
+ publicAssetPlugin(),
353
+ ],
310
354
  jsxImportSource: "vaderjs",
311
355
  });
312
356
 
313
- // After build, replace the 'vaderjs' import to the correct path
357
+ // --- FIX IMPORT PATHS IN JS ---
314
358
  let jsContent = await fs.readFile(outJsPath, "utf8");
359
+
360
+ // Vader import fix
315
361
  jsContent = jsContent.replace(/from\s+['"]vaderjs['"]/g, `from '/src/vader/index.js'`);
362
+
363
+ // Asset path fix for JS
364
+ jsContent = jsContent.replace(
365
+ /(["'(])([^"'()]+?\.(png|jpe?g|gif|svg|webp|ico))(["')])/gi,
366
+ (match, p1, assetPath, ext, p4) => p1 + resolvePublicPath(assetPath) + p4
367
+ );
368
+
316
369
  await fs.writeFile(outJsPath, jsContent);
317
370
  }
318
371
  }
319
372
 
373
+
320
374
 
321
375
 
322
376
  async function buildAll(isDev = false) {
@@ -442,6 +496,7 @@ console.log(banner);
442
496
  const command = process.argv[2];
443
497
 
444
498
  if (command === "dev") {
499
+ globalThis.isDev = true
445
500
  await runDevServer();
446
501
  } else if (command === "build") {
447
502
  await buildAll(false);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vaderjs",
3
- "version": "2.3.4",
3
+ "version": "2.3.6",
4
4
  "description": "A simple and powerful JavaScript library for building modern web applications.",
5
5
  "bin": {
6
6
  "vaderjs": "./main.js"
package/plugins/index.ts CHANGED
@@ -9,7 +9,13 @@ export interface VaderAPI {
9
9
  injectHTML(content: string): void;
10
10
 
11
11
  /** Log a message prefixed with [Vader Plugin] */
12
- log(msg: string): void;
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
+ }
13
19
 
14
20
  /** Get absolute path to the project root */
15
21
  getProjectRoot(): string;
@@ -33,6 +39,9 @@ export type PluginHookName =
33
39
 
34
40
  /** Interface for a single plugin */
35
41
  export interface VaderPlugin {
42
+ name: string;
43
+ description: string;
44
+ version: string;
36
45
  /** Called before build starts */
37
46
  onBuildStart?(api: VaderAPI): Promise<void> | void;
38
47