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
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Frequently Asked Questions
|
|
2
|
+
|
|
3
|
+
Quick answers to the most common LegoDOM questions.
|
|
4
|
+
|
|
5
|
+
## Project Setup
|
|
6
|
+
|
|
7
|
+
### Where do I define my routes?
|
|
8
|
+
|
|
9
|
+
**In your entry file (`app.js` or `main.js`), before `Lego.init()`.**
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
// src/app.js
|
|
13
|
+
import { Lego } from 'lego-dom';
|
|
14
|
+
import registerComponents from 'virtual:lego-components';
|
|
15
|
+
|
|
16
|
+
registerComponents();
|
|
17
|
+
|
|
18
|
+
// Define routes HERE ⭐
|
|
19
|
+
Lego.route('/', 'home-page');
|
|
20
|
+
Lego.route('/login', 'login-page');
|
|
21
|
+
Lego.route('/users/:id', 'user-profile');
|
|
22
|
+
|
|
23
|
+
await Lego.init(); // Must come AFTER routes
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Where does my config go?
|
|
27
|
+
|
|
28
|
+
Everything related to app configuration belongs in your entry file:
|
|
29
|
+
|
|
30
|
+
| What | Where |
|
|
31
|
+
|------|-------|
|
|
32
|
+
| Component registration | `registerComponents()` |
|
|
33
|
+
| Route definitions | `Lego.route(...)` |
|
|
34
|
+
| Global state init | `Lego.globals.user = null` |
|
|
35
|
+
| Engine start | `Lego.init()` |
|
|
36
|
+
|
|
37
|
+
### Should I use `main.js` or `app.js`?
|
|
38
|
+
|
|
39
|
+
Either works! It's just a naming convention. Use whatever your Vite template created, or rename it. Just make sure your `index.html` points to it:
|
|
40
|
+
|
|
41
|
+
```html
|
|
42
|
+
<script type="module" src="/src/app.js"></script>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Components
|
|
48
|
+
|
|
49
|
+
### Why isn't my component showing?
|
|
50
|
+
|
|
51
|
+
Check these common issues:
|
|
52
|
+
|
|
53
|
+
1. **No route defined** – Did you add `Lego.route('/', 'my-component')`?
|
|
54
|
+
2. **Missing `<lego-router>`** – Your HTML needs `<lego-router></lego-router>`
|
|
55
|
+
3. **Wrong component name** – Filename `user-card.lego` → component `<user-card>`
|
|
56
|
+
4. **Not registered** – Did you call `registerComponents()` before `init()`?
|
|
57
|
+
|
|
58
|
+
### Why do component names need hyphens?
|
|
59
|
+
|
|
60
|
+
It's a Web Components standard! Custom elements must contain a hyphen to avoid conflicts with future HTML elements.
|
|
61
|
+
|
|
62
|
+
✅ Valid: `user-card`, `app-nav`, `my-button`
|
|
63
|
+
❌ Invalid: `usercard`, `Card`, `button`
|
|
64
|
+
|
|
65
|
+
### Can I use PascalCase filenames?
|
|
66
|
+
|
|
67
|
+
Yes! LegoDOM automatically converts:
|
|
68
|
+
- `UserCard.lego` → `<user-card>`
|
|
69
|
+
- `AppNav.lego` → `<app-nav>`
|
|
70
|
+
- `my_component.lego` → `<my-component>`
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Navigation
|
|
75
|
+
|
|
76
|
+
### How do I navigate between pages?
|
|
77
|
+
|
|
78
|
+
**Option 1: Declarative (in templates)**
|
|
79
|
+
```html
|
|
80
|
+
<a href="/login" b-link>Go to Login</a>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Option 2: Programmatic (in JavaScript)**
|
|
84
|
+
```javascript
|
|
85
|
+
this.$go('/login').get();
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### What's the difference between `b-link` and `b-target`?
|
|
89
|
+
|
|
90
|
+
| Attribute | What it does |
|
|
91
|
+
|-----------|--------------|
|
|
92
|
+
| `b-link` | SPA navigation, updates URL, swaps `<lego-router>` |
|
|
93
|
+
| `b-target="#id"` | Swaps content of specific element, updates URL |
|
|
94
|
+
| `b-target="#id" b-link="false"` | Swaps content, does NOT update URL |
|
|
95
|
+
|
|
96
|
+
### How do I pass data when navigating?
|
|
97
|
+
|
|
98
|
+
Use global state:
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
// Before navigating
|
|
102
|
+
Lego.globals.selectedUser = { id: 42, name: 'John' };
|
|
103
|
+
this.$go('/user-details').get();
|
|
104
|
+
|
|
105
|
+
// In the target component
|
|
106
|
+
mounted() {
|
|
107
|
+
console.log(Lego.globals.selectedUser.name); // 'John'
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Or use route parameters:
|
|
112
|
+
|
|
113
|
+
```javascript
|
|
114
|
+
// Route: Lego.route('/users/:id', 'user-profile')
|
|
115
|
+
this.$go('/users/42').get();
|
|
116
|
+
|
|
117
|
+
// In user-profile component
|
|
118
|
+
mounted() {
|
|
119
|
+
const userId = this.$route.params.id; // '42'
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## State
|
|
126
|
+
|
|
127
|
+
### How do I share data between components?
|
|
128
|
+
|
|
129
|
+
Use `Lego.globals`:
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
// Component A sets it
|
|
133
|
+
Lego.globals.user = { name: 'John' };
|
|
134
|
+
|
|
135
|
+
// Component B reads it
|
|
136
|
+
console.log(Lego.globals.user.name); // 'John'
|
|
137
|
+
|
|
138
|
+
// In templates
|
|
139
|
+
<p>Hello, [[ global.user.name ]]!</p>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Why isn't my data updating the view?
|
|
143
|
+
|
|
144
|
+
Make sure you're mutating the reactive object, not replacing references:
|
|
145
|
+
|
|
146
|
+
```javascript
|
|
147
|
+
// ✅ This works - mutating property
|
|
148
|
+
this.items.push(newItem);
|
|
149
|
+
this.user.name = 'Jane';
|
|
150
|
+
|
|
151
|
+
// ❌ This might not work - reassigning local variable
|
|
152
|
+
let items = this.items;
|
|
153
|
+
items.push(newItem); // Won't trigger re-render!
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Styling
|
|
159
|
+
|
|
160
|
+
### What is `self` in styles?
|
|
161
|
+
|
|
162
|
+
`self` is a special keyword that targets the component's root element (like `:host` in Shadow DOM):
|
|
163
|
+
|
|
164
|
+
```html
|
|
165
|
+
<style>
|
|
166
|
+
self {
|
|
167
|
+
display: block;
|
|
168
|
+
padding: 1rem;
|
|
169
|
+
}
|
|
170
|
+
</style>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
LegoDOM automatically transforms this to `:host` for Shadow DOM.
|
|
174
|
+
|
|
175
|
+
### Do styles leak to other components?
|
|
176
|
+
|
|
177
|
+
No! Styles are scoped via Shadow DOM. Your `.button` class won't affect buttons in other components.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Build & Development
|
|
182
|
+
|
|
183
|
+
### Can I use LegoDOM without Vite?
|
|
184
|
+
|
|
185
|
+
Yes! Use the CDN approach:
|
|
186
|
+
|
|
187
|
+
```html
|
|
188
|
+
<script src="https://unpkg.com/lego-dom/main.js"></script>
|
|
189
|
+
<template b-id="my-component">...</template>
|
|
190
|
+
<my-component></my-component>
|
|
191
|
+
<script>Lego.init();</script>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
See [CDN Usage](/guide/cdn-usage) for details.
|
|
195
|
+
|
|
196
|
+
### Why use Vite?
|
|
197
|
+
|
|
198
|
+
Benefits of Vite + `.lego` files:
|
|
199
|
+
- **Hot reload** – Changes appear instantly
|
|
200
|
+
- **Auto-discovery** – No manual component registration
|
|
201
|
+
- **Better organization** – One file per component
|
|
202
|
+
- **Syntax highlighting** – Editor support for `.lego` files
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Still Stuck?
|
|
207
|
+
|
|
208
|
+
- 📖 [Complete Tutorial](/tutorial/) – Build an app step-by-step
|
|
209
|
+
- 💬 [GitHub Discussions](https://github.com/rayattack/LegoDOM/discussions)
|
|
210
|
+
- 🐛 [Report Issues](https://github.com/rayattack/LegoDOM/issues)
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Get up and running with Lego in under 5 minutes.
|
|
4
4
|
|
|
5
|
+
::: tip 🚀 Want a Complete Walkthrough?
|
|
6
|
+
Check out our **[Step-by-Step Tutorial](/tutorial/)** – build a full multi-page app from scratch in 15 minutes!
|
|
7
|
+
:::
|
|
8
|
+
|
|
5
9
|
## Installation
|
|
6
10
|
|
|
7
11
|
### Option 1: CDN (No Build Tools)
|
|
@@ -21,7 +25,7 @@ The fastest way to try Lego is via CDN:
|
|
|
21
25
|
<template b-id="my-component" b-data="{ count: 0 }">
|
|
22
26
|
<h1>Hello Lego!</h1>
|
|
23
27
|
<button @click="count++">Click me</button>
|
|
24
|
-
<p>Count:
|
|
28
|
+
<p>Count: [[ count ]]</p>
|
|
25
29
|
</template>
|
|
26
30
|
</body>
|
|
27
31
|
</html>
|
|
@@ -45,7 +49,7 @@ import { Lego } from 'lego-dom';
|
|
|
45
49
|
Lego.define('my-component', `
|
|
46
50
|
<h1>Hello Lego!</h1>
|
|
47
51
|
<button @click="count++">Click me</button>
|
|
48
|
-
<p>Count:
|
|
52
|
+
<p>Count: [[ count ]]</p>
|
|
49
53
|
`, {
|
|
50
54
|
count: 0,
|
|
51
55
|
});
|
|
@@ -144,8 +148,8 @@ Lego.define('click-counter', `
|
|
|
144
148
|
}
|
|
145
149
|
</style>
|
|
146
150
|
|
|
147
|
-
<h2>
|
|
148
|
-
<p>Count:
|
|
151
|
+
<h2>[[ message ]]</h2>
|
|
152
|
+
<p>Count: [[ count ]]</p>
|
|
149
153
|
<button @click="increment()">Click Me!</button>
|
|
150
154
|
`, {
|
|
151
155
|
message: 'Welcome!',
|
|
@@ -175,8 +179,8 @@ Create `src/components/click-counter.lego`:
|
|
|
175
179
|
}
|
|
176
180
|
</style>
|
|
177
181
|
|
|
178
|
-
<h2>
|
|
179
|
-
<p>Count:
|
|
182
|
+
<h2>[[ message ]]</h2>
|
|
183
|
+
<p>Count: [[ count ]]</p>
|
|
180
184
|
<button @click="increment()">Click Me!</button>
|
|
181
185
|
</template>
|
|
182
186
|
|
|
@@ -197,11 +201,11 @@ The Vite plugin automatically discovers and registers it!
|
|
|
197
201
|
|
|
198
202
|
### 1. Templates
|
|
199
203
|
|
|
200
|
-
Templates define what your component looks like. Use `
|
|
204
|
+
Templates define what your component looks like. Use `[[ ]]` for dynamic content:
|
|
201
205
|
|
|
202
206
|
```html
|
|
203
|
-
<h1>Hello
|
|
204
|
-
<p>
|
|
207
|
+
<h1>Hello [[ name ]]!</h1>
|
|
208
|
+
<p>[[ calculateAge() ]] years old</p>
|
|
205
209
|
```
|
|
206
210
|
|
|
207
211
|
### 2. State (Studs)
|
|
@@ -238,7 +242,7 @@ Special attributes for common patterns:
|
|
|
238
242
|
|
|
239
243
|
```html
|
|
240
244
|
<p b-show="isLoggedIn">Welcome back!</p>
|
|
241
|
-
<li b-for="item in items">
|
|
245
|
+
<li b-for="item in items">[[ item.name ]]</li>
|
|
242
246
|
<input b-sync="username" />
|
|
243
247
|
```
|
|
244
248
|
|
package/docs/guide/index.md
CHANGED
|
@@ -77,7 +77,7 @@ Compare that to:
|
|
|
77
77
|
| Aspect | Lego | Traditional Frameworks |
|
|
78
78
|
|--------|--------|----------------------|
|
|
79
79
|
| **Reactivity** | Direct object mutation | setState / dispatch / ref() |
|
|
80
|
-
| **Templates** | HTML with <code v-pre>
|
|
80
|
+
| **Templates** | HTML with <code v-pre>[[ ]]</code> | JSX / template syntax |
|
|
81
81
|
| **Styles** | Shadow DOM (native) | CSS-in-JS / scoped CSS |
|
|
82
82
|
| **Build** | Optional | Required |
|
|
83
83
|
| **Learning Curve** | Hours | Days/Weeks |
|
package/docs/guide/lifecycle.md
CHANGED
|
@@ -233,6 +233,38 @@ Called when the component is removed from the DOM.
|
|
|
233
233
|
}
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Performance Hooks (Metrics)
|
|
241
|
+
|
|
242
|
+
For advanced monitoring, LegoDOM provides global hooks in `Lego.config.metrics`. These run for **every** component.
|
|
243
|
+
|
|
244
|
+
### `onRenderStart` & `onRenderEnd`
|
|
245
|
+
|
|
246
|
+
Useful for tracking how long renders take, which helps identify slow components.
|
|
247
|
+
|
|
248
|
+
```javascript
|
|
249
|
+
Lego.config.metrics = {
|
|
250
|
+
onRenderStart(el) {
|
|
251
|
+
console.time(`render-${el.tagName}`);
|
|
252
|
+
},
|
|
253
|
+
onRenderEnd(el) {
|
|
254
|
+
console.timeEnd(`render-${el.tagName}`);
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### `onAllSettled` (New in v2.0)
|
|
260
|
+
|
|
261
|
+
Called when the entire component tree (including Shadow DOM children) has finished its initial render pass. This is perfect for removing loading spinners or measuring "Time to Interactive".
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
/* main.js */
|
|
265
|
+
Lego.init(document.body).then(() => {
|
|
266
|
+
document.getElementById('global-loader').remove();
|
|
267
|
+
});
|
|
236
268
|
```
|
|
237
269
|
|
|
238
270
|
## Lifecycle Flow
|
|
@@ -12,16 +12,16 @@ The fastest way to get started with Lego is using the CDN. No build tools requir
|
|
|
12
12
|
<title>Lego Quick Start</title>
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
15
|
-
<!--
|
|
16
|
-
<hello-world></hello-world>
|
|
15
|
+
<!-- Interpolation works here too -->
|
|
16
|
+
<hello-world name="Lego"></hello-world>
|
|
17
17
|
|
|
18
18
|
<!-- 3. Define the template -->
|
|
19
19
|
<template b-id="hello-world">
|
|
20
20
|
<style>
|
|
21
21
|
h1 { color: #646cff; }
|
|
22
22
|
</style>
|
|
23
|
-
<h1>Hello
|
|
24
|
-
<button @click="
|
|
23
|
+
<h1>Hello [[ name ]]!</h1>
|
|
24
|
+
<button @click="count++">Count is [[ count ]]</button>
|
|
25
25
|
</template>
|
|
26
26
|
|
|
27
27
|
<!-- 4. Load Lego -->
|
package/docs/guide/reactivity.md
CHANGED
|
@@ -189,7 +189,7 @@ Nested objects are automatically reactive:
|
|
|
189
189
|
|
|
190
190
|
```html
|
|
191
191
|
<!-- All reactive -->
|
|
192
|
-
<p>
|
|
192
|
+
<p>[[ user.profile.settings.theme ]]</p>
|
|
193
193
|
```
|
|
194
194
|
|
|
195
195
|
```js
|
|
@@ -381,7 +381,7 @@ Use methods:
|
|
|
381
381
|
```
|
|
382
382
|
|
|
383
383
|
```html
|
|
384
|
-
<p>
|
|
384
|
+
<p>[[ fullName() ]]</p>
|
|
385
385
|
```
|
|
386
386
|
|
|
387
387
|
### Debouncing Updates
|
package/docs/guide/routing.md
CHANGED
|
@@ -1,14 +1,75 @@
|
|
|
1
1
|
# Surgical Routing
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Stop rebuilding your entire page just to change one div.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
LegoDOM's router is different. It doesn't have a single "Root Outlet". Instead, **any element** can be a router target. This allows you to build **Persistent Layouts** (like Sidebars, Music Players, or Chat Windows) that never reload or lose state while the user navigates.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
::: tip 🚀 Just Want to Navigate Between Pages?
|
|
8
|
+
Here's the quick answer:
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
**1. Define routes in `app.js`:**
|
|
11
|
+
```javascript
|
|
12
|
+
Lego.route('/', 'home-page');
|
|
13
|
+
Lego.route('/login', 'login-page');
|
|
14
|
+
Lego.route('/dashboard', 'dashboard-page');
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**2. Navigate with links:**
|
|
18
|
+
```html
|
|
19
|
+
<a href="/login" b-link>Go to Login</a>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**3. Or navigate with JavaScript:**
|
|
23
|
+
```javascript
|
|
24
|
+
this.$go('/login').get();
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
That's it! For the full tutorial, see [Adding Routes](/tutorial/03-adding-routes).
|
|
28
|
+
:::
|
|
29
|
+
|
|
30
|
+
## Quick Reference
|
|
31
|
+
|
|
32
|
+
| I want to... | Code |
|
|
33
|
+
|--------------|------|
|
|
34
|
+
| Define a route | `Lego.route('/path', 'component-name')` |
|
|
35
|
+
| Link to a page | `<a href="/path" b-link>Click</a>` |
|
|
36
|
+
| Navigate via JS | `this.$go('/path').get()` |
|
|
37
|
+
| Get URL params | `this.$route.params.id` |
|
|
38
|
+
| Update only one div | `<a href="/x" b-target="#myDiv">` |
|
|
39
|
+
| Navigate without URL change | `this.$go('/x').get(false)` |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## The Architecture: "The Persistent Shell"
|
|
44
|
+
|
|
45
|
+
The best way to use LegoDOM is to define a static "Shell" that holds your persistent tools, and standard outlets for your content.
|
|
46
|
+
|
|
47
|
+
```html
|
|
48
|
+
<body>
|
|
49
|
+
<!-- 1. The Shell (Sidebar): Never reloads. Keeps scroll pos & draft state. -->
|
|
50
|
+
<aside id="sidebar">
|
|
51
|
+
<file-tree></file-tree>
|
|
52
|
+
</aside>
|
|
53
|
+
|
|
54
|
+
<!-- 2. The Stage (Main Content): This changes when URL changes. -->
|
|
55
|
+
<lego-router id="stage"></lego-router>
|
|
56
|
+
|
|
57
|
+
<!-- 3. The Context (Right Panel): Tools based on selection. -->
|
|
58
|
+
<aside id="tools"></aside>
|
|
59
|
+
</body>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Then, you simply tell links *where* to render their content:
|
|
63
|
+
|
|
64
|
+
```html
|
|
65
|
+
<!-- Updates component in #stage (Default URL navigation) -->
|
|
66
|
+
<a href="/dashboard" b-target="#stage">Dashboard</a>
|
|
67
|
+
|
|
68
|
+
<!-- Updates component in #tools (Keeps URL sync, but only touches right panel) -->
|
|
69
|
+
<a href="/tools/settings" b-target="#tools">Settings</a>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
This feels like a native app. The Sidebar doesn't flicker. The scroll position isn't lost.
|
|
12
73
|
|
|
13
74
|
---
|
|
14
75
|
|
|
@@ -130,7 +191,7 @@ Keep a persistent Main Content while swapping sidebars.
|
|
|
130
191
|
<!-- Context specific tools render here -->
|
|
131
192
|
<template b-id="user-profile">
|
|
132
193
|
<h1>User Profile</h1>
|
|
133
|
-
<p>User ID:
|
|
194
|
+
<p>User ID: [[ $route.params.id ]]</p>
|
|
134
195
|
<button @click="loadUser()">Load User</button>
|
|
135
196
|
</template>
|
|
136
197
|
</aside>
|
|
@@ -213,7 +274,7 @@ Lego.route('/customers/:id/orders/:orderId', 'order-details-page');
|
|
|
213
274
|
<!-- order-details-page.lego -->
|
|
214
275
|
<template>
|
|
215
276
|
<customers-layout>
|
|
216
|
-
<order-info id="
|
|
277
|
+
<order-info id="[[ $route.params.orderId ]]"></order-info>
|
|
217
278
|
</customers-layout>
|
|
218
279
|
</template>
|
|
219
280
|
```
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Server-Side Architecture
|
|
2
|
+
|
|
3
|
+
LegoDOM is designed to play nicely with backend frameworks like **Heaven**, **Django**, **Rails**, or **Go**.
|
|
4
|
+
|
|
5
|
+
Unlike typical SPAs that require a massive build step, LegoDOM can fetch components **on-demand** from your server. This gives you the routing simplicity of a backend with the interactivity of a frontend framework.
|
|
6
|
+
|
|
7
|
+
## The Auto-Loader Pattern
|
|
8
|
+
|
|
9
|
+
Instead of bundling every component `users-list`, `chat-widget`, `billing-modal` into one big `app.js` file, you can load them lazily.
|
|
10
|
+
|
|
11
|
+
Configure the `loader` hook in your main entry file:
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
Lego.init(document.body, {
|
|
15
|
+
loader: (tagName) => `/components/${tagName}.lego`
|
|
16
|
+
});
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Now, your HTML can just use tags that haven't been defined yet:
|
|
20
|
+
|
|
21
|
+
```html
|
|
22
|
+
<!-- index.html (Server Rendered) -->
|
|
23
|
+
<h1>Dashboard</h1>
|
|
24
|
+
|
|
25
|
+
<!-- LegoDOM sees this, fetches /components/user-feed.lego, and upgrades it -->
|
|
26
|
+
<user-feed></user-feed>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Power Mode: Authentication & State
|
|
30
|
+
|
|
31
|
+
Often you need to pass **Authentication Tokens** or **Global State** to the server to get a personalized component.
|
|
32
|
+
|
|
33
|
+
Return a `Promise` from your loader to take full control of the fetch:
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
Lego.init(document.body, {
|
|
37
|
+
loader: async (tagName) => {
|
|
38
|
+
const token = localStorage.getItem('jwt');
|
|
39
|
+
|
|
40
|
+
const response = await fetch(`/components/${tagName}`, {
|
|
41
|
+
headers: {
|
|
42
|
+
'Authorization': `Bearer ${token}`,
|
|
43
|
+
'X-Theme': Lego.globals.theme
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (!response.ok) return null;
|
|
48
|
+
return await response.text(); // Return the SFC content
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Server-Side State Injection
|
|
54
|
+
|
|
55
|
+
Since the server generates the `.lego` file, it can inject data before sending it to the browser.
|
|
56
|
+
|
|
57
|
+
**Example (Backend Pseudocode):**
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
# GET /components/user-card ("Flask-like" syntax)
|
|
61
|
+
@app.route('/components/user-card')
|
|
62
|
+
def get_user_card():
|
|
63
|
+
user = db.get_current_user()
|
|
64
|
+
|
|
65
|
+
# We bake the data right into the template!
|
|
66
|
+
return f"""
|
|
67
|
+
<template>
|
|
68
|
+
<div class="card">
|
|
69
|
+
<h2>[[ name ]]</h2>
|
|
70
|
+
<p>Balance: $[[ balance ]]</p>
|
|
71
|
+
</div>
|
|
72
|
+
</template>
|
|
73
|
+
|
|
74
|
+
<script>
|
|
75
|
+
export default {
|
|
76
|
+
name: "{user.name}",
|
|
77
|
+
balance: {user.balance}
|
|
78
|
+
}
|
|
79
|
+
</script>
|
|
80
|
+
"""
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The browser receives a component that **already has the data**. No second API call needed!
|
|
84
|
+
|
|
85
|
+
## Runtime Components
|
|
86
|
+
|
|
87
|
+
If you fetch component code manually (e.g. via WebSockets or a custom pipeline), you can register it using `Lego.defineSFC`:
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
socket.on('component_update', (msg) => {
|
|
91
|
+
// msg.code contains the <template>... string
|
|
92
|
+
Lego.defineSFC(msg.code, msg.name + '.lego');
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Production Considerations
|
|
98
|
+
|
|
99
|
+
### 1. Error Handling
|
|
100
|
+
What if the server returns a 404 or 500? Your `loader` should handle this gracefully so the user isn't stuck with a blank screen.
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
loader: async (tagName) => {
|
|
104
|
+
try {
|
|
105
|
+
const res = await fetch(`/components/${tagName}.lego`);
|
|
106
|
+
if (!res.ok) {
|
|
107
|
+
console.error(`Component ${tagName} failed: ${res.status}`);
|
|
108
|
+
// Fallback to a generic error component
|
|
109
|
+
return `<template><div class="error">Failed to load ${tagName}</div></template>`;
|
|
110
|
+
}
|
|
111
|
+
return await res.text();
|
|
112
|
+
} catch (err) {
|
|
113
|
+
return `<template><div class="error">Network Error</div></template>`;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 2. Caching Strategy
|
|
119
|
+
LegoDOM caches the *compiled class* of a component once loaded. It does **not** re-fetch the `.lego` file for every instance.
|
|
120
|
+
However, if you want browser-level caching for the HTTP requests, ensure your server sends correct headers:
|
|
121
|
+
|
|
122
|
+
```http
|
|
123
|
+
Cache-Control: public, max-age=3600, immutable
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 3. Preloading
|
|
127
|
+
If you know a user is about to visit a page (e.g. hovering a link), you can preload the component SFC:
|
|
128
|
+
|
|
129
|
+
```javascript
|
|
130
|
+
const preload = (tagName) => {
|
|
131
|
+
// Just calling the loader puts it in the browser's fetch cache
|
|
132
|
+
Lego.config.loader(tagName);
|
|
133
|
+
};
|
|
134
|
+
```
|