Alternativas a Electron: criterios para elegir Tauri, Wails y Neutralinojs
Compara Tauri, Wails y Neutralinojs antes de descartar Electron o adoptar Tauri por defecto: consistencia, límites nativos, lenguaje del equipo, seguridad, distribución y operación, más allá del tamaño del paquete.
Las razones para buscar una alternativa a Electron suelen comenzar con una cosa: el archivo de la aplicación es grande. Utiliza mucha memoria. Quiero escribir código nativo en un idioma que no sea Node.js. O surgió una demanda: "Quiero publicar la base del código web en el escritorio". Sin embargo, si elige un nombre de marco como respuesta, normalmente encontrará otros costos después del lanzamiento.
Tauri·Wails·Neutralinojs pueden crear pantallas con HTML·CSS·JavaScript, y las funciones del sistema operativo se dejan en una capa nativa separada. Sin embargo, el lenguaje para diseñar los límites, el modelo de seguridad, el entorno de ejecución y el método de depuración cuando ocurre un problema son bastante diferentes. Un pequeño archivo de instalación puede ser bueno, pero no es un buen punto de partida para la selección.
Este artículo no repite la conclusión de que “Electron es pesado y Tauri es liviano”. Primero, determinaremos para qué producto es más importante la coherencia del renderizador, qué lenguaje operativo es mejor entre Rust y Go, y si está listo para adoptar el sistema WebView. Los criterios de selección para Electron en sí son Tauri vs Electron: criterios de selección de aplicaciones de escritorio SvelteKit, y la cuestión de expandir una base de código a dispositivos móviles es Capacitor vs Tauri: criterios de selección para extender una única base de código web a aplicaciones móviles y de escritorio.
Primero, nombre el costo que desea descartar.
El término "reemplazar" Electron es una combinación de cuatro necesidades diferentes.
| necesidades reales | Preguntas para hacer primero | una dirección fuerte |
|---|---|---|
| Archivos de instalación y memoria inicial. | ¿Debería incluirse el motor del navegador con cada aplicación? | Tauri·Wails·Neutralinojs |
| Poder de procesamiento de funciones nativas | ¿Qué idioma y biblioteca utilizarás para hacer el trabajo pesado? | Tauri(Rust)·Wails(Go) |
| Consistencia de renderizado frontal | ¿Es realmente necesario que todos los OS tengan el mismo comportamiento Chromium? | Electron Mantenibilidad |
| Reducir la dependencia del nodo | ¿A qué puedo mover la lógica fuera del renderizador? | Tauri·Wails·Neutralinojs |
Aquí el primero y el tercero suelen entrar en conflicto. Electron incluye Chromium y Node.js juntos, por lo que el paquete de la aplicación puede ser grande. En cambio, el motor del navegador en el que se ejecuta la aplicación es relativamente consistente. Por el contrario, las tres alternativas utilizan WebView del sistema operativo, que tiene la ventaja de no incluir un motor de navegador en la aplicación. Sin embargo, las diferencias de motor entre Windows·macOS·Linux y la versión WebView que realmente tiene pueden afectar la calidad del producto.
Entonces, la unidad de comparación no es el "punto de referencia promedio del marco", sino el riesgo de mi producto. ¿Existe alguna característica que sea sensible a las diferencias del motor, como WebRTC, Canvas avanzado, el último CSS, un editor basado en web o la extensión del navegador API? Por el contrario, si solo es una utilidad para procesar rápidamente archivos locales, una herramienta de entrada interna o una pequeña aplicación de productividad, la consistencia del Chromium incluido puede resultar demasiado cara.
Los tres marcos no son la misma aplicación WebView
| Artículo | Tauri | Wails | Neutralinojs |
|---|---|---|---|
| Idioma principal de la capa nativa. | Rust | Go | Núcleo de C++ + libertad de lenguaje extendida |
| motor de pantalla | SISTEMA WebView | SISTEMA WebView | Sistema WebView o modo Chrome |
| Frontend → Conexión nativa | command·plugin·capability | Ir al enlace del método · tiempo de ejecución | Extensión nativa basada en WebSocket API· |
| un equipo en buena forma | Un equipo que adopta Rust y valora el diseño de permisos. | Equipo con sólida experiencia en servicio/backend de Go | Equipo que experimenta rápidamente con pequeñas herramientas de escritorio enfocadas en JS |
| Riesgos a verificar primero | WebView y configuración de capacidad para cada OS | Superficie de enlace y modelo de ejecución Go. | Lista de permitidos y extensión nativa API·Límites de WebSocket |
La tabla es una secuencia de preguntas, no una conclusión. Los tres pueden usar React, Svelte, Vue o web pura en el front-end. La verdadera diferencia ocurre fuera de la interfaz de usuario. Depende de qué tipo de sistema de archivos, bandeja, actualización automática, base de datos, hardware, tarea larga, evento OS desea llamar y probar.
Tauri: Cuando desea manejar el modelo de permiso como un código de producto
Tauri conecta la interfaz JavaScript y el núcleo Rust. La característica clave de Tauri v2 es que API expuesto al front-end puede estar limitado por capacidades y permisos. Básicamente, asumiendo el alcance del acceso al código de la aplicación incluida, puede configurar los permisos que se otorgarán para cada ventana·WebView·origen.
Reunir permisos en un archivo puede resultar complicado al principio. Sin embargo, tan pronto como la ventana de contenido remoto y la ventana principal de la aplicación se mezclan o se agrega un complemento, "¿a qué pantallas se puede exponer esta función?" puede estar sujeto a revisión de código.
// src-tauri/capabilities/main.json
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "main-window",
"description": "메인 앱 창에만 필요한 최소 권한",
"windows": ["main"],
"permissions": [
"core:default",
"dialog:allow-open",
"dialog:allow-save",
{
"identifier": "fs:allow-read-text-file",
"allow": [{ "path": "$HOME/Documents/**" }]
}
]
}
Para el identificador de permiso específico y la ruta del esquema, debe verificar los valores generados en la documentación oficial de acuerdo con la versión del complemento Tauri que esté utilizando. El propósito del ejemplo no es memorizar la gramática, sino adquirir el hábito de expresar el alcance, como "leer texto en la carpeta de documentos del usuario" en lugar de "todo el sistema de archivos".
El comando Rust también está diseñado como un producto API al que el renderizador puede llamar. Es mejor no convertir un comando en un shell de propósito general o en un archivo de propósito general API.
// src-tauri/src/lib.rs
#[tauri::command]
fn normalize_note_title(title: String) -> Result<String, String> {
let trimmed = title.trim();
if trimmed.is_empty() {
return Err("제목을 입력해 주세요.".into());
}
Ok(trimmed.chars().take(80).collect())
}
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![normalize_note_title])
.run(tauri::generate_context!())
.expect("error while running Tauri application");
}
// src/lib/native/notes.ts
import { invoke } from '@tauri-apps/api/core'
export function normalizeNoteTitle(title: string) {
return invoke<string>('normalize_note_title', { title })
}
La razón por la que Tauri es correcta no es la única afirmación de que Rust es más rápido. Es atractivo cuando las operaciones nativas como el hash de archivos, el procesamiento de imágenes y la sincronización local son fundamentales para su producto, desea administrar permisos de manera estricta y puede operar la revisión, compilación y depuración del código Rust. Por el contrario, si un equipo no puede mantener Rust en absoluto, sino que elige basándose únicamente en el tamaño de la instalación, el límite nativo puede convertirse en un cuello de botella.
Wails: Cuando desea llevar el modelo de servicio Go al escritorio.
Wails crea un enlace para que los métodos Go puedan llamarse desde el front-end. Esta es una elección natural si ya ejecuta su motor de sincronización, servidores locales, cifrado/procesamiento de archivos y lógica de dominio en Go, o si su equipo de backend está familiarizado con Go. El frontend es fácil de entender como un modelo que "se comunica con el backend de Go".
// app.go
package main
import (
"context"
"fmt"
"strings"
)
type App struct {
ctx context.Context
}
func NewApp() *App { return &App{} }
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
}
func (a *App) NormalizeNoteTitle(title string) (string, error) {
value := strings.TrimSpace(title)
if value == "" {
return "", fmt.Errorf("제목을 입력해 주세요")
}
runes := []rune(value)
if len(runes) > 80 {
runes = runes[:80]
}
return string(runes), nil
}
Wails Es conveniente que la interfaz de usuario utilice directamente el enlace creado en el modo de desarrollo. Sin embargo, en lugar de dispersar la ruta del producto por la interfaz de usuario, envolverlo en un pequeño adaptador facilita el reemplazo y las pruebas del marco.
// frontend/src/lib/native/notes.ts
import { NormalizeNoteTitle } from '../../wailsjs/go/main/App'
export async function normalizeNoteTitle(title: string) {
return NormalizeNoteTitle(title)
}
El hecho de que tenga el enlace Go no significa que deba exponer todos los métodos exportados a la interfaz de usuario. Dado que la estructura App puede ser la superficie del front-end API, es más seguro colocar métodos como el identificador de la base de datos, el diagnóstico de operaciones y el acceso a variables de entorno en servicios internos separados y limitar solo los métodos de la interfaz de usuario. Para las llamadas de UI, adopte los mismos hábitos del servidor API, como la validación de entradas, la verificación del estado actual de la ventana/usuario y la limitación de las rutas de los archivos.
Wails es una elección desafortunada si solo lo miras como "Tauri, pero Go en lugar de Rust". Si las rutinas de Go, las bibliotecas estándar, el código de servicio existente y las herramientas de implementación son los puntos fuertes de su equipo, la capa local de la aplicación de escritorio también puede tomar el control. Por otro lado, las diferencias específicas de OS, las firmas de distribución y la verificación específica de la plataforma de las funciones nativas de WebView aún deben realizarse después de salir de Electron.
Neutralinojs: Para afinar los límites de herramientas pequeñas y simples.
Neutralinojs utiliza un núcleo pequeño y un sistema WebView, y el cliente JavaScript integrado llama a API nativo a través de un WebSocket local. Si es necesario, la extensión se puede escribir en otro idioma. Es atractivo cuando todo lo que necesita es una interfaz de usuario y algunas funciones OS, como una pequeña utilidad de escritorio, una herramienta interna o una experimentación ligera.
Sin embargo, esto no significa que “diseñar permisos sea simple porque solo se usa JavaScript”. El API nativo puede contener funciones potentes como sistemas de archivos, procesos y ventanas. La lista de permitidos y la lista de bloqueo deben especificarse de acuerdo con los requisitos del producto.
// neutralino.config.json
{
"applicationId": "com.example.notes",
"defaultMode": "window",
"enableNativeAPI": true,
"tokenSecurity": "one-time",
"nativeAllowList": [
"app.*",
"window.*",
"filesystem.readFile",
"filesystem.writeFile",
"os.showOpenDialog",
"os.showSaveDialog"
],
"nativeBlockList": [
"os.execCommand",
"extensions.*"
]
}
Una cosa a tener en cuenta sobre esta configuración es que bloquea os.execCommand. La solicitud de que el usuario quiera "guardar y abrir archivos" no significa que el "renderizador pueda ejecutar comandos arbitrarios". Active solo el API nativo necesario, e incluso cuando se necesite una extensión, la entrada, la autenticación y el registro de la extensión deben diseñarse por separado.
// resources/js/notes.ts
import '@neutralinojs/lib'
export async function saveNote(name: string, content: string) {
const safeName = name.replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 80)
const selected = await Neutralino.os.showSaveDialog('메모 저장', {
defaultPath: `${safeName}.md`
})
if (!selected) return { saved: false }
await Neutralino.filesystem.writeFile(selected, content)
return { saved: true }
}
Aquí también, la ruta del archivo y las restricciones de extensión/tamaño del producto real deben revisarse por separado. El punto es que el hecho de que elija Neutralinojs no garantiza automáticamente un valor predeterminado seguro. WebSocket local y token, lista de permitidos nativa API son los modelos de permisos del producto.
SISTEMA WebView cambia de ubicación sin eliminar costes
Tauri · Wails · Neutralinojs no incluye el motor del navegador para cada aplicación, lo cual es una clara ventaja. Sin embargo, el sistema WebView se mueve con sus actualizaciones OS. Dependiendo del sistema operativo·proveedor WebView·distribución Linux, puede encontrar diferencias en CSS, método de entrada, medios, GPU, Web API y herramientas de depuración.
No hay necesidad de exagerar esta diferencia. Los formularios, listas, configuraciones y pantallas CRUD locales comunes suelen funcionar bien. Sin embargo, si la experiencia principal de su producto se basa en las funciones más recientes del motor web o en la coherencia a nivel de píxeles, debe verificar la combinación OS real en la PoC.
| función | Qué debes hacer en PoC |
|---|---|
| Entrada coreana, japonesa y china. | Verifique la cancelación, el movimiento de enfoque y el conflicto de atajos durante la combinación usando el teclado real. |
| Arrastrar y soltar archivos | Verificar ruta/permiso/archivo grande/cancelar por OS |
| Audio·Vídeo·WebRTC | Verifique la ventana emergente de permiso, cambio de dispositivo, regreso al fondo/suspensión |
| Lienzo·Editor | Mida documentos largos, DPI altos, zoom, respaldo de GPU |
| actualización automática | Instalar la versión firmada → actualizar → revertir en cada plataforma |
| inicio sin conexión | Verifique la pantalla de inicio y la recuperación de datos locales mientras la red está desconectada |
Antes de cambiar el marco, intente realizar la tarea anterior como una pantalla representativa en lugar de "tamaño del paquete hola mundo". Esto se debe a que es la prueba que más expone los límites nativos del producto y los límites WebView. Electron puede ser una opción que permita una menor verificación y un marco alternativo puede ser una opción que reduzca los costos de instalación/actualización a cambio.
La migración no se mueve desde el renderizador.
Al mover una aplicación Electron existente a Tauri o Wails, el enfoque más arriesgado es traducir el código del proceso principal de una sola vez. Si transfiere un API de uso general como ipcRenderer.invoke('fs:write') al comando Tauri o al método Go, solo cambia el marco y el límite sigue siendo el mismo.
Primero, organiza el puente en Electron en un verbo de producto. Si ya tiene un window.desktop.saveExport() y un window.desktop.openSettings() angostos, puede simplemente reemplazar el adaptador sin casi dañar el renderizador.
// renderer가 의존할 공통 계약
export interface DesktopPlatform {
saveExport(input: { name: string; content: string }): Promise<{ saved: boolean }>
getAppVersion(): Promise<string>
openDocumentation(): Promise<void>
}
// electron-platform.ts
export const electronPlatform: DesktopPlatform = {
saveExport: (input) => window.desktop.saveExport(input),
getAppVersion: () => window.desktop.getAppVersion(),
openDocumentation: () => window.desktop.openDocumentation()
}
// tauri-platform.ts
import { invoke } from '@tauri-apps/api/core'
import { openUrl } from '@tauri-apps/plugin-opener'
export const tauriPlatform: DesktopPlatform = {
saveExport: (input) => invoke('save_export', { input }),
getAppVersion: () => invoke('app_version'),
openDocumentation: () => openUrl('https://docs.example.com')
}
La ventaja de esta estructura no es la portabilidad sino la verificabilidad. Sabrá cuando se revele la lista de funciones de escritorio que su interfaz de usuario realmente requiere y las implementaciones específicas del marco superen esa lista. Esto es especialmente útil para productos que requieren que PWA, Capacitor, Tauri y Electron se ejecuten juntos.
Alcance mínimo para una PoC de decisión de 4 semanas
PoC no es un proyecto para utilizar la CLI de un nuevo marco. La tarea consiste en encontrar de forma proactiva los fallos más costosos después de la implementación.
- Primera semana: elija un flujo representativo. Implemente un flujo que sea importante en el producto real: iniciar sesión, abrir/guardar archivos locales, lista larga/editor, buscar actualizaciones.
- Semana 2: cree un límite nativo. Adjunte al menos dos de las siguientes notificaciones: permisos de archivos, llavero, bandeja, vínculo profundo y notificaciones OS con código real. No se pasa por una burla.
- Semana 3: Ejecute la matriz del sistema operativo. Verifique la instalación, el inicio sin conexión, la reactivación, la entrada y la recopilación de registros para las combinaciones Windows·macOS·Linux compatibles.
- Cuarta semana: practique la implementación y los fallos. Ejecute la firma, la actualización, el informe de fallos, la migración de datos existentes, la degradación o la reversión una vez.
Los indicadores de desempeño también se determinan de antemano. No mida simplemente los arranques en frío, la memoria inactiva, los tiempos de operación típicos y el tamaño del paquete; También registre los tiempos de compilación, los tiempos de CI, la dificultad de reproducción de errores, la cantidad de correcciones por plataforma y el tiempo que le toma a un nuevo miembro del equipo agregar una única característica nativa. Estas cifras describen mejor los costos a largo plazo.
Árbol de decisión corto para selección.
모든 OS에서 Chromium 기반의 같은 렌더링이 핵심인가?
├─ 예 → Electron을 유지하거나 별도 Chromium 전략을 검토한다.
└─ 아니오 → 시스템 WebView의 PoC를 한다.
│
├─ Rust로 로컬 작업을 다루고 capability 기반 권한을 명시하고 싶은가?
│ └─ 예 → Tauri
│
├─ Go 서비스 코드·인력이 있고 Go binding이 자연스러운가?
│ └─ 예 → Wails
│
└─ 작은 JS 중심 도구이며 제한된 native API만 필요한가?
└─ 예 → Neutralinojs
Este árbol no es una prescripción absoluta. Puede usar menos Rust en Tauri, puede crear aplicaciones nativas complejas en Wails y puede expandir Neutralinojs con extensiones. Sin embargo, muestra el camino para obtener la menor ventaja del marco. Cuanto más alineadas estén las capacidades principales del producto con las capacidades operativas requeridas por el marco, mayores serán los beneficios que los archivos de instalación más pequeños.
Conclusión: elegir un límite que pueda operarse en lugar de ser liviano
Puede haber muchas razones para abandonar Electron. Pero si define una “aplicación liviana” en términos de la cantidad de MB por paquete, descubrirá las diferencias WebView, el modelo de permisos, la cadena de herramientas de compilación y las responsabilidades de implementación más adelante.
Tauri es una buena opción para equipos que desean crear límites nativos con permisos claros a través de Rust y capacidades. Wails es adecuado para equipos que desean conectar de forma natural las capacidades de servicio ya integradas en Go al escritorio. Los puntos fuertes de Neutralinojs residen en la creación de herramientas de escritorio con funciones pequeñas y límites definidos centradas en JS. Y si la consistencia de Chromium es la clave de su producto, entonces Electron no es una falla que deba ser reemplazada, sino una base estable entre la que puede elegir conociendo sus costos.
Las buenas opciones no provienen de qué marco es el "ganador", sino de verificar las pantallas más riesgosas de mi aplicación y los permisos OS más sólidos antes del lanzamiento.