lego-dom 1.3.3 → 1.4.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 +61 -0
- package/main.js +24 -3
- package/main.min.js +7 -0
- package/package.json +3 -1
- package/vite-plugin.js +0 -14
- package/.github/workflows/deploy-docs.yml +0 -56
- package/.legodom +0 -87
- package/docs/.vitepress/config.js +0 -162
- 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 -328
- package/docs/guide/components.md +0 -412
- 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 -420
- 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/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,67 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.4.0] - 2026-01-16
|
|
6
|
+
|
|
7
|
+
### Breaking Changes
|
|
8
|
+
|
|
9
|
+
- **`$ancestors()` now returns element instead of state:** For consistency with `$element`, `$ancestors()` now returns the ancestor element. Access state via `.state` property.
|
|
10
|
+
```javascript
|
|
11
|
+
// Before (v1.3.x)
|
|
12
|
+
const userId = this.$ancestors('user-profile').userId;
|
|
13
|
+
|
|
14
|
+
// After (v1.4.0)
|
|
15
|
+
const userId = this.$ancestors('user-profile').state.userId;
|
|
16
|
+
```
|
|
17
|
+
**Migration:** Add `.state` after all `$ancestors()` calls that access component state.
|
|
18
|
+
|
|
19
|
+
## [1.3.5] - 2026-01-16
|
|
20
|
+
|
|
21
|
+
### Features
|
|
22
|
+
|
|
23
|
+
- **Lego Studio:** Added zero-config component development environment. Enable with `Lego.init(document.body, { studio: true })` and navigate to `/_/studio` to browse, preview, and inspect components in real-time.
|
|
24
|
+
- Component browser with search/filter
|
|
25
|
+
- Live component preview in isolation
|
|
26
|
+
- Real-time state inspector with JSON editor
|
|
27
|
+
- URL deep linking for sharing components
|
|
28
|
+
- Published as separate package: `@legodom/studio@0.0.2`
|
|
29
|
+
|
|
30
|
+
- **Advanced API:** Exposed `Lego.snap()` and `Lego.unsnap()` for manual component initialization and cleanup. Useful for building dev tools, testing, and dynamic component creation in Shadow DOMs.
|
|
31
|
+
```javascript
|
|
32
|
+
const el = document.createElement('my-component');
|
|
33
|
+
mount.appendChild(el);
|
|
34
|
+
Lego.snap(el); // Manually initialize
|
|
35
|
+
|
|
36
|
+
// Later...
|
|
37
|
+
Lego.unsnap(el); // Clean up
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- **Component State Property:** Added `element.state` getter/setter to all component instances for cleaner state access.
|
|
41
|
+
```javascript
|
|
42
|
+
// Before (internal API)
|
|
43
|
+
const state = element._studs;
|
|
44
|
+
|
|
45
|
+
// After (public API)
|
|
46
|
+
const state = element.state;
|
|
47
|
+
element.state = { count: 5 }; // Merges into existing state
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Documentation
|
|
51
|
+
|
|
52
|
+
- **Advanced API Guide:** Added comprehensive documentation for `Lego.snap()` and `Lego.unsnap()` at `/docs/api/advanced.md`
|
|
53
|
+
- **Lego Studio Guide:** Added full Studio documentation at `/docs/guide/studio.md` with quick start, examples, and troubleshooting
|
|
54
|
+
|
|
55
|
+
### Improvements
|
|
56
|
+
|
|
57
|
+
- **Studio CDN Loading:** Studio is loaded on-demand from unpkg CDN when `studio: true` is set, keeping core LegoDOM lightweight
|
|
58
|
+
- **State Reactivity:** The `element.state` setter uses `Object.assign()` to merge new values while preserving the reactive Proxy
|
|
59
|
+
|
|
60
|
+
## [1.3.4] - 2026-01-16
|
|
61
|
+
|
|
62
|
+
### Improvements
|
|
63
|
+
|
|
64
|
+
- **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.
|
|
65
|
+
|
|
5
66
|
## [1.3.3] - 2026-01-16
|
|
6
67
|
|
|
7
68
|
### Fixes
|
package/main.js
CHANGED
|
@@ -193,11 +193,11 @@ const Lego = (() => {
|
|
|
193
193
|
return current ?? '';
|
|
194
194
|
};
|
|
195
195
|
|
|
196
|
-
const
|
|
196
|
+
const findAncestor = (el, tagName) => {
|
|
197
197
|
let parent = el.parentElement || el.getRootNode().host;
|
|
198
198
|
while (parent) {
|
|
199
199
|
if (parent.tagName && parent.tagName.toLowerCase() === tagName.toLowerCase()) {
|
|
200
|
-
return parent
|
|
200
|
+
return parent;
|
|
201
201
|
}
|
|
202
202
|
parent = parent.parentElement || (parent.getRootNode && parent.getRootNode().host);
|
|
203
203
|
}
|
|
@@ -231,7 +231,7 @@ const Lego = (() => {
|
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
const helpers = {
|
|
234
|
-
$ancestors: (tag) =>
|
|
234
|
+
$ancestors: (tag) => findAncestor(context.self, tag),
|
|
235
235
|
$registry: (tag) => sharedStates.get(tag.toLowerCase()),
|
|
236
236
|
$element: context.self,
|
|
237
237
|
$route: Lego.globals.$route,
|
|
@@ -568,6 +568,13 @@ const Lego = (() => {
|
|
|
568
568
|
get $go() { return Lego.globals.$go }
|
|
569
569
|
}, el);
|
|
570
570
|
|
|
571
|
+
Object.defineProperty(el, 'state', {
|
|
572
|
+
get() { return this._studs },
|
|
573
|
+
set(v) { Object.assign(this._studs, v) },
|
|
574
|
+
configurable: true,
|
|
575
|
+
enumerable: false
|
|
576
|
+
});
|
|
577
|
+
|
|
571
578
|
shadow.appendChild(tpl);
|
|
572
579
|
|
|
573
580
|
const style = shadow.querySelector('style');
|
|
@@ -649,6 +656,8 @@ const Lego = (() => {
|
|
|
649
656
|
};
|
|
650
657
|
|
|
651
658
|
const publicAPI = {
|
|
659
|
+
snap,
|
|
660
|
+
unsnap,
|
|
652
661
|
init: async (root = document.body, options = {}) => {
|
|
653
662
|
if (!root || typeof root.nodeType !== 'number') root = document.body;
|
|
654
663
|
styleConfig = options.styles || {};
|
|
@@ -706,6 +715,17 @@ const Lego = (() => {
|
|
|
706
715
|
bind(root, root);
|
|
707
716
|
render(root);
|
|
708
717
|
|
|
718
|
+
if (options.studio) {
|
|
719
|
+
if (!registry['lego-studio']) {
|
|
720
|
+
const script = document.createElement('script');
|
|
721
|
+
script.src = 'https://unpkg.com/@legodom/studio@0.0.2/dist/lego-studio.js';
|
|
722
|
+
script.onerror = () => console.warn('[Lego] Failed to load Studio from CDN');
|
|
723
|
+
document.head.appendChild(script);
|
|
724
|
+
}
|
|
725
|
+
Lego.route('/_/studio', 'lego-studio');
|
|
726
|
+
Lego.route('/_/studio/:component', 'lego-studio');
|
|
727
|
+
}
|
|
728
|
+
|
|
709
729
|
if (routes.length > 0) {
|
|
710
730
|
// Smart History: Restore surgical targets on Back button
|
|
711
731
|
window.addEventListener('popstate', (event) => {
|
|
@@ -826,6 +846,7 @@ const Lego = (() => {
|
|
|
826
846
|
},
|
|
827
847
|
// For specific test validation
|
|
828
848
|
getActiveComponentsCount: () => activeComponents.size,
|
|
849
|
+
getLegos: () => Object.keys(registry),
|
|
829
850
|
config, // Expose config for customization
|
|
830
851
|
route: (path, tagName, middleware = null) => {
|
|
831
852
|
const paramNames = [];
|
package/main.min.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
const Lego=(()=>{const w={},M=new WeakMap,k=new WeakMap,O=new WeakMap,$=new Set,P=new Map,B=new Map,H=new Map,q=new Map;let G={};const p={onError:(e,s,c)=>{console.error(`[Lego Error] [${s}]`,e,c)},metrics:{},syntax:"brackets"},_=()=>p.syntax==="brackets"?["[[","]]"]:["{{","}}"],L=()=>{const[e,s]=_(),c=e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),o=s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`${c}(.*?)${o}`,"g")},R=[],ee=e=>typeof e!="string"?e:e.replace(/[&<>"']/g,s=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[s]),K=e=>{const c=e.split("/").pop().replace(/\.lego$/,"").replace(/_/g,"-").replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase();if(!c.includes("-"))throw new Error(`[Lego] Invalid component definition: "${e}". Component names must contain a hyphen (e.g. user-card.lego or UserCard.lego).`);return c},V=(e,s)=>{if(typeof e=="function"){const o=Array.from(document.querySelectorAll("*")).filter(t=>t.tagName.includes("-"));return[].concat(e(o))}if(e.startsWith("#")){const o=document.getElementById(e.slice(1));return o?[o]:[]}const c=s?.querySelectorAll(e)||[];return c.length>0?[...c]:[...document.querySelectorAll(e)]},j=(e,...s)=>c=>{const o=async(t,a=null,r=!0,n={})=>{if(r){const d={legoTargets:s.filter(i=>typeof i=="string"),method:t,body:a};history.pushState(d,"",e)}await F(s.length?s:null,c)};return{get:(t=!0,a={})=>o("GET",null,t,a),post:(t,a=!0,r={})=>o("POST",t,a,r),put:(t,a=!0,r={})=>o("PUT",t,a,r),patch:(t,a=!0,r={})=>o("PATCH",t,a,r),delete:(t=!0,a={})=>o("DELETE",null,t,a)}},J=(()=>{let e=!1;const s=new Set;let c=!1;return{add:o=>{!o||c||(s.add(o),!e&&(e=!0,requestAnimationFrame(()=>{c=!0;const t=Array.from(s);s.clear(),e=!1,t.forEach(a=>C(a)),setTimeout(()=>{t.forEach(a=>{const r=a._studs;if(r&&typeof r.updated=="function")try{r.updated.call(r)}catch(n){console.error("[Lego] Error in updated hook:",n)}}),c=!1},0)})))}}})(),S=(e,s,c=J)=>{if(e===null||typeof e!="object"||e instanceof Node)return e;if(M.has(e))return M.get(e);const o={get:(a,r)=>{const n=Reflect.get(a,r);return n!==null&&typeof n=="object"&&!(n instanceof Node)?S(n,s,c):n},set:(a,r,n)=>{const l=a[r],d=Reflect.set(a,r,n);return l!==n&&c.add(s),d},deleteProperty:(a,r)=>{const n=Reflect.deleteProperty(a,r);return c.add(s),n}},t=new Proxy(e,o);return M.set(e,t),t},I=e=>{try{return new Function(`return (${e})`)()}catch(s){return console.error("[Lego] Failed to parse b-data:",e,s),{}}},N=e=>(k.has(e)||k.set(e,{snapped:!1,bindings:null,bound:!1,rendering:!1,anchor:null,hasGlobalDependency:!1}),k.get(e)),D=(e,s)=>{if(!e)return"";const c=e.trim().split(".");let o=s;for(const t of c){if(o==null)return"";o=o[t]}return o??""},Z=(e,s)=>{let c=e.parentElement||e.getRootNode().host;for(;c;){if(c.tagName&&c.tagName.toLowerCase()===s.toLowerCase())return c._studs;c=c.parentElement||c.getRootNode&&c.getRootNode().host}},h=(e,s,c=!1)=>{if(/\b(function|eval|import|class|module|deploy|constructor|__proto__)\b/.test(e)){console.warn(`[Lego] Security Warning: Blocked dangerous expression "${e}"`);return}try{const o=s.state||{};let t=H.get(e);t||(t=new Function("global","self","event","helpers",`
|
|
2
|
+
with(helpers) {
|
|
3
|
+
with(this) {
|
|
4
|
+
return ${e}
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
`),H.set(e,t));const a={$ancestors:n=>Z(s.self,n),$registry:n=>B.get(n.toLowerCase()),$element:s.self,$route:Lego.globals.$route,$go:(n,...l)=>j(n,...l)(s.self),$emit:(n,l)=>{s.self.dispatchEvent(new CustomEvent(n,{detail:l,bubbles:!0,composed:!0}))}},r=t.call(o,s.global,s.self,s.event,a);return typeof r=="function"?r.call(o,s.event):r}catch(o){if(c)throw o;p.onError(o,"render-error",s.self);return}},z=(e,s)=>{if(e.type==="checkbox")e.checked!==!!s&&(e.checked=!!s);else{const c=s==null?"":String(s);e.value!==c&&(e.value=c)}},x=(e,s,c=null)=>{const o=s._studs,t=n=>{const l=N(n);if(!l.bound){if(n.hasAttributes()){const d=n.attributes;for(let i=0;i<d.length;i++){const u=d[i];if(u.name.startsWith("@")){const f=u.name.slice(1);n.addEventListener(f,g=>{try{let m=o;if(c){const y=h(c.listName,{state:o,global:Lego.globals,self:s})[c.index];m=Object.assign(Object.create(o),{[c.name]:y})}h(u.value,{state:m,global:Lego.globals,self:n,event:g},!0)}catch(m){p.onError(m,"event-handler",n)}})}}if(n.hasAttribute("b-sync")){const i=n.getAttribute("b-sync"),u=()=>{try{let f,g;if(c&&i.startsWith(c.name+".")){const y=h(c.listName,{state:o,global:Lego.globals,self:s})[c.index];if(!y)return;const E=i.split(".").slice(1);g=E.pop(),f=E.reduce((T,A)=>T[A],y)}else{const b=i.split(".");g=b.pop(),f=b.reduce((y,E)=>y[E],o)}const m=n.type==="checkbox"?n.checked:n.value;f&&f[g]!==m&&(f[g]=m)}catch(f){p.onError(f,"sync-update",n)}};n.addEventListener("input",u),n.addEventListener("change",u)}if(n.hasAttribute("b-var")){const i=n.getAttribute("b-var");o.$vars&&(o.$vars[i]=n)}}l.bound=!0}};e instanceof Element&&t(e);const a=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT);let r;for(;r=a.nextNode();)t(r)},Y=e=>{const s=[],c=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_TEXT);let o;for(;o=c.nextNode();){if((r=>{let n=r.parentNode;for(;n&&n!==e;){if(n.hasAttribute&&n.hasAttribute("b-for"))return!0;n=n.parentNode}return!1})(o))continue;const a=r=>{if(/\bglobal\b/.test(r)){const n=e.host||e;N(n).hasGlobalDependency=!0}};if(o.nodeType===Node.ELEMENT_NODE){if(o.hasAttribute("b-if")){const n=o.getAttribute("b-if");a(n);const l=document.createComment(`b-if: ${n}`),d=N(o);d.anchor=l,s.push({type:"b-if",node:o,anchor:l,expr:n})}if(o.hasAttribute("b-show")){const n=o.getAttribute("b-show");a(n),s.push({type:"b-show",node:o,expr:n})}if(o.hasAttribute("b-for")){const n=o.getAttribute("b-for").match(/^\s*(\w+)\s+in\s+([\s\S]+?)\s*$/);n&&(a(n[2]),s.push({type:"b-for",node:o,itemName:n[1],listName:n[2].trim(),template:o.cloneNode(!0)}),o.innerHTML="")}if(o.hasAttribute("b-text")&&s.push({type:"b-text",node:o,path:o.getAttribute("b-text")}),o.hasAttribute("b-html")){const n=o.getAttribute("b-html");a(n),s.push({type:"b-html",node:o,expr:n})}o.hasAttribute("b-sync")&&s.push({type:"b-sync",node:o});const[r]=_();[...o.attributes].forEach(n=>{n.value.includes(r)&&(a(n.value),s.push({type:"attr",node:o,attrName:n.name,template:n.value}))})}else if(o.nodeType===Node.TEXT_NODE){const[r]=_();o.textContent.includes(r)&&(a(o.textContent),s.push({type:"text",node:o,template:o.textContent}))}}return s},Q=(e,s)=>{const c=a=>{if(a.nodeType===Node.TEXT_NODE){a._tpl===void 0&&(a._tpl=a.textContent);const r=a._tpl.replace(L(),(n,l)=>h(l.trim(),{state:s,global:Lego.globals,self:a})??"");a.textContent!==r&&(a.textContent=r)}else if(a.nodeType===Node.ELEMENT_NODE){const[r]=_();[...a.attributes].forEach(n=>{if(n._tpl===void 0&&(n._tpl=n.value),n._tpl.includes(r)){const l=n._tpl.replace(L(),(d,i)=>h(i.trim(),{state:s,global:Lego.globals,self:a})??"");n.value!==l&&(n.value=l,n.name==="class"&&(a.className=l))}})}};c(e);const o=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT|NodeFilter.SHOW_TEXT);let t;for(;t=o.nextNode();)c(t)},C=e=>{const s=e._studs;if(!s)return;const c=N(e);if(!c.rendering){c.rendering=!0,p.metrics&&p.metrics.onRenderStart&&p.metrics.onRenderStart(e);try{const o=e.shadowRoot||e;c.bindings||(c.bindings=Y(o)),c.bindings.forEach(t=>{if(t.type==="b-if"){const a=!!h(t.expr,{state:s,global:Lego.globals,self:t.node}),r=!!t.node.parentNode;a&&!r?t.anchor.parentNode&&t.anchor.parentNode.replaceChild(t.node,t.anchor):!a&&r&&t.node.parentNode.replaceChild(t.anchor,t.node)}if(t.type==="b-show"&&(t.node.style.display=h(t.expr,{state:s,global:Lego.globals,self:t.node})?"":"none"),t.type==="b-text"&&(t.node.textContent=D(t.path,s)),t.type==="b-html"&&(t.node.innerHTML=h(t.expr,{state:s,global:Lego.globals,self:t.node})||""),t.type==="b-sync"&&z(t.node,D(t.node.getAttribute("b-sync"),s)),t.type==="text"){const a=t.template.replace(L(),(r,n)=>h(n.trim(),{state:s,global:Lego.globals,self:t.node})??"");t.node.textContent!==a&&(t.node.textContent=a)}if(t.type==="attr"){const a=t.template.replace(L(),(r,n)=>h(n.trim(),{state:s,global:Lego.globals,self:t.node})??"");t.node.getAttribute(t.attrName)!==a&&(t.node.setAttribute(t.attrName,a),t.attrName==="class"&&(t.node.className=a))}if(t.type==="b-for"){const a=h(t.listName,{state:s,global:Lego.globals,self:e})||[];O.has(t.node)||O.set(t.node,new Map);const r=O.get(t.node),n=new Set;a.forEach((l,d)=>{const i=l&&typeof l=="object"?l.__id||(l.__id=Math.random()):`${d}-${l}`;n.add(i);let u=r.get(i);u||(u=t.template.cloneNode(!0),u.removeAttribute("b-for"),r.set(i,u),x(u,e,{name:t.itemName,listName:t.listName,index:d}));const f=Object.assign(Object.create(s),{[t.itemName]:l});Q(u,f),u.querySelectorAll("[b-sync]").forEach(g=>{const m=g.getAttribute("b-sync");if(m.startsWith(t.itemName+".")){const b=h(t.listName,{state:s,global:Lego.globals,self:e});z(g,D(m.split(".").slice(1).join("."),b[d]))}}),t.node.children[d]!==u&&t.node.insertBefore(u,t.node.children[d]||null)});for(const[l,d]of r.entries())n.has(l)||(d.remove(),r.delete(l))}}),s===Lego.globals&&$.forEach(t=>{N(t).hasGlobalDependency&&C(t)})}catch(o){p.onError(o,"render",e)}finally{p.metrics&&p.metrics.onRenderEnd&&p.metrics.onRenderEnd(e),c.rendering=!1}}},v=e=>{if(!e||e.nodeType!==Node.ELEMENT_NODE)return;const s=N(e),c=e.tagName.toLowerCase(),o=w[c];if(o&&!s.snapped){s.snapped=!0;const a=o.content.cloneNode(!0),r=e.attachShadow({mode:"open"}),n=(o.getAttribute("b-styles")||"").split(/\s+/).filter(Boolean);if(n.length>0){const f=n.flatMap(g=>q.get(g)||[]);f.length>0&&(r.adoptedStyleSheets=[...f])}const l=P.get(c)||{},d=I(o.getAttribute("b-data")||"{}"),i=I(e.getAttribute("b-data")||"{}");e._studs=S({...l,...d,...i,$vars:{},$element:e,$emit:(f,g)=>{e.dispatchEvent(new CustomEvent(f,{detail:g,bubbles:!0,composed:!0}))},get $route(){return Lego.globals.$route},get $go(){return Lego.globals.$go}},e),r.appendChild(a);const u=r.querySelector("style");if(u&&(u.textContent=u.textContent.replace(/\bself\b/g,":host")),x(r,e),$.add(e),C(e),[...r.children].forEach(v),typeof e._studs.mounted=="function")try{e._studs.mounted.call(e._studs)}catch(f){p.onError(f,"mounted",e)}}let t=e.parentElement;for(;t&&!t._studs;)t=t.parentElement;t&&t._studs&&x(e,t),[...e.children].forEach(v)},W=e=>{if(e._studs&&typeof e._studs.unmounted=="function")try{e._studs.unmounted.call(e._studs)}catch(s){console.error("[Lego] Error in unmounted:",s)}e.shadowRoot&&[...e.shadowRoot.children].forEach(W),$.delete(e),[...e.children].forEach(W)},F=async(e=null,s=null)=>{const c=window.location.pathname,o=window.location.search,t=R.find(d=>d.regex.test(c));if(!t)return;let a=[];if(e)a=e.flatMap(d=>V(d,s));else{const d=document.querySelector("lego-router");d&&(a=[d])}if(a.length===0)return;const r=c.match(t.regex).slice(1),n=Object.fromEntries(t.paramNames.map((d,i)=>[d,r[i]])),l=Object.fromEntries(new URLSearchParams(o));t.middleware&&!await t.middleware(n,Lego.globals)||(Lego.globals.$route.url=c+o,Lego.globals.$route.route=t.path,Lego.globals.$route.params=n,Lego.globals.$route.query=l,Lego.globals.$route.method=history.state?.method||"GET",Lego.globals.$route.body=history.state?.body||null,a.forEach(d=>{if(d){const i=document.createElement(t.tagName);d.replaceChildren(i)}}))},U={init:async(e=document.body,s={})=>{(!e||typeof e.nodeType!="number")&&(e=document.body),G=s.styles||{},p.loader=s.loader;const c=Object.entries(G).map(async([t,a])=>{const r=await Promise.all(a.map(async n=>{try{const d=await(await fetch(n)).text(),i=new CSSStyleSheet;return await i.replace(d),i}catch(l){return console.error(`[Lego] Failed to load stylesheet: ${n}`,l),null}}));q.set(t,r.filter(n=>n!==null))});await Promise.all(c),document.querySelectorAll("template[b-id]").forEach(t=>{w[t.getAttribute("b-id")]=t}),new MutationObserver(t=>t.forEach(a=>{a.addedNodes.forEach(r=>{if(r.nodeType===Node.ELEMENT_NODE){v(r);const n=r.tagName.toLowerCase();if(n.includes("-")&&!w[n]&&p.loader&&!$.has(r)){const l=p.loader(n);if(l){const d=typeof l=="string"?fetch(l).then(i=>i.text()):l;Promise.resolve(d).then(i=>U.defineSFC(i,n+".lego")).catch(i=>console.error(`[Lego] Failed to load ${n}:`,i))}}}}),a.removedNodes.forEach(r=>r.nodeType===Node.ELEMENT_NODE&&W(r))})).observe(e,{childList:!0,subtree:!0}),e._studs=Lego.globals,v(e),x(e,e),C(e),R.length>0&&(window.addEventListener("popstate",t=>{const a=t.state?.legoTargets||null;F(a)}),document.addEventListener("submit",t=>{t.preventDefault()}),document.addEventListener("click",t=>{const r=t.composedPath().find(n=>n.tagName==="A"&&(n.hasAttribute("b-target")||n.hasAttribute("b-link")));if(r){t.preventDefault();const n=r.getAttribute("href"),l=r.getAttribute("b-target"),d=l?l.split(/\s+/).filter(Boolean):[],i=r.getAttribute("b-link")!=="false";Lego.globals.$go(n,...d).get(i)}}),F())},globals:S({$route:{url:window.location.pathname,route:"",params:{},query:{},method:"GET",body:null},$go:(e,...s)=>j(e,...s)(document.body)},document.body),defineSFC:(e,s="component.lego")=>{let c="",o="{}",t="",a="",r=e;const n=/<(template|script|style)\b((?:\s+(?:[^>"']|"[^"]*"|'[^']*')*)*)>/i;for(;r;){const i=r.match(n);if(!i)break;const u=i[1].toLowerCase(),f=i[2],g=i[0],m=i.index,b=`</${u}>`,y=m+g.length,E=r.indexOf(b,y);if(E===-1){console.warn(`[Lego] Unclosed <${u}> tag in ${s}`);break}const T=r.slice(y,E);if(u==="template"){c=T.trim();const A=f.match(/b-styles=["']([^"']+)["']/);A&&(t=A[1])}else if(u==="script"){const A=T.trim(),X=A.match(/export\s+default\s+({[\s\S]*})/);o=X?X[1]:A}else u==="style"&&(a=T.trim());r=r.slice(E+b.length)}const l=K(s),d=new Function(`return ${o}`)();a&&(c=`<style>${a}</style>`+c),w[l]=document.createElement("template"),w[l].innerHTML=c,w[l].setAttribute("b-styles",t),P.set(l,d),document.querySelectorAll(l).forEach(i=>!N(i).snapped&&v(i))},define:(e,s,c={},o="")=>{const t=document.createElement("template");t.setAttribute("b-id",e),t.setAttribute("b-styles",o),t.innerHTML=s,w[e]=t,P.set(e,c);try{B.set(e.toLowerCase(),S({...c},document.body))}catch(a){p.onError(a,"define",e)}document.querySelectorAll(e).forEach(v)},getActiveComponentsCount:()=>$.size,config:p,route:(e,s,c=null)=>{const o=[],t=e.replace(/:([^\/]+)/g,(a,r)=>(o.push(r),"([^/]+)"));R.push({path:e,regex:new RegExp(`^${t}$`),tagName:s,paramNames:o,middleware:c})}};return U})();typeof window<"u"&&(window.Lego=Lego);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lego-dom",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "A feature-rich web components + SFC frontend framework",
|
|
6
6
|
"main": "main.js",
|
|
@@ -22,11 +22,13 @@
|
|
|
22
22
|
"author": "Tersoo Ortserga",
|
|
23
23
|
"scripts": {
|
|
24
24
|
"test": "vitest run",
|
|
25
|
+
"build": "esbuild main.js --minify --outfile=main.min.js",
|
|
25
26
|
"docs:dev": "vitepress dev docs",
|
|
26
27
|
"docs:build": "vitepress build docs",
|
|
27
28
|
"docs:preview": "vitepress preview docs"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
31
|
+
"esbuild": "^0.27.2",
|
|
30
32
|
"jsdom": "^22.0.0",
|
|
31
33
|
"vitepress": "^1.6.4",
|
|
32
34
|
"vitest": "^1.0.0"
|
package/vite-plugin.js
CHANGED
|
@@ -153,20 +153,6 @@ export default '${parsed.componentName}';
|
|
|
153
153
|
}
|
|
154
154
|
},
|
|
155
155
|
|
|
156
|
-
transform(code, id) {
|
|
157
|
-
if (id.endsWith('.lego') && !id.includes('?')) {
|
|
158
|
-
const parsed = parseLego(code, path.basename(id));
|
|
159
|
-
const validation = validateLego(parsed);
|
|
160
|
-
|
|
161
|
-
if (!validation.valid) {
|
|
162
|
-
throw new Error(`Invalid .lego file:\n${validation.errors.join('\n')}`);
|
|
163
|
-
}
|
|
164
156
|
|
|
165
|
-
return {
|
|
166
|
-
code: generateDefineCall(parsed),
|
|
167
|
-
map: null
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
157
|
};
|
|
172
158
|
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
name: Deploy Documentation
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [main]
|
|
6
|
-
workflow_dispatch:
|
|
7
|
-
|
|
8
|
-
permissions:
|
|
9
|
-
contents: read
|
|
10
|
-
pages: write
|
|
11
|
-
id-token: write
|
|
12
|
-
|
|
13
|
-
concurrency:
|
|
14
|
-
group: pages
|
|
15
|
-
cancel-in-progress: false
|
|
16
|
-
|
|
17
|
-
jobs:
|
|
18
|
-
build:
|
|
19
|
-
runs-on: ubuntu-latest
|
|
20
|
-
steps:
|
|
21
|
-
- name: Checkout
|
|
22
|
-
uses: actions/checkout@v4
|
|
23
|
-
with:
|
|
24
|
-
fetch-depth: 0
|
|
25
|
-
|
|
26
|
-
- name: Setup Node
|
|
27
|
-
uses: actions/setup-node@v4
|
|
28
|
-
with:
|
|
29
|
-
node-version: 20
|
|
30
|
-
cache: npm
|
|
31
|
-
|
|
32
|
-
- name: Setup Pages
|
|
33
|
-
uses: actions/configure-pages@v4
|
|
34
|
-
|
|
35
|
-
- name: Install dependencies
|
|
36
|
-
run: npm ci
|
|
37
|
-
|
|
38
|
-
- name: Build with VitePress
|
|
39
|
-
run: npm run docs:build
|
|
40
|
-
|
|
41
|
-
- name: Upload artifact
|
|
42
|
-
uses: actions/upload-pages-artifact@v3
|
|
43
|
-
with:
|
|
44
|
-
path: docs/.vitepress/dist
|
|
45
|
-
|
|
46
|
-
deploy:
|
|
47
|
-
environment:
|
|
48
|
-
name: github-pages
|
|
49
|
-
url: ${{ steps.deployment.outputs.page_url }}
|
|
50
|
-
needs: build
|
|
51
|
-
runs-on: ubuntu-latest
|
|
52
|
-
name: Deploy
|
|
53
|
-
steps:
|
|
54
|
-
- name: Deploy to GitHub Pages
|
|
55
|
-
id: deployment
|
|
56
|
-
uses: actions/deploy-pages@v4
|
package/.legodom
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
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
|
-
```
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'vitepress';
|
|
2
|
-
|
|
3
|
-
export default defineConfig({
|
|
4
|
-
title: 'Lego',
|
|
5
|
-
description: 'A feature-rich web components + SFC frontend framework',
|
|
6
|
-
base: '/legodom/',
|
|
7
|
-
|
|
8
|
-
themeConfig: {
|
|
9
|
-
logo: '/logo.svg',
|
|
10
|
-
|
|
11
|
-
nav: [
|
|
12
|
-
{ text: 'Guide', link: '/guide/' },
|
|
13
|
-
{ text: 'Tutorial', link: '/tutorial/' },
|
|
14
|
-
{ text: 'Contributing', link: '/contributing/' },
|
|
15
|
-
{ text: 'API', link: '/api/' },
|
|
16
|
-
{ text: 'Examples', link: '/examples/' },
|
|
17
|
-
{ text: 'Router', link: '/router/' },
|
|
18
|
-
{
|
|
19
|
-
text: 'v1.3.3',
|
|
20
|
-
items: [
|
|
21
|
-
{ text: 'Changelog', link: 'https://github.com/rayattack/LegoDOM/releases' }
|
|
22
|
-
|
|
23
|
-
]
|
|
24
|
-
}
|
|
25
|
-
],
|
|
26
|
-
|
|
27
|
-
sidebar: {
|
|
28
|
-
'/contributing/': [
|
|
29
|
-
{
|
|
30
|
-
text: 'Contributing',
|
|
31
|
-
items: [
|
|
32
|
-
{ text: 'Topic 1 - Welcome', link: '/contributing/01-welcome' },
|
|
33
|
-
{ text: 'Topic 2 - The Registry', link: '/contributing/02-registry' },
|
|
34
|
-
{ text: 'Topic 3 - Batching', link: '/contributing/03-batcher' },
|
|
35
|
-
{ text: 'Topic 4 - Reactivity', link: '/contributing/04-reactivity' },
|
|
36
|
-
{ text: 'Topic 5 - Caching', link: '/contributing/05-caching' },
|
|
37
|
-
{ text: 'Topic 6 - LegoDOM Init', link: '/contributing/06-init' },
|
|
38
|
-
{ text: 'Topic 7 - Observer', link: '/contributing/07-observer' },
|
|
39
|
-
{ text: 'Topic 8 - Snap', link: '/contributing/08-snap' },
|
|
40
|
-
{ text: 'Topic 9 - Diffing', link: '/contributing/09-diffing' },
|
|
41
|
-
{ text: 'Topic 10 - State', 'link': '/contributing/10-studs' },
|
|
42
|
-
{ text: 'Topic 11 - Scanner', 'link': '/contributing/11-scanner' },
|
|
43
|
-
{ text: 'Topic 12 - Render', 'link': '/contributing/12-render' },
|
|
44
|
-
{ text: 'Topic 13 - Directives', 'link': '/contributing/13-directives' },
|
|
45
|
-
{ text: 'Topic 14 - Events', 'link': '/contributing/14-events' },
|
|
46
|
-
{ text: 'Topic 15 - Router', 'link': '/contributing/15-router' },
|
|
47
|
-
{ text: 'Topic 16 - State', 'link': '/contributing/16-state' },
|
|
48
|
-
{ text: 'Topic 17 - LegoDOM', 'link': '/contributing/17-legodom' },
|
|
49
|
-
]
|
|
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
|
-
],
|
|
65
|
-
'/guide/': [
|
|
66
|
-
{
|
|
67
|
-
text: 'Introduction',
|
|
68
|
-
items: [
|
|
69
|
-
{ text: 'What is LegoDOM?', link: '/guide/' },
|
|
70
|
-
{ text: 'Getting Started', link: '/guide/getting-started' },
|
|
71
|
-
{ text: 'Quick Start', link: '/guide/quick-start' },
|
|
72
|
-
{ text: 'Contributing', link: '/guide/contributing' }
|
|
73
|
-
]
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
text: 'Core Concepts',
|
|
77
|
-
items: [
|
|
78
|
-
{ text: 'Components', link: '/guide/components' },
|
|
79
|
-
{ text: 'Templating', link: '/guide/templating' },
|
|
80
|
-
{ text: 'Reactivity', link: '/guide/reactivity' },
|
|
81
|
-
{ text: 'Directives', link: '/guide/directives' }
|
|
82
|
-
]
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
text: 'Advanced',
|
|
86
|
-
items: [
|
|
87
|
-
{ text: 'Single File Components', link: '/guide/sfc' },
|
|
88
|
-
{ text: 'Routing', link: '/guide/routing' },
|
|
89
|
-
{ text: 'CDN Usage', link: '/guide/cdn-usage' },
|
|
90
|
-
{ text: 'Lifecycle Hooks', link: '/guide/lifecycle' },
|
|
91
|
-
{ text: 'Large Apps', link: '/guide/directory-structure' },
|
|
92
|
-
{ text: 'FAQ', link: '/guide/faq' }
|
|
93
|
-
]
|
|
94
|
-
}
|
|
95
|
-
],
|
|
96
|
-
'/api/': [
|
|
97
|
-
{
|
|
98
|
-
text: 'API Reference',
|
|
99
|
-
items: [
|
|
100
|
-
{ text: 'Overview', link: '/api/' },
|
|
101
|
-
{ text: 'Lego.define()', link: '/api/define' },
|
|
102
|
-
{ text: 'Lego.route()', link: '/api/route' },
|
|
103
|
-
{ text: 'Lego.globals', link: '/api/globals' },
|
|
104
|
-
{ text: 'Lego.config', link: '/api/config' },
|
|
105
|
-
{ text: 'Directives', link: '/api/directives' },
|
|
106
|
-
{ text: 'Lifecycle Hooks', link: '/api/lifecycle' },
|
|
107
|
-
{ text: 'Vite Plugin', link: '/api/vite-plugin' }
|
|
108
|
-
]
|
|
109
|
-
}
|
|
110
|
-
],
|
|
111
|
-
'/router/': [
|
|
112
|
-
{
|
|
113
|
-
text: 'LegoDOM Router',
|
|
114
|
-
items: [
|
|
115
|
-
{ text: 'About', link: '/router/' },
|
|
116
|
-
{ text: 'Basic Routing', link: '/router/basic-routing' },
|
|
117
|
-
{ text: 'Surgical Swaps', link: '/router/surgical-swaps' },
|
|
118
|
-
{ text: 'Cold Entry', link: '/router/cold-entry' },
|
|
119
|
-
{ text: 'History', link: '/router/history' }
|
|
120
|
-
]
|
|
121
|
-
}
|
|
122
|
-
],
|
|
123
|
-
'/examples/': [
|
|
124
|
-
{
|
|
125
|
-
text: 'Examples',
|
|
126
|
-
items: [
|
|
127
|
-
{ text: 'Overview', link: '/examples/' },
|
|
128
|
-
{ text: 'Todo App', link: '/examples/todo-app' },
|
|
129
|
-
{ text: 'Routing Demo', link: '/examples/routing' },
|
|
130
|
-
{ text: 'SFC Showcase', link: '/examples/sfc-showcase' },
|
|
131
|
-
{ text: 'Form Validation', link: '/examples/form' }
|
|
132
|
-
]
|
|
133
|
-
}
|
|
134
|
-
]
|
|
135
|
-
},
|
|
136
|
-
|
|
137
|
-
socialLinks: [
|
|
138
|
-
{ icon: 'github', link: 'https://github.com/rayattack/LegoDOM' }
|
|
139
|
-
],
|
|
140
|
-
|
|
141
|
-
footer: {
|
|
142
|
-
message: 'Released under the MIT License.',
|
|
143
|
-
copyright: 'Copyright © 2024-present'
|
|
144
|
-
},
|
|
145
|
-
|
|
146
|
-
search: {
|
|
147
|
-
provider: 'local'
|
|
148
|
-
},
|
|
149
|
-
|
|
150
|
-
editLink: {
|
|
151
|
-
pattern: 'https://github.com/rayattack/LegoDOM/edit/main/docs/:path',
|
|
152
|
-
text: 'Edit this page on GitHub'
|
|
153
|
-
}
|
|
154
|
-
},
|
|
155
|
-
|
|
156
|
-
markdown: {
|
|
157
|
-
theme: {
|
|
158
|
-
light: 'github-light',
|
|
159
|
-
dark: 'github-dark'
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
});
|
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
|
-
```
|