目录
一步一步搭建Electron+vue-cli3开发平台(亲测有效)
Electron是一个基于Chromium和 Node.js,可以使用 HTML、CSS和JavaScript构建跨平台应用的技术框架,兼容 Mac、Windows 和 Linux。虽然B/S是目前开发的主流,但是C/S仍然有很大的市场需求。受限于浏览器的沙盒限制
[^沙盒(英语:sandbox,又译为沙箱),计算机专业术语,在计算机安全领域中是一种安全机制,为运行中的程序提供的隔离环境。通常是作为一些来源不可信、具破坏力或无法判定程序意图的程序提供实验之用。]
网页应用无法满足某些场景下的使用需求,而桌面应用可以读写本地文件、调用更多系统资源,再加上Web开发的低成本、高效率的优势,这种方式越来越受到开发者的喜爱。
这篇博客一步一步教你如何使用Electron和vue-cli3,在完全保留vue开发web应用的习惯下,搭建桌面应用。
这篇博客不涉及Electron和vue的开发教程,仅以实现两者结合为目的,若要深入学习Electron和vue,请访问官方:
学习该框架之前,希望读者需要提前掌握以下技能:
HTML、CSS、JavaScript | Vue2.x | Stylus |
1.创建项目
1.1 安装Nodejs
Node.js 中文网下载安装
1.2 使用cnpm加速下载
npm有时***很慢,可以安装cnpm,从国内淘宝镜像下载,执行以下命令:
1 | $ npm install -g cnpm --registry=https: //registry.npm.taobao.org |
之后所有的npm命令都可以直接替换成cnpm使用了。
1.3 为什么不使用electron-vue搭建呢
Electron-vue官方中文文档
由于SimulatedGREG/electron-vue已经很久没有更新了,而且其生成的工程结构并不是vue-cli3。所以放弃使用。
1.4 安装/升级vue-cli3
- 先执行以下命令,确认下本地安装的vue-cli版本(如果自己的电脑没有安装过vue,跳过前两步到第三步吧!):
$ vue -V
或$ vue --version
在写本篇博客时,我的vue是2.9.6版本。
- 如果本地使用的是vue-cli2.x或者更早版本,可先执行下面命令全局卸载:
$ cnpm uninstall vue-cli -g
(1)vue-cli3使用了新的npm包名,与旧版本不一样。
(2)在vue2.9.6下执行上面命令,控制台输入vue -V
仍然有输出,解决方法:
a. 当然, 卸载命令还是要执行的: 全局卸载:npm uninstall vue-cli -g;
b. npmrc文件删除掉
c. 检索删除vue-cli文件夹
再试试: vue -V
,就不显示版本号了。
(3)可以忽略上述问题,直接安装最新版本的vue-cli3即可(亲测有效)。
$ cnpm install @vue/cli -g
$ cnpm update @vue/cli -g
1.5 创建vue项目
- 在指定目录下,打开终端,执行以下命令,创建vue项目:
$ vue create electron-vue-demo
这里的项目名称为electron-vue-demo(不能出现大写字母),可根据自己的具体项目改变。
- 创建命令执行后,在完成创建之前,会出现以下选项(如果熟悉此步骤可跳过本节内容):
1 2 3 4 | Vue CLI v4. 4.6 ? Please pick a preset: (Use arrow keys) default (babel, eslint) > Manually select features |
选择 Manually select features
(自定义安装)。
1 2 3 4 5 6 7 8 9 10 11 | ? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection) >(*) Babel ( ) TypeScript ( ) Progressive Web App (PWA) Support (*) Router (*) Vuex (*) CSS Pre-processors (*) Linter / Formatter ( ) Unit Testing ( ) E2E Testing |
这里选择了常用的模块,请根据实际需求进行选择。
1 2 | ? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) n |
如果选择了router,这里会询问是否使用history模式。
vue-router 默认使用hash模式(即通过url#hash来跳转页面),使用URL的hash来模拟一个完整的 URL,当URL改变时,页面不会重新加载。
如果使用history模式,URL就像正常的url,例如 http://yoursite.com/page
,比较好看。但是还需要后台配置支持。
这里我们选择n
。
1 2 3 4 5 6 | ? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default ): (Use arrow keys) Sass/SCSS (with dart-sass) Sass/SCSS (with node-sass) Less > Stylus |
选择CSS预处理模块,这里我们使用Stylus
。
1 2 3 4 5 | ? Pick a linter / formatter config: (Use arrow keys) ESLint with error prevention only ESLint + Airbnb config > ESLint + Standard config ESLint + Prettier |
选择ESLint代码格式检查工具的配置,选择ESLint + Standard config
,标准配置。
1 2 3 | ? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection) >(*) Lint on save ( ) Lint and fix on commit |
Line on save
表示在保存代码的时候,进行格式检查。
Lint and fix on commit
表示在$ git commit
的时候自动纠正格式。
这里只选择Lint on save
。
1 2 3 | ? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config files > In package .json |
这里问把 babel,postcss,eslint 这些配置文件放哪?
In dedicated config files
表示独立文件。
In package.json
表示放在package.json里。
这里选择In package.json
。
1 | ? Save this as a preset for future projects? (y/N) N |
是否为以后的项目保留这些设置?选择N
。
然后耐心等待项目安装完成。
1.6 自动安装Electron
$ cd electron-vue-demo
$ vue add electron-builder
electron-builder
是一个简单又强大的库。解决了打包这个棘手的问题,而且可以应对大部分的打包需求。
1 2 3 4 | ? Choose Electron Version (Use arrow keys) ^ 7.0 . 0 ^ 8.0 . 0 > ^ 9.0 . 0 |
选择Electron版本,我写这篇博客的时候,选项是上面3个版本,选择最新的即可。
然后耐心等待安装完成。
1.7 手动安装Electron
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | ... "scripts" : { "serve" : "vue-cli-service serve" , "build" : "vue-cli-service build" , "lint" : "vue-cli-service lint" , "electron:build" : "vue-cli-service electron:build" , // add "electron:serve" : "vue-cli-service electron:serve" , // add "postinstall" : "electron-builder install-app-deps" , // add "postuninstall" : "electron-builder install-app-deps" // add }, "main" : "background.js" , // add "dependencies" : { "core-js" : "^3.6.5" , "vue" : "^2.6.11" , "vue-router" : "^3.2.0" , "vuex" : "^3.4.0" }, "devDependencies" : { "@vue/cli-plugin-babel" : "~4.4.0" , "@vue/cli-plugin-eslint" : "~4.4.0" , "@vue/cli-plugin-router" : "~4.4.0" , "@vue/cli-plugin-vuex" : "~4.4.0" , "@vue/cli-service" : "~4.4.0" , "@vue/eslint-config-standard" : "^5.1.2" , "babel-eslint" : "^10.1.0" , "electron" : "^9.0.5" , // add "eslint" : "^6.7.2" , "eslint-plugin-import" : "^2.20.2" , "eslint-plugin-node" : "^11.1.0" , "eslint-plugin-promise" : "^4.2.1" , "eslint-plugin-standard" : "^4.0.0" , "eslint-plugin-vue" : "^6.2.2" , "stylus" : "^0.54.7" , "stylus-loader" : "^3.0.2" , "vue-cli-plugin-electron-builder" : "~2.0.0-rc.3" , // add "vue-template-compiler" : "^2.6.11" }, ... |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | 'use strict' import { app, protocol, BrowserWindow } from 'electron' import { createProtocol, installVueDevtools } from 'vue-cli-plugin-electron-builder/lib' const isDevelopment = process.env.NODE_ENV !== 'production' // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. let win // Scheme must be registered before the app is ready protocol.registerSchemesAsPrivileged([ { scheme: 'app' , privileges: { secure: true , standard: true } } ]) function createWindow() { // Create the browser window. win = new BrowserWindow({ width: 1000 , height: 600 , webPreferences: { // Use pluginOptions.nodeIntegration, leave this alone // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration // for more info // nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION webSecurity: false , nodeIntegration: true } }) if (process.env.WEBPACK_DEV_SERVER_URL) { // Load the url of the dev server if in development mode win.loadURL(process.env.WEBPACK_DEV_SERVER_URL) if (!process.env.IS_TEST) win.webContents.openDevTools() } else { createProtocol( 'app' ) // Load the index.html when not in development win.loadURL( 'app://./index.html' ) } win.on( 'closed' , () => { win = null }) } // Quit when all windows are closed. app.on( 'window-all-closed' , () => { // On macOS 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() } }) app.on( 'activate' , () => { // On macOS it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (win === null ) { createWindow() } }) // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.on( 'ready' , async () => { if (isDevelopment && !process.env.IS_TEST) { // Install Vue Devtools try { await installVueDevtools() } catch (e) { console.error( 'Vue Devtools failed to install:' , e.toString()) } } createWindow() }) // Exit cleanly on request from parent process in development mode. if (isDevelopment) { if (process.platform === 'win32' ) { process.on( 'message' , (data) => { if (data === 'graceful-exit' ) { app.quit() } }) } else { process.on( 'SIGTERM' , () => { app.quit() }) } } |
以上代码是1.6小节使用自动化方式安装后生成的。
在项目目录下执行以下命令,安装全部依赖包:
$ cnpm install
1.8 编译并启动APP
- 执行以下命令,开始编译APP,并启动开发环境APP:
$ npm run electron:serve
或$ yarn electron:serve
首次启动可能会等待很久,加载完后会自动打开APP,等待即可。
- 编译成功后,就会出现开发环境的APP了,如下图(Win10启动界面):
1 2 3 4 5 6 7 | INFO Launching Electron... Failed to fetch extension, trying 4 more times Failed to fetch extension, trying 3 more times Failed to fetch extension, trying 2 more times Failed to fetch extension, trying 1 more times Failed to fetch extension, trying 0 more times Vue Devtools failed to install: Error: net::ERR_CONNECTION_TIMED_OUT |
这是因为在请求安装vuejs devtools
插件。需要***才能安装成功。可以忽略上面的问题,耐心等待5次请求失败后会自动跳过,上面的成功界面即证实跳过依然编译成功。
依然有解决方案:
注释掉src/background.js
中的以下代码就行了:
1 | /* if (isDevelopment && !process.env.IS_TEST) { // Install Vue Devtools try { await installVueDevtools(); } catch (e) { console.error("Vue Devtools failed to install:", e.toString()); } } */ |
2.配置项目
2.1 配置ESLint代码格式检查工具
ESlint可以高效的检查代码格式,让参与项目的所有工程师都能保持统一的代码风格。其检测精度甚至可以精确到是否多一个空格或者少一个空格。代码格式的统一对提高团队的协同开发效率有很大的帮助,特别是对有代码洁癖的工程师。
在项目根目录下创建.eslintrc.js
(注意文件名前面有个.
)
请粘贴以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | module.exports = { root: true , env: { node: true }, 'extends' : [ 'plugin:vue/essential' , '@vue/standard' ], rules: { 'no-debugger' : process.env.NODE_ENV === 'production' ? 'error' : 'off' , // 不检测语句末尾的分号 'semi' : [ 'off' , 'always' ], // 强制缩进为2个空格 'indent' : [ 'error' , 2 ], // 关闭函数名称跟括号之间的空格检测 'space-before-function-paren' : 0 , // 忽略大括号内的空格 'object-curly-spacing' : 0 }, parserOptions: { parser: 'babel-eslint' } } |
这里说明下关于indent缩进的配置,要配合项目根目录下的.editorconfig
1 2 3 4 5 | [*.{js,jsx,ts,tsx,vue}] indent_style = space <--这里定义缩进类型是空格还是tab indent_size = 2 <--这里需要与.eslintrc.js的indent对应 trim_trailing_whitespace = true insert_final_newline = true |
.editorconfig
用于IDE自动格式化代码
.eslintrc.js
用于ESlint检测
更多配置可参阅 ESLint教程。
2.2 配置vue
在项目目录下创建vue.config.js,粘贴以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | const path = require( 'path' ); function resolve (dir) { return path.join(__dirname, dir); } module.exports = { publicPath: './' , devServer: { // can be overwritten by process.env.HOST host: '0.0.0.0' , port: 8080 }, chainWebpack: config => { config.resolve.alias .set( '@' , resolve( 'src' )) .set( 'src' , resolve( 'src' )) .set( 'common' , resolve( 'src/common' )) .set( 'components' , resolve( 'src/components' )); } }; |
devServer 用于设置开发环境的服务,这里表示在本地8080端口启动web服务。
chainWebpack 我们给项目目录起了"别名(alias)",在代码中,我们可以直接用“别名”访问资源,省去了每次输入完整相对路径的麻烦。
在js代码中可直接使用别名,例如:
@/common/js/xxx.js
等价于 src/common/js/xxx.js
common/js/xxx.js
等价于 src/common/js/xxx.js
在css或者html中使用别名,需要在别名前加“~”,例如:
@import "~common/stylus/font.styl"
3.项目基本设定
3.1 主进程和渲染进程简介
在开始下面的步骤之前,很有必要简单了解下Electron的应用架构。
Electron 运行 package.json 的 main 脚本(background.js)的进程被称为主进程。 在主进程中运行的脚本通过创建web页面来展示用户界面。 一个 Electron 应用总是有且只有一个主进程。
由于 Electron 使用了 Chromium 来展示 web 页面,所以 Chromium 的多进程架构也被使用到。 每个 Electron 中的 web 页面运行在它自己的渲染进程中。
在普通的浏览器中,web页面通常在一个沙盒环境中运行,不被允许去接触原生的资源。 然而 Electron 的用户在 Node.js 的 API 支持下可以在页面中和操作系统进行一些底层交互。
主进程使用 BrowserWindow 实例创建页面。 每个 BrowserWindow 实例都在自己的渲染进程里运行页面。 当一个 BrowserWindow 实例被销毁后,相应的渲染进程也会被终止。
主进程管理所有的web页面和它们对应的渲染进程。 每个渲染进程都是独立的,它只关心它所运行的 web 页面。
具体可参阅官方文档。
3.2 APP窗口大小
修改background.js:
1 2 3 4 5 6 7 8 9 10 | win = new BrowserWindow({ width: 1000 , height: 600 , webPreferences: { // Use pluginOptions.nodeIntegration, leave this alone // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration // for more info nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION } }) |
3.3 取消跨域限制
修改background.js:
1 2 3 4 5 6 7 8 9 10 11 12 | win = new BrowserWindow({ width: 1000 , height: 600 , webPreferences: { // Use pluginOptions.nodeIntegration, leave this alone // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration // for more info // nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION webSecurity: false , nodeIntegration: true } }) |
3.4 取消菜单栏
在我们生成的桌面APP中,我们可以看到默认的菜单栏。
在windows中,菜单栏在APP窗口内的顶部;在macOS中,菜单栏位于电脑屏幕顶部。
为了方便项目将来也能直接生成纯web应用,尽量把APP的全部功能都做到渲染进程里,这里我们取消菜单栏。
由于macOS的特殊性,顶部菜单栏无法删除,所以我们针对macOS特殊处理,把菜单栏只保留“关于”和“退出”。
修改background.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 'use strict' // 添加Menu组件 import { app, protocol, BrowserWindow, Menu } from 'electron' ... function createWindow() { // Create the browser window. ... win.on( 'closed' , () => { win = null }) createMenu() } // 设置菜单栏 function createMenu() { // darwin表示macOS,针对macOS的设置 if (process.platform === 'darwin' ) { const template = [{ label: 'App Demo' , submenu: [ {role: 'about' }, { role: 'quit' }] }] const menu = Menu.buildFromTemplate(template) Menu.setApplicationMenu(menu) } else { // windows及linux系统 Menu.setApplicationMenu( null ) } } |
改变后的APP界面样式:
更多关于菜单栏设置,请参阅:Electron官方API
3.5 设置APP窗口图标
准备windows和macOS两版图标:
Platform | File Type | Specification |
---|
Windows | app.ico | 最小尺寸:256x256 |
MacOS | app.png或app.icns | 最小尺寸:512x512 |
把图标文件放到public/
目录下,项目结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |- /dist_electron |... |- / public |- app.icns |- app.ico |- app.png |- favicon.ico |- index.html |- /src |... |- .editorconfig |- .eslintrc.js |- .gitignore |- babel.config.js |- package .json |- package -lock.json |- README.md |
可以顺便把favicon.ico
也修改一下,但是在桌面版APP上是用不到的。如果以后生成纯web项目才会用到。
修改background.js
,让APP窗口应用图标:
1 2 3 4 5 6 7 8 9 10 11 | function createWindow() { // Create the browser window. win = new BrowserWindow({ ... // eslint-disable-next-line no-undef icon: `${__static}/app.ico` }) ... } |
这里的${__static}
对应的是public目录
现在,Windows系统上可以看到开发环境的APP窗口图标已经生效了。
MacOS图标请参照4.1章节,并且需要在build后才能生效。
3.6 设置APP窗口标题栏名称
修改public/index.html
,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "utf-8" > <meta http-equiv= "X-UA-Compatible" content= "IE=edge" > <meta name= "viewport" content= "width=device-width,initial-scale=1.0" > <link rel= "icon" href= "<%= BASE_URL %>favicon.ico" > <!-- 把 '<%= htmlWebpackPlugin.options.title %>' 改为App Demo --> <title>My App</title> </head> <body> ... </body> </html> |
4.打包
这里我们已经集成了electron-builder
工具,可以参阅官方文档。