说到前端写Windows
应用程序,首先想到的一定是Electron
,这次我们换一个小众点的工具aardio
。aardio
是一个很轻量的工具,有着自己的语法,只
面向windows
应用开发,刚好其中也有支持前端应用打包功能。
选型
aardio
中涉及到HTML
+CSS
+JS
的功能。有Chrome App
、Electron
,同时还有Sciter.js
、HTMLayout
两个UI引擎。
一下子就有四种选项,这里按自己摸索的简单总结一下,可能会不太准确:
Chrome App
默认打包出来的应用程序不携带浏览器,程序会启动调用本地的浏览器渲染(不限于官方的Chrome
,Edge
以及国产主流Chromium
内核的浏览器都行),如果都没有会自动安装微软的Edge(Chromium版)
浏览器。
优势是生成体积比较小(但外部环境不稳定,产生的显示Bug可能性也更多),基础大小估计在800KB
左右(经UPX压缩过),前端项目迁移起来不用过多修改就能跑起来。如果要指定环境,也可以在启动代码内指定浏览器路径。
Electron
可以构建Electron
的应用,Electron
本身就是做桌面程序的为什么还需要使用aardio
?
看了一下,除了可以利用aardio
的一些内置的方便功能函数,同时生成的应用体积也是差异很大,软件自带的示例Electron
项目生成文件大概在800KB
左右(经UPX压缩过),作者可能使用了精简版的内核替换了原本的程序。如果只做Windows平台应用,这样的打包体积还是很吸引人的。
Sciter.js
这主要是面向桌面开发的一个UI渲染引擎,可以利用HTML
+CSS
写界面,将浏览器许多无用功能剔除,只留下UI引擎常用的功能,使得体积尽量小巧。
原本以为能将整个前端项目打包进去,但尝试了一下,好像不行。HTML
与CSS
是单独传入的,没办法像是前端那么自由的引用。使用逻辑更像是一个html
界面就配置一个Sciter.js
对象,而不是将html
程序的所有逻辑放到里面去执行。
生成的应用体积也是比较小的,大概在2.8MB
左右(经UPX压缩过)。看介绍,大部分常用的CSS
支持,同时也有一些特殊的语法方便处理界面。
HTMLayout
HTMLayout
是Sciter.js
的前身,程序体积更小,但支持的CSS
功能也更加有限。同样也不太适用前端项目的打包。
对比了一下HTMLayout
和Sciter.js
生成应用的体积,应用大概在1.5MB
左右(经UPX压缩过),两者只差了1.3MB
左右,但放弃的新语法可能要很多,所以这个大部分场景可以不必考虑了。
尝试一下
官方有推过一个修改版的hostsSwitchHelper
,使用的就是Chrome App
模式,既然是尝试,那就先选这个模式来试试。
Chrome App
模式其实很小白,如果没有与系统交互的功能,那么几乎就是直接将前端项目编译,然后导入到开发工具中就能生成一个应用。
先来看看界面:
选择Web界面
栏目,然后选择第一项Chrome App
,设置好目录就能创建出一个Chrome App
例子了,其中自带已经能跑起来的代码。
代码量很少,不熟悉的可以直接在这个项目上做简单修改,就是一个自己的项目了。
做一个自己的程序
这是已完成的界面:
前端部分
先从熟悉的开始吧,把前端部分先完成。
由于程序不大,于是不使用Vue
或者React
了,找了个原生漂亮的UI框架LuLu UI
,配合TypeScript
,打包工具也不要太重的WebPack
,我们选用更加轻量的parcel
。
其实一开始是直接用LuLu UI
+原生JS,简单的写几个页面,后来对界面细节不断调整(这块反复修改花了不少时间),以及操作逻辑的优化,总体逻辑相对复杂了起来,于是引入了打包工具,将CSS
换成了LESS
,JavaScript
换成了TypeScript
,并且分出了目录结构出来。
打包其实只需要编译完成的前端文件,所以前端工程可以随意放在哪儿都行,最后只需要把编译出的文件放到aardio
设置的目录下就可以了。
至于前端就很普通,常见的建立项目,补充样式与逻辑,加上parcel
本身也很轻量易用,太细节的就不多说了,挑一些主要的说。
关闭翻译提示
如果不设置,网页默认是英文,打开后Chrome
会弹出翻译提示,这个功能对于本地应用程序无意义。设置的话就是标注网页语言为中文,或是简单粗暴在head
标签里加上这句,禁止使用翻译功能。
屏蔽影响使用的快捷键
网页的F12
、Ctrl+Shift+i
、Ctrl+u
等,万一触碰到会影响应用程序体验,用JS代码将这些快捷键禁用。
由于KeyboardEvent.keyCode
已经被废弃了,这里直接使用KeyboardEvent.key
判断
window.onkeydown = window.onkeyup = window.onkeypress = function (event: KeyboardEvent) {
//禁止ctrl+u
if (event.ctrlKey && event.key.toLowerCase() == 'u') return false;
//禁止ctrl+s
if (event.ctrlKey && event.key.toLowerCase() == 's') return false;
//禁止ctrl+shift+i
if (event.ctrlKey && event.shiftKey && event.key.toLowerCase() == 'i') return false;
//禁止 F5
if (event.key == 'F5') return false;
//禁止 F12
if (event.key == 'F12') return false;
};
屏蔽右键
右键的菜单也有无关动东西,影响使用体验,重点还很难看。如果没有右键功能可以直接屏蔽,这里为了方便加上了右键功能,所以重画了右键菜单键,替换了原生的右键。
window.oncontextmenu = function (event: MouseEvent) {
event.preventDefault();
show(event); // 里面包括打开自己的菜单层
return false;
};
document.addEventListener('click', close); // 关闭右键菜单 close函数里就是将相应的层隐藏
show
的逻辑很简单,获取当前鼠标位置,然后判断窗口是否会溢出,做相应的高度修改,最后将指定的菜单层显示。
/**
* 显示右键菜单
* @param {MouseEvent} event
*/
function show(event: MouseEvent) {
if (!isNavItem(event)) return;
// 获取当前鼠标位置
let mouseX = event.clientX;
let mouseY = event.clientY;
// 判断边界值,防止菜单栏溢出可视窗口
const winHeight = document.documentElement.clientHeight ?? document.body.clientHeight;
// BUS.contextMenu是存储的前端菜单DOM
if (mouseY > winHeight - BUS.contextMenu.offsetHeight) mouseY = winHeight - BUS.contextMenu.offsetHeight - 10;
else mouseY = mouseY;
BUS.contextMenu.style.left = `${mouseX}px`;
BUS.contextMenu.style.top = `${mouseY}px`;
}
这里增加了一些细节判断,像是编辑器区域不弹出右键,在标签项右键与导航空白区右键显示不同菜单。
调试模式
由于禁用掉开发者工具在调试的时候很麻烦,通过注释代码屏蔽又容易忘记,parcel
打包工具同样提供了环境变量。
process.env.NODE_ENV === 'production'
网页元素重渲染
由于没有使用现代框架,这部分起始逻辑很重。像是鼠标右键不同地方显示不同菜单的功能实现起来就会很麻烦,要不然代码就很冗余。于是引入了小巧快捷的前端模板引擎art-template
,一部分操作交给引擎判断,通过添加不同的类名展示不同的状态。
比如鼠标右键点击在标签项上,如果当前的项没有添加url
参数,则是本地的Hosts
,渲染时会添加disabled
类名,标识禁用此元素。disabled
类的CSS
核心是pointer-events: none;
,这样即可让此元素不响应鼠标事件,无需再添加多余的js逻辑。
导航栏的当前选中的类名添加、添加新项等操作全部只是在统一的数据中做记录,然后依赖重新渲染一次性替换掉HTML
,避免了写复杂冗长的DOM
增删逻辑,也简化了代码耦合。
所以项目里单独做了一个数据管理的模块。从原本的简单nav
渲染单文件逻辑开始,到后来由于UI逻辑越做越细致,这个里也从单文件变成了多文件,尽量把外部操作与纯数据操作隔离。
文本编辑器
如果使用textarea
标签可以实现多行文本,但没办法提供注释文字的区分显示,也没办法提供行号、快捷键、搜索等扩展功能,所以我们需要一个文本编辑器。
经过筛选比较常见的代码编辑器有CodeMirror
、aceEditor
、monacoEditor
(VS Code
的前身),三者中CodeMirror
体积最小,我们需要的功能也不要太多,于是就选这个了。
使用方法网上很多,源码中注释也很清楚,使用方式就不多提了。
语法颜色
由于我们的Hosts
实际只需要给 #
号上色,编辑器有类似的语法,但也会将多余的关键字和结构显示出颜色,这时候我们可以自己修改一个。
简单的翻阅语法源码能发现在tokenBase
和return
中的token
有对很多信息做匹配,将没有的信息删除,减少判断逻辑,连带的可以删除几个相关函数代码。之后在底部CodeMirror.defineMIME
中有自定义许多关键字,对我们也没用,都可以删除。然后记得改一个新名字,这样就实现了一个我们专门的语法,导入插件中就能使用了。
主题样式我们也可以自定义,同样是找一份看的不错的样式,然后把CSS
代码复制过来,其中有许多状态的样式名称,根据相应名称修颜色即可。
这里我开启了高亮选中行,所以在当前行会有一个蓝色底色,由于我在编辑器文本框背景上又加了一个当前项的类型图标,会导致图标被覆盖,这时候只需要将div.CodeMirror-selected
的颜色调成半通明模式即可,但由于我背景的透明度调的很低(背景透明度是0.06,而选中的透明度是0.8),所以不仔细看的话实际上还是有点像是被覆盖住。
快捷键
本身是做切换标签才自动保存的逻辑,但如果用户修改了文本内容,误以为是实时保存而直接关闭软件,这样就会导致修改丢失。所以需要加上一个当前文本修改过的标识,于是又在右上角加了一个保存按钮(一个不好看,顺便又加上了刷新与更新远程文件按钮)。
有了按钮,自然的会习惯用Ctrl+s
去保存文件,好在CodeMirror
是提供了自定义快捷键的。
extraKeys: {
'Ctrl-S': eventTextareaSave, // 这里是自定义的保存逻辑
}
网络请求及代理
常见的应用场景是从网络上复制别人整理好的Hosts
信息,所以为了方便增加了从网络获取Hosts
功能。但这种一般需要借助代理功能才能获取到,搜索网上并没见到js能直接设置代理的办法。
这时候就应该将前端不易操作的功能放到aardio
中,通过inet.http
库可以直接发起网络请求,同时默认也会走系统代理设置,完全满足了目前的功能所需。
aardio部分
到这里就进入了我们不是熟悉的部分了。aardio
语法很像js,但细节上跟js又有差距,所以写起来的时候很容易弄混,要注意一下。
这里说几个看目前代码需要理解的点:import
,是将对应的命名空间导入当前的命名空间,使用的时候可以直接使用。会按内核库
、标准库
、用户库
(应用程序lib目录)查询引入。var
,申明变量不一定都要使用var
关键字,如果没使用是默认挂在当前命名空间下的。如果使用了,则作用域是在当前代码块。同时申明变量也只有var
关键字,不要顺手把ES6
的关键字用上了。global
,这是默认的全局空间名字,简写是..
在代码中经常看到,比如要操作io
库,可以写成..io
调用。table
,aardio
里table
是一种类型,类似js里的数组和对象,两种类型同时是table
类型。写法上key
和value
连接是用=
,末尾结束是用;
,这个与js差异很大,不小心写错会导致报错,复查错觉无时往往容易因为惯性而忽略。多返回值
,程序是支持多返回值的,用,
分隔,接收返回也是直接用,
分隔写变量即可接收。目录环境
,路径如果是~
开头的代表exe
应用程序运行的当前目录,如果是在测试环境,则是aardio.exe
主程序的目录。
其他不用写大段逻辑的话,基本也能看懂了。
创建chrome程序
main.aardio
是程序主入口,在这个文件里写起始逻辑。
import chrome.app; // 导入所需的库
var theApp = chrome.app(); // 创建一个实例
theApp.start(url); // chrome打开指定的url
这样就能启动一个chrome
应用了。
js与aardio交互
theApp.external = {
key = function(){
// 逻辑
};
}
在external
里面传入相应的逻辑,前端通过aardio
组件可以实现调用。
引入前端文件
在左侧新建“虚拟目录”,右侧会出现设置项,“目录路径”修改成我们前端的路径,“内嵌资源”要选true
,不然无法生成单文件的应用程序。
最后对着这个虚拟目录单击右键,选择“同步本地目录”,如果目录内有文件的话会全部显示出来。如果有修改重新生成前端文件的话,也要记得手动同步一次,目录是不会自动刷新的。
编译应用
这样我们的前端项目就能打包成win的应用程序了,按F7
就能一键编译,稍微等一儿编译好的应用就生成了,是不是很简单?
Electron版尝试
在aardio
中Electron
做了简化,目前的项目可以直接复制到Electron
工程的渲染进程
中使用,只要启动代码做相应修改即可。但尝试下来,发现UI渲染十分卡顿,不知道是什么原因。目前功能暂时够用,这块等之后有空再了解吧。
结尾
总体使用下来,对于熟悉前端以及前端工具链的形式,aardio
的界面和文本编辑器很不适应(VS Code
上有人做了一个插件,说是可以直接在VS Code
编译,配置了依然报错,而且没有格式化,没有智能提示,反不如原生IDE方便)。
文档也相对比较简单,需要有一定的耐心,很多地方需要自己从右侧的范例中查找例子学习,如果没有例子,则要从左下角的标准库里,看源代码和智能提示了解具体有什么功能和如何编写代码。
项目源码
具体细节有兴趣的话请查阅源码: HostsSwitch
https://github.com/Lnncoco/ho...