lego-dom 1.3.4 → 1.5.0
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/CHANGELOG.md +72 -1
- package/main.js +48 -17
- package/main.min.js +2 -2
- package/package.json +1 -1
- package/parse-lego.js +2 -2
- package/vite-plugin.js +0 -14
- package/.github/workflows/deploy-docs.yml +0 -56
- package/.legodom +0 -87
- package/docs/.vitepress/config.js +0 -161
- package/docs/api/config.md +0 -95
- package/docs/api/define.md +0 -58
- package/docs/api/directives.md +0 -50
- package/docs/api/globals.md +0 -29
- package/docs/api/index.md +0 -30
- package/docs/api/lifecycle.md +0 -40
- package/docs/api/route.md +0 -37
- package/docs/api/vite-plugin.md +0 -58
- package/docs/contributing/01-welcome.md +0 -38
- package/docs/contributing/02-registry.md +0 -133
- package/docs/contributing/03-batcher.md +0 -110
- package/docs/contributing/04-reactivity.md +0 -87
- package/docs/contributing/05-caching.md +0 -59
- package/docs/contributing/06-init.md +0 -136
- package/docs/contributing/07-observer.md +0 -72
- package/docs/contributing/08-snap.md +0 -140
- package/docs/contributing/09-diffing.md +0 -69
- package/docs/contributing/10-studs.md +0 -78
- package/docs/contributing/11-scanner.md +0 -117
- package/docs/contributing/12-render.md +0 -138
- package/docs/contributing/13-directives.md +0 -243
- package/docs/contributing/14-events.md +0 -57
- package/docs/contributing/15-router.md +0 -57
- package/docs/contributing/16-state.md +0 -47
- package/docs/contributing/17-legodom.md +0 -48
- package/docs/contributing/index.md +0 -24
- package/docs/examples/form.md +0 -42
- package/docs/examples/index.md +0 -104
- package/docs/examples/routing.md +0 -409
- package/docs/examples/sfc-showcase.md +0 -34
- package/docs/examples/todo-app.md +0 -383
- package/docs/guide/cdn-usage.md +0 -354
- package/docs/guide/components.md +0 -418
- package/docs/guide/directives.md +0 -539
- package/docs/guide/directory-structure.md +0 -248
- package/docs/guide/faq.md +0 -210
- package/docs/guide/getting-started.md +0 -262
- package/docs/guide/index.md +0 -88
- package/docs/guide/lifecycle.md +0 -525
- package/docs/guide/quick-start.md +0 -49
- package/docs/guide/reactivity.md +0 -415
- package/docs/guide/routing.md +0 -334
- package/docs/guide/server-side.md +0 -134
- package/docs/guide/sfc.md +0 -464
- package/docs/guide/templating.md +0 -388
- package/docs/index.md +0 -160
- package/docs/public/logo.svg +0 -17
- package/docs/router/basic-routing.md +0 -103
- package/docs/router/cold-entry.md +0 -91
- package/docs/router/history.md +0 -69
- package/docs/router/index.md +0 -73
- package/docs/router/resolver.md +0 -74
- package/docs/router/surgical-swaps.md +0 -134
- package/docs/tutorial/01-project-setup.md +0 -152
- package/docs/tutorial/02-your-first-component.md +0 -226
- package/docs/tutorial/03-adding-routes.md +0 -279
- package/docs/tutorial/04-multi-page-app.md +0 -329
- package/docs/tutorial/05-state-and-globals.md +0 -285
- package/docs/tutorial/index.md +0 -40
- package/examples/vite-app/README.md +0 -71
- package/examples/vite-app/index.html +0 -42
- package/examples/vite-app/package.json +0 -18
- package/examples/vite-app/src/app.css +0 -3
- package/examples/vite-app/src/app.js +0 -29
- package/examples/vite-app/src/components/app-navbar.lego +0 -34
- package/examples/vite-app/src/components/customers/customer-details.lego +0 -24
- package/examples/vite-app/src/components/customers/customer-orders.lego +0 -21
- package/examples/vite-app/src/components/customers/order-list.lego +0 -55
- package/examples/vite-app/src/components/greeting-card.lego +0 -41
- package/examples/vite-app/src/components/sample-component.lego +0 -75
- package/examples/vite-app/src/components/shells/customers-shell.lego +0 -21
- package/examples/vite-app/src/components/side-menu.lego +0 -46
- package/examples/vite-app/src/components/todo-list.lego +0 -239
- package/examples/vite-app/src/components/widgets/user-card.lego +0 -27
- package/examples/vite-app/vite.config.js +0 -22
- package/tests/error.test.js +0 -74
- package/tests/main.test.js +0 -103
- package/tests/memory.test.js +0 -68
- package/tests/monitoring.test.js +0 -74
- package/tests/naming.test.js +0 -74
- package/tests/parse-lego.test.js +0 -65
- package/tests/security.test.js +0 -67
- package/tests/server.test.js +0 -114
- package/tests/syntax.test.js +0 -67
package/docs/api/config.md
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# Lego.define()
|
|
2
|
-
|
|
3
|
-
Defining components in JavaScript.
|
|
4
|
-
|
|
5
|
-
## Type Signature
|
|
6
|
-
|
|
7
|
-
```ts
|
|
8
|
-
Lego.define(tagName: string, template: string, state?: object)
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Arguments
|
|
12
|
-
|
|
13
|
-
- **tagName**: The name of the custom element (must contain a hyphen).
|
|
14
|
-
- **template**: The HTML string for the component. Can include `<style>`, markup, and bindings.
|
|
15
|
-
- **state**: Initial state object and methods.
|
|
16
|
-
|
|
17
|
-
## Example
|
|
18
|
-
|
|
19
|
-
```js
|
|
20
|
-
import { Lego } from 'lego-dom';
|
|
21
|
-
|
|
22
|
-
Lego.define('user-card', `
|
|
23
|
-
<div class="card">
|
|
24
|
-
<h3>[[ name ]]</h3>
|
|
25
|
-
<p>[[ role ]]</p>
|
|
26
|
-
</div>
|
|
27
|
-
`, {
|
|
28
|
-
name: 'John Doe',
|
|
29
|
-
role: 'Admin'
|
|
30
|
-
});
|
|
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
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# Directives
|
|
2
|
-
|
|
3
|
-
Special attributes that control DOM behavior.
|
|
4
|
-
|
|
5
|
-
## b-show
|
|
6
|
-
|
|
7
|
-
Conditionally render an element.
|
|
8
|
-
|
|
9
|
-
```html
|
|
10
|
-
<div b-show="loading">Loading...</div>
|
|
11
|
-
<div b-show="!loading">Content loaded!</div>
|
|
12
|
-
```
|
|
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
|
-
|
|
22
|
-
## b-for
|
|
23
|
-
|
|
24
|
-
Render a list of items.
|
|
25
|
-
|
|
26
|
-
```html
|
|
27
|
-
<ul>
|
|
28
|
-
<li b-for="user in users">
|
|
29
|
-
[[ user.name ]]
|
|
30
|
-
</li>
|
|
31
|
-
</ul>
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## b-sync
|
|
35
|
-
|
|
36
|
-
Two-way data binding for form inputs.
|
|
37
|
-
|
|
38
|
-
```html
|
|
39
|
-
<input b-sync="username" placeholder="Enter username">
|
|
40
|
-
<p>You typed: [[ username ]]</p>
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## @event
|
|
44
|
-
|
|
45
|
-
Event listeners.
|
|
46
|
-
|
|
47
|
-
```html
|
|
48
|
-
<button @click="save()">Save</button>
|
|
49
|
-
<input @input="validate($event)">
|
|
50
|
-
```
|
package/docs/api/globals.md
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# Global Helpers
|
|
2
|
-
|
|
3
|
-
Lego exposes a global `Lego` object when loaded via CDN, or as exports when using modules.
|
|
4
|
-
|
|
5
|
-
## Lego.page
|
|
6
|
-
|
|
7
|
-
Access to the router's current state.
|
|
8
|
-
|
|
9
|
-
```js
|
|
10
|
-
console.log(Lego.page.params); // URL parameters
|
|
11
|
-
console.log(Lego.page.query); // Query string parameters
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
## Lego.create()
|
|
15
|
-
|
|
16
|
-
Manually create a reactive object (stud) detached from a component.
|
|
17
|
-
|
|
18
|
-
```js
|
|
19
|
-
const store = Lego.create({ count: 0 });
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## Event Bus
|
|
23
|
-
|
|
24
|
-
Simple global event bus.
|
|
25
|
-
|
|
26
|
-
```js
|
|
27
|
-
Lego.on('user-login', (user) => { ... });
|
|
28
|
-
Lego.emit('user-login', { name: 'Alice' });
|
|
29
|
-
```
|
package/docs/api/index.md
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# API Reference
|
|
2
|
-
|
|
3
|
-
Welcome to the Lego API documentation.
|
|
4
|
-
|
|
5
|
-
## Core
|
|
6
|
-
|
|
7
|
-
- [Lego.define()](/api/define) - Defining components
|
|
8
|
-
- [Lego.route()](/api/route) - Client-side routing
|
|
9
|
-
- [Lego.globals](/api/globals) - Global state
|
|
10
|
-
- [Lego.config](/api/config) - Configuration & Error Handling
|
|
11
|
-
- [Lifecycle Hooks](/api/lifecycle) - Component lifecycle methods
|
|
12
|
-
|
|
13
|
-
## Templates & Binding
|
|
14
|
-
|
|
15
|
-
- [Directives](/api/directives) - `b-show`, `b-for`, `b-sync`
|
|
16
|
-
|
|
17
|
-
## Browser Support
|
|
18
|
-
|
|
19
|
-
Lego requires:
|
|
20
|
-
- Web Components (Custom Elements v1)
|
|
21
|
-
- Shadow DOM v1
|
|
22
|
-
- ES6 Proxy
|
|
23
|
-
- Template Literals
|
|
24
|
-
- MutationObserver
|
|
25
|
-
|
|
26
|
-
Supported browsers:
|
|
27
|
-
- Chrome 63+
|
|
28
|
-
- Firefox 63+
|
|
29
|
-
- Safari 11.1+
|
|
30
|
-
- Edge 79+
|
package/docs/api/lifecycle.md
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# Lifecycle Hooks
|
|
2
|
-
|
|
3
|
-
Methods that are automatically called during a component's lifecycle.
|
|
4
|
-
|
|
5
|
-
## init()
|
|
6
|
-
|
|
7
|
-
Called when the component is initialized and state is reactive, but before rendering.
|
|
8
|
-
|
|
9
|
-
```js
|
|
10
|
-
{
|
|
11
|
-
init() {
|
|
12
|
-
this.fetchData();
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## render()
|
|
18
|
-
|
|
19
|
-
Called after the DOM has been updated.
|
|
20
|
-
|
|
21
|
-
```js
|
|
22
|
-
{
|
|
23
|
-
render() {
|
|
24
|
-
console.log('Component rendered');
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## destroy()
|
|
30
|
-
|
|
31
|
-
Called when the component is removed from the DOM.
|
|
32
|
-
|
|
33
|
-
```js
|
|
34
|
-
{
|
|
35
|
-
destroy() {
|
|
36
|
-
// Cleanup timers or listeners
|
|
37
|
-
clearInterval(this.timer);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
```
|
package/docs/api/route.md
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# Lego.route()
|
|
2
|
-
|
|
3
|
-
Client-side routing.
|
|
4
|
-
|
|
5
|
-
## Type Signature
|
|
6
|
-
|
|
7
|
-
```ts
|
|
8
|
-
Lego.route(path: string, componentOrHtml: string | HTMLElement)
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Arguments
|
|
12
|
-
|
|
13
|
-
- **path**: The URL path pattern (e.g., `/users/:id`).
|
|
14
|
-
- **componentOrHtml**: The tag name of the component to render, or raw HTML.
|
|
15
|
-
|
|
16
|
-
## Example
|
|
17
|
-
|
|
18
|
-
```js
|
|
19
|
-
// Route to a component
|
|
20
|
-
Lego.route('/', 'home-page');
|
|
21
|
-
Lego.route('/about', 'about-page');
|
|
22
|
-
|
|
23
|
-
// Route with params
|
|
24
|
-
Lego.route('/user/:id', 'user-profile');
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Router Outlet
|
|
28
|
-
|
|
29
|
-
You must have a `<router-outlet>` in your DOM where the routed content will appear.
|
|
30
|
-
|
|
31
|
-
```html
|
|
32
|
-
<nav>
|
|
33
|
-
<a href="/">Home</a>
|
|
34
|
-
<a href="/about">About</a>
|
|
35
|
-
</nav>
|
|
36
|
-
<router-outlet></router-outlet>
|
|
37
|
-
```
|
package/docs/api/vite-plugin.md
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# Vite Plugin API
|
|
2
|
-
|
|
3
|
-
Lego includes a Vite plugin for processing `.lego` Single File Components.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install vite lego-dom
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Usage
|
|
12
|
-
|
|
13
|
-
```js
|
|
14
|
-
// vite.config.js
|
|
15
|
-
import { defineConfig } from 'vite';
|
|
16
|
-
import legoPlugin from 'lego-dom/vite-plugin';
|
|
17
|
-
|
|
18
|
-
export default defineConfig({
|
|
19
|
-
plugins: [
|
|
20
|
-
legoPlugin({
|
|
21
|
-
// Options
|
|
22
|
-
})
|
|
23
|
-
]
|
|
24
|
-
});
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Options
|
|
28
|
-
|
|
29
|
-
### `componentsDir`
|
|
30
|
-
|
|
31
|
-
- **Type**: `string`
|
|
32
|
-
- **Default**: `'src/components'`
|
|
33
|
-
|
|
34
|
-
Directory to search for `.lego` files.
|
|
35
|
-
|
|
36
|
-
### `include`
|
|
37
|
-
|
|
38
|
-
- **Type**: `string | string[]`
|
|
39
|
-
- **Default**: `'**/*.lego'`
|
|
40
|
-
|
|
41
|
-
Glob pattern(s) to match files.
|
|
42
|
-
|
|
43
|
-
### `exclude`
|
|
44
|
-
|
|
45
|
-
- **Type**: `string | string[]`
|
|
46
|
-
- **Default**: `null`
|
|
47
|
-
|
|
48
|
-
Glob pattern(s) to exclude files.
|
|
49
|
-
|
|
50
|
-
## Virtual Module
|
|
51
|
-
|
|
52
|
-
The plugin exposes a virtual module to register all components:
|
|
53
|
-
|
|
54
|
-
```js
|
|
55
|
-
import registerComponents from 'virtual:lego-components';
|
|
56
|
-
|
|
57
|
-
registerComponents();
|
|
58
|
-
```
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# Topic 1: The Module Pattern & Private Scope
|
|
2
|
-
|
|
3
|
-
LegoDOM is wrapped in an **IIFE** (Immediately Invoked Function Expression) assigned to `const Lego`. This creates a closure, meaning any variable declared at the top (like `registry` or `proxyCache`) is "private"—it cannot be accessed or tampered with from the browser console unless explicitly exposed.
|
|
4
|
-
|
|
5
|
-
```js
|
|
6
|
-
const Lego = (() => {
|
|
7
|
-
// ... all the logic ...
|
|
8
|
-
return {
|
|
9
|
-
init: () => { ... },
|
|
10
|
-
init: () => { ... },
|
|
11
|
-
define: (tagName, templateHTML, logic = {}) => { ... },
|
|
12
|
-
defineSFC: (content, filename) => { ... }, // Runtime SFC Parser
|
|
13
|
-
// ...
|
|
14
|
-
};
|
|
15
|
-
})();
|
|
16
|
-
|
|
17
|
-
if (typeof window !== 'undefined') {
|
|
18
|
-
document.addEventListener('DOMContentLoaded', Lego.init);
|
|
19
|
-
window.Lego = Lego;
|
|
20
|
-
}
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
### Why Not Modular ES6?
|
|
24
|
-
|
|
25
|
-
I started with an **IIFE** and underestimated how big it could grow. It might be a better idea to use ES6 modules, but I'm too lazy to refactor it at the moment.
|
|
26
|
-
|
|
27
|
-
### The use of `WeakMap`
|
|
28
|
-
|
|
29
|
-
You’ll notice the use of `WeakMap` for `proxyCache`, `privateData`, and `forPools`.
|
|
30
|
-
|
|
31
|
-
- **Why not a regular Map?** A `WeakMap` allows the keys (which are DOM elements in this code) to be **garbage collected** if the element is removed from the DOM.
|
|
32
|
-
|
|
33
|
-
- **Memory Leak Prevention:** If we used a regular `Map`, the library would hold a reference to every component ever created, even if you deleted them, eventually crashing the browser tab.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
### Internal Registry
|
|
37
|
-
|
|
38
|
-
`const registry = {}` acts as the library's "brain." It stores the `<template>` elements that define what a component looks like. When you write `<my-button>`, Lego looks into this object to find the blueprint.
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
# The Good Ole Registry (Lego Basket)
|
|
2
|
-
|
|
3
|
-
Let's not keep our toys everywhere, let's be good citizens and put them in a basket.
|
|
4
|
-
|
|
5
|
-
PSS!!! Come here lemme tell you a secret - are you a frontend developer? "The DOM is not your enemy"
|
|
6
|
-
we talk about Light DOM and Shadow DOM in a minute ;-).
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
## Topic 2: The Registry & Internal Storage
|
|
10
|
-
|
|
11
|
-
LegoDOM uses three specialized collections to store the "DNA" of your application. This separation allows the framework to distinguish between what a component **looks like** versus how it **behaves**.
|
|
12
|
-
|
|
13
|
-
```js
|
|
14
|
-
const registry = {};
|
|
15
|
-
const sfcLogic = new Map();
|
|
16
|
-
const sharedStates = new Map();
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
### 1. The HTML Blueprint Collection (`registry`)
|
|
20
|
-
|
|
21
|
-
`const registry = {}` is a plain object that stores references to `<template>` elements.
|
|
22
|
-
|
|
23
|
-
- When the library initializes, it scans the DOM for any `<template b-id="my-component">` and saves it here.
|
|
24
|
-
|
|
25
|
-
- The `b-id` becomes the key, and the DOM node itself is the value.
|
|
26
|
-
|
|
27
|
-
- **Purpose:** This is the "Light DOM" source used to populate the "Shadow DOM" later during the "snapping" process.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
### 2. The SFC Logic Collection (`sfcLogic`)
|
|
31
|
-
|
|
32
|
-
`const sfcLogic = new Map();` stores the JavaScript objects passed into `Lego.define()`.
|
|
33
|
-
|
|
34
|
-
- While `registry` holds the HTML/CSS, `sfcLogic` holds the functions like `mounted()`, `updated()`, or custom methods.
|
|
35
|
-
|
|
36
|
-
- **Why a Map?** Unlike a plain object, a `Map` is more performant for frequent lookups by string keys (tag names) - I think!!! or maybe I read wrong (call me out).
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
### 3. The Singleton States Collection (`sharedStates`)
|
|
40
|
-
|
|
41
|
-
`const sharedStates = new Map();` is one of the most powerful "hidden" features of LegoDOM.
|
|
42
|
-
|
|
43
|
-
- Every time you define a component, Lego creates a **reactive version** of its logic and stores it here.
|
|
44
|
-
|
|
45
|
-
- This allows other components to access a specific component's state globally via the `$registry('tag-name')` helper. **NOTE** the component's (template/blueprint) state, not the instance state.
|
|
46
|
-
|
|
47
|
-
- **Example:** If you have 3 `user-profile` components, any other component on the page can peek at their (shared) state data by asking the `sharedStates` map.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
#### Dissecting Registration
|
|
51
|
-
|
|
52
|
-
The `registry` doesn't just wait for you to type; it's fed by three distinct streams across 2 paradigms.
|
|
53
|
-
|
|
54
|
-
**Paradigm 1: Explicitly**
|
|
55
|
-
|
|
56
|
-
```js
|
|
57
|
-
const sfcLogic = new Map(); // Specifically for SFC script logic
|
|
58
|
-
// ...
|
|
59
|
-
define: (tagName, templateHTML, logic = {}) => {
|
|
60
|
-
const t = document.createElement('template');
|
|
61
|
-
// ... stores template in registry
|
|
62
|
-
sfcLogic.set(tagName, logic); // Stores the JS part separately
|
|
63
|
-
}
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
**Paradigm 2: Implicitly**
|
|
67
|
-
|
|
68
|
-
```js
|
|
69
|
-
// vite-plugin.js
|
|
70
|
-
async buildStart() {
|
|
71
|
-
const root = config?.root || process.cwd();
|
|
72
|
-
legoFiles = await fg(include, { cwd: searchPath, absolute: true }); // Scans for .lego files
|
|
73
|
-
}
|
|
74
|
-
// ...
|
|
75
|
-
async load(id) {
|
|
76
|
-
if (id.endsWith('.lego')) {
|
|
77
|
-
const defineCall = generateDefineCall(parsed); // Converts .lego file to Lego.define()
|
|
78
|
-
return `import { Lego } from 'lego-dom/main.js';\n${defineCall}`;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
**The Three Ways to Register:**
|
|
85
|
-
1. **The HTML Manual Method:** You put `<template b-id="my-comp">` directly in your `*.html` files. During `Lego.init()`, the library scrapes these and populates the registry. This is great for "no-build" prototypes.
|
|
86
|
-
|
|
87
|
-
2. **The `Lego.define()` JS Method:** You call `Lego.define('my-comp', '<h1>Hi</h1>', { ... logic })` in a standard JavaScript file.
|
|
88
|
-
|
|
89
|
-
3. **The SFC Automatic Method (The "Vite Way"):** * The **Vite Plugin** (as seen in `vite-plugin.js`) acts as a build-time robot.
|
|
90
|
-
|
|
91
|
-
- It uses `fast-glob` (`fg`) to scan your entire `src/components` directory for any file ending in `.lego`.
|
|
92
|
-
|
|
93
|
-
- It parses the `<template>` and `<script>` inside that `.lego` file.
|
|
94
|
-
|
|
95
|
-
- It **injects** a `Lego.define()` call into your JavaScript bundle automatically.
|
|
96
|
-
|
|
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>`
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
# Batching, Scheduling, and doing things the right way
|
|
2
|
-
|
|
3
|
-
Say you are building the next big thing, if you update checkboxes, input fields a 100 times,
|
|
4
|
-
you don't want the DOM to re-render 100 times. That would be a performance nightmare.
|
|
5
|
-
LegoDOM uses Batching & Scheduling to do things the right way.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
## Topic 3: The Global Batcher & Scheduler
|
|
9
|
-
|
|
10
|
-
When you change data in a reactive application, you often change multiple things at once (e.g., updating a user's name, age, and profile picture). Without a **Batcher**, the browser would try to re-render the HTML every single time one of those properties changed. This would cause "jank" (stuttering) and poor performance.
|
|
11
|
-
|
|
12
|
-
### The `createBatcher` Factory
|
|
13
|
-
|
|
14
|
-
The library defines `createBatcher` as a closure that manages the update cycle. It provides three critical layers of protection:
|
|
15
|
-
|
|
16
|
-
```js
|
|
17
|
-
const createBatcher = () => {
|
|
18
|
-
let queued = false;
|
|
19
|
-
const componentsToUpdate = new Set();
|
|
20
|
-
let isProcessing = false;
|
|
21
|
-
|
|
22
|
-
return {
|
|
23
|
-
add: (el) => {
|
|
24
|
-
if (!el || isProcessing) return;
|
|
25
|
-
componentsToUpdate.add(el);
|
|
26
|
-
if (queued) return;
|
|
27
|
-
queued = true;
|
|
28
|
-
|
|
29
|
-
requestAnimationFrame(() => {
|
|
30
|
-
isProcessing = true;
|
|
31
|
-
const batch = Array.from(componentsToUpdate);
|
|
32
|
-
componentsToUpdate.clear();
|
|
33
|
-
queued = false;
|
|
34
|
-
|
|
35
|
-
batch.forEach(el => render(el));
|
|
36
|
-
|
|
37
|
-
setTimeout(() => {
|
|
38
|
-
batch.forEach(el => {
|
|
39
|
-
const state = el._studs;
|
|
40
|
-
if (state && typeof state.updated === 'function') {
|
|
41
|
-
try {
|
|
42
|
-
state.updated.call(state);
|
|
43
|
-
} catch (e) {
|
|
44
|
-
console.error(`[Lego] Error in updated hook:`, e);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
isProcessing = false;
|
|
49
|
-
}, 0);
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const globalBatcher = createBatcher();
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
1. **Deduplication with `Set`**:
|
|
59
|
-
|
|
60
|
-
- The batcher maintains a `componentsToUpdate = new Set()`.
|
|
61
|
-
|
|
62
|
-
- Because a `Set` only stores unique values, if you trigger an update on the same component 50 times in a single loop, it is only added to the "todo" list **once**.
|
|
63
|
-
|
|
64
|
-
2. **The `queued` Gatekeeper**:
|
|
65
|
-
|
|
66
|
-
- A boolean flag `queued` prevents multiple update cycles from being scheduled simultaneously.
|
|
67
|
-
|
|
68
|
-
- Once the first change hits the batcher, it "locks the gate," schedules the work, and ignores further requests to start a new cycle until the current one begins.
|
|
69
|
-
|
|
70
|
-
3. **The `isProcessing` Lock**:
|
|
71
|
-
|
|
72
|
-
- This flag ensures that if a component’s state changes _while_ the render is actually happening, it doesn't cause a collision or infinite loop.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
### Timing: `requestAnimationFrame` (rAF)
|
|
76
|
-
|
|
77
|
-
Instead of updating immediately, the library uses `requestAnimationFrame`.
|
|
78
|
-
|
|
79
|
-
- **What it does**: It tells the browser, "Wait until you are just about to draw the next frame on the screen, then run this code".
|
|
80
|
-
|
|
81
|
-
- **Efficiency**: It bundles every single change from every component into a single "tick."
|
|
82
|
-
|
|
83
|
-
- **The Benefit**: This syncs your JavaScript logic with the monitor's refresh rate (usually 60 times per second), making animations and updates look buttery smooth.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
### The Execution Phase
|
|
87
|
-
|
|
88
|
-
When the "frame" triggers, the batcher:
|
|
89
|
-
|
|
90
|
-
1. Takes a snapshot of the `Set`.
|
|
91
|
-
|
|
92
|
-
2. Clears the `Set` for the next round.
|
|
93
|
-
|
|
94
|
-
3. Runs `render(el)` for every component in that batch.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
### The `updated` Lifecycle Hook
|
|
98
|
-
|
|
99
|
-
After the render is complete, the batcher uses a `setTimeout(() => ..., 0)`.
|
|
100
|
-
|
|
101
|
-
- **The Trick**: Even with a delay of `0`, this "macro-task" ensures the code inside runs **after** the browser has finished its rendering work.
|
|
102
|
-
|
|
103
|
-
- **The Hook**: It looks for `our` lifecycle hook function a.k.a or notoriously known as `updated`
|
|
104
|
-
in each component's state (`_studs`) and executes it. This is the perfect place for us to run code that needs to measure the new size of an element or scroll a list to the bottom.
|
|
105
|
-
|
|
106
|
-
**Example**
|
|
107
|
-
If you have a `chat-box` component and you update the messages, you might use the `updated()` hook to scroll to the bottom. Because of this batcher, you are guaranteed that the DOM has finished changing before your scroll logic runs.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
> **Visualizing the flow:** State Change -> `batcher.add(el)` -> `Set` collects `el` -> `rAF` triggers -> `render(el)` runs -> `updated()` hook fires.
|