@emkodev/emroute 1.7.3 → 1.8.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/README.md +1 -1
  2. package/core/component/abstract.component.ts +74 -0
  3. package/{src → core}/component/page.component.ts +3 -61
  4. package/core/component/widget.component.ts +54 -0
  5. package/core/pipeline/pipeline.ts +224 -0
  6. package/{src/renderer/ssr → core/renderer}/html.renderer.ts +26 -47
  7. package/{src/renderer/ssr → core/renderer}/md.renderer.ts +22 -41
  8. package/{src/renderer/ssr → core/renderer}/ssr.renderer.ts +44 -58
  9. package/{src/route → core/router}/route.resolver.ts +1 -10
  10. package/core/router/route.trie.ts +175 -0
  11. package/core/runtime/abstract.runtime.ts +47 -0
  12. package/core/server/emroute.server.ts +324 -0
  13. package/core/type/component.type.ts +39 -0
  14. package/core/type/element.type.ts +10 -0
  15. package/core/type/logger.type.ts +20 -0
  16. package/core/type/markdown.type.ts +8 -0
  17. package/core/type/route-tree.type.ts +28 -0
  18. package/core/type/route.type.ts +75 -0
  19. package/core/type/widget.type.ts +27 -0
  20. package/core/util/html.util.ts +50 -0
  21. package/{src → core}/util/md.util.ts +3 -5
  22. package/{src/route → core/util}/route-tree.util.ts +0 -2
  23. package/{src → core}/util/widget-resolve.util.ts +15 -46
  24. package/{src → core}/widget/widget.parser.ts +2 -23
  25. package/core/widget/widget.registry.ts +36 -0
  26. package/dist/core/component/abstract.component.d.ts +48 -0
  27. package/dist/core/component/abstract.component.js +42 -0
  28. package/dist/core/component/abstract.component.js.map +1 -0
  29. package/dist/core/component/page.component.d.ts +23 -0
  30. package/dist/core/component/page.component.js +49 -0
  31. package/dist/core/component/page.component.js.map +1 -0
  32. package/dist/core/component/widget.component.d.ts +17 -0
  33. package/dist/core/component/widget.component.js +37 -0
  34. package/dist/core/component/widget.component.js.map +1 -0
  35. package/dist/core/pipeline/pipeline.d.ts +61 -0
  36. package/dist/core/pipeline/pipeline.js +189 -0
  37. package/dist/core/pipeline/pipeline.js.map +1 -0
  38. package/dist/{src/renderer/ssr → core/renderer}/html.renderer.d.ts +8 -24
  39. package/dist/{src/renderer/ssr → core/renderer}/html.renderer.js +20 -35
  40. package/dist/core/renderer/html.renderer.js.map +1 -0
  41. package/dist/{src/renderer/ssr → core/renderer}/md.renderer.d.ts +6 -21
  42. package/dist/{src/renderer/ssr → core/renderer}/md.renderer.js +16 -32
  43. package/dist/core/renderer/md.renderer.js.map +1 -0
  44. package/dist/{src/renderer/ssr → core/renderer}/ssr.renderer.d.ts +11 -27
  45. package/dist/{src/renderer/ssr → core/renderer}/ssr.renderer.js +33 -37
  46. package/dist/core/renderer/ssr.renderer.js.map +1 -0
  47. package/dist/{src/route → core/router}/route.resolver.d.ts +1 -8
  48. package/dist/{src/route → core/router}/route.resolver.js +0 -1
  49. package/dist/core/router/route.resolver.js.map +1 -0
  50. package/dist/core/router/route.trie.d.ts +32 -0
  51. package/dist/core/router/route.trie.js +152 -0
  52. package/dist/core/router/route.trie.js.map +1 -0
  53. package/dist/core/runtime/abstract.runtime.d.ts +32 -0
  54. package/dist/core/runtime/abstract.runtime.js +26 -0
  55. package/dist/core/runtime/abstract.runtime.js.map +1 -0
  56. package/dist/core/server/emroute.server.d.ts +48 -0
  57. package/dist/core/server/emroute.server.js +239 -0
  58. package/dist/core/server/emroute.server.js.map +1 -0
  59. package/dist/core/server/server.type.d.ts +45 -0
  60. package/dist/core/server/server.type.js +11 -0
  61. package/dist/core/server/server.type.js.map +1 -0
  62. package/dist/core/type/component.type.d.ts +37 -0
  63. package/dist/core/type/component.type.js +7 -0
  64. package/dist/core/type/component.type.js.map +1 -0
  65. package/dist/core/type/element.type.d.ts +9 -0
  66. package/dist/core/type/element.type.js +5 -0
  67. package/dist/core/type/element.type.js.map +1 -0
  68. package/dist/core/type/logger.type.d.ts +14 -0
  69. package/dist/core/type/logger.type.js +8 -0
  70. package/dist/core/type/logger.type.js.map +1 -0
  71. package/dist/core/type/markdown.type.d.ts +7 -0
  72. package/dist/core/type/markdown.type.js +5 -0
  73. package/dist/core/type/markdown.type.js.map +1 -0
  74. package/dist/{src → core}/type/route-tree.type.d.ts +0 -12
  75. package/dist/{src → core}/type/route-tree.type.js +0 -1
  76. package/dist/core/type/route-tree.type.js.map +1 -0
  77. package/dist/core/type/route.type.d.ts +62 -0
  78. package/dist/core/type/route.type.js +7 -0
  79. package/dist/core/type/route.type.js.map +1 -0
  80. package/dist/core/type/widget.type.d.ts +27 -0
  81. package/dist/core/type/widget.type.js +5 -0
  82. package/dist/core/type/widget.type.js.map +1 -0
  83. package/dist/core/util/html.util.d.ts +14 -0
  84. package/dist/core/util/html.util.js +43 -0
  85. package/dist/core/util/html.util.js.map +1 -0
  86. package/dist/{src → core}/util/md.util.d.ts +0 -1
  87. package/dist/{src → core}/util/md.util.js +0 -2
  88. package/dist/core/util/md.util.js.map +1 -0
  89. package/dist/{src/route → core/util}/route-tree.util.js +0 -2
  90. package/dist/core/util/route-tree.util.js.map +1 -0
  91. package/dist/core/util/widget-resolve.util.d.ts +28 -0
  92. package/dist/{src → core}/util/widget-resolve.util.js +12 -42
  93. package/dist/core/util/widget-resolve.util.js.map +1 -0
  94. package/dist/{src → core}/widget/widget.parser.d.ts +0 -13
  95. package/dist/{src → core}/widget/widget.parser.js +1 -22
  96. package/dist/core/widget/widget.parser.js.map +1 -0
  97. package/dist/core/widget/widget.registry.d.ts +14 -0
  98. package/dist/core/widget/widget.registry.js +26 -0
  99. package/dist/core/widget/widget.registry.js.map +1 -0
  100. package/dist/emroute.js +1092 -1220
  101. package/dist/emroute.js.map +36 -5
  102. package/dist/runtime/abstract.runtime.d.ts +41 -7
  103. package/dist/runtime/abstract.runtime.js +404 -9
  104. package/dist/runtime/abstract.runtime.js.map +1 -1
  105. package/dist/runtime/bun/fs/bun-fs.runtime.d.ts +1 -0
  106. package/dist/runtime/bun/fs/bun-fs.runtime.js +15 -1
  107. package/dist/runtime/bun/fs/bun-fs.runtime.js.map +1 -1
  108. package/dist/runtime/bun/sqlite/bun-sqlite.runtime.d.ts +2 -0
  109. package/dist/runtime/bun/sqlite/bun-sqlite.runtime.js +8 -0
  110. package/dist/runtime/bun/sqlite/bun-sqlite.runtime.js.map +1 -1
  111. package/dist/runtime/fetch.runtime.d.ts +3 -3
  112. package/dist/runtime/fetch.runtime.js +3 -3
  113. package/dist/runtime/sitemap.generator.d.ts +1 -1
  114. package/dist/runtime/sitemap.generator.js +1 -1
  115. package/dist/runtime/sitemap.generator.js.map +1 -1
  116. package/dist/runtime/universal/fs/universal-fs.runtime.d.ts +1 -0
  117. package/dist/runtime/universal/fs/universal-fs.runtime.js +15 -1
  118. package/dist/runtime/universal/fs/universal-fs.runtime.js.map +1 -1
  119. package/dist/server/build.util.d.ts +9 -10
  120. package/dist/server/build.util.js +11 -31
  121. package/dist/server/build.util.js.map +1 -1
  122. package/dist/server/codegen.util.d.ts +1 -1
  123. package/dist/server/emroute.server.d.ts +8 -35
  124. package/dist/server/emroute.server.js +7 -351
  125. package/dist/server/emroute.server.js.map +1 -1
  126. package/dist/server/esbuild-manifest.plugin.js +1 -1
  127. package/dist/server/esbuild-manifest.plugin.js.map +1 -1
  128. package/dist/server/server-api.type.d.ts +3 -71
  129. package/dist/server/server-api.type.js +1 -8
  130. package/dist/server/server-api.type.js.map +1 -1
  131. package/dist/src/element/component.element.d.ts +6 -14
  132. package/dist/src/element/component.element.js +13 -40
  133. package/dist/src/element/component.element.js.map +1 -1
  134. package/dist/src/element/markdown.element.d.ts +2 -2
  135. package/dist/src/element/markdown.element.js +3 -2
  136. package/dist/src/element/markdown.element.js.map +1 -1
  137. package/dist/src/index.d.ts +15 -14
  138. package/dist/src/index.js +8 -8
  139. package/dist/src/index.js.map +1 -1
  140. package/dist/src/renderer/spa/emroute.app.d.ts +50 -0
  141. package/dist/src/renderer/spa/emroute.app.js +246 -0
  142. package/dist/src/renderer/spa/emroute.app.js.map +1 -0
  143. package/dist/src/renderer/spa/mod.d.ts +17 -16
  144. package/dist/src/renderer/spa/mod.js +9 -9
  145. package/dist/src/renderer/spa/mod.js.map +1 -1
  146. package/dist/src/renderer/spa/thin-client.d.ts +3 -3
  147. package/dist/src/renderer/spa/thin-client.js +7 -7
  148. package/dist/src/renderer/spa/thin-client.js.map +1 -1
  149. package/dist/src/route/route.core.d.ts +3 -3
  150. package/dist/src/util/html.util.d.ts +5 -22
  151. package/dist/src/util/html.util.js +8 -56
  152. package/dist/src/util/html.util.js.map +1 -1
  153. package/dist/src/widget/breadcrumb.widget.d.ts +2 -2
  154. package/dist/src/widget/breadcrumb.widget.js +2 -2
  155. package/dist/src/widget/breadcrumb.widget.js.map +1 -1
  156. package/dist/src/widget/page-title.widget.d.ts +1 -1
  157. package/dist/src/widget/page-title.widget.js +1 -1
  158. package/dist/src/widget/page-title.widget.js.map +1 -1
  159. package/package.json +8 -8
  160. package/runtime/abstract.runtime.ts +433 -17
  161. package/runtime/bun/fs/bun-fs.runtime.ts +15 -1
  162. package/runtime/bun/sqlite/bun-sqlite.runtime.ts +9 -0
  163. package/runtime/fetch.runtime.ts +3 -3
  164. package/runtime/sitemap.generator.ts +2 -2
  165. package/runtime/universal/fs/universal-fs.runtime.ts +15 -1
  166. package/server/build.util.ts +17 -43
  167. package/server/codegen.util.ts +1 -1
  168. package/server/emroute.server.ts +12 -426
  169. package/src/element/component.element.ts +14 -54
  170. package/src/element/markdown.element.ts +4 -3
  171. package/src/index.ts +22 -19
  172. package/src/renderer/spa/{thin-client.ts → emroute.app.ts} +19 -20
  173. package/src/renderer/spa/mod.ts +22 -22
  174. package/src/util/html.util.ts +16 -61
  175. package/src/widget/breadcrumb.widget.ts +3 -3
  176. package/src/widget/page-title.widget.ts +1 -1
  177. package/dist/src/component/abstract.component.d.ts +0 -199
  178. package/dist/src/component/abstract.component.js +0 -84
  179. package/dist/src/component/abstract.component.js.map +0 -1
  180. package/dist/src/component/page.component.d.ts +0 -74
  181. package/dist/src/component/page.component.js +0 -107
  182. package/dist/src/component/page.component.js.map +0 -1
  183. package/dist/src/component/widget.component.d.ts +0 -47
  184. package/dist/src/component/widget.component.js +0 -69
  185. package/dist/src/component/widget.component.js.map +0 -1
  186. package/dist/src/renderer/ssr/html.renderer.js.map +0 -1
  187. package/dist/src/renderer/ssr/md.renderer.js.map +0 -1
  188. package/dist/src/renderer/ssr/ssr.renderer.js.map +0 -1
  189. package/dist/src/route/route-tree.util.js.map +0 -1
  190. package/dist/src/route/route.matcher.d.ts +0 -86
  191. package/dist/src/route/route.matcher.js +0 -214
  192. package/dist/src/route/route.matcher.js.map +0 -1
  193. package/dist/src/route/route.resolver.js.map +0 -1
  194. package/dist/src/route/route.trie.d.ts +0 -38
  195. package/dist/src/route/route.trie.js +0 -206
  196. package/dist/src/route/route.trie.js.map +0 -1
  197. package/dist/src/type/element.type.d.ts +0 -19
  198. package/dist/src/type/element.type.js +0 -9
  199. package/dist/src/type/element.type.js.map +0 -1
  200. package/dist/src/type/logger.type.d.ts +0 -17
  201. package/dist/src/type/logger.type.js +0 -9
  202. package/dist/src/type/logger.type.js.map +0 -1
  203. package/dist/src/type/markdown.type.d.ts +0 -20
  204. package/dist/src/type/markdown.type.js +0 -2
  205. package/dist/src/type/markdown.type.js.map +0 -1
  206. package/dist/src/type/route-tree.type.js.map +0 -1
  207. package/dist/src/type/route.type.d.ts +0 -94
  208. package/dist/src/type/route.type.js +0 -8
  209. package/dist/src/type/route.type.js.map +0 -1
  210. package/dist/src/type/widget.type.d.ts +0 -55
  211. package/dist/src/type/widget.type.js +0 -10
  212. package/dist/src/type/widget.type.js.map +0 -1
  213. package/dist/src/util/logger.util.d.ts +0 -26
  214. package/dist/src/util/logger.util.js +0 -80
  215. package/dist/src/util/logger.util.js.map +0 -1
  216. package/dist/src/util/md.util.js.map +0 -1
  217. package/dist/src/util/widget-resolve.util.d.ts +0 -52
  218. package/dist/src/util/widget-resolve.util.js.map +0 -1
  219. package/dist/src/widget/widget.parser.js.map +0 -1
  220. package/dist/src/widget/widget.registry.d.ts +0 -23
  221. package/dist/src/widget/widget.registry.js +0 -42
  222. package/dist/src/widget/widget.registry.js.map +0 -1
  223. package/runtime/bun/esbuild-runtime-loader.plugin.ts +0 -112
  224. package/server/esbuild-manifest.plugin.ts +0 -209
  225. package/server/server-api.type.ts +0 -101
  226. package/src/component/abstract.component.ts +0 -231
  227. package/src/component/widget.component.ts +0 -85
  228. package/src/route/route.core.ts +0 -371
  229. package/src/route/route.trie.ts +0 -265
  230. package/src/type/element.type.ts +0 -22
  231. package/src/type/logger.type.ts +0 -24
  232. package/src/type/markdown.type.ts +0 -21
  233. package/src/type/route-tree.type.ts +0 -51
  234. package/src/type/route.type.ts +0 -124
  235. package/src/type/widget.type.ts +0 -65
  236. package/src/util/logger.util.ts +0 -83
  237. package/src/widget/widget.registry.ts +0 -51
  238. /package/dist/{src/route → core/util}/route-tree.util.d.ts +0 -0
@@ -1,209 +0,0 @@
1
- /**
2
- * esbuild Virtual Manifest Plugin
3
- *
4
- * Intercepts `emroute:routes` and `emroute:widgets` import specifiers.
5
- * Reads JSON manifests from the runtime and generates TypeScript modules
6
- * with `moduleLoaders` (dynamic `import()` calls) in-memory — no .g.ts
7
- * files on disk.
8
- *
9
- * This is the single source of truth: JSON manifest → esbuild bundle.
10
- */
11
-
12
- import type { Runtime } from '../runtime/abstract.runtime.ts';
13
- import { ROUTES_MANIFEST_PATH, WIDGETS_MANIFEST_PATH } from '../runtime/abstract.runtime.ts';
14
- import { EMROUTE_VIRTUAL_NS } from './build.util.ts';
15
-
16
- /** Escape a string for use inside a single-quoted JS/TS string literal. */
17
- function esc(value: string): string {
18
- return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
19
- }
20
-
21
- interface ManifestPluginOptions {
22
- runtime: Runtime;
23
- /**
24
- * Directory prefix to strip from module paths so that import() calls
25
- * are relative to the entry point (e.g. 'routes/' strips '/routes/').
26
- */
27
- stripPrefix?: string;
28
- /** Absolute directory for resolving relative import() paths in generated code. */
29
- resolveDir: string;
30
- }
31
-
32
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
- type EsbuildPlugin = any;
34
-
35
- export function createManifestPlugin(options: ManifestPluginOptions): EsbuildPlugin {
36
- const { runtime, stripPrefix = '', resolveDir } = options;
37
-
38
- const strip = (p: string): string =>
39
- stripPrefix && p.startsWith(stripPrefix) ? p.slice(stripPrefix.length) : p;
40
-
41
- return {
42
- name: 'emroute-manifest',
43
-
44
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
- setup(build: any) {
46
- // ── Resolve virtual specifiers ──────────────────────────────────
47
- build.onResolve(
48
- { filter: /^emroute:/ },
49
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
50
- (args: any) => ({ path: args.path, namespace: EMROUTE_VIRTUAL_NS }),
51
- );
52
-
53
- // ── Load virtual modules ────────────────────────────────────────
54
- build.onLoad(
55
- { filter: /.*/, namespace: EMROUTE_VIRTUAL_NS },
56
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
- async (args: any) => {
58
- if (args.path === 'emroute:routes') {
59
- return { contents: await generateRoutesModule(), loader: 'ts' as const, resolveDir };
60
- }
61
- if (args.path === 'emroute:widgets') {
62
- return { contents: await generateWidgetsModule(), loader: 'ts' as const, resolveDir };
63
- }
64
- return undefined;
65
- },
66
- );
67
- },
68
- };
69
-
70
- // ── Routes module generator ───────────────────────────────────────
71
-
72
- async function generateRoutesModule(): Promise<string> {
73
- const response = await runtime.query(ROUTES_MANIFEST_PATH);
74
- if (response.status === 404) {
75
- return `import type { RouteNode } from '@emkodev/emroute';
76
- export const routeTree: RouteNode = {};
77
- export const moduleLoaders: Record<string, () => Promise<unknown>> = {};
78
- `;
79
- }
80
- const raw = await response.json();
81
-
82
- // Walk the RouteNode tree to collect all .ts module paths for import() loaders
83
- const tsModulePaths = new Set<string>();
84
- collectModulePaths(raw, tsModulePaths);
85
-
86
- // Serialize the tree with stripped file paths
87
- const strippedTree = stripTreePaths(raw);
88
-
89
- const moduleLoadersCode = [...tsModulePaths]
90
- .map((p) => {
91
- const key = strip(p);
92
- const rel = key.replace(/^\.?\//, '');
93
- return ` '${esc(key)}': () => import('./${esc(rel)}'),`;
94
- })
95
- .join('\n');
96
-
97
- return `import type { RouteNode } from '@emkodev/emroute';
98
-
99
- export const routeTree: RouteNode = ${JSON.stringify(strippedTree, null, 2)};
100
-
101
- export const moduleLoaders: Record<string, () => Promise<unknown>> = {
102
- ${moduleLoadersCode}
103
- };
104
- `;
105
- }
106
-
107
- /**
108
- * Recursively collect .ts module paths from a RouteNode tree.
109
- */
110
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
111
- function collectModulePaths(node: any, paths: Set<string>): void {
112
- const modulePath = node.files?.ts ?? node.files?.js;
113
- if (modulePath) paths.add(modulePath);
114
- if (node.errorBoundary) paths.add(node.errorBoundary);
115
- if (node.redirect) paths.add(node.redirect);
116
- if (node.children) {
117
- for (const child of Object.values(node.children)) {
118
- collectModulePaths(child, paths);
119
- }
120
- }
121
- if (node.dynamic) collectModulePaths(node.dynamic.child, paths);
122
- if (node.wildcard) collectModulePaths(node.wildcard.child, paths);
123
- }
124
-
125
- /**
126
- * Deep-clone a RouteNode tree with stripped file paths.
127
- */
128
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
129
- function stripTreePaths(node: any): any {
130
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
131
- const out: any = {};
132
-
133
- if (node.files) {
134
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
135
- out.files = {} as any;
136
- for (const [ext, path] of Object.entries(node.files)) {
137
- if (path) out.files[ext] = strip(path as string);
138
- }
139
- }
140
-
141
- if (node.errorBoundary) out.errorBoundary = strip(node.errorBoundary);
142
- if (node.redirect) out.redirect = strip(node.redirect);
143
-
144
- if (node.children) {
145
- out.children = {};
146
- for (const [seg, child] of Object.entries(node.children)) {
147
- out.children[seg] = stripTreePaths(child);
148
- }
149
- }
150
-
151
- if (node.dynamic) {
152
- out.dynamic = { param: node.dynamic.param, child: stripTreePaths(node.dynamic.child) };
153
- }
154
-
155
- if (node.wildcard) {
156
- out.wildcard = { param: node.wildcard.param, child: stripTreePaths(node.wildcard.child) };
157
- }
158
-
159
- return out;
160
- }
161
-
162
- // ── Widgets module generator ──────────────────────────────────────
163
-
164
- async function generateWidgetsModule(): Promise<string> {
165
- const response = await runtime.query(WIDGETS_MANIFEST_PATH);
166
- if (response.status === 404) {
167
- return `export const widgetsManifest = { widgets: [], moduleLoaders: {} };`;
168
- }
169
- const entries = await response.json();
170
-
171
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
172
- const widgetEntries = (entries as any[]).map((e) => {
173
- const filesStr = e.files
174
- ? `\n files: { ${
175
- Object.entries(e.files)
176
- .filter(([_, v]) => v)
177
- .map(([k, v]) => `${k}: '${esc(strip(v as string))}'`)
178
- .join(', ')
179
- } },`
180
- : '';
181
-
182
- return ` {
183
- name: '${esc(e.name)}',
184
- modulePath: '${esc(strip(e.modulePath))}',
185
- tagName: '${esc(e.tagName)}',${filesStr}
186
- }`;
187
- }).join(',\n');
188
-
189
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
190
- const loaderEntries = (entries as any[]).map((e) => {
191
- const key = strip(e.modulePath);
192
- const rel = key.replace(/^\.?\//, '');
193
- return ` '${esc(key)}': () => import('./${esc(rel)}'),`;
194
- }).join('\n');
195
-
196
- return `import type { WidgetsManifest } from '@emkodev/emroute';
197
-
198
- export const widgetsManifest: WidgetsManifest = {
199
- widgets: [
200
- ${widgetEntries}
201
- ],
202
-
203
- moduleLoaders: {
204
- ${loaderEntries}
205
- },
206
- };
207
- `;
208
- }
209
- }
@@ -1,101 +0,0 @@
1
- /**
2
- * Server API Types
3
- *
4
- * Interfaces for the emroute server.
5
- * Consumers use `createEmrouteServer()` to get a server that handles
6
- * SSR rendering, static file serving, and route matching.
7
- */
8
-
9
- import type { RouteNode } from '../src/type/route-tree.type.ts';
10
- import type { MarkdownRenderer } from '../src/type/markdown.type.ts';
11
- import type { SpaMode, WidgetManifestEntry } from '../src/type/widget.type.ts';
12
- import type { ElementManifestEntry } from '../src/type/element.type.ts';
13
- import type { ContextProvider } from '../src/component/abstract.component.ts';
14
- import type { BasePath } from '../src/route/route.core.ts';
15
- import type { WidgetRegistry } from '../src/widget/widget.registry.ts';
16
- import type { SsrHtmlRouter } from '../src/renderer/ssr/html.renderer.ts';
17
- import type { SsrMdRouter } from '../src/renderer/ssr/md.renderer.ts';
18
-
19
- // ── SSR Render Result ──────────────────────────────────────────────────
20
-
21
- /** Result of rendering a URL through an SSR renderer. */
22
- export interface SsrRenderResult {
23
- /** Rendered content (HTML or Markdown) */
24
- content: string;
25
- /** HTTP status code */
26
- status: number;
27
- /** Page title (from the leaf route's getTitle) */
28
- title?: string;
29
- /** Redirect target URL (for 301/302 responses) */
30
- redirect?: string;
31
- }
32
-
33
- // ── Server ─────────────────────────────────────────────────────────────
34
-
35
- /**
36
- * Config for `createEmrouteServer()`.
37
- *
38
- * The server reads manifests from the Runtime and handles SSR rendering,
39
- * static file serving, and route matching.
40
- */
41
- export interface EmrouteServerConfig {
42
- /** Pre-built route tree (alternative to reading from runtime) */
43
- routeTree?: RouteNode;
44
-
45
- /** Pre-built widget registry (alternative to reading from runtime) */
46
- widgets?: WidgetRegistry;
47
-
48
- /** SPA mode — controls which routers are constructed and what gets served */
49
- spa?: SpaMode;
50
-
51
- /** Base paths for SSR endpoints (default: { html: '/html', md: '/md' }) */
52
- basePath?: BasePath;
53
-
54
- /** Page title (fallback when no route provides one) */
55
- title?: string;
56
-
57
- /** Markdown renderer for server-side <mark-down> expansion */
58
- markdownRenderer?: MarkdownRenderer;
59
-
60
- /** Enrich every ComponentContext with app-level services. */
61
- extendContext?: ContextProvider;
62
-
63
- /**
64
- * Pre-bundled module loaders (route + widget modules).
65
- * When provided, skips `runtime.loadModule()` — used in the browser
66
- * where modules are already bundled into app.js.
67
- */
68
- moduleLoaders?: Record<string, () => Promise<unknown>>;
69
- }
70
-
71
- /**
72
- * An emroute server instance.
73
- *
74
- * Handles SSR rendering, static file serving, and route matching.
75
- * Use `handleRequest(req)` to compose with your own request handling.
76
- */
77
- export interface EmrouteServer {
78
- /**
79
- * Handle an HTTP request for SSR routes and bare paths.
80
- * Returns `null` for unmatched file requests — consumer handles 404.
81
- */
82
- handleRequest(req: Request): Promise<Response | null>;
83
-
84
- /** The SSR HTML router (null in 'only' mode — no server rendering). */
85
- readonly htmlRouter: SsrHtmlRouter | null;
86
-
87
- /** The SSR Markdown router (null in 'only' mode). */
88
- readonly mdRouter: SsrMdRouter | null;
89
-
90
- /** The resolved route tree. */
91
- readonly routeTree: RouteNode;
92
-
93
- /** Discovered widget entries. */
94
- readonly widgetEntries: WidgetManifestEntry[];
95
-
96
- /** Discovered element entries. */
97
- readonly elementEntries: ElementManifestEntry[];
98
-
99
- /** The resolved HTML shell. */
100
- readonly shell: string;
101
- }
@@ -1,231 +0,0 @@
1
- /**
2
- * Unified Component Architecture
3
- *
4
- * Everything is a Component: pages and widgets.
5
- * Components render differently based on context:
6
- * - /md/* → Markdown (LLMs, text clients)
7
- * - /html/* → Pre-rendered HTML (SSR)
8
- * - SPA → Hydrated custom elements
9
- *
10
- * Precedence (like .ts/.html/.md):
11
- * - renderHTML() if defined → full HTML control
12
- * - renderMarkdown() → converted to HTML via markdown renderer
13
- */
14
-
15
- import type { RouteInfo } from '../type/route.type.ts';
16
- import { escapeHtml } from '../util/html.util.ts';
17
-
18
- /**
19
- * Context passed to components during rendering.
20
- * Extends RouteInfo (pathname, pattern, params)
21
- * with pre-loaded file content and an abort signal.
22
- *
23
- * Consumers can extend this interface via module augmentation
24
- * to add app-level services (RPC clients, auth, feature flags, etc.).
25
- */
26
- /** Shape of companion file contents (html, md, css). Used by generated `.page.files.g.ts` modules. */
27
- export type FileContents = { html?: string; md?: string; css?: string };
28
-
29
- export interface ComponentContext extends RouteInfo {
30
- /** @deprecated Use context.url.pathname */
31
- readonly pathname: string;
32
- /** @deprecated Use context.url.searchParams */
33
- readonly searchParams: URLSearchParams;
34
- readonly files?: Readonly<FileContents>;
35
- readonly signal?: AbortSignal;
36
- /** True when this component is the leaf (matched) route, false when rendered as a layout parent. */
37
- readonly isLeaf?: boolean;
38
- }
39
-
40
- /**
41
- * Callback that enriches the base ComponentContext with app-level services.
42
- * Registered once at router creation; called for every context construction.
43
- *
44
- * **1. Register** — always spread `base` to preserve routing/file/signal data:
45
- * ```ts
46
- * createSpaHtmlRouter(manifest, {
47
- * extendContext: (base) => ({ ...base, rpc: myRpcClient }),
48
- * });
49
- * ```
50
- *
51
- * **2. Access** — expose custom properties to components via module augmentation:
52
- * ```ts
53
- * declare module '@emkodev/emroute' {
54
- * interface ComponentContext { rpc: RpcClient; }
55
- * }
56
- * ```
57
- * or per-component via the third generic:
58
- * ```ts
59
- * class MyPage extends PageComponent<Params, Data, AppContext> {}
60
- * ```
61
- */
62
- export type ContextProvider = (base: ComponentContext) => ComponentContext;
63
-
64
- /**
65
- * Render context determines how components are rendered.
66
- */
67
- export type RenderContext = 'markdown' | 'html' | 'spa';
68
-
69
- /**
70
- * Abstract base class for all components.
71
- *
72
- * Subclasses must implement:
73
- * - name: unique identifier for custom element tag
74
- * - getData(): fetch/compute data
75
- * - renderMarkdown(): render as markdown
76
- *
77
- * Optional override:
78
- * - renderHTML(): custom HTML rendering (defaults to markdown→HTML conversion)
79
- * - validateParams(): params validation
80
- *
81
- * @typeParam TContext — custom context shape; defaults to ComponentContext.
82
- * Use with `extendContext` on the router to inject app-level services.
83
- * See {@link ContextProvider} for details.
84
- */
85
- export abstract class Component<
86
- TParams = unknown,
87
- TData = unknown,
88
- TContext extends ComponentContext = ComponentContext,
89
- > {
90
- /** Type carrier for getData args — use as `this['DataArgs']` in overrides. */
91
- declare readonly DataArgs: {
92
- params: TParams;
93
- signal?: AbortSignal;
94
- context: TContext;
95
- };
96
-
97
- /** Type carrier for render args — use as `this['RenderArgs']` in overrides. */
98
- declare readonly RenderArgs: {
99
- data: TData | null;
100
- params: TParams;
101
- context: TContext;
102
- };
103
-
104
- /** Unique name in kebab-case. Used for custom element: `<widget-{name}>` */
105
- abstract readonly name: string;
106
-
107
- /** Host element reference, set by ComponentElement in the browser. */
108
- element?: HTMLElement | undefined;
109
-
110
- /** Associated file paths for pre-loaded content (html, md, css). */
111
- readonly files?: { html?: string; md?: string; css?: string };
112
-
113
- /**
114
- * When true, SSR serializes the getData() result into the element's
115
- * light DOM so the client can access it immediately in hydrate()
116
- * without re-fetching.
117
- *
118
- * Default is false — hydrate() receives `data: null`. Most widgets
119
- * don't need this because the rendered Shadow DOM already contains
120
- * the visual representation of the data.
121
- *
122
- * If you find yourself parsing the shadow DOM in hydrate() trying to
123
- * reconstruct the original data object, set this to true instead.
124
- * The server-fetched data will be available as `args.data` in hydrate().
125
- */
126
- readonly exposeSsrData?: boolean;
127
-
128
- /**
129
- * Fetch or compute data based on params.
130
- * Called server-side for SSR, client-side for SPA.
131
- *
132
- * @example
133
- * ```ts
134
- * override async getData({ params, signal }: this['DataArgs']) {
135
- * const res = await fetch(`/api/${params.id}`, { signal });
136
- * return res.json();
137
- * }
138
- * ```
139
- */
140
- abstract getData(args: this['DataArgs']): Promise<TData | null>;
141
-
142
- /**
143
- * Render as markdown.
144
- * This is the canonical content representation.
145
- *
146
- * @example
147
- * ```ts
148
- * override renderMarkdown({ data }: this['RenderArgs']) {
149
- * return `# ${data?.title}`;
150
- * }
151
- * ```
152
- */
153
- abstract renderMarkdown(args: this['RenderArgs']): string;
154
-
155
- /**
156
- * Render as HTML for browser context.
157
- *
158
- * Default implementation converts renderMarkdown() output to HTML.
159
- * Override for custom HTML rendering with rich styling/interactivity.
160
- */
161
- renderHTML(args: this['RenderArgs']): string {
162
- if (args.data === null) {
163
- return `<div data-component="${this.name}">Loading...</div>`;
164
- }
165
- // Default: wrap markdown in a container
166
- // The actual markdown→HTML conversion happens at render time
167
- const markdown = this.renderMarkdown({
168
- data: args.data,
169
- params: args.params,
170
- context: args.context,
171
- });
172
- return `<div data-component="${this.name}" data-markdown>${escapeHtml(markdown)}</div>`;
173
- }
174
-
175
- /**
176
- * Hydration hook called after SSR content is adopted or after SPA rendering.
177
- * Use to attach event listeners to existing DOM without re-rendering.
178
- *
179
- * @example
180
- * ```ts
181
- * override hydrate({ data, params, context }: this['RenderArgs']) {
182
- * const button = this.element?.querySelector('button');
183
- * button?.addEventListener('click', () => this.deleteItem(data.id));
184
- * }
185
- * ```
186
- */
187
- hydrate?(args: this['RenderArgs']): void;
188
-
189
- /**
190
- * Cleanup hook called when the component is removed from the DOM.
191
- * Use for clearing timers, removing event listeners, unmounting
192
- * third-party renderers, closing connections, etc.
193
- *
194
- * Intentionally synchronous (called from disconnectedCallback). You can
195
- * fire async cleanup here, but it will not be awaited.
196
- */
197
- destroy?(): void;
198
-
199
- /**
200
- * Validate params.
201
- * @returns Error message if invalid, undefined if valid.
202
- */
203
- validateParams?(params: TParams): string | undefined;
204
-
205
- /**
206
- * Render error state.
207
- */
208
- renderError(args: { error: unknown; params: TParams }): string {
209
- const msg = args.error instanceof Error ? args.error.message : String(args.error);
210
- return `<div data-component="${this.name}">Error: ${escapeHtml(msg)}</div>`;
211
- }
212
-
213
- /**
214
- * Render error as markdown.
215
- */
216
- renderMarkdownError(error: unknown): string {
217
- const msg = error instanceof Error ? error.message : String(error);
218
- return `> **Error** (\`${this.name}\`): ${msg}`;
219
- }
220
- }
221
-
222
- /**
223
- * Component manifest entry for code generation.
224
- */
225
- export interface ComponentManifestEntry {
226
- name: string;
227
- modulePath: string;
228
- tagName: string;
229
- type: 'page' | 'widget';
230
- pattern?: string;
231
- }
@@ -1,85 +0,0 @@
1
- /**
2
- * WidgetComponent — embeddable unit within page content.
3
- *
4
- * Everything reusable that is not a page is a Widget.
5
- * Widgets render across all contexts (HTML, Markdown, SPA) and are
6
- * resolved by name via WidgetRegistry.
7
- *
8
- * Pages live in the routes manifest. Widgets live in the registry.
9
- *
10
- * Default rendering fallback chains (parallel to PageComponent):
11
- * - renderHTML: html file → md file in <mark-down> → base Component default
12
- * - renderMarkdown: md file → ''
13
- */
14
-
15
- import { Component, type ComponentContext } from './abstract.component.ts';
16
- import { escapeHtml, scopeWidgetCss } from '../util/html.util.ts';
17
-
18
- export abstract class WidgetComponent<
19
- TParams = unknown,
20
- TData = unknown,
21
- TContext extends ComponentContext = ComponentContext,
22
- > extends Component<TParams, TData, TContext> {
23
- /**
24
- * Render widget as HTML.
25
- *
26
- * Fallback chain:
27
- * 1. html file content from context
28
- * 2. md file content wrapped in `<mark-down>`
29
- * 3. base Component default (markdown→HTML conversion)
30
- *
31
- * @example
32
- * ```ts
33
- * override renderHTML({ data, params }: this['RenderArgs']) {
34
- * return `<span>${params.coin}: $${data?.price}</span>`;
35
- * }
36
- * ```
37
- */
38
- override renderHTML(
39
- args: this['RenderArgs'],
40
- ): string {
41
- const files = args.context.files;
42
- // @scope needed for SSR Light DOM output; redundant but harmless in SPA Shadow DOM
43
- const style = files?.css ? `<style>${scopeWidgetCss(files.css, this.name)}</style>\n` : '';
44
-
45
- if (files?.html) {
46
- return style + files.html;
47
- }
48
-
49
- if (files?.md) {
50
- return `${style}<mark-down>${escapeHtml(files.md)}</mark-down>`;
51
- }
52
-
53
- if (style) {
54
- return style + super.renderHTML(args);
55
- }
56
-
57
- return super.renderHTML(args);
58
- }
59
-
60
- /**
61
- * Render widget as Markdown.
62
- *
63
- * Fallback chain:
64
- * 1. md file content from context
65
- * 2. empty string
66
- *
67
- * @example
68
- * ```ts
69
- * override renderMarkdown({ data, params }: this['RenderArgs']) {
70
- * return `**${params.coin}**: $${data?.price}`;
71
- * }
72
- * ```
73
- */
74
- override renderMarkdown(
75
- args: this['RenderArgs'],
76
- ): string {
77
- const files = args.context.files;
78
-
79
- if (files?.md) {
80
- return files.md;
81
- }
82
-
83
- return '';
84
- }
85
- }