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.
- package/.legodom +87 -0
- package/CHANGELOG.md +87 -3
- package/cdn.html +10 -5
- package/docs/.vitepress/config.js +23 -7
- package/docs/api/config.md +95 -0
- package/docs/api/define.md +29 -2
- package/docs/api/directives.md +10 -2
- package/docs/api/index.md +1 -0
- package/docs/contributing/01-welcome.md +2 -0
- package/docs/contributing/02-registry.md +37 -3
- package/docs/contributing/06-init.md +13 -2
- package/docs/contributing/07-observer.md +3 -0
- package/docs/contributing/08-snap.md +15 -1
- package/docs/contributing/10-studs.md +3 -1
- package/docs/contributing/11-scanner.md +13 -0
- package/docs/contributing/12-render.md +32 -10
- package/docs/contributing/13-directives.md +19 -1
- package/docs/contributing/14-events.md +1 -1
- package/docs/contributing/15-router.md +49 -1
- package/docs/contributing/16-state.md +9 -10
- package/docs/contributing/17-legodom.md +1 -8
- package/docs/contributing/index.md +23 -4
- package/docs/examples/form.md +1 -1
- package/docs/examples/index.md +3 -3
- package/docs/examples/routing.md +10 -10
- package/docs/examples/sfc-showcase.md +1 -1
- package/docs/examples/todo-app.md +7 -7
- package/docs/guide/cdn-usage.md +44 -18
- package/docs/guide/components.md +18 -12
- package/docs/guide/directives.md +131 -22
- package/docs/guide/directory-structure.md +248 -0
- package/docs/guide/faq.md +210 -0
- package/docs/guide/getting-started.md +14 -10
- package/docs/guide/index.md +1 -1
- package/docs/guide/lifecycle.md +32 -0
- package/docs/guide/quick-start.md +4 -4
- package/docs/guide/reactivity.md +2 -2
- package/docs/guide/routing.md +69 -8
- package/docs/guide/server-side.md +134 -0
- package/docs/guide/sfc.md +96 -13
- package/docs/guide/templating.md +62 -57
- package/docs/index.md +9 -9
- package/docs/router/basic-routing.md +8 -8
- package/docs/router/cold-entry.md +2 -2
- package/docs/router/history.md +7 -7
- package/docs/router/index.md +1 -1
- package/docs/router/resolver.md +5 -5
- package/docs/router/surgical-swaps.md +5 -5
- package/docs/tutorial/01-project-setup.md +152 -0
- package/docs/tutorial/02-your-first-component.md +226 -0
- package/docs/tutorial/03-adding-routes.md +279 -0
- package/docs/tutorial/04-multi-page-app.md +329 -0
- package/docs/tutorial/05-state-and-globals.md +285 -0
- package/docs/tutorial/index.md +40 -0
- package/examples/vite-app/index.html +1 -0
- package/examples/vite-app/src/app.js +2 -2
- package/examples/vite-app/src/components/side-menu.lego +46 -0
- package/examples/vite-app/vite.config.js +2 -1
- package/main.js +261 -72
- package/main.min.js +7 -0
- package/monitoring-plugin.js +111 -0
- package/package.json +4 -2
- package/parse-lego.js +49 -22
- package/tests/error.test.js +74 -0
- package/tests/main.test.js +2 -2
- package/tests/memory.test.js +68 -0
- package/tests/monitoring.test.js +74 -0
- package/tests/naming.test.js +74 -0
- package/tests/parse-lego.test.js +2 -2
- package/tests/security.test.js +67 -0
- package/tests/server.test.js +114 -0
- package/tests/syntax.test.js +67 -0
- package/vite-plugin.js +3 -2
- 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(
|
|
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(
|
|
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
|
|
122
|
+
You’ll notice that for things like `b-show` or mustaches, the library calls `safeEval(expr, { state, self: b.node })`.
|
|
102
123
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
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 "
|
|
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
|
-
|
|
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
|
|
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
|
|
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**: `
|
|
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`
|
|
20
|
+
### 3. The `$global` Dependency Check (Smart Broadcast)
|
|
22
21
|
|
|
23
|
-
The library uses a specific
|
|
22
|
+
The library uses a specific optimization to avoid re-rendering the whole world.
|
|
24
23
|
|
|
25
|
-
- **
|
|
26
|
-
|
|
27
|
-
- **The
|
|
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.
|
|
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
|
-
#
|
|
1
|
+
# Architectural Deep Dive
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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.
|
package/docs/examples/form.md
CHANGED
package/docs/examples/index.md
CHANGED
|
@@ -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:
|
|
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,
|
|
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="
|
|
45
|
+
<span class="[[ todo.done ? 'done' : '' ]]">[[ todo.text ]]</span>
|
|
46
46
|
</li>
|
|
47
47
|
</ul>
|
|
48
48
|
</template>
|
package/docs/examples/routing.md
CHANGED
|
@@ -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>
|
|
156
|
-
<p style="margin:0;color:#666;">
|
|
155
|
+
<strong>[[ user.name ]]</strong>
|
|
156
|
+
<p style="margin:0;color:#666;">[[ user.email ]]</p>
|
|
157
157
|
</div>
|
|
158
|
-
<a href="/users/
|
|
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>
|
|
185
|
-
<p><strong>Email:</strong>
|
|
186
|
-
<p><strong>Phone:</strong>
|
|
187
|
-
<p><strong>Website:</strong>
|
|
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
|
-
|
|
194
|
-
|
|
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,
|
|
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>
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
205
|
-
|
|
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>
|
|
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 (
|
|
217
|
+
Clear completed ([[ completedCount() ]])
|
|
218
218
|
</button>
|
|
219
219
|
</div>
|
|
220
220
|
</template>
|
package/docs/guide/cdn-usage.md
CHANGED
|
@@ -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>
|
|
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@
|
|
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@
|
|
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="
|
|
137
|
-
|
|
132
|
+
<span class="[[ todo.done ? 'done' : '' ]]">
|
|
133
|
+
[[ todo.text ]]
|
|
138
134
|
</span>
|
|
139
135
|
</li>
|
|
140
136
|
</ul>
|
|
141
137
|
|
|
142
|
-
<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,
|
|
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@
|
|
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@
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
|
package/docs/guide/components.md
CHANGED
|
@@ -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="
|
|
28
|
-
<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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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>
|
|
350
|
-
<p>
|
|
355
|
+
<h2>[[ data.title ]]</h2>
|
|
356
|
+
<p>[[ data.content ]]</p>
|
|
351
357
|
</div>
|
|
352
358
|
<div b-show="!loading && error">
|
|
353
|
-
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: $
|
|
411
|
+
<p>Total: $[[ total().toFixed(2) ]]</p>
|
|
406
412
|
```
|
|
407
413
|
|
|
408
414
|
## Next Steps
|