@cloudcrane/app-runtime 0.1.0 → 0.3.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.
- package/README.md +25 -2
- package/dist/compiler/compile.d.ts +20 -1
- package/dist/compiler/compile.js +22 -4
- package/dist/compiler/runtime.generated.d.ts +3 -3
- package/dist/compiler/runtime.generated.js +3 -3
- package/dist/host.d.ts +1 -1
- package/dist/host.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/runtime/styles.d.ts +1 -0
- package/dist/runtime/styles.js +1 -0
- package/dist/runtime/theme.d.ts +6 -1
- package/dist/runtime/theme.js +30 -4
- package/package.json +3 -2
- package/runtime-lock.json +2 -1
package/README.md
CHANGED
|
@@ -12,10 +12,33 @@ const { html, sha256, runtime } = compile(spec);
|
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
- `compile(spec)` returns the page, its sha256 and the runtime it was built
|
|
15
|
-
with (`@cloudcrane/app-runtime@0.
|
|
15
|
+
with (`@cloudcrane/app-runtime@0.2.0+<hash>`); it throws `CompileError`
|
|
16
16
|
listing every problem when the spec doesn't validate.
|
|
17
|
+
- `compile(spec, { runtime: "shared" })` links the runtime instead of inlining
|
|
18
|
+
it: the page names `runtimeFile().path` (`/_cc/runtime/<version>-<hash>.js`)
|
|
19
|
+
with an integrity hash, so a host serves one copy to every spec app and a
|
|
20
|
+
browser runs nothing but those bytes. The page is then a few KB. Inline is
|
|
21
|
+
the default, for a page that stands alone.
|
|
17
22
|
- Each runtime version is locked to one bundle hash (`runtime-lock.json`), so a
|
|
18
|
-
version always draws apps the same way.
|
|
23
|
+
version always draws apps the same way. The runtime's version
|
|
24
|
+
(`runtimeVersion` in package.json) moves only when the bundle does; the
|
|
25
|
+
package can release without it.
|
|
26
|
+
- A coded component runs in a frame that's sandboxed (scripts only, so it has
|
|
27
|
+
no storage and nothing of the page's), in a closed shadow root, reached only
|
|
28
|
+
through a `MessagePort`. Under CloudCrane's lockdown the lockdown makes the
|
|
29
|
+
frame (`window.__ccFrame`); elsewhere the runtime makes the same kind. The
|
|
30
|
+
page drives it: a seed and the inputs, then its clock each frame, paused
|
|
31
|
+
while hidden. The game kit turns that clock into fixed steps, so a game moves
|
|
32
|
+
the same way for the same time and seed:
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
game.start({ rate: 6, setup() {}, step() {}, draw(g, width, height) {} });
|
|
36
|
+
game.on("swipe", (dir) => {}); // also "tap" and "input"
|
|
37
|
+
game.state = { score }; // shown at /ui/coded/<id>/state
|
|
38
|
+
game.emit("over", { score }); // the screen's action for "over" runs
|
|
39
|
+
game.random(); // seeded by the page
|
|
40
|
+
```
|
|
41
|
+
|
|
19
42
|
- `@cloudcrane/app-runtime/host`: what a host publishing spec apps needs in one
|
|
20
43
|
import (validate, compile, derive the blueprint).
|
|
21
44
|
|
|
@@ -8,9 +8,28 @@ export declare class CompileError extends Error {
|
|
|
8
8
|
problem: string;
|
|
9
9
|
}[]);
|
|
10
10
|
}
|
|
11
|
+
/** Where a page that links the runtime finds it, and the hash its bytes must have. */
|
|
12
|
+
export type LinkedRuntime = {
|
|
13
|
+
path: string;
|
|
14
|
+
integrity: string;
|
|
15
|
+
};
|
|
11
16
|
export type Compiled = {
|
|
12
17
|
html: string;
|
|
13
18
|
sha256: string;
|
|
14
19
|
runtime: string;
|
|
20
|
+
linked?: LinkedRuntime;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* How a page carries the runtime: inline (the default: the page stands alone,
|
|
24
|
+
* as blackbox preview and anyone hosting it themselves need) or shared (it
|
|
25
|
+
* links one file every spec app on a host shares, which the host serves from
|
|
26
|
+
* runtimeFile(); the integrity hash means a browser runs nothing else).
|
|
27
|
+
*/
|
|
28
|
+
export type CompileOptions = {
|
|
29
|
+
runtime?: "inline" | "shared";
|
|
30
|
+
};
|
|
31
|
+
/** The runtime as the file a shared page links: its path (named by version and hash), its bytes and their integrity hash. */
|
|
32
|
+
export declare function runtimeFile(): LinkedRuntime & {
|
|
33
|
+
js: string;
|
|
15
34
|
};
|
|
16
|
-
export declare function compile(input: unknown): Compiled;
|
|
35
|
+
export declare function compile(input: unknown, options?: CompileOptions): Compiled;
|
package/dist/compiler/compile.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { canonical, validateSpec } from "@cloudcrane/app-spec";
|
|
3
|
-
import { DATESTRIP_CSS } from "../runtime/styles.js";
|
|
3
|
+
import { CODED_CSS, DATESTRIP_CSS } from "../runtime/styles.js";
|
|
4
4
|
import { tokensCss } from "../runtime/theme.js";
|
|
5
5
|
import { RUNTIME_JS, RUNTIME_SHA256, RUNTIME_VERSION } from "./runtime.generated.js";
|
|
6
6
|
// The compiler: a spec in, one HTML page out, the same bytes every time for the
|
|
@@ -15,6 +15,16 @@ export class CompileError extends Error {
|
|
|
15
15
|
this.problems = problems;
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
+
let file;
|
|
19
|
+
/** The runtime as the file a shared page links: its path (named by version and hash), its bytes and their integrity hash. */
|
|
20
|
+
export function runtimeFile() {
|
|
21
|
+
file ??= {
|
|
22
|
+
path: `/_cc/runtime/${RUNTIME_VERSION}-${RUNTIME_SHA256.slice(0, 12)}.js`,
|
|
23
|
+
js: RUNTIME_JS,
|
|
24
|
+
integrity: `sha256-${createHash("sha256").update(RUNTIME_JS).digest("base64")}`,
|
|
25
|
+
};
|
|
26
|
+
return file;
|
|
27
|
+
}
|
|
18
28
|
/** The page's base: the tokens set A2UI's variables; this lays out the page around the surface. */
|
|
19
29
|
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
30
|
const escapeHtml = (s) => s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
@@ -23,15 +33,20 @@ const scriptSafe = (json) => json
|
|
|
23
33
|
.replace(/</g, "\\u003c")
|
|
24
34
|
.replace(new RegExp(String.fromCharCode(0x2028), "g"), "\\u2028")
|
|
25
35
|
.replace(new RegExp(String.fromCharCode(0x2029), "g"), "\\u2029");
|
|
26
|
-
export function compile(input) {
|
|
36
|
+
export function compile(input, options = {}) {
|
|
37
|
+
const mode = options.runtime ?? "inline";
|
|
38
|
+
if (mode !== "inline" && mode !== "shared")
|
|
39
|
+
throw new TypeError("The runtime is inline or shared.");
|
|
27
40
|
const { spec, problems } = validateSpec(input);
|
|
28
41
|
if (!spec)
|
|
29
42
|
throw new CompileError(problems);
|
|
30
43
|
if (spec.manifest.runtime !== RUNTIME_VERSION)
|
|
31
44
|
throw new CompileError([{ at: "manifest.runtime", problem: `this compiler builds runtime ${RUNTIME_VERSION}` }]);
|
|
32
45
|
// Only what drawing the app needs goes in the page: not the journeys, promises or tests.
|
|
46
|
+
// Coded components go in only when there are some.
|
|
33
47
|
const inPage = {
|
|
34
48
|
module: spec.module,
|
|
49
|
+
...(Object.keys(spec.components).length ? { components: spec.components } : {}),
|
|
35
50
|
data: spec.data,
|
|
36
51
|
logic: spec.logic,
|
|
37
52
|
surface: spec.surface,
|
|
@@ -41,14 +56,17 @@ export function compile(input) {
|
|
|
41
56
|
'<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">' +
|
|
42
57
|
`<meta name="cc-runtime" content="${RUNTIME_VERSION}">` +
|
|
43
58
|
`<title>${escapeHtml(spec.module.name)}</title>` +
|
|
44
|
-
`<style>${tokensCss(spec.tokens)}${BASE_CSS}${DATESTRIP_CSS}</style>` +
|
|
59
|
+
`<style>${tokensCss(spec.tokens)}${BASE_CSS}${DATESTRIP_CSS}${CODED_CSS}</style>` +
|
|
45
60
|
'</head><body><main id="app"></main>' +
|
|
46
61
|
`<script type="application/json" id="cc-spec">${scriptSafe(canonical(inPage))}</script>` +
|
|
47
|
-
|
|
62
|
+
(mode === "shared"
|
|
63
|
+
? `<script type="module" src="${runtimeFile().path}" integrity="${runtimeFile().integrity}"></script>`
|
|
64
|
+
: `<script type="module">${RUNTIME_JS}</script>`) +
|
|
48
65
|
"</body></html>\n";
|
|
49
66
|
return {
|
|
50
67
|
html,
|
|
51
68
|
sha256: createHash("sha256").update(html).digest("hex"),
|
|
52
69
|
runtime: `@cloudcrane/app-runtime@${RUNTIME_VERSION}+${RUNTIME_SHA256.slice(0, 12)}`,
|
|
70
|
+
...(mode === "shared" ? { linked: { path: runtimeFile().path, integrity: runtimeFile().integrity } } : {}),
|
|
53
71
|
};
|
|
54
72
|
}
|