chocola 1.3.6 → 1.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,427 +1,41 @@
1
- # 🍫 Chocola JS
1
+ # chocola
2
2
 
3
- > A Sweet Taste of Reactive Web Development
3
+ Chocola is a JavaScript library for creating web user interfaces.
4
4
 
5
- Chocola is a lightweight, reactive component-based web framework that brings simplicity and modularity to modern web development. Build dynamic components with reactivity, global state management, and a developer experience as sweet as chocolate.
5
+ `chocola` only contains the functionality to build and serve web static builds. For production you may want to implement `chocola` to your workflow with other frameworks as Vite until official workflows are provided.
6
6
 
7
- ## ✨ Features
8
-
9
- - **🧩 Component-Based Architecture** - Build reusable, modular components with ease
10
- - **⚡ Reactive Runtime** - Components automatically update when reactive variables change
11
- - **🔄 Reactive Variables** - Mutable state with `&{sfx.var}` syntax and automatic re-rendering
12
- - **🎯 Context System** - Pass data to components using intuitive `ctx.*` attributes
13
- - **🌐 Global State Management** - Share state across components with `sfx` variables
14
- - **📡 Variable Subscription** - Subscribe to state changes across your application
15
- - **🎭 Conditional Rendering** - Show/hide components dynamically with `chif` attribute
16
- - **🔌 Component Lifecycle API** - Public APIs for mounting, manipulating, and removing components
17
- - **📦 Built-in Bundler** - Automatic compilation and optimization
18
- - **🔥 Hot Reload Development** - See changes instantly with the dev server
19
- - **🎨 Template Syntax** - Clean HTML templates with `{}` and `&{}` interpolation
20
- - **⚙️ Zero Config** - Works out of the box with sensible defaults
21
-
22
- ## 🚀 Quick Start
23
-
24
- ### Installation
25
-
26
- ```bash
27
- npm install chocola
28
- ```
29
-
30
- ### Project Structure
31
-
32
- ```
33
- my-chocola-app/
34
- ├── src/
35
- │ ├── lib/
36
- │ │ ├── Counter.js
37
- │ │ ├── TodoItem.js
38
- │ │ └── html/
39
- │ │ ├── counter.body.html
40
- │ │ └── todoItem.body.html
41
- │ ├── styles/
42
- │ │ └── mainStyle.css
43
- │ └── index.html
44
- ├── chocola.config.json
45
- ├── chocola.server.js
46
- └── index.js
47
- ```
48
-
49
- ### Configuration
50
-
51
- Create a `chocola.config.json` file:
52
-
53
- ```json
54
- {
55
- "bundle": {
56
- "srcDir": "/src",
57
- "outDir": "/dist",
58
- "libDir": "/lib",
59
- "emptyOutDir": true
60
- },
61
- "dev": {
62
- "hostname": "localhost",
63
- "port": 3000
64
- }
65
- }
66
- ```
67
-
68
- ## 📝 Creating Components
69
-
70
- ### 1. Define Your HTML Template
71
-
72
- > NOTE: Reactivity, component APIs and global variables are not implemented yet.
73
- > Any of these features displayed here are for future references and may be modified.
74
-
75
- Create `src/lib/html/counter.html`:
7
+ ## Usage
76
8
 
77
9
  ```html
78
- <div class="counter">
79
- <h2>{ctx.title}</h2>
80
- <!-- Use & to set reactivity -->
81
- <p>Count: &{sfx.count}</p>
82
- <button class="increment">+</button>
83
- <button class="decrement">-</button>
10
+ <!-- counter.html -->
11
+ <div>
12
+ <button title={title}>{text}</button>
13
+
14
+ <div class="main">
15
+ <div class="number">${count}</div>
16
+ </div>
84
17
  </div>
85
18
  ```
86
19
 
87
- ### 2. Create the Component Logic
88
-
89
- Create `src/lib/Counter.js`:
90
-
91
- ```javascript
92
- import { lib } from "chocola";
20
+ ```js
93
21
  import HTML from "./html/counter.html";
22
+ import CSS from "./css/counter.css";
94
23
 
95
24
  function RUNTIME(self, ctx) {
96
- // Acces to component effects
97
- let sfx = self.sfx;
98
-
99
- // Event handlers
100
- self.querySelector(".increment").addEventListener("click", () => {
101
- sfx.incrementCount(); // Automatically updates the UI
102
- });
103
-
104
- self.querySelector(".decrement").addEventListener("click", () => {
105
- sfx.decrementCount(); // Automatically updates the UI
106
- });
107
- }
108
-
109
- function EFFECTS(self, sfx) {
110
- // Component lifecycle effects
111
- sfx.onMount: () => {
112
- console.log("Counter mounted with count:", sfx.count);
113
- },
114
- sfx.onUpdate: () => {
115
- // Log updated variables
116
- console.log("Counter updated:", sfx.diff);
117
- },
118
- sfx.onRemove: () => {
119
- console.log("Counter removed");
120
- };
121
-
122
- // Create the component public API
123
- const api = lib.api(self, sfx);
124
-
125
- // Create methods to expose
126
- api.incrementCount = () => {
127
- sfx.count++
128
- }
129
- api.decrementCount = () => {
130
- sfx.count--
131
- }
132
-
133
- // Expose the component API
134
- return api
25
+ self.querySelector("button").addEventListener("click", () => {
26
+ ctx.count++;
27
+ })
135
28
  }
136
29
 
137
30
  export default function Counter() {
138
31
  return {
139
32
  body: HTML,
140
- script: RUNTIME,
141
- effects: EFFECTS
142
- };
143
- }
144
- ```
145
-
146
- ### 3. Use the Component
147
-
148
- In your `src/index.html`:
149
-
150
- ```html
151
- <html>
152
- <head>
153
- <title>Chocola Counter App</title>
154
- </head>
155
- <body>
156
- <app>
157
- <Counter ctx.title="My Counter" sfx.initialCount="0"></Counter>
158
- </app>
159
- </body>
160
- </html>
161
- ```
162
-
163
- ## 🎭 Component Anatomy
164
-
165
- ### Template (`body`)
166
- - Standard HTML with template variables
167
- - **Static context**: `{ctx.propertyName}`, `{sfx.propertyName}` - rendered once at initialization
168
- - **Reactive state**: `&{sfx.propertyName}` - automatically updates on change
169
- - Clean separation of markup and logic
170
-
171
- ### Runtime (`script`) - Optional
172
- - Function that receives `self` (component API, properties and DOM element), `ctx` (static context), and `sfx` (dynamic context)
173
- - Initialize runtime script and event handlers
174
- - Full access to DOM APIs and browser features
175
- - Executes when the component mounts
176
-
177
- ### Effects (`effects`) - Optional
178
- - Function that receives `self` and `sfx`
179
- - Returns lifecycle hooks: `onMount`, `onUpdate`, `onRemove`
180
- - Manage side effects and component lifecycle
181
- - Clean up resources when component is removed
182
-
183
- ## ⚡ Reactivity System
184
-
185
- ### Mutable Reactive Variables
186
-
187
- Use `&{sfx.varName}` in templates for automatic reactivity:
188
-
189
- ```html
190
- <div>
191
- <p>Username: &{sfx.username}</p>
192
- <p>Online: &{sfx.isOnline}</p>
193
- </div>
194
- ```
195
-
196
- ```javascript
197
- function RUNTIME(self, ctx) {
198
- let sfx = self.sfx;
199
-
200
- // Changes automatically update the UI
201
- setTimeout(() => {
202
- sfx.username = "Alice";
203
- sfx.isOnline = true;
204
- }, 2000);
205
- }
206
- ```
207
-
208
- ### Variable Subscription
209
-
210
- Subscribe to changes in reactive variables across components:
211
-
212
- ```javascript
213
- import { app } from "chocola";
214
-
215
- function RUNTIME(self, ctx) {
216
- let sfx = self.sfx;
217
-
218
- // Subscribe to global state
219
- app.watch("globalCounter", (newValue) => {
220
- console.log("Global counter changed to:", newValue);
221
- sfx.localCount = newValue * 2;
222
- });
223
- }
224
- ```
225
-
226
- ### Conditional Rendering
227
-
228
- Use the `chif` attribute to conditionally render components:
229
-
230
- ```html
231
- <app>
232
- <!-- Based on ctx variable (static) -->
233
- <LoginForm chif="ctx.isLoggedOut"></LoginForm>
234
-
235
- <!-- Based on sfx variable (reactive with &) -->
236
- <Dashboard chif="sfx.&isAuthenticated"></Dashboard>
237
- <LoadingSpinner chif="sfx.&isLoading"></LoadingSpinner>
238
- </app>
239
- ```
240
-
241
- ```javascript
242
- function RUNTIME(self, ctx) {
243
- let sfx = self.sfx;
244
-
245
- // Simulate authentication
246
- setTimeout(() => {
247
- sfx.isLoading = false;
248
- sfx.isAuthenticated = true;
249
- // Dashboard appears, LoadingSpinner disappears
250
- }, 2000);
251
- }
252
- ```
253
-
254
- ## 🌐 Global State Management
255
-
256
- Share state across your entire application:
257
-
258
- ```javascript
259
- // In any component
260
- import * as globals from "path/to/globals.js";
261
-
262
- function RUNTIME(self, ctx) {
263
- // Set global variables
264
- globals.userTheme = "dark";
265
- globals.notifications = [];
266
-
267
- // Access global variables from other components
268
- console.log(globals.userTheme);
269
- }
270
- ```
271
-
272
- ## 🔌 Component Public API
273
-
274
- ### Mounting Components Dynamically
275
-
276
- ```javascript
277
- // In any component
278
- import { lib } from "chocola";
279
- import Counter from "./lib/Counter.js";
280
-
281
- // Mount a component programmatically in RUNTIME and/or EFFECTS
282
- function RUNTIME(self, ctx) {
283
- const counterInstance = lib.mount(Counter)
284
- .defCtx({
285
- title: "Dynamic Counter",
286
- initialCount: "10"
287
- })
288
- .render() // Render accepts a DOM element or component
289
- // instance as a target parameter; default
290
- // target is set as your <app> element
291
- }
292
- ```
293
-
294
- ### Manipulating Components
295
-
296
- ```javascript
297
- // Acces and modify variables
298
- console.log(counterInstance.ctx.title);
299
- counterInstance.sfx.count = 1;
300
-
301
- // Call component API methods
302
- counterInstance.reset();
303
- console.log(counterInstance.getCount())
304
- ```
305
-
306
- ### Removing Components
307
-
308
- ```javascript
309
- // Remove and clean up
310
- counterInstance.remove();
311
- ```
312
-
313
- ## 🔧 Development
314
-
315
- ### Start Development Server
316
-
317
- Create `chocola.server.js`:
318
-
319
- ```javascript
320
- import { dev } from "chocola";
321
- import path from "path";
322
- import { fileURLToPath } from "url";
323
-
324
- const __filename = fileURLToPath(import.meta.url);
325
- const __dirname = path.dirname(__filename);
326
-
327
- dev.server(__dirname);
328
- ```
329
-
330
- Run:
331
- ```bash
332
- node chocola.server.js
333
- ```
334
-
335
- Visit `http://localhost:3000` to see your app with hot reload enabled.
336
-
337
- ### Build for Production
338
-
339
- Create `index.js`:
340
-
341
- ```javascript
342
- import { app } from "chocola";
343
- import path from "path";
344
- import { fileURLToPath } from "url";
345
-
346
- const __filename = fileURLToPath(import.meta.url);
347
- const __dirname = path.dirname(__filename);
348
-
349
- app.build(__dirname);
350
- ```
351
-
352
- Build:
353
- ```bash
354
- node index.js
355
- ```
356
-
357
- Your optimized app will be in the `dist/` directory.
358
-
359
- ---
360
-
361
- ## 🏗️ Build Output
362
-
363
- Chocola automatically:
364
- - ✅ Compiles components into optimized JavaScript
365
- - ✅ Generates unique component IDs (`chid`)
366
- - ✅ Bundles scripts with proper dependency resolution
367
- - ✅ Processes templates and injects runtime code
368
- - ✅ Sets up reactivity system for `&{sfx.*}` variables
369
- - ✅ Optimizes conditional rendering with `chif` attributes
370
- - ✅ Generates component API wrappers for mounting/unmounting
371
- - ✅ Optimizes assets for production
372
-
373
- ## 🎯 Best Practices
374
-
375
- ### When to Use `ctx` vs `sfx`
376
-
377
- - **Use `ctx`** for static configuration that won't change (titles, labels, initial values)
378
- - **Use `sfx`** for dynamic data that updates over time and its configurations (counts, user input, API responses)
379
-
380
- ### Component Lifecycle
381
-
382
- ```javascript
383
- function EFFECTS(self, sfx) {
384
- return {
385
- onMount: () => {
386
- // Initialize, fetch data, set up subscriptions
387
- },
388
- onUpdate: () => {
389
- // React to specific state changes
390
- // Only runs when sfx variables change
391
- },
392
- onRemove: () => {
393
- // Clean up timers, subscriptions, event listeners
394
- }
395
- };
33
+ styles: CSS,
34
+ script: RUNTIME
35
+ }
396
36
  }
397
37
  ```
398
38
 
399
- ### Conditional Rendering Performance
400
-
401
- - Use `chif` with reactive variables for dynamic show/hide
402
- - Use `chif` with static variables for static conditional rendering
403
- - Components with `chif="false"` are not mounted at all (performance optimization)
404
-
405
- ## 🤝 Contributing
406
-
407
- Contributions are welcome! Chocola is in active development and we'd love your input.
408
-
409
- ## 📄 License
410
-
411
- MIT License - feel free to use Chocola in your projects!
412
-
413
- ## 🍫 Why Chocola?
414
-
415
- - **Simple**: No complex build configurations or CLI tools to learn
416
- - **Reactive**: Built-in reactivity without the complexity of larger frameworks
417
- - **Fast**: Minimal runtime overhead with efficient reactive updates
418
- - **Flexible**: Use as much or as little as you need
419
- - **Modern**: Built with ES modules and modern JavaScript features
420
- - **Powerful**: Global state, subscriptions, and lifecycle hooks out of the box
421
- - **Sweet**: Developer experience that's actually enjoyable
422
-
423
- ---
424
-
425
- Made with 🍫 and ❤️
39
+ ## Documentation
426
40
 
427
- **Start building sweet reactive web apps today!**
41
+ See https://github.com/sad-gabi/chocola/wiki
@@ -1,6 +1,6 @@
1
1
  import { JSDOM } from "jsdom";
2
2
  import { extractContextFromElement } from "./dom-processor.js";
3
- import { genRandomId, incrementAlfabet } from "./utils.js";
3
+ import { genRandomId, incrementAlfabet, throwError } from "./utils.js";
4
4
  import chalk from "chalk";
5
5
  import beautify from "js-beautify";
6
6
 
@@ -17,6 +17,20 @@ function scopeCss(cssString, cssId) {
17
17
  });
18
18
  }
19
19
 
20
+ function interpolateNode(node, ctxProxy) {
21
+ if (node.nodeType === 3) {
22
+ node.textContent = node.textContent.replace(/\{([^}]+)\}/g, (_, expr) => {
23
+ try {
24
+ return Function("ctx", `with(ctx) { return (${expr}); }`)(ctxProxy);
25
+ } catch {
26
+ return "";
27
+ }
28
+ });
29
+ } else {
30
+ node.childNodes.forEach(child => interpolateNode(child, ctxProxy));
31
+ }
32
+ }
33
+
20
34
 
21
35
  /**
22
36
  * Processes a single component element and inserts it into the DOM
@@ -43,31 +57,47 @@ export function processComponentElement(
43
57
  const ctx = extractContextFromElement(element);
44
58
  const srcInnerHtml = element.innerHTML;
45
59
 
60
+ const ctxProxy = new Proxy(ctx, {
61
+ has() { return true; },
62
+ get(target, key) { return target[key]; }
63
+ });
64
+
46
65
  const instance = loadedComponents.get(compName);
47
66
  if (!instance || instance === undefined) return false;
48
67
 
49
68
  if (instance.body) {
50
69
  let body = instance.body;
51
- body = body.replace(
52
- /(?<!\b(?:if|del-if)=)\{(\w+)\}/g,
53
- (_, key) => ctx[key] || ""
54
- );
70
+
55
71
  const fragment = JSDOM.fragment(body);
56
72
  const children = Array.from(fragment.querySelectorAll("*"));
73
+
57
74
  children.forEach(child => {
75
+ const reservedAttrs = ["if", "del-if"];
76
+ Array.from(child.attributes).forEach(attribute => {
77
+ if (!attribute || attribute === undefined) return;
78
+ if (reservedAttrs.includes(attribute.name)) return;
79
+ attribute.value = attribute.value.replace(
80
+ /\{([^}]+)\}/g,
81
+ (_, key) => ctx[key] ?? ""
82
+ );
83
+ });
84
+
58
85
  ["if", "del-if"].forEach(statement => {
59
86
  const statAtt = child.getAttribute(statement);
60
87
  if (!statAtt) return;
61
88
  const expr = statAtt.slice(1, -1);
62
- const fn = new Function("ctx", `return ctx.${expr} === true || ctx.${expr} === '{true}'`);
63
- if (!fn(ctx)) {
89
+ const eva = ctxProxy[expr] ? ctxProxy[expr].slice(1, -1) : null;
90
+ const fn = new Function("ctx", `{ return (${eva}); }`);
91
+ if (!fn()) {
64
92
  if (statement === "if") child.style.display = "none";
65
93
  if (statement === "del-if") child.remove();
66
94
  }
67
-
68
95
  child.removeAttribute(statement);
69
96
  });
97
+
98
+ interpolateNode(child, ctxProxy)
70
99
  });
100
+
71
101
  const firstChild = fragment.firstChild;
72
102
 
73
103
  if (firstChild && firstChild.nodeType === 1) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chocola",
3
- "version": "1.3.6",
3
+ "version": "1.3.8",
4
4
  "description": "The sweetest way to build reactive web apps",
5
5
  "keywords": [
6
6
  "web",