关于桌面应用:语雀桌面端技术架构实践

作者:易芝林(维骏) 语雀桌面端作为语雀为用户提供的生产力工具,上线两年多来始终放弃高频的迭代和衰弱的业务增长。本次次要介绍咱们在做桌面端时的一些技术架构思考和实际,同时也将分享咱们积淀的一些通用桌面利用解决方案和教训。 文章会分为四局部,首先会简略介绍语雀桌面端,而后介绍以后语雀桌面端的利用架构以及关键点,之后介绍架构中的几个架构重点项,最初在进行总结。 语雀桌面端介绍语雀是孵化自蚂蚁体验技术部的一款笔记与文档知识库工具。咱们在两年前,针对语雀用户特点,以及后续倒退策略,旨在为创作者提供更好的创作体验,推出语雀桌面客户端。 相较于现有浏览器提供的产品服务而言,咱们提供的桌面端产品次要思考以下几点: 无烦扰 :给用户一个沉迷式的创作体验,而不像浏览器有其余窗口、tab 进行烦扰,以及用完即走的用户心智。零碎级常驻 :关上速度更快,能够一键启动或者利用各类快捷工具唤起。集成更多操作系统能力 :晋升创作效率的多窗口、零碎菜单和快捷键、对文件读写、与系统软件集成等。离线 :冀望能在离线或弱网的状况下,无障碍的进行创作。 桌面端架构概览 研发测次要分为右边三层,最底层是语雀的基础设施,依赖了语雀后盾提供或封装的大量云服务,以及底层依赖的平安能力和存储模块。 两头一层是比拟偏利用架构的一些能力封装,下面是代码层面用的的辅助能力,还有主过程的模块,而后有给利用提供的一些治理能力和一些跟 UI 相干的功能模块,最上层就是基于底层架构搭建的业务利用。包含桌面端利用比拟外围的几个模块,以及一些由子利用形式承接的业务模块(前面也会具体介绍子利用这个概念) 同时最右侧也有很多辅助研发的依赖能力,来实现语雀桌面端的公布、品质和稳定性治理。 架构概览 - 关键点相比拟一般 web 利用来说,咱们感觉桌面端有以下几个能力比拟重要:包含平安、软件降级、以及桌面端通用的的根底能力: 架构概览 - 平安平安是一个生产力工具软件的生命线,特地是语雀作为一款常识管理工具,对于平安是十分看重。 根底平安下载安装包时,须要有平安管理机制,防止下载过程中被歹意替换;降级到最新的 Electron 版本(语雀目前紧跟官网大版本,同时也会参考微软的头部利用 (vscode),防止有没思考到的场景;同时用户离线下载到本地的文件,包含图片,附件等,也须要通过加密。启动平安杀毒软件:启动平安次要是在启动软件时的一些平安问题,例如二进制模块是否有加签名,防止被杀毒软件查杀导致无奈启动,也能够分割平安厂商加白名单,同时还能晋升启动速度。禁止调试:因为软件代码都会下载到客户端,能够禁止软件在客户端浏览器进行调试。数据库秘钥治理:本地数据库文件须要保障即便被歹意拿到,也要保障无奈查看到外面的内容。咱们通过生成内存安全级别的计划,确保其余非以后电脑的语雀软件即便拿到数据库文件后也无奈关上。利用平安渲染容器设置白名单来管制不被引入歹意页面;渲染过程敞开 Node 性能以及开启隔离模式,防止渲染过程权限过高;Electron 自身是 web 开发模式,所以 web 中遇到的平安问题,在 Electron 同样会遇到,能够对立解决。架构概览 - 软件降级客户端软件相比拟于 web 来说,还有一个十分大的区别就是有性能更新时,有一个降级过程,不像 web 间接刷新页面即可。语雀桌面端作为迭代迅速的产品,对于降级这块也是踩了不少坑。 语雀桌面端由两大部分组成:包含 Electron 和 Node.js 等根底模块的软件包 + 以及本人的业务代码。 Mac OS:Mac 下的降级流程比较简单,软件下载实现后,利用 hdiutil来模仿用户手动装置流程,用户重启即可实现装置。 Windows:Windows 下因为环境特殊性,须要下载安装包后,通过主过程主动关上装置界面,疏导用户进行手动一步一步装置。 其实这种计划很好的满足的咱们晚期的性能迭代,然而随着用户量上涨,也遇到了很多问题。 比方: 每次降级带宽耗费微小:对于每次装置每个 UV 都有近 100M 的下载,每次推送版本时,都会遇到 OSS 流量告警,这背地都对应着老本;装置体验差:Windows 下因为每次降级简直都是一次新的装置流程,所以体验也是比拟差的,常常收到用户吐槽。所以咱们就调研了一种增量更新的计划,一个 Electron 程序包含 Electron 外围包以及业务代码,其实每次变更的仅仅是业务代码,所以实践上每次更新只须要增量更新业务代码即可。 ...

September 21, 2022 · 1 min · jiezi

使用nwvue开发一个桌面应用程序Mac

环境准备nodejsnwjsvue-cli3vue我在开发nw app时,将它看作一个前后端分离的web项目,后端服务没有一一实现,通过自己搭建的mock服务器模拟接口数据,所以这边对后端的环境依赖不做介绍,如果需要使用的话,可以根据个人的情况来实现后台API,如java、nodejs原生、express、koa等都可以。 安装nwnodejs的安装就不介绍了,这里介绍一下nw,nw就是node-webkit的缩写,意味着他是一个基于nodejs开发的一个webkit内核的浏览器壳,可以嵌套我们的web页面,让我们不需要兼容不同版本的浏览器,也可以通过内置的api调用一些底层方法。官网在这:https://nwjs.io/ 下载sdk版本: 添加别名下载完成后解压将文件下的nwjs.app复制到应用程序目录下,然后添加别名,让我们调试程序更加方便 vim ~/.bash_profile新增一句: alias nw="/Applications/nwjs.app/Contents/MacOS/nwjs"然后命令行输入: nw如果启动了nw窗口如下,表示成功了: 效果展示本次简单的模拟一个微信页面工程目录如下: 可以看出就是一个简单的vue工程,我们需要在package.json中修改一些配置: { "main": "localhost:8080/", "node-remote": "http://localhost:8080/", "chromium-args": "--load-extension='./node_modules/nw-vue-devtools/extension'", "window": { "width": 830, // 窗口的初始宽度 "height": 580, // 窗口的初始高度 "frame": false // 不显示默认边框 },}其他的页面就根普通的vue工程一样的开发就可以了。如果需要使用nwjs的api,则需要将node-remote开启: { "node-remote": "http://localhost:8080/",}使用时如下,如应用的最大化最小化组件: <script> import img from '@/assets/avatar.png' const win = nw.Window.get() export default { data () { return { now: 1 } }, computed: { avatar () { return img } }, methods: { close () { nw.App.quit() }, min () { win.minimize() }, max () { win.toggleFullscreen() }, to (num) { this.now = num } } }</script>代码编写完成后,在项目的根目录执行: ...

June 24, 2019 · 1 min · jiezi

学起来Flutter将支持桌面应用开发

英文原文 Flutter团队正在扩展Flutter,支持创建macOS、Windows和Linux应用程序。从长远来看,这项工作将提供一个完全继承的解决方案,flutter create,flutter run 和 flutter build 在桌面平台开发上的表现将和现在的移动平台开发中一样,但是目前这项工作还在进行中。 当前的状况下面提供了平台状况的高级概述。 详细信息请参阅 源码仓库 重要提示:Flutter桌面API仍处于早期阶段,如有更改,恕不另行通知。不会提供API或ABI的向后兼容性。Flutter更新之后,所有使用了Flutter的项目的代码都需要做更新并且重新编译。macOS系统这是最成熟的桌面平台(出于一些原因,它非常接近于我们已经支持的iOS)。 桌面版中以Flutter开头的类与iOS通用,所以应该基本稳定。以FLE开头的类仍处于早期阶段。 Windows系统当前的 Windows shell 只是 GLFW 占位符, 以便与前期实验. 未来它将被 Win32 或者 UWP shell 替代,因为Win32 或者 UWP shell 允许在Flutter应用程序中嵌入view-level。 预计,最终版本的shell APIs和当前实现的方式完全不同。 Linux和Windows一样,当前 Linux shell 只是 GLFW 占位符。我们想创建一个库,让开发可以任何部分嵌入Flutter,无论你使用GTK+, Qt, wxWidgets, Motif, 还是其他任意工具包。但是我们还没有确定一个好方法。 插件所有平台都支持编写插件(例如 flutter-desktop-embedding 这些插件),但是,目前依然很少有插件实际上具有桌面支持。 工具Flutter支持桌面的工具开发还在进行中。要使用任何桌面支持工具(例如用flutter devices列出主机)目前必须满足两点: 你不能使用稳定的Flutter channel。因为桌面支持还没有被认为是稳定的和适合生产环境的你必须设置ENABLE_FLUTTER_DESKTOP环境变量为true。这是为了避免在指定长期解决方案时影响现有的移动开发工作流程(参见:#30724。预构建Shell库默认情况下,桌面库未下载,可以通过运行运行flutter precache下载,根据你的你的操作系统带上参数 --linux,--macos或 --windows。 C++ WrapperWindows和Linux库提供C语言API。为了更容易使用他们,可以使用C++包装器,将其构建到应用程序,中以便与提供更高级的API调用。上面提到的flutter precache命令会将这个包装器的源码下载到与该库同目录下的cpp_client_wrapper文件夹中。 使用Shells由于目前没有桌面shell工具的支持,你需要自己写一个应用的运行工具,并且在库里链接,就像任何你使用的插件那样。这将需要做一些你熟悉所使用的桌面系统的原生开发。如果你在桌面系统系统开发方面没有经验,你需要等到flutter桌面开发工具支持可用。 所以,使用Shells请参阅你所使用的操作系统的库的头。将来会补充更多的文档。至于现在,可以参考flutter-desktop-embedding示例,也许会有启发。 另外,你的Flutter桌面应用程序还需要bundle Flutter assets(由flutter build bundle创建)。在Windows和Linux你将还会需要Flutter引擎的ICU数据。(在你的Flutter目录中下的bin/cache/artifacts/engine查找icudtl.dat) macOS 注意目前你必须在XIB中设置FLEView,而不是在代码中设置(以后会改)。如下: 拖入一个OpenGL视图修改类型为FLEView.选中Double Buffer选项. 如果你的视图没有被绘制出来,可能是因为忘记这个步骤.选中Hi-Res Backing支持选项. 如果在高DPI显示器上只显示部分程序,那么可能是因为忘记这个步骤。

April 28, 2019 · 1 min · jiezi

Electron 跨平台应用开发入门

本文由 Deguang 发表于 码路-技术博客Tips:Electron 介绍Electron 环境搭建进程通信调用系统 APIWrite once, run anywhere. Sun 公司 Java 介绍词。端的跨平台实现方案有哪些?Web(浏览器)移动端设备:Hybrid(混合)、React Native、Weex、Flutter桌面端:NW.js、Electron、Flutter(~1.0)Electron1. 什么是 ElectronElectron 是由 Github 开发,用 HTML,CSS 和 JavaScript 来构建跨平台桌面应用程序的一个开源库。 Electron 通过将 Chromium 和 Node.js 合并到同一个运行时环境中,并将其打包为 Mac,Windows 和 Linux 系统下的应用来实现这一目的。开发者只要使用 Web 技术完成业务部分即可,这就是对前端开发者最友好的地方。2. Electron 应用结构Elctron 应用运行时,分为 主进程 和 渲染进程。主进程 (Main Process)package.json 中定义的 main 脚本运行的进程,被定义为 主进程,一个 Electron 应用有且只有一个主进程。主进程可以创建 Web 页面窗口,并传入 URL 加载网页作为图形界面。渲染进程 (Renderer Process)Electron 的每个页面都有它自己的进程,叫做渲染进程。每一个渲染进程都是独立的,只关心它所运行的页面;进程通信(IPC, Inter-Process Comminication)Electron 不允许渲染进程调用系统 GUI 的原生 API,例如打开文件选择之类的系统操作,这种对系统 API 的调用只允许存在与主进程中。渲染进程,也就是页面调用 系统 API,需要由主进程担任桥梁的作用,来完成操作并得到返回结果。这里有两种方式可以实现进程通信:使用 ipcRenderer 和 ipcMain 模块通信ipcRendereripcRenderer 是从渲染进程到主进程的异步通信,可以使用它提供的一些方法从渲染进程发送同步或异步的消息ipcMainipcMain 是从主进程到渲染进程的异步通信,处理从渲染进程发送出来的异步和同步信息,// renderer processconst {ipcRenderer} = requier(’electron’)// asyncipcRenderer.on(‘PICK_FILE_CALLBACK’, (event, arg) => { console.log(arg) // files})ipcRenderer.send(‘PICK_FILE’)// syncipcRenderer.sendSync(‘PICK_FILE_SYNC’) // files// main processconst {ipcMain} = require(’electron’)// asyncipcMain.on(‘PICK_FILE’, (event, data) => { // do something … event.sender.send(‘PICK_FILE_CALLBACK’, ‘files’)})// syncipcMain.on(‘PICK_FILE_SYNC’, (event, data) => { // do something event.returnValue = ‘files’})more: ipcRenderer、 ipcMainipcMain、ipcRenderer 又是什么?ipcMain 和 ipcRenderer 都是 EventEmitter 类的一个实例。 EventEmitter 类是由 NodeJS 中的 events 模块定义、导出的。EventEmitter 类是 NodeJS 事件的基础,实现了事件模型需要的接口, 包括 addListener,removeListener, emit 及其它工具方法. 同原生 JavaScript 事件类似, 采用了发布/订阅(观察者)的方式, 使用内部 _events 列表来记录注册的事件处理器。使用 remote 进行 RPC 通信在渲染进程中使用主系统模块。// 渲染进程中const { BrowserWindow } = require(’electron’).remotelet win = new BrowserWindow({width: 600, height: 400})win.loadURL(‘https://github.com’)remote 对象remote 模块返回的对象表示主进程中的一个对象,调用远程对象、远程函数时,相当于发送同步进程信息。let win = new BrowserWindow({width: 600, height: 400}),相当于在主进程中创建了一个 BrowserWindow 对象,然后在渲染进程中返回了相应的远程对象。more: remote3. APIElectron APIElectron 提供了大量 API 以优化桌面应用开发体验。Electron API 都有指派进程类型(文档中标记):只能用于主进程(BrowserWindow)、只能用于渲染进程(remote) 和 两种进程中均可使用。Node.js APIElectron 同时在主进程和渲染进程中对Node.js 暴露了所有的接口原生 API// Native APIconst fs = require(‘fs’)fs.readFile(…)第三方 Node.js 模块npm install -save axiosconst axios = require(‘axios’)axios.interceptors.request(…)4. 第一个 Electron 应用环境依赖:Node.js && Npm (Yarn)Node.js: 安装参考 https://nodejs.orgYarn: npm i -g yarn推荐使用 yarn 构建 Electron,使用 npm 大概率出现依赖安装失败的情况起步0.0.1 创建 demo 目录,执行 npm init,配置启动脚本 “start”: “electron .”// package.json{ “name”: “demo”, “version”: “1.0.0”, “description”: “”, “main”: “main.js”, “scripts”: { “start”: “electron .” }, “author”: “”, “license”: “ISC”}0.0.2 创建 main.js && index.html➜ demo tree.├── index.html├── main.js└── package.json0.0.3 安装 Electronnpm install –save-dev electron0.0.4 创建一个窗口:// main.jsconst { app, BrowserWindow } = require(’electron’)let winfunction createWindow() { win = new BrowserWindow({ width: 600, height: 400}) win.loadFile(‘index.html’)}app.on(‘ready’, createWindow)<!– index.html –><body> <h1>Hello, Electron.</h1> Node.js: <script>document.write(process.versions.node)</script>; Chrome: <script>document.write(process.versions.chrome)</script>; Electron: <script>document.write(process.versions.electron)</script>.</body>0.0.5 执行 npm run start,即可打开一个窗口,显示 Hello, Electron.。0.0.6 更新main.js,尝试更多功能:// main.jsconst { app, BrowserWindow } = require(’electron’)let winfunction createWindow() { win = new BrowserWindow({ width: 600, height: 400}) win.loadFile(‘index.html’) win.webContents.openDevTools() win.on(‘close’, () => { // 关闭窗口、清空 win 对象 win = null }) app.on(‘window-all-closed’, () => { // macOS 应用通常关闭窗口还是保活的,只有使用 cmd + q 强制退出 if (process.platform !== ‘darwin’) { app.quit() } }) app.on(‘activate’, () => { // macOS 点击 dock 图标并且没有其他窗口打开时,重新创建一个窗口 if (win === null) { createWindow() } })}app.on(‘ready’, createWindow)0.0.7 重新执行 npm run start 启动应用。more: 更多 Electron API 使用,可以尝试官方 electron-api-demos打包应用electron-packager# 安装 electron-packagernpm install electron-packager –save-dev# 基本命令electron-packager <sourcedir> <appname> –platform=<platform> –arch=<arch> [optional flags…]# 例如:# electron-packager ./ demo –out ./dist –app-version 1.0.0 –overwrite参数说明:sourcedir:项目路径appname:应用名称platform:构建平台的应用(Windows、Mac 还是 Linux)arch:x86、 x64 还是两个架构都用optional options:可选选项为了更方便的构建,在 package.json 增加构建脚本"package": “electron-packager ./ demo –out ./dist –app-version 1.0.0 –overwrite"执行 npm run packagemore: electron-packagerelectron-builder# 安装 electron-buildernpm install electron-builder –save-dev// package.json 增加如下配置"build”: { “productName”: “xxx”, “appId”: “com.xxx.xxx”},“scripts”: { “dist”: “electron-builder”}执行 npm run dist,完成打包more: electron-builderps:mac 跨平台 打包 win32 需要 wine客户端数据库Electron 应用可以在本地使用客户端数据库,来维护本地数据,进行相应的 CURD 操作,下面是常用的一些轻量级数据库:nedbsqliteRxdb…参考文档:Electron 文档 ...

March 29, 2019 · 3 min · jiezi

使用Rust + Electron开发跨平台桌面应用 ( 一 )

前言近段时间学习了Rust,一直想着做点什么东西深入学习,因为是刚学习,很多地方都不熟悉,所以也就不能拿它来做编译器这些,至于web开发,实际上我并不建议拿这个来学习一门语言,大概有几个方面,一是web开发的套路无非也就那么几个,对学习一门语言并不会有多大的帮助。二是web开发大多已经被封装了很多东西,对学习语言本身其实不利,真的要深入学习的话还是建议从语言本身出发,尽量不要用封装好的东西,当然,标准库除外。为什么是Rust + Electron原因其实很简单,我不想做太复杂的东西,因为大部分的精力还是要放在工作上,其次是希望做一个我日常能用的东西,当然现在还没想好,可能是个音乐播放器,也可能是个天气展示的app,这样我就可以每天使用了,这也会更有动力促使我开发好它。Rust 和 Electron 想必就不用我多介绍了吧,至于为什么是这个组合可以查看知乎的这个问题,我赞同的是的方案是使用 C/Cpp/Rust 开发的核心 + Electron / Qt 开发界面本期目标本期的目标非常简单,将Rust 和 Electron结合起来,使用Rust获取电脑cpu核数,Electron将数据绘制在界面上展示。初始化Electron项目Electron项目的初始化我用的工具是electron-forge,首先我们按照electron-forge的官网介绍来npm install -g electron-forgeelectron-forge init my-new-projectcd my-new-projectelectron-forge start解释一下,首先我们要安装electron-forge,这是一个脚手架工具,类似于Vue-cli。然后我们初始化一个项目,项目名称为my-new-project。需要注意的是这初始化的过程中electron-forge会构建package.json, 然后下载依赖,我第一次下载依赖的时候卡在了electron-runtime,第二次重试的时候就好了。第二个是electron-forge中的依赖会对Python版本有要求,只能要求Python2,这里要注意的一点是,我十分不建议使用pyenv来控制Python版本,会出现以下错误,我的解决方式是使用virtualenv新建一个Python2 的环境。Fatal Python error: PyThreadState_Get: no current thread现在我们来看一下项目结构整个项目结构非常简单,src中是我们的源文件,index.html是界面文件,index.js是界面逻辑文件,大家打开index.js就可以看到一段自动生成的代码,主要是创建了一个app,以及监听app的活动,需要注意到的是其中对mac的处理。app.on(‘window-all-closed’, () => { // On OS X it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q if (process.platform !== ‘darwin’) { app.quit(); }});好了,现在让我们把项目跑起来,在项目目录下执行electron-forge start命令,稍等一会我们就可以看到界面运行起来了初始化Rust项目在开发之前我们要知道,JS是无法直接运行Rust的,就像JS无法直接运行C++一样。所以我们需要将Rust打包成Node模块提供给JS进行调用。所以我们会使用neon来做到这件事,neon的github地址在这里首先我们需要安装neon,注意,neon对python版本也是有要求的,如果你是mac,python版本必须要是Python2.7,不支持Python3,同样,这里也会出现上面说过的no current thread问题,所以我们在开发时最好用virtualenv新建一个Python2的环境。安装完neon之后我们执行neon new thread-count,新建一个项目。看一下项目结构lib是我们最终的导出文件,提供给electron进行调用,native下则是我们的rust代码,注意,这里的入口文件是native/src/lib.rs,因为我们建立的是一个库而不是一个可执行的应用程序。让我们先编译项目,在文件目录下执行neon build –release命令。让我们进入终端调用一下项目试试:成功!到现在我们就成功的将rust写的代码封装成node库,使得JS可以进行调用了,接下来我们回到上面说过的,将rust的功能更改为获取CPU核数,然后将它封装成一个函数并进行导出。首先我们要修改Cargo.toml,在[dependencies]下增加一个num_cpus = “1.4.0"的依赖项,然后修改native/src/lib.rs文件如下#[macro_use]extern crate neon;use neon::prelude::*;fn thread_count(mut cx: FunctionContext) -> JsResult<JsNumber> { Ok(cx.number(num_cpus::get() as f64))}register_module!(mut cx, { cx.export_function(“thread_count”, thread_count)});修改lib/index.js如下:var addon = require(’../native’);module.exports = addon.thread_count;然后我们再进行编译,执行neon build –release命令,然后再进入终端调用这个函数试试成功啦,至此,我们就成功的将rust代码封装给JS进行了调用。需要注意的是编译rust的node版本需要与运行electron的node版本一致,否则会出现无法调用的情况。好了,到此第一期就结束了,代码我会抽空整理到github,以供有需要的同学查看。最后看一下效果图吧ps: 现在Rust的各项工具和库都不是很成熟,所以大家再实践过程中会遇到各种问题,都可以评论到下面大家一起讨论。 ...

January 30, 2019 · 1 min · jiezi