DSCode uses a hybrid architecture: Tauri frontend (web-based UI with Monaco) + Rust backend (file ops, Git, LSP management).
The project initially considered egui (pure Rust UI) but chose Tauri because:
- Monaco Editor can be used directly (same as VS Code) instead of building an editor from scratch
- Web-based UI enables pixel-perfect VS Code parity with Svelte components
- Full Node.js extension compatibility vs ~70-80% with a custom UI
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β TAURI APPLICATION β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β FRONTEND (WebView - Chromium) β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β UI Layer (TypeScript + Svelte) β β β
β β β - Activity Bar, Sidebar, Panels, StatusBar β β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β Monaco Editor (from VS Code) β β β
β β β - Same editor as VS Code β β β
β β β - All Monaco features built-in β β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββ β β
β ββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββ β
β β β
β β Tauri IPC (invoke/emit) β
β β - Async commands β
β β - Event streams β
β β β
β ββββββββββββββββββΌββββββββββββββββββββββββββββββββββββ β
β β RUST BACKEND β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β Tauri Command Handlers β β β
β β β - File operations (read, write, watch) β β β
β β β - Git operations (status, commit, diff) β β β
β β β - Configuration (settings, keybindings) β β β
β β β - Command registry β β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β Core Services β β β
β β β - rope (text buffer) β β β
β β β - tree-sitter (syntax) β β β
β β β - git2 (git integration) β β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββ β β
β ββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββ β
β β β
βββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββ
β
β nng IPC (backend-to-backend only)
β
βββββββββββββββββ΄βββββββββββββββββ¬βββββββββββββββ
β β β
βββββΌβββββββββββ ββββββββββΌββββββ βββββΌββββββββββ
β Extension β β LSP Proxy β β Remote β
β Host Process β β Process β β Agent β
β β β β β β
β ββββββββββββ β β ββββββββββββ β β File Ops β
β β Node.js β β β β rust- β β β Terminal β
β β Runtime β β β β analyzer β β β Port Fwd β
β β β β β β ts-serverβ β βββββββββββββββ
β β vscode.* β β β β ... β β
β β API β β β ββββββββββββ β
β β β β β β
β β Electron β β β JSON-RPC β β
β β Shims β β β nng Bridge β
β ββββββββββββ β ββββββββββββββββ
ββββββββββββββββ
| Aspect | Electron (VS Code) | egui (Pure Rust) | Tauri (Our Choice) |
|---|---|---|---|
| Editor | Monaco β | Build from scratch β | Monaco β |
| UI Complexity | Web (easy) β | Custom widgets (hard) β | Web (easy) β |
| Extension Compat | 100% β | 70-80% |
95%+ β |
| Bundle Size | 200MB+ β | 10MB β | 50MB β |
| Memory | 350MB β | 150MB β | 140MB β |
| Startup | 2.5s |
0.8s β | 0.6s β |
| Backend | Node.js |
Rust β | Rust β |
| Risk Level | Low β | HIGH β | Low β |
Decision: Tauri gives us the best of all worlds!
1. Tauri IPC (Frontend β Rust Backend)
// Rust side: Define commands
#[tauri::command]
async fn read_file(path: String) -> Result<String, String> {
tokio::fs::read_to_string(path)
.await
.map_err(|e| e.to_string())
}
#[tauri::command]
async fn git_status(repo_path: String) -> Result<Vec<GitChange>, String> {
let repo = Repository::open(repo_path)?;
// ... git operations
Ok(changes)
}
// Frontend side: Invoke commands
import { invoke } from '@tauri-apps/api/tauri';
const content = await invoke('read_file', { path: '/path/to/file' });
const changes = await invoke('git_status', { repoPath: '/path/to/repo' });2. nng IPC (Rust Backend β Extension Host/LSP/Remote)
// Extension Host communication (nng)
let msg = IPCMessage::ExtensionAPICall {
method: "workspace.openTextDocument",
args: vec![uri],
};
let response = nng_client.send(serde_json::to_string(&msg)?).await?;- Tauri IPC: Frontend <-> Backend (async/await, type-safe, built-in)
- NNG IPC: Backend services (high-performance, multiple patterns, serde_json serialization)
Extension Host Process (separate)
βββββββββββββββββββββββββββββββββββββββ
β Node.js v18+ β
β βββββββββββββββββββββββββββββββββ β
β β vscode.* API Implementation β β
β β - window, workspace, etc. β β
β β β β
β β Electron API Shims β β
β β - electron.remote.app β β
β β - electron.ipcRenderer β β
β β β β
β β Extension Loader β β
β β - Load .vsix β β
β β - Execute activate() β β
β βββββββββββββββββββββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββββ β
β β Loaded Extensions β β
β β - ESLint β
β β
β β - Prettier β
β β
β β - Python (with native) β
β β
β β - GitLens β
β β
β βββββββββββββββββββββββββββββββββ β
ββββββββββββββββ¬βββββββββββββββββββββββ
β nng IPC
βββββββΌβββββββ
β Rust β
β Backend β
ββββββββββββββ
// electron-shim/index.ts
// Provide compatibility for extensions using Electron APIs
export const remote = {
app: {
getPath(name: string): string {
// Map to Tauri's path API
return invoke('get_app_path', { name });
},
getVersion(): string {
return invoke('get_app_version');
},
},
dialog: {
showOpenDialog(options: any): Promise<any> {
return invoke('show_open_dialog', { options });
},
},
};
export const ipcRenderer = {
send(channel: string, ...args: any[]): void {
invoke('electron_ipc_send', { channel, args });
},
on(channel: string, listener: Function): void {
// Subscribe to events from Rust
listen(`electron:${channel}`, listener);
},
};<!-- src/components/EditorArea.svelte -->
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import * as monaco from 'monaco-editor';
import { invoke } from '@tauri-apps/api/core';
let container: HTMLDivElement;
let editor: monaco.editor.IStandaloneCodeEditor;
onMount(() => {
editor = monaco.editor.create(container, {
value: initialContent,
language: 'typescript',
theme: 'vs-dark',
minimap: { enabled: true },
lineNumbers: 'on',
glyphMargin: true,
});
editor.onDidChangeModelContent(async (e) => {
await invoke('text_document_changed', {
uri: currentFile,
changes: e.changes,
});
});
});
onDestroy(() => {
editor?.dispose();
});
</script>
<div bind:this={container} class="editor-container" />- Vim mode (via monaco-vim) β
- Emacs bindings β
- Bracket matching β
- Multi-cursor β
- IntelliSense β
- All VS Code editor features β
VS Code (Electron):
βββ Chromium: 150 MB
βββ Node.js: 80 MB
βββ Extensions: 100 MB
βββ Total: ~350 MB
DSCode (Tauri):
βββ WebView: 80 MB (system WebView on macOS/Linux)
βββ Rust Backend: 30 MB
βββ Extensions: 40 MB (separate process)
βββ Total: ~140 MB (60% reduction!)
VS Code (Electron):
1. Launch Electron: 800ms
2. Initialize Node.js: 600ms
3. Load UI: 800ms
4. Activate extensions: 300ms
Total: 2.5s
DSCode (Tauri):
1. Launch Tauri: 200ms
2. Load WebView: 200ms
3. Initialize Rust: 100ms
4. Activate extensions: 100ms
Total: 0.6s (4x faster!)
use tokio::fs;
use notify::{Watcher, RecursiveMode};
#[tauri::command]
async fn watch_files(path: String) -> Result<(), String> {
let (tx, mut rx) = tokio::sync::mpsc::channel(100);
let mut watcher = notify::recommended_watcher(move |res| {
tx.blocking_send(res).ok();
}).unwrap();
watcher.watch(Path::new(&path), RecursiveMode::Recursive)?;
tokio::spawn(async move {
while let Some(event) = rx.recv().await {
// Emit event to frontend
app_handle.emit_all("file-changed", event).ok();
}
});
Ok(())
}- β UI Parity: 99% (Monaco + web UI)
- β Extension Compat: 95%+ (Node.js + Electron shims)
- β Startup Time: <0.6s (Tauri performance)
- β Memory: <150MB (60% less than Electron)
- β Bundle Size: <60MB (vs 200MB for Electron)
- β Cross-platform: Linux, macOS, Windows (Tauri built-in)
- β Theme Compat: 100% (Monaco theming)
| Component | Risk Level |
|---|---|
| Text Editor | LOW (Monaco) |
| UI Layout | LOW (Svelte + Web) |
| Extensions | LOW (Node.js) |
| Webviews | LOW (Tauri) |
| Overall | LOW |
Tauri architecture = Low risk, high reward!
- Get Monaco editor (same as VS Code) β
- Get 95%+ extension compatibility β
- Get better performance than Electron β
- Get smaller bundle size β
- Keep Rust backend benefits β