@effindomv2/create-fui-as-app 0.1.32 → 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/README.md +26 -0
- package/templates/hello/asconfig.json +1 -1
- package/templates/hello/scripts/prepare-runtime.ts +68 -1
- package/templates/hello/stage-assets.json +3 -0
- package/templates/mvc/README.md +48 -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 +69 -1
- 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/stage-assets.json +3 -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
|
@@ -8,6 +8,32 @@ This template is the smallest FUI-AS app shape:
|
|
|
8
8
|
- `src/host/host-services.ts` and `src/host/host-events.ts` define app-owned JS bridge contracts.
|
|
9
9
|
- `src/host/generated/*` is generated from those host definition files.
|
|
10
10
|
|
|
11
|
+
## Staging project assets
|
|
12
|
+
|
|
13
|
+
`stage-assets.json` is a convention-over-configuration manifest for copying project-owned files into `public/runtime/` during `npm run build:assets`. You should never need to edit `scripts/prepare-runtime.ts` — just drop files into your project and declare them here.
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"stage": {
|
|
18
|
+
"fonts/*.ttf": "fonts",
|
|
19
|
+
"images/**": "images"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- **Keys** are **glob patterns** relative to the project root. Supported patterns:
|
|
25
|
+
- `"*.ext"` — all files with a given extension in the project root.
|
|
26
|
+
- `"dir/*.ext"` — all files matching the extension inside `dir/` (non-recursive).
|
|
27
|
+
- `"dir/**"` — copy the entire `dir/` tree recursively.
|
|
28
|
+
- **Values** are the **destination subdirectory** under `public/runtime/`. The value can be a nested path like `"fonts/custom"` and directories are created automatically.
|
|
29
|
+
- The config is optional — if `stage-assets.json` is missing, the build script skips staging with no error.
|
|
30
|
+
|
|
31
|
+
For example, to ship custom fonts alongside the bundled Noto set:
|
|
32
|
+
|
|
33
|
+
1. Place your `.ttf` files in a `fonts/` directory at the project root.
|
|
34
|
+
2. Add `"fonts/*.ttf": "fonts"` to `stage-assets.json`.
|
|
35
|
+
3. Reference them in app code as `/runtime/fonts/YourFont.ttf`.
|
|
36
|
+
|
|
11
37
|
## Typical workflow
|
|
12
38
|
|
|
13
39
|
1. Build UI from `src/App.ts` with controls/nodes from `./fui/Fui`.
|
|
@@ -1,12 +1,79 @@
|
|
|
1
|
-
import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, join } from "node:path";
|
|
2
3
|
|
|
3
4
|
const outputDir = "public";
|
|
4
5
|
|
|
6
|
+
interface StageConfig {
|
|
7
|
+
stage: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function resolveGlob(pattern: string): string[] {
|
|
11
|
+
const slash = pattern.lastIndexOf("/");
|
|
12
|
+
if (slash === -1) {
|
|
13
|
+
const suffix = pattern.startsWith("*.") ? pattern.slice(1) : "";
|
|
14
|
+
if (suffix.length === 0) return [];
|
|
15
|
+
return readdirSync(".").filter((f) => f.endsWith(suffix));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const dir = pattern.slice(0, slash);
|
|
19
|
+
const filePart = pattern.slice(slash + 1);
|
|
20
|
+
|
|
21
|
+
if (filePart === "**") {
|
|
22
|
+
return [dir];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (filePart.startsWith("*.")) {
|
|
26
|
+
const suffix = filePart.slice(1);
|
|
27
|
+
try {
|
|
28
|
+
return readdirSync(dir)
|
|
29
|
+
.filter((f) => f.endsWith(suffix))
|
|
30
|
+
.map((f) => join(dir, f));
|
|
31
|
+
} catch {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (existsSync(pattern)) {
|
|
37
|
+
return [pattern];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function stageProjectAssets(outputDir: string): void {
|
|
44
|
+
const configPath = "stage-assets.json";
|
|
45
|
+
if (!existsSync(configPath)) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const raw = readFileSync(configPath, "utf8");
|
|
50
|
+
const config: StageConfig = JSON.parse(raw);
|
|
51
|
+
|
|
52
|
+
for (const [pattern, destDir] of Object.entries(config.stage)) {
|
|
53
|
+
const matches = resolveGlob(pattern);
|
|
54
|
+
if (matches.length === 0) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const dest = join(outputDir, "runtime", destDir);
|
|
59
|
+
mkdirSync(dest, { recursive: true });
|
|
60
|
+
|
|
61
|
+
for (const match of matches) {
|
|
62
|
+
if (statSync(match).isDirectory()) {
|
|
63
|
+
cpSync(match, dest, { recursive: true });
|
|
64
|
+
} else {
|
|
65
|
+
copyFileSync(match, join(dest, basename(match)));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
5
71
|
rmSync(outputDir, { recursive: true, force: true });
|
|
6
72
|
mkdirSync(`${outputDir}/runtime`, { recursive: true });
|
|
7
73
|
|
|
8
74
|
cpSync("node_modules/@effindomv2/runtime/dist", `${outputDir}/runtime/dist`, { recursive: true });
|
|
9
75
|
cpSync("node_modules/@effindomv2/runtime/dist/fonts", `${outputDir}/runtime/fonts`, { recursive: true });
|
|
76
|
+
stageProjectAssets(outputDir);
|
|
10
77
|
copyFileSync("node_modules/@effindomv2/runtime/dist/bridge.js", `${outputDir}/bridge.js`);
|
|
11
78
|
if (existsSync("node_modules/@effindomv2/runtime/dist/bridge.js.map")) {
|
|
12
79
|
copyFileSync("node_modules/@effindomv2/runtime/dist/bridge.js.map", `${outputDir}/bridge.js.map`);
|
package/templates/mvc/README.md
CHANGED
|
@@ -12,6 +12,54 @@ This template scaffolds a multi-route FUI-AS app with explicit page-level MVC.
|
|
|
12
12
|
- `route-shell.html` is the per-route host shell copied for each route path.
|
|
13
13
|
- `src/host/host-services.ts` and `src/host/host-events.ts` are app-owned bridge contracts; `src/host/generated/*` is generated output.
|
|
14
14
|
|
|
15
|
+
## Staging project assets
|
|
16
|
+
|
|
17
|
+
`stage-assets.json` is a convention-over-configuration manifest for copying project-owned files into `public/runtime/` during `npm run build:assets`. You should never need to edit `scripts/prepare-runtime.ts` — just drop files into your project and declare them here.
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"stage": {
|
|
22
|
+
"fonts/*.ttf": "fonts",
|
|
23
|
+
"images/**": "images"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- **Keys** are **glob patterns** relative to the project root. Supported patterns:
|
|
29
|
+
- `"*.ext"` — all files with a given extension in the project root.
|
|
30
|
+
- `"dir/*.ext"` — all files matching the extension inside `dir/` (non-recursive).
|
|
31
|
+
- `"dir/**"` — copy the entire `dir/` tree recursively.
|
|
32
|
+
- **Values** are the **destination subdirectory** under `public/runtime/`. The value can be a nested path like `"fonts/custom"` and directories are created automatically.
|
|
33
|
+
- The config is optional — if `stage-assets.json` is missing, the build script skips staging with no error.
|
|
34
|
+
|
|
35
|
+
For example, to ship custom fonts alongside the bundled Noto set:
|
|
36
|
+
|
|
37
|
+
1. Place your `.ttf` files in a `fonts/` directory at the project root.
|
|
38
|
+
2. Add `"fonts/*.ttf": "fonts"` to `stage-assets.json`.
|
|
39
|
+
3. Reference them in app code as `/runtime/fonts/YourFont.ttf`.
|
|
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
|
+
|
|
15
63
|
## Routing + deployment model
|
|
16
64
|
|
|
17
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
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, join } from "node:path";
|
|
2
3
|
import { renderRoutedPageHead, resolveRouteManifest, routeHead } from "@effindomv2/fui-as/browser/routed-app-conventions";
|
|
3
4
|
import { routeManifest } from "../src/route-config";
|
|
4
5
|
|
|
@@ -9,6 +10,71 @@ const routeShellTemplate = readFileSync("route-shell.html", "utf8");
|
|
|
9
10
|
const loadingOverlayStyles = readFileSync("node_modules/@effindomv2/fui-as/browser/loading-overlay-styles.html", "utf8");
|
|
10
11
|
const loadingOverlayBody = readFileSync("node_modules/@effindomv2/fui-as/browser/loading-overlay-body.html", "utf8");
|
|
11
12
|
|
|
13
|
+
interface StageConfig {
|
|
14
|
+
stage: Record<string, string>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function resolveGlob(pattern: string): string[] {
|
|
18
|
+
const slash = pattern.lastIndexOf("/");
|
|
19
|
+
if (slash === -1) {
|
|
20
|
+
const suffix = pattern.startsWith("*.") ? pattern.slice(1) : "";
|
|
21
|
+
if (suffix.length === 0) return [];
|
|
22
|
+
return readdirSync(".").filter((f) => f.endsWith(suffix));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const dir = pattern.slice(0, slash);
|
|
26
|
+
const filePart = pattern.slice(slash + 1);
|
|
27
|
+
|
|
28
|
+
if (filePart === "**") {
|
|
29
|
+
return [dir];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (filePart.startsWith("*.")) {
|
|
33
|
+
const suffix = filePart.slice(1);
|
|
34
|
+
try {
|
|
35
|
+
return readdirSync(dir)
|
|
36
|
+
.filter((f) => f.endsWith(suffix))
|
|
37
|
+
.map((f) => join(dir, f));
|
|
38
|
+
} catch {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (existsSync(pattern)) {
|
|
44
|
+
return [pattern];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function stageProjectAssets(outputDir: string): void {
|
|
51
|
+
const configPath = "stage-assets.json";
|
|
52
|
+
if (!existsSync(configPath)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const raw = readFileSync(configPath, "utf8");
|
|
57
|
+
const config: StageConfig = JSON.parse(raw);
|
|
58
|
+
|
|
59
|
+
for (const [pattern, destDir] of Object.entries(config.stage)) {
|
|
60
|
+
const matches = resolveGlob(pattern);
|
|
61
|
+
if (matches.length === 0) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const dest = join(outputDir, "runtime", destDir);
|
|
66
|
+
mkdirSync(dest, { recursive: true });
|
|
67
|
+
|
|
68
|
+
for (const match of matches) {
|
|
69
|
+
if (statSync(match).isDirectory()) {
|
|
70
|
+
cpSync(match, dest, { recursive: true });
|
|
71
|
+
} else {
|
|
72
|
+
copyFileSync(match, join(dest, basename(match)));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
12
78
|
function renderLoadingOverlay(template: string): string {
|
|
13
79
|
return template
|
|
14
80
|
.replace("{{LOADING_OVERLAY_STYLES}}", loadingOverlayStyles)
|
|
@@ -23,6 +89,7 @@ for (const route of resolvedManifest.routes) {
|
|
|
23
89
|
|
|
24
90
|
cpSync("node_modules/@effindomv2/runtime/dist", `${outputDir}/runtime/dist`, { recursive: true });
|
|
25
91
|
cpSync("node_modules/@effindomv2/runtime/dist/fonts", `${outputDir}/runtime/fonts`, { recursive: true });
|
|
92
|
+
stageProjectAssets(outputDir);
|
|
26
93
|
copyFileSync("node_modules/@effindomv2/runtime/dist/bridge.js", `${outputDir}/bridge.js`);
|
|
27
94
|
if (existsSync("node_modules/@effindomv2/runtime/dist/bridge.js.map")) {
|
|
28
95
|
copyFileSync("node_modules/@effindomv2/runtime/dist/bridge.js.map", `${outputDir}/bridge.js.map`);
|
|
@@ -33,6 +100,7 @@ writeFileSync(
|
|
|
33
100
|
"utf8",
|
|
34
101
|
);
|
|
35
102
|
copyFileSync("favicon.ico", `${outputDir}/favicon.ico`);
|
|
103
|
+
copyFileSync("worker-manifest.json", `${outputDir}/worker-manifest.json`);
|
|
36
104
|
const defaultRoute = resolvedManifest.routes.length == 0 ? undefined : resolvedManifest.routes[0];
|
|
37
105
|
const defaultRouteTitle = defaultRoute == null ? "FUI-AS Routed App" : defaultRoute.title;
|
|
38
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
|
+
};
|