vaderjs 2.3.3 → 2.3.5

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.
Files changed (3) hide show
  1. package/index.ts +158 -83
  2. package/main.js +124 -57
  3. package/package.json +1 -1
package/index.ts CHANGED
@@ -13,7 +13,9 @@ let deletions: Fiber[] | null = null;
13
13
  let wipFiber: Fiber | null = null;
14
14
  let hookIndex = 0;
15
15
  let isRenderScheduled = false;
16
-
16
+ // Add to the top of your Vader.js file
17
+
18
+
17
19
  interface Fiber {
18
20
  type?: string | Function;
19
21
  dom?: Node;
@@ -28,9 +30,13 @@ interface Fiber {
28
30
  effectTag?: "PLACEMENT" | "UPDATE" | "DELETION";
29
31
  hooks?: Hook[];
30
32
  key?: string | number | null;
33
+ propsCache?: Record<string, any>;
34
+ __compareProps?: (prev: any, next: any) => boolean;
35
+ __skipMemo?: boolean;
36
+ _needsUpdate?: boolean;
31
37
  }
32
38
 
33
- interface VNode {
39
+ export interface VNode {
34
40
  type: string | Function;
35
41
  props: {
36
42
  children: VNode[];
@@ -84,33 +90,55 @@ const isGone = (prev: object, next: object) => (key: string) => !(key in next);
84
90
  * @returns {Node} The created DOM node.
85
91
  */
86
92
  function createDom(fiber: Fiber): Node {
87
- const dom =
88
- fiber.type == "TEXT_ELEMENT"
89
- ? document.createTextNode("")
90
- : document.createElement(fiber.type as string);
93
+ let dom: Node;
94
+
95
+ if (fiber.type === "TEXT_ELEMENT") {
96
+ dom = document.createTextNode("");
97
+ } else {
98
+ const isSvg = isSvgElement(fiber);
99
+ if (isSvg) {
100
+ dom = document.createElementNS("http://www.w3.org/2000/svg", fiber.type as string);
101
+ } else {
102
+ dom = document.createElement(fiber.type as string);
103
+ }
104
+ }
91
105
 
92
106
  updateDom(dom, {}, fiber.props);
93
107
  return dom;
94
108
  }
95
109
 
110
+ function isSvgElement(fiber: Fiber): boolean {
111
+ // Check if the fiber is an <svg> itself or inside an <svg>
112
+ let parent = fiber.parent;
113
+ if (fiber.type === "svg") return true;
114
+ while (parent) {
115
+ if (parent.type === "svg") return true;
116
+ parent = parent.parent;
117
+ }
118
+ return false;
119
+ }
120
+
121
+
96
122
  /**
97
123
  * Applies updated props to a DOM node.
98
124
  * @param {Node} dom - The DOM node to update.
99
125
  * @param {object} prevProps - The previous properties.
100
126
  * @param {object} nextProps - The new properties.
101
127
  */
102
- function updateDom(dom: Node, prevProps: object, nextProps: object): void {
128
+ function updateDom(dom: Node, prevProps: any, nextProps: any): void {
103
129
  prevProps = prevProps || {};
104
130
  nextProps = nextProps || {};
105
131
 
132
+ const isSvg = dom instanceof SVGElement;
133
+
106
134
  // Remove old or changed event listeners
107
135
  Object.keys(prevProps)
108
136
  .filter(isEvent)
109
- .filter((key) => !(key in nextProps) || isNew(prevProps, nextProps)(key))
110
- .forEach((name) => {
137
+ .filter(key => !(key in nextProps) || isNew(prevProps, nextProps)(key))
138
+ .forEach(name => {
111
139
  const eventType = name.toLowerCase().substring(2);
112
140
  if (typeof prevProps[name] === 'function') {
113
- dom.removeEventListener(eventType, prevProps[name]);
141
+ (dom as Element).removeEventListener(eventType, prevProps[name]);
114
142
  }
115
143
  });
116
144
 
@@ -118,12 +146,17 @@ function createDom(fiber: Fiber): Node {
118
146
  Object.keys(prevProps)
119
147
  .filter(isProperty)
120
148
  .filter(isGone(prevProps, nextProps))
121
- .forEach((name) => {
122
- // FIX: Handle both `class` and `className`
149
+ .forEach(name => {
123
150
  if (name === 'className' || name === 'class') {
124
- (dom as HTMLElement).removeAttribute("class");
151
+ (dom as Element).removeAttribute('class');
152
+ } else if (name === 'style') {
153
+ (dom as HTMLElement).style.cssText = '';
125
154
  } else {
126
- dom[name] = "";
155
+ if (isSvg) {
156
+ (dom as Element).removeAttribute(name);
157
+ } else {
158
+ (dom as any)[name] = '';
159
+ }
127
160
  }
128
161
  });
129
162
 
@@ -131,14 +164,24 @@ function createDom(fiber: Fiber): Node {
131
164
  Object.keys(nextProps)
132
165
  .filter(isProperty)
133
166
  .filter(isNew(prevProps, nextProps))
134
- .forEach((name) => {
135
- if (name === 'style' && typeof nextProps[name] === 'string') {
136
- (dom as HTMLElement).style.cssText = nextProps[name];
167
+ .forEach(name => {
168
+ if (name === 'style') {
169
+ const style = nextProps[name];
170
+ if (typeof style === 'string') {
171
+ (dom as HTMLElement).style.cssText = style;
172
+ } else if (typeof style === 'object' && style !== null) {
173
+ for (const [key, value] of Object.entries(style)) {
174
+ (dom as HTMLElement).style[key] = value;
175
+ }
176
+ }
137
177
  } else if (name === 'className' || name === 'class') {
138
- // FIX: Handle both `class` and `className`
139
- (dom as HTMLElement).className = nextProps[name];
178
+ (dom as Element).setAttribute('class', nextProps[name]);
140
179
  } else {
141
- dom[name] = nextProps[name];
180
+ if (isSvg) {
181
+ (dom as Element).setAttribute(name, nextProps[name]);
182
+ } else {
183
+ (dom as any)[name] = nextProps[name];
184
+ }
142
185
  }
143
186
  });
144
187
 
@@ -146,15 +189,32 @@ function createDom(fiber: Fiber): Node {
146
189
  Object.keys(nextProps)
147
190
  .filter(isEvent)
148
191
  .filter(isNew(prevProps, nextProps))
149
- .forEach((name) => {
192
+ .forEach(name => {
150
193
  const eventType = name.toLowerCase().substring(2);
151
194
  const handler = nextProps[name];
152
195
  if (typeof handler === 'function') {
153
- dom.addEventListener(eventType, handler);
196
+ (dom as Element).addEventListener(eventType, handler);
197
+ }
198
+ });
199
+
200
+ Object.keys(nextProps)
201
+ .filter(isEvent)
202
+ .filter(isNew(prevProps, nextProps))
203
+ .forEach(name => {
204
+ const eventType = name.toLowerCase().substring(2);
205
+ const handler = nextProps[name];
206
+ if (typeof handler === 'function') {
207
+ // Remove old listener first if it exists
208
+ if (prevProps[name]) {
209
+ dom.removeEventListener(eventType, prevProps[name]);
210
+ }
211
+ // Add new listener with passive: true for better performance
212
+ dom.addEventListener(eventType, handler, { passive: true });
154
213
  }
155
214
  });
156
215
  }
157
216
 
217
+
158
218
  /**
159
219
  * Commits the entire work-in-progress tree to the DOM.
160
220
  */
@@ -227,11 +287,7 @@ export function render(element: VNode, container: Node): void {
227
287
  };
228
288
  deletions = [];
229
289
  nextUnitOfWork = wipRoot;
230
-
231
- if (!isRenderScheduled) {
232
- isRenderScheduled = true;
233
- requestAnimationFrame(workLoop);
234
- }
290
+ requestAnimationFrame(workLoop);
235
291
  }
236
292
 
237
293
  /**
@@ -247,7 +303,7 @@ function workLoop(): void {
247
303
  deletions = [];
248
304
  nextUnitOfWork = wipRoot;
249
305
  }
250
-
306
+
251
307
  while (nextUnitOfWork) {
252
308
  nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
253
309
  }
@@ -257,6 +313,7 @@ function workLoop(): void {
257
313
  }
258
314
  }
259
315
 
316
+
260
317
  /**
261
318
  * Performs work on a single fiber unit.
262
319
  * @param {Fiber} fiber - The fiber to perform work on.
@@ -264,7 +321,7 @@ function workLoop(): void {
264
321
  */
265
322
  function performUnitOfWork(fiber: Fiber): Fiber | null {
266
323
  const isFunctionComponent = fiber.type instanceof Function;
267
-
324
+
268
325
  if (isFunctionComponent) {
269
326
  updateFunctionComponent(fiber);
270
327
  } else {
@@ -289,19 +346,20 @@ function performUnitOfWork(fiber: Fiber): Fiber | null {
289
346
  * Updates a function component fiber.
290
347
  * @param {Fiber} fiber - The function component fiber to update.
291
348
  */
292
- function updateFunctionComponent(fiber: Fiber): void {
349
+ function updateFunctionComponent(fiber: Fiber) {
293
350
  wipFiber = fiber;
294
351
  hookIndex = 0;
295
- wipFiber.hooks = fiber.alternate ? fiber.alternate.hooks : [];
352
+ fiber.hooks = fiber.alternate?.hooks || [];
296
353
 
297
- const children = [fiber.type(fiber.props)]
354
+ // Directly call the component function without memoization
355
+ // The 'createComponent' call is removed.
356
+ const children = [(fiber.type as Function)(fiber.props)]
298
357
  .flat()
299
358
  .filter(child => child != null && typeof child !== 'boolean')
300
- .map(child => typeof child === "object" ? child : createTextElement(child));
359
+ .map(child => typeof child === 'object' ? child : createTextElement(child));
301
360
 
302
361
  reconcileChildren(fiber, children);
303
362
  }
304
-
305
363
  /**
306
364
  * Updates a host component fiber (DOM element).
307
365
  * @param {Fiber} fiber - The host component fiber to update.
@@ -318,32 +376,31 @@ function updateHostComponent(fiber: Fiber): void {
318
376
  * @param {Fiber} wipFiber - The work-in-progress fiber.
319
377
  * @param {VNode[]} elements - The new child elements.
320
378
  */
321
- function reconcileChildren(wipFiber: Fiber, elements: VNode[]): void {
379
+ function reconcileChildren(wipFiber: Fiber, elements: VNode[]) {
322
380
  let index = 0;
323
381
  let oldFiber = wipFiber.alternate?.child;
324
- let prevSibling = null;
325
-
326
- const oldFibersByKey = new Map<string | number, Fiber>();
327
-
382
+ let prevSibling: Fiber | null = null;
383
+
384
+ // Create map of existing fibers by key
385
+ const existingFibers = new Map<string | number | null, Fiber>();
328
386
  while (oldFiber) {
329
- const key = oldFiber.key != null ? oldFiber.key : index;
330
- oldFibersByKey.set(key, oldFiber);
387
+ const key = oldFiber.key ?? index;
388
+ existingFibers.set(key, oldFiber);
331
389
  oldFiber = oldFiber.sibling;
332
390
  index++;
333
391
  }
334
392
 
335
393
  index = 0;
336
- prevSibling = null;
337
-
338
394
  for (; index < elements.length; index++) {
339
395
  const element = elements[index];
340
- const key = element.key != null ? element.key : index;
341
- const oldFiber = oldFibersByKey.get(key);
342
- const sameType = oldFiber && element.type === oldFiber.type;
343
-
396
+ const key = element?.key ?? index;
397
+ const oldFiber = existingFibers.get(key);
398
+
399
+ const sameType = oldFiber && element && element.type === oldFiber.type;
344
400
  let newFiber: Fiber | null = null;
345
401
 
346
402
  if (sameType) {
403
+ // Reuse the fiber
347
404
  newFiber = {
348
405
  type: oldFiber.type,
349
406
  props: element.props,
@@ -351,37 +408,44 @@ function reconcileChildren(wipFiber: Fiber, elements: VNode[]): void {
351
408
  parent: wipFiber,
352
409
  alternate: oldFiber,
353
410
  effectTag: "UPDATE",
354
- key,
411
+ hooks: oldFiber.hooks,
412
+ key
355
413
  };
356
- oldFibersByKey.delete(key);
357
- } else {
358
- if (element) {
359
- newFiber = {
360
- type: element.type,
361
- props: element.props,
362
- dom: null,
363
- parent: wipFiber,
364
- alternate: null,
365
- effectTag: "PLACEMENT",
366
- key,
367
- };
368
- }
414
+ existingFibers.delete(key);
415
+ } else if (element) {
416
+ // Create new fiber
417
+ newFiber = {
418
+ type: element.type,
419
+ props: element.props,
420
+ dom: null,
421
+ parent: wipFiber,
422
+ alternate: null,
423
+ effectTag: "PLACEMENT",
424
+ key
425
+ };
426
+ }
427
+
428
+ if (oldFiber && !sameType) {
429
+ oldFiber.effectTag = "DELETION";
430
+ deletions.push(oldFiber);
369
431
  }
370
432
 
371
- if (prevSibling == null) {
433
+ if (index === 0) {
372
434
  wipFiber.child = newFiber;
373
- } else {
435
+ } else if (prevSibling && newFiber) {
374
436
  prevSibling.sibling = newFiber;
375
437
  }
376
- prevSibling = newFiber;
438
+
439
+ if (newFiber) {
440
+ prevSibling = newFiber;
441
+ }
377
442
  }
378
443
 
379
- oldFibersByKey.forEach(fiber => {
444
+ // Mark remaining old fibers for deletion
445
+ existingFibers.forEach(fiber => {
380
446
  fiber.effectTag = "DELETION";
381
447
  deletions.push(fiber);
382
448
  });
383
-
384
- if (prevSibling) prevSibling.sibling = null;
385
449
  }
386
450
 
387
451
  /**
@@ -432,39 +496,45 @@ function createTextElement(text: string): VNode {
432
496
  * @param {T|(() => T)} initial - The initial state value or initializer function.
433
497
  * @returns {[T, (action: T | ((prevState: T) => T)) => void]} A stateful value and a function to update it.
434
498
  */
435
- export function useState<T>(
436
- initial: T | (() => T)
437
- ): [T, (action: T | ((prevState: T) => T)) => void] {
499
+
500
+
501
+
502
+ export function useState<T>(initial: T | (() => T)): [T, (action: T | ((prevState: T) => T)) => void] {
438
503
  if (!wipFiber) {
439
504
  throw new Error("Hooks can only be called inside a Vader.js function component.");
440
505
  }
441
506
 
442
- let hook = wipFiber.hooks[hookIndex];
507
+ let hook = wipFiber.hooks[hookIndex];
443
508
  if (!hook) {
444
509
  hook = {
445
- state: typeof initial === "function" ? (initial as () => T)() : initial,
446
- queue: []
510
+ state: typeof initial === "function" ? (initial as () => T)() : initial,
511
+ queue: [],
512
+ _needsUpdate: false
447
513
  };
448
514
  wipFiber.hooks[hookIndex] = hook;
449
515
  }
450
516
 
451
- hook.queue.forEach((action) => {
452
- hook.state = typeof action === "function" ? action(hook.state) : action;
453
- });
454
- hook.queue = [];
455
-
456
517
  const setState = (action: T | ((prevState: T) => T)) => {
457
- hook.queue.push(action);
458
- if (!isRenderScheduled) {
459
- isRenderScheduled = true;
518
+ // Calculate new state based on current state
519
+ const newState = typeof action === "function"
520
+ ? (action as (prevState: T) => T)(hook.state)
521
+ : action;
522
+
523
+ hook.state = newState;
524
+
525
+ // Reset work-in-progress root to trigger re-r
526
+
527
+ deletions = [];
528
+ nextUnitOfWork = wipRoot;
529
+
530
+ // Start the render process
460
531
  requestAnimationFrame(workLoop);
461
- }
462
532
  };
463
533
 
464
534
  hookIndex++;
465
535
  return [hook.state, setState];
466
536
  }
467
-
537
+
468
538
  /**
469
539
  * A React-like useEffect hook for side effects.
470
540
  * @param {Function} callback - The effect callback.
@@ -529,6 +599,10 @@ export function Match({ when, children }: { when: boolean, children: VNode[] }):
529
599
  return when ? children : null;
530
600
  }
531
601
 
602
+ export function Show({ when, children }: { when: boolean, children: VNode[] }): VNode | null {
603
+ return when ? children : null;
604
+ }
605
+
532
606
  /**
533
607
  * A React-like useRef hook for mutable references.
534
608
  * @template T
@@ -923,3 +997,4 @@ export function useOnClickOutside(ref: { current: HTMLElement | null }, handler:
923
997
  };
924
998
  }, [ref, handler]);
925
999
  }
1000
+
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";
@@ -29,6 +19,17 @@ const colors = {
29
19
  cyan: "\x1b[36m",
30
20
  };
31
21
 
22
+ function safeWatch(dir, cb) {
23
+ try {
24
+ const watcher = fsSync.watch(dir, { recursive: true }, cb);
25
+ watcher.on("error", (err) => logger.warn(`Watcher error on ${dir}:`, err));
26
+ return watcher;
27
+ } catch (err) {
28
+ logger.warn(`Failed to watch ${dir}:`, err);
29
+ }
30
+ }
31
+
32
+
32
33
  const logger = {
33
34
  _log: (color, ...args) => console.log(color, ...args, colors.reset),
34
35
  info: (...args) => logger._log(colors.cyan, "ℹ", ...args),
@@ -46,8 +47,8 @@ async function timedStep(name, fn) {
46
47
  const duration = (performance.now() - start).toFixed(2);
47
48
  logger.success(`Finished '${name}' in ${duration}ms`);
48
49
  } catch (e) {
49
- logger.error(`Error during '${name}':`, e.message);
50
- process.exit(1);
50
+ logger.error(`Error during '${name}':`, e);
51
+ if (!isDev) process.exit(1);
51
52
  }
52
53
  }
53
54
 
@@ -82,9 +83,10 @@ const vaderAPI = {
82
83
 
83
84
  async function loadConfig() {
84
85
  try {
85
- const configModule = await import(path.join(PROJECT_ROOT, "vaderjs.config.js"));
86
+ const configModule = await import(path.join(PROJECT_ROOT, "vaderjs.config.js"));
86
87
  return configModule.default || configModule;
87
88
  } catch {
89
+ console.log(path.join(PROJECT_ROOT, "vaderjs.config.js"))
88
90
  logger.warn("No 'vader.config.js' found, using defaults.");
89
91
  return {};
90
92
  }
@@ -101,13 +103,14 @@ async function runPluginHook(hookName) {
101
103
  try {
102
104
  await plugin[hookName](vaderAPI);
103
105
  } catch (e) {
104
- logger.error(`Plugin hook error (${hookName} in ${plugin.name || 'anonymous plugin'}):`, e);
106
+ logger.error(`Plugin hook error (${hookName} in ${plugin.name || 'anonymous'}):`, e);
105
107
  }
106
108
  }
107
109
  }
108
110
  }
109
111
 
110
112
 
113
+
111
114
  // --- BUILD LOGIC ---
112
115
 
113
116
  /**
@@ -137,10 +140,43 @@ async function buildVaderCore() {
137
140
  function patchHooksUsage(code) {
138
141
  return code.replace(/import\s+{[^}]*use(State|Effect|Memo|Navigation)[^}]*}\s+from\s+['"]vaderjs['"];?\n?/g, "");
139
142
  }
143
+ function publicAssetPlugin() {
144
+ return {
145
+ name: "public-asset-replacer",
146
+ setup(build) {
147
+ build.onLoad({ filter: /\.(js|ts|jsx|tsx|html)$/ }, async (args) => {
148
+ let code = await fs.readFile(args.path, "utf8");
149
+
150
+ code = code.replace(/\{\{public:(.+?)\}\}/g, (_, relPath) => {
151
+ const absPath = path.join(PUBLIC_DIR, relPath.trim());
152
+ if (fsSync.existsSync(absPath)) {
153
+ return "/" + relPath.trim().replace(/\\/g, "/");
154
+ }
155
+ logger.warn(`Public asset not found: ${relPath}`);
156
+ return relPath;
157
+ });
158
+
159
+ return {
160
+ contents: code,
161
+ loader: args.path.endsWith(".html")
162
+ ? "text"
163
+ : args.path.endsWith(".tsx")
164
+ ? "tsx"
165
+ : args.path.endsWith(".jsx")
166
+ ? "jsx"
167
+ : args.path.endsWith(".ts")
168
+ ? "ts"
169
+ : "js",
170
+ };
171
+ });
172
+ },
173
+ };
174
+ }
140
175
 
141
176
  /**
142
177
  * Step 3: Pre-processes all files in `/src` into a temporary directory.
143
178
  */
179
+
144
180
  async function preprocessSources(srcDir, tempDir) {
145
181
  await fs.mkdir(tempDir, { recursive: true });
146
182
  for (const entry of await fs.readdir(srcDir, { withFileTypes: true })) {
@@ -151,7 +187,7 @@ async function preprocessSources(srcDir, tempDir) {
151
187
  await preprocessSources(srcPath, destPath);
152
188
  } else if (/\.(tsx|jsx|ts|js)$/.test(entry.name)) {
153
189
  let content = await fs.readFile(srcPath, "utf8");
154
- content = patchHooksUsage(content);
190
+ content = patchHooksUsage(content);
155
191
  await fs.writeFile(destPath, content);
156
192
  } else {
157
193
  await fs.copyFile(srcPath, destPath);
@@ -189,6 +225,9 @@ async function buildSrc() {
189
225
  jsxImportSource: "vaderjs",
190
226
  target: "browser",
191
227
  minify: false,
228
+ plugins: [
229
+ publicAssetPlugin(),
230
+ ],
192
231
  external: ["vaderjs"],
193
232
  });
194
233
  }
@@ -210,57 +249,61 @@ async function copyPublicAssets() {
210
249
  return;
211
250
  }
212
251
 
213
- // Ensure the dist directory exists
214
252
  if (!fsSync.existsSync(DIST_DIR)) {
215
253
  await fs.mkdir(DIST_DIR, { recursive: true });
216
254
  }
217
255
 
218
- const devClientScript = isDev ? `
219
- <script>
220
- new WebSocket("ws://" + location.host + "/__hmr").onmessage = (msg) => {
221
- if (msg.data === "reload") location.reload();
222
- };
223
- </script>` : "";
256
+ const devClientScript = isDev
257
+ ? `<script>
258
+ new WebSocket("ws://" + location.host + "/__hmr").onmessage = (msg) => {
259
+ if (msg.data === "reload") location.reload();
260
+ };
261
+ </script>`
262
+ : "";
224
263
 
225
264
  const entries = fsSync.readdirSync(APP_DIR, { recursive: true })
226
265
  .filter(file => /index\.(jsx|tsx)$/.test(file))
227
266
  .map(file => ({
228
- name: path.dirname(file) === '.' ? 'index' : path.dirname(file).replace(/\\/g, '/'),
229
- path: path.join(APP_DIR, file)
267
+ name: path.dirname(file) === '.' ? 'index' : path.dirname(file).replace(/\\/g, '/'),
268
+ path: path.join(APP_DIR, file)
230
269
  }));
231
270
 
271
+ // Helper to resolve any asset path from /public
272
+ function resolvePublicPath(p) {
273
+ const assetPath = p.replace(/^(\.\/|\/)/, ""); // strip leading ./ or /
274
+ const absPath = path.join(PUBLIC_DIR, assetPath);
275
+ if (fsSync.existsSync(absPath)) {
276
+ return "/" + assetPath.replace(/\\/g, "/");
277
+ }
278
+ return p; // leave unchanged if not in public
279
+ }
280
+
232
281
  for (const { name, path: entryPath } of entries) {
233
- // Check for the specific case where 'name' could be 'index.js' and prevent duplication
234
282
  const outDir = path.join(DIST_DIR, name === 'index' ? '' : name);
235
- const outJsPath = path.join(outDir, 'index.js'); // Output JavaScript file path
236
-
237
- // Ensure the output directory exists
283
+ const outJsPath = path.join(outDir, 'index.js');
238
284
  await fs.mkdir(outDir, { recursive: true });
239
285
 
240
- // **FIXED CSS HANDLING**: Find, copy, and correctly link CSS files
286
+ // --- CSS HANDLING ---
241
287
  const cssLinks = [];
242
- const cssContent = await fs.readFile(entryPath, "utf8");
243
- const cssImports = [...cssContent.matchAll(/import\s+['"](.*\.css)['"]/g)];
244
-
288
+ let content = await fs.readFile(entryPath, "utf8");
289
+ const cssImports = [...content.matchAll(/import\s+['"](.*\.css)['"]/g)];
245
290
  for (const match of cssImports) {
246
- const cssImportPath = match[1]; // e.g., './styles.css'
247
- const sourceCssPath = path.resolve(path.dirname(entryPath), cssImportPath);
248
- if (fsSync.existsSync(sourceCssPath)) {
249
- const relativeCssPath = path.relative(APP_DIR, sourceCssPath);
250
- const destCssPath = path.join(DIST_DIR, relativeCssPath);
251
-
252
- await fs.mkdir(path.dirname(destCssPath), { recursive: true });
253
- await fs.copyFile(sourceCssPath, destCssPath);
254
-
255
- const htmlRelativePath = path.relative(outDir, destCssPath).replace(/\\/g, '/');
256
- cssLinks.push(`<link rel="stylesheet" href="${htmlRelativePath}">`);
257
- } else {
258
- logger.warn(`CSS file not found: ${sourceCssPath}`);
259
- }
291
+ const cssImportPath = match[1];
292
+ const sourceCssPath = path.resolve(path.dirname(entryPath), cssImportPath);
293
+ if (fsSync.existsSync(sourceCssPath)) {
294
+ const relativeCssPath = path.relative(APP_DIR, sourceCssPath);
295
+ const destCssPath = path.join(DIST_DIR, relativeCssPath);
296
+ await fs.mkdir(path.dirname(destCssPath), { recursive: true });
297
+ await fs.copyFile(sourceCssPath, destCssPath);
298
+ const htmlRelativePath = path.relative(outDir, destCssPath).replace(/\\/g, '/');
299
+ cssLinks.push(`<link rel="stylesheet" href="${htmlRelativePath}">`);
300
+ } else {
301
+ logger.warn(`CSS file not found: ${sourceCssPath}`);
302
+ }
260
303
  }
261
304
 
262
- // Update the script tag to use relative paths for index.js
263
- const htmlContent = `<!DOCTYPE html>
305
+ // --- HTML GENERATION ---
306
+ let htmlContent = `<!DOCTYPE html>
264
307
  <html lang="en">
265
308
  <head>
266
309
  <meta charset="UTF-8" />
@@ -272,39 +315,56 @@ async function copyPublicAssets() {
272
315
  <body>
273
316
  <div id="app"></div>
274
317
  <script type="module">
275
- import App from '${name !== 'index' ? name : ''}/index.js';
318
+ import App from '${name !== 'index' ? "/" + name : ''}/index.js';
276
319
  import * as Vader from '/src/vader/index.js';
277
- window.Vader = Vader
320
+ window.Vader = Vader;
278
321
  Vader.render(Vader.createElement(App, null), document.getElementById("app"));
279
322
  </script>
280
323
  ${devClientScript}
281
324
  </body>
282
325
  </html>`;
283
326
 
284
- await fs.writeFile(path.join(outDir, "index.html"), htmlContent);
327
+ // --- FIX ASSET PATHS IN HTML ---
328
+ htmlContent = htmlContent.replace(
329
+ /(["'(])([^"'()]+?\.(png|jpe?g|gif|svg|webp|ico))(["')])/gi,
330
+ (match, p1, assetPath, ext, p4) => p1 + resolvePublicPath(assetPath) + p4
331
+ );
285
332
 
286
- // Log for debugging
333
+ await fs.writeFile(path.join(outDir, "index.html"), htmlContent);
287
334
 
288
- // Build the JavaScript file and ensure it uses the correct paths
335
+ // --- JS BUILD ---
289
336
  await build({
290
337
  entrypoints: [entryPath],
291
- outdir: outDir, // Pass the directory path to outdir
338
+ outdir: outDir,
292
339
  target: "browser",
293
340
  minify: false,
294
341
  sourcemap: "external",
295
342
  external: ["vaderjs"],
296
343
  jsxFactory: "e",
297
344
  jsxFragment: "Fragment",
345
+ plugins: [
346
+ publicAssetPlugin(),
347
+ ],
298
348
  jsxImportSource: "vaderjs",
299
349
  });
300
350
 
301
- // After build, replace the 'vaderjs' import to the correct path
351
+ // --- FIX IMPORT PATHS IN JS ---
302
352
  let jsContent = await fs.readFile(outJsPath, "utf8");
353
+
354
+ // Vader import fix
303
355
  jsContent = jsContent.replace(/from\s+['"]vaderjs['"]/g, `from '/src/vader/index.js'`);
356
+
357
+ // Asset path fix for JS
358
+ jsContent = jsContent.replace(
359
+ /(["'(])([^"'()]+?\.(png|jpe?g|gif|svg|webp|ico))(["')])/gi,
360
+ (match, p1, assetPath, ext, p4) => p1 + resolvePublicPath(assetPath) + p4
361
+ );
362
+
304
363
  await fs.writeFile(outJsPath, jsContent);
305
364
  }
306
365
  }
307
366
 
367
+
308
368
 
309
369
 
310
370
  async function buildAll(isDev = false) {
@@ -379,7 +439,7 @@ async function runDevServer() {
379
439
 
380
440
  const watchDirs = [APP_DIR, SRC_DIR, PUBLIC_DIR].filter(fsSync.existsSync);
381
441
  for (const dir of watchDirs) {
382
- fsSync.watch(dir, { recursive: true }, debouncedBuild);
442
+ safeWatch(dir, debouncedBuild);
383
443
  }
384
444
  }
385
445
 
@@ -430,6 +490,7 @@ console.log(banner);
430
490
  const command = process.argv[2];
431
491
 
432
492
  if (command === "dev") {
493
+ globalThis.isDev = true
433
494
  await runDevServer();
434
495
  } else if (command === "build") {
435
496
  await buildAll(false);
@@ -454,3 +515,9 @@ main().catch(err => {
454
515
  logger.error("An unexpected error occurred:", err);
455
516
  process.exit(1);
456
517
  });
518
+ process.on("unhandledRejection", (err) => {
519
+ logger.error("Unhandled Promise rejection:", err);
520
+ });
521
+ process.on("uncaughtException", (err) => {
522
+ logger.error("Uncaught Exception:", err);
523
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vaderjs",
3
- "version": "2.3.3",
3
+ "version": "2.3.5",
4
4
  "description": "A simple and powerful JavaScript library for building modern web applications.",
5
5
  "bin": {
6
6
  "vaderjs": "./main.js"