@emkodev/emroute 1.0.3 → 1.6.0-beta.1
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/LICENSE +28 -0
- package/README.md +142 -12
- package/package.json +48 -7
- package/runtime/abstract.runtime.ts +439 -0
- package/runtime/bun/esbuild-runtime-loader.plugin.ts +94 -0
- package/runtime/bun/fs/bun-fs.runtime.ts +244 -0
- package/runtime/bun/sqlite/bun-sqlite.runtime.ts +278 -0
- package/runtime/sitemap.generator.ts +180 -0
- package/server/codegen.util.ts +66 -0
- package/server/emroute.server.ts +401 -0
- package/server/esbuild-manifest.plugin.ts +243 -0
- package/server/scanner.util.ts +243 -0
- package/server/server-api.type.ts +90 -0
- package/src/component/abstract.component.ts +229 -0
- package/src/component/page.component.ts +134 -0
- package/src/component/widget.component.ts +85 -0
- package/src/element/component.element.ts +353 -0
- package/src/element/markdown.element.ts +107 -0
- package/src/element/slot.element.ts +31 -0
- package/src/index.ts +61 -0
- package/src/overlay/mod.ts +10 -0
- package/src/overlay/overlay.css.ts +170 -0
- package/src/overlay/overlay.service.ts +348 -0
- package/src/overlay/overlay.type.ts +38 -0
- package/src/renderer/spa/base.renderer.ts +186 -0
- package/src/renderer/spa/hash.renderer.ts +215 -0
- package/src/renderer/spa/html.renderer.ts +382 -0
- package/src/renderer/spa/mod.ts +76 -0
- package/src/renderer/ssr/html.renderer.ts +159 -0
- package/src/renderer/ssr/md.renderer.ts +142 -0
- package/src/renderer/ssr/ssr.renderer.ts +286 -0
- package/src/route/route.core.ts +316 -0
- package/src/route/route.matcher.ts +260 -0
- package/src/type/logger.type.ts +24 -0
- package/src/type/markdown.type.ts +21 -0
- package/src/type/navigation-api.d.ts +95 -0
- package/src/type/route.type.ts +149 -0
- package/src/type/widget.type.ts +65 -0
- package/src/util/html.util.ts +186 -0
- package/src/util/logger.util.ts +83 -0
- package/src/util/widget-resolve.util.ts +197 -0
- package/src/web-doc/index.md +15 -0
- package/src/widget/breadcrumb.widget.ts +106 -0
- package/src/widget/page-title.widget.ts +52 -0
- package/src/widget/widget.parser.ts +89 -0
- package/src/widget/widget.registry.ts +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024, emko.dev
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
CHANGED
|
@@ -1,22 +1,152 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="doc/logo-full.png" alt="emroute" width="197" height="40">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<p align="center">
|
|
6
|
+
File-based router with triple rendering. Zero dependencies.
|
|
7
|
+
</p>
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
---
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
Every route renders three ways from the same component: as a **Single Page App**
|
|
12
|
+
in the browser, as **server-rendered HTML**, and as **plain Markdown**. No
|
|
13
|
+
separate API layer needed — prefix any route with `/md/` and get text that LLMs,
|
|
14
|
+
scripts, and `curl` can consume directly.
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
GET /projects/42 → SPA (hydrated in browser)
|
|
18
|
+
GET /html/projects/42 → pre-rendered HTML
|
|
19
|
+
GET /md/projects/42 → plain Markdown
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Install
|
|
8
23
|
|
|
9
24
|
```bash
|
|
10
|
-
|
|
11
|
-
|
|
25
|
+
bun add @emkodev/emroute
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
> emroute ships TypeScript source. Your toolchain must handle `.ts` imports
|
|
29
|
+
> (Bun, tsx, esbuild, etc.).
|
|
30
|
+
|
|
31
|
+
## How It Works
|
|
32
|
+
|
|
33
|
+
<p align="center">
|
|
34
|
+
<img src="doc/diagram-full.png" alt="emroute architecture" width="480" height="480">
|
|
35
|
+
</p>
|
|
36
|
+
|
|
37
|
+
One component, three rendering paths:
|
|
38
|
+
|
|
39
|
+
<p align="center">
|
|
40
|
+
<img src="doc/diagram-flow-spa.png" alt="SPA flow" width="320" height="320">
|
|
41
|
+
<img src="doc/diagram-flow-ssr-html.png" alt="SSR HTML flow" width="320" height="320">
|
|
42
|
+
<img src="doc/diagram-flow-ssr-md.png" alt="SSR Markdown flow" width="320" height="320">
|
|
43
|
+
</p>
|
|
44
|
+
|
|
45
|
+
The SPA and SSR HTML flows both call `renderHTML()` — same output, different
|
|
46
|
+
delivery. The SSR Markdown flow calls `renderMarkdown()` instead, bypassing
|
|
47
|
+
HTML entirely for plain text output.
|
|
48
|
+
|
|
49
|
+
Routes are files. The filesystem is the config.
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
routes/
|
|
53
|
+
index.page.md → /
|
|
54
|
+
about.page.html → /about
|
|
55
|
+
projects.page.md → /projects
|
|
56
|
+
projects/
|
|
57
|
+
[id].page.ts → /projects/:id
|
|
58
|
+
[id]/
|
|
59
|
+
tasks.page.ts → /projects/:id/tasks
|
|
60
|
+
404.page.html → not found
|
|
61
|
+
index.error.ts → root error handler
|
|
62
|
+
```
|
|
12
63
|
|
|
13
|
-
|
|
14
|
-
|
|
64
|
+
A route can be a `.md` file, an `.html` template, a `.ts` component, or a
|
|
65
|
+
combination. When a `.page.ts` exists, it controls data fetching and rendering.
|
|
66
|
+
When it doesn't, the framework renders the `.html` or `.md` file directly.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { PageComponent } from '@emkodev/emroute';
|
|
70
|
+
|
|
71
|
+
class ProjectPage extends PageComponent<{ id: string }, ProjectData> {
|
|
72
|
+
override readonly name = 'project';
|
|
73
|
+
|
|
74
|
+
override async getData({ params }: this['DataArgs']) {
|
|
75
|
+
const res = await fetch(`/api/projects/${params.id}`);
|
|
76
|
+
return res.json();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
override renderHTML({ data, params, context }: this['RenderArgs']) {
|
|
80
|
+
// context.files.html has the companion .page.html template if it exists
|
|
81
|
+
const template = context.files?.html ?? `<h1>${data.name}</h1>`;
|
|
82
|
+
return template.replaceAll('{{id}}', params.id) + '<router-slot></router-slot>';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
override renderMarkdown({ data, context }: this['RenderArgs']) {
|
|
86
|
+
// context.files.md has the companion .page.md content if it exists
|
|
87
|
+
return context.files?.md ?? `# ${data.name}\n\nStatus: ${data.status}`;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export default new ProjectPage();
|
|
15
92
|
```
|
|
16
93
|
|
|
94
|
+
## Features
|
|
95
|
+
|
|
96
|
+
- **File-based routing** with dynamic segments (`[id]`), catch-all directories, and nested layouts via `<router-slot>`. Routes follow REST conventions: exact routes are terminal resources, catch-all directories own their namespace
|
|
97
|
+
- **Triple rendering** — SPA, SSR HTML, SSR Markdown from one component
|
|
98
|
+
- **Companion files** — `.page.html`, `.page.md`, `.page.css` loaded automatically and passed through context
|
|
99
|
+
- **Widgets** — interactive islands with their own data lifecycle, error handling, and optional file companions (`.html`, `.md`, `.css`). Auto-discovered from a `widgets/` directory or registered manually. `this.element` gives opt-in DOM access in the browser. `<widget-foo lazy>` defers loading until visible via `IntersectionObserver`
|
|
100
|
+
- **View Transitions** — SPA route changes animate via `document.startViewTransition()`. Progressive enhancement with CSS-only customization
|
|
101
|
+
- **Scoped CSS** — companion `.widget.css` files auto-wrapped in `@scope (widget-{name}) { ... }`
|
|
102
|
+
- **Shadow DOM** — unified Declarative Shadow DOM architecture for SSR and SPA. Widgets render into shadow roots for true CSS encapsulation and Web Components spec compliance
|
|
103
|
+
- **SSR hydration** — server-rendered HTML adopted by the SPA without re-rendering. Widgets can implement `hydrate(args)` to attach event listeners after SSR adoption, receiving `{ data, params, context }`
|
|
104
|
+
- **Error boundaries** — scoped error handlers per route prefix, plus status pages (`404.page.html`) and a root fallback
|
|
105
|
+
- **Extensible context** — inject app-level services (RPC clients, auth, feature flags) into every component via `extendContext` on the router. Type-safe access through module augmentation or a per-component generic
|
|
106
|
+
- **Declarative overlays** — popovers, modals, and toasts with zero JS via Invoker Commands API and CSS keyframe animations. Programmatic API available for dynamic content
|
|
107
|
+
- **Zero dependencies** — native APIs only (URLPattern, custom elements, Navigation API). No framework runtime, no virtual DOM, no build-time magic
|
|
108
|
+
- **Pluggable markdown** — `<mark-down>` custom element with a swappable parser interface; bring your own renderer
|
|
109
|
+
- **Redirects** — declarative `.redirect.ts` files with 301/302 support
|
|
110
|
+
- **Configurable base paths** — `/html/` and `/md/` prefixes are configurable via `BasePath`
|
|
111
|
+
- **SPA modes** — `'root'` (default), `'leaf'`, `'none'`, or `'only'` to control how the server handles non-file requests and SSR endpoints
|
|
112
|
+
- **Sitemap generation** — opt-in `sitemap.xml` from the routes manifest with support for dynamic route enumerators
|
|
113
|
+
- **Dev server** — zero-config: auto-generates `main.ts`, `index.html`, and route/widget manifests. File watcher with hot reload and bundle serving
|
|
114
|
+
|
|
115
|
+
## Why Bun?
|
|
116
|
+
|
|
117
|
+
emroute 1.5.x shipped on JSR (Deno's registry). Starting with 1.6.0, emroute
|
|
118
|
+
publishes to npm and targets Bun as the primary runtime.
|
|
119
|
+
|
|
120
|
+
**TL;DR:** JSR's design freezes the entire module graph at publish time. This
|
|
121
|
+
breaks dynamic `import()` of consumer dependencies, peer dependency
|
|
122
|
+
deduplication, and runtime resolution of package entry points for bundling — all
|
|
123
|
+
things a framework with plugin architecture needs. The npm/`node_modules` model
|
|
124
|
+
handles them with zero friction.
|
|
125
|
+
|
|
126
|
+
Full analysis with documentation and issue references:
|
|
127
|
+
[ADR-0017 — Move to Bun ecosystem](doc/architecture/ADR-0017-move-to-bun-ecosystem.md).
|
|
128
|
+
|
|
129
|
+
## Getting Started
|
|
130
|
+
|
|
131
|
+
See [Setup](doc/01-setup.md) and [First Route](doc/02-first-route.md).
|
|
132
|
+
|
|
17
133
|
## Documentation
|
|
18
134
|
|
|
19
|
-
- [
|
|
20
|
-
- [
|
|
21
|
-
- [
|
|
22
|
-
- [
|
|
135
|
+
- [Setup](doc/01-setup.md) — install and create a server
|
|
136
|
+
- [First route](doc/02-first-route.md) — create your first page
|
|
137
|
+
- [Pages](doc/03-pages.md) — page components, companion files, data fetching
|
|
138
|
+
- [Routing](doc/04-routing.md) — dynamic segments, catch-all, redirects
|
|
139
|
+
- [Nesting](doc/05-nesting.md) — layouts, slots, passthrough pages, tips and tricks
|
|
140
|
+
- [Widgets](doc/06-widgets.md) — interactive islands with data lifecycle
|
|
141
|
+
- [Server](doc/07-server.md) — `createEmrouteServer`, composition, static files
|
|
142
|
+
- [Markdown renderers](doc/08-markdown-renderer.md) — pluggable parser interface and setup
|
|
143
|
+
- [Runtime](doc/09-runtime.md) — abstract runtime, BunFsRuntime, BunSqliteRuntime
|
|
144
|
+
- [SPA modes](doc/10-spa-mode.md) — none, leaf, root, only
|
|
145
|
+
- [Error handling](doc/11-error-handling.md) — widget errors, boundaries, status pages
|
|
146
|
+
- [Shadow DOM](doc/12-shadow-dom.md) — unified architecture, SSR hydration
|
|
147
|
+
|
|
148
|
+
### For contributors and architects
|
|
149
|
+
|
|
150
|
+
- [Architectural decisions](doc/architecture/) — ADR-0001 through ADR-0017
|
|
151
|
+
|
|
152
|
+
<img src="doc/logo-full.png" alt="emroute" width="197" height="40">
|
package/package.json
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emkodev/emroute",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.6.0-beta.1",
|
|
4
4
|
"description": "File-based router with triple rendering (SPA, SSR HTML, SSR Markdown). Zero dependencies.",
|
|
5
5
|
"license": "BSD-3-Clause",
|
|
6
6
|
"author": "emko.dev",
|
|
7
|
-
"
|
|
8
|
-
"repository": {
|
|
9
|
-
"type": "git",
|
|
10
|
-
"url": "https://github.com/vedokme/emroute.git"
|
|
11
|
-
},
|
|
7
|
+
"type": "module",
|
|
12
8
|
"keywords": [
|
|
13
9
|
"router",
|
|
14
10
|
"routing",
|
|
@@ -16,7 +12,52 @@
|
|
|
16
12
|
"ssr",
|
|
17
13
|
"file-based",
|
|
18
14
|
"framework",
|
|
15
|
+
"bun",
|
|
19
16
|
"typescript",
|
|
20
17
|
"markdown"
|
|
21
|
-
]
|
|
18
|
+
],
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/vedokme/emroute.git"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"src/",
|
|
25
|
+
"server/*.ts",
|
|
26
|
+
"runtime/",
|
|
27
|
+
"LICENSE",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"exports": {
|
|
31
|
+
".": "./src/index.ts",
|
|
32
|
+
"./spa": "./src/renderer/spa/mod.ts",
|
|
33
|
+
"./overlay": "./src/overlay/mod.ts",
|
|
34
|
+
"./ssr/html": "./src/renderer/ssr/html.renderer.ts",
|
|
35
|
+
"./ssr/md": "./src/renderer/ssr/md.renderer.ts",
|
|
36
|
+
"./server": "./server/emroute.server.ts",
|
|
37
|
+
"./runtime": "./runtime/abstract.runtime.ts",
|
|
38
|
+
"./runtime/sitemap": "./runtime/sitemap.generator.ts",
|
|
39
|
+
"./runtime/bun/fs": "./runtime/bun/fs/bun-fs.runtime.ts",
|
|
40
|
+
"./runtime/bun/sqlite": "./runtime/bun/sqlite/bun-sqlite.runtime.ts"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@eslint/js": "^10.0.1",
|
|
44
|
+
"@types/bun": "^1.3.9",
|
|
45
|
+
"esbuild": "^0.27.3",
|
|
46
|
+
"eslint": "^10.0.2",
|
|
47
|
+
"playwright": "^1.58.2",
|
|
48
|
+
"typescript-eslint": "^8.56.1"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"check": "bun x tsc --noEmit",
|
|
52
|
+
"test": "bun test test/unit",
|
|
53
|
+
"test:unit": "bun test test/unit",
|
|
54
|
+
"test:integration": "bun test test/integration",
|
|
55
|
+
"test:browser": "bun test test/browser",
|
|
56
|
+
"test:browser:none": "bun test test/browser/none",
|
|
57
|
+
"test:browser:leaf": "bun test test/browser/leaf",
|
|
58
|
+
"test:browser:root": "bun test test/browser/root",
|
|
59
|
+
"test:browser:only": "bun test test/browser/only",
|
|
60
|
+
"lint": "eslint .",
|
|
61
|
+
"dev": "echo 'Use a custom server.ts — see doc/01-setup.md'"
|
|
62
|
+
}
|
|
22
63
|
}
|