https://github.com/ronarthas/appjet
Modern Electron with web dev DX
https://github.com/ronarthas/appjet
appjet bun desktop-app electron tauri vue
Last synced: 3 months ago
JSON representation
Modern Electron with web dev DX
- Host: GitHub
- URL: https://github.com/ronarthas/appjet
- Owner: ronarthas
- Created: 2025-07-19T11:24:30.000Z (11 months ago)
- Default Branch: main
- Last Pushed: 2025-07-20T19:00:23.000Z (11 months ago)
- Last Synced: 2025-07-20T21:05:41.946Z (11 months ago)
- Topics: appjet, bun, desktop-app, electron, tauri, vue
- Language: TypeScript
- Homepage:
- Size: 22.5 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Appjet âïļ
> â ïļ **This project is in early development** - Only basic template is currently available.
**Fast, simple desktop apps with modern web technologies**
Appjet is a lightweight alternative to Electron, built for speed and simplicity. Create desktop applications using Vue.js, HTML, CSS, and JavaScript - powered by Bun and native webviews.
## ð Prerequisites
### Required
- **[Bun](https://bun.sh)** (not Node.js compatible)
- **System webview libraries**:
#### Windows
- Windows 11: â
Built-in (no additional setup)
- Windows 10: May require WebView2 runtime
#### macOS
- Built-in WebKit (no additional setup required)
#### Linux
Install GTK 4 and WebkitGTK 6:
```bash
# Debian/Ubuntu
sudo apt install libgtk-4-1 libwebkitgtk-6.0-4
# Arch Linux
sudo pacman -S gtk4 webkitgtk-6.0
# Fedora
sudo dnf install gtk4 webkitgtk6.0
```
## ⥠Why Appjet?
- **ð Lightning Fast** - Built on Bun, compiles to native executables
- **ðŠķ Lightweight** - No bundled Chromium, uses system webview
- **ðŊ Simple** - Minimal API, focus on your app logic
- **ð§ Modern Stack** - Vue.js, Vite, TypeScript out of the box
- **ðĶ Single Binary** - Compile to standalone executables
- **ðĨ Hot Reload** - Instant updates during development
- **ð Auto Binding** - Automatic function binding between backend and frontend
## ð Quick Start
Create a new Appjet app:
```bash
bun create appjet my-app
cd my-app
```
### Setup Frontend (Vue.js example)
```bash
cd frontend
bun create vue@latest .
bun install
# Start frontend dev server
bun dev
```
### Setup Backend
```bash
cd ../backend
bun install
# Start backend (in another terminal)
bun dev
```
## âïļ Configuration
### Window & Webview Settings (`appjet.config.ts`)
```typescript
import type { AppjetConfig } from "appjet";
export const config: AppjetConfig = {
window: {
debug: true, // Enable dev tools
height: 800, // Window height
width: 1200, // Window width
resizable: true, // Allow window resize
title: "My Appjet App", // Window title
},
frontend: {
distPath: "../frontend/dist", // Production build path
entryPointFile: "index.html", // Entry HTML file
viteServer: "http://localhost:5173", // Dev server URL
},
};
```
### Build Configuration (`appjet.build.ts`)
```typescript
import { buildAppjetApp, type BuildConfig } from "appjet";
export const build: BuildConfig = {
appName: "my-app", // Executable name
entrypoint: "./appjet.ts", // Backend entry point
outputDir: "./dist", // Build output directory
frontendDir: "../frontend", // Frontend source directory
targets: ["bun-linux-x64"], // Target platforms
};
await buildAppjetApp(build);
```
## ð Backend â Frontend Communication
Appjet's **magic**: just register a function in your backend, and it's automatically available in your frontend!
### Backend (`backend/api.ts`)
```typescript
import { registerBinding } from "appjet";
import { readdir, writeFile } from "fs/promises";
// Simple function
const greetUser = (name: string) => {
return `Hello ${name}! Welcome to Appjet âïļ`;
};
// Async function with file system access
const listFiles = async (directory: string) => {
try {
const files = await readdir(directory);
return { success: true, files };
} catch (error) {
return { success: false, error: error.message };
}
};
// Complex function with multiple operations
const saveUserData = async (userData: { name: string; email: string }) => {
await writeFile('user.json', JSON.stringify(userData, null, 2));
console.log("User data saved!");
return { saved: true, timestamp: Date.now() };
};
// Register functions (they become available in frontend automatically!)
registerBinding("greetUser", greetUser);
registerBinding("listFiles", listFiles);
registerBinding("saveUserData", saveUserData);
// Or register multiple at once
registerBinding({
getCurrentTime: () => new Date().toISOString(),
getSystemInfo: () => ({ platform: process.platform, arch: process.arch })
});
```
### Frontend (Vue.js, React, or vanilla JS)
```typescript
// Cast window to access bound functions
const w = window as any;
// Call the backend functions directly
const greeting = await w.greetUser("Alice");
console.log(greeting); // "Hello Alice! Welcome to Appjet âïļ"
// File system operations from frontend
const result = await w.listFiles("/home/user/documents");
if (result.success) {
console.log("Files:", result.files);
}
// Save data
await w.saveUserData({ name: "Bob", email: "bob@example.com" });
// Get system info
const info = await w.getSystemInfo();
console.log(info); // { platform: "linux", arch: "x64" }
```
**ðĄ Pro tip:** For better TypeScript support, you can create a types file:
```typescript
// types/appjet.d.ts
declare global {
interface Window {
greetUser: (name: string) => Promise;
listFiles: (directory: string) => Promise<{success: boolean, files?: string[], error?: string}>;
saveUserData: (userData: {name: string, email: string}) => Promise<{saved: boolean, timestamp: number}>;
getSystemInfo: () => Promise<{platform: string, arch: string}>;
}
}
```
Then use with full type safety:
```typescript
const greeting = await window.greetUser("Alice"); // â
Fully typed!
```
**âĻ That's it!** No IPC setup, no manual bindings, no complex messaging. Just write functions in your backend and call them from your frontend like magic! ðŠ
## ð ïļ Build for Production
### For Vue.js Projects
1. **Update your router** (`src/router/index.ts`) to use memory history:
```typescript
import { createRouter, createMemoryHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createMemoryHistory(), // Use memory history for embedded apps
routes: [
{
path: '/',
name: 'home',
component: HomeView,
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue'),
},
],
})
export default router
```
2. **Update your `vite.config.ts`** for single-file output:
```typescript
export default defineConfig({
// ... other config
build: {
assetsInlineLimit: 999999999, // Inline all assets
cssCodeSplit: false, // Single CSS file
rollupOptions: {
output: {
manualChunks: undefined,
inlineDynamicImports: true,
entryFileNames: 'assets/index.js',
chunkFileNames: 'assets/index.js',
assetFileNames: 'assets/index.[ext]',
},
},
},
});
```
### Build Commands
```bash
# Build everything
cd backend
bun run build
# Or build executable directly
bun run appjet.build.ts
```
## ð Available Targets
- `bun-linux-x64` - Linux 64-bit
- `bun-windows-x64` - Windows 64-bit
- `bun-darwin-x64` - macOS Intel
- `bun-darwin-arm64` - macOS Apple Silicon
## ðĪ Contributing
Contributions welcome! This project is in early development.
## ð License
MIT ÂĐ Bastien Etienne