@constela/server 3.0.1 → 4.1.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
@@ -1,6 +1,6 @@
1
1
  # @constela/server
2
2
 
3
- Server-side rendering (SSR) for the Constela UI framework.
3
+ Server-side rendering (SSR) for Constela JSON programs.
4
4
 
5
5
  ## Installation
6
6
 
@@ -11,78 +11,127 @@ npm install @constela/server
11
11
  **Peer Dependencies:**
12
12
  - `@constela/compiler` ^0.7.0
13
13
 
14
- ## Overview
14
+ ## How It Works
15
+
16
+ JSON program → HTML string
17
+
18
+ ```json
19
+ {
20
+ "version": "1.0",
21
+ "state": { "name": { "type": "string", "initial": "World" } },
22
+ "view": {
23
+ "kind": "element",
24
+ "tag": "h1",
25
+ "children": [
26
+ { "kind": "text", "value": { "expr": "lit", "value": "Hello, " } },
27
+ { "kind": "text", "value": { "expr": "state", "name": "name" } }
28
+ ]
29
+ }
30
+ }
31
+ ```
15
32
 
16
- This package provides SSR capabilities for Constela applications. Features:
33
+ SSR
17
34
 
18
- - **HTML Generation** - Render programs to HTML strings
19
- - **Route Context** - Pass route params and query strings
20
- - **Markdown & Code** - Server-side Markdown and syntax highlighting
21
- - **Dual Theme** - Light and dark theme code blocks
35
+ ```html
36
+ <h1>Hello, World</h1>
37
+ ```
22
38
 
23
- ## API Reference
39
+ ## Features
24
40
 
25
- ### renderToString
41
+ ### Markdown Rendering
26
42
 
27
- Main SSR function that renders a compiled program to HTML.
43
+ ```json
44
+ {
45
+ "kind": "markdown",
46
+ "content": { "expr": "data", "name": "article", "path": "content" }
47
+ }
48
+ ```
28
49
 
29
- ```typescript
30
- import { renderToString } from '@constela/server';
50
+ Rendered with async parsing and Shiki syntax highlighting.
31
51
 
32
- const html = await renderToString(compiledProgram, {
33
- route: {
34
- params: { id: '123' },
35
- query: { tab: 'overview' },
36
- path: '/users/123',
37
- },
38
- imports: {
39
- config: { siteName: 'My Site' },
40
- },
41
- });
52
+ ### Code Highlighting
53
+
54
+ ```json
55
+ {
56
+ "kind": "code",
57
+ "code": { "expr": "lit", "value": "const x = 1;" },
58
+ "language": { "expr": "lit", "value": "typescript" }
59
+ }
42
60
  ```
43
61
 
44
- **Parameters:**
45
- - `program: CompiledProgram` - Compiled program from `@constela/compiler`
46
- - `options?: RenderOptions` - Optional render configuration
62
+ Features:
63
+ - Dual theme support (github-light, github-dark)
64
+ - CSS custom properties for theme switching
65
+ - Preloaded languages: javascript, typescript, json, html, css, python, rust, go, java, bash, markdown
66
+
67
+ ### Route Context
47
68
 
48
- **RenderOptions:**
49
- ```typescript
50
- interface RenderOptions {
51
- route?: {
52
- params?: Record<string, string>;
53
- query?: Record<string, string>;
54
- path?: string;
55
- };
56
- imports?: Record<string, unknown>;
69
+ Pass route parameters for dynamic pages:
70
+
71
+ ```json
72
+ {
73
+ "route": { "path": "/users/:id" },
74
+ "view": {
75
+ "kind": "text",
76
+ "value": { "expr": "route", "name": "id", "source": "param" }
77
+ }
57
78
  }
58
79
  ```
59
80
 
60
- **Returns:** `Promise<string>` - Complete HTML string
81
+ ### Import Data
61
82
 
62
- ## Internal Features
83
+ Pass external data at render time:
63
84
 
64
- The package internally handles:
85
+ ```json
86
+ {
87
+ "imports": { "config": "./data/config.json" },
88
+ "view": {
89
+ "kind": "text",
90
+ "value": { "expr": "import", "name": "config", "path": "siteName" }
91
+ }
92
+ }
93
+ ```
65
94
 
66
- **Markdown Rendering:**
67
- - Async parsing with Shiki syntax highlighting
68
- - Sanitization via DOMPurify
95
+ ### Style Evaluation
96
+
97
+ Style expressions are evaluated during SSR, producing CSS class strings:
98
+
99
+ ```json
100
+ {
101
+ "styles": {
102
+ "button": {
103
+ "base": "px-4 py-2 rounded",
104
+ "variants": {
105
+ "variant": {
106
+ "primary": "bg-blue-500 text-white",
107
+ "secondary": "bg-gray-200 text-gray-800"
108
+ }
109
+ },
110
+ "defaultVariants": { "variant": "primary" }
111
+ }
112
+ },
113
+ "view": {
114
+ "kind": "element",
115
+ "tag": "button",
116
+ "props": {
117
+ "className": {
118
+ "expr": "style",
119
+ "name": "button",
120
+ "variants": { "variant": { "expr": "lit", "value": "primary" } }
121
+ }
122
+ }
123
+ }
124
+ }
125
+ ```
69
126
 
70
- **Code Highlighting:**
71
- - Dual theme support (github-light, github-dark)
72
- - CSS custom properties for theme switching
73
- - Preloaded languages: javascript, typescript, json, html, css, python, rust, go, java, bash, markdown
127
+ SSR
74
128
 
75
- **CSS Variables:** Code blocks use CSS custom properties:
76
- ```css
77
- /* Light mode */
78
- .shiki { background-color: var(--shiki-light-bg); }
79
- .shiki span { color: var(--shiki-light); }
80
-
81
- /* Dark mode */
82
- .dark .shiki { background-color: var(--shiki-dark-bg); }
83
- .dark .shiki span { color: var(--shiki-dark); }
129
+ ```html
130
+ <button class="px-4 py-2 rounded bg-blue-500 text-white">...</button>
84
131
  ```
85
132
 
133
+ Pass style presets via `RenderOptions.styles` for evaluation.
134
+
86
135
  ## Output Structure
87
136
 
88
137
  ### Code Block HTML
@@ -91,23 +140,23 @@ The package internally handles:
91
140
  <div class="constela-code" data-code-content="...">
92
141
  <div class="group relative">
93
142
  <div class="language-badge">typescript</div>
94
- <button class="constela-copy-btn">
95
- <!-- Copy icon SVG -->
96
- </button>
143
+ <button class="constela-copy-btn"><!-- Copy icon --></button>
97
144
  <pre><code class="shiki">...</code></pre>
98
145
  </div>
99
146
  </div>
100
147
  ```
101
148
 
102
- The `data-code-content` attribute contains the raw code for copy functionality.
103
-
104
- ## Expression Evaluation
149
+ ### CSS Variables
105
150
 
106
- SSR evaluates expressions server-side with some limitations:
151
+ ```css
152
+ /* Light mode */
153
+ .shiki { background-color: var(--shiki-light-bg); }
154
+ .shiki span { color: var(--shiki-light); }
107
155
 
108
- - **No DOM refs** - `ref` expressions return `null`
109
- - **No safe globals** - Limited to basic operations
110
- - **Static only** - No reactive updates
156
+ /* Dark mode */
157
+ .dark .shiki { background-color: var(--shiki-dark-bg); }
158
+ .dark .shiki span { color: var(--shiki-dark); }
159
+ ```
111
160
 
112
161
  ## Security
113
162
 
@@ -115,29 +164,56 @@ SSR evaluates expressions server-side with some limitations:
115
164
  - **DOMPurify** - Markdown content is sanitized
116
165
  - **Prototype Pollution Prevention** - Same as runtime
117
166
 
118
- ## Example
167
+ ## Internal API
168
+
169
+ > For framework developers only. End users should use the CLI.
170
+
171
+ ### renderToString
119
172
 
120
173
  ```typescript
121
- import { compile } from '@constela/compiler';
122
174
  import { renderToString } from '@constela/server';
123
175
 
124
- const program = compile({
125
- version: '1.0',
126
- state: { name: { type: 'string', initial: 'World' } },
127
- actions: [],
128
- view: {
129
- kind: 'element',
130
- tag: 'h1',
131
- children: [
132
- { kind: 'text', value: { expr: 'lit', value: 'Hello, ' } },
133
- { kind: 'text', value: { expr: 'state', name: 'name' } },
134
- ],
176
+ const html = await renderToString(compiledProgram, {
177
+ route: {
178
+ params: { id: '123' },
179
+ query: { tab: 'overview' },
180
+ path: '/users/123',
181
+ },
182
+ imports: {
183
+ config: { siteName: 'My Site' },
184
+ },
185
+ styles: {
186
+ button: {
187
+ base: 'px-4 py-2 rounded',
188
+ variants: {
189
+ variant: {
190
+ primary: 'bg-blue-500 text-white',
191
+ secondary: 'bg-gray-200 text-gray-800',
192
+ },
193
+ },
194
+ defaultVariants: { variant: 'primary' },
195
+ },
135
196
  },
136
197
  });
198
+ ```
199
+
200
+ **RenderOptions:**
201
+
202
+ ```typescript
203
+ interface RenderOptions {
204
+ route?: {
205
+ params?: Record<string, string>;
206
+ query?: Record<string, string>;
207
+ path?: string;
208
+ };
209
+ imports?: Record<string, unknown>;
210
+ styles?: Record<string, StylePreset>;
211
+ }
137
212
 
138
- if (program.ok) {
139
- const html = await renderToString(program.program);
140
- console.log(html); // '<h1>Hello, World</h1>'
213
+ interface StylePreset {
214
+ base: string;
215
+ variants?: Record<string, Record<string, string>>;
216
+ defaultVariants?: Record<string, string>;
141
217
  }
142
218
  ```
143
219
 
@@ -145,14 +221,21 @@ if (program.ok) {
145
221
 
146
222
  Server-rendered HTML can be hydrated on the client:
147
223
 
148
- ```typescript
149
- // Server
150
- import { renderToString } from '@constela/server';
151
- const html = await renderToString(program, { route, imports });
152
-
153
- // Client
154
- import { hydrateApp } from '@constela/runtime';
155
- hydrateApp({ program, mount, route, imports });
224
+ ```json
225
+ {
226
+ "version": "1.0",
227
+ "lifecycle": { "onMount": "initializeClient" },
228
+ "state": { ... },
229
+ "actions": [
230
+ {
231
+ "name": "initializeClient",
232
+ "steps": [
233
+ { "do": "storage", "operation": "get", "key": { "expr": "lit", "value": "preferences" }, ... }
234
+ ]
235
+ }
236
+ ],
237
+ "view": { ... }
238
+ }
156
239
  ```
157
240
 
158
241
  ## License
package/dist/index.d.ts CHANGED
@@ -6,6 +6,17 @@ import { CompiledProgram } from '@constela/compiler';
6
6
  * Renders CompiledProgram to HTML string for Server-Side Rendering.
7
7
  */
8
8
 
9
+ /**
10
+ * Style preset definition for SSR
11
+ */
12
+ interface StylePreset {
13
+ base: string;
14
+ variants?: Record<string, Record<string, string>>;
15
+ defaultVariants?: Record<string, string>;
16
+ compoundVariants?: Array<Record<string, string> & {
17
+ class: string;
18
+ }>;
19
+ }
9
20
  /**
10
21
  * Options for renderToString
11
22
  */
@@ -16,6 +27,7 @@ interface RenderOptions {
16
27
  path?: string;
17
28
  };
18
29
  imports?: Record<string, unknown>;
30
+ styles?: Record<string, StylePreset>;
19
31
  }
20
32
  /**
21
33
  * Renders a CompiledProgram to an HTML string.
package/dist/index.js CHANGED
@@ -213,6 +213,12 @@ function evaluate(expr, ctx) {
213
213
  if (typeof key === "string" && forbiddenKeys.has(key)) return void 0;
214
214
  return base[key];
215
215
  }
216
+ case "param": {
217
+ return void 0;
218
+ }
219
+ case "style": {
220
+ return evaluateStyle(expr, ctx);
221
+ }
216
222
  default: {
217
223
  const _exhaustiveCheck = expr;
218
224
  throw new Error(`Unknown expression type: ${JSON.stringify(_exhaustiveCheck)}`);
@@ -304,6 +310,36 @@ function evaluateBinary(op, left, right, ctx) {
304
310
  throw new Error("Unknown binary operator: " + op);
305
311
  }
306
312
  }
313
+ function evaluateStyle(expr, ctx) {
314
+ const preset = ctx.styles?.[expr.name];
315
+ if (!preset) return "";
316
+ let classes = preset.base;
317
+ if (preset.variants) {
318
+ for (const variantKey of Object.keys(preset.variants)) {
319
+ let variantValueStr = null;
320
+ if (expr.variants?.[variantKey]) {
321
+ let variantValue;
322
+ try {
323
+ variantValue = evaluate(expr.variants[variantKey], ctx);
324
+ } catch {
325
+ continue;
326
+ }
327
+ if (variantValue != null) {
328
+ variantValueStr = String(variantValue);
329
+ }
330
+ } else if (preset.defaultVariants?.[variantKey] !== void 0) {
331
+ variantValueStr = preset.defaultVariants[variantKey];
332
+ }
333
+ if (variantValueStr !== null) {
334
+ const variantClasses = preset.variants[variantKey]?.[variantValueStr];
335
+ if (variantClasses) {
336
+ classes += " " + variantClasses;
337
+ }
338
+ }
339
+ }
340
+ }
341
+ return classes.trim();
342
+ }
307
343
  function formatValue(value) {
308
344
  if (value === null || value === void 0) {
309
345
  return "";
@@ -432,7 +468,8 @@ async function renderToString(program, options) {
432
468
  query: options.route.query ?? {},
433
469
  path: options.route.path ?? ""
434
470
  } : void 0,
435
- imports: options?.imports ?? program.importData
471
+ imports: options?.imports ?? program.importData,
472
+ styles: options?.styles
436
473
  };
437
474
  return await renderNode(program.view, ctx);
438
475
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constela/server",
3
- "version": "3.0.1",
3
+ "version": "4.1.0",
4
4
  "description": "Server-side rendering for Constela UI framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -15,7 +15,7 @@
15
15
  "dist"
16
16
  ],
17
17
  "peerDependencies": {
18
- "@constela/compiler": "^0.7.0"
18
+ "@constela/compiler": "^0.8.0"
19
19
  },
20
20
  "dependencies": {
21
21
  "isomorphic-dompurify": "^2.35.0",
@@ -28,7 +28,7 @@
28
28
  "tsup": "^8.0.0",
29
29
  "typescript": "^5.3.0",
30
30
  "vitest": "^2.0.0",
31
- "@constela/compiler": "0.7.0"
31
+ "@constela/compiler": "0.8.0"
32
32
  },
33
33
  "engines": {
34
34
  "node": ">=20.0.0"