@csedl/svelte-on-rails 12.0.0 → 12.0.2
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/bin/svelte-ssr-server.js +13 -19
- package/package.json +1 -1
- package/src/hydrate-build/initializeSvelteComponent.js +8 -2
- package/src/ssr/render.js +12 -7
- package/src/utils.js +26 -0
package/bin/svelte-ssr-server.js
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
// app/javascript/ssr/svelte-ssr-server.js
|
|
2
|
-
// Minimal persistent SSR server for SvelteOnRails
|
|
3
|
-
// Listens on Unix socket (preferred) or TCP port fallback
|
|
4
1
|
|
|
5
2
|
import http from 'node:http';
|
|
6
3
|
import fs from 'node:fs';
|
|
7
4
|
import path from 'node:path';
|
|
8
5
|
import { render } from 'svelte/server';
|
|
9
|
-
import { readPropsFromStdin } from "../src/ssr/readStdin.js";
|
|
10
6
|
import { loadComponentModule } from "../src/ssr/loadComponent.js";
|
|
7
|
+
import { componentRenderError} from "../src/utils.js";
|
|
11
8
|
|
|
12
|
-
// Config from ENV or defaults
|
|
13
9
|
const SOCKET_PATH = process.argv[2];
|
|
14
10
|
const PORT = (/^\d+$/.test(SOCKET_PATH) ? SOCKET_PATH : null);
|
|
15
11
|
const LOGFILE = process.env.SVELTE_SSR_SERVER_LOGFILE;
|
|
@@ -32,10 +28,11 @@ if (SOCKET_PATH.startsWith('/') && fs.existsSync(SOCKET_PATH)) {
|
|
|
32
28
|
}
|
|
33
29
|
|
|
34
30
|
const server = http.createServer(async (req, res) => {
|
|
35
|
-
|
|
31
|
+
|
|
36
32
|
logger(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
|
|
37
33
|
|
|
38
34
|
// Health check / ping
|
|
35
|
+
|
|
39
36
|
if (req.method === 'GET' && (req.url === '/ping' || req.url === '/health')) {
|
|
40
37
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
41
38
|
res.end(JSON.stringify({
|
|
@@ -47,18 +44,20 @@ const server = http.createServer(async (req, res) => {
|
|
|
47
44
|
return;
|
|
48
45
|
}
|
|
49
46
|
|
|
50
|
-
//
|
|
47
|
+
// Render
|
|
48
|
+
|
|
51
49
|
if (req.method === 'POST' && req.url === '/render') {
|
|
52
50
|
let request_body = '';
|
|
53
51
|
req.on('data', chunk => { request_body += chunk; });
|
|
54
52
|
req.on('end', async () => {
|
|
53
|
+
let basename = 'unknown';
|
|
55
54
|
try {
|
|
56
55
|
|
|
57
56
|
// parsing request-body
|
|
58
57
|
|
|
59
58
|
logger(`Parsing request_body...`);
|
|
60
59
|
const { component, props = {} } = JSON.parse(request_body);
|
|
61
|
-
|
|
60
|
+
basename = path.basename(component);
|
|
62
61
|
logger(`Rendering component: ${basename} with props: ${JSON.stringify(props)}`);
|
|
63
62
|
|
|
64
63
|
// lad component
|
|
@@ -71,18 +70,9 @@ const server = http.createServer(async (req, res) => {
|
|
|
71
70
|
const { body, head } = render(compiledComponent, { props });
|
|
72
71
|
logger(`Component rendered, content: «${body}», head: «${head}»`);
|
|
73
72
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
// TODO: Replace with real Svelte SSR rendering
|
|
77
|
-
// Example: const { html, css, head } = YourSvelteComponent.render(props);
|
|
78
|
-
const dummyHtml = `<div data-component="${component}">Hello from SSR! Props: ${JSON.stringify(props)}</div>`;
|
|
79
|
-
const dummyCss = '';
|
|
80
|
-
const dummyHead = '<meta name="ssr-stub" content="placeholder">';
|
|
81
|
-
|
|
82
73
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
83
74
|
res.end(JSON.stringify({
|
|
84
75
|
html: body,
|
|
85
|
-
css: dummyCss,
|
|
86
76
|
head: head
|
|
87
77
|
}));
|
|
88
78
|
|
|
@@ -91,7 +81,10 @@ const server = http.createServer(async (req, res) => {
|
|
|
91
81
|
logger(`Render error: ${err}`);
|
|
92
82
|
logger(`body => «${request_body}»`);
|
|
93
83
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
94
|
-
res.end(JSON.stringify({
|
|
84
|
+
res.end(JSON.stringify({
|
|
85
|
+
html: componentRenderError(basename, err, true),
|
|
86
|
+
head: ''
|
|
87
|
+
}));
|
|
95
88
|
}
|
|
96
89
|
});
|
|
97
90
|
return;
|
|
@@ -99,7 +92,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
99
92
|
|
|
100
93
|
// Fallback 404
|
|
101
94
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
102
|
-
res.end(JSON.stringify({
|
|
95
|
+
res.end(JSON.stringify({
|
|
96
|
+
error: 'Not found' }));
|
|
103
97
|
});
|
|
104
98
|
|
|
105
99
|
// LOGGING
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {mount, hydrate} from "svelte";
|
|
2
2
|
import {debugLog} from "../logger.js";
|
|
3
|
-
import {
|
|
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,6 +2,7 @@
|
|
|
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
|
|
|
@@ -17,21 +18,25 @@ const compiledComponentPath = process.argv[2];
|
|
|
17
18
|
console.log(`[svelte-on-rails:debug] props read: «${JSON.stringify(props)}»`);
|
|
18
19
|
|
|
19
20
|
try {
|
|
20
|
-
// Svelte 5+ SSR rendering (runes-compatible; signals omitted on server for perf)
|
|
21
|
-
// Returns { body: string, head: string } — no 'html' or 'css' props
|
|
22
21
|
const { body, head } = render(compiledComponent, { props });
|
|
23
22
|
|
|
24
23
|
const res = {
|
|
25
24
|
status: 'SUCCESS',
|
|
26
|
-
html: body,
|
|
27
|
-
head: head || '',
|
|
28
|
-
// Add bodyAttributes or other result props if needed in future
|
|
25
|
+
html: body,
|
|
26
|
+
head: head || '',
|
|
29
27
|
};
|
|
30
28
|
console.log(`<time>${performance.now()}`);
|
|
31
29
|
console.log('[svelte-on-rails:successful-json-response]' + JSON.stringify(res));
|
|
32
30
|
} catch (error) {
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
|
|
32
|
+
const res = {
|
|
33
|
+
status: 'SUCCESS',
|
|
34
|
+
html: componentRenderError(compiledComponentPath, error, true),
|
|
35
|
+
head: '',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
console.log(`<time>${performance.now()}`);
|
|
39
|
+
console.log('[svelte-on-rails:successful-json-response]' + JSON.stringify(res));
|
|
35
40
|
}
|
|
36
41
|
})();
|
|
37
42
|
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function componentRenderError(component, error, ssr = false) {
|
|
2
|
+
|
|
3
|
+
const hint = 'Check browser console for full details.'
|
|
4
|
+
const ssrHint = 'This is Server Side rendered. When Hydrated, you will see a more detailed error message.'
|
|
5
|
+
|
|
6
|
+
return (`
|
|
7
|
+
<div style="
|
|
8
|
+
padding: 1.5rem;
|
|
9
|
+
margin: 1rem 0;
|
|
10
|
+
border: 2px solid #ef4444;
|
|
11
|
+
background: #fef2f2;
|
|
12
|
+
color: #991b1b;
|
|
13
|
+
border-radius: 0.5rem;
|
|
14
|
+
font-family: system-ui, sans-serif;
|
|
15
|
+
">
|
|
16
|
+
<strong style="font-size: 1.2em;">Component failed to render</strong>
|
|
17
|
+
<p style="margin: 0.75rem 0 0;">
|
|
18
|
+
<code>${component}</code><br>
|
|
19
|
+
${error.message || 'Unknown rendering error'}
|
|
20
|
+
</p>
|
|
21
|
+
<small style="color: #7f1d1d; opacity: 0.8;">
|
|
22
|
+
${ssr ? ssrHint : hint}
|
|
23
|
+
</small>
|
|
24
|
+
</div>
|
|
25
|
+
`)
|
|
26
|
+
}
|