@coherent.js/core 1.1.0 → 2.0.0-rc.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 CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@coherent.js/core.svg)](https://www.npmjs.com/package/@coherent.js/core)
4
4
  [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](../../LICENSE)
5
- [![Node >= 20](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org)
5
+ [![Node >= 22.12](https://img.shields.io/badge/node-%3E%3D22.12-brightgreen)](https://nodejs.org)
6
6
 
7
- Core runtime for Coherent.js — an object-based SSR framework focused on performance, streaming, and simplicity.
7
+ Core runtime for Coherent.js — an object-based SSR framework: components are plain JavaScript objects rendered to HTML.
8
8
 
9
- - ESM-only, Node 20+
10
- - Pure object rendering to HTML
11
- - Optional CSS-like scoping for component encapsulation
12
- - Component system utilities, error boundaries, and performance hooks
9
+ - ESM-only, Node 22.12+
10
+ - Synchronous `render()` to a string, and `renderToStream()` for large pages
11
+ - Escaped text and attribute values by default; raw HTML only through `html:` or `dangerouslySetInnerContent()`
12
+ - Opt-in scoped CSS, memoization and whole-render caching
13
13
 
14
14
  For a high-level overview and repository-wide instructions, see the root README: ../../README.md
15
15
 
@@ -19,103 +19,91 @@ For a high-level overview and repository-wide instructions, see the root README:
19
19
  pnpm add @coherent.js/core
20
20
  ```
21
21
 
22
- Requirements:
23
- - Node.js >= 20
24
- - ESM module system
25
-
26
22
  ## Quick start
27
23
 
28
- JavaScript (ESM):
29
24
  ```js
30
25
  import { render } from '@coherent.js/core';
31
26
 
32
- const html = render({
33
- div: { class: 'greeting', text: 'Hello Coherent' }
27
+ const Greeting = ({ name }) => ({
28
+ div: {
29
+ className: ['greeting', name === 'Ada' && 'greeting--vip'],
30
+ children: [
31
+ { h1: { text: `Hello ${name}` } }, // text is escaped
32
+ name === 'Ada' && { p: { text: 'Welcome back' } } // false renders nothing
33
+ ]
34
+ }
34
35
  });
35
36
 
36
- console.log(html);
37
+ render(Greeting({ name: 'Ada' }));
38
+ // <div class="greeting greeting--vip"><h1>Hello Ada</h1><p>Welcome back</p></div>
37
39
  ```
38
40
 
39
- TypeScript:
40
- ```ts
41
- import { render } from '@coherent.js/core';
41
+ ## Rendering
42
42
 
43
- const html = render({
44
- div: { class: 'greeting', text: 'Hello Coherent (TS)' }
45
- });
46
- console.log(html);
47
- ```
43
+ - `render(component, options?)` returns an HTML string. It is synchronous: await data and async components first (a Promise in the tree throws).
44
+ - A function component that throws makes `render()` throw a `RenderingError` naming the component's path, with the original error as `cause`. Pass `onError: (error, { path }) => replacement` to render something else in its place (`null` omits it).
45
+ - Options: `scoped` (scoped CSS), `minify`, `maxDepth`, `enableMonitoring`, `enableCache` / `cache` (see below), `onError`.
48
46
 
49
- ## Exports overview
47
+ ### Streaming
48
+
49
+ ```js
50
+ import { Readable } from 'node:stream';
51
+ import { renderToStream, streamingUtils } from '@coherent.js/core';
52
+
53
+ // An async generator of HTML chunks with exactly render()'s output
54
+ Readable.from(renderToStream(Page(), { chunkSize: 16384 })).pipe(res);
50
55
 
51
- The package ships built ESM and CJS bundles under `dist/` with types under `types/`.
56
+ // or: writes with backpressure, aborts the response if rendering fails and
57
+ // stops rendering if the client disconnects
58
+ await streamingUtils.streamToResponse(renderToStream(Page()), res);
59
+ ```
52
60
 
53
- Key APIs (selected):
54
- - Rendering
55
- - `render(input, options?)` – renders a component object to an HTML string
56
- - Component system (re-exported from internal modules)
57
- - `createComponent`, `defineComponent`, `registerComponent`, `getComponent`, `getRegisteredComponents`
58
- - State helpers: `withState`, `withStateUtils`, `createStateManager`
59
- - Lazy: `lazy`, `isLazy`, `evaluateLazy`
60
- - Error boundaries (selected)
61
- - `createErrorBoundary`, `withErrorBoundary`, `createAsyncErrorBoundary`
62
- - `createGlobalErrorHandler`, `GlobalErrorHandler`
61
+ The event loop gets a turn after every chunk, so the first bytes of a large page leave early and other requests keep being served; total render time is somewhat higher than `render()`.
63
62
 
64
- Tip: When working in the monorepo website/dev flow, imports can resolve to `src` via `exports.development`.
63
+ ### Caching
65
64
 
66
- ## Minimal component example
65
+ Nothing is cached unless you ask:
67
66
 
68
67
  ```js
69
- import { createComponent, render } from '@coherent.js/core';
68
+ import { memo, render, createCacheManager } from '@coherent.js/core';
70
69
 
71
- const Counter = createComponent(({ count = 0 }) => ({
72
- div: {
73
- class: 'counter',
74
- children: [
75
- { span: { text: `Count: ${count}` } }
76
- ]
77
- }
78
- }));
70
+ // Per-component memoization; every memoized component has its own LRU
71
+ const Row = memo(({ item }) => ({ li: { text: item.name } }), {
72
+ keyFn: ({ item }) => `${item.id}:${item.version}`,
73
+ maxSize: 1000
74
+ });
79
75
 
80
- const html = render(Counter({ count: 2 }));
76
+ // Whole-render cache, keyed on the complete tree (trees containing
77
+ // functions are never cached). Only useful for re-rendering identical trees.
78
+ const cache = createCacheManager({ maxCacheSize: 500, ttlMs: 60_000 });
79
+ render(StaticPage(), { enableCache: true, cache });
81
80
  ```
82
81
 
83
- TypeScript:
84
- ```ts
85
- import { createComponent, render } from '@coherent.js/core';
82
+ ### Raw HTML
86
83
 
87
- type Props = { count?: number };
84
+ Text and attribute values are always escaped. Raw markup goes through the `html:` key or `dangerouslySetInnerContent()`; markers from that function carry a symbol brand, so objects parsed from JSON are never treated as trusted. Attribute names that could break out of the tag (whitespace, quotes, `<`, `>`, `/`, `=`) make `render()` throw.
88
85
 
89
- const Counter = createComponent((props: Props) => ({
90
- div: {
91
- class: 'counter',
92
- children: [ { span: { text: `Count: ${props.count ?? 0}` } } ]
93
- }
94
- }));
86
+ ### Event handlers
95
87
 
96
- const html = render(Counter({ count: 2 }));
97
- ```
88
+ Function-valued `on*` props render nothing on the server; `@coherent.js/client`'s `hydrate()` attaches them in the browser. Inline string handlers (`onclick: 'history.back()'`) are rendered as attributes.
98
89
 
99
- ## Development
90
+ ## Exports overview
100
91
 
101
- Run tests for this package:
102
- ```bash
103
- pnpm --filter @coherent.js/core run test
104
- ```
92
+ - Rendering: `render`, `renderToStream`, `streamingUtils`, `formatAttributes`, `escapeHtml`, `isValidAttributeName`, `dangerouslySetInnerContent`, `isTrustedContent`
93
+ - Components: `createComponent`, `defineComponent`, `registerComponent`, `memo`, `memoComponent`, `lazy`, `withState`, `withStateUtils`
94
+ - Error boundaries: `createErrorBoundary`, `withErrorBoundary`, `createAsyncErrorBoundary`, `createGlobalErrorHandler`
95
+ - Caching and monitoring: `createCacheManager`, `cacheManager`, `memoize`, `performanceMonitor`
96
+ - Events: `createEventBus`, `globalEventBus`, `withEventBus`, `eventSystem`
105
97
 
106
- Watch mode:
107
- ```bash
108
- pnpm --filter @coherent.js/core run test:watch
109
- ```
98
+ `createComponent()` returns a *stateful instance* (`mount`, `update`, `destroy`, instance state) meant for the browser. On the server, pass per-request data through props: instance state is shared by every request that renders the same component.
110
99
 
111
- Type check:
112
- ```bash
113
- pnpm --filter @coherent.js/core run typecheck
114
- ```
100
+ ## Development
115
101
 
116
- Build (from package dir or via workspace filter):
117
102
  ```bash
103
+ pnpm vitest run packages/core # tests (from the repo root)
104
+ pnpm --filter @coherent.js/core run typecheck
118
105
  pnpm --filter @coherent.js/core run build
106
+ pnpm perf:render # rendering benchmark
119
107
  ```
120
108
 
121
109
  ## License