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.
Files changed (93) hide show
  1. package/CHANGELOG.md +72 -1
  2. package/main.js +48 -17
  3. package/main.min.js +2 -2
  4. package/package.json +1 -1
  5. package/parse-lego.js +2 -2
  6. package/vite-plugin.js +0 -14
  7. package/.github/workflows/deploy-docs.yml +0 -56
  8. package/.legodom +0 -87
  9. package/docs/.vitepress/config.js +0 -161
  10. package/docs/api/config.md +0 -95
  11. package/docs/api/define.md +0 -58
  12. package/docs/api/directives.md +0 -50
  13. package/docs/api/globals.md +0 -29
  14. package/docs/api/index.md +0 -30
  15. package/docs/api/lifecycle.md +0 -40
  16. package/docs/api/route.md +0 -37
  17. package/docs/api/vite-plugin.md +0 -58
  18. package/docs/contributing/01-welcome.md +0 -38
  19. package/docs/contributing/02-registry.md +0 -133
  20. package/docs/contributing/03-batcher.md +0 -110
  21. package/docs/contributing/04-reactivity.md +0 -87
  22. package/docs/contributing/05-caching.md +0 -59
  23. package/docs/contributing/06-init.md +0 -136
  24. package/docs/contributing/07-observer.md +0 -72
  25. package/docs/contributing/08-snap.md +0 -140
  26. package/docs/contributing/09-diffing.md +0 -69
  27. package/docs/contributing/10-studs.md +0 -78
  28. package/docs/contributing/11-scanner.md +0 -117
  29. package/docs/contributing/12-render.md +0 -138
  30. package/docs/contributing/13-directives.md +0 -243
  31. package/docs/contributing/14-events.md +0 -57
  32. package/docs/contributing/15-router.md +0 -57
  33. package/docs/contributing/16-state.md +0 -47
  34. package/docs/contributing/17-legodom.md +0 -48
  35. package/docs/contributing/index.md +0 -24
  36. package/docs/examples/form.md +0 -42
  37. package/docs/examples/index.md +0 -104
  38. package/docs/examples/routing.md +0 -409
  39. package/docs/examples/sfc-showcase.md +0 -34
  40. package/docs/examples/todo-app.md +0 -383
  41. package/docs/guide/cdn-usage.md +0 -354
  42. package/docs/guide/components.md +0 -418
  43. package/docs/guide/directives.md +0 -539
  44. package/docs/guide/directory-structure.md +0 -248
  45. package/docs/guide/faq.md +0 -210
  46. package/docs/guide/getting-started.md +0 -262
  47. package/docs/guide/index.md +0 -88
  48. package/docs/guide/lifecycle.md +0 -525
  49. package/docs/guide/quick-start.md +0 -49
  50. package/docs/guide/reactivity.md +0 -415
  51. package/docs/guide/routing.md +0 -334
  52. package/docs/guide/server-side.md +0 -134
  53. package/docs/guide/sfc.md +0 -464
  54. package/docs/guide/templating.md +0 -388
  55. package/docs/index.md +0 -160
  56. package/docs/public/logo.svg +0 -17
  57. package/docs/router/basic-routing.md +0 -103
  58. package/docs/router/cold-entry.md +0 -91
  59. package/docs/router/history.md +0 -69
  60. package/docs/router/index.md +0 -73
  61. package/docs/router/resolver.md +0 -74
  62. package/docs/router/surgical-swaps.md +0 -134
  63. package/docs/tutorial/01-project-setup.md +0 -152
  64. package/docs/tutorial/02-your-first-component.md +0 -226
  65. package/docs/tutorial/03-adding-routes.md +0 -279
  66. package/docs/tutorial/04-multi-page-app.md +0 -329
  67. package/docs/tutorial/05-state-and-globals.md +0 -285
  68. package/docs/tutorial/index.md +0 -40
  69. package/examples/vite-app/README.md +0 -71
  70. package/examples/vite-app/index.html +0 -42
  71. package/examples/vite-app/package.json +0 -18
  72. package/examples/vite-app/src/app.css +0 -3
  73. package/examples/vite-app/src/app.js +0 -29
  74. package/examples/vite-app/src/components/app-navbar.lego +0 -34
  75. package/examples/vite-app/src/components/customers/customer-details.lego +0 -24
  76. package/examples/vite-app/src/components/customers/customer-orders.lego +0 -21
  77. package/examples/vite-app/src/components/customers/order-list.lego +0 -55
  78. package/examples/vite-app/src/components/greeting-card.lego +0 -41
  79. package/examples/vite-app/src/components/sample-component.lego +0 -75
  80. package/examples/vite-app/src/components/shells/customers-shell.lego +0 -21
  81. package/examples/vite-app/src/components/side-menu.lego +0 -46
  82. package/examples/vite-app/src/components/todo-list.lego +0 -239
  83. package/examples/vite-app/src/components/widgets/user-card.lego +0 -27
  84. package/examples/vite-app/vite.config.js +0 -22
  85. package/tests/error.test.js +0 -74
  86. package/tests/main.test.js +0 -103
  87. package/tests/memory.test.js +0 -68
  88. package/tests/monitoring.test.js +0 -74
  89. package/tests/naming.test.js +0 -74
  90. package/tests/parse-lego.test.js +0 -65
  91. package/tests/security.test.js +0 -67
  92. package/tests/server.test.js +0 -114
  93. package/tests/syntax.test.js +0 -67
@@ -1,87 +0,0 @@
1
- # Reacting to stimuli is how we react to stimuli
2
-
3
- If living things react to stimuli, then so should our components.
4
-
5
-
6
- ## Topic 4: Reactivity (The Proxy)
7
-
8
- LegoDOM uses the JavaScript **`Proxy`** object to create its reactivity. Think of a Proxy as a "security guard" that sits in front of your data object. Every time you try to read or change a property, the guard intercepts the request.
9
-
10
- ### The `reactive(obj, el, batcher)` Function
11
-
12
- When a component is "snapped" (created), its data is passed through this function.
13
-
14
- ```js
15
- //... rest of the code
16
- const reactive = (obj, el, batcher = globalBatcher) => {
17
- if (obj === null || typeof obj !== 'object' || obj instanceof Node) return obj;
18
- if (proxyCache.has(obj)) return proxyCache.get(obj);
19
-
20
- const handler = {
21
- get: (t, k) => {
22
- const val = Reflect.get(t, k);
23
- if (val !== null && typeof val === 'object' && !(val instanceof Node)) {
24
- return reactive(val, el, batcher);
25
- }
26
- return val;
27
- },
28
- set: (t, k, v) => {
29
- const old = t[k];
30
- const r = Reflect.set(t, k, v);
31
- if (old !== v) batcher.add(el);
32
- return r;
33
- },
34
- deleteProperty: (t, k) => {
35
- const r = Reflect.deleteProperty(t, k);
36
- batcher.add(el);
37
- return r;
38
- }
39
- };
40
-
41
- const p = new Proxy(obj, handler);
42
- proxyCache.set(obj, p);
43
- return p;
44
- };
45
-
46
- //... rest of the code
47
- ```
48
-
49
- 1. **The Traps (`get` and `set`)**:
50
-
51
- - **The `get` trap**: When you access a property (e.g., `state.count`), the Proxy checks if that property is _also_ an object. If it is, it recursively wraps that object in a Proxy too. This ensures that "deep" data like `user.profile.name` is also reactive.
52
-
53
- - **The `set` trap**: This is the trigger. When you do `state.count = 5`, the Proxy compares the `old` value with the `new` value. If they are different, it immediately calls `batcher.add(el)`.
54
-
55
- - **The `deleteProperty` trap**: Even if you delete a key (e.g., `delete state.tempData`), the Proxy intercepts this and tells the batcher to re-render the UI.
56
-
57
- 2. **Handling Objects vs. Nodes**:
58
-
59
- - The code explicitly checks if a value is an `instanceof Node`. If you try to store a raw HTML element in your state, the library **will not** wrap it in a Proxy. This prevents the library from accidentally trying to "observe" the entire DOM tree, which would crash the browser.
60
-
61
-
62
- ### Concrete Example
63
-
64
- Imagine you have a component defined like this:
65
-
66
- ```js
67
- Lego.define('counter-app', '<h1>{{count}}</h1>', {
68
- count: 0,
69
- increment() { this.count++; }
70
- });
71
-
72
- ```
73
-
74
- - **Step A**: Lego takes that object `{ count: 0, ... }` and wraps it in a Proxy.
75
-
76
- - **Step B**: You call `increment()`.
77
-
78
- - **Step C**: The line `this.count++` triggers the Proxy's `set` trap.
79
-
80
- - **Step D**: The `set` trap notices `0` is now `1` and calls `globalBatcher.add(thisElement)`.
81
-
82
- - **Step E**: The Batcher (from Topic 3) schedules a render for the next animation frame.
83
-
84
-
85
- ### Why this is "Surgical"
86
-
87
- Because the `reactive` function is passed the specific element (`el`) it belongs to, it knows exactly which component in the DOM needs to re-render. It doesn't have to guess or refresh the whole page; it targets the specific "Lego block" that owns that data.
@@ -1,59 +0,0 @@
1
- # There are 2 hard things in computer science
2
-
3
- - cache invalidation
4
- - naming things
5
- - off-by-one errors
6
-
7
-
8
- ## Proxy Caching (`proxyCache`)
9
-
10
- In [Topic 4](/contributing/04-reactivity), we saw how `reactive()` wraps objects in a Proxy.
11
- However, a common issue in reactive programming is trying to wrap the same object multiple
12
- times, or dealing with circular references
13
- (e.g. Object A points to Object B, and Object B points back to Object A).
14
-
15
- ### The Problem: Infinite Recursion
16
-
17
- Without a cache, every time you access a nested object, the `get` trap would create a **new** Proxy wrapper.
18
-
19
- ```js
20
- // Without a cache:
21
- const p1 = state.user;
22
- const p2 = state.user;
23
- console.log(p1 === p2); // false! They are different "guards" for the same data.
24
-
25
- ```
26
-
27
- This wastes memory and breaks object identity. Even worse, if an object points to itself, the code would keep creating Proxies until the browser crashed with a "Maximum call stack size exceeded" error.
28
-
29
- ### The Solution: `proxyCache`
30
-
31
- The library uses `const proxyCache = new WeakMap()` to keep track of every object it has already turned into a Proxy.
32
-
33
- 1. **Checking the Map**: At the very start of the `reactive()` function, the code checks: `if (proxyCache.has(obj)) return proxyCache.get(obj);`.
34
-
35
- 2. **Storing the Result**: If it's a new object, the code creates the Proxy and then immediately saves it: `proxyCache.set(obj, p);`.
36
-
37
- 3. **The Result**: If you access `state.user` 100 times, you get the exact same Proxy instance every time. It ensures that `p1 === p2` is always `true`.
38
-
39
-
40
- ### Why a `WeakMap`?
41
-
42
- This is a critical "expert-level" choice.
43
-
44
- - A regular `Map` holds a "strong reference" to its keys. If you deleted a piece of data from your state, but that data was still a key in a regular `Map`, the browser could never delete it from memory.
45
-
46
- - Because `proxyCache` is a `WeakMap`, as soon as your component is destroyed and the original object is no longer needed, the browser’s Garbage Collector can automatically wipe it from the cache.
47
-
48
-
49
- ### Summary of Logic:
50
-
51
- - **Step 1:** Request to make `obj` reactive.
52
-
53
- - **Step 2:** Check `proxyCache`. Found it? Return the existing Proxy.
54
-
55
- - **Step 3:** Not found? Create a new Proxy.
56
-
57
- - **Step 4:** Store `obj -> Proxy` in `proxyCache`.
58
-
59
- - **Step 5:** Return the new Proxy.
@@ -1,136 +0,0 @@
1
- # In the beginning - `Lego.init()`
2
-
3
- The `init()` function is the heart of LegoDOM. It orchestrates the entire initialization process, setting up the observer, connecting the global state, and processing existing components.
4
-
5
- ## The LegoDOM Entry Point
6
-
7
- When your HTML finishes loading
8
-
9
- ```js
10
- if (typeof window !== 'undefined') {
11
- document.addEventListener('DOMContentLoaded', Lego.init);
12
- window.Lego = Lego;
13
- }
14
- ```
15
-
16
-
17
- LegoDOM executes `Lego.init()`. This is the orchestration phase where the library sets up its "eyes" (the observer) and connects the global state to the page.
18
-
19
- ```js
20
- return {
21
- init: () => {
22
- document.querySelectorAll('template[b-id]').forEach(t => {
23
- registry[t.getAttribute('b-id')] = t;
24
- });
25
- const observer = new MutationObserver(m => m.forEach(r => {
26
- r.addedNodes.forEach(n => n.nodeType === Node.ELEMENT_NODE && snap(n));
27
- r.removedNodes.forEach(n => n.nodeType === Node.ELEMENT_NODE && unsnap(n));
28
- }));
29
- observer.observe(document.body, { childList: true, subtree: true });
30
-
31
- snap(document.body);
32
- bind(document.body, { _studs: Lego.globals, _data: { bound: false } });
33
-
34
- if (routes.length > 0) {
35
- // Smart History: Restore surgical targets on Back button
36
- window.addEventListener('popstate', (event) => {
37
- const targets = event.state?.legoTargets || null;
38
- _matchRoute(targets);
39
- });
40
-
41
- document.addEventListener('click', e => {
42
- const link = e.target.closest('a[b-link]');
43
- if (link) {
44
- e.preventDefault();
45
- const href = link.getAttribute('href');
46
- const targetAttr = link.getAttribute('b-target');
47
- const targets = targetAttr ? targetAttr.split(' ') : [];
48
-
49
- // Execute navigation via $go logic
50
- Lego.globals.$go(href, ...targets);
51
- }
52
- });
53
- _matchRoute();
54
- }
55
- },
56
- // ... other functions ...
57
- };
58
-
59
- ```
60
-
61
- ### 1. Template Registration
62
-
63
- The first thing `init()` does is look for blueprints you've already defined in your HTML.
64
-
65
- ```js
66
- document.querySelectorAll('template[b-id]').forEach(t => {
67
- registry[t.getAttribute('b-id')] = t;
68
- });
69
-
70
- ```
71
-
72
- It scans the entire document for any `<template>` tag that has a `b-id` attribute. It then adds these to the `registry` object we discussed in [Topic 2](/contributing/02-registry). This allows you to define components directly in your HTML file without writing a single line of JavaScript.
73
-
74
- ### 2. Setting up the "Eyes" (`MutationObserver`)
75
-
76
- This is the most critical part of the initialization. The library creates a `MutationObserver`.
77
-
78
- ```js
79
- const observer = new MutationObserver(m => m.forEach(r => {
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
- });
91
- r.removedNodes.forEach(n => n.nodeType === Node.ELEMENT_NODE && unsnap(n));
92
- }));
93
- observer.observe(document.body, { childList: true, subtree: true });
94
- ```
95
-
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.
98
-
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.
100
-
101
- - **The Reaction**:
102
-
103
- - If a new node is **added**, it calls `snap(n)` (which we will cover soon) to turn that raw HTML into a living component.
104
-
105
- - If a node is **removed**, it calls `unsnap(n)` to clean up memory and fire lifecycle hooks.
106
-
107
-
108
- ### 3. The "First Snap"
109
-
110
- After setting up the observer, it calls `snap(document.body)`.
111
-
112
- - **Why?** The observer only sees _new_ things being added. It doesn't see what was already there when the page loaded.
113
-
114
- - By calling `snap` on the body, the library manually processes every custom component that was present in the initial HTML.
115
-
116
-
117
- ### 4. Global Data Binding
118
-
119
- Finally, it binds the `Lego.globals` state to the `document.body`.
120
-
121
- JavaScript
122
-
123
- ```
124
- bind(document.body, { _studs: Lego.globals, _data: { bound: false } });
125
-
126
- ```
127
-
128
- This is a clever move: it treats the entire website body as if it were a giant component. This allows you to use reactive data in your "Light DOM" (the regular HTML outside of components) by referencing values stored in `Lego.globals`.
129
-
130
- ### 5. Routing Initialization
131
-
132
- If you have defined any routes using `Lego.route()`, the `init` function sets up global click listeners and history management so that links with the `b-link` attribute don't refresh the page but instead perform a "surgical" swap of content.
133
-
134
- ----------
135
-
136
- **Summary of `init()`:** It finds templates, starts a "watchdog" for new elements, processes existing elements, and enables global data.
@@ -1,72 +0,0 @@
1
- # Big Brother is Watching
2
-
3
- In Topic 6, we saw that Lego.init() sets up a "watchdog." Now, let's look at exactly how that watchdog functions. This is what makes the library feel "automatic"—you can inject HTML into the page using vanilla JavaScript, and Lego will instantly recognize it and bring it to life.
4
-
5
- ## Mutation Observer
6
-
7
- The `MutationObserver` is a built-in browser API that provides the ability to watch for changes being made to the DOM tree. In this library, it acts as the "Event Loop" for component lifecycles.
8
-
9
- ### 1. The Configuration
10
-
11
- The library observes the `document.body` with specific settings:
12
-
13
- ```js
14
- observer.observe(document.body, { childList: true, subtree: true });
15
-
16
- ```
17
-
18
- - **`childList: true`**: Tells the observer to watch for elements being added or removed.
19
-
20
- - **`subtree: true`**: This is vital. Without this, the library would only see things added directly to the `<body>`. With it, the library sees changes happening deep inside nested divs or other components.
21
-
22
-
23
- ### 2. Processing `addedNodes`
24
-
25
- Whenever a new piece of HTML is injected (via `innerHTML`, `appendChild`, etc.), the observer receives a list of `addedNodes`.
26
-
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
-
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
-
33
-
34
-
35
- ### 3. Processing `removedNodes`
36
-
37
- When an element is deleted from the page, the observer catches it in `removedNodes`.
38
-
39
- - **Cleanup**: It calls `unsnap(n)`.
40
-
41
- - **Lifecycle Hook**: `unsnap` checks the component's state for an `unmounted` function. This allows you to perform cleanup, like stopping timers or closing WebSocket connections, preventing memory leaks.
42
-
43
-
44
- ### Why this is superior to manual initialization
45
- In most frameworks, you have to tell the library when you’ve changed the page (e.g., calling `root.render()`). This library turns that upside down by using the browser's native **MutationObserver** to watch the DOM and react automatically.
46
- Unlike some libraries where adding a button via `innerHTML` requires a manual "re-scan" or "re-bind" call, this setup makes LegoDOM **reactive to the DOM itself**: the moment a tag like `<user-card>` appears in the document, it is automatically detected and upgraded into a functional, living component.
47
-
48
- ### 1. The Strategy: "Observe Once, Act Everywhere"
49
-
50
- The library sets up a single observer on `document.body`.
51
-
52
- - **`childList: true`**: This tells the observer to watch for the addition or removal of direct children.
53
-
54
- - **`subtree: true`**: This is the "secret sauce." It extends the observation to the entire DOM tree. If you have a deeply nested `div` and you inject a Lego component into it, the observer will see it.
55
-
56
-
57
- ### 2. The Logic Loop: Added Nodes
58
-
59
- When the observer detects changes, it provides a list of `MutationRecord` objects. The library loops through these records specifically looking for `addedNodes`.
60
-
61
- - **Type Filtering**: It checks `n.nodeType === Node.ELEMENT_NODE`. This ensures the library ignores text changes or comments and only focuses on **Elements** (tags).
62
-
63
- - **The Upgrade (Snapping)**: For every new element found, it calls `snap(n)`. This is why you can do `document.body.innerHTML += '<my-counter></my-counter>'` in the console, and the counter will immediately start working.
64
-
65
-
66
- ### 3. The Logic Loop: Removed Nodes
67
-
68
- This is equally important for "garbage collection." When an element is deleted, it appears in `removedNodes`.
69
-
70
- - **The `unsnap` Trigger**: The library calls `unsnap(n)`.
71
-
72
- - **Lifecycle Cleanup**: Inside `unsnap`, the library looks for a developer-defined `unmounted` function. This is where you'd kill `setInterval` timers or close database connections. Without this, your app would suffer from "Memory Leaks"—processes that keep running even though the component is gone.
@@ -1,140 +0,0 @@
1
- # Let there be, and it was!
2
-
3
- If LegoDOM were a robot it would probably say - snap() is the most complex function I have because it acts as the "Middleman" between the static DOM, the reactive state, and the Shadow DOM. It is the "constructor" that the ~~stingy~~ browser never gave anyone - LegoDOM.
4
-
5
-
6
- ## Snap, snap, snap!
7
-
8
- The `snap(el)` function is responsible for "upgrading" a standard HTML element. It is recursive, meaning if you snap a `<div>`, it will automatically look inside that `<div>` and snap every child as well.
9
-
10
- ```js
11
- const snap = (el) => {
12
- if (!el || el.nodeType !== Node.ELEMENT_NODE) return;
13
- const data = getPrivateData(el);
14
- const name = el.tagName.toLowerCase();
15
- const templateNode = registry[name];
16
-
17
- if (templateNode && !data.snapped) {
18
- data.snapped = true;
19
- const tpl = templateNode.content.cloneNode(true);
20
- const shadow = el.attachShadow({ mode: 'open' });
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
-
27
- // TIER 1: Logic from Lego.define (SFC)
28
- const scriptLogic = sfcLogic.get(name) || {};
29
-
30
- // TIER 2: Logic from the <template b-data="..."> attribute
31
- const templateLogic = parseJSObject(templateNode.getAttribute('b-data') || '{}');
32
-
33
- // TIER 3: Logic from the <my-comp b-data="..."> tag
34
- const instanceLogic = parseJSObject(el.getAttribute('b-data') || '{}');
35
-
36
- // Priority: Script < Template < Instance
37
- el._studs = reactive({
38
- ...scriptLogic,
39
- ...templateLogic,
40
- ...instanceLogic,
41
- // Inject Global Helpers
42
- get $route() { return Lego.globals.$route },
43
- get $go() { return Lego.globals.$go }
44
- }, el);
45
-
46
- shadow.appendChild(tpl);
47
-
48
- const style = shadow.querySelector('style');
49
- if (style) {
50
- style.textContent = style.textContent.replace(/\bself\b/g, ':host');
51
- }
52
-
53
- bind(shadow, el);
54
- render(el);
55
-
56
- if (typeof el._studs.mounted === 'function') {
57
- try { el._studs.mounted.call(el._studs); } catch (e) { console.error(`[Lego] Error in mounted <${name}>:`, e); }
58
- }
59
- }
60
-
61
- let provider = el.parentElement;
62
- while (provider && !provider._studs) provider = provider.parentElement;
63
- if (provider && provider._studs) bind(el, provider);
64
-
65
- [...el.children].forEach(snap);
66
- };
67
- ```
68
-
69
- ### 1. The Blueprint Lookup
70
-
71
- When `snap(el)` runs, the first thing it does is determine if the element is a Lego component.
72
-
73
- - It converts the tag name to lowercase (e.g., `<MY-COMP>` becomes `my-comp`).
74
-
75
- - It checks the **`registry`** (which we filled in [Topic 2](./02-registry.md)) to see if a `<template>` exists for that name.
76
-
77
- - It uses `getPrivateData(el).snapped` to ensure it never "snaps" the same element twice, preventing infinite loops.
78
-
79
-
80
- ### 2. Attaching the Shadow DOM
81
-
82
- If a template is found, Lego creates a **Shadow DOM** for the element:
83
-
84
- JavaScript
85
-
86
- ```
87
- const shadow = el.attachShadow({ mode: 'open' });
88
-
89
- ```
90
-
91
- - **Encapsulation**: By using `attachShadow`, the component’s internal styles and HTML are shielded from the rest of the page.
92
-
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
-
100
-
101
-
102
- ### 3. CSS "self" Transformation
103
-
104
- Lego includes a small but clever utility for styling:
105
-
106
- JavaScript
107
-
108
- ```
109
- style.textContent = style.textContent.replace(/\bself\b/g, ':host');
110
-
111
- ```
112
-
113
- - This allows you to write `self { color: red; }` in your template CSS.
114
-
115
- - During the snap process, Lego converts the word `self` to the official Web Component selector `:host`, which targets the component itself.
116
-
117
-
118
- ### 4. Data Merging & Reactivity
119
-
120
- This is where the component's state is born. The library merges data from three different sources (we will dive deeper into this "Tier System" in Topic 9) and wraps the result in the **`reactive()`** proxy.
121
-
122
- - The resulting proxy is stored in `el._studs`. This is the "brain" of your component.
123
-
124
-
125
- ### 5. The First Render and Lifecycle
126
-
127
- Once the data is ready and the Shadow DOM is attached:
128
-
129
- 1. **`bind(shadow, el)`**: Connects event listeners (like `@click`) inside the Shadow DOM.
130
-
131
- 2. **`render(el)`**: Performs the initial pass to fill in <code v-pre>{{ variables }}</code> and handle `b-show/b-for` logic.
132
-
133
- 3. **`mounted()`**: If you defined a `mounted` function in your logic, Lego calls it now. This is your signal that the component is officially "alive" and visible on the page.
134
-
135
- ---------
136
-
137
- **Crucial Logic Note:** At the very end of the function, `snap` calls itself on every child of the element: `[...el.children].forEach(snap)`. This ensures that if you have components nested inside components, they all "wake up" in a top-down order.
138
-
139
-
140
- **Summary**: `snap()` takes a raw tag, gives it a Shadow DOM "soul," injects its HTML blueprint, sets up its reactive "brain" (`_studs`), and triggers its first breath (`mounted`).
@@ -1,69 +0,0 @@
1
- # He said, She said, They Said, Who Said?
2
-
3
- This topic is about the "Power Struggle" inside the code. When a component is born, it needs to know what its data is. The library looks at three different places to find that data, and it has a strict hierarchy of who wins in a conflict.
4
-
5
-
6
- ## Diffing, Merging, and Priorities (The Tier System)
7
-
8
- Inside the `snap(el)` function, you will see a variable called `logic`. This isn't just a simple object; it is the result of a three-way merge. The code uses the spread operator `{ ... }` to layer these "Tiers."
9
-
10
- ### The Three Tiers of Authority
11
-
12
- 1. **Tier 1: The Global Definition (Lowest Priority)**
13
-
14
- ```js
15
- const baseLogic = sfcLogic.get(name) || {};
16
-
17
- ```
18
-
19
- This is the JavaScript object you provided when you called `Lego.define()`. It contains your default values and methods. It’s the "fallback" data. SFCs and .lego files fall under this tier.
20
-
21
- 2. **Tier 2: The Template Attributes (Middle Priority)**
22
-
23
-
24
- ```js
25
- const templateLogic = parseJSObject(t.getAttribute('b-data')) || {};
26
-
27
- ```
28
-
29
- Lego looks at the `<template>` tag itself in your HTML. If you added a `b-data` attribute there, it overrides the global definition. This is useful for creating "variants" of a component template without writing new JavaScript.
30
-
31
- 3. **Tier 3: The Instance Attributes (Highest Priority)**
32
-
33
- ```js
34
- const instanceLogic = parseJSObject(el.getAttribute('b-data')) || {};
35
-
36
- ```
37
-
38
- This is the data attached to the specific tag on your page (e.g., `<user-profile b-data="{id: 42}">`). This is the "Final Word." If the instance says the ID is 42, it doesn't matter what the template or the global definition said.
39
-
40
-
41
- ### The Merge Order
42
-
43
- The code combines them like this:
44
-
45
- JavaScript
46
-
47
- ```
48
- const mergedLogic = { ...baseLogic, ...templateLogic, ...instanceLogic };
49
-
50
- ```
51
-
52
- In JavaScript, when you spread objects, the properties on the right overwrite properties on the left. So, Instance > Template > Definition.
53
-
54
- ### The `parseJSObject` Utility
55
-
56
- To make this work, the library includes a helper called `parseJSObject`.
57
-
58
- - **Why not `JSON.parse`?** JSON is strict (requires double quotes, no functions).
59
-
60
- - **The Library's Way**: It uses `new Function('return ' + str)()`. This is a powerful (and dangerous) trick that allows you to write actual JavaScript inside your HTML attributes, including functions or arrays, which Lego then evaluates into a real object.
61
-
62
-
63
- ### The Edge Case: `b-data` as a "Ref"
64
-
65
- If the `b-data` attribute starts with a `$`, like `b-data="$someGlobal"`, the library treats it as a pointer to a global variable instead of a raw object string. This allows for deep data sharing between the component and the outside world.
66
-
67
- ----------
68
-
69
- **Summary**: A component's data is a "Cake" with three layers. The Global Logic is the bottom, the Template Logic is the middle, and the Instance Logic is the frosting. The "Frosting" (Instance) is what the user ultimately sees.
@@ -1,78 +0,0 @@
1
- # State Management
2
-
3
- Let's see how the "Cake" of data we merged in Topic 9 is actually brought to life. This is the moment a static object becomes a **Reactive State**, which the library stores in a property called `_studs`.
4
-
5
-
6
- ## The `reactive` State (`_studs`)
7
-
8
- Once `snap()` has merged the data from the SFC (Tier 1), the Template (Tier 2), and the Instance (Tier 3), it doesn't just save that object to the element. It transforms it.
9
-
10
- ```js
11
- //...rest of code
12
- const tpl = templateNode.content.cloneNode(true);
13
- const shadow = el.attachShadow({ mode: 'open' });
14
-
15
- // TIER 1: Logic from Lego.define (SFC)
16
- const scriptLogic = sfcLogic.get(name) || {};
17
-
18
- // TIER 2: Logic from the <template b-data="..."> attribute
19
- const templateLogic = parseJSObject(templateNode.getAttribute('b-data') || '{}');
20
-
21
- // TIER 3: Logic from the <my-comp b-data="..."> tag
22
- const instanceLogic = parseJSObject(el.getAttribute('b-data') || '{}');
23
-
24
- // Priority: Script < Template < Instance
25
- el._studs = reactive({
26
- ...scriptLogic,
27
- ...templateLogic,
28
- ...instanceLogic,
29
- get $route() { return Lego.globals.$route },
30
- get $go() { return Lego.globals.$go }
31
- }, el);
32
- //... rest of code
33
- ```
34
-
35
- ### 1. Why the name `_studs`?
36
-
37
- In the physical world, "studs" are the bumps on top of a Lego brick that allow it to connect to others. In this library:
38
-
39
- - **`el._studs`** represents the connection point between your JavaScript logic and the DOM.
40
-
41
- - It is the "source of truth" for the component. Every `{{variable}}` you write in your HTML is looking for a matching key inside `_studs`.
42
-
43
-
44
- ### 2. The Transformation
45
-
46
- The library executes this line during the snap process:
47
-
48
- ```js
49
- el._studs = reactive({ ...mergedLogic }, el);
50
-
51
- ```
52
-
53
- - **The `reactive` call**: This wraps the merged object in the **Proxy** we discussed in Topic 4.
54
-
55
- - **The `el` argument**: Crucially, the proxy is given a reference to the DOM element (`el`). This allows the Proxy's `set` trap to know exactly which component needs to be added to the `globalBatcher` when a property changes.
56
-
57
-
58
- ### 3. Contextual Binding (The `this` Keyword)
59
-
60
- After the `_studs` proxy is created, the library ensures that your methods work correctly. When you define a method in your SFC, you expect `this` to point to your data.
61
-
62
- - Inside `snap()`, lifecycle hooks are called like this: `el._studs.mounted.call(el._studs)`.
63
-
64
- - By using `.call(el._studs)`, the library forces the execution context of your functions to be the reactive proxy.
65
-
66
- - **Result**: When you write `this.count++` in your code, you are actually interacting with the **Proxy**, which triggers the `set` trap, which notifies the **Batcher**, which triggers the **Render**.
67
-
68
-
69
- ### 4. Visibility vs. Privacy
70
-
71
- - **`_studs`**: This is attached directly to the DOM element. You can actually type `document.querySelector('my-component')._studs` in your browser console to see and even modify the live state of any component.
72
-
73
- - **`privateData`**: Unlike `_studs`, the internal "housekeeping" (like whether the component has already snapped) is kept in a `WeakMap` called `privateData`, making it inaccessible to the outside world.
74
-
75
-
76
- ----------
77
-
78
- **Summary**: `_studs` is the reactive engine of your component. It is created by merging all data tiers into a Proxy that is uniquely linked to that specific DOM element.