@jxsuite/compiler 5.1.2 → 5.1.3
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/dist/cli.js +220 -351
- package/dist/compiler.js +126 -257
- package/package.json +4 -4
- package/src/cli.ts +12 -14
- package/src/compiler.ts +45 -23
- package/src/shadow.ts +9 -8
- package/src/shared.ts +350 -296
- package/src/site/asset-mounts.ts +22 -12
- package/src/site/bundler.ts +38 -12
- package/src/site/client-runtime.ts +31 -17
- package/src/site/csp.ts +20 -11
- package/src/site/db-push.ts +38 -24
- package/src/site/dev-command.ts +3 -3
- package/src/site/format-host.ts +56 -45
- package/src/site/headers-emitter.ts +7 -4
- package/src/site/i18n.ts +20 -19
- package/src/site/image-cache.ts +16 -3
- package/src/site/image-optimizer.ts +19 -5
- package/src/site/image-transform.ts +56 -60
- package/src/site/img-loading.ts +11 -5
- package/src/site/layout-resolver.ts +7 -6
- package/src/site/link-relations.ts +10 -6
- package/src/site/locale-negotiation.ts +1 -1
- package/src/site/pages-discovery.ts +65 -47
- package/src/site/preview-server.ts +22 -19
- package/src/site/project-sections.ts +12 -3
- package/src/site/prototype-resolver.ts +94 -42
- package/src/site/schema-command.ts +26 -13
- package/src/site/service-worker.ts +2 -2
- package/src/site/site-build.ts +379 -262
- package/src/site/site-loader.ts +6 -3
- package/src/site/static-host.ts +3 -3
- package/src/site/validate-command.ts +64 -34
- package/src/site/well-known.ts +57 -12
- package/src/targets/compile-class.ts +17 -5
- package/src/targets/compile-client.ts +141 -106
- package/src/targets/compile-element.ts +157 -140
- package/src/targets/compile-server.ts +11 -5
- package/src/targets/compile-static.ts +57 -63
- package/src/types.ts +5 -6
|
@@ -10,7 +10,7 @@ import { dereference } from "@apidevtools/json-schema-ref-parser";
|
|
|
10
10
|
import { collectServerEntries } from "../shared.ts";
|
|
11
11
|
import { localeNegotiationMiddleware } from "../site/locale-negotiation.ts";
|
|
12
12
|
import type { ResolvedI18n } from "../site/i18n.ts";
|
|
13
|
-
import type { JxElement } from "@jxsuite/schema/types";
|
|
13
|
+
import type { JxDocumentValue, JxElement } from "@jxsuite/schema/types";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Compile a Jx document to a Hono server handler file. The handler exposes each `timing: "server"`
|
|
@@ -21,18 +21,21 @@ import type { JxElement } from "@jxsuite/schema/types";
|
|
|
21
21
|
* const server = await compileServer("./dashboard.json");
|
|
22
22
|
* if (server) fs.writeFileSync("dist/server.js", server);
|
|
23
23
|
*
|
|
24
|
-
* @param {string |
|
|
24
|
+
* @param {string | JxElement} sourcePath - Path to .json file, URL, or raw document
|
|
25
25
|
* @param {object} [opts]
|
|
26
26
|
* @param {string} [opts.baseUrl] - Base path for server endpoints. Default is `'/_jx/server'`
|
|
27
27
|
* @returns {Promise<string | null>} Hono server handler source string, or null
|
|
28
28
|
*/
|
|
29
29
|
export async function compileServer(
|
|
30
|
-
sourcePath: string |
|
|
30
|
+
sourcePath: string | JxElement,
|
|
31
31
|
opts: {
|
|
32
32
|
baseUrl?: string;
|
|
33
33
|
} = {},
|
|
34
34
|
) {
|
|
35
35
|
const { baseUrl = "/_jx/server" } = opts;
|
|
36
|
+
/* SAFETY: the parameter is a Jx element document or the path of one, and `dereference` only
|
|
37
|
+
replaces each `$ref` node with the value it points to, so the result is that same document
|
|
38
|
+
with its references inlined. A file read from disk is not re-validated here. */
|
|
36
39
|
const doc = (await dereference(sourcePath)) as JxElement;
|
|
37
40
|
const entries = collectServerEntries(doc);
|
|
38
41
|
if (entries.length === 0) {
|
|
@@ -74,8 +77,11 @@ export interface SiteMountSpec {
|
|
|
74
77
|
basePath: string;
|
|
75
78
|
/** Mount order (`server.order`, ascending). */
|
|
76
79
|
order: number;
|
|
77
|
-
/**
|
|
78
|
-
|
|
80
|
+
/**
|
|
81
|
+
* JSON-inlined mount options (project sections manifest etc.): project.json data, so document
|
|
82
|
+
* values, and a cleared (`undefined`) member is dropped by the `JSON.stringify` that inlines it.
|
|
83
|
+
*/
|
|
84
|
+
options: Record<string, JxDocumentValue | undefined>;
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
/** A connector provider class the worker imports and hands to mounts via `options.connectors`. */
|
|
@@ -19,14 +19,30 @@ import {
|
|
|
19
19
|
resolveStaticValue,
|
|
20
20
|
SELF_CLOSING,
|
|
21
21
|
} from "../shared.ts";
|
|
22
|
+
import type { CompileContext } from "../shared.ts";
|
|
22
23
|
import { emitElementModule } from "./compile-element.ts";
|
|
23
|
-
import
|
|
24
|
+
import { isBoolean, isNonNullObject, isNumber, isString } from "@jxsuite/schema/guards";
|
|
25
|
+
import type { JsonPrimitive, JxDocument, JxMutableNode, JxStyle } from "@jxsuite/schema/types";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A member of a node's `children` as a parsed document holds it: an element, a text string, or —
|
|
29
|
+
* since the document is JSON — a number, boolean or `null` the walk renders as text or skips.
|
|
30
|
+
*/
|
|
31
|
+
type StaticChild = JxMutableNode | JsonPrimitive;
|
|
32
|
+
|
|
33
|
+
/** A dynamic subtree lifted out of the static page, emitted as its own element module. */
|
|
34
|
+
interface Island {
|
|
35
|
+
def: JxDocument;
|
|
36
|
+
tagName: string;
|
|
37
|
+
className: string;
|
|
38
|
+
}
|
|
24
39
|
|
|
25
40
|
/**
|
|
26
41
|
* Compile a static document to HTML, with dynamic subtrees as islands.
|
|
27
42
|
*
|
|
28
43
|
* @param {JxDocument} raw - Raw JSON document (with $ref pointers preserved)
|
|
29
|
-
* @param
|
|
44
|
+
* @param opts - Page title, CDN sources, project style, pre-paint switch and island `$src`
|
|
45
|
+
* rewriting
|
|
30
46
|
* @returns {{ html: string; files: { path: string; content: string; tagName: string }[] }}
|
|
31
47
|
*/
|
|
32
48
|
export function compileStaticPage(
|
|
@@ -37,19 +53,16 @@ export function compileStaticPage(
|
|
|
37
53
|
litHtmlSrc?: string;
|
|
38
54
|
projectStyle?: JxStyle | null;
|
|
39
55
|
prePaintScheme?: boolean;
|
|
40
|
-
|
|
56
|
+
/** Rewrite an island's Function-def `$src` specifier to its served URL. */
|
|
57
|
+
rewriteSrc?: (specifier: string) => string;
|
|
41
58
|
},
|
|
42
59
|
) {
|
|
43
60
|
const { title = "Jx App", reactivitySrc, litHtmlSrc } = opts;
|
|
44
61
|
|
|
45
62
|
const rootContext = createCompileContext(raw, null, raw.state ?? {}, raw.$media ?? {});
|
|
46
|
-
const styleBlock = compileStyles(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
(opts.projectStyle ?? null) as JxStyle | null,
|
|
50
|
-
);
|
|
51
|
-
const islands: { def: JxMutableNode; tagName: string; className: string }[] = [];
|
|
52
|
-
const bodyContent = compileNode(raw, false, raw, rootContext, islands);
|
|
63
|
+
const styleBlock = compileStyles(raw, raw.$media ?? {}, opts.projectStyle ?? null);
|
|
64
|
+
const islands: Island[] = [];
|
|
65
|
+
const bodyContent = compileNode(raw, false, rootContext, islands);
|
|
53
66
|
|
|
54
67
|
/** @type {{ path: string; content: string; tagName: string }[]} */
|
|
55
68
|
const files = [];
|
|
@@ -57,12 +70,7 @@ export function compileStaticPage(
|
|
|
57
70
|
let moduleScripts = "";
|
|
58
71
|
if (islands.length > 0) {
|
|
59
72
|
for (const island of islands) {
|
|
60
|
-
const moduleContent = emitElementModule(
|
|
61
|
-
island.def as JxDocument,
|
|
62
|
-
island.className,
|
|
63
|
-
[],
|
|
64
|
-
opts.rewriteSrc as ((specifier: string) => string) | undefined,
|
|
65
|
-
);
|
|
73
|
+
const moduleContent = emitElementModule(island.def, island.className, [], opts.rewriteSrc);
|
|
66
74
|
files.push({
|
|
67
75
|
content: moduleContent,
|
|
68
76
|
path: `_islands/${island.tagName}.js`,
|
|
@@ -115,48 +123,47 @@ export function compileStaticPage(
|
|
|
115
123
|
* Compile a single Jx node to an HTML string. Dynamic nodes become hydration islands; static nodes
|
|
116
124
|
* become plain HTML.
|
|
117
125
|
*
|
|
118
|
-
*
|
|
126
|
+
* The node is read as authored — `$ref` pointers preserved — because an island's element module
|
|
127
|
+
* resolves them itself at runtime.
|
|
128
|
+
*
|
|
129
|
+
* @param {StaticChild} def
|
|
119
130
|
* @param {boolean} dynamic
|
|
120
|
-
* @param {
|
|
121
|
-
* @param {
|
|
122
|
-
* @param {{ def: JxMutableNode; tagName: string; className: string }[]} islands
|
|
131
|
+
* @param {CompileContext} context
|
|
132
|
+
* @param {Island[]} islands
|
|
123
133
|
* @returns {string}
|
|
124
134
|
*/
|
|
125
135
|
function compileNode(
|
|
126
|
-
def:
|
|
136
|
+
def: StaticChild,
|
|
127
137
|
dynamic: boolean,
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
scope: Record<string, unknown> | null;
|
|
131
|
-
scopeDefs: Record<string, unknown>;
|
|
132
|
-
media: Record<string, string>;
|
|
133
|
-
preformatted?: boolean;
|
|
134
|
-
},
|
|
135
|
-
islands: { def: JxMutableNode; tagName: string; className: string }[],
|
|
138
|
+
context: CompileContext,
|
|
139
|
+
islands: Island[],
|
|
136
140
|
): string {
|
|
137
141
|
// String children are text nodes
|
|
138
|
-
if (
|
|
142
|
+
if (isString(def)) {
|
|
139
143
|
return escapeHtml(def);
|
|
140
144
|
}
|
|
141
|
-
if (
|
|
145
|
+
if (isNumber(def) || isBoolean(def)) {
|
|
142
146
|
return escapeHtml(String(def));
|
|
143
147
|
}
|
|
144
|
-
if (!def
|
|
148
|
+
if (!isNonNullObject(def)) {
|
|
145
149
|
return "";
|
|
146
150
|
}
|
|
147
151
|
|
|
148
152
|
const nextContext = createCompileContext(
|
|
149
|
-
|
|
153
|
+
def,
|
|
150
154
|
context.scope,
|
|
151
|
-
|
|
152
|
-
|
|
155
|
+
def.state ?? context.scopeDefs,
|
|
156
|
+
def.$media ?? context.media,
|
|
153
157
|
);
|
|
154
158
|
|
|
155
159
|
if (dynamic) {
|
|
156
160
|
const n = islands.length;
|
|
157
161
|
const tagName = `jx-island-${n}`;
|
|
158
162
|
const className = `JxIsland${n}`;
|
|
159
|
-
|
|
163
|
+
/* SAFETY: JxDocument is the element-node shape with `tagName` narrowed to a literal string,
|
|
164
|
+
and the island's `tagName` is the literal `jx-island-N` assigned here; every other member is
|
|
165
|
+
the authored node's own, carried over unchanged. */
|
|
166
|
+
const elementDef = { ...def, tagName } as JxDocument;
|
|
160
167
|
islands.push({ className, def: elementDef, tagName });
|
|
161
168
|
return `<${tagName}></${tagName}>`;
|
|
162
169
|
}
|
|
@@ -174,7 +181,7 @@ function compileNode(
|
|
|
174
181
|
|
|
175
182
|
// `white-space` inherits, so once inside a <pre> the whole subtree stays whitespace-significant.
|
|
176
183
|
const preformatted = context.preformatted === true || PREFORMATTED_TAGS.has(tag);
|
|
177
|
-
const inner = buildInnerWithIslands(def,
|
|
184
|
+
const inner = buildInnerWithIslands(def, { ...nextContext, preformatted }, islands);
|
|
178
185
|
|
|
179
186
|
return `<${tag}${attrs}>${inner}</${tag}>`;
|
|
180
187
|
}
|
|
@@ -184,40 +191,27 @@ function compileNode(
|
|
|
184
191
|
* those that are actually dynamic.
|
|
185
192
|
*
|
|
186
193
|
* @param {JxMutableNode} def
|
|
187
|
-
* @param {
|
|
188
|
-
* @param {
|
|
189
|
-
* @param {{ def: JxMutableNode; tagName: string; className: string }[]} islands
|
|
194
|
+
* @param {CompileContext} context
|
|
195
|
+
* @param {Island[]} islands
|
|
190
196
|
* @returns {string}
|
|
191
197
|
*/
|
|
192
198
|
function buildInnerWithIslands(
|
|
193
199
|
def: JxMutableNode,
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
scope: Record<string, unknown> | null;
|
|
197
|
-
scopeDefs: Record<string, unknown>;
|
|
198
|
-
media: Record<string, string>;
|
|
199
|
-
preformatted?: boolean;
|
|
200
|
-
},
|
|
201
|
-
islands: { def: JxMutableNode; tagName: string; className: string }[],
|
|
200
|
+
context: CompileContext,
|
|
201
|
+
islands: Island[],
|
|
202
202
|
): string {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if (source.textContent !== undefined) {
|
|
206
|
-
const value = resolveStaticValue(source.textContent, context.scope);
|
|
203
|
+
if (def.textContent !== undefined) {
|
|
204
|
+
const value = resolveStaticValue(def.textContent, context.scope);
|
|
207
205
|
return value == null ? "" : escapeHtml(String(value));
|
|
208
206
|
}
|
|
209
|
-
if (
|
|
210
|
-
|
|
207
|
+
if (def.innerHTML) {
|
|
208
|
+
// Raw HTML may itself be a template; one that resolves to nothing keeps its authored text.
|
|
209
|
+
const resolved = resolveStaticValue(def.innerHTML, context.scope);
|
|
210
|
+
return resolved == null ? def.innerHTML : String(resolved);
|
|
211
211
|
}
|
|
212
|
-
if (Array.isArray(
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
.map((c, i: number) => {
|
|
216
|
-
const child = c as JxMutableNode;
|
|
217
|
-
const childDynamic = isNodeDynamic(child);
|
|
218
|
-
const childRaw = (rawChildren?.[i] ?? c) as JxMutableNode;
|
|
219
|
-
return compileNode(child, childDynamic, childRaw, context, islands);
|
|
220
|
-
})
|
|
212
|
+
if (Array.isArray(def.children)) {
|
|
213
|
+
return def.children
|
|
214
|
+
.map((child) => compileNode(child, isNodeDynamic(child), context, islands))
|
|
221
215
|
.join(childSeparator(context.preformatted));
|
|
222
216
|
}
|
|
223
217
|
return "";
|
package/src/types.ts
CHANGED
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export type { HeadMergeContext } from "@jxsuite/site/head-merger";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
7
|
+
/**
|
|
8
|
+
* `SiteRoute` is owned by `@jxsuite/site/context`, whose `injectContext` is the consumer that reads
|
|
9
|
+
* it; the site build hands the same route to both, so there is one declaration of its shape.
|
|
10
|
+
*/
|
|
11
|
+
export type { SiteRoute } from "@jxsuite/site/context";
|