@rasenjs/web 0.1.0-alpha
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 +263 -0
- package/dist/dom.d.ts +2 -0
- package/dist/dom.js +3 -0
- package/dist/html.d.ts +2 -0
- package/dist/html.js +3 -0
- package/package.json +26 -0
- package/src/dom.ts +6 -0
- package/src/html.ts +6 -0
- package/tsconfig.json +8 -0
- package/tsup.config.ts +18 -0
package/README.md
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# @rasenjs/web
|
|
2
|
+
|
|
3
|
+
Isomorphic web components with routing that automatically switch between DOM and HTML rendering based on the execution environment.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @rasenjs/web @rasenjs/router @rasenjs/core zod
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
`@rasenjs/web` is a unified facade package that provides both basic elements and routing components for web rendering. It automatically exports the appropriate renderer based on the environment:
|
|
14
|
+
|
|
15
|
+
- **Server-side (SSR)**: Uses `@rasenjs/html` + `@rasenjs/router-html` to render HTML strings
|
|
16
|
+
- **Client-side (Browser)**: Uses `@rasenjs/dom` + `@rasenjs/router-dom` for reactive DOM manipulation
|
|
17
|
+
|
|
18
|
+
This allows you to write **isomorphic components with routing** that work seamlessly in both environments without code changes.
|
|
19
|
+
|
|
20
|
+
## How It Works
|
|
21
|
+
|
|
22
|
+
The package uses [conditional exports](https://nodejs.org/api/packages.html#conditional-exports) in `package.json`:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"ssr": "./dist/html.js", // Server-side rendering
|
|
29
|
+
"browser": "./dist/dom.js", // Client-side rendering
|
|
30
|
+
"default": "./dist/dom.js" // Default to browser
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Build tools like Vite automatically select the correct export condition based on the environment.
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
### Isomorphic Component
|
|
41
|
+
|
|
42
|
+
Write your component once, and it works in both SSR and client environments:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { setReactiveRuntime } from '@rasenjs/core'
|
|
46
|
+
import { createReactiveRuntime } from '@rasenjs/reactive-vue'
|
|
47
|
+
import { div, p, button } from '@rasenjs/web'
|
|
48
|
+
import { ref } from 'vue'
|
|
49
|
+
|
|
50
|
+
// Setup reactive runtime
|
|
51
|
+
setReactiveRuntime(createReactiveRuntime())
|
|
52
|
+
|
|
53
|
+
// Isomorphic component - works in both SSR and browser
|
|
54
|
+
export const Counter = () => {
|
|
55
|
+
const count = ref(0)
|
|
56
|
+
const increment = () => count.value++
|
|
57
|
+
|
|
58
|
+
return div({
|
|
59
|
+
children: [
|
|
60
|
+
p(() => `Count: ${count.value}`),
|
|
61
|
+
button({ onClick: increment }, 'Increment')
|
|
62
|
+
]
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Server-Side Rendering (SSR)
|
|
68
|
+
|
|
69
|
+
On the server, `@rasenjs/web` renders to HTML strings:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import { renderToString } from '@rasenjs/web'
|
|
73
|
+
import { Counter } from './Counter'
|
|
74
|
+
|
|
75
|
+
// Renders to HTML string
|
|
76
|
+
const html = renderToString(Counter())
|
|
77
|
+
// Result: '<div><p>Count: 0</p><button>Increment</button></div>'
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Client-Side Hydration
|
|
81
|
+
|
|
82
|
+
On the client, `@rasenjs/web` creates interactive DOM elements:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { mount } from '@rasenjs/web'
|
|
86
|
+
import { Counter } from './Counter'
|
|
87
|
+
|
|
88
|
+
// Mounts and hydrates the component
|
|
89
|
+
mount(Counter(), document.getElementById('app'))
|
|
90
|
+
// Now the button is interactive and updates the counter
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## API
|
|
94
|
+
|
|
95
|
+
`@rasenjs/web` re-exports all APIs from the appropriate renderer and router:
|
|
96
|
+
|
|
97
|
+
### SSR Mode (Server)
|
|
98
|
+
|
|
99
|
+
Exports everything from `@rasenjs/html` + `@rasenjs/router-html`:
|
|
100
|
+
|
|
101
|
+
- `renderToString()` - Render component to HTML string
|
|
102
|
+
- `div()`, `p()`, `button()`, etc. - HTML element creators
|
|
103
|
+
- `RouterView()` - Render matched route to HTML
|
|
104
|
+
- `RouterLink()` - Create `<a>` tag with href
|
|
105
|
+
- All standard HTML elements
|
|
106
|
+
|
|
107
|
+
### Browser Mode (Client)
|
|
108
|
+
|
|
109
|
+
Exports everything from `@rasenjs/dom` + `@rasenjs/router-dom`:
|
|
110
|
+
|
|
111
|
+
- `mount()` - Mount component to DOM
|
|
112
|
+
- `div()`, `p()`, `button()`, etc. - DOM element creators
|
|
113
|
+
- `RouterView()` - Render matched route to DOM
|
|
114
|
+
- `RouterLink()` - Create interactive `<a>` with click handling
|
|
115
|
+
- All standard HTML elements with event handling
|
|
116
|
+
|
|
117
|
+
## Important Considerations
|
|
118
|
+
|
|
119
|
+
### 1. Event Handlers
|
|
120
|
+
|
|
121
|
+
Event handlers only work in the browser. On the server, they are ignored:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// ✅ Safe - works in browser, ignored in SSR
|
|
125
|
+
button({ onClick: handler }, 'Click me')
|
|
126
|
+
|
|
127
|
+
// ❌ Avoid - 'on' object would render as HTML attribute in SSR
|
|
128
|
+
button({ on: { click: handler } }, 'Click me')
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Best Practice**: Use `onClick`, `onInput`, etc. (not `on: { click }` object).
|
|
132
|
+
|
|
133
|
+
### 2. Reactive Text Functions
|
|
134
|
+
|
|
135
|
+
Reactive text functions work in both environments:
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
// ✅ Works in both SSR and browser
|
|
139
|
+
p(() => `Count: ${count.value}`)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
- **SSR**: Evaluates once and renders the initial value
|
|
143
|
+
- **Browser**: Sets up reactivity and updates on changes
|
|
144
|
+
|
|
145
|
+
### 3. Import Path
|
|
146
|
+
|
|
147
|
+
Always import from `@rasenjs/web`, not from `@rasenjs/dom` or `@rasenjs/html` directly:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// ✅ Correct - automatic environment detection
|
|
151
|
+
import { div, mount } from '@rasenjs/web'
|
|
152
|
+
|
|
153
|
+
// Routing
|
|
154
|
+
|
|
155
|
+
### Define Routes
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { createRouter, createMemoryHistory, template as tpl } from '@rasenjs/router'
|
|
159
|
+
import { z } from 'zod'
|
|
160
|
+
|
|
161
|
+
export const router = createRouter({
|
|
162
|
+
home: '/',
|
|
163
|
+
about: '/about',
|
|
164
|
+
user: tpl`/user/${{ id: z.string() }}`,
|
|
165
|
+
}, {
|
|
166
|
+
history: createMemoryHistory(),
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
export const { routes } = router
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Use Router Components
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
import { RouterView, RouterLink, div, nav } from '@rasenjs/web'
|
|
176
|
+
import { router, routes } from './router'
|
|
177
|
+
|
|
178
|
+
export const App = () => div({},
|
|
179
|
+
nav({},
|
|
180
|
+
RouterLink({ to: routes.home }, 'Home'),
|
|
181
|
+
RouterLink({ to: routes.about }, 'About'),
|
|
182
|
+
RouterLink({ to: 'user', params: { id: 'alice' } }, 'User')
|
|
183
|
+
),
|
|
184
|
+
RouterView(router, {
|
|
185
|
+
[routes.home]: () => Home(),
|
|
186
|
+
[routes.about]: () => About(),
|
|
187
|
+
[routes.user]: (params) => User(params)
|
|
188
|
+
})
|
|
189
|
+
)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Use Cases
|
|
193
|
+
|
|
194
|
+
### Universal Components with Routing
|
|
195
|
+
|
|
196
|
+
Build full-featured apps that work in any environment:
|
|
197
|
+
|
|
198
|
+
```typescriptRouterView` and `RouterLink`
|
|
199
|
+
- Dynamic route parameters
|
|
200
|
+
- Hot module replacement (HMR)
|
|
201
|
+
|
|
202
|
+
## Related Packages
|
|
203
|
+
|
|
204
|
+
- [`@rasenjs/router`](../router) - Core headless router
|
|
205
|
+
- [`@rasenjs/dom`](../dom) - DOM renderer (browser)
|
|
206
|
+
- [`@rasenjs/html`](../html) - HTML renderer (SSR)
|
|
207
|
+
- [`@rasenjs/router-dom`](../router-dom) - DOM router components (internal)
|
|
208
|
+
- [`@rasenjs/router-html`](../router-html) - HTML router components (internal)
|
|
209
|
+
|
|
210
|
+
### SSR with Client Hydration and Routing
|
|
211
|
+
|
|
212
|
+
Perfect for server-rendered apps with client interactivity and navigation:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
// server.ts
|
|
216
|
+
import { renderToString } from '@rasenjs/web'
|
|
217
|
+
import { router } from './router'
|
|
218
|
+
|
|
219
|
+
const html = (url: string) => {
|
|
220
|
+
router.push(url) // Set route from request URL
|
|
221
|
+
return `<!DOCTYPE html>
|
|
222
|
+
<html>
|
|
223
|
+
<body>
|
|
224
|
+
<div id="app">${renderToString(App())}</div>
|
|
225
|
+
<script type="module" src="/client.js"></script>
|
|
226
|
+
</body>
|
|
227
|
+
</html>`
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// client.ts
|
|
231
|
+
import { mount } from '@rasenjs/web'
|
|
232
|
+
import { createBrowserHistory } from '@rasenjs/router'
|
|
233
|
+
import { router } from './router'
|
|
234
|
+
|
|
235
|
+
router.history = createBrowserHistory()
|
|
236
|
+
</html>`
|
|
237
|
+
|
|
238
|
+
// client.ts
|
|
239
|
+
import { mount } from '@rasenjs/web'
|
|
240
|
+
mount(App(), document.getElementById('app'))
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Complete Example
|
|
244
|
+
|
|
245
|
+
See the [SSR example](../../examples/ssr) for a full implementation with:
|
|
246
|
+
|
|
247
|
+
- Server-side rendering
|
|
248
|
+
- Client-side hydration
|
|
249
|
+
- Routing with `RouterView` and `RouterLink`
|
|
250
|
+
- Dynamic route parameters
|
|
251
|
+
- Hot module replacement (HMR)
|
|
252
|
+
|
|
253
|
+
## Related Packages
|
|
254
|
+
|
|
255
|
+
- [`@rasenjs/router`](../router) - Core headless router
|
|
256
|
+
- [`@rasenjs/dom`](../dom) - DOM renderer (browser)
|
|
257
|
+
- [`@rasenjs/html`](../html) - HTML renderer (SSR)
|
|
258
|
+
- [`@rasenjs/router-dom`](../router-dom) - DOM router components (internal)
|
|
259
|
+
- [`@rasenjs/router-html`](../router-html) - HTML router components (internal)
|
|
260
|
+
|
|
261
|
+
## License
|
|
262
|
+
|
|
263
|
+
MIT
|
package/dist/dom.d.ts
ADDED
package/dist/dom.js
ADDED
package/dist/html.d.ts
ADDED
package/dist/html.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rasenjs/web",
|
|
3
|
+
"version": "0.1.0-alpha",
|
|
4
|
+
"description": "Isomorphic web components with routing (exports DOM or HTML based on environment)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"ssr": "./dist/html.js",
|
|
9
|
+
"browser": "./dist/dom.js",
|
|
10
|
+
"default": "./dist/dom.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@rasenjs/dom": "0.1.5-alpha",
|
|
18
|
+
"@rasenjs/html": "0.1.1-alpha",
|
|
19
|
+
"@rasenjs/router-dom": "0.1.6-alpha",
|
|
20
|
+
"@rasenjs/router-html": "0.1.0-alpha"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"tsup": "^8.0.1",
|
|
24
|
+
"typescript": "^5.3.3"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/dom.ts
ADDED
package/src/html.ts
ADDED
package/tsconfig.json
ADDED
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig } from 'tsup'
|
|
2
|
+
|
|
3
|
+
export default defineConfig([
|
|
4
|
+
{
|
|
5
|
+
entry: { dom: 'src/dom.ts' },
|
|
6
|
+
format: ['esm'],
|
|
7
|
+
dts: true,
|
|
8
|
+
clean: true,
|
|
9
|
+
external: ['@rasenjs/dom']
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
entry: { html: 'src/html.ts' },
|
|
13
|
+
format: ['esm'],
|
|
14
|
+
dts: true,
|
|
15
|
+
clean: false,
|
|
16
|
+
external: ['@rasenjs/html']
|
|
17
|
+
}
|
|
18
|
+
])
|