vaderjs 2.3.2 → 2.3.4

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 (4) hide show
  1. package/cli.ts +157 -124
  2. package/index.ts +158 -83
  3. package/main.js +23 -5
  4. package/package.json +1 -1
package/cli.ts CHANGED
@@ -2,77 +2,77 @@
2
2
 
3
3
  import fs from "fs/promises";
4
4
  import fsSync from "fs";
5
- import path from "path";
5
+ import path from "path";
6
6
  import readline from "readline";
7
7
 
8
8
  function ask(question) {
9
- const rl = readline.createInterface({
10
- input: process.stdin,
11
- output: process.stdout,
12
- });
13
- return new Promise((resolve) =>
14
- rl.question(question + " ", (answer) => {
15
- rl.close();
16
- resolve(answer.trim());
17
- })
18
- );
9
+ const rl = readline.createInterface({
10
+ input: process.stdin,
11
+ output: process.stdout,
12
+ });
13
+ return new Promise((resolve) =>
14
+ rl.question(question + " ", (answer) => {
15
+ rl.close();
16
+ resolve(answer.trim());
17
+ })
18
+ );
19
19
  }
20
20
 
21
21
  async function run(cmd: string, args: string[] = []) {
22
- try {
23
- const proc = Bun.spawn([cmd, ...args], {
24
- stdout: "inherit",
25
- stderr: "inherit",
26
- });
27
-
28
- const status = await proc.exited;
29
- if (status !== 0) {
30
- console.error(`Command failed: ${cmd} ${args.join(" ")}`);
31
- process.exit(1);
22
+ try {
23
+ const proc = Bun.spawn([cmd, ...args], {
24
+ stdout: "inherit",
25
+ stderr: "inherit",
26
+ });
27
+
28
+ const status = await proc.exited;
29
+ if (status !== 0) {
30
+ console.error(`Command failed: ${cmd} ${args.join(" ")}`);
31
+ process.exit(1);
32
+ }
33
+ } catch (error) {
34
+ console.error(`Error executing command: ${error}`);
35
+ process.exit(1);
32
36
  }
33
- } catch (error) {
34
- console.error(`Error executing command: ${error}`);
35
- process.exit(1);
36
- }
37
37
  }
38
38
 
39
39
  export async function init() {
40
- console.log("🚀 Welcome to Vader.js project initializer!");
41
-
42
- const cwd = process.cwd();
43
- let projectDir = await ask(
44
- `Enter the directory to initialize the project (default: current dir):`
45
- );
46
- if (!projectDir) projectDir = ".";
47
-
48
- projectDir = path.resolve(cwd, projectDir);
49
- if (!fsSync.existsSync(projectDir)) {
50
- await fs.mkdir(projectDir, { recursive: true });
51
- console.log(`Created directory: ${projectDir}`);
52
- }
53
-
54
- // Confirm Tailwind usage
55
- let useTailwind = await ask("Include TailwindCSS v4 support? (y/n):");
56
- while (!["y", "n", "yes", "no"].includes(useTailwind)) {
57
- useTailwind = await ask("Please answer 'y' or 'n':");
58
- }
59
- const wantsTailwind = useTailwind === "y" || useTailwind === "yes";
60
-
61
- // Create folders: app, src, public
62
- const appDir = path.join(projectDir, "app");
63
- const srcDir = path.join(projectDir, "src");
64
- const publicDir = path.join(projectDir, "public");
40
+ console.log("🚀 Welcome to Vader.js project initializer!");
41
+
42
+ const cwd = process.cwd();
43
+ let projectDir = await ask(
44
+ `Enter the directory to initialize the project (default: current dir):`
45
+ );
46
+ if (!projectDir) projectDir = ".";
47
+
48
+ projectDir = path.resolve(cwd, projectDir);
49
+ if (!fsSync.existsSync(projectDir)) {
50
+ await fs.mkdir(projectDir, { recursive: true });
51
+ console.log(`Created directory: ${projectDir}`);
52
+ }
65
53
 
66
- for (const dir of [appDir, srcDir, publicDir]) {
67
- if (!fsSync.existsSync(dir)) {
68
- await fs.mkdir(dir, { recursive: true });
69
- console.log(`Created folder: ${dir}`);
54
+ // Confirm Tailwind usage
55
+ let useTailwind = await ask("Include TailwindCSS v4 support? (y/n):");
56
+ while (!["y", "n", "yes", "no"].includes(useTailwind)) {
57
+ useTailwind = await ask("Please answer 'y' or 'n':");
58
+ }
59
+ const wantsTailwind = useTailwind === "y" || useTailwind === "yes";
60
+
61
+ // Create folders: app, src, public
62
+ const appDir = path.join(projectDir, "app");
63
+ const srcDir = path.join(projectDir, "src");
64
+ const publicDir = path.join(projectDir, "public");
65
+
66
+ for (const dir of [appDir, srcDir, publicDir]) {
67
+ if (!fsSync.existsSync(dir)) {
68
+ await fs.mkdir(dir, { recursive: true });
69
+ console.log(`Created folder: ${dir}`);
70
+ }
70
71
  }
71
- }
72
72
 
73
- // Create example app/index.jsx with counter
74
- const counterCode = wantsTailwind
75
- ? `import { useState } from "vaderjs";
73
+ // Create example app/index.jsx with counter
74
+ const counterCode = wantsTailwind
75
+ ? `import { useState } from "vaderjs";
76
76
 
77
77
  export default function Counter() {
78
78
  let [count, setCount] = useState(0);
@@ -90,7 +90,7 @@ export default function Counter() {
90
90
  );
91
91
  }
92
92
  `
93
- : `import { useState } from "vaderjs";
93
+ : `import { useState } from "vaderjs";
94
94
 
95
95
  export default function Counter() {
96
96
  let [count, setCount] = useState(0);
@@ -104,69 +104,53 @@ export default function Counter() {
104
104
  }
105
105
  `;
106
106
 
107
- await fs.writeFile(path.join(appDir, "index.jsx"), counterCode);
108
- console.log(`Created example route: ${path.join("app", "index.jsx")}`);
107
+ await fs.writeFile(path.join(appDir, "index.jsx"), counterCode);
108
+ console.log(`Created example route: ${path.join("app", "index.jsx")}`);
109
109
 
110
- // Create public/styles.css
111
- if (wantsTailwind) {
112
- await fs.writeFile(path.join(publicDir, "styles.css"), `@import 'tailwindcss';\n`);
113
- } else {
114
- await fs.writeFile(path.join(publicDir, "styles.css"), `/* Add your styles here */\n`);
115
- }
116
- console.log(`Created public/styles.css`);
117
-
118
- // Create minimal package.json if not exist
119
- const pkgJsonPath = path.join(projectDir, "package.json");
120
- if (!fsSync.existsSync(pkgJsonPath)) {
121
- const pkg = {
122
- name: path.basename(projectDir),
123
- version: "1.0.0",
124
- scripts: {
125
- start: "bun run vaderjs build && bun run vaderjs serve",
126
- build: "bun run vaderjs build",
127
- dev: "bun run vaderjs dev",
128
- },
129
- dependencies: {
130
- vaderjs: "latest",
131
- },
132
- };
133
- await fs.writeFile(pkgJsonPath, JSON.stringify(pkg, null, 2));
134
- console.log(`Created package.json`);
135
- }
110
+ // Create public/styles.css
111
+ if (wantsTailwind) {
112
+ await fs.writeFile(path.join(publicDir, "styles.css"), `@import 'tailwindcss';\n`);
113
+ } else {
114
+ await fs.writeFile(path.join(publicDir, "styles.css"), `/* Add your styles here */\n`);
115
+ }
116
+ console.log(`Created public/styles.css`);
136
117
 
137
- // Install dependencies: vaderjs + optionally tailwindcss, postcss plugins, autoprefixer
138
- console.log("Installing dependencies with Bun...");
139
- const deps = ["vaderjs", "autoprefixer"];
140
- if (wantsTailwind) {
141
- deps.push("tailwindcss@4", "@tailwindcss/postcss", "postcss-cli");
142
- }
143
- await run("bun", ["install", ...deps]);
144
- console.log(" Dependencies installed.");
118
+ // Create minimal package.json if not exist
119
+
120
+
121
+ // Install dependencies: vaderjs + optionally tailwindcss, postcss plugins, autoprefixer
122
+ console.log("Installing dependencies with Bun...");
123
+ const deps = ["vaderjs", "autoprefixer"];
124
+ if (wantsTailwind) {
125
+ deps.push("tailwindcss@4", "@tailwindcss/postcss", "postcss-cli");
126
+ }
127
+ await run("bun", ["install", ...deps]);
128
+ console.log("✅ Dependencies installed.");
145
129
 
146
- // If Tailwind requested, create minimal tailwind.config.cjs and postcss.config.cjs
147
- if (wantsTailwind) {
148
- const tailwindConfig = `module.exports = {
130
+ // If Tailwind requested, create minimal tailwind.config.cjs and postcss.config.cjs
131
+ if (wantsTailwind) {
132
+ const tailwindConfig = `module.exports = {
149
133
  content: ["./app/**/*.{js,jsx,ts,tsx}"],
150
134
  theme: {
151
135
  extend: {},
152
136
  },
153
137
  plugins: [],
154
138
  };`;
155
- await fs.writeFile(path.join(projectDir, "tailwind.config.cjs"), tailwindConfig);
156
- console.log("Created tailwind.config.cjs");
139
+ await fs.writeFile(path.join(projectDir, "tailwind.config.cjs"), tailwindConfig);
140
+ console.log("Created tailwind.config.cjs");
157
141
 
158
- const postcssConfig = `export default {
142
+ const postcssConfig = `export default {
159
143
  plugins: {
160
144
  "@tailwindcss/postcss": {},
161
145
  autoprefixer: {},
162
146
  }
163
147
  };`;
164
- await fs.writeFile(path.join(projectDir, "postcss.config.cjs"), postcssConfig);
165
- console.log("Created postcss.config.cjs");
166
- }
148
+ await fs.writeFile(path.join(projectDir, "postcss.config.cjs"), postcssConfig);
149
+ console.log("Created postcss.config.cjs");
150
+ }
167
151
 
168
- // Create vaderjs.config.ts regardless, add Tailwind plugin if needed
169
- const vaderConfig = `import defineConfig from "vaderjs/config";
152
+ // Create vaderjs.config.ts regardless, add Tailwind plugin if needed
153
+ const vaderConfig = `import defineConfig from "vaderjs/config";
170
154
  ${wantsTailwind ? 'import tailwind from "vaderjs/plugins/tailwind";' : ''}
171
155
 
172
156
  export default defineConfig({
@@ -174,22 +158,71 @@ export default defineConfig({
174
158
  plugins: [${wantsTailwind ? "tailwind" : ""}],
175
159
  });`;
176
160
 
177
- await fs.writeFile(path.join(projectDir, "vaderjs.config.ts"), vaderConfig);
178
- console.log("Created vaderjs.config.ts");
179
-
180
- // Create jsconfig.json for VSCode/IDE support
181
- const jsConfig = {
182
- compilerOptions: {
183
- jsx: "react",
184
- jsxFactory: "Vader.createElement",
185
- jsxFragmentFactory: "Fragment",
186
- },
187
- };
188
- await fs.writeFile(path.join(projectDir, "jsconfig.json"), JSON.stringify(jsConfig, null, 2));
189
- console.log("Created jsconfig.json");
190
-
191
- // Final instructions
192
- console.log(`\n🎉 Vader.js project initialized at:\n${projectDir}`);
193
- console.log(`Run cd ${projectDir} to navigate into the project folder`);
194
- console.log("Run `bun run dev` or your build script to get started.");
161
+ await fs.writeFile(path.join(projectDir, "vaderjs.config.ts"), vaderConfig);
162
+ console.log("Created vaderjs.config.ts");
163
+
164
+ // Create jsconfig.json for VSCode/IDE support
165
+ const jsConfig = {
166
+ compilerOptions: {
167
+ jsx: "react",
168
+ jsxFactory: "Vader.createElement",
169
+ jsxFragmentFactory: "Fragment",
170
+ },
171
+ };
172
+ await fs.writeFile(path.join(projectDir, "jsconfig.json"), JSON.stringify(jsConfig, null, 2));
173
+ console.log("Created jsconfig.json");
174
+
175
+ // Final instructions
176
+ const pkgJsonPath = path.join(projectDir, "package.json");
177
+
178
+ if (!fsSync.existsSync(pkgJsonPath)) {
179
+ // If package.json doesn't exist, create it with basic content
180
+ const pkg = {
181
+ name: path.basename(projectDir),
182
+ version: "1.0.0",
183
+ scripts: {
184
+ start: "bun run vaderjs build && bun run vaderjs serve",
185
+ build: "bun run vaderjs build",
186
+ dev: "bun run vaderjs dev",
187
+ },
188
+ dependencies: {
189
+ vaderjs: "latest",
190
+ },
191
+ };
192
+
193
+ // If Tailwind is requested, add it to the dependencies
194
+ if (wantsTailwind) {
195
+ pkg.dependencies.tailwindcss = "latest";
196
+ pkg.dependencies["@tailwindcss/postcss"] = "latest";
197
+ pkg.dependencies.postcss = "latest";
198
+ pkg.dependencies.autoprefixer = "latest";
199
+ }
200
+
201
+ await fs.writeFile(pkgJsonPath, JSON.stringify(pkg, null, 2));
202
+ console.log(`Created package.json`);
203
+ } else {
204
+ // If package.json exists, update it by adding Tailwind if it's not there
205
+ const pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, "utf8"));
206
+
207
+ // Only update the dependencies and scripts if Tailwind is enabled
208
+ if (wantsTailwind && !pkgJson.dependencies.tailwindcss) {
209
+ pkgJson.dependencies.tailwindcss = "latest";
210
+ pkgJson.dependencies["@tailwindcss/postcss"] = "latest";
211
+ pkgJson.dependencies.postcss = "latest";
212
+ pkgJson.dependencies.autoprefixer = "latest";
213
+ }
214
+
215
+ // Ensure the scripts are in place (if they're not there already)
216
+ if (!pkgJson.scripts) pkgJson.scripts = {};
217
+ pkgJson.scripts.start = pkgJson.scripts.start || "bun run vaderjs build && bun run vaderjs serve";
218
+ pkgJson.scripts.build = pkgJson.scripts.build || "bun run vaderjs build";
219
+ pkgJson.scripts.dev = pkgJson.scripts.dev || "bun run vaderjs dev";
220
+
221
+ await fs.writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
222
+ console.log(`Updated package.json`);
223
+ }
224
+
225
+ console.log(`\n🎉 Vader.js project initialized at:\n${projectDir}`);
226
+ console.log(`Run cd ${projectDir} to navigate into the project folder`);
227
+ console.log("Run `bun run dev` or your build script to get started.");
195
228
  }
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
@@ -29,6 +29,17 @@ const colors = {
29
29
  cyan: "\x1b[36m",
30
30
  };
31
31
 
32
+ function safeWatch(dir, cb) {
33
+ try {
34
+ const watcher = fsSync.watch(dir, { recursive: true }, cb);
35
+ watcher.on("error", (err) => logger.warn(`Watcher error on ${dir}:`, err));
36
+ return watcher;
37
+ } catch (err) {
38
+ logger.warn(`Failed to watch ${dir}:`, err);
39
+ }
40
+ }
41
+
42
+
32
43
  const logger = {
33
44
  _log: (color, ...args) => console.log(color, ...args, colors.reset),
34
45
  info: (...args) => logger._log(colors.cyan, "ℹ", ...args),
@@ -46,8 +57,8 @@ async function timedStep(name, fn) {
46
57
  const duration = (performance.now() - start).toFixed(2);
47
58
  logger.success(`Finished '${name}' in ${duration}ms`);
48
59
  } catch (e) {
49
- logger.error(`Error during '${name}':`, e.message);
50
- process.exit(1);
60
+ logger.error(`Error during '${name}':`, e);
61
+ if (!isDev) process.exit(1);
51
62
  }
52
63
  }
53
64
 
@@ -101,13 +112,14 @@ async function runPluginHook(hookName) {
101
112
  try {
102
113
  await plugin[hookName](vaderAPI);
103
114
  } catch (e) {
104
- logger.error(`Plugin hook error (${hookName} in ${plugin.name || 'anonymous plugin'}):`, e);
115
+ logger.error(`Plugin hook error (${hookName} in ${plugin.name || 'anonymous'}):`, e);
105
116
  }
106
117
  }
107
118
  }
108
119
  }
109
120
 
110
121
 
122
+
111
123
  // --- BUILD LOGIC ---
112
124
 
113
125
  /**
@@ -272,7 +284,7 @@ async function copyPublicAssets() {
272
284
  <body>
273
285
  <div id="app"></div>
274
286
  <script type="module">
275
- import App from '${name !== 'index' ? name : ''}/index.js';
287
+ import App from '${name !== 'index' ? "/" + name : ''}/index.js';
276
288
  import * as Vader from '/src/vader/index.js';
277
289
  window.Vader = Vader
278
290
  Vader.render(Vader.createElement(App, null), document.getElementById("app"));
@@ -379,7 +391,7 @@ async function runDevServer() {
379
391
 
380
392
  const watchDirs = [APP_DIR, SRC_DIR, PUBLIC_DIR].filter(fsSync.existsSync);
381
393
  for (const dir of watchDirs) {
382
- fsSync.watch(dir, { recursive: true }, debouncedBuild);
394
+ safeWatch(dir, debouncedBuild);
383
395
  }
384
396
  }
385
397
 
@@ -454,3 +466,9 @@ main().catch(err => {
454
466
  logger.error("An unexpected error occurred:", err);
455
467
  process.exit(1);
456
468
  });
469
+ process.on("unhandledRejection", (err) => {
470
+ logger.error("Unhandled Promise rejection:", err);
471
+ });
472
+ process.on("uncaughtException", (err) => {
473
+ logger.error("Uncaught Exception:", err);
474
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vaderjs",
3
- "version": "2.3.2",
3
+ "version": "2.3.4",
4
4
  "description": "A simple and powerful JavaScript library for building modern web applications.",
5
5
  "bin": {
6
6
  "vaderjs": "./main.js"