mirror of
https://github.com/mileswolfallen2/gelectron.git
synced 2026-09-08 04:43:14 +00:00
236 lines
8.7 KiB
Markdown
236 lines
8.7 KiB
Markdown
# Contributing to Gelectron
|
|
|
|
Thanks for your interest in Gelectron! This guide covers how the project is put
|
|
together and how to contribute changes that build, pass tests, and don't regress
|
|
the app you might be targeting (e.g. `vanilla-sh`, the reference consumer).
|
|
|
|
## What Gelectron is
|
|
|
|
Gelectron is a drop-in replacement for Electron that uses the OS-native web view
|
|
(WKWebView on macOS, WebView2 on Windows, WebKitGTK on Linux) instead of
|
|
bundling Chromium. It has two halves that talk to each other:
|
|
|
|
- **`gelectron-app`** — a standalone Rust binary built on **tao** (windowing) and
|
|
**wry** (WebView). It owns windows, the native menu bar, dialogs, clipboard,
|
|
and the dock/taskbar icon.
|
|
- **`src/electron/`** — a JavaScript compatibility layer that implements the
|
|
Electron API surface (`app`, `BrowserWindow`, `Menu`, `ipcMain`, …). Your
|
|
Electron app's `main.js` imports `electron`, which resolves to this layer.
|
|
|
|
The two halves communicate over **JSON-line IPC**. The Rust side spawns Node.js
|
|
as a child process (or runs the JS layer inside the WebView in `--no-node` mode)
|
|
and both sides exchange messages like `create-window`, `load-url`,
|
|
`ipc-message`, and `set-application-menu`.
|
|
|
|
Read `README.md` for the full architecture diagram and supported-API table.
|
|
|
|
## Repository layout
|
|
|
|
```
|
|
gelectron/
|
|
├── Cargo.toml # Rust workspace (patches wry → vendor/wry)
|
|
├── crates/
|
|
│ ├── gelectron-app/ # ★ The main native binary (tao + wry)
|
|
│ │ └── src/main.rs # event loop, IPC, menus, icons, dialogs
|
|
│ └── gelectron-core/ # Legacy N-API addon (not the primary path)
|
|
├── src/electron/ # JS Electron compatibility layer
|
|
├── vendor/wry/ # Vendored wry (patched via [patch.crates-io])
|
|
├── packager/bin/gelectron-packager.js # Bundles apps into distributables
|
|
├── cli/gelectron.js # Pure-Node.js fallback runner
|
|
├── demo/ # Minimal reference app
|
|
├── benchmark/ # Memory/perf comparison vs Electron
|
|
└── scripts/install.sh # Installs binary + compat layer
|
|
```
|
|
|
|
Most day-to-day work happens in **`crates/gelectron-app/src/main.rs`** (native
|
|
behavior) and **`src/electron/`** (JS API behavior). `main.rs` is ~1900 lines;
|
|
it's the file where menu, icon, window, and IPC logic lives.
|
|
|
|
## Prerequisites
|
|
|
|
- **Rust 1.75+** — `rustup.rs`
|
|
- **Node.js 18+** and npm
|
|
- macOS builds also want Xcode command-line tools (`xcode-select --install`)
|
|
|
|
## First-time setup
|
|
|
|
```bash
|
|
git clone https://github.com/mileswolfallen2/gelectron.git
|
|
cd gelectron
|
|
npm install
|
|
```
|
|
|
|
## Development loop
|
|
|
|
The most common workflow is to build the binary, then run an app against it:
|
|
|
|
```bash
|
|
# Build the native binary
|
|
cargo build --release -p gelectron
|
|
|
|
# Run the bundled demo
|
|
cargo run --release -p gelectron -- demo/
|
|
|
|
# Run a real Electron app against your build
|
|
cargo run --release -p gelectron -- /path/to/your-app
|
|
```
|
|
|
|
If you're iterating on the JS compat layer only, `scripts/install.sh` installs
|
|
both the binary and `src/electron/` so `gelectron <app>` works from anywhere:
|
|
|
|
```bash
|
|
./scripts/install.sh
|
|
```
|
|
|
|
### Environment variables
|
|
|
|
| Variable | Use |
|
|
|---|---|
|
|
| `RUST_LOG=info` | Rust-side logging (`gelectron` crate) |
|
|
| `GELECTRON_LOG=1` | Verbose JS-side logging |
|
|
| `GELECTRON_DEV=1` | Development mode |
|
|
| `VITE_DEV_SERVER_URL=<url>` | Point the app at a Vite dev server |
|
|
|
|
## Testing
|
|
|
|
Run the Rust unit tests:
|
|
|
|
```bash
|
|
cargo test -p gelectron
|
|
```
|
|
|
|
The suite covers image/icon decoders (BMP-in-ICO, PNG-in-ICO, PNG round-trip).
|
|
Keep these green — they're fast and catch byte-layout regressions in icon
|
|
handling.
|
|
|
|
There is no end-to-end test harness yet. Manual verification looks like:
|
|
|
|
```bash
|
|
# 1. Build
|
|
cargo build --release -p gelectron
|
|
|
|
# 2. Package the reference app and launch it
|
|
# (from the vanilla-sh checkout)
|
|
npm run build
|
|
open dist/VanillaChat.app
|
|
|
|
# 3. Sanity-check the pieces you touched, e.g.:
|
|
# - Menu bar: Apple, AppName, File, Edit, View, Window, Help
|
|
# - Edit menu: Undo/Redo/Cut/Copy/Paste/Select All with Cmd key equivalents
|
|
# - Dock icon renders without distortion
|
|
# - Cmd+C / Cmd+V work inside the webview
|
|
```
|
|
|
|
To verify menu behavior programmatically (macOS), you can query System Events:
|
|
|
|
```bash
|
|
osascript -e 'tell application "System Events" to tell process "<AppName>" \
|
|
to get name of every menu bar item of menu bar 1'
|
|
```
|
|
|
|
## Rules of the road
|
|
|
|
These rules exist because each one corresponds to a bug that has actually
|
|
happened. Please respect them.
|
|
|
|
### 1. Native menus must stay alive after `init_for_nsapp()`
|
|
|
|
On macOS, AppKit stores **raw pointers** into the `muda` menu structures as
|
|
instance variables on the native `NSMenuItem`s. If you drop the `muda::Menu`
|
|
after `init_for_nsapp()`, clicking any menu item dereferences freed memory →
|
|
random, non-deterministic crashes.
|
|
|
|
- Keep the built menu in `AppState.app_menu` for the app's lifetime.
|
|
- The default menu (`install_default_app_menu`) must be stored too, not just the
|
|
one from `SetApplicationMenu`.
|
|
|
|
### 2. Menu submenus come in two shapes
|
|
|
|
The compat layer serializes a submenu either as a bare array **or** as an object
|
|
with an `items` array (`{"items": [...]}`). Always normalize with
|
|
`submenu_items()` before iterating, and treat an item as a submenu when
|
|
`type == "submenu"` **or** a `submenu` field is present. The vanilla-sh menu
|
|
template relies on the second form, and the app's own menu builder relies on the
|
|
first.
|
|
|
|
### 3. Icons: normalize 16-bit PNGs
|
|
|
|
`logo.png`-style source icons are often 16-bit RGBA. Two code paths must handle
|
|
this:
|
|
|
|
- Rust: `decode_png_icon` must set
|
|
`Transformations::EXPAND | STRIP_16` on the decoder so downstream code can
|
|
assume 8-bit RGBA bytes. Treating a 16-bit buffer as 8-bit mangles the icon.
|
|
- Packager: `generateIcns` downconverts the source to 8-bit (via `sips` or PIL)
|
|
before building the `.icns`, since `sips` preserves 16-bit at 1024px and macOS
|
|
renders that poorly.
|
|
|
|
### 4. macOS APIs require the main thread
|
|
|
|
Menu construction, `setApplicationIconImage`, and window operations all need the
|
|
main/event-loop thread. Don't call them from background threads.
|
|
|
|
### 5. Don't add comments unless they earn their place
|
|
|
|
The codebase is deliberately comment-light. A comment should explain *why*
|
|
something is non-obvious (e.g. "AppKit stores raw pointers here") rather than
|
|
restating what the code does.
|
|
|
|
### 6. Keep the wry patch minimal
|
|
|
|
`wry` is vendored and patched via `[patch.crates-io]` in `Cargo.toml`. Only
|
|
change `vendor/wry/` when you genuinely need a WebView-level behavior change
|
|
(e.g. media permissions). Prefer fixing things in `gelectron-app` or the JS
|
|
layer.
|
|
|
|
## Code style
|
|
|
|
- **Rust:** follow `rustfmt` defaults. Run `cargo fmt` before committing.
|
|
- **JS:** CommonJS, 2-space indent, `'use strict'` in CLI scripts.
|
|
- Match the surrounding code's conventions — this project has no linter config,
|
|
so consistency is on you.
|
|
|
|
## Packaging changes
|
|
|
|
If your change touches distribution, rebuild and package a real app to verify:
|
|
|
|
```bash
|
|
# From packager/ (or via npm link)
|
|
gelectron-packager --dir ./my-app --name MyApp
|
|
|
|
# macOS: verify the bundle's icon and signature
|
|
file dist/MyApp.app/Contents/Resources/AppIcon.icns
|
|
codesign --verify --deep --strict dist/MyApp.app
|
|
```
|
|
|
|
The packager: copies `node_modules` into `Resources/app/`, generates `AppIcon.icns`
|
|
from `icon.png`, writes `Info.plist` with the icon + mic/camera usage strings, and
|
|
re-signs the bundle with the entitlements. If any of that seems off, check
|
|
`packager/bin/gelectron-packager.js` and `vanilla-sh/scripts/build-desktop.sh`.
|
|
|
|
## Cross-platform notes
|
|
|
|
- **macOS** is the primary dev platform and the most battle-tested path.
|
|
- **Windows** builds target GNU (`x86_64-pc-windows-gnu`). MSVC cross-compiles
|
|
from macOS are blocked by a missing `link.exe` — use the GNU toolchain.
|
|
- **Linux** needs WebKitGTK dev packages; cross-compiling from macOS is
|
|
unconfigured.
|
|
- Platform-specific behavior should be `#[cfg(target_os = "...")]` in Rust and
|
|
`process.platform` checks in JS (the compat layer already exposes
|
|
`isMac`/`isWin` style helpers in places).
|
|
|
|
## Opening a PR
|
|
|
|
1. Fork and create a feature branch.
|
|
2. Make your changes, keeping the "Rules of the road" in mind.
|
|
3. Run `cargo fmt`, `cargo build --release -p gelectron`, and `cargo test -p gelectron`.
|
|
4. Verify against a real app (`cargo run --release -p gelectron -- /path/to/app`).
|
|
5. Open the PR with a short description of what changed and why, and note how you
|
|
verified it.
|
|
|
|
## Getting help
|
|
|
|
- Open an issue at https://github.com/mileswolfallen2/gelectron/issues
|
|
- The `vanilla-sh` checkout (`/Users/milesallen/Documents/GitHub/vanilla-sh`)
|
|
is the reference consumer — changes that regress it are treated as bugs.
|