lego-dom 0.0.7 → 0.0.8
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/.github/workflows/deploy-docs.yml +56 -0
- package/LICENSE +21 -0
- package/README.md +122 -0
- package/docs/.vitepress/config.js +107 -0
- package/docs/.vitepress/dist/404.html +22 -0
- package/docs/.vitepress/dist/api/define.html +35 -0
- package/docs/.vitepress/dist/api/directives.html +32 -0
- package/docs/.vitepress/dist/api/globals.html +27 -0
- package/docs/.vitepress/dist/api/index.html +25 -0
- package/docs/.vitepress/dist/api/lifecycle.html +38 -0
- package/docs/.vitepress/dist/api/route.html +34 -0
- package/docs/.vitepress/dist/api/vite-plugin.html +37 -0
- package/docs/.vitepress/dist/assets/api_define.md.UA-ygUnQ.js +11 -0
- package/docs/.vitepress/dist/assets/api_define.md.UA-ygUnQ.lean.js +1 -0
- package/docs/.vitepress/dist/assets/api_directives.md.BV-D251p.js +8 -0
- package/docs/.vitepress/dist/assets/api_directives.md.BV-D251p.lean.js +1 -0
- package/docs/.vitepress/dist/assets/api_globals.md.DOjt7AV0.js +3 -0
- package/docs/.vitepress/dist/assets/api_globals.md.DOjt7AV0.lean.js +1 -0
- package/docs/.vitepress/dist/assets/api_index.md.OS6h01ct.js +1 -0
- package/docs/.vitepress/dist/assets/api_index.md.OS6h01ct.lean.js +1 -0
- package/docs/.vitepress/dist/assets/api_lifecycle.md.Ccm5xw6-.js +14 -0
- package/docs/.vitepress/dist/assets/api_lifecycle.md.Ccm5xw6-.lean.js +1 -0
- package/docs/.vitepress/dist/assets/api_route.md.CAHf_KNp.js +10 -0
- package/docs/.vitepress/dist/assets/api_route.md.CAHf_KNp.lean.js +1 -0
- package/docs/.vitepress/dist/assets/api_vite-plugin.md.DNn9VhL5.js +13 -0
- package/docs/.vitepress/dist/assets/api_vite-plugin.md.DNn9VhL5.lean.js +1 -0
- package/docs/.vitepress/dist/assets/app.BG5s3B0P.js +1 -0
- package/docs/.vitepress/dist/assets/chunks/@localSearchIndexroot.DQmuWC2Z.js +1 -0
- package/docs/.vitepress/dist/assets/chunks/VPLocalSearchBox.BO-PSxt1.js +9 -0
- package/docs/.vitepress/dist/assets/chunks/framework.B7OFBR9X.js +19 -0
- package/docs/.vitepress/dist/assets/chunks/theme.DA-iSa9B.js +2 -0
- package/docs/.vitepress/dist/assets/examples_form.md.B3stGKbu.js +34 -0
- package/docs/.vitepress/dist/assets/examples_form.md.B3stGKbu.lean.js +1 -0
- package/docs/.vitepress/dist/assets/examples_index.md.BDEG_D4J.js +30 -0
- package/docs/.vitepress/dist/assets/examples_index.md.BDEG_D4J.lean.js +1 -0
- package/docs/.vitepress/dist/assets/examples_routing.md.bqZ9DjDK.js +338 -0
- package/docs/.vitepress/dist/assets/examples_routing.md.bqZ9DjDK.lean.js +1 -0
- package/docs/.vitepress/dist/assets/examples_sfc-showcase.md.DLXaUiop.js +13 -0
- package/docs/.vitepress/dist/assets/examples_sfc-showcase.md.DLXaUiop.lean.js +1 -0
- package/docs/.vitepress/dist/assets/examples_todo-app.md.D5RhZoo5.js +297 -0
- package/docs/.vitepress/dist/assets/examples_todo-app.md.D5RhZoo5.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_cdn-usage.md.CAjf03Lr.js +182 -0
- package/docs/.vitepress/dist/assets/guide_cdn-usage.md.CAjf03Lr.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_components.md.BIFWF1Hc.js +174 -0
- package/docs/.vitepress/dist/assets/guide_components.md.BIFWF1Hc.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_contributing.md.BgbUN-Mr.js +1 -0
- package/docs/.vitepress/dist/assets/guide_contributing.md.BgbUN-Mr.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_directives.md.Bi3ynu1d.js +140 -0
- package/docs/.vitepress/dist/assets/guide_directives.md.Bi3ynu1d.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_getting-started.md.2Nr1lp2z.js +107 -0
- package/docs/.vitepress/dist/assets/guide_getting-started.md.2Nr1lp2z.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_index.md.GvZq_Yf2.js +2 -0
- package/docs/.vitepress/dist/assets/guide_index.md.GvZq_Yf2.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_lifecycle.md.B28j1OzS.js +304 -0
- package/docs/.vitepress/dist/assets/guide_lifecycle.md.B28j1OzS.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_quick-start.md.CNk3VGTF.js +33 -0
- package/docs/.vitepress/dist/assets/guide_quick-start.md.CNk3VGTF.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_reactivity.md.CVsaMaPv.js +135 -0
- package/docs/.vitepress/dist/assets/guide_reactivity.md.CVsaMaPv.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_routing.md.DSpDP25o.js +193 -0
- package/docs/.vitepress/dist/assets/guide_routing.md.DSpDP25o.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_sfc.md.CVUP66tS.js +187 -0
- package/docs/.vitepress/dist/assets/guide_sfc.md.CVUP66tS.lean.js +1 -0
- package/docs/.vitepress/dist/assets/guide_templating.md.BgCGe4aa.js +119 -0
- package/docs/.vitepress/dist/assets/guide_templating.md.BgCGe4aa.lean.js +1 -0
- package/docs/.vitepress/dist/assets/index.md.xV1taCED.js +23 -0
- package/docs/.vitepress/dist/assets/index.md.xV1taCED.lean.js +1 -0
- package/docs/.vitepress/dist/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
- package/docs/.vitepress/dist/assets/style.eycE2Jhw.css +1 -0
- package/docs/.vitepress/dist/examples/form.html +58 -0
- package/docs/.vitepress/dist/examples/index.html +368 -0
- package/docs/.vitepress/dist/examples/routing.html +362 -0
- package/docs/.vitepress/dist/examples/sfc-showcase.html +37 -0
- package/docs/.vitepress/dist/examples/todo-app.html +321 -0
- package/docs/.vitepress/dist/guide/cdn-usage.html +206 -0
- package/docs/.vitepress/dist/guide/components.html +198 -0
- package/docs/.vitepress/dist/guide/contributing.html +25 -0
- package/docs/.vitepress/dist/guide/directives.html +164 -0
- package/docs/.vitepress/dist/guide/getting-started.html +131 -0
- package/docs/.vitepress/dist/guide/index.html +26 -0
- package/docs/.vitepress/dist/guide/lifecycle.html +328 -0
- package/docs/.vitepress/dist/guide/quick-start.html +57 -0
- package/docs/.vitepress/dist/guide/reactivity.html +159 -0
- package/docs/.vitepress/dist/guide/routing.html +217 -0
- package/docs/.vitepress/dist/guide/sfc.html +211 -0
- package/docs/.vitepress/dist/guide/templating.html +143 -0
- package/docs/.vitepress/dist/hashmap.json +1 -0
- package/docs/.vitepress/dist/index.html +47 -0
- package/docs/.vitepress/dist/logo.svg +38 -0
- package/docs/.vitepress/dist/vp-icons.css +1 -0
- package/docs/api/define.md +31 -0
- package/docs/api/directives.md +42 -0
- package/docs/api/globals.md +29 -0
- package/docs/api/index.md +29 -0
- package/docs/api/lifecycle.md +40 -0
- package/docs/api/route.md +37 -0
- package/docs/api/vite-plugin.md +58 -0
- package/docs/examples/form.md +42 -0
- package/docs/examples/index.md +104 -0
- package/docs/examples/routing.md +409 -0
- package/docs/examples/sfc-showcase.md +34 -0
- package/docs/examples/todo-app.md +383 -0
- package/docs/guide/cdn-usage.md +320 -0
- package/docs/guide/components.md +394 -0
- package/docs/guide/contributing.md +32 -0
- package/docs/guide/directives.md +430 -0
- package/docs/guide/getting-started.md +233 -0
- package/docs/guide/index.md +88 -0
- package/docs/guide/lifecycle.md +493 -0
- package/docs/guide/quick-start.md +46 -0
- package/docs/guide/reactivity.md +394 -0
- package/docs/guide/routing.md +373 -0
- package/docs/guide/sfc.md +381 -0
- package/docs/guide/templating.md +383 -0
- package/docs/index.md +126 -0
- package/docs/public/logo.svg +38 -0
- package/examples/vite-app/README.md +71 -0
- package/examples/vite-app/index.html +45 -0
- package/examples/vite-app/package.json +16 -0
- package/examples/vite-app/src/components/greeting-card.lego +41 -0
- package/examples/vite-app/src/components/sample-component.lego +75 -0
- package/examples/vite-app/src/main.js +11 -0
- package/examples/vite-app/vite.config.js +16 -0
- package/examples.js +99 -0
- package/package.json +33 -5
- package/parse-lego.js +119 -0
- package/parse-lego.test.js +107 -0
- package/vite-plugin.js +133 -0
- package/.ignore/auto.html +0 -135
- package/.ignore/test.html +0 -73
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<style>
|
|
3
|
+
self {
|
|
4
|
+
display: block;
|
|
5
|
+
padding: 1rem;
|
|
6
|
+
background: #f3f4f6;
|
|
7
|
+
border-radius: 0.5rem;
|
|
8
|
+
margin: 0.5rem 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
input {
|
|
12
|
+
padding: 0.5rem;
|
|
13
|
+
border: 1px solid #d1d5db;
|
|
14
|
+
border-radius: 0.25rem;
|
|
15
|
+
margin-right: 0.5rem;
|
|
16
|
+
font-size: 1rem;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.greeting {
|
|
20
|
+
margin-top: 1rem;
|
|
21
|
+
padding: 1rem;
|
|
22
|
+
background: white;
|
|
23
|
+
border-radius: 0.25rem;
|
|
24
|
+
font-size: 1.25rem;
|
|
25
|
+
color: #1f2937;
|
|
26
|
+
}
|
|
27
|
+
</style>
|
|
28
|
+
|
|
29
|
+
<div>
|
|
30
|
+
<input b-sync="name" type="text" placeholder="Enter your name">
|
|
31
|
+
<div class="greeting" b-if="name">
|
|
32
|
+
Hello, {{ name }}! 👋
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</template>
|
|
36
|
+
|
|
37
|
+
<script>
|
|
38
|
+
export default {
|
|
39
|
+
name: ''
|
|
40
|
+
}
|
|
41
|
+
</script>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<style>
|
|
3
|
+
self {
|
|
4
|
+
display: block;
|
|
5
|
+
padding: 1.5rem;
|
|
6
|
+
margin: 1rem 0;
|
|
7
|
+
border: 2px solid #4f46e5;
|
|
8
|
+
border-radius: 0.5rem;
|
|
9
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
10
|
+
color: white;
|
|
11
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
12
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.header {
|
|
16
|
+
font-size: 1.5rem;
|
|
17
|
+
font-weight: bold;
|
|
18
|
+
margin-bottom: 0.5rem;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.content {
|
|
22
|
+
margin: 1rem 0;
|
|
23
|
+
line-height: 1.6;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
button {
|
|
27
|
+
background: white;
|
|
28
|
+
color: #4f46e5;
|
|
29
|
+
border: none;
|
|
30
|
+
padding: 0.5rem 1rem;
|
|
31
|
+
border-radius: 0.25rem;
|
|
32
|
+
font-weight: 600;
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
transition: transform 0.2s;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
button:hover {
|
|
38
|
+
transform: scale(1.05);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.count {
|
|
42
|
+
display: inline-block;
|
|
43
|
+
background: rgba(255, 255, 255, 0.2);
|
|
44
|
+
padding: 0.25rem 0.75rem;
|
|
45
|
+
border-radius: 0.25rem;
|
|
46
|
+
margin-left: 0.5rem;
|
|
47
|
+
}
|
|
48
|
+
</style>
|
|
49
|
+
|
|
50
|
+
<div class="header">{{ title }}</div>
|
|
51
|
+
<div class="content">
|
|
52
|
+
<p>This is a sample LegoJS component loaded from a .lego file!</p>
|
|
53
|
+
<p>Message: {{ message }}</p>
|
|
54
|
+
</div>
|
|
55
|
+
<button @click="incrementCount()">
|
|
56
|
+
Click me!
|
|
57
|
+
<span class="count">{{ count }}</span>
|
|
58
|
+
</button>
|
|
59
|
+
</template>
|
|
60
|
+
|
|
61
|
+
<script>
|
|
62
|
+
export default {
|
|
63
|
+
title: 'Sample Component',
|
|
64
|
+
message: 'Hello from .lego file!',
|
|
65
|
+
count: 0,
|
|
66
|
+
|
|
67
|
+
incrementCount() {
|
|
68
|
+
this.count++;
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
mounted() {
|
|
72
|
+
console.log('Sample component mounted!');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
</script>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Import LegoJS core
|
|
2
|
+
import { Lego } from 'lego-dom/main.js';
|
|
3
|
+
|
|
4
|
+
// Import virtual module that auto-discovers and registers all .lego components
|
|
5
|
+
import registerComponents from 'virtual:lego-components';
|
|
6
|
+
|
|
7
|
+
// Register all auto-discovered components
|
|
8
|
+
registerComponents();
|
|
9
|
+
|
|
10
|
+
// Initialize LegoJS
|
|
11
|
+
console.log('LegoJS initialized with auto-discovered components!');
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import legoPlugin from 'lego-dom/vite-plugin';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [
|
|
6
|
+
legoPlugin({
|
|
7
|
+
componentsDir: './src/components',
|
|
8
|
+
include: ['**/*.lego']
|
|
9
|
+
})
|
|
10
|
+
],
|
|
11
|
+
resolve: {
|
|
12
|
+
alias: {
|
|
13
|
+
'lego-dom/main.js': new URL('../../main.js', import.meta.url).pathname
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
});
|
package/examples.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export const examples = {
|
|
2
|
+
counter: `<!DOCTYPE html>
|
|
3
|
+
<html>
|
|
4
|
+
<head>
|
|
5
|
+
<title>LegoJS Counter</title>
|
|
6
|
+
</head>
|
|
7
|
+
<body>
|
|
8
|
+
<template b-id="click-counter">
|
|
9
|
+
<style>
|
|
10
|
+
button { font-size: 1.2rem; padding: 0.5rem 1rem; }
|
|
11
|
+
</style>
|
|
12
|
+
<p>Count: {{ count }}</p>
|
|
13
|
+
<button @click="count++">Increment</button>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<click-counter b-data="{ count: 0 }"></click-counter>
|
|
17
|
+
|
|
18
|
+
<script src="https://unpkg.com/lego-dom/main.js"></script>
|
|
19
|
+
</body>
|
|
20
|
+
</html>`,
|
|
21
|
+
|
|
22
|
+
todo: `<!DOCTYPE html>
|
|
23
|
+
<html lang="en">
|
|
24
|
+
<head>
|
|
25
|
+
<meta charset="UTF-8">
|
|
26
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
27
|
+
<title>Todo App - LegoJS</title>
|
|
28
|
+
<style>
|
|
29
|
+
body {
|
|
30
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
31
|
+
max-width: 600px;
|
|
32
|
+
margin: 2rem auto;
|
|
33
|
+
padding: 0 1rem;
|
|
34
|
+
background: #f5f5f5;
|
|
35
|
+
}
|
|
36
|
+
h1 {
|
|
37
|
+
text-align: center;
|
|
38
|
+
color: #333;
|
|
39
|
+
}
|
|
40
|
+
</style>
|
|
41
|
+
</head>
|
|
42
|
+
<body>
|
|
43
|
+
|
|
44
|
+
<script src="https://unpkg.com/lego-dom/main.js"></script>
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
<script>
|
|
49
|
+
</script>
|
|
50
|
+
</body>
|
|
51
|
+
</html>`,
|
|
52
|
+
|
|
53
|
+
form: `<!DOCTYPE html>
|
|
54
|
+
<html>
|
|
55
|
+
<head>
|
|
56
|
+
<title>LegoJS Form</title>
|
|
57
|
+
</head>
|
|
58
|
+
<body>
|
|
59
|
+
<template b-id="login-form">
|
|
60
|
+
<form @submit.prevent="login()">
|
|
61
|
+
<div>
|
|
62
|
+
<label>Email:</label>
|
|
63
|
+
<input type="email" b-sync="email">
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div>
|
|
67
|
+
<label>Password:</label>
|
|
68
|
+
<input type="password" b-sync="password">
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<p b-if="error" style="color: red">{{ error }}</p>
|
|
72
|
+
|
|
73
|
+
<button type="submit">Login</button>
|
|
74
|
+
</form>
|
|
75
|
+
</template>
|
|
76
|
+
|
|
77
|
+
<login-form></login-form>
|
|
78
|
+
|
|
79
|
+
<script src="https://unpkg.com/lego-dom/main.js"></script>
|
|
80
|
+
|
|
81
|
+
<script>
|
|
82
|
+
Lego.define('login-form', document.querySelector('template[b-id="login-form"]').innerHTML, {
|
|
83
|
+
email: '',
|
|
84
|
+
password: '',
|
|
85
|
+
error: '',
|
|
86
|
+
|
|
87
|
+
login() {
|
|
88
|
+
if (!this.email || !this.password) {
|
|
89
|
+
this.error = 'Please fill in all fields';
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
alert(\`Logging in as \${this.email}\`);
|
|
93
|
+
this.error = '';
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
</script>
|
|
97
|
+
</body>
|
|
98
|
+
</html>`
|
|
99
|
+
};
|
package/package.json
CHANGED
|
@@ -1,16 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lego-dom",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
|
+
"license": "MIT",
|
|
4
5
|
"description": "A feature-rich web components + SFC frontend framework",
|
|
5
6
|
"main": "main.js",
|
|
6
7
|
"type": "module",
|
|
7
|
-
"
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./main.js",
|
|
10
|
+
"./main.js": "./main.js",
|
|
11
|
+
"./vite-plugin": "./vite-plugin.js",
|
|
12
|
+
"./parse-lego": "./parse-lego.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"framework",
|
|
16
|
+
"sfc",
|
|
17
|
+
"components",
|
|
18
|
+
"lego",
|
|
19
|
+
"legokit"
|
|
20
|
+
],
|
|
8
21
|
"author": "",
|
|
9
22
|
"scripts": {
|
|
10
|
-
"test": "vitest run"
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"docs:dev": "vitepress dev docs",
|
|
25
|
+
"docs:build": "vitepress build docs",
|
|
26
|
+
"docs:preview": "vitepress preview docs"
|
|
11
27
|
},
|
|
12
28
|
"devDependencies": {
|
|
13
|
-
"
|
|
14
|
-
"
|
|
29
|
+
"jsdom": "^22.0.0",
|
|
30
|
+
"vitepress": "^1.6.4",
|
|
31
|
+
"vitest": "^1.0.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"vite": "^4.0.0 || ^5.0.0"
|
|
35
|
+
},
|
|
36
|
+
"peerDependenciesMeta": {
|
|
37
|
+
"vite": {
|
|
38
|
+
"optional": true
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"fast-glob": "^3.3.2"
|
|
15
43
|
}
|
|
16
44
|
}
|
package/parse-lego.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser for .lego Single File Components
|
|
3
|
+
* Extracts template, script, and style sections from .lego files
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Parse a .lego file content into structured sections
|
|
8
|
+
* @param {string} content - Raw .lego file content
|
|
9
|
+
* @param {string} filename - Filename for error reporting
|
|
10
|
+
* @returns {{template: string, script: string, style: string, componentName: string}}
|
|
11
|
+
*/
|
|
12
|
+
export function parseLego(content, filename = 'component.lego') {
|
|
13
|
+
const result = {
|
|
14
|
+
template: '',
|
|
15
|
+
script: '',
|
|
16
|
+
style: '',
|
|
17
|
+
componentName: deriveComponentName(filename)
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Extract template section
|
|
21
|
+
const templateMatch = content.match(/<template>([\s\S]*?)<\/template>/);
|
|
22
|
+
if (templateMatch) {
|
|
23
|
+
result.template = templateMatch[1].trim();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Extract script section
|
|
27
|
+
const scriptMatch = content.match(/<script>([\s\S]*?)<\/script>/);
|
|
28
|
+
if (scriptMatch) {
|
|
29
|
+
result.script = scriptMatch[1].trim();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Extract style section
|
|
33
|
+
const styleMatch = content.match(/<style>([\s\S]*?)<\/style>/);
|
|
34
|
+
if (styleMatch) {
|
|
35
|
+
result.style = styleMatch[1].trim();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Derive component name from filename
|
|
43
|
+
* e.g., "sample-component.lego" -> "sample-component"
|
|
44
|
+
* @param {string} filename
|
|
45
|
+
* @returns {string}
|
|
46
|
+
*/
|
|
47
|
+
export function deriveComponentName(filename) {
|
|
48
|
+
const basename = filename.split('/').pop();
|
|
49
|
+
return basename.replace(/\.lego$/, '');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Generate Lego.define() code from parsed .lego file
|
|
54
|
+
* @param {object} parsed - Parsed .lego file object
|
|
55
|
+
* @returns {string} - JavaScript code string
|
|
56
|
+
*/
|
|
57
|
+
export function generateDefineCall(parsed) {
|
|
58
|
+
const { componentName, template, script, style } = parsed;
|
|
59
|
+
|
|
60
|
+
// Build template HTML
|
|
61
|
+
let templateHTML = '';
|
|
62
|
+
if (style) {
|
|
63
|
+
templateHTML += `<style>${style}</style>\n`;
|
|
64
|
+
}
|
|
65
|
+
if (template) {
|
|
66
|
+
templateHTML += template;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Extract logic object from script
|
|
70
|
+
let logicCode = '{}';
|
|
71
|
+
if (script) {
|
|
72
|
+
// Try to extract default export
|
|
73
|
+
const defaultExportMatch = script.match(/export\s+default\s+({[\s\S]*})/);
|
|
74
|
+
if (defaultExportMatch) {
|
|
75
|
+
logicCode = defaultExportMatch[1];
|
|
76
|
+
} else {
|
|
77
|
+
// If no export default, assume entire script is the logic object
|
|
78
|
+
logicCode = script;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Generate the Lego.define call
|
|
83
|
+
return `Lego.define('${componentName}', \`${escapeTemplate(templateHTML)}\`, ${logicCode});`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Escape backticks and ${} in template strings
|
|
88
|
+
* @param {string} str
|
|
89
|
+
* @returns {string}
|
|
90
|
+
*/
|
|
91
|
+
function escapeTemplate(str) {
|
|
92
|
+
return str.replace(/`/g, '\\`').replace(/\$/g, '\\$');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Validate .lego file structure
|
|
97
|
+
* @param {object} parsed - Parsed .lego file object
|
|
98
|
+
* @returns {{valid: boolean, errors: string[]}}
|
|
99
|
+
*/
|
|
100
|
+
export function validateLego(parsed) {
|
|
101
|
+
const errors = [];
|
|
102
|
+
|
|
103
|
+
if (!parsed.template && !parsed.script && !parsed.style) {
|
|
104
|
+
errors.push('Component must have at least one section: <template>, <script>, or <style>');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!parsed.componentName) {
|
|
108
|
+
errors.push('Unable to derive component name from filename');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (parsed.componentName && !/^[a-z][a-z0-9]*(-[a-z0-9]+)+$/.test(parsed.componentName)) {
|
|
112
|
+
errors.push(`Component name "${parsed.componentName}" must be kebab-case with at least one hyphen (e.g., "my-component")`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
valid: errors.length === 0,
|
|
117
|
+
errors
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { parseLego, generateDefineCall, validateLego, deriveComponentName } from './parse-lego.js';
|
|
3
|
+
|
|
4
|
+
describe('LegoJS SFC Parser', () => {
|
|
5
|
+
describe('deriveComponentName', () => {
|
|
6
|
+
it('should derive component name from filename', () => {
|
|
7
|
+
expect(deriveComponentName('sample-component.lego')).toBe('sample-component');
|
|
8
|
+
expect(deriveComponentName('path/to/my-button.lego')).toBe('my-button');
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe('parseLego', () => {
|
|
13
|
+
it('should parse all three sections', () => {
|
|
14
|
+
const content = `
|
|
15
|
+
<template>
|
|
16
|
+
<h1>{{ title }}</h1>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script>
|
|
20
|
+
export default {
|
|
21
|
+
title: 'Hello'
|
|
22
|
+
}
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<style>
|
|
26
|
+
self { color: red; }
|
|
27
|
+
</style>
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
const result = parseLego(content, 'test-component.lego');
|
|
31
|
+
expect(result.componentName).toBe('test-component');
|
|
32
|
+
expect(result.template).toContain('<h1>{{ title }}</h1>');
|
|
33
|
+
expect(result.script).toContain('export default');
|
|
34
|
+
expect(result.style).toContain('self { color: red; }');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should handle components with only template', () => {
|
|
38
|
+
const content = '<template><p>Hello</p></template>';
|
|
39
|
+
const result = parseLego(content, 'simple.lego');
|
|
40
|
+
expect(result.template).toBe('<p>Hello</p>');
|
|
41
|
+
expect(result.script).toBe('');
|
|
42
|
+
expect(result.style).toBe('');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should handle components with only script', () => {
|
|
46
|
+
const content = '<script>export default { count: 0 }</script>';
|
|
47
|
+
const result = parseLego(content, 'logic.lego');
|
|
48
|
+
expect(result.script).toContain('count: 0');
|
|
49
|
+
expect(result.template).toBe('');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('validateLego', () => {
|
|
54
|
+
it('should validate correct component name', () => {
|
|
55
|
+
const parsed = {
|
|
56
|
+
componentName: 'my-component',
|
|
57
|
+
template: '<div>Test</div>',
|
|
58
|
+
script: '',
|
|
59
|
+
style: ''
|
|
60
|
+
};
|
|
61
|
+
const result = validateLego(parsed);
|
|
62
|
+
expect(result.valid).toBe(true);
|
|
63
|
+
expect(result.errors).toHaveLength(0);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should reject invalid component names', () => {
|
|
67
|
+
const parsed = {
|
|
68
|
+
componentName: 'MyComponent', // Not kebab-case
|
|
69
|
+
template: '<div>Test</div>',
|
|
70
|
+
script: '',
|
|
71
|
+
style: ''
|
|
72
|
+
};
|
|
73
|
+
const result = validateLego(parsed);
|
|
74
|
+
expect(result.valid).toBe(false);
|
|
75
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should require at least one section', () => {
|
|
79
|
+
const parsed = {
|
|
80
|
+
componentName: 'empty-component',
|
|
81
|
+
template: '',
|
|
82
|
+
script: '',
|
|
83
|
+
style: ''
|
|
84
|
+
};
|
|
85
|
+
const result = validateLego(parsed);
|
|
86
|
+
expect(result.valid).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('generateDefineCall', () => {
|
|
91
|
+
it('should generate valid Lego.define call', () => {
|
|
92
|
+
const parsed = {
|
|
93
|
+
componentName: 'test-comp',
|
|
94
|
+
template: '<button>Click</button>',
|
|
95
|
+
script: 'export default { count: 0 }',
|
|
96
|
+
style: 'self { color: blue; }'
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const result = generateDefineCall(parsed);
|
|
100
|
+
expect(result).toContain('Lego.define');
|
|
101
|
+
expect(result).toContain('test-comp');
|
|
102
|
+
expect(result).toContain('<button>Click</button>');
|
|
103
|
+
expect(result).toContain('{ count: 0 }');
|
|
104
|
+
expect(result).toContain('self { color: blue; }');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
package/vite-plugin.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite plugin for LegoJS Single File Components
|
|
3
|
+
* Auto-discovers and transforms .lego files
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { parseLego, generateDefineCall, validateLego } from './parse-lego.js';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import fg from 'fast-glob';
|
|
9
|
+
|
|
10
|
+
const VIRTUAL_MODULE_ID = 'virtual:lego-components';
|
|
11
|
+
const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Vite plugin for LegoJS SFC support
|
|
15
|
+
* @param {object} options - Plugin options
|
|
16
|
+
* @param {string} options.componentsDir - Directory to search for .lego files
|
|
17
|
+
* @param {string[]} options.include - Glob patterns to include
|
|
18
|
+
* @returns {import('vite').Plugin}
|
|
19
|
+
*/
|
|
20
|
+
export default function legoPlugin(options = {}) {
|
|
21
|
+
const {
|
|
22
|
+
componentsDir = './src/components',
|
|
23
|
+
include = ['**/*.lego']
|
|
24
|
+
} = options;
|
|
25
|
+
|
|
26
|
+
let config;
|
|
27
|
+
let legoFiles = [];
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
name: 'vite-plugin-lego',
|
|
31
|
+
|
|
32
|
+
configResolved(resolvedConfig) {
|
|
33
|
+
config = resolvedConfig;
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
async buildStart() {
|
|
37
|
+
// Auto-discover .lego files
|
|
38
|
+
const root = config?.root || process.cwd();
|
|
39
|
+
const searchPath = path.resolve(root, componentsDir);
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
legoFiles = await fg(include, {
|
|
43
|
+
cwd: searchPath,
|
|
44
|
+
absolute: true
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (legoFiles.length > 0) {
|
|
48
|
+
console.log(`[vite-plugin-lego] Discovered ${legoFiles.length} component(s):`);
|
|
49
|
+
legoFiles.forEach(file => {
|
|
50
|
+
const name = path.basename(file);
|
|
51
|
+
console.log(` - ${name}`);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
} catch (err) {
|
|
55
|
+
console.warn(`[vite-plugin-lego] Could not scan for .lego files in ${searchPath}:`, err.message);
|
|
56
|
+
legoFiles = [];
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
resolveId(id) {
|
|
61
|
+
if (id === VIRTUAL_MODULE_ID) {
|
|
62
|
+
return RESOLVED_VIRTUAL_MODULE_ID;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
async load(id) {
|
|
67
|
+
// Handle virtual module that imports all .lego components
|
|
68
|
+
if (id === RESOLVED_VIRTUAL_MODULE_ID) {
|
|
69
|
+
const imports = legoFiles.map((file, index) =>
|
|
70
|
+
`import component${index} from '${file}?lego-component';`
|
|
71
|
+
).join('\n');
|
|
72
|
+
|
|
73
|
+
const exports = `export default function registerComponents() {\n // Components are auto-registered when imported\n}`;
|
|
74
|
+
|
|
75
|
+
return `${imports}\n\n${exports}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Handle individual .lego files
|
|
79
|
+
if (id.endsWith('.lego') || id.includes('.lego?')) {
|
|
80
|
+
const filePath = id.split('?')[0];
|
|
81
|
+
const fs = await import('fs');
|
|
82
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
83
|
+
const filename = path.basename(filePath);
|
|
84
|
+
|
|
85
|
+
const parsed = parseLego(content, filename);
|
|
86
|
+
const validation = validateLego(parsed);
|
|
87
|
+
|
|
88
|
+
if (!validation.valid) {
|
|
89
|
+
throw new Error(`Invalid .lego file "${filename}":\n${validation.errors.join('\n')}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const defineCall = generateDefineCall(parsed);
|
|
93
|
+
|
|
94
|
+
// Return as module that executes the define call
|
|
95
|
+
return `
|
|
96
|
+
import { Lego } from 'lego-dom/main.js';
|
|
97
|
+
|
|
98
|
+
${defineCall}
|
|
99
|
+
|
|
100
|
+
export default '${parsed.componentName}';
|
|
101
|
+
`;
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
handleHotUpdate({ file, server }) {
|
|
106
|
+
if (file.endsWith('.lego')) {
|
|
107
|
+
console.log(`[vite-plugin-lego] Hot reload: ${path.basename(file)}`);
|
|
108
|
+
// Trigger full reload for .lego files
|
|
109
|
+
server.ws.send({
|
|
110
|
+
type: 'full-reload',
|
|
111
|
+
path: '*'
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
transform(code, id) {
|
|
117
|
+
// Transform .lego files during build
|
|
118
|
+
if (id.endsWith('.lego') && !id.includes('?')) {
|
|
119
|
+
const parsed = parseLego(code, path.basename(id));
|
|
120
|
+
const validation = validateLego(parsed);
|
|
121
|
+
|
|
122
|
+
if (!validation.valid) {
|
|
123
|
+
throw new Error(`Invalid .lego file:\n${validation.errors.join('\n')}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
code: generateDefineCall(parsed),
|
|
128
|
+
map: null
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|