# electron-vue **Repository Path**: yhrmsk/electron-vue ## Basic Information - **Project Name**: electron-vue - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-06-09 - **Last Updated**: 2021-07-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # electron ## Project setup ``` npm install ``` ### Compiles and hot-reloads for development ``` npm run serve ``` ### Compiles and minifies for production ``` npm run build ``` ### Lints and fixes files ``` npm run lint ``` ### Customize configuration See [Configuration Reference](https://cli.vuejs.org/config/). 1 创建项目 1.1 使用cnpm加速下载 npm有时下载速度很慢,可以安装cnpm,从国内淘宝镜像下载,执行以下命令: npm install -g cnpm --registry=https://registry.npm.taobao.org 以后npm直接替换成cnpm使用。 1.2 为什么不使用SimulatedGREG/electron-vue SimulatedGREG/electron-vue已经很久没有更新了,而且其生成的工程结构并不是vue-cli3。所以放弃使用。 1.3 安装/升级vue-cli3 先执行以下命令,确认下本地安装的vue-cli版本: vue -V 在写本文时,我使用的是3.8.4版本。 如果本地使用的是vue-cli2.x或者更早版本,可先卸载: cnpm uninstall vue-cli -g ※注:vue-cli3使用了新的npm包名,与旧版本不一样。 如果还没有安装vue-cli3,先执行以下命令安装: cnpm install @vue/cli -g 如果你已安装vue-cli3,但不是最新版本,可执行以下命令升级: (我这里使用cnpm并没有完成升级,所以使用了npm) npm update @vue/cli -g 1.4 创建vue项目 找个喜欢的目录,执行以下命令,创建vue项目: (这里把项目名称定为electron-vue-demo) vue create electron-vue-demo 会出现以下选项(如果熟悉此步骤可跳过本节内容): Vue CLI v3.8.4 ? Please pick a preset: (Use arrow keys) default (babel, eslint) > Manually select features 选择“Manually select features” (自定义安装)。 ? Check the features needed for your project: (Press to select, to t oggle all, to invert selection) ❯◉ Babel ◯ TypeScript ◯ Progressive Web App (PWA) Support ◉ Router ◉ Vuex ◉ CSS Pre-processors ◉ Linter / Formatter ◯ Unit Testing ◯ E2E Testing 这里选择了常用的模块,请根据实际需求进行选择。 ? 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/user/id ,比较好看。但是还需要后台配置支持。 这里我们选择“n”。 ? 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”。 ? 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”,标准配置。 ? Pick additional lint features: (Press to select, to toggle all, to invert selection) ❯◉ Lint on save ◯ Lint and fix on commit Line on save表示在保存代码的时候,进行格式检查。 Lint and fix on commit表示在git commit的时候自动纠正格式。 这里只选择“Lint on save”。 ? 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”。 ? Save this as a preset for future projects? (y/N) N 是否为以后的项目保留这些设置?选择“N”。 然后耐心等待项目安装完成。 1.5 自动安装electron ※注:此过程可能需要科学上网,由于直接从国外镜像下载较慢,可能需要等待很漫长的时间。如果你对自己的网速没有超强自信,请跳过本节,前往1.6小节手动安装。 进入到项目根目录,执行: vue add electron-builder 在安装过程中,很可能会卡在这一步不动了: node ./download-chromedriver.js 没关系,我们先强制结束掉。再执行一次vue add electron-builder,然后就可以顺利通过了。 接下来出现配置选项: ? Choose Electron Version (Use arrow keys) ^3.0.0 ^4.0.0 ❯ ^5.0.0 选择Electron版本。选择 “^5.0.0”。 然后耐心等待安装完成。如果中间出现错误中断了,请重复此步骤。 安装完成后会自动在src目录下生成background.js并修改了package.json。 ※注:由于网络原因,如果中间出现过中断失败,再次重新安装可能会很快完成,但实际上electron可能并未安装完全。建议完成以上步骤后,直接删除项目根目录的node_modules/,并且执行cnpm install,从国内镜像重新安装所有依赖包。 1.6 手动安装electron ※注:如果已经通过1.5章节的操作,请直接跳过本小节。 修改package.json,添加以下7行: ... "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint", + "electron:build": "vue-cli-service electron:build", + "electron:serve": "vue-cli-service electron:serve", + "postinstall": "electron-builder install-app-deps", + "postuninstall": "electron-builder install-app-deps" }, + "main": "background.js", "dependencies": { "core-js": "^2.6.5", "vue": "^2.6.6", "vue-router": "^3.0.1", "vuex": "^3.0.1" }, "devDependencies": { "@vue/cli-plugin-babel": "^3.8.0", "@vue/cli-plugin-eslint": "^3.8.0", "@vue/cli-service": "^3.8.0", "@vue/eslint-config-standard": "^4.0.0", "babel-eslint": "^10.0.1", + "electron": "^5.0.6", "eslint": "^5.16.0", "eslint-plugin-vue": "^5.0.0", "stylus": "^0.54.5", "stylus-loader": "^3.0.2", + "vue-cli-plugin-electron-builder": "^1.3.5", "vue-template-compiler": "^2.6.10" }, ... 新建src/background.js 在src目录下新建background.js,复制以下代码: '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: 800, height: 600, webPreferences: { 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.5小节使用自动化方式安装后生成的。 安装依赖包 在项目根目录执行,安装全部依赖包: cnpm install 如果安装过程中报错:Error: post install error, please remove node_modules before retry!可以忽略,不影响后续使用。 1.7 编译并启动APP 执行以下命令,开始编译APP,并启动开发环境APP: npm run electron:serve 首次启动可能会等待很久,出现以下信息: 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 ... 这是因为在请求安装vuejs devtools插件。需要科学上网才能安装成功。如果不能科学上网也没关系,耐心等待5次请求失败后会自动跳过。 编译成功后,就会出现开发环境的APP了。 windows启动界面 macOS启动界面 2 配置项目 2.1 配置ESLint代码格式检查工具 ESlint可以高效的检查代码格式,让参与项目的所有工程师都能保持统一的代码风格。其检测精度甚至可以精确到是否多一个空格或者少一个空格。代码格式的统一对提高团队的协同开发效率有很大的帮助,特别是对有代码洁癖的工程师。 在项目根目录下创建.eslintrc.js (注意文件名前面有个“.”) 请粘贴以下代码: 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 [*.{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检测 以上是我常用的配置。如果你有更多的配置需求,可参阅: https://cloud.tencent.com/developer/doc/1078 2.2 配置vue 在项目根目录下创建vue.config.js,粘贴以下代码: 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 页面。 具体可参阅官方文档: https://electronjs.org/docs/tutorial/application-architecture#main-and-renderer-processes 3.2 APP窗口大小 修改background.js: function createWindow () { // Create the browser window. win = new BrowserWindow({ M width: 1200, M height: 620, webPreferences: { nodeIntegration: true } }) 3.3 取消跨域限制 修改background.js: function createWindow () { // Create the browser window. win = new BrowserWindow({ width: 1200, height: 620, webPreferences: { + webSecurity: false, nodeIntegration: true } }) 3.4 取消菜单栏 在我们生成的桌面APP中,我们可以看到默认的菜单栏。 在windows中,菜单栏在APP窗口内的顶部;在macOS中,菜单栏位于电脑屏幕顶部。 为了方便项目将来也能直接生成纯web应用,尽量把APP的全部功能都做到渲染进程里,这里我们取消菜单栏。 由于macOS的特殊性,顶部菜单栏无法删除,所以我们针对macOS特殊处理,把菜单栏只保留“关于”和“退出”。 修改background.js: M import { app, protocol, BrowserWindow, Menu } from 'electron' ... function createWindow () { ... 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' + }] + }] + let menu = Menu.buildFromTemplate(template) + Menu.setApplicationMenu(menu) + } else { + // windows及linux系统 + Menu.setApplicationMenu(null) + } + } windows界面菜单栏已消失 macOS界面菜单栏已简化 macOS菜单栏名称label的“App Demo”会在build版本生效,dev版本会显示“Electron”。 更多关于菜单栏设置,请参阅:https://electronjs.org/docs/api/menu 3.5 设置APP窗口图标 准备windows和macOS两版图标。 windows: app.ico 最小尺寸:256x256 macOS: app.png或app.icns 最小尺寸:512x512 (后续4.1章节用到) 把图标文件放到public/目录下,项目结构如下: |- /dist_electron (略) |- /public |- app.icns <-- 本教程暂时未使用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窗口应用图标: function createWindow () { // Create the browser window. win = new BrowserWindow({ width: 1200, height: 620, webPreferences: { nodeIntegration: true }, + // eslint-disable-next-line no-undef + icon: `${__static}/app.ico` }) 这里的${__static}对应的是public目录 现在,Windows系统上可以看到开发环境的APP窗口图标已经生效了。 macOS图标请参照4.1章节,并且需要在build后才能生效。 windows APP窗口及任务栏图标 3.6 设置APP窗口标题栏名称 修改public/index.html: 我们把electron-vue-demo改为App Demo。 M App Demo 4 build最终产品 这里我们已经集成了electron-builder工具,官方文档可以参阅:https://www.electron.build/ 4.1 设置APP及安装包图标 在3.5章节,我们的图标生效于运行APP的窗口。本小节将生效于最终生成的可执行文件和安装包图标。需要准备的图标文件请回看3.5章节。 修改vue.config.js chainWebpack: config => {...}, + pluginOptions: { + electronBuilder: { + builderOptions: { + win: { + icon: './public/app.ico' + }, + mac: { + icon: './public/app.png' + } + } + } + } ... 运行build后的mac版本,可以看到图标都已生效了。 macOS APP Dock图标 安装包和可执行文件的截图就不再放出了。 更多详细介绍,可参阅: https://www.electron.build/icons.html 4.2 设置APP名称 APP名称包括安装包中APP的名称、可执行文件的文件名。 修改vue.config.js: pluginOptions: { electronBuilder: { builderOptions: { win: { icon: './public/app.ico' }, mac: { icon: './public/app.png' }, + productName: 'AppDemo' } } } 4.3 打包APP 执行以下命令,可以build工程: npm run electron:build 最终在dist_electron目录下生成build后的产品。 windows版本 目录如下: /dist_electron |- /bundled (略) |- /win-unpacked <-- 绿色版 (略) |- AppDemo Setup 0.1.0.exe <-- 安装文件 |- AppDemo Setup 0.1.0.exe.blockmap |- builder-effective-config.yaml |- index.js 这里其实就win-unpacked和AppDemo Setup 0.1.0.exe有用。 ※注:在32位环境下打包生成的是32位APP,在64位环境下打包生成的是64位APP。 mac版本 /dist_electron |- /bundled (略) |- /mac |- AppDemo <-- 绿色版 |- AppDemo-0.1.0-mac.zip <-- 绿色版压缩包 |- AppDemo-0.1.0-mac.dmg <-- 安装包 |- AppDemo-0.1.0.dmg.blockmap |- builder-effective-config.yaml |- index.js 4.4 可能出现的错误 我曾经在Win10 64bit 1809版本上build失败,保存信息中提示: Error output: Can't open output file Error - aborting creation process 与此同时,在win7和win10 1803版本build正常。经研究,无果。后来把windows升级到1903版本,问题解决了。应该是vue-cli-plugin-electron-builder插件与系统之间的问题导致。 5 关于项目开发的一些经验 在完成以上章节后,后面基本可以完全按照web方式开发了。这里简单分享下一些小经验。 5.1 src目录结构参考 /src |- /common |- /fonts |- /images |- /js |- api |- libs |- /stylus |- /components |- /base |- /modules |- /moduleA |- /moduleB ... |- /views |- App.vue |- background.js |- main.js |- router.js |- store.js 下面对部分重要目录简要说明: common/ - 项目公用库 common/fonts/ - 字体文件 common/images/ - 公用图片 common/js/ - 公用js目录 common/js/api/ - 把api按类别封装成函数,并export出去,减少业务逻辑中的重复代码 common/js/lib/ - 存放一些公用函数库、定义的常量库等 common/stylus/ - Stylus样式文件 components/ - vue组件目录 component/base/ - vue基础组件,例如自定义的CheckBox、日期选择器、Dialog、Toaster、分页组件等 component/modules/ - vue模块 views/ - vue页面 5.2 换肤功能的实现 很多项目都有实时换肤的需求,在实际开发中,虽然我们使用了Sass、Less、Stylus等高端样式工具,但最终经过编译还是要回归到最原始的CSS。换肤的本质还是实时替换皮肤样式文件。 失败案例 以Stylus为例,抽象出皮肤文件skin.styl: $color-bg = #fff $color-text = #333 在业务样式中引用: @import 'skin.styl' body background: $color-bg color: $color-text 当经过编译后,生成的css为: body {background: #fff; color: #333;} 样式已经写死了,无法换肤。 那么应该怎么做呢? 成功案例 项目根目录下的public目录是静态目录,也就是说在build最终产品的时候,它里面的文件将原封不动保留。所以,可以将皮肤文件放在这里。 |- /public + |- /skin + |- /skin01 + |- skin.css + |- /skin02 + |- skin.css |- app.icns |- app.ico |- app.png |- favicon.ico |- index.html 由于Electron的是基于chromium内核,所以不用担心代码的浏览器兼容问题。接下来就是发挥CSS3变量var(--*)的时候了。 public/skin/skin01/skin.css: :root { --color-bg: #fff; --color-text: #333; } public/skin/skin02/skin.css: :root { --color-bg: #263238; --color-text: #b2ccd6; } 修改src/App.vue: ... 在public/index.html引入皮肤样式,注意加上id="app-skin": + App Demo 篇幅有限,这里就不写通过js修改皮肤的代码了。通过调试工具手动修改skin的css路径,可看到换肤效果: skin01浅色皮肤 skin02深色皮肤 5.3 从Electron4.x升级到5.x 如果你之前用的是Electron4.x,升级到5.x很简单。 修改package.json中electron的版本(写作本文时是5.0.6): ... M "electron": "^5.0.6", ... 修改background.js中的这部分: // Scheme must be registered before the app is ready // Electron 4.x代码 // protocol.registerStandardSchemes(['app'], {secure: true}) // Electron 5.x代码 protocol.registerSchemesAsPrivileged([{ scheme: 'app', privileges: { secure: true, standard: true } }]) 然后执行,等待升级安装完成: cnpm install 以上就是 Electron5 + vue-cli3 的构建跨平台应用的全部分享内容。如果有其他问题,欢迎一起交流。 5.4 注册快捷键打开devTools 在Electron中打开devTools是通过主线程中调用win.webContents.openDevTools()实现的。以上教程仅在开发环境初始启动的时候打开devTools,但是一旦关闭就不能再打开了。下面讲一下怎么通过快捷键打开devTools。 修改background.js: ... M import { app, protocol, BrowserWindow, Menu, globalShortcut } from 'electron' ... 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()) } } // 在开发环境和生产环境均可通过快捷键打开devTools + globalShortcut.register('CommandOrControl+Shift+i', function () { + win.webContents.openDevTools() + }) createWindow() }) 在windows下,按Ctrl+Shift+i即可打开devTools 在macOS下,按Commond+Shift+i即可打开devTools 为什么没用F12?因为windows系统中,F12是系统保留快捷键,无法使用。官方原话是这么解释的: The F12 key is reserved for use by the debugger at all times, so it should not be registered as a hot key. Even when you are not debugging an application, F12 is reserved in case a kernel-mode debugger or a just-in-time debugger is resident. 但是可以使用Ctrl+F12,只要不单独使用F12就可以。 以上代码在开发环境和生产环境中均有效,为保证生产环境的安全,建议不要在生产环境中使用。放到上面的if语句中即可。