@csedl/svelte-on-rails 11.0.4 → 12.0.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.
@@ -0,0 +1,135 @@
1
+
2
+ import http from 'node:http';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { render } from 'svelte/server';
6
+ import { loadComponentModule } from "../src/ssr/loadComponent.js";
7
+ import { componentRenderError} from "../src/utils.js";
8
+
9
+ const SOCKET_PATH = process.argv[2];
10
+ const PORT = (/^\d+$/.test(SOCKET_PATH) ? SOCKET_PATH : null);
11
+ const LOGFILE = process.env.SVELTE_SSR_SERVER_LOGFILE;
12
+
13
+ if (!SOCKET_PATH && (process.argv.length <= 2)) {
14
+ console.error(`Missing first argument: socket path or port number.\nUsage:\nnode svelte-ssr-server.js /path/to/socket.sock\nnode svelte-ssr-server.js 3001`);
15
+ process.exit(1);
16
+ }
17
+
18
+ if (LOGFILE) {
19
+ console.log(`Logging to ${LOGFILE}`);
20
+ } else {
21
+ console.log(`No log file specified. Logging to stdout.`);
22
+ }
23
+
24
+ // Clean up old socket file
25
+ if (SOCKET_PATH.startsWith('/') && fs.existsSync(SOCKET_PATH)) {
26
+ fs.unlinkSync(SOCKET_PATH);
27
+ logger(`Cleaned old socket: ${SOCKET_PATH}`);
28
+ }
29
+
30
+ const server = http.createServer(async (req, res) => {
31
+
32
+ logger(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
33
+
34
+ // Health check / ping
35
+
36
+ if (req.method === 'GET' && (req.url === '/ping' || req.url === '/health')) {
37
+ res.writeHead(200, { 'Content-Type': 'application/json' });
38
+ res.end(JSON.stringify({
39
+ status: 'alive',
40
+ uptime: process.uptime(),
41
+ pid: process.pid,
42
+ socket: SOCKET_PATH
43
+ }));
44
+ return;
45
+ }
46
+
47
+ // Render
48
+
49
+ if (req.method === 'POST' && req.url === '/render') {
50
+ let request_body = '';
51
+ req.on('data', chunk => { request_body += chunk; });
52
+ req.on('end', async () => {
53
+ let basename = 'unknown';
54
+ try {
55
+
56
+ // parsing request-body
57
+
58
+ logger(`Parsing request_body...`);
59
+ const { component, props = {} } = JSON.parse(request_body);
60
+ basename = path.basename(component);
61
+ logger(`Rendering component: ${basename} with props: ${JSON.stringify(props)}`);
62
+
63
+ // lad component
64
+
65
+ const compiledComponent = await loadComponentModule(component);
66
+ logger(`Component loaded: «${compiledComponent}»`);
67
+
68
+ // render
69
+
70
+ const { body, head } = render(compiledComponent, { props });
71
+ logger(`Component rendered, content: «${body}», head: «${head}»`);
72
+
73
+ res.writeHead(200, { 'Content-Type': 'application/json' });
74
+ res.end(JSON.stringify({
75
+ html: body,
76
+ head: head
77
+ }));
78
+
79
+ logger(`Successfully rendered: ${basename}`);
80
+ } catch (err) {
81
+ logger(`Render error: ${err}`);
82
+ logger(`body => «${request_body}»`);
83
+ res.writeHead(500, { 'Content-Type': 'application/json' });
84
+ res.end(JSON.stringify({
85
+ html: componentRenderError(basename, err),
86
+ head: ''
87
+ }));
88
+ }
89
+ });
90
+ return;
91
+ }
92
+
93
+ // Fallback 404
94
+ res.writeHead(404, { 'Content-Type': 'application/json' });
95
+ res.end(JSON.stringify({
96
+ error: 'Not found' }));
97
+ });
98
+
99
+ // LOGGING
100
+ function logger(msg, error = false) {
101
+ if (error) {
102
+ console.error(msg);
103
+ } else {
104
+ console.log(msg);
105
+ };
106
+ if (LOGFILE) {
107
+ fs.appendFileSync(LOGFILE, `${new Date().toISOString()} ${msg}\n`);
108
+ }
109
+ }
110
+
111
+
112
+ // Start server
113
+ if (PORT) {
114
+ // TCP mode (e.g. for debugging or non-Unix environments)
115
+ server.listen(PORT, '127.0.0.1', () => {
116
+ logger(`Svelte SSR listening on http://127.0.0.1:${PORT}`);
117
+ });
118
+ } else {
119
+ // Preferred: Unix socket
120
+ server.listen(SOCKET_PATH, () => {
121
+ fs.chmodSync(SOCKET_PATH, 0o600); // restrict access
122
+ logger(`Svelte SSR listening on unix socket: ${SOCKET_PATH}`);
123
+ });
124
+ }
125
+
126
+ // Graceful shutdown
127
+ process.on('SIGINT', () => {
128
+ logger('Shutting down SSR server...');
129
+ server.close(() => {
130
+ if (SOCKET_PATH.startsWith('/') && fs.existsSync(SOCKET_PATH)) {
131
+ fs.unlinkSync(SOCKET_PATH);
132
+ }
133
+ process.exit(0);
134
+ });
135
+ });
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@csedl/svelte-on-rails",
3
- "version": "11.0.4",
3
+ "version": "12.0.1",
4
4
  "description": "Client-side runtime for svelte-on-rails: hydration, SSR build support & Turbo Stream actions",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
+ "engines": {
8
+ "node": ">=24.0.0"
9
+ },
7
10
  "scripts": {
8
11
  "build:package": "vite build"
9
12
  },
@@ -28,10 +31,7 @@
28
31
  "type": "git",
29
32
  "url": "https://gitlab.com/sedl/svelte-on-rails"
30
33
  },
31
- "keywords": [
32
- "svelte",
33
- "rails"
34
- ],
34
+ "keywords": [ "svelte", "rails"],
35
35
  "author": "Christian Sedlmair",
36
36
  "license": "LICENSE.md",
37
37
  "bugs": {
@@ -1,6 +1,7 @@
1
1
  import {mount, hydrate} from "svelte";
2
2
  import {debugLog} from "../logger.js";
3
- import { SvelteOnRails } from "../config.js";
3
+ import {SvelteOnRails} from "../config.js";
4
+ import {componentRenderError} from "../utils.js";
4
5
 
5
6
  // Store for tracking initialized Svelte component instances
6
7
  const svelteInstances = new WeakMap();
@@ -52,5 +53,10 @@ export async function initializeSvelteComponent(element) {
52
53
  } catch (e) {
53
54
  console.error(`[svelte-on-rails] Failed to ${action} ${componentKey}:`, e);
54
55
  element.setAttribute("data-svelte-status", "error");
56
+
57
+ element.innerHTML = componentRenderError(componentKey, e);
58
+
55
59
  }
56
- }
60
+ }
61
+
62
+
package/src/ssr/render.js CHANGED
@@ -2,35 +2,41 @@
2
2
  import { render } from 'svelte/server';
3
3
  import { readPropsFromStdin } from "./readStdin.js";
4
4
  import { loadComponentModule } from "./loadComponent.js";
5
+ import { componentRenderError } from "../utils.js";
5
6
 
6
7
  const compiledComponentPath = process.argv[2];
7
8
 
8
9
 
9
10
  (async () => {
11
+ console.log(`${performance.now()}<time>`);
10
12
  console.log(`[svelte-on-rails:debug] awaiting load component => «${compiledComponentPath}»`);
11
13
  const compiledComponent = await loadComponentModule(compiledComponentPath);
12
14
 
13
- console.log(`[svelte-on-rails:debug] component read: «${compiledComponent}»`);
15
+ console.log(`[svelte-on-rails:debug] Component loaded: «${compiledComponent}»`);
14
16
 
15
17
  const props = await readPropsFromStdin();
16
18
  console.log(`[svelte-on-rails:debug] props read: «${JSON.stringify(props)}»`);
17
19
 
18
20
  try {
19
- // Svelte 5+ SSR rendering (runes-compatible; signals omitted on server for perf)
20
- // Returns { body: string, head: string } — no 'html' or 'css' props
21
21
  const { body, head } = render(compiledComponent, { props });
22
22
 
23
23
  const res = {
24
24
  status: 'SUCCESS',
25
- html: body, // Use 'body' as the main HTML content (replaces old 'html')
26
- head: head || '', // Optional: Include <head> content (styles, meta, etc.)
27
- // Add bodyAttributes or other result props if needed in future
25
+ html: body,
26
+ head: head || '',
28
27
  };
29
-
28
+ console.log(`<time>${performance.now()}`);
30
29
  console.log('[svelte-on-rails:successful-json-response]' + JSON.stringify(res));
31
30
  } catch (error) {
32
- console.error('[svelte-on-rails:debug] Error rendering component:', error);
33
- process.exit(1);
31
+
32
+ const res = {
33
+ status: 'SUCCESS',
34
+ html: componentRenderError(compiledComponentPath, error),
35
+ head: '',
36
+ };
37
+
38
+ console.log(`<time>${performance.now()}`);
39
+ console.log('[svelte-on-rails:successful-json-response]' + JSON.stringify(res));
34
40
  }
35
41
  })();
36
42
 
package/src/utils.js ADDED
@@ -0,0 +1,22 @@
1
+ export function componentRenderError(component, error) {
2
+ return (`
3
+ <div style="
4
+ padding: 1.5rem;
5
+ margin: 1rem 0;
6
+ border: 2px solid #ef4444;
7
+ background: #fef2f2;
8
+ color: #991b1b;
9
+ border-radius: 0.5rem;
10
+ font-family: system-ui, sans-serif;
11
+ ">
12
+ <strong style="font-size: 1.2em;">Component failed to render</strong>
13
+ <p style="margin: 0.75rem 0 0;">
14
+ <code>${component}</code><br>
15
+ ${error.message || 'Unknown rendering error'}
16
+ </p>
17
+ <small style="color: #7f1d1d; opacity: 0.8;">
18
+ Check browser console for full details. (Dev mode only?)
19
+ </small>
20
+ </div>
21
+ `)
22
+ }