electrobun 0.0.2 → 0.0.4

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.
Files changed (76) hide show
  1. package/.colab.json +1 -0
  2. package/README.md +14 -12
  3. package/bun.lockb +0 -0
  4. package/dist/bsdiff +0 -0
  5. package/dist/bspatch +0 -0
  6. package/dist/webview +0 -0
  7. package/docs-old/architecture.md +46 -0
  8. package/documentation/README.md +41 -0
  9. package/documentation/babel.config.js +3 -0
  10. package/documentation/blog/2024-08-20-electrobun.md +22 -0
  11. package/documentation/blog/authors.yml +8 -0
  12. package/documentation/blog/tags.yml +0 -0
  13. package/documentation/docs/apis/Application Icons.md +9 -0
  14. package/documentation/docs/apis/Bundled Assets.md +95 -0
  15. package/documentation/docs/apis/browser/DraggableRegions.md +36 -0
  16. package/documentation/docs/apis/browser/Electrobun Webview Tag.md +200 -0
  17. package/documentation/docs/apis/browser/Electroview Class.md +158 -0
  18. package/documentation/docs/apis/browser/GlobalProperties.md +11 -0
  19. package/documentation/docs/apis/browser/index.md +25 -0
  20. package/documentation/docs/apis/bun/ApplicationMenu.md +141 -0
  21. package/documentation/docs/apis/bun/BrowserView.md +513 -0
  22. package/documentation/docs/apis/bun/BrowserWindow.md +423 -0
  23. package/documentation/docs/apis/bun/ContextMenu.md +50 -0
  24. package/documentation/docs/apis/bun/Events.md +50 -0
  25. package/documentation/docs/apis/bun/PATHS.md +17 -0
  26. package/documentation/docs/apis/bun/Tray.md +115 -0
  27. package/documentation/docs/apis/bun/Updater.md +74 -0
  28. package/documentation/docs/apis/bun/Utils.md +51 -0
  29. package/documentation/docs/apis/bun/index.md +26 -0
  30. package/documentation/docs/apis/cli/Electrobun.config.md +97 -0
  31. package/documentation/docs/apis/cli/cli args.md +76 -0
  32. package/documentation/docs/guides/Architecture/Events.md +19 -0
  33. package/documentation/docs/guides/Architecture/IPC and Isolation.md +20 -0
  34. package/documentation/docs/guides/Architecture/Overview.md +140 -0
  35. package/documentation/docs/guides/Architecture/Updates.md +7 -0
  36. package/documentation/docs/guides/Architecture/Webview Tag.md +5 -0
  37. package/documentation/docs/guides/Compatability.md +8 -0
  38. package/documentation/docs/guides/Getting Started/Creating UI.md +147 -0
  39. package/documentation/docs/guides/Getting Started/Distributing.md +116 -0
  40. package/documentation/docs/guides/Getting Started/Getting Started.md +7 -0
  41. package/documentation/docs/guides/Getting Started/Hello World.md +93 -0
  42. package/documentation/docs/guides/Getting Started/What is Electrobun.md +39 -0
  43. package/documentation/docs/guides/Guides/Build UI with React +0 -0
  44. package/documentation/docs/guides/Guides/Build UI with Solidjs +0 -0
  45. package/documentation/docs/guides/Guides/Build a Web Browser +0 -0
  46. package/documentation/docs/guides/Guides/Bun <-> Browser RPC +0 -0
  47. package/documentation/docs/guides/Guides/Using Tailwind +0 -0
  48. package/documentation/docusaurus.config.ts +153 -0
  49. package/documentation/package-lock.json +14530 -0
  50. package/documentation/package.json +47 -0
  51. package/documentation/sidebars.ts +32 -0
  52. package/documentation/src/components/HomepageFeatures/index.tsx +70 -0
  53. package/documentation/src/components/HomepageFeatures/styles.module.css +11 -0
  54. package/documentation/src/css/custom.css +30 -0
  55. package/documentation/src/pages/index.module.css +23 -0
  56. package/documentation/src/pages/index.tsx +137 -0
  57. package/documentation/static/.nojekyll +0 -0
  58. package/documentation/static/img/electrobun-logo-256.png +0 -0
  59. package/documentation/static/img/electrobun-logo-32.png +0 -0
  60. package/documentation/tsconfig.json +7 -0
  61. package/package.json +11 -6
  62. package/src/browser/index.ts +7 -2
  63. package/src/browser/webviewtag.ts +149 -17
  64. package/src/bun/core/BrowserView.ts +19 -2
  65. package/src/bun/proc/zig.ts +1 -0
  66. package/src/cli/build/electrobun +0 -0
  67. package/src/cli/index.ts +3 -1
  68. package/src/extractor/zig-out/bin/extractor +0 -0
  69. package/src/launcher/zig-out/bin/launcher +0 -0
  70. package/docs/architecture.md +0 -84
  71. /package/{docs → docs-old}/api/bun-api.md +0 -0
  72. /package/{docs → docs-old}/api/view-api.md +0 -0
  73. /package/{docs → docs-old}/electrobun-config.md +0 -0
  74. /package/{docs → docs-old}/getting-started.md +0 -0
  75. /package/{docs → docs-old}/node_modules/.cache/webpack/client-development-en/0.pack +0 -0
  76. /package/{docs → docs-old}/node_modules/.cache/webpack/client-development-en/index.pack +0 -0
@@ -4,6 +4,8 @@ type WebviewEventTypes =
4
4
  | "did-commit-navigation"
5
5
  | "dom-ready";
6
6
 
7
+ type Rect = { x: number; y: number; width: number; height: number };
8
+
7
9
  const ConfigureWebviewTags = (
8
10
  enableWebviewTags: boolean,
9
11
  zigRpc: (params: any) => any,
@@ -24,6 +26,10 @@ const ConfigureWebviewTags = (
24
26
  zigRpc: any;
25
27
  syncRpc: any;
26
28
 
29
+ // querySelectors for elements that you want to appear
30
+ // in front of the webview.
31
+ maskSelectors: Set<string> = new Set();
32
+
27
33
  // observers
28
34
  resizeObserver?: ResizeObserver;
29
35
  // intersectionObserver?: IntersectionObserver;
@@ -39,12 +45,16 @@ const ConfigureWebviewTags = (
39
45
  height: 0,
40
46
  };
41
47
 
48
+ lastMasksJSON: string = "";
49
+ lastMasks: Rect[] = [];
50
+
42
51
  transparent: boolean = false;
43
52
  passthroughEnabled: boolean = false;
44
53
  hidden: boolean = false;
45
54
  delegateMode: boolean = false;
46
55
  hiddenMirrorMode: boolean = false;
47
56
  wasZeroRect: boolean = false;
57
+ isMirroring: boolean = false;
48
58
 
49
59
  partition: string | null = null;
50
60
 
@@ -59,6 +69,16 @@ const ConfigureWebviewTags = (
59
69
  });
60
70
  }
61
71
 
72
+ addMaskSelector(selector: string) {
73
+ this.maskSelectors.add(selector);
74
+ this.syncDimensions();
75
+ }
76
+
77
+ removeMaskSelector(selector: string) {
78
+ this.maskSelectors.delete(selector);
79
+ this.syncDimensions();
80
+ }
81
+
62
82
  initWebview() {
63
83
  const rect = this.getBoundingClientRect();
64
84
  this.lastRect = rect;
@@ -206,6 +226,10 @@ const ConfigureWebviewTags = (
206
226
  // know that they're chaning something in order to eliminate the lag that the
207
227
  // catch all loop will catch
208
228
  syncDimensions(force: boolean = false) {
229
+ if (!force && this.hidden) {
230
+ return;
231
+ }
232
+
209
233
  const rect = this.getBoundingClientRect();
210
234
  const { x, y, width, height } =
211
235
  this.adjustDimensionsForHiddenMirrorMode(rect);
@@ -220,19 +244,45 @@ const ConfigureWebviewTags = (
220
244
  return;
221
245
  }
222
246
 
247
+ const masks: Rect[] = [];
248
+ this.maskSelectors.forEach((selector) => {
249
+ const els = document.querySelectorAll(selector);
250
+
251
+ for (let i = 0; i < els.length; i++) {
252
+ const el = els[i];
253
+
254
+ if (el) {
255
+ const maskRect = el.getBoundingClientRect();
256
+
257
+ masks.push({
258
+ // reposition the bounding rect to be relative to the webview rect
259
+ // so objc can apply the mask correctly and handle the actual overlap
260
+ x: maskRect.x - x,
261
+ y: maskRect.y - y,
262
+ width: maskRect.width,
263
+ height: maskRect.height,
264
+ });
265
+ }
266
+ }
267
+ });
268
+
269
+ // store jsonStringified last masks value to compare
270
+ const masksJson = masks.length ? JSON.stringify(masks) : "";
271
+
223
272
  if (
224
273
  force ||
225
274
  lastRect.x !== x ||
226
275
  lastRect.y !== y ||
227
276
  lastRect.width !== width ||
228
- lastRect.height !== height
277
+ lastRect.height !== height ||
278
+ this.lastMasksJSON !== masksJson
229
279
  ) {
230
- // if we're not already in an accelerated loop then accelerate it
231
- if (!this.positionCheckLoopReset) {
232
- this.setPositionCheckLoop(true);
233
- }
280
+ // let it know we're still accelerating
281
+ this.setPositionCheckLoop(true);
234
282
 
235
283
  this.lastRect = rect;
284
+ this.lastMasks = masks;
285
+ this.lastMasksJSON = masksJson;
236
286
 
237
287
  this.zigRpc.send.webviewTagResize({
238
288
  id: this.webviewId,
@@ -242,6 +292,7 @@ const ConfigureWebviewTags = (
242
292
  x: x,
243
293
  y: y,
244
294
  },
295
+ masks: masksJson,
245
296
  });
246
297
  }
247
298
 
@@ -265,13 +316,12 @@ const ConfigureWebviewTags = (
265
316
  this.positionCheckLoopReset = undefined;
266
317
  }
267
318
 
268
- const delay = accelerate ? 100 : 400;
319
+ const delay = accelerate ? 0 : 300;
269
320
 
270
321
  if (accelerate) {
271
- clearTimeout(this.positionCheckLoopReset);
272
322
  this.positionCheckLoopReset = setTimeout(() => {
273
323
  this.setPositionCheckLoop(false);
274
- }, 600);
324
+ }, 2000);
275
325
  }
276
326
  // Note: Since there's not catch all way to listen for x/y changes
277
327
  // we have a 400ms interval to check
@@ -288,6 +338,59 @@ const ConfigureWebviewTags = (
288
338
  this.positionCheckLoop = setInterval(() => this.syncDimensions(), delay);
289
339
  }
290
340
 
341
+ // The global document mousemove will fire even when the mouse is over
342
+ // an OOPIF that's layered above this host webview. The two edge cases we
343
+ // solve for are:
344
+ // 1. dragging an element on the host over or dropping on the webview anchor
345
+ // 2. clicking on an element that's "layered over" the OOPIF visually but really uses a
346
+ // mask to make a section transparent. We want the underlying overlay UI to be
347
+ // interactive not the OOPIF.
348
+ //
349
+ // Solution: Have mirroing on by default.
350
+ // 1. mouse move events don't fire during drag. So the OOPIF remains non-interactive
351
+ // and effectively passes through to the host's anchor element underneath letting you
352
+ // react to drag events on the host as needed.
353
+ // 2. Detect when the mouse is moving over the anchor and turn mirroring off to make
354
+ // it interactive. Unless the mouse is over a masked area in which case we want to
355
+ // keep it non-interactive and pass through to the host "overylay UI".
356
+ handleDocumentMouseMove(e: MouseEvent) {
357
+ if (this.hidden) {
358
+ return;
359
+ }
360
+
361
+ const isInBounds =
362
+ e.clientX >= this.lastRect.x &&
363
+ e.clientX <= this.lastRect.x + this.lastRect.width &&
364
+ e.clientY >= this.lastRect.y &&
365
+ e.clientY <= this.lastRect.y + this.lastRect.height;
366
+
367
+ if (isInBounds) {
368
+ const isInMaskBounds = this.lastMasks.find((mask) => {
369
+ // we send relative x/y to objc but here we need the clientX/Y
370
+ // to compare against. consider doing the opposite or storing both.
371
+ const clientX = this.lastRect.x + mask.x;
372
+ const clientY = this.lastRect.y + mask.y;
373
+ const isInMaskBounds =
374
+ e.clientX >= clientX &&
375
+ e.clientX <= clientX + mask.width &&
376
+ e.clientY <= clientY + mask.height &&
377
+ e.clientY >= clientY;
378
+
379
+ return isInMaskBounds;
380
+ });
381
+ if (isInMaskBounds) {
382
+ this.startMirroring();
383
+ } else {
384
+ this.stopMirroring();
385
+ }
386
+ } else {
387
+ this.startMirroring();
388
+ }
389
+ }
390
+
391
+ boundhandleDocumentMouseMove = (e: MouseEvent) =>
392
+ this.handleDocumentMouseMove(e);
393
+
291
394
  connectedCallback() {
292
395
  this.setPositionCheckLoop();
293
396
 
@@ -300,8 +403,14 @@ const ConfigureWebviewTags = (
300
403
  // we still need to send it to objc to calculate from its bottom left position
301
404
  // otherwise it'll move around unexpectedly.
302
405
  window.addEventListener("resize", this.boundForceSyncDimensions);
303
-
304
406
  window.addEventListener("scroll", this.boundSyncDimensions);
407
+ // Note: mousemove won't fire during a drag so we get that behaviour
408
+ // for free without doing calculations.
409
+ document.addEventListener(
410
+ "mousemove",
411
+ this.boundhandleDocumentMouseMove,
412
+ true
413
+ );
305
414
 
306
415
  // todo: For chromium webviews (windows native or chromium bundled)
307
416
  // should be able to use performanceObservers on layout-shift to
@@ -318,6 +427,10 @@ const ConfigureWebviewTags = (
318
427
  // this.mutationObserver?.disconnect();
319
428
  window.removeEventListener("resize", this.boundForceSyncDimensions);
320
429
  window.removeEventListener("scroll", this.boundSyncDimensions);
430
+ document.removeEventListener(
431
+ "mousemove",
432
+ this.boundhandleDocumentMouseMove
433
+ );
321
434
  this.zigRpc.send.webviewTagRemove({ id: this.webviewId });
322
435
  }
323
436
 
@@ -407,9 +520,8 @@ const ConfigureWebviewTags = (
407
520
  DEFAULT_FRAME_RATE = Math.round(1000 / 30); // 30fps
408
521
  streamScreenInterval?: Timer;
409
522
 
410
- // todo: add a flag to disable automirroring since it's cpu intensive and
411
- // developers may want to disable it in specific cases
412
- startMirroring(frameRate: number = this.DEFAULT_FRAME_RATE) {
523
+ // NOTE: This is very cpu intensive, Prefer startMirroring where possible
524
+ startMirroringToDom(frameRate: number = this.DEFAULT_FRAME_RATE) {
413
525
  if (this.streamScreenInterval) {
414
526
  clearInterval(this.streamScreenInterval);
415
527
  }
@@ -419,13 +531,33 @@ const ConfigureWebviewTags = (
419
531
  }, frameRate);
420
532
  }
421
533
 
422
- stopMirroring() {
534
+ stopMirroringToDom() {
423
535
  if (this.streamScreenInterval) {
424
536
  clearInterval(this.streamScreenInterval);
425
537
  this.streamScreenInterval = undefined;
426
538
  }
427
539
  }
428
540
 
541
+ startMirroring() {
542
+ if (this.isMirroring === false) {
543
+ this.isMirroring = true;
544
+ this.zigRpc.send.webviewTagToggleMirroring({
545
+ id: this.webviewId,
546
+ enable: true,
547
+ });
548
+ }
549
+ }
550
+
551
+ stopMirroring() {
552
+ if (this.isMirroring === true) {
553
+ this.isMirroring = false;
554
+ this.zigRpc.send.webviewTagToggleMirroring({
555
+ id: this.webviewId,
556
+ enable: false,
557
+ });
558
+ }
559
+ }
560
+
429
561
  clearScreenImage() {
430
562
  this.style.backgroundImage = "";
431
563
  }
@@ -503,11 +635,11 @@ const ConfigureWebviewTags = (
503
635
  this.syncScreenshot(() => {
504
636
  this.delegateMode = true;
505
637
  this.toggleTransparent(true, true);
506
- this.startMirroring();
638
+ this.startMirroringToDom();
507
639
  });
508
640
  } else {
509
641
  this.delegateMode = false;
510
- this.stopMirroring();
642
+ this.stopMirroringToDom();
511
643
  this.toggleTransparent(this.transparent);
512
644
  this.tryClearScreenImage();
513
645
  }
@@ -527,10 +659,10 @@ const ConfigureWebviewTags = (
527
659
  this.hiddenMirrorMode = true;
528
660
  this.toggleHidden(true, true);
529
661
  this.togglePassthrough(true, true);
530
- this.startMirroring();
662
+ this.startMirroringToDom();
531
663
  });
532
664
  } else {
533
- this.stopMirroring();
665
+ this.stopMirroringToDom();
534
666
  this.toggleHidden(this.hidden);
535
667
  this.togglePassthrough(this.passthroughEnabled);
536
668
  this.tryClearScreenImage();
@@ -16,6 +16,8 @@ import type { BuiltinBunToWebviewSchema } from "../../browser/builtinrpcSchema";
16
16
  const BrowserViewMap = {};
17
17
  let nextWebviewId = 1;
18
18
 
19
+ const CHUNK_SIZE = 4096; // 4KB
20
+
19
21
  type BrowserViewOptions<T = undefined> = {
20
22
  url: string | null;
21
23
  html: string | null;
@@ -30,6 +32,7 @@ type BrowserViewOptions<T = undefined> = {
30
32
  rpc: T;
31
33
  syncRpc: { [method: string]: (params: any) => any };
32
34
  hostWebviewId: number;
35
+ autoResize: boolean;
33
36
  };
34
37
 
35
38
  interface ElectrobunWebviewRPCSChema {
@@ -66,6 +69,7 @@ const internalSyncRpcHandlers = {
66
69
  partition,
67
70
  frame,
68
71
  hostWebviewId,
72
+ autoResize: false,
69
73
  });
70
74
 
71
75
  // Note: we have to give it a couple of ticks to fully create the browserview
@@ -102,6 +106,7 @@ export class BrowserView<T> {
102
106
  html: string | null = null;
103
107
  preload: string | null = null;
104
108
  partition: string | null = null;
109
+ autoResize: boolean = true;
105
110
  frame: {
106
111
  x: number;
107
112
  y: number;
@@ -133,6 +138,7 @@ export class BrowserView<T> {
133
138
  // file exists first
134
139
  this.pipePrefix = `/private/tmp/electrobun_ipc_pipe_${hash}_${randomId}_${this.id}`;
135
140
  this.hostWebviewId = options.hostWebviewId;
141
+ this.autoResize = options.autoResize === false ? false : true;
136
142
 
137
143
  this.init();
138
144
  }
@@ -156,7 +162,7 @@ export class BrowserView<T> {
156
162
  x: this.frame.x,
157
163
  y: this.frame.y,
158
164
  },
159
- // autoResize: true,
165
+ autoResize: this.autoResize,
160
166
  });
161
167
 
162
168
  this.createStreams();
@@ -213,8 +219,19 @@ export class BrowserView<T> {
213
219
  this.executeJavascript(wrappedMessage);
214
220
  }
215
221
 
222
+ // Note: the OS has a buffer limit on named pipes. If we overflow it
223
+ // it won't trigger the kevent for zig to read the pipe and we'll be stuck.
224
+ // so we have to chunk it
216
225
  executeJavascript(js: string) {
217
- this.inStream.write(js + "\n");
226
+ let offset = 0;
227
+ while (offset < js.length) {
228
+ const chunk = js.slice(offset, offset + CHUNK_SIZE);
229
+ this.inStream.write(chunk);
230
+ offset += CHUNK_SIZE;
231
+ }
232
+
233
+ // Ensure the newline is written after all chunks
234
+ this.inStream.write("\n");
218
235
  }
219
236
 
220
237
  loadURL(url: string) {
@@ -192,6 +192,7 @@ type ZigHandlers = RPCSchema<{
192
192
  width: number;
193
193
  height: number;
194
194
  };
195
+ autoResize: boolean;
195
196
  };
196
197
  response: void;
197
198
  };
Binary file
package/src/cli/index.ts CHANGED
@@ -716,7 +716,9 @@ if (commandArg === "init") {
716
716
  const cacheBuster = Math.random().toString(36).substring(7);
717
717
  const updateJsonResponse = await fetch(
718
718
  urlToPrevUpdateJson + `?${cacheBuster}`
719
- );
719
+ ).catch((err) => {
720
+ console.log("bucketURL not found: ", err);
721
+ });
720
722
 
721
723
  const urlToLatestTarball = join(
722
724
  config.release.bucketUrl,
Binary file
Binary file
@@ -1,84 +0,0 @@
1
- ## Project Structure
2
-
3
- <pre>
4
- /src - electrobun's src code
5
- /src/browser - typescript compiles to the in-webview electrobun javascript api
6
- /src/bun - typescript compiles to the main process javascript api
7
- /src/objc - c-abi wrapped objective c, compiled to a static lib
8
- /src/zig - zig native bindings, compiles to the native renderer process.
9
- /src/zig/build - where the compiled src/objc ends up so zig can see it and embed it
10
- /src/cli - a cli for building and running developer apps, it reads electrobun.config files
11
- /example - Interactive example using the library.
12
- </pre>
13
-
14
- ## Building
15
-
16
- Tldr;
17
-
18
- - clang to compile objective c wrappers for macos in src/objc (.m files) into a static library, since objc is a superset of c the wrappers have intentionally been designed with c-compatible wrappers/apis
19
- - zig is built with zig's build system. must specify zig equivalent types for objc wrappers to map memory
20
- - electrobun in-webview-api that runs in all frames of the webviews is built using bun with a browser target
21
- - the in-webview-api and objc are built into src/zig/build/ so zig can see it
22
-
23
- ## IPC
24
-
25
- Bun spawns the zig bindings as a separate process. GUI applications require the main thread of a process to run a blocking event loop, so this is separate to the bun process.
26
-
27
- There are two categories of named pipes for IPC.
28
-
29
- 1. bun <-> zig. This is used for communicating with zig. ie: creating windows and webviews, and binding native handlers that need a response from bun like will-navigate.
30
- 2. bun <-> webview. Each webview gets its own pair of named pipes for communicating with bun.
31
-
32
- ### bun <-> zig
33
-
34
- There is a primary named pipe used to send rpc-anywhere encoded json messages between bun and zig on this pipe. There is a minimal zig implementation of rpc-anywhere that also implements a promise-like api in zig (freezes the main thread while waiting and unfreezes it returning a value).
35
-
36
- In order to simulate something promise-like in zig we send the request to bun and pause the main zig thread. We have a pipe listener on another thread waiting for a reply, when it gets a response it unfreezes the main thread and returns the value back to the function that was waiting.
37
-
38
- In general (whether receiving rpc requests, responses, and messages from bun) there is a performant loop in zig listening on another thread. We use native functions to group named pipe into a kqueue and let the process subscribe to events that continue the loop so it isn't busy waiting. For any gui-related rpc whe have to pass messages to the main thread to be executed.
39
-
40
- ### bun <-> webview
41
-
42
- When creating a webview a new named pipe pair is created. zig creates a bridge between the named pipe and the webview that passes anything between the named pipe and the webview so it's never deserialized in zig. We also have code in the webview for handling the other end of the RPC anywhere bun<->webview communication. By using a named pipe for each webview we eliminate the need for the zig bridge having to double wrap or partially de/serialize json messages to route them to the right webview which makes bun <-> webview communciation significantly faster.
43
-
44
- ## Events
45
-
46
- Electrobun.events is a custom event emitter. If we look at how the 'will-navigate' event works.
47
-
48
- 1. zig sends json rpc request to decide navigation over the main bun named pipe with a url and a webviewid
49
- 2. zig pauses the main thread simulating a promise-ish while listening on a separate named pipe listener thread
50
- 3. bun parses the json rpc and sees it's a 'will navigate' request
51
- 4. bun creates a new Electrobun.events.webview.willNavigate event with the data (url and webviewid).
52
- 5. bun then emits the event globally ('will-navigate') that could be listened to with Electrobun.events.on('will-navigate')
53
- 6. bun the passes the same event to a specific event ('will-navigate-<webviewId>' ie: will-navigate-1 for the webivew with id 1)
54
- 7. you could listen to this globally via Electrobun.events.on('will-navigate-1') if you wanted to. Since ids are incremented deterministically this allows for some interesting patterns like handling navigation for the first window created
55
- 8. you can also listen on the webview with myWebview.on('will-navigate'). Webview extends a "event emitter class' that essentially provides on, off, appendEventListener, etc. but instead of being an event listener it modifies the event name to include its id, and subscribes the handler to the main event emitter. So it basically does Electrobun.events.on('will-navigate-1') for you without you having to think about the webview's id and providing other lifecycle handling for you.
56
- 9. the event object has a response getter/setter and a clearResponse method that allows any handler to respond with a value as well as a 'responseWasSet' flag. so in any handler you can both check if another handler set a response, and if you want override or modify the response.
57
- 10. the response is then sent serialized as an rpc response and sent back to zig which is listening on another thread and unfreezes the main thread returning the value
58
-
59
- Global events are always called before specific events.
60
-
61
- ## Working on Electrobun
62
-
63
- There are some npm scripts to facilitate building everything from the objc, zig, bundling webview api, transpiling the bun api and so an, as well as building the example app and executing it.
64
-
65
- The example app is meant to be an interactive example of Electrobun's functionality, it's useful when implementing new functionality in any part of Electrobun to have everything rebuilt so you can interact with it in the example app which then doubles as a demo app for developers wanting to explore what Electrobun can do.
66
-
67
- You currently need zig installed globally, and to be on an ARM mac. I dunno if you have to install xcode or xcode tools to get clang on your system. Will iron out a better dev flow in the future.
68
-
69
- For now you can simply
70
-
71
- 1. clone the repo
72
- 2. in the repo root run `bun run dev:example`
73
-
74
- If you take a look at the repo's package.json as well as example app's package.json and electrobun.config you'll get a better sense of what's happening for each step.
75
-
76
- ## How Developer apps are built
77
-
78
- > Note: in order for an application to get keyboard focus on macos you can't run it as a subprocess of the terminal which greedily steals keyboard input, so it needs to be built into an app bundle.
79
-
80
- This part is wip, but currently we create a minimal macos app bundle and execute it. There is a launcher shell script which calls bun with your typescript. It configures stdout/err to write to a named pipe log file and starts listening to it so you get the output in the terminal.
81
-
82
- You can cmd+c to stop that, but for now to quit your running app you have to close the window.
83
-
84
- A better dev flow, as well as installing bun to a global location outside the app bundle is being actively developed.
File without changes
File without changes
File without changes
File without changes