@jxsuite/compiler 0.29.0 → 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 +86 -86
- package/dist/compiler.js +56 -56
- package/package.json +7 -7
- package/src/cli.ts +38 -29
- package/src/compile-cli.ts +15 -7
- package/src/shared.ts +1 -1
- package/src/site/image-optimizer.ts +8 -1
- package/src/site/site-build.ts +6 -2
- 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": {
|
|
@@ -42,14 +42,14 @@
|
|
|
42
42
|
"upgrade": "bunx npm-check-updates -u && bun install"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@apidevtools/json-schema-ref-parser": "^15.
|
|
46
|
-
"@jxsuite/parser": "^0.
|
|
47
|
-
"@jxsuite/runtime": "^0.
|
|
48
|
-
"@jxsuite/schema": "^0.
|
|
49
|
-
"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"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@happy-dom/global-registrator": "^20.10.
|
|
52
|
+
"@happy-dom/global-registrator": "^20.10.6",
|
|
53
53
|
"@types/mdast": "^4.0.4"
|
|
54
54
|
}
|
|
55
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/shared.ts
CHANGED
|
@@ -1024,7 +1024,7 @@ function _walkServerEntries(
|
|
|
1024
1024
|
|
|
1025
1025
|
// ─── Component pre-rendering ─────────────────────────────────────────────────
|
|
1026
1026
|
|
|
1027
|
-
const SELF_CLOSING = new Set<string>([
|
|
1027
|
+
export const SELF_CLOSING = new Set<string>([
|
|
1028
1028
|
"input",
|
|
1029
1029
|
"br",
|
|
1030
1030
|
"hr",
|
|
@@ -43,6 +43,13 @@ export interface ImageConfig {
|
|
|
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
|
|
package/src/site/site-build.ts
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
rmSync,
|
|
18
18
|
writeFileSync,
|
|
19
19
|
} from "node:fs";
|
|
20
|
-
import { dirname, join, resolve } from "node:path";
|
|
20
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
21
21
|
import { isMappedArray, isRef } from "@jxsuite/schema/guards";
|
|
22
22
|
import { loadProjectConfig } from "./site-loader.ts";
|
|
23
23
|
import { discoverPages, expandDynamicRoutes, readPageDocument } from "./pages-discovery.ts";
|
|
@@ -158,7 +158,11 @@ export async function buildSite(
|
|
|
158
158
|
formats: formatRegistry,
|
|
159
159
|
});
|
|
160
160
|
for (const f of result.files) {
|
|
161
|
-
|
|
161
|
+
// Reduce the emitted path (an absolute source path with .json swapped to .js) to a bare
|
|
162
|
+
// Filename for the dist/components/ output. basename handles both / and \ — a
|
|
163
|
+
// Forward-slash-only split left the full drive path on Windows, so resolve() then wrote
|
|
164
|
+
// The sidecar back into the source tree instead of dist.
|
|
165
|
+
const outName = basename(f.path);
|
|
162
166
|
writeFileSync(resolve(componentOutDir, outName), f.content, "utf8");
|
|
163
167
|
if (f.tagName) {
|
|
164
168
|
compiledComponentTags.push(f.tagName);
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
escapeHtml,
|
|
13
13
|
isNodeDynamic,
|
|
14
14
|
resolveStaticValue,
|
|
15
|
+
SELF_CLOSING,
|
|
15
16
|
} from "../shared.ts";
|
|
16
17
|
import { emitElementModule } from "./compile-element.ts";
|
|
17
18
|
import type { JxDocument, JxMutableNode, JxStyle } from "@jxsuite/schema/types";
|
|
@@ -143,6 +144,13 @@ function compileNode(
|
|
|
143
144
|
|
|
144
145
|
const tag = def.tagName ?? "div";
|
|
145
146
|
const attrs = buildAttrs(def, nextContext.scope);
|
|
147
|
+
|
|
148
|
+
// Void elements (br, hr, img, …) have no closing tag.
|
|
149
|
+
// Emitting `<br></br>` makes HTML parsers read `</br>` as a second `<br>`.
|
|
150
|
+
if (SELF_CLOSING.has(tag)) {
|
|
151
|
+
return `<${tag}${attrs}>`;
|
|
152
|
+
}
|
|
153
|
+
|
|
146
154
|
const inner = buildInnerWithIslands(def, raw, nextContext, islands);
|
|
147
155
|
|
|
148
156
|
return `<${tag}${attrs}>${inner}</${tag}>`;
|