@effindomv2/create-fui-as-app 0.1.33 → 0.1.35
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/src/versions.d.ts +2 -2
- package/dist/src/versions.js +2 -2
- package/package.json +1 -1
- package/templates/hello/asconfig.json +1 -1
- package/templates/mvc/README.md +22 -0
- package/templates/mvc/asconfig.json +1 -1
- package/templates/mvc/harness.ts +4 -0
- package/templates/mvc/package.json +11 -3
- package/templates/mvc/scripts/build-worker-wasm.ts +66 -0
- package/templates/mvc/scripts/prepare-runtime.ts +1 -0
- package/templates/mvc/src/host/generated/WorkerHostServices.ts +9 -0
- package/templates/mvc/src/host/worker-host-services.ts +13 -0
- package/templates/mvc/src/worker-config.ts +13 -0
- package/templates/mvc/src/workers/advanced_workers.ts +96 -0
- package/templates/mvc/worker-host-services.ts +11 -0
- package/templates/mvc/worker-manifest.json +6 -0
package/dist/src/versions.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const FUI_AS_VERSION = "0.1.
|
|
2
|
-
export declare const RUNTIME_VERSION = "0.1.
|
|
1
|
+
export declare const FUI_AS_VERSION = "0.1.29";
|
|
2
|
+
export declare const RUNTIME_VERSION = "0.1.12";
|
package/dist/src/versions.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const FUI_AS_VERSION = "0.1.
|
|
2
|
-
export const RUNTIME_VERSION = "0.1.
|
|
1
|
+
export const FUI_AS_VERSION = "0.1.29";
|
|
2
|
+
export const RUNTIME_VERSION = "0.1.12";
|
package/package.json
CHANGED
package/templates/mvc/README.md
CHANGED
|
@@ -38,6 +38,28 @@ For example, to ship custom fonts alongside the bundled Noto set:
|
|
|
38
38
|
2. Add `"fonts/*.ttf": "fonts"` to `stage-assets.json`.
|
|
39
39
|
3. Reference them in app code as `/runtime/fonts/YourFont.ttf`.
|
|
40
40
|
|
|
41
|
+
## Workers
|
|
42
|
+
|
|
43
|
+
Workers are compiled as separate WASM modules and loaded on demand via `Worker.start(...)`. The worker manifest lives at `src/worker-config.ts`; add entries there for each worker entrypoint.
|
|
44
|
+
|
|
45
|
+
### Adding a worker
|
|
46
|
+
|
|
47
|
+
1. Create a worker module in `src/workers/` (use `advanced_workers.ts` as a reference).
|
|
48
|
+
2. Register it in `src/worker-config.ts`.
|
|
49
|
+
3. Add build scripts in `package.json` (copy the `build:wasm:workers:advanced` pattern).
|
|
50
|
+
4. Define any host services the worker needs in `src/host/worker-host-services.ts`, then regenerate with `npm run generate:worker-host-services`.
|
|
51
|
+
5. Ensure `worker-host-services.ts` at the project root registers the services under `globalThis.__fuiWorkerHostServicesModule`.
|
|
52
|
+
|
|
53
|
+
The harness (`harness.ts`) already wires `workerHostServices` to `startRoutedHarness`. Workers are available from any route via:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
const worker = Worker.start("advanced-workers")
|
|
57
|
+
.onProgress(this, (view, message) => { ... })
|
|
58
|
+
.onComplete(this, (view, result) => { ... })
|
|
59
|
+
.onError(this, (view, message) => { ... });
|
|
60
|
+
worker.sendString("input-data");
|
|
61
|
+
```
|
|
62
|
+
|
|
41
63
|
## Routing + deployment model
|
|
42
64
|
|
|
43
65
|
Each route builds to its own wasm (`home.wasm`, `settings.wasm`) and is mounted by routed harness config. This is designed for true MFE slices: each route app can evolve and deploy independently while still sharing the same browser host/runtime contract.
|
package/templates/mvc/harness.ts
CHANGED
|
@@ -24,4 +24,8 @@ startRoutedHarness<RouteExports>({
|
|
|
24
24
|
},
|
|
25
25
|
hostEvents: appHostEvents,
|
|
26
26
|
hostServices: appHostServices,
|
|
27
|
+
workerHostServices: {
|
|
28
|
+
scriptUrl: new URL('./worker-host-services.js', import.meta.url).toString(),
|
|
29
|
+
exportName: 'appWorkerHostServices',
|
|
30
|
+
},
|
|
27
31
|
});
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Scaffolded FUI-AS routed app",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"build": "npm run generate:host && npm run build:assets && npm run build:wasm && npm run build:harness",
|
|
9
|
-
"build:dev": "npm run generate:host && npm run build:assets && npm run build:wasm:dev && npm run build:harness",
|
|
10
|
-
"publish": "npm run generate:host && npm run build:assets && npm run build:wasm:publish && npm run build:harness && npm run publish:stage",
|
|
8
|
+
"build": "npm run generate:host && npm run build:assets && npm run build:wasm && npm run build:wasm:workers && npm run build:harness && npm run build:worker-bootstrap && npm run build:worker-host-services",
|
|
9
|
+
"build:dev": "npm run generate:host && npm run build:assets && npm run build:wasm:dev && npm run build:wasm:workers:dev && npm run build:harness && npm run build:worker-bootstrap && npm run build:worker-host-services",
|
|
10
|
+
"publish": "npm run generate:host && npm run build:assets && npm run build:wasm:publish && npm run build:wasm:workers:publish && npm run build:harness && npm run build:worker-bootstrap && npm run build:worker-host-services && npm run publish:stage",
|
|
11
11
|
"build:assets": "tsx scripts/prepare-runtime.ts",
|
|
12
12
|
"build:wasm": "npm run build:wasm:home && npm run build:wasm:settings",
|
|
13
13
|
"build:wasm:home": "asc src/routes/HomeApp.ts --config asconfig.json --target release --outFile public/home.wasm",
|
|
@@ -19,9 +19,17 @@
|
|
|
19
19
|
"build:wasm:publish:home": "asc src/routes/HomeApp.ts --config asconfig.json --target release --optimizeLevel 3 --shrinkLevel 2 --noAssert --outFile public/home.wasm",
|
|
20
20
|
"build:wasm:publish:settings": "asc src/routes/SettingsApp.ts --config asconfig.json --target release --optimizeLevel 3 --shrinkLevel 2 --noAssert --outFile public/settings.wasm",
|
|
21
21
|
"build:harness": "esbuild harness.ts --bundle --format=esm --platform=browser --outfile=public/harness.js",
|
|
22
|
+
"build:worker-host-services": "esbuild worker-host-services.ts --bundle --format=esm --platform=browser --outfile=public/worker-host-services.js",
|
|
23
|
+
"build:worker-bootstrap": "esbuild node_modules/@effindomv2/fui-as/browser/src/worker-bootstrap.ts --bundle --format=esm --platform=browser --outfile=public/worker-bootstrap.js",
|
|
24
|
+
|
|
25
|
+
"build:wasm:workers": "tsx scripts/build-worker-wasm.ts --all --target release",
|
|
26
|
+
"build:wasm:workers:dev": "tsx scripts/build-worker-wasm.ts --all --target debug",
|
|
27
|
+
"build:wasm:workers:publish": "tsx scripts/build-worker-wasm.ts --all --target release --publish",
|
|
22
28
|
"generate:host-services": "tsx ./node_modules/@effindomv2/fui-as/scripts/generate-host-services.ts src/host/host-services.ts appHostServices src/host/generated/HostServices.ts ../../fui/FuiPrimitives",
|
|
23
29
|
"generate:host-events": "tsx ./node_modules/@effindomv2/fui-as/scripts/generate-host-events.ts src/host/host-events.ts appHostEvents src/host/generated/HostEvents.ts ../../fui/FuiPrimitives",
|
|
24
30
|
"generate:host": "npm run generate:host-services && npm run generate:host-events",
|
|
31
|
+
"generate:worker-host-services": "tsx ./node_modules/@effindomv2/fui-as/scripts/generate-host-services.ts src/host/worker-host-services.ts appWorkerHostServices src/host/generated/WorkerHostServices.ts ../../fui/FuiPrimitives fui_host_service",
|
|
32
|
+
"generate:all": "npm run generate:host && npm run generate:worker-host-services",
|
|
25
33
|
"publish:stage": "rm -rf published && mkdir -p published && cp -R public/. published/ && cp -f index.html published/index.html && cp -f route-shell.html published/route-shell.html",
|
|
26
34
|
"watch": "chokidar \"src/**/*.ts\" \"harness.ts\" \"route-shell.html\" \"index.html\" \"asconfig.json\" --ignore \"src/host/generated/**\" -c \"npm run build:dev\"",
|
|
27
35
|
"serve": "serve public --listen 8080",
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { mkdirSync } from "node:fs";
|
|
3
|
+
import { workerManifest } from "../src/worker-config";
|
|
4
|
+
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
let target = "release";
|
|
7
|
+
let publish = false;
|
|
8
|
+
let workerKey = "";
|
|
9
|
+
let buildAll = false;
|
|
10
|
+
|
|
11
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
12
|
+
const arg = args[index];
|
|
13
|
+
switch (arg) {
|
|
14
|
+
case "--all":
|
|
15
|
+
buildAll = true;
|
|
16
|
+
break;
|
|
17
|
+
case "--worker":
|
|
18
|
+
workerKey = args[++index] ?? "";
|
|
19
|
+
break;
|
|
20
|
+
case "--target":
|
|
21
|
+
target = args[++index] ?? "release";
|
|
22
|
+
break;
|
|
23
|
+
case "--publish":
|
|
24
|
+
publish = true;
|
|
25
|
+
break;
|
|
26
|
+
default:
|
|
27
|
+
if (workerKey === "") {
|
|
28
|
+
workerKey = arg;
|
|
29
|
+
} else {
|
|
30
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
31
|
+
}
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const selectedWorkers = buildAll
|
|
37
|
+
? workerManifest
|
|
38
|
+
: workerManifest.filter((worker): boolean => worker.key === workerKey);
|
|
39
|
+
|
|
40
|
+
if (!buildAll && selectedWorkers.length === 0) {
|
|
41
|
+
throw new Error(`Unknown worker key: ${workerKey}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
mkdirSync("public", { recursive: true });
|
|
45
|
+
|
|
46
|
+
for (const worker of selectedWorkers) {
|
|
47
|
+
const ascArgs = [
|
|
48
|
+
worker.entrypoint,
|
|
49
|
+
"--config",
|
|
50
|
+
"asconfig.json",
|
|
51
|
+
"--target",
|
|
52
|
+
target,
|
|
53
|
+
"--outFile",
|
|
54
|
+
`public/${worker.wasmFile}`,
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
if (publish) {
|
|
58
|
+
ascArgs.push("--optimizeLevel", "3", "--shrinkLevel", "2", "--noAssert");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log(`Building worker ${worker.key} -> ${worker.wasmFile}`);
|
|
62
|
+
const result = spawnSync("asc", ascArgs, { stdio: "inherit" });
|
|
63
|
+
if (result.status !== 0) {
|
|
64
|
+
process.exit(result.status ?? 1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -100,6 +100,7 @@ writeFileSync(
|
|
|
100
100
|
"utf8",
|
|
101
101
|
);
|
|
102
102
|
copyFileSync("favicon.ico", `${outputDir}/favicon.ico`);
|
|
103
|
+
copyFileSync("worker-manifest.json", `${outputDir}/worker-manifest.json`);
|
|
103
104
|
const defaultRoute = resolvedManifest.routes.length == 0 ? undefined : resolvedManifest.routes[0];
|
|
104
105
|
const defaultRouteTitle = defaultRoute == null ? "FUI-AS Routed App" : defaultRoute.title;
|
|
105
106
|
const defaultRouteHref = defaultRoute == null ? "/home/" : defaultRoute.publishedRoutePath;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// Generated by scripts/generate-host-services.ts from ./src/host/worker-host-services.ts#appWorkerHostServices.
|
|
3
|
+
|
|
4
|
+
@external("fui_host_service", "appWorkerClockWallClockSinceEpochMs")
|
|
5
|
+
declare function __host_appWorkerClockWallClockSinceEpochMs(): f64;
|
|
6
|
+
|
|
7
|
+
export function appWorkerClockWallClockSinceEpochMs(): f64 {
|
|
8
|
+
return __host_appWorkerClockWallClockSinceEpochMs();
|
|
9
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineHostServices, hostService } from "@effindomv2/fui-as/browser/host-services";
|
|
2
|
+
|
|
3
|
+
export const appWorkerHostServices = defineHostServices({
|
|
4
|
+
appWorkerClock: {
|
|
5
|
+
wallClockSinceEpochMs: hostService({
|
|
6
|
+
args: [] as const,
|
|
7
|
+
returns: "f64",
|
|
8
|
+
implementation() {
|
|
9
|
+
return Date.now();
|
|
10
|
+
},
|
|
11
|
+
}),
|
|
12
|
+
},
|
|
13
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface WorkerEntry {
|
|
2
|
+
readonly key: string;
|
|
3
|
+
readonly entrypoint: string;
|
|
4
|
+
readonly wasmFile: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const workerManifest: readonly WorkerEntry[] = [
|
|
8
|
+
{
|
|
9
|
+
key: "advanced-workers",
|
|
10
|
+
entrypoint: "src/workers/advanced_workers.ts",
|
|
11
|
+
wasmFile: "advanced-workers.wasm",
|
|
12
|
+
},
|
|
13
|
+
];
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export * from "@effindomv2/fui-as/src/FuiWorkerExports";
|
|
2
|
+
|
|
3
|
+
import { WorkerJob } from "@effindomv2/fui-as/src/FuiWorker";
|
|
4
|
+
import { appWorkerClockWallClockSinceEpochMs } from "../host/generated/WorkerHostServices";
|
|
5
|
+
|
|
6
|
+
const PRIME_SEARCH_TOTAL_MS: f64 = 5000.0;
|
|
7
|
+
const PRIME_SEARCH_YIELD_INTERVAL_MS: f64 = 1000.0;
|
|
8
|
+
const PRIME_TIME_CHECK_MASK: i32 = 127;
|
|
9
|
+
|
|
10
|
+
function parsePrimeSearchPercent(startedAtMs: f64, nowMs: f64): i32 {
|
|
11
|
+
const elapsedMs = nowMs - startedAtMs;
|
|
12
|
+
if (elapsedMs <= 0.0) {
|
|
13
|
+
return 0;
|
|
14
|
+
}
|
|
15
|
+
if (elapsedMs >= PRIME_SEARCH_TOTAL_MS) {
|
|
16
|
+
return 100;
|
|
17
|
+
}
|
|
18
|
+
return <i32>((elapsedMs * 100.0) / PRIME_SEARCH_TOTAL_MS);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isPrime(value: i32): bool {
|
|
22
|
+
if (value < 2) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
if (value == 2) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
if ((value & 1) == 0) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
let divisor: i32 = 3;
|
|
32
|
+
while (divisor <= value / divisor) {
|
|
33
|
+
if (value % divisor == 0) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
divisor += 2;
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
class LargestPrimeCalculatorJob extends WorkerJob {
|
|
42
|
+
private startedAtMs: f64 = 0.0;
|
|
43
|
+
private deadlineMs: f64 = 0.0;
|
|
44
|
+
private nextYieldAtMs: f64 = 0.0;
|
|
45
|
+
private candidate: i32 = 2;
|
|
46
|
+
private largestPrime: i32 = 2;
|
|
47
|
+
|
|
48
|
+
protected onStart(): void {
|
|
49
|
+
this.receiveMessage();
|
|
50
|
+
const now = appWorkerClockWallClockSinceEpochMs();
|
|
51
|
+
this.startedAtMs = now;
|
|
52
|
+
this.deadlineMs = now + PRIME_SEARCH_TOTAL_MS;
|
|
53
|
+
this.nextYieldAtMs = now + PRIME_SEARCH_YIELD_INTERVAL_MS;
|
|
54
|
+
this.candidate = 2;
|
|
55
|
+
this.largestPrime = 2;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
run(): void {
|
|
59
|
+
if (this.isCancelled()) {
|
|
60
|
+
this.fail("cancelled:" + parsePrimeSearchPercent(this.startedAtMs, appWorkerClockWallClockSinceEpochMs()).toString());
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
let now = appWorkerClockWallClockSinceEpochMs();
|
|
64
|
+
const sliceDeadline = this.nextYieldAtMs < this.deadlineMs ? this.nextYieldAtMs : this.deadlineMs;
|
|
65
|
+
while (now < sliceDeadline) {
|
|
66
|
+
if (isPrime(this.candidate)) {
|
|
67
|
+
this.largestPrime = this.candidate;
|
|
68
|
+
}
|
|
69
|
+
this.candidate += 1;
|
|
70
|
+
if ((this.candidate & PRIME_TIME_CHECK_MASK) == 0) {
|
|
71
|
+
now = appWorkerClockWallClockSinceEpochMs();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
now = appWorkerClockWallClockSinceEpochMs();
|
|
75
|
+
this.reportProgress(parsePrimeSearchPercent(this.startedAtMs, now).toString());
|
|
76
|
+
if (now >= this.deadlineMs) {
|
|
77
|
+
this.complete(this.largestPrime.toString());
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this.nextYieldAtMs += PRIME_SEARCH_YIELD_INTERVAL_MS;
|
|
81
|
+
if (this.nextYieldAtMs > this.deadlineMs) {
|
|
82
|
+
this.nextYieldAtMs = this.deadlineMs;
|
|
83
|
+
}
|
|
84
|
+
this.yield();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let largestPrimeCalculatorJob: LargestPrimeCalculatorJob | null = null;
|
|
89
|
+
|
|
90
|
+
export function largestPrimeCalculatorWorker(): void {
|
|
91
|
+
let activeJob = largestPrimeCalculatorJob;
|
|
92
|
+
if (activeJob === null) {
|
|
93
|
+
activeJob = new LargestPrimeCalculatorJob();
|
|
94
|
+
}
|
|
95
|
+
largestPrimeCalculatorJob = WorkerJob.resume<LargestPrimeCalculatorJob>(activeJob);
|
|
96
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { appWorkerHostServices } from "./src/host/worker-host-services";
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
// eslint-disable-next-line no-var
|
|
5
|
+
var __fuiWorkerHostServicesModule: Record<string, unknown> | undefined;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
globalThis.__fuiWorkerHostServicesModule = {
|
|
9
|
+
...(globalThis.__fuiWorkerHostServicesModule ?? {}),
|
|
10
|
+
appWorkerHostServices,
|
|
11
|
+
};
|