lego-dom 1.0.0 → 1.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 (74) hide show
  1. package/.legodom +87 -0
  2. package/CHANGELOG.md +87 -3
  3. package/cdn.html +10 -5
  4. package/docs/.vitepress/config.js +23 -7
  5. package/docs/api/config.md +95 -0
  6. package/docs/api/define.md +29 -2
  7. package/docs/api/directives.md +10 -2
  8. package/docs/api/index.md +1 -0
  9. package/docs/contributing/01-welcome.md +2 -0
  10. package/docs/contributing/02-registry.md +37 -3
  11. package/docs/contributing/06-init.md +13 -2
  12. package/docs/contributing/07-observer.md +3 -0
  13. package/docs/contributing/08-snap.md +15 -1
  14. package/docs/contributing/10-studs.md +3 -1
  15. package/docs/contributing/11-scanner.md +13 -0
  16. package/docs/contributing/12-render.md +32 -10
  17. package/docs/contributing/13-directives.md +19 -1
  18. package/docs/contributing/14-events.md +1 -1
  19. package/docs/contributing/15-router.md +49 -1
  20. package/docs/contributing/16-state.md +9 -10
  21. package/docs/contributing/17-legodom.md +1 -8
  22. package/docs/contributing/index.md +23 -4
  23. package/docs/examples/form.md +1 -1
  24. package/docs/examples/index.md +3 -3
  25. package/docs/examples/routing.md +10 -10
  26. package/docs/examples/sfc-showcase.md +1 -1
  27. package/docs/examples/todo-app.md +7 -7
  28. package/docs/guide/cdn-usage.md +44 -18
  29. package/docs/guide/components.md +18 -12
  30. package/docs/guide/directives.md +131 -22
  31. package/docs/guide/directory-structure.md +248 -0
  32. package/docs/guide/faq.md +210 -0
  33. package/docs/guide/getting-started.md +14 -10
  34. package/docs/guide/index.md +1 -1
  35. package/docs/guide/lifecycle.md +32 -0
  36. package/docs/guide/quick-start.md +4 -4
  37. package/docs/guide/reactivity.md +2 -2
  38. package/docs/guide/routing.md +69 -8
  39. package/docs/guide/server-side.md +134 -0
  40. package/docs/guide/sfc.md +96 -13
  41. package/docs/guide/templating.md +62 -57
  42. package/docs/index.md +9 -9
  43. package/docs/router/basic-routing.md +8 -8
  44. package/docs/router/cold-entry.md +2 -2
  45. package/docs/router/history.md +7 -7
  46. package/docs/router/index.md +1 -1
  47. package/docs/router/resolver.md +5 -5
  48. package/docs/router/surgical-swaps.md +5 -5
  49. package/docs/tutorial/01-project-setup.md +152 -0
  50. package/docs/tutorial/02-your-first-component.md +226 -0
  51. package/docs/tutorial/03-adding-routes.md +279 -0
  52. package/docs/tutorial/04-multi-page-app.md +329 -0
  53. package/docs/tutorial/05-state-and-globals.md +285 -0
  54. package/docs/tutorial/index.md +40 -0
  55. package/examples/vite-app/index.html +1 -0
  56. package/examples/vite-app/src/app.js +2 -2
  57. package/examples/vite-app/src/components/side-menu.lego +46 -0
  58. package/examples/vite-app/vite.config.js +2 -1
  59. package/main.js +261 -72
  60. package/main.min.js +7 -0
  61. package/monitoring-plugin.js +111 -0
  62. package/package.json +4 -2
  63. package/parse-lego.js +49 -22
  64. package/tests/error.test.js +74 -0
  65. package/tests/main.test.js +2 -2
  66. package/tests/memory.test.js +68 -0
  67. package/tests/monitoring.test.js +74 -0
  68. package/tests/naming.test.js +74 -0
  69. package/tests/parse-lego.test.js +2 -2
  70. package/tests/security.test.js +67 -0
  71. package/tests/server.test.js +114 -0
  72. package/tests/syntax.test.js +67 -0
  73. package/vite-plugin.js +3 -2
  74. package/docs/guide/contributing.md +0 -32
@@ -20,16 +20,34 @@ const render = (el) => {
20
20
  if (!shadow) return;
21
21
  if (!data.bindings) data.bindings = scanForBindings(shadow);
22
22
 
23
+ if (config.metrics?.onRenderStart) config.metrics.onRenderStart(el);
24
+
23
25
  data.bindings.forEach(b => {
26
+ // 1. Conditionals (b-if)
27
+ if (b.type === 'b-if') {
28
+ const condition = !!safeEval(b.expr, { state, global: Lego.globals, self: b.node });
29
+ const isAttached = !!b.node.parentNode;
30
+ if (condition && !isAttached) b.anchor.parentNode.replaceChild(b.node, b.anchor);
31
+ else if (!condition && isAttached) b.node.parentNode.replaceChild(b.anchor, b.node);
32
+ }
33
+
34
+ // 2. Visibility (b-show)
24
35
  if (b.type === 'b-show') b.node.style.display = safeEval(b.expr, { state, self: b.node }) ? '' : 'none';
36
+
37
+ // 3. Text (b-text, b-html)
25
38
  if (b.type === 'b-text') b.node.textContent = escapeHTML(resolve(b.path, state));
39
+ if (b.type === 'b-html') b.node.innerHTML = safeEval(b.expr, { state, self: b.node }); // Trusted HTML
40
+
41
+ // 4. Sync (b-sync)
26
42
  if (b.type === 'b-sync') syncModelValue(b.node, resolve(b.node.getAttribute('b-sync'), state));
43
+
44
+ // 5. Mustaches
27
45
  if (b.type === 'text') {
28
- const out = b.template.replace(/{{(.*?)}}/g, (_, k) => escapeHTML(safeEval(k.trim(), { state, self: b.node }) ?? ''));
46
+ const out = b.template.replace(/\[\[(.*?)\]\]/g, (_, k) => escapeHTML(safeEval(k.trim(), { state, self: b.node }) ?? ''));
29
47
  if (b.node.textContent !== out) b.node.textContent = out;
30
48
  }
31
49
  if (b.type === 'attr') {
32
- const out = b.template.replace(/{{(.*?)}}/g, (_, k) => escapeHTML(safeEval(k.trim(), { state, self: b.node }) ?? ''));
50
+ const out = b.template.replace(/\[\[(.*?)\]\]/g, (_, k) => escapeHTML(safeEval(k.trim(), { state, self: b.node }) ?? ''));
33
51
  if (b.node.getAttribute(b.attrName) !== out) {
34
52
  b.node.setAttribute(b.attrName, out);
35
53
  if (b.attrName === 'class') b.node.className = out;
@@ -69,8 +87,11 @@ const render = (el) => {
69
87
  }
70
88
  });
71
89
  } finally {
90
+ } finally {
91
+ if (config.metrics?.onRenderEnd) config.metrics.onRenderEnd(el);
72
92
  data.rendering = false;
73
93
  }
94
+ }
74
95
  };
75
96
  ```
76
97
 
@@ -96,16 +117,17 @@ The function loops through `data.bindings` and performs specific actions based o
96
117
  - **`attr`**: It updates attributes like `src`, `href`, or `class`. It even has a special check: if the attribute is `class`, it also updates `node.className` to ensure the browser applies the styles correctly.
97
118
 
98
119
 
99
- ### 3. The `safeEval` Bridge
120
+ ### 3. The `safeEval` Bridge & Security
100
121
 
101
- You’ll notice that for things like `b-show` or <code v-pre>{{mustaches}}</code>, the library calls `safeEval(expr, { state, self: b.node })`.
122
+ You’ll notice that for things like `b-show` or mustaches, the library calls `safeEval(expr, { state, self: b.node })`.
102
123
 
103
- - This allows your HTML to handle more than just simple variables.
104
-
105
- - You can write logic directly in your template, like <code v-pre>{{ count > 10 ? 'Big' : 'Small' }}</code>.
106
-
107
- - `render` passes the current state as the "context," so those expressions know exactly what `count` refers to.
108
-
124
+ **Why not just `eval()`?**
125
+ `eval()` executes code in the global scope, which is a massive security hole and performance killer.
126
+
127
+ `safeEval` uses `new Function` with a **Proxy Sandbox**:
128
+ 1. **Block List**: It immediately throws if it sees dangerous keywords like `eval`, `Function`, `import`, or global objects like `window`, `document`, `fetch` (unless explicitly provided).
129
+ 2. **Scope Proxy**: The execution context is a `Proxy` (`with(proxy) { ... }`). If the code tries to access `document.cookie` effectively, the proxy intercepts it.
130
+ 3. **Configurable Syntax**: As of v2.0, this also handles the dynamic regex for `[[ ]]` vs `{{ }}` support via `Lego.config.syntax`.
109
131
 
110
132
  ### 4. Directives vs. Mustache Priority
111
133
 
@@ -79,6 +79,24 @@ By using `getPrivateData` (which is a `WeakMap`), we ensure that if the componen
79
79
  **In short:** The anchor is the "Reserved Seat" sign at a theater. `getPrivateData` is the list that remembers which seat belongs to which person while they are out in the lobby.
80
80
 
81
81
 
82
+ ## Raw HTML Injection (`b-html`)
83
+
84
+ LegoDOM is secure by default: all text interpolation is escaped. If your data contains `<b>Bold</b>`, it detects it as text, not HTML.
85
+
86
+ `b-html` is the **only** hatch to render raw HTML.
87
+
88
+ ```javascript
89
+ if (b.type === 'b-html') {
90
+ // SECURITY CRITICAL: This is the only place we set innerHTML directly.
91
+ b.node.innerHTML = safeEval(b.expr, { state, self: b.node });
92
+ }
93
+ ```
94
+
95
+ **Why separate directive?**
96
+ By forcing you to use `b-html="..."`, we make it obvious during code reviews that "This part is dangerous." It prevents accidental XSS where a developer thought `{{ content }}` would render HTML.
97
+
98
+
99
+
82
100
  ## Simple Text Interpolation (`b-text`)
83
101
 
84
102
  While you can use <code v-pre>{{mustaches}}</code>, `b-text` is the "cleaner" way to bind the entire content of an element to a single variable.
@@ -109,7 +127,7 @@ Because both of these were "mapped" during the `scanForBindings` phase (Topic 11
109
127
 
110
128
  The library looks for the pattern `item in list` (e.g., `user in users`).
111
129
 
112
- - **Capture**: During the scanning phase, it saves the inner HTML of the element as a "template string" and then empties the element so it can be filled dynamically.
130
+ - **Capture**: During the scanning phase, it saves a deep clone of the `b-for` element itself as the "template node" (using `node.cloneNode(true)`) and then empties the element so it can be filled dynamically. This approach handles both element children and text-only content correctly.
113
131
 
114
132
 
115
133
  ### The Concept of "The Pool" (`forPools`)
@@ -38,7 +38,7 @@ The library is flexible with how you call these methods:
38
38
 
39
39
  - **Parameterized Call**: `@click="deleteItem(5)"`. The library uses a regex to check if there are parentheses. If it finds them, it parses the arguments (like `5`) and passes them to your function.
40
40
 
41
- - **The "Magic" `$event`**: If you write `@click="move($event, 10)"`, the library intelligently injects the native browser event object into that specific slot.
41
+ - **The "Native" `event`**: If you write `@click="move(event, 10)"`, the library injects the native browser event object into that specific slot. (Note: use `event`, not `$event`).
42
42
 
43
43
 
44
44
  ### 4. Event Modifiers
@@ -2,8 +2,56 @@
2
2
 
3
3
  The true power of the Lego router isn't just changing the URL; it's the **targeted DOM injection** that allows you to swap _any_ part of the page with _any_ component, without writing a single line of `fetch` or `innerHTML` logic.
4
4
 
5
+ ## The "Surgical" Philosophy
5
6
 
6
- ## LegoDOM Router
7
+ Most SPAs use a **Replacer Strategy**:
8
+ `URL Change -> Match Route -> Destroy App -> Rebuild App with new Page.`
7
9
 
10
+ LegoDOM uses a **Surgical Strategy**:
11
+ `URL Change -> Match Route -> Find Targets (#sidebar, #main) -> Modify ONLY those nodes.`
8
12
 
13
+ ### The Implementation
9
14
 
15
+ The core function is `_go` (exposed as `$go`). It doesn't just look for a `<router-outlet>`. It accepts a list of targets.
16
+
17
+ ```javascript
18
+ /* main.js (simplified) */
19
+ const _go = (path, ...targets) => {
20
+ // 1. Update History API
21
+ history.pushState({ legoTargets: targets }, "", path);
22
+
23
+ // 2. Find the component for this route
24
+ const route = routes.find(r => r.path === path);
25
+ const template = registry[route.tagName];
26
+
27
+ // 3. Surgical Swap
28
+ targets.forEach(selector => {
29
+ const el = document.querySelector(selector);
30
+ // CRITICAL: We don't touch the parent, we only replace children.
31
+ // This preserves the element's own state (scroll, attributes).
32
+ el.replaceChildren(template.cloneNode(true));
33
+
34
+ // 4. Trigger Snap (Reactivity & Lifecycle) on new content
35
+ snap(el);
36
+ });
37
+ };
38
+ ```
39
+
40
+ ### Why this matters
41
+
42
+ This architecture enables **Persistent Shells**. You can have a sidebar that plays music or holds chat state, while the main content navigates freely. Traditional routers usually require complex "Layout Components" to achieve this. LegoDOM does it by simply *not touching the sidebar*.
43
+
44
+ ## Intelligent Defaults
45
+
46
+ While surgical routing is powerful, sometimes you just want standard navigation.
47
+ LegoDOM checks for a default `<lego-router>` element if no targets are specified.
48
+
49
+ ```javascript
50
+ /* main.js */
51
+ const resolveTargets = (query) => {
52
+ if (!query) return [document.querySelector('lego-router')];
53
+ // ...
54
+ };
55
+ ```
56
+
57
+ This hybrid approach gives you the best of both worlds: Rapid prototyping (defaults) and App-like fidelity (surgical targets).
@@ -9,22 +9,21 @@ In Lego, the code is designed to allow a component in the footer to talk to a co
9
9
 
10
10
  In most frameworks, data flows down like a waterfall. If a deeply nested component needs a piece of data, every parent above it must "pass it down." The code in `main.js` avoids this by creating a centralized, reactive hub.
11
11
 
12
- ### 2. The Implementation: The "Universal Proxy"
12
+ ### 2. The Implementation: "The Body is the Root"
13
13
 
14
- When you define `Lego.globals`, the library doesn't just store your object. It wraps the entire thing in the same `reactive()` proxy used for individual components.
14
+ When you define `Lego.globals`, the library doesn't just store your object. It wraps it in the same `reactive()` proxy, but binds it to `document.body`.
15
15
 
16
- - **The Code**: `Lego.globals = reactive(userDefinedGlobals, null)`.
17
-
18
- - **The Magic of `null`**: Notice that when a component's state is made reactive, we pass the `el` (the element) so it knows what to render. When we create `Lego.globals`, we pass `null`. This tells the proxy: "You don't belong to one element; you belong to everyone".
16
+ - **The Code**: `Globals = reactive(userState, document.body)`.
19
17
 
18
+ - **The Effect**: This means the entire `<body>` is technically the "component" for global state.
20
19
 
21
- ### 3. The `$global` Prefix and Subscriptions
20
+ ### 3. The `$global` Dependency Check (Smart Broadcast)
22
21
 
23
- The library uses a specific naming convention to trigger its "Global Watcher" logic. Whenever the `render()` engine or a `b-sync` sees a variable starting with `$`, it knows to ignore the local component state (`_studs`) and look into `Lego.globals` instead.
22
+ The library uses a specific optimization to avoid re-rendering the whole world.
24
23
 
25
- - **The Subscription Logic**: In the `render()` function, if a global is accessed, the code automatically adds that component to a "Global Subscribers" list.
26
-
27
- - **The Update Loop**: When you change a global (e.g., `Lego.globals.theme = 'dark'`), the Proxy's `set` trap fires. Because this proxy is global, it iterates through **every single component** currently on the page and tells them to check if they need a re-render.
24
+ - **Depenedency Tracking**: During `scanForBindings`, if the parser sees a variable that looks global (e.g. `[[ global.user ]]`), it marks that specific component with a `hasGlobalDependency` flag.
25
+
26
+ - **The Broadcast Loop**: When you change a global (e.g., `Lego.globals.theme = 'dark'`), the Proxy's `set` trap fires on `document.body`. The `render` function sees this is a global update and iterates through **activeComponents**, checking which ones have the flag. Only those components re-render.
28
27
 
29
28
 
30
29
  ### 4. Why `Object.defineProperty` is avoided for Globals
@@ -5,14 +5,7 @@ Everything we’ve discussed i.e. the Scanner, the Registry, the Router, and the
5
5
 
6
6
  The code for `init()` is deceptively small because its primary job is to flip the switches on the systems we've already built.
7
7
 
8
- ### 1. The Singleton Guard
9
-
10
- The very first thing `init()` does is check a internal flag: `if (initialized) return;`.
11
-
12
- - **The Why**: In a complex app, you might accidentally call `init()` multiple times. Without this guard, you would attach multiple `MutationObservers` to the body, causing every component to "snap" and "render" twice for every single change.
13
-
14
-
15
- ### 2. Bootstrapping the Watchdog
8
+ ### 1. Bootstrapping the Watchdog
16
9
 
17
10
  The core of the initialization is setting up the `MutationObserver` we discussed in Topic 7.
18
11
 
@@ -1,5 +1,24 @@
1
- # Welcome to the LegoDOM Source Code Explainer Series ;-)
1
+ # Architectural Deep Dive
2
2
 
3
- Here I will give a sneak peek into my brain and thinking when designing the LegoDOM library. Some
4
- decisions might suck to you - I am happy to hear your honest feedback on the
5
- [community chat](https://github.com/rayattack/lego-dom/discussions).
3
+ Welcome to the internal documentation of LegoDOM.
4
+
5
+ This isn't a "how to use" guide. This is a **"how it works"** guide. I believe that understanding the soul of LegoDOM should/could make you a better contributor.
6
+
7
+ ## The Philosophy
8
+ **"The Platform is the Runtime."**
9
+ We avoid compilers, transpilers, and VDOMs. We use:
10
+ - **Proxies** for state.
11
+ - **TreeWalkers** for scanning.
12
+ - **Regex** for parsing.
13
+ - **MutationObservers** for efficiency.
14
+
15
+ ## The Journey
16
+ Follow the path of a component from HTML string to Pixel:
17
+
18
+ 1. [**Init**](./06-init) - How the library wakes up.
19
+ 2. [**Scanner**](./11-scanner) - How we find holes in your HTML (Regex vs AST).
20
+ 3. [**Studs**](./10-studs) - The Reactivity Engine (Proxies).
21
+ 4. [**Render**](./12-render) - The "Loop of Truth" & Security.
22
+ 5. [**Router**](./15-router) - The "Surgical" Update philosophy.
23
+
24
+ Dive in.
@@ -17,7 +17,7 @@ Handling forms in Lego.
17
17
  <input type="password" b-sync="password">
18
18
  </div>
19
19
 
20
- <p b-show="error" style="color: red">{{ error }}</p>
20
+ <p b-show="error" style="color: red">[[ error ]]</p>
21
21
 
22
22
  <button type="submit">Login</button>
23
23
  </form>
@@ -13,7 +13,7 @@ A simple reactive counter demonstrating basic state and events.
13
13
  <style>
14
14
  button { font-size: 1.2rem; padding: 0.5rem 1rem; }
15
15
  </style>
16
- <p>Count: {{ count }}</p>
16
+ <p>Count: [[ count ]]</p>
17
17
  <button @click="count++">Increment</button>
18
18
  </template>
19
19
 
@@ -27,7 +27,7 @@ Two-way data binding with `b-sync`.
27
27
  ```html
28
28
  <template b-id="name-input">
29
29
  <input b-sync="name" placeholder="Enter your name">
30
- <p b-show="name">Hello, {{ name }}!</p>
30
+ <p b-show="name">Hello, [[ name ]]!</p>
31
31
  </template>
32
32
 
33
33
  <name-input b-data="{ name: '' }"></name-input>
@@ -42,7 +42,7 @@ Lists with `b-for`.
42
42
  <ul>
43
43
  <li b-for="todo in todos">
44
44
  <input type="checkbox" b-sync="todo.done">
45
- <span class="{{ todo.done ? 'done' : '' }}">{{ todo.text }}</span>
45
+ <span class="[[ todo.done ? 'done' : '' ]]">[[ todo.text ]]</span>
46
46
  </li>
47
47
  </ul>
48
48
  </template>
@@ -152,10 +152,10 @@ A multi-page application demonstrating client-side routing.
152
152
 
153
153
  <div class="user-card" b-for="user in users">
154
154
  <div>
155
- <strong>{{ user.name }}</strong>
156
- <p style="margin:0;color:#666;">{{ user.email }}</p>
155
+ <strong>[[ user.name ]]</strong>
156
+ <p style="margin:0;color:#666;">[[ user.email ]]</p>
157
157
  </div>
158
- <a href="/users/{{ user.id }}" b-link>View Profile →</a>
158
+ <a href="/users/[[ user.id ]]" b-link>View Profile →</a>
159
159
  </div>
160
160
  </template>
161
161
 
@@ -181,17 +181,17 @@ A multi-page application demonstrating client-side routing.
181
181
  </div>
182
182
 
183
183
  <div b-show="!loading && user" class="profile">
184
- <h1>{{ user.name }}</h1>
185
- <p><strong>Email:</strong> {{ user.email }}</p>
186
- <p><strong>Phone:</strong> {{ user.phone }}</p>
187
- <p><strong>Website:</strong> {{ user.website }}</p>
184
+ <h1>[[ user.name ]]</h1>
185
+ <p><strong>Email:</strong> [[ user.email ]]</p>
186
+ <p><strong>Phone:</strong> [[ user.phone ]]</p>
187
+ <p><strong>Website:</strong> [[ user.website ]]</p>
188
188
 
189
189
  <hr>
190
190
 
191
191
  <h3>Address</h3>
192
192
  <p>
193
- {{ user.address.street }}<br>
194
- {{ user.address.city }}, {{ user.address.zipcode }}
193
+ [[ user.address.street ]]<br>
194
+ [[ user.address.city ]], [[ user.address.zipcode ]]
195
195
  </p>
196
196
 
197
197
  <p><a href="/users" b-link>← Back to users</a></p>
@@ -264,7 +264,7 @@ A multi-page application demonstrating client-side routing.
264
264
 
265
265
  <div b-show="submitted" class="success">
266
266
  <h3>✅ Message Sent!</h3>
267
- <p>Thank you for contacting us, {{ form.name }}. We'll respond to {{ form.email }} soon.</p>
267
+ <p>Thank you for contacting us, [[ form.name ]]. We'll respond to [[ form.email ]] soon.</p>
268
268
  <button @click="submitted = false">Send Another</button>
269
269
  </div>
270
270
  </template>
@@ -7,7 +7,7 @@ Using `.lego` files with Vite.
7
7
  **Counter.lego**
8
8
  ```html
9
9
  <template>
10
- <button @click="count++">Count: {{ count }}</button>
10
+ <button @click="count++">Count: [[ count ]]</button>
11
11
  </template>
12
12
 
13
13
  <style>
@@ -182,17 +182,17 @@ A complete todo application demonstrating Lego features.
182
182
 
183
183
  <div class="filters">
184
184
  <button
185
- class="filter-btn {{ filter === 'all' ? 'active' : '' }}"
185
+ class="filter-btn [[ filter === 'all' ? 'active' : '' ]]"
186
186
  @click="filter = 'all'">
187
187
  All
188
188
  </button>
189
189
  <button
190
- class="filter-btn {{ filter === 'active' ? 'active' : '' }}"
190
+ class="filter-btn [[ filter === 'active' ? 'active' : '' ]]"
191
191
  @click="filter = 'active'">
192
192
  Active
193
193
  </button>
194
194
  <button
195
- class="filter-btn {{ filter === 'completed' ? 'active' : '' }}"
195
+ class="filter-btn [[ filter === 'completed' ? 'active' : '' ]]"
196
196
  @click="filter = 'completed'">
197
197
  Completed
198
198
  </button>
@@ -201,20 +201,20 @@ A complete todo application demonstrating Lego features.
201
201
  <ul>
202
202
  <li b-for="todo in filteredTodos()">
203
203
  <input type="checkbox" b-sync="todo.done">
204
- <span class="todo-text {{ todo.done ? 'done' : '' }}">
205
- {{ todo.text }}
204
+ <span class="todo-text [[ todo.done ? 'done' : '' ]]">
205
+ [[ todo.text ]]
206
206
  </span>
207
207
  <button class="delete-btn" @click="deleteTodo(todo)">Delete</button>
208
208
  </li>
209
209
  </ul>
210
210
 
211
211
  <div class="stats">
212
- <span>{{ remaining() }} item{{ remaining() === 1 ? '' : 's' }} left</span>
212
+ <span>[[ remaining() ]] item[[ remaining() === 1 ? '' : 's' ]] left</span>
213
213
  <button
214
214
  class="clear-completed"
215
215
  b-show="completedCount() > 0"
216
216
  @click="clearCompleted()">
217
- Clear completed ({{ completedCount() }})
217
+ Clear completed ([[ completedCount() ]])
218
218
  </button>
219
219
  </div>
220
220
  </template>
@@ -13,7 +13,7 @@ Lego works perfectly without any build tools. Just include it via CDN and start
13
13
  <body>
14
14
  <!-- Define your component -->
15
15
  <template b-id="hello-world">
16
- <h1>{{ message }}</h1>
16
+ <h1>[[ message ]]</h1>
17
17
  </template>
18
18
 
19
19
  <!-- Use it -->
@@ -39,7 +39,7 @@ That's it! Open this file in any browser and it works.
39
39
  <script src="https://unpkg.com/lego-dom/main.js"></script>
40
40
 
41
41
  <!-- Specific version -->
42
- <script src="https://unpkg.com/lego-dom@0.0.7/main.js"></script>
42
+ <script src="https://unpkg.com/lego-dom@1.3.4/main.js"></script>
43
43
  ```
44
44
 
45
45
  ### jsdelivr
@@ -49,14 +49,10 @@ That's it! Open this file in any browser and it works.
49
49
  <script src="https://cdn.jsdelivr.net/npm/lego-dom/main.js"></script>
50
50
 
51
51
  <!-- Specific version -->
52
- <script src="https://cdn.jsdelivr.net/npm/lego-dom@0.0.7/main.js"></script>
52
+ <script src="https://cdn.jsdelivr.net/npm/lego-dom@1.3.4/main.js"></script>
53
53
  ```
54
54
 
55
- ### cdnjs
56
55
 
57
- ```html
58
- <script src="https://cdnjs.cloudflare.com/ajax/libs/lego-dom/0.0.7/main.js"></script>
59
- ```
60
56
 
61
57
  ## Complete Example
62
58
 
@@ -133,13 +129,13 @@ Here's a full working application using only CDN:
133
129
  <ul>
134
130
  <li b-for="todo in todos">
135
131
  <input type="checkbox" b-sync="todo.done">
136
- <span class="{{ todo.done ? 'done' : '' }}">
137
- {{ todo.text }}
132
+ <span class="[[ todo.done ? 'done' : '' ]]">
133
+ [[ todo.text ]]
138
134
  </span>
139
135
  </li>
140
136
  </ul>
141
137
 
142
- <p>{{ remaining() }} remaining</p>
138
+ <p>[[ remaining() ]] remaining</p>
143
139
  </template>
144
140
 
145
141
  <script src="https://unpkg.com/lego-dom/main.js"></script>
@@ -198,7 +194,7 @@ Lego is perfect for progressively enhancing existing sites:
198
194
  box-shadow: 0 2px 10px rgba(0,0,0,0.1);
199
195
  }
200
196
  </style>
201
- <p>Welcome, {{ username }}!</p>
197
+ <p>Welcome, [[ username ]]!</p>
202
198
  <button @click="logout()">Logout</button>
203
199
  </template>
204
200
 
@@ -246,7 +242,7 @@ Lego components work alongside other frameworks:
246
242
  Always pin to a specific version:
247
243
 
248
244
  ```html
249
- <script src="https://unpkg.com/lego-dom@0.0.7/main.js"></script>
245
+ <script src="https://unpkg.com/lego-dom@1.3.4/main.js"></script>
250
246
  ```
251
247
 
252
248
  ### For Development/Prototyping
@@ -271,7 +267,7 @@ For maximum security:
271
267
 
272
268
  ```html
273
269
  <script
274
- src="https://unpkg.com/lego-dom@0.0.7/main.js"
270
+ src="https://unpkg.com/lego-dom@1.3.4/main.js"
275
271
  integrity="sha384-..."
276
272
  crossorigin="anonymous">
277
273
  </script>
@@ -288,9 +284,42 @@ Lego works in all modern browsers:
288
284
 
289
285
  No polyfills needed for these browsers!
290
286
 
287
+ ## Using .lego Files without Build Tools
288
+
289
+ You can use full Single File Components (`.lego` files) directly in the browser without any build step!
290
+ Lego provides a `loader` configuration that lets you fetch component files on demand.
291
+
292
+ ### How it Works
293
+
294
+ 1. **Serve your files**: Make sure your `.lego` files are accessible via HTTP (e.g., in a `/components` folder).
295
+ 2. **Configure the loader**: Tell Lego how to fetch a component when it encounters an unknown tag.
296
+
297
+ ### Example
298
+
299
+ ```html
300
+ <script>
301
+ Lego.init(document.body, {
302
+ // The loader function receives the tag name (e.g., 'user-card')
303
+ // and must return a Promise that resolves to the component's source code.
304
+ loader: (tagName) => {
305
+ // Fetch the raw text content of the .lego file
306
+ return fetch(`/components/${tagName}.lego`).then(res => res.text());
307
+ }
308
+ });
309
+ </script>
310
+
311
+ <!-- Now just use the tag! Lego will fetch, compile, and render it automatically. -->
312
+ <user-card></user-card>
313
+ ```
314
+
315
+ This is perfect for:
316
+ - **Micro-frontends**: Load components from different services.
317
+ - **CMS Integration**: Store component code in a database.
318
+ - **Dynamic Apps**: Load features only when they are needed.
319
+
291
320
  ## Pros and Cons
292
321
 
293
- ### Advantages
322
+ ### Advantages
294
323
 
295
324
  - **No build step** - Instant development
296
325
  - **No npm** - No dependency management
@@ -298,11 +327,8 @@ No polyfills needed for these browsers!
298
327
  - **Progressive enhancement** - Add to existing sites easily
299
328
  - **Low barrier** - Great for beginners
300
329
 
301
- ### ⚠️ Limitations
330
+ ### Limitations
302
331
 
303
- - No tree-shaking (you get the whole library)
304
- - No TypeScript compilation
305
- - No `.lego` SFC support
306
332
  - No hot module replacement
307
333
  - Slower for large apps compared to bundled versions
308
334
 
@@ -6,6 +6,12 @@ Learn how to create and use components in Lego.
6
6
 
7
7
  A component is a reusable, self-contained piece of UI with its own template, styles, and logic.
8
8
 
9
+
10
+ ::: warning Note That
11
+ `style` tags inside NON SFC components are inside the `<template>` tag. And outside the tag in SFCs.
12
+ :::
13
+
14
+
9
15
  ```html
10
16
  <template b-id="user-badge">
11
17
  <style>
@@ -24,8 +30,8 @@ A component is a reusable, self-contained piece of UI with its own template, sty
24
30
  }
25
31
  </style>
26
32
 
27
- <img class="avatar" src="{{ avatarUrl }}" alt="{{ name }}">
28
- <span>{{ name }}</span>
33
+ <img class="avatar" src="[[ avatarUrl ]]" alt="[[ name ]]">
34
+ <span>[[ name ]]</span>
29
35
  </template>
30
36
  ```
31
37
 
@@ -37,7 +43,7 @@ Define components directly in your HTML with `<template b-id>`:
37
43
 
38
44
  ```html
39
45
  <template b-id="hello-world" b-data="{ name: 'Default User' }">
40
- <h1>Hello {{ name }}!</h1>
46
+ <h1>Hello [[ name ]]!</h1>
41
47
  </template>
42
48
 
43
49
  <!-- Uses the default "Default User" -->
@@ -53,7 +59,7 @@ Use `Lego.define()` for programmatic component creation:
53
59
 
54
60
  ```js
55
61
  Lego.define('hello-world', `
56
- <h1>Hello {{ name }}!</h1>
62
+ <h1>Hello [[ name ]]!</h1>
57
63
  `, {
58
64
  name: 'Alice'
59
65
  });
@@ -66,7 +72,7 @@ With Vite, use `.lego` files:
66
72
  ```html
67
73
  <!-- hello-world.lego -->
68
74
  <template>
69
- <h1>Hello {{ name }}!</h1>
75
+ <h1>Hello [[ name ]]!</h1>
70
76
  </template>
71
77
 
72
78
  <script>
@@ -98,10 +104,10 @@ State is defined in the component's logic object:
98
104
  }
99
105
  ```
100
106
 
101
- Access state in templates using `{{ }}`:
107
+ Access state in templates using `[[ ]]`:
102
108
 
103
109
  ```html
104
- <p>Count: {{ count }}</p>
110
+ <p>Count: [[ count ]]</p>
105
111
  <button @click="increment()">+1</button>
106
112
  ```
107
113
 
@@ -182,7 +188,7 @@ Use `$ancestors()` to read parent component state:
182
188
 
183
189
  ```html
184
190
  <!-- In nested component -->
185
- <p>App title: {{ $ancestors('app-root').title }}</p>
191
+ <p>App title: [[ $ancestors('app-root').title ]]</p>
186
192
  ```
187
193
 
188
194
  ::: warning Read-Only
@@ -346,11 +352,11 @@ Clear timers, remove listeners:
346
352
  ```html
347
353
  <div b-show="loading">Loading...</div>
348
354
  <div b-show="!loading && data">
349
- <h2>{{ data.title }}</h2>
350
- <p>{{ data.content }}</p>
355
+ <h2>[[ data.title ]]</h2>
356
+ <p>[[ data.content ]]</p>
351
357
  </div>
352
358
  <div b-show="!loading && error">
353
- Error: {{ error }}
359
+ Error: [[ error ]]
354
360
  </div>
355
361
  ```
356
362
 
@@ -402,7 +408,7 @@ Use methods for computed values:
402
408
  ```
403
409
 
404
410
  ```html
405
- <p>Total: ${{ total().toFixed(2) }}</p>
411
+ <p>Total: $[[ total().toFixed(2) ]]</p>
406
412
  ```
407
413
 
408
414
  ## Next Steps