@devera_se/bedrockjs 0.1.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/README.md ADDED
@@ -0,0 +1,519 @@
1
+ ![BedrockJS](bedrockjs.png)
2
+
3
+ A lightweight web framework built on Web Components with lit-html-style templating, reactive state management, and a router with async data fetching.
4
+
5
+ ## Features
6
+
7
+ - **Tagged Template Literals** - Declarative UI with `html` tagged templates
8
+ - **Web Components** - Native custom elements with reactive properties
9
+ - **Efficient DOM Updates** - Comment-marker-based patching (no virtual DOM)
10
+ - **Reactive State** - Proxy-based reactivity with automatic dependency tracking
11
+ - **Router** - History API routing with async data loaders
12
+ - **Shadow DOM Support** - Optional style encapsulation per component
13
+ - **Zero Dependencies** - Pure JavaScript, no build step required
14
+
15
+ ## Installation
16
+
17
+ JSR (Deno or `jsr:` import):
18
+
19
+ ```bash
20
+ deno add jsr:@devera/bedrockjs
21
+ ```
22
+
23
+ NPM (via JSR CLI):
24
+
25
+ ```bash
26
+ npx jsr add @devera/bedrockjs
27
+ ```
28
+
29
+ NPM (via JSR package):
30
+
31
+ ```bash
32
+ npm install @jsr/devera__bedrockjs
33
+ ```
34
+
35
+ PNPM:
36
+
37
+ ```bash
38
+ pnpm add jsr:@devera/bedrockjs
39
+ ```
40
+
41
+ Yarn:
42
+
43
+ ```bash
44
+ yarn add jsr:@devera/bedrockjs
45
+ ```
46
+
47
+ VLT:
48
+
49
+ ```bash
50
+ vlt add jsr:@devera/bedrockjs
51
+ ```
52
+
53
+ Or include directly in your HTML:
54
+
55
+ ```html
56
+ <script type="module">
57
+ import { html, Component, createRouter } from './src/index.js';
58
+ </script>
59
+ ```
60
+
61
+ ## Quick Start
62
+
63
+ ### Hello World
64
+
65
+ ```javascript
66
+ import { html, Component } from '@devera/bedrockjs';
67
+
68
+ class HelloWorld extends Component {
69
+ static tag = 'hello-world';
70
+ static properties = {
71
+ name: { type: String, default: 'World' }
72
+ };
73
+
74
+ render() {
75
+ return html`<h1>Hello, ${this.name}!</h1>`;
76
+ }
77
+ }
78
+
79
+ HelloWorld.register();
80
+ ```
81
+
82
+ ```html
83
+ <hello-world name="BedrockJS"></hello-world>
84
+ ```
85
+
86
+ ### Counter with Events
87
+
88
+ ```javascript
89
+ import { html, Component } from '@devera/bedrockjs';
90
+
91
+ class MyCounter extends Component {
92
+ static tag = 'my-counter';
93
+ static properties = {
94
+ count: { type: Number, default: 0 }
95
+ };
96
+
97
+ render() {
98
+ return html`
99
+ <div>
100
+ <p>Count: ${this.count}</p>
101
+ <button on-click=${() => this.count++}>Increment</button>
102
+ <button on-click=${() => this.count--}>Decrement</button>
103
+ </div>
104
+ `;
105
+ }
106
+ }
107
+
108
+ MyCounter.register();
109
+ ```
110
+
111
+ ## Core Concepts
112
+
113
+ ### Templates
114
+
115
+ BedrockJS uses tagged template literals for declarative UI:
116
+
117
+ ```javascript
118
+ import { html } from '@devera/bedrockjs';
119
+
120
+ const template = html`
121
+ <div class=${className}>
122
+ <input .value=${text} on-input=${handleInput}>
123
+ <button on-click=${handleClick}>Submit</button>
124
+ ${items.map(item => html`<li>${item}</li>`)}
125
+ </div>
126
+ `;
127
+ ```
128
+
129
+ #### Binding Types
130
+
131
+ | Syntax | Description | Example |
132
+ |--------|-------------|---------|
133
+ | `${value}` | Text content | `<p>${message}</p>` |
134
+ | `attr=${value}` | Attribute | `<div class=${cls}>` |
135
+ | `.prop=${value}` | Property | `<input .value=${text}>` |
136
+ | `on-event=${fn}` | Event listener | `<button on-click=${handler}>` |
137
+
138
+ #### Conditional Rendering
139
+
140
+ ```javascript
141
+ render() {
142
+ return html`
143
+ <div>
144
+ ${this.isLoading
145
+ ? html`<span>Loading...</span>`
146
+ : html`<span>Ready!</span>`
147
+ }
148
+ </div>
149
+ `;
150
+ }
151
+ ```
152
+
153
+ #### List Rendering
154
+
155
+ ```javascript
156
+ import { html, keyed } from 'bedrockjs';
157
+
158
+ render() {
159
+ return html`
160
+ <ul>
161
+ ${this.items.map(item =>
162
+ keyed(item.id, html`<li>${item.name}</li>`)
163
+ )}
164
+ </ul>
165
+ `;
166
+ }
167
+ ```
168
+
169
+ Use `keyed()` for efficient updates when items can be reordered or removed.
170
+
171
+ ### Components
172
+
173
+ Components extend the `Component` base class:
174
+
175
+ ```javascript
176
+ import { html, Component } from 'bedrockjs';
177
+
178
+ class UserCard extends Component {
179
+ // Required: unique tag name
180
+ static tag = 'user-card';
181
+
182
+ // Optional: enable Shadow DOM (default: false)
183
+ static shadow = true;
184
+
185
+ // Optional: reactive properties
186
+ static properties = {
187
+ name: { type: String, default: 'Anonymous' },
188
+ age: { type: Number },
189
+ active: { type: Boolean, default: false },
190
+ data: { type: Object, default: () => ({}) }
191
+ };
192
+
193
+ // Called after each render
194
+ updated() {
195
+ console.log('Component updated');
196
+ }
197
+
198
+ // Return template
199
+ render() {
200
+ return html`
201
+ <div class="card">
202
+ <h2>${this.name}</h2>
203
+ <p>Age: ${this.age}</p>
204
+ </div>
205
+ `;
206
+ }
207
+ }
208
+
209
+ // Register with custom elements
210
+ UserCard.register();
211
+ ```
212
+
213
+ #### Property Types
214
+
215
+ | Type | Coercion | Default |
216
+ |------|----------|---------|
217
+ | `String` | `String(value)` | `undefined` |
218
+ | `Number` | `Number(value)` | `undefined` |
219
+ | `Boolean` | `Boolean(value)` | `false` |
220
+ | `Array` | Pass through | `[]` |
221
+ | `Object` | Pass through | `{}` |
222
+
223
+ Properties are automatically synced with attributes (camelCase to kebab-case):
224
+
225
+ ```html
226
+ <user-card name="John" age="30" active></user-card>
227
+ ```
228
+
229
+ #### Shadow DOM
230
+
231
+ Enable Shadow DOM for style encapsulation:
232
+
233
+ ```javascript
234
+ class StyledButton extends Component {
235
+ static tag = 'styled-button';
236
+ static shadow = true;
237
+
238
+ render() {
239
+ return html`
240
+ <style>
241
+ button {
242
+ background: blue;
243
+ color: white;
244
+ padding: 10px 20px;
245
+ }
246
+ </style>
247
+ <button><slot></slot></button>
248
+ `;
249
+ }
250
+ }
251
+ ```
252
+
253
+ ### Reactive State
254
+
255
+ For state shared between components or outside component context:
256
+
257
+ ```javascript
258
+ import { reactive, watch, computed } from 'bedrockjs';
259
+
260
+ // Create reactive object
261
+ const state = reactive({
262
+ count: 0,
263
+ items: []
264
+ });
265
+
266
+ // Watch for changes
267
+ const stopWatch = watch(() => {
268
+ console.log('Count changed:', state.count);
269
+ });
270
+
271
+ // Computed values
272
+ const doubled = computed(() => state.count * 2);
273
+ console.log(doubled.value); // Access with .value
274
+
275
+ // Update state (triggers watchers)
276
+ state.count++;
277
+ state.items.push('new item');
278
+
279
+ // Stop watching
280
+ stopWatch();
281
+ ```
282
+
283
+ #### Signals
284
+
285
+ For simple reactive values:
286
+
287
+ ```javascript
288
+ import { signal } from 'bedrockjs';
289
+
290
+ const [getCount, setCount] = signal(0);
291
+
292
+ console.log(getCount()); // 0
293
+ setCount(5);
294
+ console.log(getCount()); // 5
295
+ ```
296
+
297
+ #### Batching Updates
298
+
299
+ ```javascript
300
+ import { reactive, batch } from 'bedrockjs';
301
+
302
+ const state = reactive({ a: 1, b: 2 });
303
+
304
+ // Multiple updates trigger only one flush
305
+ batch(() => {
306
+ state.a = 10;
307
+ state.b = 20;
308
+ });
309
+ ```
310
+
311
+ ### Router
312
+
313
+ Create a single-page application with the router:
314
+
315
+ ```javascript
316
+ import { createRouter } from 'bedrockjs';
317
+
318
+ const router = createRouter({
319
+ // Optional: base path for deployment in subdirectory
320
+ base: '/app',
321
+
322
+ routes: [
323
+ {
324
+ path: '/',
325
+ component: 'home-page'
326
+ },
327
+ {
328
+ path: '/users',
329
+ component: 'users-page',
330
+ loader: async () => {
331
+ const res = await fetch('/api/users');
332
+ return res.json();
333
+ }
334
+ },
335
+ {
336
+ path: '/users/:id',
337
+ component: 'user-detail',
338
+ loader: async ({ id }) => {
339
+ const res = await fetch(`/api/users/${id}`);
340
+ return res.json();
341
+ }
342
+ }
343
+ ]
344
+ });
345
+ ```
346
+
347
+ ```html
348
+ <nav>
349
+ <router-link to="/">Home</router-link>
350
+ <router-link to="/users">Users</router-link>
351
+ </nav>
352
+
353
+ <main>
354
+ <router-outlet></router-outlet>
355
+ </main>
356
+ ```
357
+
358
+ #### Route Data
359
+
360
+ Components receive `routeData` with loading state:
361
+
362
+ ```javascript
363
+ class UserDetail extends Component {
364
+ static tag = 'user-detail';
365
+
366
+ render() {
367
+ const { loading, data, error, params } = this.routeData || {};
368
+
369
+ if (loading) {
370
+ return html`<div>Loading...</div>`;
371
+ }
372
+
373
+ if (error) {
374
+ return html`<div>Error: ${error.message}</div>`;
375
+ }
376
+
377
+ return html`
378
+ <div>
379
+ <h1>${data.name}</h1>
380
+ <p>ID: ${params.id}</p>
381
+ </div>
382
+ `;
383
+ }
384
+ }
385
+ ```
386
+
387
+ #### Programmatic Navigation
388
+
389
+ ```javascript
390
+ import { navigate } from 'bedrockjs';
391
+
392
+ // Navigate to a path
393
+ navigate('/users/123');
394
+
395
+ // Replace current history entry
396
+ navigate('/login', { replace: true });
397
+ ```
398
+
399
+ #### Route Parameters
400
+
401
+ | Pattern | URL | Params |
402
+ |---------|-----|--------|
403
+ | `/users/:id` | `/users/123` | `{ id: '123' }` |
404
+ | `/posts/:category/:slug` | `/posts/tech/hello` | `{ category: 'tech', slug: 'hello' }` |
405
+
406
+ ## API Reference
407
+
408
+ ### Template Functions
409
+
410
+ | Function | Description |
411
+ |----------|-------------|
412
+ | `html` | Tagged template literal for creating templates |
413
+ | `render(result, container)` | Render a template into a container |
414
+ | `keyed(key, template)` | Create a keyed template for list rendering |
415
+
416
+ ### Component
417
+
418
+ | Static Property | Type | Description |
419
+ |-----------------|------|-------------|
420
+ | `tag` | `string` | Custom element tag name (required) |
421
+ | `shadow` | `boolean` | Enable Shadow DOM (default: `false`) |
422
+ | `properties` | `object` | Reactive property definitions |
423
+
424
+ | Instance Property | Description |
425
+ |-------------------|-------------|
426
+ | `renderRoot` | The root element for rendering (shadow root or element) |
427
+ | `routeData` | Route data when used with router |
428
+
429
+ | Method | Description |
430
+ |--------|-------------|
431
+ | `render()` | Return template (override in subclass) |
432
+ | `updated()` | Called after each render |
433
+ | `requestUpdate()` | Manually trigger a re-render |
434
+ | `static register(tagName?)` | Register the custom element |
435
+
436
+ ### Reactive
437
+
438
+ | Function | Description |
439
+ |----------|-------------|
440
+ | `reactive(obj)` | Create a reactive proxy |
441
+ | `watch(fn, options?)` | Watch reactive dependencies, returns stop function |
442
+ | `computed(fn)` | Create a computed value (access via `.value`) |
443
+ | `signal(initial)` | Create a signal, returns `[getter, setter]` |
444
+ | `batch(fn)` | Batch multiple updates |
445
+
446
+ ### Router
447
+
448
+ | Function | Description |
449
+ |----------|-------------|
450
+ | `createRouter(options)` | Create and start a router |
451
+ | `navigate(path, options?)` | Navigate programmatically |
452
+
453
+ | Router Options | Type | Description |
454
+ |----------------|------|-------------|
455
+ | `routes` | `array` | Route definitions |
456
+ | `base` | `string` | Base path prefix |
457
+ | `hash` | `boolean` | Use hash-based routing |
458
+
459
+ | Route Definition | Type | Description |
460
+ |------------------|------|-------------|
461
+ | `path` | `string` | URL pattern with optional `:params` |
462
+ | `component` | `string` | Tag name of component to render |
463
+ | `loader` | `function` | Async function to load data |
464
+
465
+ ## Browser Support
466
+
467
+ BedrockJS uses modern JavaScript features:
468
+ - ES Modules
469
+ - Custom Elements v1
470
+ - Proxy
471
+ - Private class fields
472
+
473
+ Supported in all modern browsers (Chrome, Firefox, Safari, Edge).
474
+
475
+ ## Development
476
+
477
+ ```bash
478
+ # Clone the repository
479
+ git clone https://github.com/your-repo/bedrockjs.git
480
+ cd bedrockjs
481
+
482
+ # Install dependencies
483
+ npm install
484
+
485
+ # Start development server
486
+ npm run dev
487
+
488
+ # Open examples
489
+ open http://localhost:3000/examples/
490
+ ```
491
+
492
+ ## Architecture
493
+
494
+ ### How Templates Work
495
+
496
+ 1. **Parse**: The `html` tagged template creates a `TemplateResult` with static strings and dynamic values
497
+ 2. **Compile**: On first render, strings are joined with comment markers and parsed into a `<template>` element
498
+ 3. **Walk**: The template DOM is walked to find marker positions and create a parts array
499
+ 4. **Clone**: The template is cloned for each render instance
500
+ 5. **Patch**: Only the dynamic parts are updated on subsequent renders
501
+
502
+ ### How Reactivity Works
503
+
504
+ 1. **Proxy**: `reactive()` wraps objects in a Proxy that tracks property access
505
+ 2. **Track**: When a watcher runs, accessed properties are recorded as dependencies
506
+ 3. **Trigger**: When a property changes, dependent watchers are queued
507
+ 4. **Flush**: Queued watchers run in the next microtask (batched)
508
+
509
+ ### How Components Work
510
+
511
+ 1. **Define**: Class extends `Component` with static `tag` and `properties`
512
+ 2. **Register**: `customElements.define()` registers the element
513
+ 3. **Connect**: When added to DOM, `connectedCallback` initializes and renders
514
+ 4. **Update**: Property changes schedule a re-render via microtask
515
+ 5. **Render**: `render()` returns a template that patches the DOM
516
+
517
+ ## License
518
+
519
+ MIT
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@devera_se/bedrockjs",
3
+ "version": "0.1.1",
4
+ "description": "A lightweight web framework built on web components",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "types": "src/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./src/index.d.ts",
11
+ "default": "./src/index.js"
12
+ },
13
+ "./html": {
14
+ "types": "./src/html.d.ts",
15
+ "default": "./src/html.js"
16
+ },
17
+ "./render": {
18
+ "types": "./src/render.d.ts",
19
+ "default": "./src/render.js"
20
+ },
21
+ "./component": {
22
+ "types": "./src/component.d.ts",
23
+ "default": "./src/component.js"
24
+ },
25
+ "./reactive": {
26
+ "types": "./src/reactive.d.ts",
27
+ "default": "./src/reactive.js"
28
+ },
29
+ "./router": {
30
+ "types": "./src/router.d.ts",
31
+ "default": "./src/router.js"
32
+ }
33
+ },
34
+ "files": [
35
+ "src"
36
+ ],
37
+ "sideEffects": false,
38
+ "scripts": {
39
+ "dev": "serve -p 3000 .",
40
+ "start": "serve -p 3000 .",
41
+ "examples": "serve -p 3000 . -o /examples"
42
+ },
43
+ "devDependencies": {
44
+ "serve": "^14.2.0"
45
+ },
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/stonetwig/bedrockjs.git"
49
+ },
50
+ "bugs": {
51
+ "url": "https://github.com/stonetwig/bedrockjs/issues"
52
+ },
53
+ "homepage": "https://github.com/stonetwig/bedrockjs#readme",
54
+ "keywords": [
55
+ "web-components",
56
+ "framework",
57
+ "reactive",
58
+ "router",
59
+ "template",
60
+ "lit",
61
+ "custom-elements",
62
+ "signals"
63
+ ],
64
+ "license": "MIT"
65
+ }
@@ -0,0 +1,36 @@
1
+ import type { TemplateResult } from './html.js';
2
+
3
+ export interface ComponentPropertyConfig<T = any> {
4
+ type?: { new (...args: any[]): T } | Function;
5
+ default?: T | (() => T);
6
+ }
7
+
8
+ export type ComponentProperties = Record<string, ComponentPropertyConfig | Function>;
9
+
10
+ export class Component extends HTMLElement {
11
+ static tag: string | null;
12
+ static shadow: boolean;
13
+ static properties: ComponentProperties;
14
+ static autoRegister: boolean;
15
+
16
+ readonly renderRoot: Element | ShadowRoot;
17
+
18
+ routeData: any;
19
+
20
+ render(): TemplateResult | null;
21
+ updated(): void;
22
+ requestUpdate(): void;
23
+
24
+ static register(tagName?: string): typeof Component;
25
+ }
26
+
27
+ export function defineComponent(
28
+ tag: string,
29
+ options?: {
30
+ shadow?: boolean;
31
+ properties?: ComponentProperties;
32
+ autoRegister?: boolean;
33
+ }
34
+ ): <T extends typeof Component>(ComponentClass: T) => T;
35
+
36
+ export function autoRegister<T extends typeof Component>(ComponentClass: T): T;