@jxsuite/compiler 0.28.5 → 0.30.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.
- package/dist/cli.js +114 -109
- package/dist/compiler.js +93 -88
- package/package.json +8 -7
- package/src/cli.ts +38 -29
- package/src/compile-cli.ts +15 -7
- package/src/compiler.ts +6 -14
- package/src/shared.ts +27 -31
- package/src/site/content-loader.ts +7 -5
- package/src/site/context-injection.ts +1 -1
- package/src/site/image-cache.ts +2 -2
- package/src/site/image-optimizer.ts +11 -4
- package/src/site/image-transform.ts +5 -5
- package/src/site/pages-discovery.ts +4 -5
- package/src/site/site-build.ts +95 -42
- package/src/targets/compile-class.ts +7 -11
- package/src/targets/compile-client.ts +102 -42
- package/src/targets/compile-element.ts +82 -70
- package/src/targets/compile-static.ts +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.1",
|
|
4
4
|
"description": "Jx static HTML compiler, island detector, and site builder",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -35,20 +35,21 @@
|
|
|
35
35
|
"scripts": {
|
|
36
36
|
"prepare": "bun build src/compiler.ts src/cli.ts --outdir=dist --format=esm --minify --target=node",
|
|
37
37
|
"build": "bun run prepare",
|
|
38
|
+
"build:metafile": "bun build src/compiler.ts src/cli.ts --outdir=dist --format=esm --minify --target=node --metafile=metafile.json",
|
|
38
39
|
"build:site": "bun src/cli.ts build",
|
|
39
40
|
"test": "bun test --isolate",
|
|
40
41
|
"test:coverage": "bun test --isolate --coverage",
|
|
41
42
|
"upgrade": "bunx npm-check-updates -u && bun install"
|
|
42
43
|
},
|
|
43
44
|
"dependencies": {
|
|
44
|
-
"@apidevtools/json-schema-ref-parser": "^15.
|
|
45
|
-
"@jxsuite/parser": "^0.
|
|
46
|
-
"@jxsuite/runtime": "^0.
|
|
47
|
-
"@jxsuite/schema": "^0.
|
|
48
|
-
"sharp": "^0.35.
|
|
45
|
+
"@apidevtools/json-schema-ref-parser": "^15.4.0",
|
|
46
|
+
"@jxsuite/parser": "^0.30.1",
|
|
47
|
+
"@jxsuite/runtime": "^0.30.1",
|
|
48
|
+
"@jxsuite/schema": "^0.30.1",
|
|
49
|
+
"sharp": "^0.35.2"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
51
|
-
"@happy-dom/global-registrator": "^20.10.
|
|
52
|
+
"@happy-dom/global-registrator": "^20.10.6",
|
|
52
53
|
"@types/mdast": "^4.0.4"
|
|
53
54
|
}
|
|
54
55
|
}
|
package/src/cli.ts
CHANGED
|
@@ -9,11 +9,16 @@
|
|
|
9
9
|
|
|
10
10
|
import { resolve } from "node:path";
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
// The CLI body runs inside an async function rather than via top-level await: when a test pulls this
|
|
13
|
+
// Entry in with a dynamic import(), Bun's test runtime drops the continuation after a top-level await
|
|
14
|
+
// (it never resumes on Windows), so the build step would silently never run. `ready` lets the
|
|
15
|
+
// In-process CLI harness await the same sequence.
|
|
16
|
+
async function main() {
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
const [command] = args;
|
|
14
19
|
|
|
15
|
-
if (!command || command === "--help" || command === "-h") {
|
|
16
|
-
|
|
20
|
+
if (!command || command === "--help" || command === "-h") {
|
|
21
|
+
console.log(`Usage: jx <command> [options]
|
|
17
22
|
|
|
18
23
|
Commands:
|
|
19
24
|
build [root] Build a Jx site to dist/
|
|
@@ -21,39 +26,43 @@ Commands:
|
|
|
21
26
|
Options:
|
|
22
27
|
--verbose Print detailed build progress
|
|
23
28
|
--no-clean Don't clean outDir before building`);
|
|
24
|
-
|
|
25
|
-
}
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
26
31
|
|
|
27
|
-
if (command === "build") {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
if (command === "build") {
|
|
33
|
+
const rest = args.slice(1);
|
|
34
|
+
const flags = new Set(rest.filter((a) => a.startsWith("--")));
|
|
35
|
+
const positional = rest.find((a) => !a.startsWith("--"));
|
|
31
36
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
const projectRoot = resolve(positional ?? ".");
|
|
38
|
+
const verbose = flags.has("--verbose");
|
|
39
|
+
const clean = !flags.has("--no-clean");
|
|
35
40
|
|
|
36
|
-
|
|
41
|
+
console.log(`Building site from ${projectRoot}...`);
|
|
37
42
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
try {
|
|
44
|
+
const { buildSite } = await import("./site/site-build.ts");
|
|
45
|
+
const result = await buildSite(projectRoot, { clean, verbose });
|
|
41
46
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
if (result.errors.length > 0) {
|
|
48
|
+
console.error(`\nBuild completed with ${result.errors.length} error(s):`);
|
|
49
|
+
for (const err of result.errors) {
|
|
50
|
+
console.error(` - ${err}`);
|
|
51
|
+
}
|
|
52
|
+
process.exit(1);
|
|
46
53
|
}
|
|
54
|
+
|
|
55
|
+
console.log(`\nDone: ${result.routes} routes → ${result.files} files`);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
const err = error as Error;
|
|
58
|
+
console.error(`Build failed: ${err.message}`);
|
|
47
59
|
process.exit(1);
|
|
48
60
|
}
|
|
49
|
-
|
|
50
|
-
console.
|
|
51
|
-
} catch (error) {
|
|
52
|
-
const err = error as Error;
|
|
53
|
-
console.error(`Build failed: ${err.message}`);
|
|
61
|
+
} else {
|
|
62
|
+
console.error(`Unknown command: ${command}\nRun "jx --help" for usage.`);
|
|
54
63
|
process.exit(1);
|
|
55
64
|
}
|
|
56
|
-
} else {
|
|
57
|
-
console.error(`Unknown command: ${command}\nRun "jx --help" for usage.`);
|
|
58
|
-
process.exit(1);
|
|
59
65
|
}
|
|
66
|
+
|
|
67
|
+
// oxlint-disable-next-line unicorn/prefer-top-level-await
|
|
68
|
+
export const ready = main();
|
package/src/compile-cli.ts
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { runCli } from "./compiler.ts";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
// Body wrapped in an async function rather than a top-level await: Bun's test runtime drops the
|
|
5
|
+
// Continuation after a dynamically-imported module's top-level await (it never resumes on Windows),
|
|
6
|
+
// Which would skip the error handling. `ready` lets the in-process CLI harness await the sequence.
|
|
7
|
+
async function main() {
|
|
8
|
+
const [src, out] = process.argv.slice(2);
|
|
9
|
+
if (src) {
|
|
10
|
+
try {
|
|
11
|
+
await runCli(src, out);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
console.error(error);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
11
16
|
}
|
|
12
17
|
}
|
|
18
|
+
|
|
19
|
+
// oxlint-disable-next-line unicorn/prefer-top-level-await
|
|
20
|
+
export const ready = main();
|
package/src/compiler.ts
CHANGED
|
@@ -22,13 +22,9 @@ import {
|
|
|
22
22
|
isDynamic,
|
|
23
23
|
tagNameToClassName,
|
|
24
24
|
} from "./shared.ts";
|
|
25
|
-
import { compileServer
|
|
25
|
+
import { compileServer } from "./targets/compile-server.ts";
|
|
26
26
|
|
|
27
|
-
import {
|
|
28
|
-
compileElement,
|
|
29
|
-
compileElementPage,
|
|
30
|
-
emitElementModule,
|
|
31
|
-
} from "./targets/compile-element.ts";
|
|
27
|
+
import { emitElementModule } from "./targets/compile-element.ts";
|
|
32
28
|
import { compileStaticPage } from "./targets/compile-static.ts";
|
|
33
29
|
import { compileClient } from "./targets/compile-client.ts";
|
|
34
30
|
import { isClassDef } from "@jxsuite/schema/guards";
|
|
@@ -37,14 +33,10 @@ import type { JxDocument, JxStyle } from "@jxsuite/schema/types";
|
|
|
37
33
|
import type { FormatRegistry } from "@jxsuite/schema/format-registry";
|
|
38
34
|
|
|
39
35
|
// Re-exports for consumers
|
|
40
|
-
export {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
compileElement,
|
|
45
|
-
compileElementPage,
|
|
46
|
-
compileClient,
|
|
47
|
-
};
|
|
36
|
+
export { isDynamic } from "./shared.ts";
|
|
37
|
+
export { compileServer, compileSiteServer } from "./targets/compile-server.ts";
|
|
38
|
+
export { compileElement, compileElementPage } from "./targets/compile-element.ts";
|
|
39
|
+
export { compileClient } from "./targets/compile-client.ts";
|
|
48
40
|
|
|
49
41
|
// ─── Entry ────────────────────────────────────────────────────────────────────
|
|
50
42
|
|
package/src/shared.ts
CHANGED
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { RESERVED_KEYS, camelToKebab, toCSSText } from "@jxsuite/runtime";
|
|
10
|
-
import {
|
|
10
|
+
import { evaluateExpression, isMutating } from "@jxsuite/runtime/expression";
|
|
11
11
|
import {
|
|
12
|
+
childrenContainArray,
|
|
12
13
|
isExpressionDef,
|
|
13
14
|
isFunctionDef,
|
|
15
|
+
isMappedArray,
|
|
14
16
|
isPrototypeDef,
|
|
15
17
|
isRef,
|
|
16
18
|
isSchemaOnlyDef as isSchemaOnly,
|
|
@@ -22,7 +24,6 @@ import type { ExpressionNode } from "@jxsuite/runtime/expression";
|
|
|
22
24
|
import type {
|
|
23
25
|
JsonValue,
|
|
24
26
|
JxElement,
|
|
25
|
-
JxMappedArray,
|
|
26
27
|
JxMutableNode,
|
|
27
28
|
JxPrototypeDef,
|
|
28
29
|
JxRef,
|
|
@@ -32,15 +33,9 @@ import type {
|
|
|
32
33
|
} from "@jxsuite/schema/types";
|
|
33
34
|
|
|
34
35
|
// Re-export runtime utilities used by submodules
|
|
35
|
-
export {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
RESERVED_KEYS,
|
|
39
|
-
compileExpression,
|
|
40
|
-
isMutating,
|
|
41
|
-
evaluateExpression,
|
|
42
|
-
};
|
|
43
|
-
export type { ExpressionNode };
|
|
36
|
+
export { RESERVED_KEYS, camelToKebab, toCSSText } from "@jxsuite/runtime";
|
|
37
|
+
export { compileExpression, evaluateExpression, isMutating } from "@jxsuite/runtime/expression";
|
|
38
|
+
export type { ExpressionNode } from "@jxsuite/runtime/expression";
|
|
44
39
|
|
|
45
40
|
// CDN defaults
|
|
46
41
|
export const DEFAULT_REACTIVITY_SRC = "https://esm.sh/@vue/reactivity@3.5.32";
|
|
@@ -110,10 +105,7 @@ export function isDynamic(def: JxElement | JxMutableNode | string) {
|
|
|
110
105
|
if (def.$switch) {
|
|
111
106
|
return true;
|
|
112
107
|
}
|
|
113
|
-
if (
|
|
114
|
-
!Array.isArray(def.children) &&
|
|
115
|
-
(def.children as JxMappedArray | undefined)?.$prototype === "Array"
|
|
116
|
-
) {
|
|
108
|
+
if (isMappedArray(def) || childrenContainArray(def.children)) {
|
|
117
109
|
return true;
|
|
118
110
|
}
|
|
119
111
|
|
|
@@ -170,10 +162,7 @@ export function isNodeDynamic(def: JxElement | JxMutableNode | string) {
|
|
|
170
162
|
if (def.$switch) {
|
|
171
163
|
return true;
|
|
172
164
|
}
|
|
173
|
-
if (
|
|
174
|
-
!Array.isArray(def.children) &&
|
|
175
|
-
(def.children as JxMappedArray | undefined)?.$prototype === "Array"
|
|
176
|
-
) {
|
|
165
|
+
if (isMappedArray(def) || childrenContainArray(def.children)) {
|
|
177
166
|
return true;
|
|
178
167
|
}
|
|
179
168
|
|
|
@@ -252,9 +241,9 @@ export function createCompileContext(
|
|
|
252
241
|
scopeDefs: Record<string, unknown> = {},
|
|
253
242
|
media: Record<string, string> = {},
|
|
254
243
|
): CompileContext {
|
|
255
|
-
const scope = raw?.state
|
|
244
|
+
const scope: Record<string, unknown> = raw?.state
|
|
256
245
|
? buildInitialScope(raw.state, parentScope)
|
|
257
|
-
: (parentScope ?? Object.create(null));
|
|
246
|
+
: (parentScope ?? (Object.create(null) as Record<string, unknown>));
|
|
258
247
|
return { media, scope, scopeDefs };
|
|
259
248
|
}
|
|
260
249
|
|
|
@@ -267,7 +256,7 @@ export function buildInitialScope(
|
|
|
267
256
|
defs: Record<string, JxStateDefinition> = {},
|
|
268
257
|
parentScope: Record<string, unknown> | null = null,
|
|
269
258
|
) {
|
|
270
|
-
const scope = Object.create(parentScope ?? null)
|
|
259
|
+
const scope = Object.create(parentScope ?? null) as Record<string, unknown>;
|
|
271
260
|
|
|
272
261
|
for (const [key, def] of Object.entries(defs)) {
|
|
273
262
|
if (typeof def !== "object" || def === null || Array.isArray(def)) {
|
|
@@ -306,7 +295,9 @@ export function buildInitialScope(
|
|
|
306
295
|
if (isFunctionDef(def)) {
|
|
307
296
|
if (def.body) {
|
|
308
297
|
const names = def.parameters ? paramNames(def.parameters) : (def.arguments ?? []);
|
|
309
|
-
const fn = new Function("state", ...names, def.body)
|
|
298
|
+
const fn = new Function("state", ...names, def.body) as (
|
|
299
|
+
state: Record<string, unknown>,
|
|
300
|
+
) => unknown;
|
|
310
301
|
if (def.body.includes("return")) {
|
|
311
302
|
defineLazyScopeValue(scope, key, () => fn(scope));
|
|
312
303
|
} else {
|
|
@@ -393,7 +384,7 @@ export function resolveRefValue(refValue: unknown, scope: Record<string, unknown
|
|
|
393
384
|
}
|
|
394
385
|
if (refValue.startsWith("$map/")) {
|
|
395
386
|
const parts = refValue.split("/");
|
|
396
|
-
const
|
|
387
|
+
const key = parts[1]!;
|
|
397
388
|
const base = (scope.$map as Record<string, unknown> | undefined)?.[key] ?? scope[`$map/${key}`];
|
|
398
389
|
return parts.length > 2 ? getPathValue(base, parts.slice(2).join("/")) : base;
|
|
399
390
|
}
|
|
@@ -417,10 +408,16 @@ export function evaluateStaticTemplate(str: string, scope: Record<string, unknow
|
|
|
417
408
|
try {
|
|
418
409
|
const singleExprMatch = str.match(/^\$\{(.+)\}$/s);
|
|
419
410
|
if (singleExprMatch) {
|
|
420
|
-
const fn = new Function("state", "$map", `return (${singleExprMatch[1]})`)
|
|
411
|
+
const fn = new Function("state", "$map", `return (${singleExprMatch[1]})`) as (
|
|
412
|
+
state: Record<string, unknown>,
|
|
413
|
+
$map: unknown,
|
|
414
|
+
) => unknown;
|
|
421
415
|
return fn(scope, scope?.$map);
|
|
422
416
|
}
|
|
423
|
-
const fn = new Function("state", "$map", `return \`${str}\``)
|
|
417
|
+
const fn = new Function("state", "$map", `return \`${str}\``) as (
|
|
418
|
+
state: Record<string, unknown>,
|
|
419
|
+
$map: unknown,
|
|
420
|
+
) => unknown;
|
|
424
421
|
return fn(scope, scope?.$map);
|
|
425
422
|
} catch {
|
|
426
423
|
return null;
|
|
@@ -632,7 +629,7 @@ export function compileStyles(
|
|
|
632
629
|
if (mProps) {
|
|
633
630
|
rules.push(`${atRule} { ${selector} { ${mProps} } }`);
|
|
634
631
|
}
|
|
635
|
-
for (const [sel, sub] of Object.entries(
|
|
632
|
+
for (const [sel, sub] of Object.entries(val as Record<string, unknown>)) {
|
|
636
633
|
if (sub === null || typeof sub !== "object" || Array.isArray(sub)) {
|
|
637
634
|
continue;
|
|
638
635
|
}
|
|
@@ -753,7 +750,7 @@ function emitNestedElement(
|
|
|
753
750
|
if (mProps) {
|
|
754
751
|
rules.push(`${atRule} { ${selector} { ${mProps} } }`);
|
|
755
752
|
}
|
|
756
|
-
for (const [sel, sub] of Object.entries(
|
|
753
|
+
for (const [sel, sub] of Object.entries(val as Record<string, unknown>)) {
|
|
757
754
|
if (sub === null || typeof sub !== "object" || Array.isArray(sub)) {
|
|
758
755
|
continue;
|
|
759
756
|
}
|
|
@@ -995,8 +992,7 @@ function _walkSrc(def: JxElement | JxMutableNode | string, srcs: Set<string>) {
|
|
|
995
992
|
* @returns {{ key: string; exportName: string; src: string }[]}
|
|
996
993
|
*/
|
|
997
994
|
export function collectServerEntries(doc: JxElement) {
|
|
998
|
-
|
|
999
|
-
const entries = new Map();
|
|
995
|
+
const entries = new Map<string, { key: string; exportName: string; src: string }>();
|
|
1000
996
|
_walkServerEntries(doc, entries);
|
|
1001
997
|
return [...entries.values()];
|
|
1002
998
|
}
|
|
@@ -1028,7 +1024,7 @@ function _walkServerEntries(
|
|
|
1028
1024
|
|
|
1029
1025
|
// ─── Component pre-rendering ─────────────────────────────────────────────────
|
|
1030
1026
|
|
|
1031
|
-
const SELF_CLOSING = new Set<string>([
|
|
1027
|
+
export const SELF_CLOSING = new Set<string>([
|
|
1032
1028
|
"input",
|
|
1033
1029
|
"br",
|
|
1034
1030
|
"hr",
|
|
@@ -21,6 +21,7 @@ import type { FormatEntry, FormatRegistry } from "@jxsuite/schema/format-registr
|
|
|
21
21
|
import type {
|
|
22
22
|
ContentTypeDef,
|
|
23
23
|
ContentTypeSchema,
|
|
24
|
+
ContentTypeSchemaField,
|
|
24
25
|
JxMutableNode,
|
|
25
26
|
ProjectConfig,
|
|
26
27
|
} from "@jxsuite/schema/types";
|
|
@@ -36,16 +37,16 @@ import type { ContentLoaderEntry } from "@jxsuite/parser/types";
|
|
|
36
37
|
* @returns {ContentLoaderEntry[]} Array of ContentEntry shapes
|
|
37
38
|
*/
|
|
38
39
|
function loadJSONEntries(filePath: string) {
|
|
39
|
-
const raw = JSON.parse(readFileSync(filePath, "utf8"));
|
|
40
|
+
const raw = JSON.parse(readFileSync(filePath, "utf8")) as unknown;
|
|
40
41
|
if (Array.isArray(raw)) {
|
|
41
|
-
return raw.map((item: Record<string, unknown>, i: number) => ({
|
|
42
|
+
return (raw as Record<string, unknown>[]).map((item: Record<string, unknown>, i: number) => ({
|
|
42
43
|
body: null,
|
|
43
44
|
data: item,
|
|
44
45
|
id: (item.id as string) ?? `${basename(filePath, ".json")}-${i}`,
|
|
45
46
|
}));
|
|
46
47
|
}
|
|
47
48
|
// Single object file — filename is the id
|
|
48
|
-
const rawObj
|
|
49
|
+
const rawObj = raw as Record<string, unknown>;
|
|
49
50
|
return [
|
|
50
51
|
{
|
|
51
52
|
body: null,
|
|
@@ -108,7 +109,7 @@ export async function loadContentTypes(
|
|
|
108
109
|
) {
|
|
109
110
|
const result = loadContentConfig(projectRoot, projectConfig);
|
|
110
111
|
if (!result) {
|
|
111
|
-
return new Map();
|
|
112
|
+
return new Map<string, ContentLoaderEntry[]>();
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
const { config } = result;
|
|
@@ -359,7 +360,8 @@ export function resolveContentTypeRefs(
|
|
|
359
360
|
continue;
|
|
360
361
|
}
|
|
361
362
|
|
|
362
|
-
for (const [field,
|
|
363
|
+
for (const [field, rawDef] of Object.entries(schema.properties)) {
|
|
364
|
+
const def = rawDef as ContentTypeSchemaField;
|
|
363
365
|
if (!def.$ref?.startsWith("#/contentTypes/")) {
|
|
364
366
|
continue;
|
|
365
367
|
}
|
|
@@ -36,7 +36,7 @@ export function injectContext(
|
|
|
36
36
|
doc: JxDocument,
|
|
37
37
|
projectConfig: ProjectConfig,
|
|
38
38
|
route: SiteRoute,
|
|
39
|
-
|
|
39
|
+
_contentTypes = new Map<string, ContentLoaderEntry[]>(),
|
|
40
40
|
projectRoot: string | null = null,
|
|
41
41
|
) {
|
|
42
42
|
if (!doc.state) {
|
package/src/site/image-cache.ts
CHANGED
|
@@ -94,13 +94,13 @@ export function cacheKey(srcPath: string, config: ImageConfig) {
|
|
|
94
94
|
* @param {string} projectRoot
|
|
95
95
|
* @returns {CacheManifest}
|
|
96
96
|
*/
|
|
97
|
-
export function loadCache(projectRoot: string) {
|
|
97
|
+
export function loadCache(projectRoot: string): CacheManifest {
|
|
98
98
|
const manifestPath = resolve(getImageCacheDir(projectRoot), "manifest.json");
|
|
99
99
|
if (!existsSync(manifestPath)) {
|
|
100
100
|
return { entries: {}, version: 1 };
|
|
101
101
|
}
|
|
102
102
|
try {
|
|
103
|
-
return JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
103
|
+
return JSON.parse(readFileSync(manifestPath, "utf8")) as CacheManifest;
|
|
104
104
|
} catch {
|
|
105
105
|
return { entries: {}, version: 1 };
|
|
106
106
|
}
|
|
@@ -10,7 +10,8 @@ import { errorMessage } from "@jxsuite/schema/parse";
|
|
|
10
10
|
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
11
11
|
import { basename, extname, resolve } from "node:path";
|
|
12
12
|
import { createRequire } from "node:module";
|
|
13
|
-
|
|
13
|
+
// Sharp 0.35 only has a default export (function merged with a types namespace).
|
|
14
|
+
import type SharpNS from "sharp";
|
|
14
15
|
|
|
15
16
|
export interface ImageVariant {
|
|
16
17
|
width: number; // Pixel width of the variant
|
|
@@ -41,8 +42,14 @@ export interface ImageConfig {
|
|
|
41
42
|
remoteDomains?: string[];
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
type SharpModule = typeof SharpNS;
|
|
46
|
+
/**
|
|
47
|
+
* Accepted output-format type, derived from the Sharp instance's own toFormat signature. Reaching
|
|
48
|
+
* into the `SharpNS.FormatEnum` namespace instead is fragile: sharp ships as `export = sharp`, so
|
|
49
|
+
* whether a default type-import exposes the merged namespace depends on esModuleInterop resolution
|
|
50
|
+
* (differs local vs CI).
|
|
51
|
+
*/
|
|
52
|
+
type SharpFormat = Parameters<ReturnType<SharpModule>["toFormat"]>[0];
|
|
46
53
|
|
|
47
54
|
let _sharp: SharpModule | null = null;
|
|
48
55
|
|
|
@@ -175,7 +182,7 @@ export async function processImage(srcPath: string, cacheImgDir: string, config:
|
|
|
175
182
|
const quality = config.quality[format as keyof ImageConfig["quality"]] ?? 80;
|
|
176
183
|
const task = sharp(srcPath)
|
|
177
184
|
.resize(width)
|
|
178
|
-
.toFormat(format as
|
|
185
|
+
.toFormat(format as SharpFormat, { quality })
|
|
179
186
|
.toFile(absolutePath)
|
|
180
187
|
.then(() => {});
|
|
181
188
|
|
|
@@ -340,7 +340,7 @@ async function transformImgNode(
|
|
|
340
340
|
imageRefs.set(absoluteSrc, manifest);
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
-
const preferredFormat = config.formats.includes("avif") ? "avif" : config.formats[0]
|
|
343
|
+
const preferredFormat = config.formats.includes("avif") ? "avif" : config.formats[0]!;
|
|
344
344
|
srcset = buildSrcset(manifest.variants, preferredFormat);
|
|
345
345
|
}
|
|
346
346
|
}
|
|
@@ -384,8 +384,8 @@ async function transformInnerHtmlImages(
|
|
|
384
384
|
const replacements = [];
|
|
385
385
|
|
|
386
386
|
for (const m of html.matchAll(IMG_TAG_RE)) {
|
|
387
|
-
const
|
|
388
|
-
const
|
|
387
|
+
const tag = m[0]!;
|
|
388
|
+
const attrs = m[1]!;
|
|
389
389
|
|
|
390
390
|
if (SRCSET_ATTR_RE.test(attrs)) {
|
|
391
391
|
continue;
|
|
@@ -400,7 +400,7 @@ async function transformInnerHtmlImages(
|
|
|
400
400
|
}
|
|
401
401
|
|
|
402
402
|
// Attribute values in pre-rendered innerHTML are entity-escaped — decode for processing
|
|
403
|
-
const src = srcMatch[1]
|
|
403
|
+
const src = srcMatch[1]!.replaceAll("&", "&");
|
|
404
404
|
const remote = isAllowedRemote(src, config);
|
|
405
405
|
if (!remote && shouldSkip(src)) {
|
|
406
406
|
continue;
|
|
@@ -425,7 +425,7 @@ async function transformInnerHtmlImages(
|
|
|
425
425
|
imageRefs.set(absoluteSrc as string, manifest);
|
|
426
426
|
}
|
|
427
427
|
|
|
428
|
-
const preferredFormat = config.formats.includes("avif") ? "avif" : config.formats[0]
|
|
428
|
+
const preferredFormat = config.formats.includes("avif") ? "avif" : config.formats[0]!;
|
|
429
429
|
srcset = buildSrcset(manifest.variants, preferredFormat);
|
|
430
430
|
}
|
|
431
431
|
if (!srcset) {
|
|
@@ -169,7 +169,7 @@ async function fileToRoute(relativePath: string, absolutePath: string, registry?
|
|
|
169
169
|
// Convert [param] → :param and [...param] → *
|
|
170
170
|
const urlPattern = urlPath.replaceAll(
|
|
171
171
|
/\[\.\.\.(\w+)\]|\[(\w+)\]/g,
|
|
172
|
-
(
|
|
172
|
+
(_match: string, spread: string, named: string) => {
|
|
173
173
|
if (spread) {
|
|
174
174
|
isCatchAll = true;
|
|
175
175
|
isDynamic = true;
|
|
@@ -319,10 +319,9 @@ function resolvePathEntries(
|
|
|
319
319
|
// Data file ref: { "$ref": "./data/products.json", param: "id", field: "sku" }
|
|
320
320
|
if ("$ref" in $paths && $paths.$ref) {
|
|
321
321
|
const filePath = resolve(projectRoot, $paths.$ref);
|
|
322
|
-
|
|
323
|
-
let data;
|
|
322
|
+
let data: unknown;
|
|
324
323
|
try {
|
|
325
|
-
data = JSON.parse(readFileSync(filePath, "utf8"));
|
|
324
|
+
data = JSON.parse(readFileSync(filePath, "utf8")) as unknown;
|
|
326
325
|
} catch (error) {
|
|
327
326
|
const err = error as Error;
|
|
328
327
|
console.warn(`Warning: $paths.$ref could not load "${$paths.$ref}": ${err.message}`);
|
|
@@ -334,7 +333,7 @@ function resolvePathEntries(
|
|
|
334
333
|
}
|
|
335
334
|
const param = $paths.param ?? "id";
|
|
336
335
|
const field = $paths.field ?? "id";
|
|
337
|
-
return data.map((item: Record<string, unknown>) => ({
|
|
336
|
+
return (data as Record<string, unknown>[]).map((item: Record<string, unknown>) => ({
|
|
338
337
|
[param]: item[field] ?? item.id ?? String(item),
|
|
339
338
|
}));
|
|
340
339
|
}
|