janela 0.1.1 → 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.
- package/README.md +156 -14
- package/bin/janela.mjs +148 -13
- package/package.json +3 -3
- package/runtime/janela.ts +248 -101
- package/shim/wvshim.cc +359 -46
- package/templates/index.html +24 -0
- package/templates/main.ts +36 -16
package/README.md
CHANGED
|
@@ -19,8 +19,33 @@ janela build # .janela/out/my-app (+ my-app.app on macOS)
|
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
Requirements: Node 18+, a C++ compiler (Xcode CLT on macOS; g++ +
|
|
22
|
-
`libwebkit2gtk-4.1-dev` on Linux). A worked
|
|
23
|
-
[`examples/demo`](examples/demo) — commands, events, and a
|
|
22
|
+
`libwebkit2gtk-4.1-dev` on Linux; see [Windows](#windows) below). A worked
|
|
23
|
+
example lives in [`examples/demo`](examples/demo) — commands, events, and a
|
|
24
|
+
file reader.
|
|
25
|
+
|
|
26
|
+
## Windows
|
|
27
|
+
|
|
28
|
+
`janela build` produces `.janela/out/<name>.exe` using the WebView2 backend.
|
|
29
|
+
|
|
30
|
+
**You need a MinGW-targeting clang on `PATH`** — [llvm-mingw][llvm-mingw],
|
|
31
|
+
MSYS2's `clang64` toolchain, or WinLibs. MSVC does **not** work: scriptc's
|
|
32
|
+
runtime uses POSIX types and calls (`ssize_t`, `nanosleep`, `clock_gettime`)
|
|
33
|
+
that the MSVC CRT does not provide. janela checks `clang -dumpmachine` and
|
|
34
|
+
tells you if the wrong one is first on `PATH`.
|
|
35
|
+
|
|
36
|
+
webview.h's Win32 backend includes `WebView2.h`, which Microsoft ships in a
|
|
37
|
+
nuget package rather than in the Windows SDK, so the first build downloads
|
|
38
|
+
`Microsoft.Web.WebView2` into `.janela/cache/` automatically. To build offline
|
|
39
|
+
or pin your own copy, point `JANELA_WEBVIEW2_INCLUDE` at a directory
|
|
40
|
+
containing `WebView2.h`. No `WebView2Loader.dll` is needed — webview.h has its
|
|
41
|
+
own loader — and end users need only the **WebView2 runtime**, which is
|
|
42
|
+
preinstalled on current Windows 10/11 (it ships with Edge).
|
|
43
|
+
|
|
44
|
+
Two caveats today: the binary is a console-subsystem app, so a console window
|
|
45
|
+
appears behind the UI (handy for `janela dev`, wrong for shipping), and there
|
|
46
|
+
is no installer step — you get a bare `.exe`, not an MSI.
|
|
47
|
+
|
|
48
|
+
[llvm-mingw]: https://github.com/mstorsjo/llvm-mingw/releases
|
|
24
49
|
|
|
25
50
|
## A project
|
|
26
51
|
|
|
@@ -44,19 +69,116 @@ Backend API (`src-host/main.ts`):
|
|
|
44
69
|
import type { JanelaApp } from "./janela";
|
|
45
70
|
|
|
46
71
|
export function setup(app: JanelaApp): void {
|
|
47
|
-
app.command("add", (
|
|
48
|
-
const a =
|
|
49
|
-
app.emit("added",
|
|
50
|
-
return
|
|
72
|
+
app.command("add", (args) => { // values in, values out
|
|
73
|
+
const a = args as { a: number; b: number };
|
|
74
|
+
app.emit("added", a.a + a.b); // push an event to the page
|
|
75
|
+
return a.a + a.b;
|
|
51
76
|
});
|
|
52
77
|
// app.quit() closes the window and returns from run()
|
|
53
78
|
}
|
|
54
79
|
```
|
|
55
80
|
|
|
81
|
+
## Async commands
|
|
82
|
+
|
|
83
|
+
A command that has to wait — or to chew through real work — should not freeze
|
|
84
|
+
the window. Register it with `commandAsync` and answer whenever you are ready;
|
|
85
|
+
the page keeps using the same `await janela.invoke(...)`.
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
app.commandAsync("wait", (args, resolve, reject) => {
|
|
89
|
+
const a = args as { ms: number };
|
|
90
|
+
app.sleep(a.ms, () => resolve("done")); // resolve later
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Work that cannot just wait: slice it, yielding to the UI between slices.
|
|
94
|
+
app.commandAsync("countTo", (args, resolve) => {
|
|
95
|
+
const a = args as { n: number };
|
|
96
|
+
let i = 0;
|
|
97
|
+
const step = (): void => {
|
|
98
|
+
const end = Math.min(i + 2_000_000, a.n);
|
|
99
|
+
for (; i < end; i++) { /* ... */ }
|
|
100
|
+
if (i < a.n) app.defer(step); else resolve(i);
|
|
101
|
+
};
|
|
102
|
+
app.defer(step);
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
- `app.defer(fn)` — run `fn` on the next turn of the host loop.
|
|
107
|
+
- `app.sleep(ms, fn)` — run `fn` after at least `ms`.
|
|
108
|
+
- `resolve(value)` / `reject(reason)` settle the page's promise; `reject`
|
|
109
|
+
makes `await janela.invoke(...)` throw. Settling twice is ignored.
|
|
110
|
+
|
|
111
|
+
## File I/O
|
|
112
|
+
|
|
113
|
+
`node:fs` works in a handler, but `readFileSync` **blocks the window** for as
|
|
114
|
+
long as the syscall runs — parking a promise does not change that. Use the
|
|
115
|
+
async pair instead: the syscall runs on a worker thread inside the shim, and
|
|
116
|
+
only the result crosses back to your (single-threaded) TypeScript.
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
app.commandAsync("readFile", (args, resolve) => {
|
|
120
|
+
const a = args as { path: string };
|
|
121
|
+
app.readFileAsync(a.path, (err, text) => {
|
|
122
|
+
if (err !== null) { resolve({ ok: false, error: err }); return; }
|
|
123
|
+
resolve({ ok: true, text });
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
app.writeFileAsync("out.txt", "contents", (err) => { /* err is null on success */ });
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Errors arrive as values, never throws — `err` carries a Node-shaped message
|
|
131
|
+
(`ENOENT: no such file or directory, open '/x'`). UTF-8 round-trips exactly,
|
|
132
|
+
astral characters included.
|
|
133
|
+
|
|
134
|
+
The payload still crosses the FFI boundary one byte per call (format 2), which
|
|
135
|
+
costs about **115 ms per MB on the UI thread** — fine for config files and
|
|
136
|
+
documents, wrong for streaming large media. See
|
|
137
|
+
[docs/async.md](../../docs/async.md) for the measurements.
|
|
138
|
+
|
|
139
|
+
**Use `app.sleep`, not `setTimeout`.** scriptc's own event loop is parked for
|
|
140
|
+
as long as the program sits inside the `run()` FFI call, so `setTimeout`,
|
|
141
|
+
`queueMicrotask` and `await` in host code never fire while the window is open
|
|
142
|
+
(they all run after it closes). janela supplies its own loop instead: a native
|
|
143
|
+
ticker posts work to the UI thread via `webview_dispatch`, and it only runs
|
|
144
|
+
while something is queued, so an idle app costs nothing.
|
|
145
|
+
|
|
146
|
+
**Still single-threaded.** scriptc's runtime is not thread-safe (concurrent
|
|
147
|
+
calls from several threads abort the process), so host code always runs on the
|
|
148
|
+
UI thread. Async here means *interleaved*, not parallel: a handler that blocks
|
|
149
|
+
without yielding still freezes the window. Slice long work with `defer`.
|
|
150
|
+
|
|
56
151
|
The backend is ordinary TypeScript with scriptc's stdlib — including a
|
|
57
152
|
`node:fs` subset — so "read a file" or "call an API" is just code in a
|
|
58
153
|
command handler, no plugin layer needed.
|
|
59
154
|
|
|
155
|
+
## Migrating from 0.1.x
|
|
156
|
+
|
|
157
|
+
Commands used to take and return **JSON text**; they now take and return
|
|
158
|
+
**values**, with the runtime handling serialisation. The page-side API
|
|
159
|
+
(`janela.invoke` / `janela.listen`) is unchanged.
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
// 0.1.x
|
|
163
|
+
app.command("add", (argsJson) => {
|
|
164
|
+
const a = JSON.parse(argsJson) as { a: number; b: number };
|
|
165
|
+
app.emit("added", JSON.stringify(a.a + a.b));
|
|
166
|
+
return JSON.stringify(a.a + a.b);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// 0.2.x
|
|
170
|
+
app.command("add", (args) => {
|
|
171
|
+
const a = args as { a: number; b: number };
|
|
172
|
+
app.emit("added", a.a + a.b);
|
|
173
|
+
return a.a + a.b;
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Mechanically: drop the `JSON.parse(argsJson)` (cast `args` instead), drop
|
|
178
|
+
every `JSON.stringify` around a result, `resolve`/`reject`/`emit` payload, and
|
|
179
|
+
return nothing at all where you used to return `"null"`. Requires Node 24 to
|
|
180
|
+
build (scriptc 0.0.35's floor).
|
|
181
|
+
|
|
60
182
|
## What the CLI hides
|
|
61
183
|
|
|
62
184
|
`janela build` assembles `.janela/build/` (runtime + your `main.ts` +
|
|
@@ -66,18 +188,19 @@ scriptc. On macOS the frameworks are linked as SDK `.tbd` stubs (scriptc has
|
|
|
66
188
|
no `-framework` support) and the binary is wrapped into an ad-hoc-signed
|
|
67
189
|
`.app` bundle.
|
|
68
190
|
|
|
69
|
-
## Constraints inherited from scriptc
|
|
191
|
+
## Constraints inherited from scriptc
|
|
70
192
|
|
|
71
|
-
- Command args
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
193
|
+
- Command args and results are ordinary values; the runtime serialises them at
|
|
194
|
+
the boundary, so anything that survives `JSON.stringify`/`JSON.parse` round
|
|
195
|
+
trips (including full Unicode). `args` is typed `unknown` — cast it to the
|
|
196
|
+
shape you expect.
|
|
75
197
|
- Never use a bare FFI call as a complete variable initializer or assignment
|
|
76
198
|
RHS — it is silently miscompiled. Wrap it in any expression (`+ 0`). Plain
|
|
77
199
|
TypeScript is unaffected; only the runtime does FFI, so app code rarely
|
|
78
200
|
meets this.
|
|
79
|
-
- One window per app for now
|
|
80
|
-
command blocks the UI
|
|
201
|
+
- One window per app for now. Host code is single-threaded: a synchronous
|
|
202
|
+
command blocks the UI while it runs — use `commandAsync` + `defer`/`sleep`
|
|
203
|
+
(see "Async commands") for anything slow.
|
|
81
204
|
- `console.log` from commands goes to stdout — visible under `janela dev`,
|
|
82
205
|
not when launched from Finder.
|
|
83
206
|
|
|
@@ -86,9 +209,28 @@ no `-framework` support) and the binary is wrapped into an ad-hoc-signed
|
|
|
86
209
|
Early proof of concept, macOS (arm64) and Linux (WebKitGTK). The design
|
|
87
210
|
notes and scriptc findings behind it are in
|
|
88
211
|
[docs/findings.md](docs/findings.md). Not yet: Windows, async commands
|
|
89
|
-
|
|
212
|
+
that run in parallel (host code is single-threaded; `commandAsync` interleaves
|
|
213
|
+
instead), native dialogs/tray/menus, multi-window,
|
|
90
214
|
icons/installers/notarization.
|
|
91
215
|
|
|
216
|
+
## Releasing
|
|
217
|
+
|
|
218
|
+
Bump `version` in `package.json` and merge to `main`. The publish workflow
|
|
219
|
+
then does the rest: it compares the version against the registry, and if it
|
|
220
|
+
is new, scaffolds/builds/runs a smoke app on Linux, publishes to npm with
|
|
221
|
+
trusted publishing (OIDC — no token secret), tags the published commit
|
|
222
|
+
`v<version>`, and opens a GitHub release with generated notes.
|
|
223
|
+
|
|
224
|
+
A merge that does not change the version is a clean no-op: nothing is
|
|
225
|
+
published and no tag is created. If a publish ever succeeds but the tagging
|
|
226
|
+
step does not, npm and git are briefly out of step — reconcile with:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
git tag -a v<version> -m "janela <version>" <commit>
|
|
230
|
+
git push origin v<version>
|
|
231
|
+
gh release create v<version> --generate-notes --verify-tag
|
|
232
|
+
```
|
|
233
|
+
|
|
92
234
|
## License
|
|
93
235
|
|
|
94
236
|
MIT. Bundles [webview/webview](https://github.com/webview/webview) headers
|
package/bin/janela.mjs
CHANGED
|
@@ -54,16 +54,95 @@ function loadConf(root) {
|
|
|
54
54
|
return c;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
// ---- Windows toolchain ------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
// scriptc's win32 lane is MinGW-shaped twice over: its runtime uses POSIX
|
|
60
|
+
// types the MSVC CRT lacks (ssize_t), and its event loop calls POSIX time
|
|
61
|
+
// APIs that only a full mingw-w64 provides ("the idle sleep is nanosleep
|
|
62
|
+
// (mingw-w64 ships it, over Sleep)" — scr_async.c). So Windows builds need a
|
|
63
|
+
// clang whose DEFAULT target is mingw: llvm-mingw, MSYS2's clang64, or
|
|
64
|
+
// WinLibs. A stock MSVC-targeting clang cannot compile scriptc's runtime, and
|
|
65
|
+
// zig's bundled mingw omits winpthreads, so it cannot either.
|
|
66
|
+
function winCcOrFail() {
|
|
67
|
+
const probe = spawnSync("clang", ["-dumpmachine"], { encoding: "utf8" });
|
|
68
|
+
if (probe.status !== 0) {
|
|
69
|
+
fail(
|
|
70
|
+
"Windows builds need a MinGW-targeting clang on PATH. Install llvm-mingw " +
|
|
71
|
+
"(https://github.com/mstorsjo/llvm-mingw/releases) or MSYS2's clang64 toolchain.",
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
const triple = probe.stdout.trim();
|
|
75
|
+
if (!/mingw|windows-gnu/i.test(triple)) {
|
|
76
|
+
fail(
|
|
77
|
+
`clang on PATH targets '${triple}', but scriptc's Windows runtime only builds with ` +
|
|
78
|
+
"MinGW (it uses ssize_t/nanosleep/clock_gettime, which the MSVC CRT lacks). " +
|
|
79
|
+
"Put a MinGW-targeting clang first on PATH — llvm-mingw " +
|
|
80
|
+
"(https://github.com/mstorsjo/llvm-mingw/releases) or MSYS2's clang64.",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return triple;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ---- WebView2 SDK (Windows only) -------------------------------------------
|
|
87
|
+
|
|
88
|
+
// webview.h's Win32 backend includes <WebView2.h>, which ships in Microsoft's
|
|
89
|
+
// nuget package rather than the Windows SDK. Fetch it once into the build
|
|
90
|
+
// cache (a .nupkg is a zip) unless the caller points at their own copy.
|
|
91
|
+
const WEBVIEW2_VERSION = "1.0.2903.40";
|
|
92
|
+
|
|
93
|
+
function webview2Include(cacheDir) {
|
|
94
|
+
const override = process.env.JANELA_WEBVIEW2_INCLUDE;
|
|
95
|
+
if (override) {
|
|
96
|
+
if (!existsSync(join(override, "WebView2.h"))) {
|
|
97
|
+
fail(`JANELA_WEBVIEW2_INCLUDE=${override} does not contain WebView2.h`);
|
|
98
|
+
}
|
|
99
|
+
return override;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const sdkDir = join(cacheDir, `webview2-${WEBVIEW2_VERSION}`);
|
|
103
|
+
const incDir = join(sdkDir, "build", "native", "include");
|
|
104
|
+
if (existsSync(join(incDir, "WebView2.h"))) return incDir;
|
|
105
|
+
|
|
106
|
+
console.log(`janela: fetching WebView2 SDK ${WEBVIEW2_VERSION}`);
|
|
107
|
+
const zip = join(cacheDir, `webview2-${WEBVIEW2_VERSION}.zip`);
|
|
108
|
+
const url = `https://www.nuget.org/api/v2/package/Microsoft.Web.WebView2/${WEBVIEW2_VERSION}`;
|
|
109
|
+
run([
|
|
110
|
+
"powershell", "-NoProfile", "-NonInteractive", "-Command",
|
|
111
|
+
`$ErrorActionPreference='Stop';` +
|
|
112
|
+
`[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12;` +
|
|
113
|
+
`Invoke-WebRequest -Uri '${url}' -OutFile '${zip}';` +
|
|
114
|
+
`Expand-Archive -Path '${zip}' -DestinationPath '${sdkDir}' -Force`,
|
|
115
|
+
]);
|
|
116
|
+
if (!existsSync(join(incDir, "WebView2.h"))) {
|
|
117
|
+
fail(`WebView2 SDK unpacked to ${sdkDir} but no build/native/include/WebView2.h`);
|
|
118
|
+
}
|
|
119
|
+
return incDir;
|
|
120
|
+
}
|
|
121
|
+
|
|
57
122
|
// ---- shim ----------------------------------------------------------------
|
|
58
123
|
|
|
59
124
|
function buildShim(cacheDir) {
|
|
60
125
|
const src = join(KIT, "shim", "wvshim.cc");
|
|
61
|
-
const
|
|
62
|
-
|
|
126
|
+
const win = process.platform === "win32";
|
|
127
|
+
// On Windows the object file is handed to the link directly: `ar` is not
|
|
128
|
+
// part of an MSVC toolchain, and a lone object needs no archive index.
|
|
129
|
+
const obj = join(cacheDir, win ? "wvshim.obj" : "wvshim.o");
|
|
130
|
+
const lib = win ? obj : join(cacheDir, "libwvshim.a");
|
|
63
131
|
if (existsSync(lib) && statSync(lib).mtimeMs > statSync(src).mtimeMs) return lib;
|
|
64
132
|
|
|
65
133
|
console.log("janela: compiling webview shim");
|
|
66
134
|
const inc = `-I${join(KIT, "vendor-webview", "core", "include")}`;
|
|
135
|
+
if (win) {
|
|
136
|
+
console.log(`janela: building for ${winCcOrFail()}`);
|
|
137
|
+
run([
|
|
138
|
+
"clang++", "-c", src, "-o", obj, "-std=c++17", "-O2", inc,
|
|
139
|
+
// WebView2.h from the nuget SDK, plus mingw's missing EventToken.h.
|
|
140
|
+
`-I${webview2Include(cacheDir)}`,
|
|
141
|
+
`-I${join(KIT, "vendor-webview", "compatibility", "mingw", "include")}`,
|
|
142
|
+
"-DWIN32_LEAN_AND_MEAN", "-D_WIN32_WINNT=0x0601",
|
|
143
|
+
]);
|
|
144
|
+
return lib;
|
|
145
|
+
}
|
|
67
146
|
if (process.platform === "darwin") {
|
|
68
147
|
run(["clang++", "-c", src, "-o", obj, "-std=c++17", "-O2", inc]);
|
|
69
148
|
} else {
|
|
@@ -87,28 +166,80 @@ function ffiManifest(shimLib) {
|
|
|
87
166
|
STR("wvInit", "wv_init"),
|
|
88
167
|
STR("wvEval", "wv_eval"),
|
|
89
168
|
STR("wvBind", "wv_bind"),
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
169
|
+
STR("wvReply", "wv_reply"),
|
|
170
|
+
// Retained handlers (format 4): registered once, valid for the app's
|
|
171
|
+
// lifetime, so wv_run is a plain blocking call. The request rides in as a
|
|
172
|
+
// `string` param (format 3) rather than a byte-at-a-time drain.
|
|
94
173
|
{
|
|
95
|
-
name: "
|
|
174
|
+
name: "wvOnInvoke", symbol: "wv_on_invoke",
|
|
96
175
|
params: [
|
|
97
176
|
"i32",
|
|
98
|
-
{ callback: { id: "
|
|
99
|
-
{ context: "
|
|
177
|
+
{ callback: { id: "inv", params: ["string", { context: "inv" }], returns: "i32", lifetime: "retained" } },
|
|
178
|
+
{ context: "inv" },
|
|
100
179
|
],
|
|
101
180
|
returns: "i32",
|
|
102
181
|
},
|
|
182
|
+
{
|
|
183
|
+
name: "wvOnTick", symbol: "wv_on_tick",
|
|
184
|
+
params: [
|
|
185
|
+
"i32",
|
|
186
|
+
{ callback: { id: "tick", params: [{ context: "tick" }], returns: "void", lifetime: "retained" } },
|
|
187
|
+
{ context: "tick" },
|
|
188
|
+
],
|
|
189
|
+
returns: "i32",
|
|
190
|
+
},
|
|
191
|
+
{ name: "wvRun", symbol: "wv_run", params: ["i32"], returns: "i32" },
|
|
103
192
|
{ name: "wvTerminate", symbol: "wv_terminate", params: ["i32"], returns: "i32" },
|
|
193
|
+
// async: deferred returns + the UI-thread pump behind app.defer/sleep
|
|
194
|
+
{ name: "wvDefer", symbol: "wv_defer", params: ["i32"], returns: "i32" },
|
|
195
|
+
{ name: "wvResolve", symbol: "wv_resolve", params: ["i32", "i32", "i32"], returns: "i32" },
|
|
196
|
+
{ name: "wvTickStart", symbol: "wv_tick_start", params: ["i32", "i32"], returns: "i32" },
|
|
197
|
+
{ name: "wvTickStop", symbol: "wv_tick_stop", params: ["i32"], returns: "i32" },
|
|
198
|
+
// async file I/O: the blocking syscall runs on a shim worker thread
|
|
199
|
+
{ name: "wvFsRead", symbol: "wv_fs_read", params: ["i32", "string"], returns: "i32" },
|
|
200
|
+
{ name: "wvFsWrite", symbol: "wv_fs_write", params: ["i32", "string", "string"], returns: "i32" },
|
|
201
|
+
{ name: "wvFsStatus", symbol: "wv_fs_status", params: ["i32", "i32"], returns: "i32" },
|
|
202
|
+
{
|
|
203
|
+
name: "wvFsTake", symbol: "wv_fs_take",
|
|
204
|
+
params: [
|
|
205
|
+
"i32", "i32",
|
|
206
|
+
{ callback: { id: "sink", params: ["string", { context: "sink" }], returns: "void", lifetime: "call" } },
|
|
207
|
+
{ context: "sink" },
|
|
208
|
+
],
|
|
209
|
+
returns: "i32",
|
|
210
|
+
},
|
|
211
|
+
{ name: "wvFsFree", symbol: "wv_fs_free", params: ["i32", "i32"], returns: "i32" },
|
|
104
212
|
];
|
|
105
213
|
|
|
214
|
+
if (process.platform === "win32") {
|
|
215
|
+
// MinGW ignores MSVC's #pragma comment(lib, ...), so the Win32 imports the
|
|
216
|
+
// WebView2 backend needs are named explicitly. scriptc's own win32 lane
|
|
217
|
+
// already adds advapi32/iphlpapi/ws2_32, so those are omitted here.
|
|
218
|
+
// `c++` pulls libc++ for the shim's std::string/exceptions.
|
|
219
|
+
//
|
|
220
|
+
// `pthread` (mingw's libwinpthread) is here to work around an upstream
|
|
221
|
+
// scriptc bug: its runtime calls clock_gettime/nanosleep, which mingw
|
|
222
|
+
// declares in <time.h> but implements in winpthreads, and scriptc's win32
|
|
223
|
+
// link never adds it. Without this the link dies with
|
|
224
|
+
// "undefined symbol: clock_gettime" — reproducible with a plain
|
|
225
|
+
// `scriptc build hello.ts` on Windows, no FFI involved.
|
|
226
|
+
return {
|
|
227
|
+
ffi_format: 4,
|
|
228
|
+
functions,
|
|
229
|
+
libraries: [shimLib],
|
|
230
|
+
system_libraries: [
|
|
231
|
+
"c++", "pthread",
|
|
232
|
+
"ole32", "oleaut32", "shlwapi", "shell32", "user32", "version", "gdi32",
|
|
233
|
+
],
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
106
237
|
if (process.platform === "darwin") {
|
|
107
238
|
// scriptc has no -framework support, but `libraries` entries are passed to
|
|
108
239
|
// the link as plain input files and ld64 accepts .tbd stubs.
|
|
109
240
|
const sdk = capture(["xcrun", "--sdk", "macosx", "--show-sdk-path"]);
|
|
110
241
|
return {
|
|
111
|
-
ffi_format:
|
|
242
|
+
ffi_format: 4,
|
|
112
243
|
functions,
|
|
113
244
|
libraries: [
|
|
114
245
|
shimLib,
|
|
@@ -119,7 +250,7 @@ function ffiManifest(shimLib) {
|
|
|
119
250
|
};
|
|
120
251
|
}
|
|
121
252
|
return {
|
|
122
|
-
ffi_format:
|
|
253
|
+
ffi_format: 4,
|
|
123
254
|
functions,
|
|
124
255
|
libraries: [shimLib],
|
|
125
256
|
system_libraries: [
|
|
@@ -180,11 +311,15 @@ function build(root) {
|
|
|
180
311
|
writeFileSync(join(buildDir, "janela.ffi.json"), JSON.stringify(ffiManifest(shimLib), null, 2) + "\n");
|
|
181
312
|
|
|
182
313
|
console.log("janela: compiling TypeScript to a native binary");
|
|
183
|
-
|
|
314
|
+
// An explicit --out is used verbatim, so the PE suffix is ours to add.
|
|
315
|
+
const bin = join(outDir, process.platform === "win32" ? `${conf.name}.exe` : conf.name);
|
|
316
|
+
// No SCRIPTC_CC/SCRIPTC_TARGET on Windows: scriptc's default driver is
|
|
317
|
+
// plain `clang`, which is exactly the MinGW-targeting clang checked above.
|
|
184
318
|
run(["node", scriptcBin(), "build", "entry.ts", "--ffi", "janela.ffi.json", "-o", bin], { cwd: buildDir });
|
|
185
319
|
|
|
186
320
|
// Symbol/debug metadata is ~16% of the binary and apps don't need it.
|
|
187
|
-
// (On arm64 macOS, strip re-signs ad-hoc automatically.
|
|
321
|
+
// (On arm64 macOS, strip re-signs ad-hoc automatically. MinGW keeps DWARF
|
|
322
|
+
// inside the .exe rather than a side-by-side .pdb, so Windows benefits too.)
|
|
188
323
|
run(["strip", bin]);
|
|
189
324
|
|
|
190
325
|
if (process.platform === "darwin") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "janela",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Desktop apps in pure TypeScript, compiled to native. No Rust, no Node, no Electron.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"jn": "bin/janela.mjs"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"scriptc": "0.0.
|
|
11
|
+
"scriptc": "0.0.35"
|
|
12
12
|
},
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"author": "Mateus Lage",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"scriptc"
|
|
31
31
|
],
|
|
32
32
|
"engines": {
|
|
33
|
-
"node": ">=
|
|
33
|
+
"node": ">=24"
|
|
34
34
|
},
|
|
35
35
|
"files": [
|
|
36
36
|
"bin/",
|