用Vuido打造轻量级桌面应用的实战经验分享
项目初期的技术选型
说实话,最开始接到这个项目的时候我也没想到会用 Vuido。客户的需求很简单:开发一个桌面端的轻量级日志查看工具,支持 Windows 和 macOS,界面不用太 fancy,但要能实时刷新、搜索过滤、导出文本,最好还能有点自定义配置。
按理说这种需求用 Electron 就完事了,但我之前用 Electron 打包出来的应用动不动就七八十 MB,客户那边网络环境一般,部署起来太痛苦。而且这工具后期可能要集成到他们内部的运维系统里,越轻越好。
后来翻 GitHub 的时候偶然看到 Vuido,基于 Vue 和 libui-node,直接调用系统原生 UI 组件,打包出来只有几 MB,启动也快。关键是它支持 Vue 的写法,学习成本低,我就抱着试试看的心态搭了个 demo,结果发现基本功能都能实现,于是决定赌一把。
第一个版本跑起来了,但问题一堆
初始化项目用了官方推荐的模板:
npx create-vuido-app my-logger-tool
结构挺清晰,main.js 是主进程入口,App.vue 是主组件。我照着改了一版,把日志读取逻辑加上去,用 fs.watch 监听文件变化,再通过事件发给前端更新。代码大概长这样:
// main.js
const { App, Window, Box, TextArea } = require('vuido')
const fs = require('fs')
class MainWindow extends Window {
constructor() {
super({
title: 'Log Viewer',
width: 800,
height: 600,
margined: true
})
const box = new Box({
vertical: true,
padded: true
})
this.textArea = new TextArea({
readOnly: true
})
box.append(this.textArea, true)
this.setChild(box)
this.loadLogs()
this.watchLogFile()
}
loadLogs() {
try {
const content = fs.readFileSync('./app.log', 'utf-8')
this.textArea.setText(content)
} catch (err) {
this.textArea.setText('无法读取日志文件')
}
}
watchLogFile() {
fs.watch('./app.log', () => {
this.loadLogs()
})
}
}
const app = new App()
app.on('ready', () => {
new MainWindow()
})
app.start()
Vue 部分其实没怎么动,主要是数据展示和输入框做搜索:
<!-- App.vue -->
<template>
<Window title="Log Viewer" width="800" height="600">
<Box vertical padded>
<Entry v-model="search" placeholder="输入关键字搜索..." />
<Button @click="performSearch">搜索</Button>
<TextArea :text="filteredLogs" readonly />
</Box>
</Window>
</template>
<script>
export default {
data() {
return {
search: '',
rawLogs: '',
filteredLogs: ''
}
},
methods: {
performSearch() {
if (!this.search) {
this.filteredLogs = this.rawLogs
} else {
this.filteredLogs = this.rawLogs
.split('n')
.filter(line => line.includes(this.search))
.join('n')
}
},
updateLogs(logs) {
this.rawLogs = logs
this.filteredLogs = logs
}
}
}
</script>
最大的坑:性能问题
一开始测试小日志文件(几十 KB)完全没问题,但换成真实环境的 log 文件——动辄几百 MB,直接卡死。textarea setText 操作一旦数据量大,UI 线程就阻塞,整个窗口无响应。我试过节流、防抖、分块读取,都没根本解决。
后来折腾了半天发现,libui 对大文本的支持本来就有缺陷,而 Vuido 又是直接封装的 libui-node,根本没法异步渲染。我甚至试过把文本拆成数组,用多个 Label 拼接显示,结果布局乱成一锅粥,还更卡。
最后只能妥协:加个最大读取限制。改成只读最后 5 万行:
function readLastNLines(filePath, n = 50000) {
try {
const data = fs.readFileSync(filePath, 'utf-8')
const lines = data.split('n')
return lines.slice(-n).join('n')
} catch {
return '读取失败'
}
}
虽然不完美,但至少不会崩了。用户如果真要看全量日志,让他们用别的工具吧,这也不是这个工具的核心目标。
又踩坑了:搜索功能响应慢
接下来是搜索。原以为正则搞定就行,结果上万行文本里做 includes 匹配,输入框一打字就卡顿。开始没想到要做优化,后来加了 debounce,延迟 300ms 才触发搜索:
watch: {
search(newVal) {
this.debounceSearch(newVal)
}
},
methods: {
debounceSearch: _.debounce(function (val) {
this.performSearch(val)
}, 300)
}
但这里注意我踩过好几次坑:_ 是 lodash,你得先 npm install lodash。Vuido 项目默认不带这些工具库,得手动加。而且不能用 import,因为底层是 Node 环境,要用 require:
const _ = require('lodash')
最终的解决方案
综合下来,最后的方案是:
- 限制单次加载日志不超过 5 万行
- 使用 fs.watch 监听文件变化,自动刷新
- 搜索使用 debounce + 字符串 filter
- 增加“清空”“重新加载”按钮,避免缓存问题
- 导出功能用原生 fs.writeFileSync 实现,保存为 .txt
导出那段代码其实特别简单:
exportLogs() {
const path = require('path')
const { saveFileDialog } = require('vuido')
const filePath = saveFileDialog({
title: '保存日志',
filters: [{ name: '文本文件', pattern: '*.txt' }]
})
if (filePath) {
fs.writeFileSync(filePath, this.filteredLogs, 'utf-8')
}
}
回顾与反思
做完这个项目,我对 Vuido 的认知彻底变了。它不是用来做复杂 UI 的,而是适合那种“需要原生桌面体验 + 功能简单”的场景。如果你要做类似设置面板、日志监控、轻量工具类应用,它是真的香——打包小、启动快、API 简洁。
但它不适合做大体量数据展示,也不适合复杂交互。比如你要做富文本编辑器、表格排序筛选一大堆列,别碰它。
另外,文档是真的少,遇到问题基本靠翻 GitHub issues。社区也不活跃,很多 bug 提了没人回。但我们这个项目影响不大,关键路径都走通了。
还有一个小问题到现在没完美解决:Mac 下窗口关闭后进程偶尔不退出,得手动 kill。查了一圈怀疑是 fs.watch 没 properly close,但我加了 process.on(‘exit’) 也没完全搞定。不过实际使用中影响很小,用户基本不会频繁开关,就放着了。
总结一下
Vuido 这个技术,属于“冷门但能救命”的类型。Electron 太重的时候,你可以想想有没有更轻的方案。Vuido 就是其中一个选择,前提是你接受它的局限性。
以上是我个人对这个项目的完整分享,有更优的实现方式或者更好的桌面轻量化方案,欢迎评论区交流。这个技巧的拓展用法还有很多,后续我也会继续分享这类实战经验。
以上是我踩坑后的总结,希望对你有帮助。
