evolit 0.1.0-alpha.2 → 0.1.0-alpha.21
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 +73 -3
- package/package.json +13 -6
- package/src/build.js +37 -9
- package/src/cli.js +16 -9
- package/src/client-assets.js +326 -70
- package/src/compiler.js +176 -9
- package/src/deployment-runtime.js +246 -43
- package/src/development-events.js +96 -0
- package/src/navigation-client.js +519 -0
- package/src/navigation-server.js +3 -0
- package/src/navigation-url.js +28 -0
- package/src/render.js +328 -19
- package/src/route-config.js +3 -1
- package/src/route-segments.js +272 -0
- package/src/scaffold.js +4 -0
- package/src/server.js +156 -13
- package/src/ssr-adapter.js +127 -12
- package/src/terminal.js +32 -0
- package/templates/default/yarnrc.yml.template +1 -0
package/src/ssr-adapter.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { renderBootstrap, renderDocument } from "@litsx/ssr";
|
|
2
|
+
import { createRouteSegmentPayload, renderRouteSegmentPayload } from "./route-segments.js";
|
|
2
3
|
|
|
3
4
|
function escapeHtmlAttribute(value) {
|
|
4
5
|
return String(value)
|
|
@@ -16,13 +17,52 @@ function normalizeHeadMarkup(value) {
|
|
|
16
17
|
return typeof value === "string" ? value : "";
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
function renderDescriptionMarkup(description) {
|
|
21
|
+
return description
|
|
22
|
+
? `<meta name="description" content="${escapeHtmlAttribute(description)}" />`
|
|
23
|
+
: "";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function renderRouteHeadMarkup(markup) {
|
|
27
|
+
return `<!--evolit:route-head:start-->${markup}<!--evolit:route-head:end-->`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeDocumentAttributes(attributes) {
|
|
31
|
+
if (!attributes || typeof attributes !== "object" || Array.isArray(attributes)) return {};
|
|
32
|
+
return Object.fromEntries(
|
|
33
|
+
Object.entries(attributes)
|
|
34
|
+
.filter(([name, value]) => name !== "lang" && /^[^\s"'>/=]+$/.test(name) && value !== false && value != null)
|
|
35
|
+
.map(([name, value]) => [name, value === true ? true : String(value)]),
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function renderDocumentAttributes(attributes) {
|
|
40
|
+
return Object.entries(normalizeDocumentAttributes(attributes))
|
|
41
|
+
.map(([name, value]) => value === true ? name : `${name}="${escapeHtmlAttribute(value)}"`)
|
|
42
|
+
.join(" ");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function createDocumentState(metadata) {
|
|
46
|
+
return {
|
|
47
|
+
lang: String(metadata.lang ?? "en"),
|
|
48
|
+
htmlAttributes: normalizeDocumentAttributes(metadata.htmlAttributes),
|
|
49
|
+
bodyAttributes: normalizeDocumentAttributes(metadata.bodyAttributes),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function renderDocumentStateScript(metadata) {
|
|
54
|
+
const serialized = JSON.stringify(createDocumentState(metadata))
|
|
55
|
+
.replaceAll("<", "\\u003C")
|
|
56
|
+
.replaceAll(">", "\\u003E")
|
|
57
|
+
.replaceAll("&", "\\u0026");
|
|
58
|
+
return `<script type="application/json" id="__EVOLIT_DOCUMENT__">${serialized}</script>`;
|
|
59
|
+
}
|
|
60
|
+
|
|
19
61
|
function resolveDocumentMetadata(routeMetadata = {}, adapterOptions = {}) {
|
|
20
62
|
const description =
|
|
21
63
|
routeMetadata.description ?? adapterOptions.description ?? "A LitSX application";
|
|
22
64
|
const extraHead = [
|
|
23
|
-
description
|
|
24
|
-
? `<meta name="description" content="${escapeHtmlAttribute(description)}" />`
|
|
25
|
-
: "",
|
|
65
|
+
renderDescriptionMarkup(description),
|
|
26
66
|
normalizeHeadMarkup(adapterOptions.head),
|
|
27
67
|
normalizeHeadMarkup(routeMetadata.head),
|
|
28
68
|
]
|
|
@@ -32,6 +72,7 @@ function resolveDocumentMetadata(routeMetadata = {}, adapterOptions = {}) {
|
|
|
32
72
|
return {
|
|
33
73
|
lang: String(routeMetadata.lang ?? adapterOptions.lang ?? "en"),
|
|
34
74
|
title: String(routeMetadata.title ?? adapterOptions.title ?? "evolit"),
|
|
75
|
+
description: String(description),
|
|
35
76
|
head: extraHead,
|
|
36
77
|
htmlAttributes: {
|
|
37
78
|
...(adapterOptions.htmlAttributes ?? {}),
|
|
@@ -46,19 +87,18 @@ function resolveDocumentMetadata(routeMetadata = {}, adapterOptions = {}) {
|
|
|
46
87
|
|
|
47
88
|
function createHtmlDocument({ body, metadata = {} }) {
|
|
48
89
|
const title = metadata.title ? String(metadata.title) : "evolit";
|
|
49
|
-
const
|
|
50
|
-
|
|
90
|
+
const htmlAttributes = renderDocumentAttributes(metadata.htmlAttributes);
|
|
91
|
+
const bodyAttributes = renderDocumentAttributes(metadata.bodyAttributes);
|
|
51
92
|
|
|
52
93
|
return `<!DOCTYPE html>
|
|
53
|
-
<html lang="${escapeHtmlAttribute(metadata.lang ?? "en")}">
|
|
94
|
+
<html lang="${escapeHtmlAttribute(metadata.lang ?? "en")}"${htmlAttributes ? ` ${htmlAttributes}` : ""}>
|
|
54
95
|
<head>
|
|
55
96
|
<meta charset="utf-8" />
|
|
56
97
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
57
|
-
<title>${title}</title
|
|
58
|
-
<meta name="description" content="${escapeHtmlAttribute(description)}" />` : ""}
|
|
98
|
+
<title>${title}</title>
|
|
59
99
|
${metadata.head ? ` ${String(metadata.head).split("\n").join("\n ")}` : ""}
|
|
60
100
|
</head>
|
|
61
|
-
<body>${body}</body>
|
|
101
|
+
<body${bodyAttributes ? ` ${bodyAttributes}` : ""}>${body}</body>
|
|
62
102
|
</html>`;
|
|
63
103
|
}
|
|
64
104
|
|
|
@@ -69,6 +109,15 @@ function createRouteHeaders(routeResult, headers = {}) {
|
|
|
69
109
|
};
|
|
70
110
|
}
|
|
71
111
|
|
|
112
|
+
function renderHydrationDataScript(hydrationData) {
|
|
113
|
+
if (!hydrationData) return "";
|
|
114
|
+
const serialized = JSON.stringify(hydrationData)
|
|
115
|
+
.replaceAll("<", "\\u003C")
|
|
116
|
+
.replaceAll(">", "\\u003E")
|
|
117
|
+
.replaceAll("&", "\\u0026");
|
|
118
|
+
return `<script type="application/json" id="__LITSX_HYDRATION__">${serialized}</script>`;
|
|
119
|
+
}
|
|
120
|
+
|
|
72
121
|
function responseHeadersToObject(headers) {
|
|
73
122
|
const values = Object.fromEntries(headers.entries());
|
|
74
123
|
if (typeof headers.getSetCookie === "function") {
|
|
@@ -111,6 +160,66 @@ export function createSsrAdapter(options = {}) {
|
|
|
111
160
|
};
|
|
112
161
|
}
|
|
113
162
|
|
|
163
|
+
if (typeof routeResult.tree === "string" && routeResult.ssrArtifacts) {
|
|
164
|
+
const result = routeResult.ssrArtifacts;
|
|
165
|
+
const metadata = resolveDocumentMetadata(routeResult.metadata, options);
|
|
166
|
+
const generatedBootstrap =
|
|
167
|
+
!options.bootstrap && !options.clientEntry && typeof options.resolveBootstrap === "function"
|
|
168
|
+
? await options.resolveBootstrap({ routeResult, result })
|
|
169
|
+
: "";
|
|
170
|
+
const additionalHead =
|
|
171
|
+
typeof options.resolveAdditionalHead === "function"
|
|
172
|
+
? await options.resolveAdditionalHead({ routeResult, result })
|
|
173
|
+
: "";
|
|
174
|
+
// Adapter head is global. Route metadata and renderer-provided head
|
|
175
|
+
// tags are delimited so browser navigation can replace only that part.
|
|
176
|
+
const routeHead = [
|
|
177
|
+
renderDescriptionMarkup(metadata.description),
|
|
178
|
+
normalizeHeadMarkup(routeResult.metadata?.head),
|
|
179
|
+
...(result.headTags ?? []),
|
|
180
|
+
]
|
|
181
|
+
.filter(Boolean)
|
|
182
|
+
.join("\n");
|
|
183
|
+
const composedHead = [
|
|
184
|
+
normalizeHeadMarkup(options.head),
|
|
185
|
+
renderRouteHeadMarkup(routeHead),
|
|
186
|
+
additionalHead,
|
|
187
|
+
]
|
|
188
|
+
.filter(Boolean)
|
|
189
|
+
.join("\n");
|
|
190
|
+
const document = createHtmlDocument({
|
|
191
|
+
body: `${routeResult.tree}${renderHydrationDataScript(result.hydrationData)}`,
|
|
192
|
+
metadata: {
|
|
193
|
+
...routeResult.metadata,
|
|
194
|
+
...metadata,
|
|
195
|
+
head: composedHead,
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
const transformedDocument =
|
|
199
|
+
typeof options.transformDocument === "function"
|
|
200
|
+
? await options.transformDocument({ routeResult, result, document })
|
|
201
|
+
: document;
|
|
202
|
+
const routeRuntimeScripts = [
|
|
203
|
+
renderRouteSegmentPayload(createRouteSegmentPayload(routeResult)),
|
|
204
|
+
renderDocumentStateScript(metadata),
|
|
205
|
+
].filter(Boolean).join("\n");
|
|
206
|
+
const documentWithRouteSegments = routeRuntimeScripts
|
|
207
|
+
? transformedDocument.replace("</body>", `${routeRuntimeScripts}\n</body>`)
|
|
208
|
+
: transformedDocument;
|
|
209
|
+
const body = generatedBootstrap
|
|
210
|
+
? documentWithRouteSegments.replace(
|
|
211
|
+
"</body>",
|
|
212
|
+
`${renderBootstrap({ bootstrap: { content: generatedBootstrap } })}\n</body>`,
|
|
213
|
+
)
|
|
214
|
+
: documentWithRouteSegments;
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
status: routeResult.status ?? 200,
|
|
218
|
+
headers: createRouteHeaders(routeResult, { "content-type": "text/html; charset=utf-8" }),
|
|
219
|
+
body,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
114
223
|
if (typeof routeResult.tree === "string") {
|
|
115
224
|
const metadata = resolveDocumentMetadata(routeResult.metadata, options);
|
|
116
225
|
return {
|
|
@@ -121,7 +230,6 @@ export function createSsrAdapter(options = {}) {
|
|
|
121
230
|
metadata: {
|
|
122
231
|
...routeResult.metadata,
|
|
123
232
|
...metadata,
|
|
124
|
-
description: routeResult.metadata?.description ?? options.description,
|
|
125
233
|
},
|
|
126
234
|
}),
|
|
127
235
|
};
|
|
@@ -156,6 +264,13 @@ export function createSsrAdapter(options = {}) {
|
|
|
156
264
|
document: documentWithAdditionalHead,
|
|
157
265
|
})
|
|
158
266
|
: documentWithAdditionalHead;
|
|
267
|
+
const routeRuntimeScripts = [
|
|
268
|
+
renderRouteSegmentPayload(createRouteSegmentPayload(routeResult)),
|
|
269
|
+
renderDocumentStateScript(documentOptions),
|
|
270
|
+
].filter(Boolean).join("\n");
|
|
271
|
+
const documentWithRouteSegments = routeRuntimeScripts
|
|
272
|
+
? transformedDocument.replace("</body>", `${routeRuntimeScripts}\n</body>`)
|
|
273
|
+
: transformedDocument;
|
|
159
274
|
|
|
160
275
|
if (generatedBootstrap) {
|
|
161
276
|
const bootstrapMarkup = renderBootstrap({
|
|
@@ -163,7 +278,7 @@ export function createSsrAdapter(options = {}) {
|
|
|
163
278
|
content: generatedBootstrap,
|
|
164
279
|
},
|
|
165
280
|
});
|
|
166
|
-
const documentWithBootstrap =
|
|
281
|
+
const documentWithBootstrap = documentWithRouteSegments.replace(
|
|
167
282
|
"</body>",
|
|
168
283
|
`${bootstrapMarkup}\n</body>`,
|
|
169
284
|
);
|
|
@@ -178,7 +293,7 @@ export function createSsrAdapter(options = {}) {
|
|
|
178
293
|
return {
|
|
179
294
|
status: routeResult.status ?? 200,
|
|
180
295
|
headers: createRouteHeaders(routeResult, { "content-type": "text/html; charset=utf-8" }),
|
|
181
|
-
body:
|
|
296
|
+
body: documentWithRouteSegments,
|
|
182
297
|
};
|
|
183
298
|
},
|
|
184
299
|
};
|
package/src/terminal.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const enabled = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
|
|
2
|
+
|
|
3
|
+
function style(open, value) {
|
|
4
|
+
return enabled ? `\u001B[${open}m${value}\u001B[0m` : String(value);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const terminal = {
|
|
8
|
+
bold(value) {
|
|
9
|
+
return style("1", value);
|
|
10
|
+
},
|
|
11
|
+
dim(value) {
|
|
12
|
+
return style("2", value);
|
|
13
|
+
},
|
|
14
|
+
red(value) {
|
|
15
|
+
return style("31", value);
|
|
16
|
+
},
|
|
17
|
+
green(value) {
|
|
18
|
+
return style("32", value);
|
|
19
|
+
},
|
|
20
|
+
yellow(value) {
|
|
21
|
+
return style("33", value);
|
|
22
|
+
},
|
|
23
|
+
blue(value) {
|
|
24
|
+
return style("34", value);
|
|
25
|
+
},
|
|
26
|
+
magenta(value) {
|
|
27
|
+
return style("35", value);
|
|
28
|
+
},
|
|
29
|
+
cyan(value) {
|
|
30
|
+
return style("36", value);
|
|
31
|
+
},
|
|
32
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
nodeLinker: node-modules
|