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
package/.legodom
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# LegoDOM Framework Definition (.legodom)
|
|
2
|
+
|
|
3
|
+
This file defines the valid syntax, API, and behavior of the LegoDOM framework. Use this context to write valid `.lego` components and understand the runtime behavior.
|
|
4
|
+
|
|
5
|
+
## 1. Component Structure (.lego)
|
|
6
|
+
A `.lego` file is a Single File Component (SFC) composed of three sections:
|
|
7
|
+
|
|
8
|
+
```html
|
|
9
|
+
<template b-styles="optional-style-id">
|
|
10
|
+
<!-- HTML template with directives -->
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script>
|
|
14
|
+
export default {
|
|
15
|
+
// Reactive State
|
|
16
|
+
count: 0,
|
|
17
|
+
|
|
18
|
+
// Lifecycle Hooks
|
|
19
|
+
mounted() { console.log('Mounted'); },
|
|
20
|
+
updated() { console.log('State updated'); },
|
|
21
|
+
unmounted() { console.log('Destroyed'); },
|
|
22
|
+
|
|
23
|
+
// Methods
|
|
24
|
+
increment() { this.count++ }
|
|
25
|
+
}
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<style>
|
|
29
|
+
/* Scoped CSS. 'self' is replaced with ':host' automatically */
|
|
30
|
+
self { display: block; }
|
|
31
|
+
h1 { color: blue; }
|
|
32
|
+
</style>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 2. Template Directives
|
|
36
|
+
|
|
37
|
+
| Directive | Syntax | Description |
|
|
38
|
+
|-----------|--------|-------------|
|
|
39
|
+
| **b-if** | `b-if="expr"` | Conditionally renders element if `expr` is truthy. Replaced by comment anchor if false. |
|
|
40
|
+
| **b-show**| `b-show="expr"` | Toggles `display: none` based on `expr`. |
|
|
41
|
+
| **b-for** | `b-for="item in list"` | Renders element for each item in `list`. `item` is available in scope. |
|
|
42
|
+
| **b-text**| `b-text="path"` | Sets `textContent` to value at `path` (e.g. `user.name`). |
|
|
43
|
+
| **b-html**| `b-html="expr"` | Sets `innerHTML` to result of `expr`. **Unsafe**. |
|
|
44
|
+
| **b-sync**| `b-sync="path"` | Two-way binding for inputs. Updates `state.path` on input/change. |
|
|
45
|
+
| **Event** | `@event="expr"` | Native event listener. `event` is available in scope. E.g. `@click="save()"`. |
|
|
46
|
+
| **Interpolation** | `[[ expr ]]` | Text interpolation. Delimiters are `[[` and `]]` by default. |
|
|
47
|
+
|
|
48
|
+
Note: `b-data` can be used on `<template>` or the custom element tag to inject initial state from JSON string.
|
|
49
|
+
|
|
50
|
+
## 3. Script Context & Helpers
|
|
51
|
+
The `script` exports a default object which becomes the reactive state (`this`).
|
|
52
|
+
Inside methods and template expressions, the following helpers are available:
|
|
53
|
+
|
|
54
|
+
### Scope
|
|
55
|
+
- **this**: The reactive state of the component.
|
|
56
|
+
- **event**: The native DOM event (only in `@event` handlers).
|
|
57
|
+
|
|
58
|
+
### Helpers
|
|
59
|
+
- **$element**: The component's host DOM element.
|
|
60
|
+
- **$emit(name, detail)**: Dispatches a CustomEvent `name` with `detail` (bubbles: true, composed: true).
|
|
61
|
+
- **$go(path, ...targets)**: Router navigation helper. Returns object with method calls.
|
|
62
|
+
- `this.$go('/url', '#target').get()`
|
|
63
|
+
- `this.$go('/api', '#response').post({data})`
|
|
64
|
+
- **$ancestors(tagName)**: Finds the state of the nearest ancestor component with `tagName`.
|
|
65
|
+
- **$registry(tagName)**: Access shared state of logical-only components defined via `Lego.define`.
|
|
66
|
+
- **$route**: Global route state object.
|
|
67
|
+
|
|
68
|
+
## 4. Router & Targets
|
|
69
|
+
LegoDOM uses a "Target-Oriented" router. Navigation updates specific DOM elements (targets) rather than full page reloads.
|
|
70
|
+
|
|
71
|
+
- **Definition**: `Lego.route('/path/:id', 'tag-name', middleware)`
|
|
72
|
+
- **Targets**:
|
|
73
|
+
- `string`: CSS selector (e.g., `#main`, `user-card`).
|
|
74
|
+
- `function`: `(allComponents) => elements`
|
|
75
|
+
- **Usage**:
|
|
76
|
+
- HTML: `<a href="/path" b-target="#main">Link</a>` creates a surgical navigation link.
|
|
77
|
+
- JS: `this.$go('/path', '#main').get()`
|
|
78
|
+
|
|
79
|
+
## 5. Lifecycle Hooks
|
|
80
|
+
- **mounted()**: Called when the component is attached to DOM, shadow root created, and initial render complete.
|
|
81
|
+
- **updated()**: Called after reactive state changes have been batched and applied to the DOM.
|
|
82
|
+
- **unmounted()**: Called when component is removed from the DOM.
|
|
83
|
+
|
|
84
|
+
## 6. Global Configuration
|
|
85
|
+
- `Lego.init(root, { styles: { ... }, loader: callback })`: Initializes the app.
|
|
86
|
+
- `Lego.globals`: Global reactive state shared across all components.
|
|
87
|
+
```
|
package/CHANGELOG.md
CHANGED
|
@@ -2,12 +2,96 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.3.4] - 2026-01-16
|
|
6
|
+
|
|
7
|
+
### Improvements
|
|
8
|
+
|
|
9
|
+
- **Minified Build:** Added a build script (`npm run build`) that uses `esbuild` to generate a `main.min.js` file, reducing file size by ~55% for optimal CDN performance.
|
|
10
|
+
|
|
11
|
+
## [1.3.3] - 2026-01-16
|
|
12
|
+
|
|
13
|
+
### Fixes
|
|
14
|
+
|
|
15
|
+
- **Inline Arrays in `b-for`:** Fixed a bug where inline arrays like `b-for="item in [{ name: 'A' }, { name: 'B' }]"` would fail to render. The fix changes `b-for` to clone the entire node as a template instead of storing innerHTML.
|
|
16
|
+
|
|
17
|
+
### Documentation
|
|
18
|
+
|
|
19
|
+
- **Large Apps Guide:** Added "Scaling to Multi-Domain Apps" section for enterprise projects with multiple business domains (HRIS, Finance, Planning, etc.).
|
|
20
|
+
|
|
21
|
+
### Improvements
|
|
22
|
+
|
|
23
|
+
- **Vite Plugin:** Added `importPath` option to `legoPlugin()`, allowing developers to override where the `Lego` core is imported from (e.g., for local testing or custom builds).
|
|
24
|
+
|
|
25
|
+
## [1.3.1] - 2026-01-16
|
|
26
|
+
|
|
27
|
+
### Fixes
|
|
28
|
+
|
|
29
|
+
- **`this.$emit()` in Script Methods:** `$emit` is now available on the component state, allowing event dispatching from script logic.
|
|
30
|
+
```javascript
|
|
31
|
+
handleSave() {
|
|
32
|
+
this.$emit('save', { id: this.itemId });
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## [1.3.0] - 2026-01-16
|
|
37
|
+
|
|
38
|
+
### New Features
|
|
39
|
+
|
|
40
|
+
- **`b-var` Directive:** Access DOM elements directly via `this.$vars.name`. Useful for triggering `.click()`, `.focus()`, or `.play()` on hidden inputs, video elements, etc.
|
|
41
|
+
```html
|
|
42
|
+
<input type="file" b-var="fileInput" style="display:none">
|
|
43
|
+
<button @click="$vars.fileInput.click()">Upload</button>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Documentation
|
|
47
|
+
|
|
48
|
+
- **New Guide: Large Apps ("Chaos Tends To A Minimum"):** Added a comprehensive architectural guide for structuring enterprise-scale projects with 200+ components. Introduces the Blocks, Widgets, Components, Pages hierarchy to cleanly separate Identity, Intent, Computation, and Coordination.
|
|
49
|
+
- **`b-if` Directive:** Documented the `b-if` directive for conditional rendering (DOM insertion/removal).
|
|
50
|
+
- **`b-text` Directive:** Documented the limited `b-text` directive (property paths only, no expressions).
|
|
51
|
+
- **Directives "See Also":** Added cross-references to `b-target`, `b-id`, `b-styles` in the Directives guide.
|
|
52
|
+
|
|
53
|
+
## [1.2.0] - 2026-01-15
|
|
54
|
+
|
|
55
|
+
**Breaking Change: New Template Syntax** 🚨
|
|
56
|
+
To ensure compatibility with server-side frameworks (Jinja, Django, Flask) and JavaScript template literals, **LegoDOM now uses `[[ ]]` by default** instead of `{{ }}`.
|
|
57
|
+
|
|
58
|
+
- **Breaking:** Default interpolation syntax changed from `{{ variable }}` to `[[ variable ]]`.
|
|
59
|
+
- **Feature:** Added `Lego.config.syntax` to configure delimiters.
|
|
60
|
+
- Set to `'mustache'` to revert to `{{ }}`.
|
|
61
|
+
- Set to `'brackets'` (default) for `[[ ]]`.
|
|
62
|
+
- **Feature:** Added support for snake_case, PascalCase, camelCase, and kebab-case component names for `.lego` files.
|
|
63
|
+
- **Feature:** Added `Lego.config.loader` to fetch SFCs from a server endpoint.
|
|
64
|
+
- **Fix:** Fixed a critical bug where `snap()` triggered double renders and mounted hooks.
|
|
65
|
+
|
|
66
|
+
## [1.1.0] - 2026-01-12
|
|
67
|
+
|
|
68
|
+
**The Enterprise Readiness Update**
|
|
69
|
+
This release focuses on Security, Performance, and Resilience, making LegoDOM suitable for high-traffic production environments.
|
|
70
|
+
|
|
71
|
+
### Security Hardening
|
|
72
|
+
|
|
73
|
+
- **Secure Expression Evaluation:** Implemented a new `safeEval` validator that blocks dangerous keywords (e.g., `function`, `eval`, `constructor`) to prevent arbitrary code execution.
|
|
74
|
+
- **XSS Protection:** `safeEval` now automatically escapes output by default.
|
|
75
|
+
- **New `b-html` Directive:** Added `b-html` for safely rendering raw HTML content (replacing `innerHTML`), requiring explicit opt-in for potential XSS risks.
|
|
76
|
+
|
|
77
|
+
### Performance
|
|
78
|
+
|
|
79
|
+
- **Expression Caching:** Compiled expressions are now cached in a `WeakMap`, transforming `O(n)` compilation costs into `O(1)` for repeated renders. This yields a massive performance boost for large lists (`b-for`).
|
|
80
|
+
- **Optimized Binding:** Replaced `querySelectorAll('*')` with `TreeWalker`, significantly reducing memory allocation and DOM traversal time during component initialization.
|
|
81
|
+
|
|
82
|
+
### Resilience & Scalability
|
|
83
|
+
|
|
84
|
+
- **Global Error Handling:** Introduced `Lego.config.onError` hook for centralized error reporting (compatible with Sentry/Datadog).
|
|
85
|
+
- **Graceful Rendering:** Rendering errors (e.g., accessing undefined properties in `{{ }}`) are now caught and reported, but do not crash the component or application.
|
|
86
|
+
- **Memory Management:** Fixed nested component lifecycle issues where Shadow DOM children were not correctly tracked, preventing memory leaks in complex trees.
|
|
87
|
+
- **Monitoring Plugin:** Added performance monitoring hooks (`onRenderStart`, `onRenderEnd`) and a new `monitoring-plugin.js` for realtime metrics.
|
|
88
|
+
|
|
5
89
|
## [1.0.0] - 2026-01-10
|
|
6
90
|
|
|
7
91
|
**The Launch Release!** 🚀
|
|
8
92
|
LegoDOM moves out of beta with a finalized API, robust routing, and a hybrid rendering engine.
|
|
9
93
|
|
|
10
|
-
###
|
|
94
|
+
### Major features
|
|
11
95
|
|
|
12
96
|
- **Surgical Routing:** Introduced a groundbreaking `b-target` attribute that allows any link to update any part of the page without a full reload.
|
|
13
97
|
- **Smart History:** The router now tracks surgical updates in `history.state`, correctly restoring "fragment" states when using the Back/Forward buttons.
|
|
@@ -21,7 +105,7 @@ LegoDOM moves out of beta with a finalized API, robust routing, and a hybrid ren
|
|
|
21
105
|
- **Cleaner Templates:** Template expressions now support `$route` directly (`{{ $route.params.id }}`) without `global.` prefix.
|
|
22
106
|
- **HMR 2.0:** The Vite plugin now correctly handles adding/deleting `.lego` files and performs smarter hot updates.
|
|
23
107
|
|
|
24
|
-
###
|
|
108
|
+
### Improvements
|
|
25
109
|
|
|
26
110
|
- **Router:**
|
|
27
111
|
- Exposed `$go(path, ...targets).get()` for programmatic surgical navigation.
|
|
@@ -37,7 +121,7 @@ LegoDOM moves out of beta with a finalized API, robust routing, and a hybrid ren
|
|
|
37
121
|
- Complete overhaul of Routing guide with "Surgical Swaps", "Deep Linking", and "Self-Healing" patterns.
|
|
38
122
|
- Clarified component naming conventions (Filename for Vite vs. `b-id` for CDN).
|
|
39
123
|
|
|
40
|
-
###
|
|
124
|
+
### Bug Fixes
|
|
41
125
|
|
|
42
126
|
- Fixed "Literal Mustaches" appearing in `href` and text content on initial load.
|
|
43
127
|
- Fixed Deep Linking where hitting Refresh on a sub-route would render an empty shell (addressed via Self-Healing pattern).
|
package/cdn.html
CHANGED
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
}
|
|
57
57
|
</style>
|
|
58
58
|
|
|
59
|
-
<h3>
|
|
60
|
-
<p>
|
|
59
|
+
<h3>[[title]]</h3>
|
|
60
|
+
<p>[[items.filter(t => !t.done).length]] of [[items.length]] items remaining</p>
|
|
61
61
|
<div style="margin-bottom: 20px;">
|
|
62
62
|
<input b-sync="newItem" placeholder="Add task..." class="input">
|
|
63
63
|
<button @click="() => {
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
<div b-for="todo in items">
|
|
72
72
|
<div class="item-row {{todo.done ? 'done' : ''}}">
|
|
73
73
|
<input type="checkbox" b-sync="todo.done">
|
|
74
|
-
<span>
|
|
74
|
+
<span>[[todo.text]]</span>
|
|
75
75
|
</div>
|
|
76
76
|
</div>
|
|
77
77
|
</template>
|
|
@@ -96,8 +96,8 @@
|
|
|
96
96
|
}
|
|
97
97
|
</style>
|
|
98
98
|
|
|
99
|
-
<h3>
|
|
100
|
-
<p>
|
|
99
|
+
<h3>[[name]]</h3>
|
|
100
|
+
<p>[[bio]]</p>
|
|
101
101
|
<slot></slot>
|
|
102
102
|
</template>
|
|
103
103
|
|
|
@@ -119,6 +119,11 @@
|
|
|
119
119
|
}">
|
|
120
120
|
</todo-list>
|
|
121
121
|
</user-card>
|
|
122
|
+
<script>
|
|
123
|
+
// Initialize Lego
|
|
124
|
+
// We need to wait for DOM, but script at end of body is fine
|
|
125
|
+
Lego.init();
|
|
126
|
+
</script>
|
|
122
127
|
</body>
|
|
123
128
|
|
|
124
129
|
</html>
|
|
@@ -10,14 +10,15 @@ export default defineConfig({
|
|
|
10
10
|
|
|
11
11
|
nav: [
|
|
12
12
|
{ text: 'Guide', link: '/guide/' },
|
|
13
|
+
{ text: 'Tutorial', link: '/tutorial/' },
|
|
13
14
|
{ text: 'Contributing', link: '/contributing/' },
|
|
14
15
|
{ text: 'API', link: '/api/' },
|
|
15
16
|
{ text: 'Examples', link: '/examples/' },
|
|
16
17
|
{ text: 'Router', link: '/router/' },
|
|
17
18
|
{
|
|
18
|
-
text: 'v1.
|
|
19
|
+
text: 'v1.3.4',
|
|
19
20
|
items: [
|
|
20
|
-
{ text: 'Changelog', link: 'https://github.com/rayattack/
|
|
21
|
+
{ text: 'Changelog', link: 'https://github.com/rayattack/LegoDOM/releases' }
|
|
21
22
|
|
|
22
23
|
]
|
|
23
24
|
}
|
|
@@ -48,14 +49,26 @@ export default defineConfig({
|
|
|
48
49
|
]
|
|
49
50
|
}
|
|
50
51
|
],
|
|
52
|
+
'/tutorial/': [
|
|
53
|
+
{
|
|
54
|
+
text: 'Tutorial: Your First App',
|
|
55
|
+
items: [
|
|
56
|
+
{ text: 'Overview', link: '/tutorial/' },
|
|
57
|
+
{ text: '1. Project Setup', link: '/tutorial/01-project-setup' },
|
|
58
|
+
{ text: '2. Your First Component', link: '/tutorial/02-your-first-component' },
|
|
59
|
+
{ text: '3. Adding Routes', link: '/tutorial/03-adding-routes' },
|
|
60
|
+
{ text: '4. Multi-Page App', link: '/tutorial/04-multi-page-app' },
|
|
61
|
+
{ text: '5. State & Globals', link: '/tutorial/05-state-and-globals' }
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
],
|
|
51
65
|
'/guide/': [
|
|
52
66
|
{
|
|
53
67
|
text: 'Introduction',
|
|
54
68
|
items: [
|
|
55
|
-
{ text: 'What is
|
|
69
|
+
{ text: 'What is LegoDOM?', link: '/guide/' },
|
|
56
70
|
{ text: 'Getting Started', link: '/guide/getting-started' },
|
|
57
71
|
{ text: 'Quick Start', link: '/guide/quick-start' },
|
|
58
|
-
{ text: 'Contributing', link: '/guide/contributing' }
|
|
59
72
|
]
|
|
60
73
|
},
|
|
61
74
|
{
|
|
@@ -73,7 +86,9 @@ export default defineConfig({
|
|
|
73
86
|
{ text: 'Single File Components', link: '/guide/sfc' },
|
|
74
87
|
{ text: 'Routing', link: '/guide/routing' },
|
|
75
88
|
{ text: 'CDN Usage', link: '/guide/cdn-usage' },
|
|
76
|
-
{ text: 'Lifecycle Hooks', link: '/guide/lifecycle' }
|
|
89
|
+
{ text: 'Lifecycle Hooks', link: '/guide/lifecycle' },
|
|
90
|
+
{ text: 'Large Apps', link: '/guide/directory-structure' },
|
|
91
|
+
{ text: 'FAQ', link: '/guide/faq' }
|
|
77
92
|
]
|
|
78
93
|
}
|
|
79
94
|
],
|
|
@@ -85,6 +100,7 @@ export default defineConfig({
|
|
|
85
100
|
{ text: 'Lego.define()', link: '/api/define' },
|
|
86
101
|
{ text: 'Lego.route()', link: '/api/route' },
|
|
87
102
|
{ text: 'Lego.globals', link: '/api/globals' },
|
|
103
|
+
{ text: 'Lego.config', link: '/api/config' },
|
|
88
104
|
{ text: 'Directives', link: '/api/directives' },
|
|
89
105
|
{ text: 'Lifecycle Hooks', link: '/api/lifecycle' },
|
|
90
106
|
{ text: 'Vite Plugin', link: '/api/vite-plugin' }
|
|
@@ -118,7 +134,7 @@ export default defineConfig({
|
|
|
118
134
|
},
|
|
119
135
|
|
|
120
136
|
socialLinks: [
|
|
121
|
-
{ icon: 'github', link: 'https://github.com/rayattack/
|
|
137
|
+
{ icon: 'github', link: 'https://github.com/rayattack/LegoDOM' }
|
|
122
138
|
],
|
|
123
139
|
|
|
124
140
|
footer: {
|
|
@@ -131,7 +147,7 @@ export default defineConfig({
|
|
|
131
147
|
},
|
|
132
148
|
|
|
133
149
|
editLink: {
|
|
134
|
-
pattern: 'https://github.com/rayattack/
|
|
150
|
+
pattern: 'https://github.com/rayattack/LegoDOM/edit/main/docs/:path',
|
|
135
151
|
text: 'Edit this page on GitHub'
|
|
136
152
|
}
|
|
137
153
|
},
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Global Configuration
|
|
2
|
+
|
|
3
|
+
`Lego.config` allows you to customize framework behavior, including error handling and metrics.
|
|
4
|
+
|
|
5
|
+
## Properties
|
|
6
|
+
|
|
7
|
+
### `syntax`
|
|
8
|
+
|
|
9
|
+
* **Type**: `'mustache' | 'brackets'`
|
|
10
|
+
* **Default**: `'brackets'`
|
|
11
|
+
|
|
12
|
+
Configures the template delimiter style.
|
|
13
|
+
|
|
14
|
+
* `'brackets'`: Uses <code v-pre>[[ variable ]]</code> (Default)
|
|
15
|
+
* `'mustache'`: Uses <code v-pre>{{ variable }}</code> (Legacy/Vue-style)
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
// Switch back to mustache syntax if preferred
|
|
19
|
+
Lego.config.syntax = 'mustache';
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### `loader`
|
|
23
|
+
|
|
24
|
+
* **Type**: `(tagName: string) => string | Promise<string> | null`
|
|
25
|
+
* **Default**: `undefined`
|
|
26
|
+
|
|
27
|
+
Use this hook to implement **Server-Side Component Delivery**.
|
|
28
|
+
|
|
29
|
+
**Option 1: Simple Mode (Return URL)**
|
|
30
|
+
We fetch it for you.
|
|
31
|
+
```javascript
|
|
32
|
+
loader: (tag) => `/components/${tag}.lego`
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Option 2: Power Mode (Return Promise)**
|
|
36
|
+
You control the fetch. Useful for **Authentication** (Cookies, JWT) or Custom Headers.
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
Lego.init(document.body, {
|
|
40
|
+
loader: async (tagName) => {
|
|
41
|
+
// Custom Authenticated Fetch
|
|
42
|
+
const res = await fetch(`/components/${tagName}.lego`, {
|
|
43
|
+
credentials: 'include', // Send Cookies
|
|
44
|
+
headers: { 'Authorization': getToken() }
|
|
45
|
+
});
|
|
46
|
+
return await res.text(); // Return SFC content directly
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
**Mechanism:**
|
|
51
|
+
1. Browser sees unknown `<admin-widget>`.
|
|
52
|
+
2. `Lego` calls `config.loader('admin-widget')`.
|
|
53
|
+
3. If URL returned, it fetches the file.
|
|
54
|
+
4. The server returns raw SFC content (`<template>...`).
|
|
55
|
+
5. `Lego.defineSFC()` parses and upgrades the element instantly.
|
|
56
|
+
|
|
57
|
+
### `onError`
|
|
58
|
+
|
|
59
|
+
* **Type**: `(error: Error, type: string, context: HTMLElement) => void`
|
|
60
|
+
* **Default**: `undefined`
|
|
61
|
+
|
|
62
|
+
Global error handler hook. Called when an error occurs during:
|
|
63
|
+
* `render`: Template rendering (expression evaluation)
|
|
64
|
+
* `event-handler`: `@event` callbacks
|
|
65
|
+
* `define`: Component definition
|
|
66
|
+
* `sync-update`: `b-sync` updates
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
Lego.config.onError = (err, type, context) => {
|
|
70
|
+
console.error(`Error in ${type}:`, err);
|
|
71
|
+
// Send to Sentry/Datadog
|
|
72
|
+
captureException(err, { tags: { type } });
|
|
73
|
+
};
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### `metrics`
|
|
77
|
+
|
|
78
|
+
* **Type**: `Object`
|
|
79
|
+
|
|
80
|
+
Performance monitoring hooks, primarily used by plugins.
|
|
81
|
+
|
|
82
|
+
* `onRenderStart(el)`: Called before a component renders.
|
|
83
|
+
* `onRenderEnd(el)`: Called after a component finishes rendering.
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
// Example monitoring implementation
|
|
87
|
+
Lego.config.metrics = {
|
|
88
|
+
onRenderStart(el) {
|
|
89
|
+
console.time(el.tagName);
|
|
90
|
+
},
|
|
91
|
+
onRenderEnd(el) {
|
|
92
|
+
console.timeEnd(el.tagName);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
```
|
package/docs/api/define.md
CHANGED
|
@@ -21,11 +21,38 @@ import { Lego } from 'lego-dom';
|
|
|
21
21
|
|
|
22
22
|
Lego.define('user-card', `
|
|
23
23
|
<div class="card">
|
|
24
|
-
<h3>
|
|
25
|
-
<p>
|
|
24
|
+
<h3>[[ name ]]</h3>
|
|
25
|
+
<p>[[ role ]]</p>
|
|
26
26
|
</div>
|
|
27
27
|
`, {
|
|
28
28
|
name: 'John Doe',
|
|
29
29
|
role: 'Admin'
|
|
30
30
|
});
|
|
31
31
|
```
|
|
32
|
+
|
|
33
|
+
## Lego.defineSFC()
|
|
34
|
+
|
|
35
|
+
Runtime parser for Single File Components (SFC). Useful for **Server-Side Rendering** or dynamic loading architectures.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
Lego.defineSFC(content: string, filename?: string)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Example
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
const sfc = `
|
|
45
|
+
<template>
|
|
46
|
+
<h1>[[ title ]]</h1>
|
|
47
|
+
</template>
|
|
48
|
+
<script>
|
|
49
|
+
export default { title: 'Hello World' }
|
|
50
|
+
</script>
|
|
51
|
+
<style>
|
|
52
|
+
h1 { color: red; }
|
|
53
|
+
</style>
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
// Registers <my-component> instantly
|
|
57
|
+
Lego.defineSFC(sfc, 'my-component.lego');
|
|
58
|
+
```
|
package/docs/api/directives.md
CHANGED
|
@@ -11,6 +11,14 @@ Conditionally render an element.
|
|
|
11
11
|
<div b-show="!loading">Content loaded!</div>
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
+
## b-html
|
|
15
|
+
|
|
16
|
+
Renders raw HTML. **Security Risk**: Use with caution.
|
|
17
|
+
|
|
18
|
+
```html
|
|
19
|
+
<div b-html="htmlContent"></div>
|
|
20
|
+
```
|
|
21
|
+
|
|
14
22
|
## b-for
|
|
15
23
|
|
|
16
24
|
Render a list of items.
|
|
@@ -18,7 +26,7 @@ Render a list of items.
|
|
|
18
26
|
```html
|
|
19
27
|
<ul>
|
|
20
28
|
<li b-for="user in users">
|
|
21
|
-
|
|
29
|
+
[[ user.name ]]
|
|
22
30
|
</li>
|
|
23
31
|
</ul>
|
|
24
32
|
```
|
|
@@ -29,7 +37,7 @@ Two-way data binding for form inputs.
|
|
|
29
37
|
|
|
30
38
|
```html
|
|
31
39
|
<input b-sync="username" placeholder="Enter username">
|
|
32
|
-
<p>You typed:
|
|
40
|
+
<p>You typed: [[ username ]]</p>
|
|
33
41
|
```
|
|
34
42
|
|
|
35
43
|
## @event
|
package/docs/api/index.md
CHANGED
|
@@ -7,6 +7,7 @@ Welcome to the Lego API documentation.
|
|
|
7
7
|
- [Lego.define()](/api/define) - Defining components
|
|
8
8
|
- [Lego.route()](/api/route) - Client-side routing
|
|
9
9
|
- [Lego.globals](/api/globals) - Global state
|
|
10
|
+
- [Lego.config](/api/config) - Configuration & Error Handling
|
|
10
11
|
- [Lifecycle Hooks](/api/lifecycle) - Component lifecycle methods
|
|
11
12
|
|
|
12
13
|
## Templates & Binding
|
|
@@ -6,8 +6,10 @@ LegoDOM is wrapped in an **IIFE** (Immediately Invoked Function Expression) assi
|
|
|
6
6
|
const Lego = (() => {
|
|
7
7
|
// ... all the logic ...
|
|
8
8
|
return {
|
|
9
|
+
init: () => { ... },
|
|
9
10
|
init: () => { ... },
|
|
10
11
|
define: (tagName, templateHTML, logic = {}) => { ... },
|
|
12
|
+
defineSFC: (content, filename) => { ... }, // Runtime SFC Parser
|
|
11
13
|
// ...
|
|
12
14
|
};
|
|
13
15
|
})();
|
|
@@ -94,6 +94,40 @@ async load(id) {
|
|
|
94
94
|
|
|
95
95
|
- It **injects** a `Lego.define()` call into your JavaScript bundle automatically.
|
|
96
96
|
|
|
97
|
-
- **Result:** You just create a file named `
|
|
98
|
-
|
|
99
|
-
**
|
|
97
|
+
- **Result:** You just create a file named `user-card.lego`, and suddenly `<user-card>` is a valid HTML tag in your app.
|
|
98
|
+
|
|
99
|
+
**Paradigm 3: Runtime Component Definition (New in v2.0)**
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
defineSFC: (content, filename) => {
|
|
103
|
+
// Regex parsing of <template>, <script>, <style>
|
|
104
|
+
const templateMatch = content.match(/<template([\s\S]*?)>([\s\S]*?)<\/template>/);
|
|
105
|
+
// ...
|
|
106
|
+
const logicObj = new Function(`return ${script}`)();
|
|
107
|
+
// ...
|
|
108
|
+
sfcLogic.set(name, logicObj);
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
This is the power behind the **Server Loader**. We can fetch a string from the server and "compile" it in the browser using `new Function()`. It populates `registry` and `sfcLogic` just like the build-time tools, but on the fly.
|
|
113
|
+
|
|
114
|
+
### 4. Component Naming Conventions ("Explicit or Go Home")
|
|
115
|
+
|
|
116
|
+
When you define a component via a file (e.g. `.lego`), the library automatically derives the tag name. To keep the web platform happy, we enforce **Custom Element Best Practices**:
|
|
117
|
+
|
|
118
|
+
1. **Automatic Conversion**: All filenames are converted to `kebab-case`.
|
|
119
|
+
- `UserProfile.lego` -> `<user-profile>`
|
|
120
|
+
- `navBar.lego` -> `<nav-bar>`
|
|
121
|
+
- `data_table.lego` -> `<data-table>`
|
|
122
|
+
|
|
123
|
+
2. **The Hyphen Rule**: A custom element **MUST** contain a hyphen.
|
|
124
|
+
- `Button.lego` -> Error
|
|
125
|
+
- `Adidas.lego` -> Error
|
|
126
|
+
|
|
127
|
+
> [!TIP]
|
|
128
|
+
> **Single Word Component? Namespace It!**
|
|
129
|
+
> Custom Element specs require a hyphen to distinguish from native tags. To ensure forward compatibility, **LegoDOM will throw an error** if you try to define a single-word component.
|
|
130
|
+
> Simply add your app or product prefix to the filename:
|
|
131
|
+
> - `fb-button.lego` -> `<fb-button>`
|
|
132
|
+
> - `shop-card.lego` -> `<shop-card>`
|
|
133
|
+
> - `mobile-button.lego` -> `<mobile-button>`
|
|
@@ -77,13 +77,24 @@ This is the most critical part of the initialization. The library creates a `Mut
|
|
|
77
77
|
|
|
78
78
|
```js
|
|
79
79
|
const observer = new MutationObserver(m => m.forEach(r => {
|
|
80
|
-
r.addedNodes.forEach(n =>
|
|
80
|
+
r.addedNodes.forEach(n => {
|
|
81
|
+
if (n.nodeType === Node.ELEMENT_NODE) {
|
|
82
|
+
snap(n);
|
|
83
|
+
|
|
84
|
+
// Auto-Discovery (v2.0): Check for remote components
|
|
85
|
+
const tagName = n.tagName.toLowerCase();
|
|
86
|
+
if (tagName.includes('-') && !registry[tagName] && config.loader && !activeComponents.has(n)) {
|
|
87
|
+
// ... Call loader ...
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
81
91
|
r.removedNodes.forEach(n => n.nodeType === Node.ELEMENT_NODE && unsnap(n));
|
|
82
92
|
}));
|
|
83
93
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
84
94
|
```
|
|
85
95
|
|
|
86
|
-
-
|
|
96
|
+
- **What it does**: It watches the `document.body` for any changes to the HTML structure.
|
|
97
|
+
- **Auto-Discovery (New in v2.0)**: If `snap(n)` fails because the component isn't in the registry, the observer now checks `config.loader`. If a loader is defined, it triggers a fetch to pull the component definition from the server. This enables the "HTMX+Components" pattern.
|
|
87
98
|
|
|
88
99
|
- **The Config**: It observes `{ childList: true, subtree: true }`. This means it sees if an element is added to the body, or if an element is added deep inside another element.
|
|
89
100
|
|
|
@@ -27,6 +27,9 @@ Whenever a new piece of HTML is injected (via `innerHTML`, `appendChild`, etc.),
|
|
|
27
27
|
- **Filter**: The library checks `n.nodeType === Node.ELEMENT_NODE` to ensure it is an **Element** (like a `<div>` or `<my-comp>`) and not just a fragment of text.
|
|
28
28
|
|
|
29
29
|
- **The Action**: It calls `snap(n)`. This triggers the entire initialization process: attaching the Shadow DOM, creating reactive state, and rendering the template.
|
|
30
|
+
|
|
31
|
+
- **Auto-Discovery (v2.0)**: If `snap(n)` doesn't find a template in the registry, the observer now checks `Lego.config.loader`. If configured, it pauses to fetch the component definition from the server.
|
|
32
|
+
|
|
30
33
|
|
|
31
34
|
|
|
32
35
|
### 3. Processing `removedNodes`
|
|
@@ -19,6 +19,11 @@ const snap = (el) => {
|
|
|
19
19
|
const tpl = templateNode.content.cloneNode(true);
|
|
20
20
|
const shadow = el.attachShadow({ mode: 'open' });
|
|
21
21
|
|
|
22
|
+
const splitStyles = (templateNode.getAttribute('b-styles') || "").split(/\s+/).filter(Boolean);
|
|
23
|
+
if (splitStyles.length) {
|
|
24
|
+
shadow.adoptedStyleSheets = splitStyles.flatMap(k => styleRegistry.get(k) || []);
|
|
25
|
+
}
|
|
26
|
+
|
|
22
27
|
// TIER 1: Logic from Lego.define (SFC)
|
|
23
28
|
const scriptLogic = sfcLogic.get(name) || {};
|
|
24
29
|
|
|
@@ -32,7 +37,10 @@ const snap = (el) => {
|
|
|
32
37
|
el._studs = reactive({
|
|
33
38
|
...scriptLogic,
|
|
34
39
|
...templateLogic,
|
|
35
|
-
...instanceLogic
|
|
40
|
+
...instanceLogic,
|
|
41
|
+
// Inject Global Helpers
|
|
42
|
+
get $route() { return Lego.globals.$route },
|
|
43
|
+
get $go() { return Lego.globals.$go }
|
|
36
44
|
}, el);
|
|
37
45
|
|
|
38
46
|
shadow.appendChild(tpl);
|
|
@@ -83,6 +91,12 @@ const shadow = el.attachShadow({ mode: 'open' });
|
|
|
83
91
|
- **Encapsulation**: By using `attachShadow`, the component’s internal styles and HTML are shielded from the rest of the page.
|
|
84
92
|
|
|
85
93
|
- **Template Injection**: It clones the content of the template and appends it to this new Shadow Root.
|
|
94
|
+
|
|
95
|
+
#### What about `<slot>`?
|
|
96
|
+
Because we use native Shadow DOM, `<slot>` just works.
|
|
97
|
+
When `snap` attaches the shadow root, any children *already* inside the custom element (the "Light DOM") are automatically projected into the `<slot>` tags defined in your template.
|
|
98
|
+
We don't need to write any code for this—the browser does it for us.
|
|
99
|
+
|
|
86
100
|
|
|
87
101
|
|
|
88
102
|
### 3. CSS "self" Transformation
|
|
@@ -25,7 +25,9 @@ Once `snap()` has merged the data from the SFC (Tier 1), the Template (Tier 2),
|
|
|
25
25
|
el._studs = reactive({
|
|
26
26
|
...scriptLogic,
|
|
27
27
|
...templateLogic,
|
|
28
|
-
...instanceLogic
|
|
28
|
+
...instanceLogic,
|
|
29
|
+
get $route() { return Lego.globals.$route },
|
|
30
|
+
get $go() { return Lego.globals.$go }
|
|
29
31
|
}, el);
|
|
30
32
|
//... rest of code
|
|
31
33
|
```
|
|
@@ -52,6 +52,19 @@ const scanForBindings = (container) => {
|
|
|
52
52
|
};
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
## Why Regex? (The "Forbidden" Choice)
|
|
56
|
+
|
|
57
|
+
You will notice we use Regular Expressions to find bindings.
|
|
58
|
+
|
|
59
|
+
**"Regex is bad for HTML!"** they say.
|
|
60
|
+
Usually, yes. But we are not parsing *arbitrary* HTML to build a DOM. We are scanning *specific known tokens* inside trusted templates.
|
|
61
|
+
|
|
62
|
+
**The Trade-off:**
|
|
63
|
+
- **AST Parser**: Reliable, but heavy (10kb+).
|
|
64
|
+
- **Regex Scanner**: Good enough for 99% of bindings, extremely light (<1kb).
|
|
65
|
+
|
|
66
|
+
Since LegoDOM targets **speed** and **size** (<4kb), Regex is the correct architectural choice. We mitigate edge cases by ignoring bindings inside `<script>` and `<style>` blocks.
|
|
67
|
+
|
|
55
68
|
### 1. The `TreeWalker` Efficiency
|
|
56
69
|
|
|
57
70
|
LegoDOM uses a native browser tool called a `TreeWalker`.
|