@cloudcrane/app-runtime 0.0.0-stage → 0.2.0

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.
@@ -0,0 +1,16 @@
1
+ export declare class CompileError extends Error {
2
+ readonly problems: {
3
+ at: string;
4
+ problem: string;
5
+ }[];
6
+ constructor(problems: {
7
+ at: string;
8
+ problem: string;
9
+ }[]);
10
+ }
11
+ export type Compiled = {
12
+ html: string;
13
+ sha256: string;
14
+ runtime: string;
15
+ };
16
+ export declare function compile(input: unknown): Compiled;
@@ -0,0 +1,56 @@
1
+ import { createHash } from "node:crypto";
2
+ import { canonical, validateSpec } from "@cloudcrane/app-spec";
3
+ import { CODED_CSS, DATESTRIP_CSS } from "../runtime/styles.js";
4
+ import { tokensCss } from "../runtime/theme.js";
5
+ import { RUNTIME_JS, RUNTIME_SHA256, RUNTIME_VERSION } from "./runtime.generated.js";
6
+ // The compiler: a spec in, one HTML page out, the same bytes every time for the
7
+ // same spec and runtime version. It writes no code: it checks the spec, turns
8
+ // the tokens into CSS, puts the spec in the page as data (canonical JSON) and
9
+ // inlines the prebuilt runtime that draws it. Nothing here reads the time, the
10
+ // locale, the environment or a file.
11
+ export class CompileError extends Error {
12
+ problems;
13
+ constructor(problems) {
14
+ super(`The spec has ${problems.length} problem${problems.length === 1 ? "" : "s"}: ${problems.map((p) => `${p.at}: ${p.problem}`).join("; ")}`);
15
+ this.problems = problems;
16
+ }
17
+ }
18
+ /** The page's base: the tokens set A2UI's variables; this lays out the page around the surface. */
19
+ const BASE_CSS = "*{box-sizing:border-box}html,body{margin:0}body{background:var(--a2ui-color-background,#fff);color:var(--a2ui-color-on-background,#111);font-family:var(--a2ui-font-family,system-ui,sans-serif);-webkit-text-size-adjust:100%}#app{max-width:30rem;margin:0 auto;padding:max(16px,env(safe-area-inset-top)) 16px max(24px,env(safe-area-inset-bottom))}@media (prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}";
20
+ const escapeHtml = (s) => s.replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
21
+ /** JSON that can't end its own <script> element. */
22
+ const scriptSafe = (json) => json
23
+ .replace(/</g, "\\u003c")
24
+ .replace(new RegExp(String.fromCharCode(0x2028), "g"), "\\u2028")
25
+ .replace(new RegExp(String.fromCharCode(0x2029), "g"), "\\u2029");
26
+ export function compile(input) {
27
+ const { spec, problems } = validateSpec(input);
28
+ if (!spec)
29
+ throw new CompileError(problems);
30
+ if (spec.manifest.runtime !== RUNTIME_VERSION)
31
+ throw new CompileError([{ at: "manifest.runtime", problem: `this compiler builds runtime ${RUNTIME_VERSION}` }]);
32
+ // Only what drawing the app needs goes in the page: not the journeys, promises or tests.
33
+ // Coded components go in only when there are some.
34
+ const inPage = {
35
+ module: spec.module,
36
+ ...(Object.keys(spec.components).length ? { components: spec.components } : {}),
37
+ data: spec.data,
38
+ logic: spec.logic,
39
+ surface: spec.surface,
40
+ };
41
+ const html = "<!doctype html>\n" +
42
+ '<html lang="en"><head><meta charset="utf-8">' +
43
+ '<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">' +
44
+ `<meta name="cc-runtime" content="${RUNTIME_VERSION}">` +
45
+ `<title>${escapeHtml(spec.module.name)}</title>` +
46
+ `<style>${tokensCss(spec.tokens)}${BASE_CSS}${DATESTRIP_CSS}${CODED_CSS}</style>` +
47
+ '</head><body><main id="app"></main>' +
48
+ `<script type="application/json" id="cc-spec">${scriptSafe(canonical(inPage))}</script>` +
49
+ `<script type="module">${RUNTIME_JS}</script>` +
50
+ "</body></html>\n";
51
+ return {
52
+ html,
53
+ sha256: createHash("sha256").update(html).digest("hex"),
54
+ runtime: `@cloudcrane/app-runtime@${RUNTIME_VERSION}+${RUNTIME_SHA256.slice(0, 12)}`,
55
+ };
56
+ }