使用 Electron 和 Vue.js 构建跨平台桌面应用

友好速搭

使用 Electron 和 Vue.js 构建跨平台桌面应用

分类: 友技术 | 发表时间:2015.10.23 11:10
min
·
-
友技术 2015.10.23 11:10
min

开发 Theme Dev Tools

友好速搭店铺后台的代码编辑器,对于不经常改动代码的用户,毋容置疑用户体验很佳,但对于要开发主题的开发者,面对频繁的代码修改和测试就显得很笨拙了,所以我们想开发一个工具,帮助开发者更自由更方便的开发主题。起初的主题开发者是友好速搭的工作人员,所以第一个版本是一个命令行版本,后面友好速搭功能迭代得差不多,开发文档也完善了,就想做个 GUI 版本,让该工具的使用变得更简单便捷友好。

命令行版本

#Line

命令行版本设置

#Line Set

GUI 版本

#GUI

选择 Electron 和 Vue.js

Electron

我们想要快速的开发一款体验友好的跨平台应用,网上逛了一圈,对 Electron 和 NW.js 产生的兴趣多点,而最后选择了 Electron,总结有以下几点:

  • Java、C# 等技术,开发成本高、耗时;
  • RubyMotion 等技术:
    • 相对于第一点好很多,但还是不够,除非应用需要原生级别的性能需求;
    • 而且 RubyMotion 暂时不支持 Windows 和 Linux;
  • 我是前端小工匠,上手 Electron 非常快,学习成本低;
  • JavaScript/Node.js 的性能很不错,足够用了;
  • Electron 比 NW.js 更接近 Node.js 运行时,API 也更加底层。

Electron(原名 Atom-Shell)是 GitHub 随 Atom 一起开源的跨平台的利用 Web 技术(Node.js、JavaScript、HTML 和 CSS)开发桌面应用的框架。Atom 即构建在 Electron 之上。

#Logo

Electron 为用纯 JavaScript 创建跨平台桌面应用提供了运行时,它通过集成的 Node.js 运行 Main 文件创建一个主进程从而启动一个应用程序,主进程调用一个叫 BrowserWindow 的模块创建应用窗口并赋予它系统原生的 GUI 交互功能,每个应用窗口会运行自己的渲染进程,渲染进程会在窗口中渲染出 Web 页面,Web 页面由 Chromium 渲染,跨平台兼容性相当不错。

Vue.js

起初只是冲着 Vue.js 的优雅轻巧想学习了解一下,把官方的教程快速过了一遍,然后就做开发了,对于之前有接触过 AngularJS 这类 MVVM 前端框架的同学来说,上手学习成本非常低,初尝感觉不错,现在打算在下一个大项目继续尝试之。

在这我就不多介绍 Vue.js 了,总的来说就是优雅、轻巧、功能丰富、非常不错的轻量级 MVVM 框架,引用官方的介绍:

Vue.js 是一个用于创建 Web 交互界面的库,专注于 MVVM 模型的 ViewModel 层。它通过双向数据绑定把 View 层和 Model 层连接了起来。实际的 DOM 封装和输出格式都被抽象为了 Directives 和 Filters。Vue.js 的 API 设计深受 AngularJS、KnockoutJS、Ractive.js 和 Rivets.js 的影响。尽管有不少相似之处,但 Vue.js 能够在简约和功能之间的微妙平衡中体现出其独有的价值。

官方有中文文档,传送门:Vue.js

Electron 相关特点

程序入口

Electron 的程序入口是一个 JavaScript 脚本(package.json 指定),由 Node.js 来运行,你需要手动创建应用窗口,并通过相应的 API 加载 HTML 文件或 URL,你同时需要监听应用窗口事件以便决定何时退出应用。

API

  • Electron API,提供与系统交互的相关接口,方便直接使用 JavaScript 调用;
  • Node.js 提供 Node API,有开发 Node.js 程序一致的体验,还可以很方便使用各种包;
  • Chromium 提供的 Web API,使得开发 GUI 就像开发浏览器页面一样简单。

Main Process、Render Process 和进程间通信

Main Process 和 Render Process 是 Electron 中两个分开的概念,彼此是隔离的。开发者的 Main 文件运行环境就是 Main Process,而每个 BrowserWindow 实例则是一个个 Render Process。Main Process 创建并管理所有的 Render Process,每个 Render Process 都是独立的,只关心所运行的 Web 页面。

#Process

Main Process 和 Render Process 之间可以通过 IPC 模块或 Remote 模块(RPC)进行通信,实现互相访问资源并进行协调工作。

其他特点

  • 原生对话框(Dialog API)
  • 全局快捷键(Global-Shortcut)
  • 原生应用菜单(Menu)
  • 协议支持(Protocol)
  • 系统托盘(Tray)
  • 等等...

其他的大家看看官方文档,传送门:Electron

一些开发记录

第一次写桌面应用,边学边做,结构比较乱,后面更新再理清修改下 ^^

防止双开

没有找到 Electron 避免双开相关支持,只能自己搞了,这个比较重要,关键代码如下:

run =
  init: (callback) ->

    # Code...

    # 写入 PID 到文件
    if fs.existsSync(pathHandle DATA_DIR)
      try
        fs.writeFileSync pathHandle(DATA_DIR + '/app.pid'), process.pid
      catch e
        return callback 'Error - 创建 App PID 文件失败 - Error Msg: ' + e
    else
      return callback 'Error - DATA_DIR 文件夹不存在 - Error Msg: ' + e

    # Code...

    callback null
  close: ->
    if fs.existsSync pathHandle(DATA_DIR + '/app.pid')
      try
        fs.unlinkSync pathHandle(DATA_DIR + '/app.pid')
      catch e
        null
    do app.quit
  isRunning: ->
    mark = false
    if fs.existsSync pathHandle(DATA_DIR + '/app.pid')
      pid = fs.readFileSync pathHandle(DATA_DIR + '/app.pid'), 'utf8'
      try
        mark = process.kill pid, 0
      catch e
        null
    mark
  start: ->

    # Code...

应用菜单与基础快捷键

Mac 下如果未设置应用菜单,相关基础快捷键(CMD + C/V 等)将失效,必须设置应用菜单。

设置 Theme Dev Tools 应用菜单模板数据:

# Menu Template
menuTpl = [
  label: 'Theme Dev Tools'
  submenu: [
      label: 'About Theme Dev Tools'
      selector: 'orderFrontStandardAboutPanel:'
    ,
      type: 'separator'
    ,
      label: 'Services'
      submenu: []
    ,
      type: 'separator'
    ,
      label: 'Hide Theme Dev Tools'
      accelerator: 'Command+H'
      selector: 'hide:'
    ,
      label: 'Hide Others'
      accelerator: 'Command+Shift+H'
      selector: 'hideOtherApplications:'
    ,
      label: 'Show All'
      selector: 'unhideAllApplications:'
    ,
      type: 'separator'
    ,
      label: 'Quit'
      accelerator: 'Command+Q'
      selector: 'terminate:'
  ]
  ,
    label: 'Edit'
    submenu: [
      label: 'Undo'
      accelerator: 'Command+Z'
      selector: 'undo:'
    ,
      label: 'Redo'
      accelerator: 'Shift+Command+Z'
      selector: 'redo:'
    ,
      type: 'separator'
    ,
      label: 'Cut'
      accelerator: 'Command+X'
      selector: 'cut:'
    ,
      label: 'Copy'
      accelerator: 'Command+C'
      selector: 'copy:'
    ,
      label: 'Paste'
      accelerator: 'Command+V'
      selector: 'paste:'
    ,
      label: 'Select All'
      accelerator: 'Command+A'
      selector: 'selectAll:'
    ]
  ,
    label: 'View'
    submenu: [
      label: 'Reload'
      accelerator: 'Command+R'
      click: -> _mainWin.reload()
    ,
      label: 'Toggle DevTools'
      accelerator: 'Alt+Command+I'
      click: -> _mainWin.toggleDevTools()
    ]
  ,
    label: 'Window'
    submenu: [
      label: 'Minimize'
      accelerator: 'Command+M'
      selector: 'performMiniaturize:'
    ,
      label: 'Close'
      accelerator: 'Command+W'
      selector: 'performMiniaturize:'
      # selector: 'performClose:'    # 关闭应用
    ,
      type: 'separator'
    ,
      label: 'Bring All to Front'
      selector: 'arrangeInFront:'
    ]
  ,
    label: 'Help'
    submenu: []
]

设置应用菜单,一般放在 App 实例的 Ready 事件触发之后,创建应用窗口代码运行之前。

  # Mnue Set
  Menu.setApplicationMenu(Menu.buildFromTemplate menuTpl)

简单的 Debug 弹窗提示

方便初学者对 Main Process 运行时的调试,Render Process 运行时就直接使用console.log()和 Developer Tools。

# Main Debug
_mLog = (msg) ->
  if typeof msg != 'string'
    msg = JSON.stringify msg
  dialog.showMessageBox(_mainWin, {type: 'info', buttons: ['OK'], message: msg})

多平台路径格式化

经常用到,直接简单封装一下^^

## 路径处理
pathHandle = (arg) ->
  path.normalize path.resolve(DATA_DIR, arg)

系统窗口打开目录和浏览器打开链接

必须的功能,封装之。

加载 Shell 模块:

shell = require 'shell'

系统窗口打开目录:

ipc.on 'open_dir', (evt, path) ->
  if fs.existsSync(pathHandle path)
    shell.openItem path

浏览器打开链接:

ipc.on 'open_uri', (evt, path) ->
  shell.openExternal path

Require 设置

控制请求数,避免服务器请求量过大。

## Request 设置
requestOpt =
  pool:
    maxSockets: 1
    keepAlive: true
request    = request.defaults requestOpt
requestJar = do request.jar

Browsersync 设置

使用 Browsersync 做开发辅助,自动刷新页面,极力推荐,太方便了。

# Browsersync Services
bs =
  reload: null
  run: (callback) ->
    self = this
    if !bSync.active
      bSync.init
        proxy: current.cont.storeURI
        port : SERVICES_PORT
        open : false
        injectChanges: false
        ghostMode: false
        ui: false
      self.reload = bSync.reload
    callback null
  exit: ->
    self = this
    if bSync.active
      # bSync.exit()    # 会退出进程
      bSync.cleanup()
      self.reload = null

API LeakyBucket

友好速搭使用 Leaky Bucket(漏桶算法)来限制 API 的访问频率,所以封装了个简易的判断,请求前都会运行bucket.throttle()做检测,每个请求成功后都会记录leaky值(例如:35/40)到current.cont.leaky

bucket =
  throttle: (callback) ->
    if current.cont && current.cont.leaky
      if eval(current.cont.leaky) < 1
        callback null
      else
        callback '水桶满了'

推荐文章

Electron 在国内相对来说还是小众点,相关资料比较少,推荐下面的入门文章给大家看看了解一下。

结语

Electron 和 Vue.js,我觉得它俩都是优雅精巧又不失强大,大家可以去尝尝。

发现写技术文是一门脑力活,写的时候会去考证一些自己所说的技术、术语和语句用词什么的,对自己很有帮助,分享出去后相信也可以帮助一些同学了解相关的技术,以后要多写写,吹吹水^^


分享文章
友好速搭与你一起,共赢未来
联系客服
电话咨询
服务热线
0755-83051027
获取报价
QQ咨询
微信咨询
微信二维码
在线咨询

在线获取方案和报价,专人快速响应

所选城市 *

您的称呼 *

联系电话 *

网站类型 *

请选择
展示官网 电商网站 微信商城 小程序 其他类型

您希望我们为您提供哪些服务?(可多选)

B2C商城 跨境解决方案 B2B2C平台电商 网站备案 自营电商培训
获取报价

提交成功

你好, XXX女士/先生 ,你的需求已提交成功,后续会有专门的客户经理与你电话联系。谢谢!