tkeron 4.1.0 → 4.3.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/README.md +2 -0
- package/bun.lock +5 -5
- package/changelog.md +59 -0
- package/docs/components-markdown.md +322 -0
- package/docs/components-typescript.md +40 -0
- package/docs/getting-started.md +2 -1
- package/docs/pre-rendering.md +20 -0
- package/examples/init_sample/tkeron.d.ts +13 -0
- package/examples/init_sample/websrc/index.ts +6 -6
- package/examples/init_sample/websrc/user-badge.com.ts +4 -3
- package/mcp-server.ts +6 -0
- package/package.json +1 -1
- package/src/build.ts +4 -2
- package/src/processComMd.ts +197 -0
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ bun install -g tkeron
|
|
|
19
19
|
- TypeScript → vanilla JavaScript
|
|
20
20
|
- HTML components (`.com.html`) - inline at build time
|
|
21
21
|
- TypeScript components (`.com.ts`) - dynamic generation
|
|
22
|
+
- Markdown components (`.com.md`) - write content in Markdown, rendered to HTML at build time
|
|
22
23
|
- Pre-rendering (`.pre.ts`) - DOM manipulation at build time
|
|
23
24
|
- Dev server with hot reload
|
|
24
25
|
- Zero config
|
|
@@ -72,6 +73,7 @@ document.getElementById("quote").textContent = data.content;
|
|
|
72
73
|
- **[Getting Started](./docs/getting-started.md)** - Installation and first steps
|
|
73
74
|
- **[HTML Components](./docs/components-html.md)** - Reusable markup with `.com.html`
|
|
74
75
|
- **[TypeScript Components](./docs/components-typescript.md)** - Dynamic components with `.com.ts`
|
|
76
|
+
- **[Markdown Components](./docs/components-markdown.md)** - Content in Markdown with `.com.md`
|
|
75
77
|
- **[Pre-rendering](./docs/pre-rendering.md)** - Build-time HTML transformation
|
|
76
78
|
- **[CLI Reference](./docs/cli-reference.md)** - All commands and options
|
|
77
79
|
- **[Best Practices](./docs/best-practices.md)** - Patterns, limits, and anti-patterns
|
package/bun.lock
CHANGED
|
@@ -30,11 +30,11 @@
|
|
|
30
30
|
|
|
31
31
|
"@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
|
|
32
32
|
|
|
33
|
-
"@types/node": ["@types/node@25.
|
|
33
|
+
"@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="],
|
|
34
34
|
|
|
35
35
|
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
|
36
36
|
|
|
37
|
-
"ajv": ["ajv@8.
|
|
37
|
+
"ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
|
|
38
38
|
|
|
39
39
|
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
|
|
40
40
|
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
|
|
111
111
|
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
|
112
112
|
|
|
113
|
-
"hono": ["hono@4.
|
|
113
|
+
"hono": ["hono@4.12.1", "", {}, "sha512-hi9afu8g0lfJVLolxElAZGANCTTl6bewIdsRNhaywfP9K8BPf++F2z6OLrYGIinUwpRKzbZHMhPwvc0ZEpAwGw=="],
|
|
114
114
|
|
|
115
115
|
"http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
|
|
116
116
|
|
|
@@ -166,7 +166,7 @@
|
|
|
166
166
|
|
|
167
167
|
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
|
|
168
168
|
|
|
169
|
-
"qs": ["qs@6.
|
|
169
|
+
"qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="],
|
|
170
170
|
|
|
171
171
|
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
|
|
172
172
|
|
|
@@ -204,7 +204,7 @@
|
|
|
204
204
|
|
|
205
205
|
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
206
206
|
|
|
207
|
-
"undici-types": ["undici-types@7.
|
|
207
|
+
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
|
208
208
|
|
|
209
209
|
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
|
|
210
210
|
|
package/changelog.md
CHANGED
|
@@ -1,3 +1,62 @@
|
|
|
1
|
+
# v4.3.0
|
|
2
|
+
|
|
3
|
+
## Markdown Components
|
|
4
|
+
|
|
5
|
+
### New Features
|
|
6
|
+
|
|
7
|
+
- **Added**: `.com.md` component type — write components in Markdown, rendered to HTML at build time using Bun's native `Bun.markdown.html()` API with full GFM support (headings, bold, italic, strikethrough, lists, tables, blockquotes, links, images, code)
|
|
8
|
+
- **Added**: `src/processComMd.ts` — new processor following the same pattern as `processCom` and `processComTs`
|
|
9
|
+
- Circular dependency detection
|
|
10
|
+
- Max nesting depth (50 levels)
|
|
11
|
+
- Component resolution: local directory first, then root
|
|
12
|
+
- Returns `true` when changes were made (drives the build loop)
|
|
13
|
+
|
|
14
|
+
### Breaking Changes (Build Behavior)
|
|
15
|
+
|
|
16
|
+
- **Changed**: Component resolution priority is now enforced: `.com.ts` > `.com.html` > `.com.md`
|
|
17
|
+
- Previously the loop ran `processCom` (HTML) before `processComTs` (TS), giving `.com.html` effective priority over `.com.ts` when both existed for the same tag
|
|
18
|
+
- Now `.com.ts` is resolved first as intended
|
|
19
|
+
- **Impact**: If you had both a `.com.ts` and `.com.html` for the same component name and were relying on the `.com.html` being used, the `.com.ts` will now win
|
|
20
|
+
|
|
21
|
+
### Documentation
|
|
22
|
+
|
|
23
|
+
- **Added**: `docs/components-markdown.md` — full documentation for `.com.md` components including GFM features, naming rules, priority rules, and examples
|
|
24
|
+
- **Updated**: `README.md` — `.com.md` added to features list and documentation links
|
|
25
|
+
- **Updated**: `mcp-server.ts` — `tkeron://components-markdown` resource registered
|
|
26
|
+
|
|
27
|
+
### Tests
|
|
28
|
+
|
|
29
|
+
- **Added**: `tests/processComMd.test.ts` — 23 tests covering all behaviors
|
|
30
|
+
- **Updated**: `tests/examples.test.ts` — updated `with_com_mixed_priority` tests to reflect correct `.com.ts` > `.com.html` priority
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
# v4.2.0
|
|
35
|
+
|
|
36
|
+
## Developer Experience
|
|
37
|
+
|
|
38
|
+
### New Features
|
|
39
|
+
|
|
40
|
+
- **Added**: TypeScript configuration to `init_sample` example
|
|
41
|
+
- `tsconfig.json` - Minimal TypeScript configuration for IDE support
|
|
42
|
+
- `tkeron.d.ts` - Type definitions for `.com.ts` and `.pre.ts` files
|
|
43
|
+
- Enables IntelliSense for `com` variable in `.com.ts` files
|
|
44
|
+
- Enables IntelliSense for `document` variable in `.pre.ts` files
|
|
45
|
+
- Improves developer experience in VS Code and other IDEs
|
|
46
|
+
|
|
47
|
+
### Bug Fixes
|
|
48
|
+
|
|
49
|
+
- **Fixed**: Variable naming conflict in `init_sample/websrc/index.ts`
|
|
50
|
+
- Renamed `count` to `clickCount` to avoid TypeScript scope collision
|
|
51
|
+
- **Fixed**: TypeScript type error in `init_sample/websrc/user-badge.com.ts`
|
|
52
|
+
- Added explicit `parseInt` radix parameter for type safety
|
|
53
|
+
|
|
54
|
+
### Tests
|
|
55
|
+
|
|
56
|
+
- **Updated**: Test expectations to match renamed variables in `init_sample`
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
1
60
|
# v4.1.0
|
|
2
61
|
|
|
3
62
|
## Documentation & Developer Experience
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# Markdown Components
|
|
2
|
+
|
|
3
|
+
Markdown components (`.com.md` files) let you write reusable content in Markdown that gets converted to HTML and inlined at build time. Powered by Bun's built-in Markdown parser with full GitHub Flavored Markdown (GFM) support.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
Create a file named with `.com.md` extension:
|
|
8
|
+
|
|
9
|
+
```markdown
|
|
10
|
+
<!-- hero-text.com.md -->
|
|
11
|
+
|
|
12
|
+
# Welcome to My Site
|
|
13
|
+
|
|
14
|
+
This is a **great** place to start.
|
|
15
|
+
|
|
16
|
+
- Fast
|
|
17
|
+
- Simple
|
|
18
|
+
- Powerful
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Use it with a matching custom element:
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<!-- index.html -->
|
|
25
|
+
<hero-text></hero-text>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Build, and the custom element is replaced with the rendered HTML.
|
|
29
|
+
|
|
30
|
+
## How They Work
|
|
31
|
+
|
|
32
|
+
### 1. Component Naming
|
|
33
|
+
|
|
34
|
+
Component filenames must:
|
|
35
|
+
|
|
36
|
+
- End with `.com.md`
|
|
37
|
+
- Match the custom element name (without `.com.md`)
|
|
38
|
+
- Use lowercase with hyphens
|
|
39
|
+
|
|
40
|
+
**Examples:**
|
|
41
|
+
|
|
42
|
+
| Filename | Custom Element | Valid? |
|
|
43
|
+
| ------------------- | -------------- | ----------------- |
|
|
44
|
+
| `hero-text.com.md` | `<hero-text>` | ✅ Yes |
|
|
45
|
+
| `about-info.com.md` | `<about-info>` | ✅ Yes |
|
|
46
|
+
| `intro.com.md` | `<intro>` | ❌ No (no hyphen) |
|
|
47
|
+
|
|
48
|
+
### 2. Component Resolution
|
|
49
|
+
|
|
50
|
+
Tkeron looks for components in this order:
|
|
51
|
+
|
|
52
|
+
1. **Same directory** as the file using it
|
|
53
|
+
2. **Root directory** (`websrc/`)
|
|
54
|
+
|
|
55
|
+
**Example structure:**
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
websrc/
|
|
59
|
+
├── index.html
|
|
60
|
+
├── site-intro.com.md # Available to all files
|
|
61
|
+
├── blog/
|
|
62
|
+
│ ├── post.html
|
|
63
|
+
│ └── post-footer.com.md # Available only to blog/post.html
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
In `blog/post.html`:
|
|
67
|
+
|
|
68
|
+
- `<post-footer>` → Finds `blog/post-footer.com.md` first
|
|
69
|
+
- `<site-intro>` → Falls back to `websrc/site-intro.com.md`
|
|
70
|
+
|
|
71
|
+
### 3. Component Priority
|
|
72
|
+
|
|
73
|
+
When multiple component types exist for the same name, Tkeron uses this priority:
|
|
74
|
+
|
|
75
|
+
1. **`.com.ts`** (TypeScript components — highest priority)
|
|
76
|
+
2. **`.com.html`** (HTML components)
|
|
77
|
+
3. **`.com.md`** (Markdown components — lowest priority)
|
|
78
|
+
|
|
79
|
+
If both `hero-text.com.html` and `hero-text.com.md` exist, the `.com.html` version is used and `.com.md` is ignored.
|
|
80
|
+
|
|
81
|
+
### 4. Build Process
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
Before Build (websrc/index.html):
|
|
85
|
+
<hero-text></hero-text>
|
|
86
|
+
|
|
87
|
+
After Build (web/index.html):
|
|
88
|
+
<h1>Welcome</h1>
|
|
89
|
+
<p>Some <strong>bold</strong> text</p>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The `.com.md` file is NOT copied to output — only its rendered HTML is inlined.
|
|
93
|
+
|
|
94
|
+
## GFM Features
|
|
95
|
+
|
|
96
|
+
Markdown components support all GitHub Flavored Markdown extensions out of the box:
|
|
97
|
+
|
|
98
|
+
### Headings
|
|
99
|
+
|
|
100
|
+
```markdown
|
|
101
|
+
# Heading 1
|
|
102
|
+
|
|
103
|
+
## Heading 2
|
|
104
|
+
|
|
105
|
+
### Heading 3
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Bold, Italic, Strikethrough
|
|
109
|
+
|
|
110
|
+
```markdown
|
|
111
|
+
**bold text**
|
|
112
|
+
_italic text_
|
|
113
|
+
~~strikethrough~~
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Lists
|
|
117
|
+
|
|
118
|
+
```markdown
|
|
119
|
+
- Unordered item 1
|
|
120
|
+
- Unordered item 2
|
|
121
|
+
|
|
122
|
+
1. Ordered item 1
|
|
123
|
+
2. Ordered item 2
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Links and Images
|
|
127
|
+
|
|
128
|
+
```markdown
|
|
129
|
+
[Link text](https://example.com)
|
|
130
|
+

|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Tables
|
|
134
|
+
|
|
135
|
+
```markdown
|
|
136
|
+
| Feature | Status |
|
|
137
|
+
| -------- | ------ |
|
|
138
|
+
| Tables | ✅ |
|
|
139
|
+
| Lists | ✅ |
|
|
140
|
+
| Headings | ✅ |
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Blockquotes
|
|
144
|
+
|
|
145
|
+
```markdown
|
|
146
|
+
> This is a blockquote
|
|
147
|
+
> with multiple lines
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Code
|
|
151
|
+
|
|
152
|
+
````markdown
|
|
153
|
+
Inline `code` and fenced blocks:
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
console.log("hello");
|
|
157
|
+
```
|
|
158
|
+
````
|
|
159
|
+
|
|
160
|
+
## Basic Examples
|
|
161
|
+
|
|
162
|
+
### About Section
|
|
163
|
+
|
|
164
|
+
```markdown
|
|
165
|
+
<!-- about-section.com.md -->
|
|
166
|
+
|
|
167
|
+
## About Us
|
|
168
|
+
|
|
169
|
+
We are a **small team** building great tools.
|
|
170
|
+
|
|
171
|
+
- Open source
|
|
172
|
+
- Community driven
|
|
173
|
+
- Built with love
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Usage:**
|
|
177
|
+
|
|
178
|
+
```html
|
|
179
|
+
<about-section></about-section>
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Feature List
|
|
183
|
+
|
|
184
|
+
```markdown
|
|
185
|
+
<!-- feature-list.com.md -->
|
|
186
|
+
|
|
187
|
+
## Features
|
|
188
|
+
|
|
189
|
+
| Feature | Description |
|
|
190
|
+
| ----------- | ------------------------- |
|
|
191
|
+
| Fast builds | Sub-second build times |
|
|
192
|
+
| Zero config | Works out of the box |
|
|
193
|
+
| Components | Reusable HTML, TS, and MD |
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### FAQ Section
|
|
197
|
+
|
|
198
|
+
```markdown
|
|
199
|
+
<!-- faq-section.com.md -->
|
|
200
|
+
|
|
201
|
+
## Frequently Asked Questions
|
|
202
|
+
|
|
203
|
+
### How do I install?
|
|
204
|
+
|
|
205
|
+
Run `bun add -g tkeron` to install globally.
|
|
206
|
+
|
|
207
|
+
### How do I build?
|
|
208
|
+
|
|
209
|
+
Run `tk build` in your project directory.
|
|
210
|
+
|
|
211
|
+
### Where is the output?
|
|
212
|
+
|
|
213
|
+
Built files go to the `web/` directory.
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Nested Components
|
|
217
|
+
|
|
218
|
+
Markdown components can include other components using HTML custom elements inside the Markdown:
|
|
219
|
+
|
|
220
|
+
```markdown
|
|
221
|
+
<!-- page-content.com.md -->
|
|
222
|
+
|
|
223
|
+
# Main Content
|
|
224
|
+
|
|
225
|
+
Here is some introductory text.
|
|
226
|
+
|
|
227
|
+
<feature-list></feature-list>
|
|
228
|
+
|
|
229
|
+
And here is the conclusion.
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The nested `<feature-list>` will be resolved — whether it's a `.com.ts`, `.com.html`, or `.com.md` component.
|
|
233
|
+
|
|
234
|
+
## When to Use Markdown Components
|
|
235
|
+
|
|
236
|
+
### ✅ Perfect For
|
|
237
|
+
|
|
238
|
+
- **Content-heavy sections**: About pages, documentation, FAQs
|
|
239
|
+
- **Blog content**: Static blog posts or content blocks
|
|
240
|
+
- **Feature descriptions**: Lists of features with formatting
|
|
241
|
+
- **Text-heavy UI**: Any section where the content is primarily text
|
|
242
|
+
|
|
243
|
+
### ❌ Not Ideal For
|
|
244
|
+
|
|
245
|
+
- **Dynamic content**: Use [TypeScript Components](./components-typescript.md) instead
|
|
246
|
+
- **Complex layouts**: Use [HTML Components](./components-html.md) for precise HTML structure
|
|
247
|
+
- **Interactive elements**: Markdown produces static HTML only
|
|
248
|
+
|
|
249
|
+
## Limitations
|
|
250
|
+
|
|
251
|
+
### ❌ No Dynamic Content
|
|
252
|
+
|
|
253
|
+
Markdown components are **static**. They can't read attributes or use logic. For dynamic content, use [TypeScript Components](./components-typescript.md).
|
|
254
|
+
|
|
255
|
+
### ❌ No Circular Dependencies
|
|
256
|
+
|
|
257
|
+
Components can't include themselves directly or indirectly. Build will fail with a clear error message.
|
|
258
|
+
|
|
259
|
+
### Maximum Nesting Depth
|
|
260
|
+
|
|
261
|
+
Components can be nested up to **50 levels** deep.
|
|
262
|
+
|
|
263
|
+
## Best Practices
|
|
264
|
+
|
|
265
|
+
### ✅ Use for Content Sections
|
|
266
|
+
|
|
267
|
+
```markdown
|
|
268
|
+
<!-- team-info.com.md -->
|
|
269
|
+
|
|
270
|
+
## Our Team
|
|
271
|
+
|
|
272
|
+
We have **10+ engineers** working on this project.
|
|
273
|
+
|
|
274
|
+
- Backend: 5 engineers
|
|
275
|
+
- Frontend: 3 engineers
|
|
276
|
+
- DevOps: 2 engineers
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### ✅ Name Descriptively
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
Good:
|
|
283
|
+
- about-section.com.md
|
|
284
|
+
- faq-content.com.md
|
|
285
|
+
- feature-overview.com.md
|
|
286
|
+
|
|
287
|
+
Bad:
|
|
288
|
+
- text1.com.md
|
|
289
|
+
- stuff.com.md
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### ✅ Combine with Other Component Types
|
|
293
|
+
|
|
294
|
+
Use Markdown for content, HTML for layout, and TypeScript for dynamic parts:
|
|
295
|
+
|
|
296
|
+
```
|
|
297
|
+
websrc/
|
|
298
|
+
├── page-layout.com.html # Layout structure
|
|
299
|
+
├── hero-content.com.md # Content in Markdown
|
|
300
|
+
├── user-greeting.com.ts # Dynamic greeting
|
|
301
|
+
├── index.html
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
```html
|
|
305
|
+
<!-- index.html -->
|
|
306
|
+
<page-layout></page-layout>
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
```html
|
|
310
|
+
<!-- page-layout.com.html -->
|
|
311
|
+
<div class="page">
|
|
312
|
+
<user-greeting></user-greeting>
|
|
313
|
+
<hero-content></hero-content>
|
|
314
|
+
</div>
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## Next Steps
|
|
318
|
+
|
|
319
|
+
- For precise HTML structure, see [**HTML Components**](./components-html.md)
|
|
320
|
+
- For dynamic components with logic, see [**TypeScript Components**](./components-typescript.md)
|
|
321
|
+
- For build-time transformations, see [**Pre-rendering**](./pre-rendering.md)
|
|
322
|
+
- For patterns and tips, see [**Best Practices**](./best-practices.md)
|
|
@@ -86,6 +86,46 @@ Same as HTML components:
|
|
|
86
86
|
1. **Same directory** as the file using it
|
|
87
87
|
2. **Root directory** (`websrc/`)
|
|
88
88
|
|
|
89
|
+
### IDE Support & IntelliSense
|
|
90
|
+
|
|
91
|
+
For the best development experience, Tkeron projects include TypeScript configuration files:
|
|
92
|
+
|
|
93
|
+
**`tkeron.d.ts`** - Provides type definitions:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
declare module "*.com.ts" {
|
|
97
|
+
global {
|
|
98
|
+
const com: HTMLElement;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
This tells your IDE that `com` is an `HTMLElement`, enabling:
|
|
104
|
+
|
|
105
|
+
- **IntelliSense** for `com.getAttribute()`, `com.innerHTML`, etc.
|
|
106
|
+
- **Type checking** for your component code
|
|
107
|
+
- **Auto-completion** for DOM methods and properties
|
|
108
|
+
|
|
109
|
+
**`tsconfig.json`** - TypeScript configuration:
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"compilerOptions": {
|
|
114
|
+
"target": "ESNext",
|
|
115
|
+
"module": "ESNext",
|
|
116
|
+
"lib": ["ESNext", "DOM"],
|
|
117
|
+
"moduleResolution": "bundler",
|
|
118
|
+
"strict": true,
|
|
119
|
+
"skipLibCheck": true
|
|
120
|
+
},
|
|
121
|
+
"include": ["websrc/**/*", "tkeron.d.ts"]
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Both files are automatically created when you run `tk init`. If you created your project manually, you can copy them from the [init_sample example](https://github.com/pablotk/tkeron/tree/master/examples/init_sample).
|
|
126
|
+
|
|
127
|
+
**Note:** These files are only for IDE support and don't affect the build process. Tkeron uses Bun's built-in TypeScript support for compilation.
|
|
128
|
+
|
|
89
129
|
## Basic Examples
|
|
90
130
|
|
|
91
131
|
### Attribute-Based Content
|
package/docs/getting-started.md
CHANGED
|
@@ -38,7 +38,8 @@ my-website/
|
|
|
38
38
|
│ ├── index.ts # TypeScript for index
|
|
39
39
|
│ ├── index.pre.ts # Pre-rendering script
|
|
40
40
|
│ └── *.com.html # Sample components
|
|
41
|
-
|
|
41
|
+
├── tkeron.d.ts # TypeScript definitions
|
|
42
|
+
└── tsconfig.json # TypeScript configuration
|
|
42
43
|
```
|
|
43
44
|
|
|
44
45
|
### Initialize in Current Directory
|
package/docs/pre-rendering.md
CHANGED
|
@@ -97,6 +97,26 @@ If the `.html` file doesn't exist, Tkeron creates a default one:
|
|
|
97
97
|
|
|
98
98
|
Then your `.pre.ts` file runs on this template.
|
|
99
99
|
|
|
100
|
+
### IDE Support & IntelliSense
|
|
101
|
+
|
|
102
|
+
The `tkeron.d.ts` file in your project provides type definitions for the `document` variable:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
declare module "*.pre.ts" {
|
|
106
|
+
global {
|
|
107
|
+
const document: Document;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
This enables:
|
|
113
|
+
|
|
114
|
+
- **IntelliSense** for `document.querySelector()`, `document.getElementById()`, etc.
|
|
115
|
+
- **Type checking** for DOM manipulation
|
|
116
|
+
- **Auto-completion** for all Document methods and properties
|
|
117
|
+
|
|
118
|
+
The `tsconfig.json` ensures your IDE recognizes these types. Both files are automatically created by `tk init`.
|
|
119
|
+
|
|
100
120
|
## Basic Examples
|
|
101
121
|
|
|
102
122
|
### Set Page Title
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Client-side TypeScript - runs in the browser
|
|
2
|
-
let
|
|
2
|
+
let clickCount = 0;
|
|
3
3
|
|
|
4
|
-
const button = document.getElementById(
|
|
5
|
-
const countDisplay = document.getElementById(
|
|
4
|
+
const button = document.getElementById("increment") as HTMLButtonElement;
|
|
5
|
+
const countDisplay = document.getElementById("count") as HTMLSpanElement;
|
|
6
6
|
|
|
7
|
-
button.addEventListener(
|
|
8
|
-
|
|
9
|
-
countDisplay.textContent =
|
|
7
|
+
button.addEventListener("click", () => {
|
|
8
|
+
clickCount++;
|
|
9
|
+
countDisplay.textContent = clickCount.toString();
|
|
10
10
|
});
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
// TypeScript component - runs at build time with logic
|
|
2
|
-
const count = com.getAttribute(
|
|
2
|
+
const count = com.getAttribute("count") || "3";
|
|
3
|
+
const numCount = parseInt(count, 10);
|
|
3
4
|
|
|
4
5
|
// Generate list dynamically
|
|
5
6
|
const items = [];
|
|
6
|
-
for (let i = 1; i <=
|
|
7
|
+
for (let i = 1; i <= numCount; i++) {
|
|
7
8
|
items.push(`<li>✓ Item ${i}</li>`);
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
com.innerHTML = `
|
|
11
12
|
<div style="background: #f0fdf4; border-left: 3px solid #22c55e; padding: 0.75rem; border-radius: 4px;">
|
|
12
13
|
<ul style="list-style: none; padding: 0; margin: 0; font-size: 0.9rem; color: #166534;">
|
|
13
|
-
${items.join(
|
|
14
|
+
${items.join("")}
|
|
14
15
|
</ul>
|
|
15
16
|
</div>
|
|
16
17
|
`;
|
package/mcp-server.ts
CHANGED
|
@@ -53,6 +53,12 @@ const DOCS = [
|
|
|
53
53
|
description: "Build dynamic components with .com.ts files",
|
|
54
54
|
file: "components-typescript.md",
|
|
55
55
|
},
|
|
56
|
+
{
|
|
57
|
+
uri: "tkeron://components-markdown",
|
|
58
|
+
name: "Markdown Components",
|
|
59
|
+
description: "Create components with Markdown using .com.md files",
|
|
60
|
+
file: "components-markdown.md",
|
|
61
|
+
},
|
|
56
62
|
{
|
|
57
63
|
uri: "tkeron://pre-rendering",
|
|
58
64
|
name: "Pre-rendering",
|
package/package.json
CHANGED
package/src/build.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { buildDir } from "./buildDir";
|
|
|
3
3
|
import { processPre } from "./processPre";
|
|
4
4
|
import { processCom } from "./processCom";
|
|
5
5
|
import { processComTs } from "./processComTs";
|
|
6
|
+
import { processComMd } from "./processComMd";
|
|
6
7
|
import { rm, exists, mkdir, cp } from "fs/promises";
|
|
7
8
|
import type { Logger } from "@tkeron/tools";
|
|
8
9
|
import { silentLogger } from "@tkeron/tools";
|
|
@@ -46,10 +47,11 @@ export const build = async (options: BuildOptions) => {
|
|
|
46
47
|
|
|
47
48
|
const MAX_ITERATIONS = 10;
|
|
48
49
|
for (let i = 0; i < MAX_ITERATIONS; i++) {
|
|
49
|
-
const htmlChanged = await processCom(tempDir, { logger: log });
|
|
50
50
|
const tsChanged = await processComTs(tempDir, { logger: log });
|
|
51
|
+
const htmlChanged = await processCom(tempDir, { logger: log });
|
|
52
|
+
const mdChanged = await processComMd(tempDir, { logger: log });
|
|
51
53
|
|
|
52
|
-
if (!htmlChanged && !
|
|
54
|
+
if (!tsChanged && !htmlChanged && !mdChanged) {
|
|
53
55
|
break;
|
|
54
56
|
}
|
|
55
57
|
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { parseHTML } from "@tkeron/html-parser";
|
|
2
|
+
import { getFilePaths } from "@tkeron/tools";
|
|
3
|
+
import { join, dirname } from "path";
|
|
4
|
+
import type { Logger } from "@tkeron/tools";
|
|
5
|
+
import { silentLogger } from "@tkeron/tools";
|
|
6
|
+
|
|
7
|
+
const DOCTYPE = "<!doctype html>\n";
|
|
8
|
+
|
|
9
|
+
const ensureHtmlDocument = (html: string): string => {
|
|
10
|
+
const hasHtmlTag = /<\s*html[\s>]/i.test(html);
|
|
11
|
+
if (hasHtmlTag) return html;
|
|
12
|
+
return `${DOCTYPE}<html><head></head><body>${html}</body></html>`;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export interface ProcessComMdOptions {
|
|
16
|
+
logger?: Logger;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const processComMd = async (
|
|
20
|
+
tempDir: string,
|
|
21
|
+
options: ProcessComMdOptions = {},
|
|
22
|
+
): Promise<boolean> => {
|
|
23
|
+
const log = options.logger || silentLogger;
|
|
24
|
+
|
|
25
|
+
if (!tempDir || typeof tempDir !== "string") {
|
|
26
|
+
log.error(`\n❌ Error: Invalid tempDir provided for processComMd.`);
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const htmlFiles = getFilePaths(tempDir, "**/*.html", true).filter(
|
|
31
|
+
(p) => !p.endsWith(".com.html"),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
let hasChanges = false;
|
|
35
|
+
|
|
36
|
+
for (const htmlFile of htmlFiles) {
|
|
37
|
+
const htmlContent = await Bun.file(htmlFile).text();
|
|
38
|
+
const document = parseHTML(ensureHtmlDocument(htmlContent));
|
|
39
|
+
|
|
40
|
+
const htmlElement =
|
|
41
|
+
document.querySelector("html") || document.documentElement;
|
|
42
|
+
if (htmlElement) {
|
|
43
|
+
const changed = await processComponents(
|
|
44
|
+
htmlElement,
|
|
45
|
+
dirname(htmlFile),
|
|
46
|
+
tempDir,
|
|
47
|
+
[],
|
|
48
|
+
0,
|
|
49
|
+
log,
|
|
50
|
+
);
|
|
51
|
+
hasChanges = hasChanges || changed;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const output =
|
|
55
|
+
DOCTYPE +
|
|
56
|
+
(htmlElement?.outerHTML || document.documentElement?.outerHTML || "");
|
|
57
|
+
await Bun.write(htmlFile, output);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return hasChanges;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const processComponents = async (
|
|
64
|
+
element: any,
|
|
65
|
+
currentDir: string,
|
|
66
|
+
rootDir: string,
|
|
67
|
+
componentStack: string[],
|
|
68
|
+
depth: number = 0,
|
|
69
|
+
log: Logger = silentLogger,
|
|
70
|
+
): Promise<boolean> => {
|
|
71
|
+
let hasChanges = false;
|
|
72
|
+
const MAX_DEPTH = 50;
|
|
73
|
+
if (depth > MAX_DEPTH) {
|
|
74
|
+
log.warn(`Maximum component nesting depth (${MAX_DEPTH}) reached`);
|
|
75
|
+
return hasChanges;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const children = Array.from(element.children || []);
|
|
79
|
+
|
|
80
|
+
for (const child of children) {
|
|
81
|
+
const tagName = (child as any).tagName?.toLowerCase();
|
|
82
|
+
|
|
83
|
+
if (tagName) {
|
|
84
|
+
if (!tagName.includes("-")) {
|
|
85
|
+
const localPath = join(currentDir, `${tagName}.com.md`);
|
|
86
|
+
const rootPath = join(rootDir, `${tagName}.com.md`);
|
|
87
|
+
if (
|
|
88
|
+
(await Bun.file(localPath).exists()) ||
|
|
89
|
+
(await Bun.file(rootPath).exists())
|
|
90
|
+
) {
|
|
91
|
+
log.error(
|
|
92
|
+
`\n❌ Error: Component name '${tagName}' must contain at least one hyphen.`,
|
|
93
|
+
);
|
|
94
|
+
log.error(`\n💡 Custom elements require hyphens to be valid HTML.`);
|
|
95
|
+
log.error(
|
|
96
|
+
` Rename '${tagName}.com.md' to 'tk-${tagName}.com.md' or similar.`,
|
|
97
|
+
);
|
|
98
|
+
log.error(
|
|
99
|
+
` File: ${(await Bun.file(localPath).exists()) ? localPath : rootPath}\n`,
|
|
100
|
+
);
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Component name '${tagName}' must contain at least one hyphen`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
const localPath = join(currentDir, `${tagName}.com.md`);
|
|
107
|
+
const rootPath = join(rootDir, `${tagName}.com.md`);
|
|
108
|
+
|
|
109
|
+
let componentPath: string | null = null;
|
|
110
|
+
|
|
111
|
+
if (await Bun.file(localPath).exists()) {
|
|
112
|
+
componentPath = localPath;
|
|
113
|
+
} else if (await Bun.file(rootPath).exists()) {
|
|
114
|
+
componentPath = rootPath;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (componentPath) {
|
|
118
|
+
hasChanges = true;
|
|
119
|
+
|
|
120
|
+
if (componentStack.includes(tagName)) {
|
|
121
|
+
const chain = [...componentStack, tagName].join(" -> ");
|
|
122
|
+
log.error(`\n❌ Error: Circular component dependency detected.`);
|
|
123
|
+
log.error(`\n💡 Component chain: ${chain}`);
|
|
124
|
+
log.error(
|
|
125
|
+
`\n Components cannot include themselves directly or indirectly.`,
|
|
126
|
+
);
|
|
127
|
+
log.error(` Check your .com.md files for circular references.\n`);
|
|
128
|
+
throw new Error(`Circular dependency: ${chain}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const mdContent = await Bun.file(componentPath).text();
|
|
132
|
+
const componentHtml = Bun.markdown.html(mdContent);
|
|
133
|
+
|
|
134
|
+
const tempDoc = parseHTML(
|
|
135
|
+
`<html><body><div id="__tkeron_component_root__">${componentHtml}</div></body></html>`,
|
|
136
|
+
);
|
|
137
|
+
const body = tempDoc.querySelector("body");
|
|
138
|
+
const tempContainer =
|
|
139
|
+
body?.querySelector("#__tkeron_component_root__") ||
|
|
140
|
+
body?.firstElementChild;
|
|
141
|
+
|
|
142
|
+
if (!tempContainer) continue;
|
|
143
|
+
|
|
144
|
+
const nodesToInsert = Array.from(
|
|
145
|
+
(tempContainer as any).childNodes || [],
|
|
146
|
+
).map((node: any) => (node?.cloneNode ? node.cloneNode(true) : node));
|
|
147
|
+
|
|
148
|
+
const parent = (child as any).parentNode;
|
|
149
|
+
|
|
150
|
+
if (parent) {
|
|
151
|
+
if (nodesToInsert.length === 0) {
|
|
152
|
+
parent.removeChild(child);
|
|
153
|
+
} else if (nodesToInsert.length === 1) {
|
|
154
|
+
parent.replaceChild(nodesToInsert[0], child);
|
|
155
|
+
} else {
|
|
156
|
+
parent.replaceChild(nodesToInsert[0], child);
|
|
157
|
+
let refNode = nodesToInsert[0];
|
|
158
|
+
for (let i = 1; i < nodesToInsert.length; i++) {
|
|
159
|
+
parent.insertBefore(nodesToInsert[i], refNode.nextSibling);
|
|
160
|
+
refNode = nodesToInsert[i];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const nextCurrentDir = dirname(componentPath);
|
|
166
|
+
const nextStack = [...componentStack, tagName];
|
|
167
|
+
|
|
168
|
+
for (const node of nodesToInsert) {
|
|
169
|
+
if ((node as any).nodeType === 1) {
|
|
170
|
+
const nestedChanged = await processComponents(
|
|
171
|
+
node,
|
|
172
|
+
nextCurrentDir,
|
|
173
|
+
rootDir,
|
|
174
|
+
nextStack,
|
|
175
|
+
depth + 1,
|
|
176
|
+
log,
|
|
177
|
+
);
|
|
178
|
+
hasChanges = hasChanges || nestedChanged;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const childChanged = await processComponents(
|
|
186
|
+
child,
|
|
187
|
+
currentDir,
|
|
188
|
+
rootDir,
|
|
189
|
+
componentStack,
|
|
190
|
+
depth,
|
|
191
|
+
log,
|
|
192
|
+
);
|
|
193
|
+
hasChanges = hasChanges || childChanged;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return hasChanges;
|
|
197
|
+
};
|