学习日常工作用不到的东西--桌面应用
前言
每家公司使用的开发技术比较固化,如果仅学习在公司中用到的那些前端知识,构建的前端知识体系肯定不全面,不完整。许多属于前端领域的知识,自己的大脑会一片空白,如何破局 ?破局方法很简单,就是看到自己所属领域的圈子,别人玩得很溜,而自己却一问三不知的技能,刻意去练习。比如说笔者在自己的日常工作中,用不上桌面应用开发,看到圈子里时常有这样的文章冒出来,开发的东西挺实用。感觉要了解学习一下。现在我们进入今天的主题。
Electron简介
传统的桌面应用,Window平台使用C++(MFC/DuiLib/QT框架)开发,Mac平台使用Cocoa(Objective-C或者Swift)/ QT 框架开发。 Electron 可以让开发者使用Javascript构建跨平台(Windows/Linux/MacOS)的桌面应用。在Electron 官网可以看到许多流行的应用,都是使用Eletron开发的(比如说我们所熟识的VSCode),所以你想开发一款桌面应用的话,不用担心Electron无法满足你的场景需求。可以将Electron的能力拆分为 Chromium + NodeJS + NativeAPIs这三个模块的能力。
1)Chromium :为Electron提供了强大的界面开发能力,无需考虑兼容传统浏览器,利用强大的Web生态技术(原生的HTML+CSS+JavaScript以及各类Web页面开发框架)来开发桌面界面; 2)NodeJS:让Electron有了操作底层的能力(比如I/O和数据库操作,集成调用C++库完成一些底层操作),另外可以使用node社区功能极其丰富的npm包完成需求开发。 3)NativeAPIs:Native APIs让Electron有了桌面端的原生和跨平台的能力(比如说在不同平台有统一的原生界面,窗口、托盘、消息通知等)。
Electron就是通过整合这三个模块的能力,让开发跨平台桌面应用变的容易与高效。
熟悉开发流程
项目初始化
使用脚手架,创建一个项目,事半功倍。我们使用create-electron-vite
脚手架生成一个electron桌面应用项目。执行npm init vite
指令。选择框架要选择Others
, 这样在下一步选择变体时,才会出现create-electron-vite
选项。
electron安装比较慢,要耐心等待,如下图所示,这个包显示下载了52.6s才下载完,实际感觉有好几分钟。
运行项目
执行一下pnpm dev
, 就可看到create-electron-vite创建的演示页面

调试代码
开发 Electron 应用,肯定要掌握如何调试代码,调试代码可分为对主进程和渲染进程调试两部分
- 主进程调试,直接在主进程代码中用console.log输出日志即可。
- 渲染进程调试 要在主进程中开启调试功能,方法如下:
import { BrowserWindow } from 'electron';
const win = new BrowserWindow();
win.webContents.openDevTools();
然后就和在 Chrome
中使用 DevTools
调试web页面一样。
打包安装
执行一下打包命令 pnpm build
, 发现功能比较简单的页面,打包之后的安装包体积比较大, 多达297M。打包之后的安装包位于项目根目录下的release文件夹中,点击release中0.0.0版本下的exe可执行文件,就可以在PC上安装我们开发的demo桌面应用。卸载方式与常规的桌面应用没有任何差异。不禁感叹,编程的尽头是JavaScript语言,既能开发网页,又能开发应用端应用,也能开发桌面应用,还能开发后台服务,堪称近乎全能的编程语言。

项目工程化配置
create-electron-vite
这个脚手架除了带有tsconfig.json配置之外,其它的项目工程化配置,比如说代码美化,代码质量检查,样式质量检查,以及保存代码时进行代码美化,这些都没有,需要我们自己添加一下。
配置Prettier
Prettier提供的配置代码美化风格的规则虽然不多,但通用性很强,支持所有的主流编程语言的代码风格美化。使用之前先安装依赖包,然后配置代码美化规则。
pnpm add prettier
.prettierrc文件内容如下:最后一项vueIndentScriptAndStyle
只有在使用vue的时候才需要配置,它的作用是让页面中的script
标签和style
标签中的内容像template
标签中的内容一样,缩进一个tab。
{
"printWidth": 120,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"jsxSingleQuote": false,
"endOfLine": "auto",
"vueIndentScriptAndStyle": true
}
配置ESLint
ESLint用于代码进行质量检查,前端开发必备。可以对代码质量不符配置规则的代码片段进行提示或修复错误,同时也有代码风格美化的功能。这一部分功能与Prettier存在冲突,所以要安装eslint-config-prettier
和eslint-plugin-prettier
处理冲突,禁用ESLint与Prettier冲突的规则。让它们能够和睦相处。
需要安装的npm包集合如下:
pnpm add eslint-config-prettier eslint-define-config eslint-plugin-prettier eslint-plugin-vue
.eslintrc.js文件的配置如下:
const { defineConfig } = require('eslint-define-config');
module.exports = defineConfig({
root: true,
env: {
browser: true,
node: true,
es6: true,
},
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaVersion: 2020,
sourceType: 'module',
jsxPragma: 'React',
ecmaFeatures: {
jsx: true,
},
},
extends: ['plugin:vue/vue3-recommended', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'],
rules: {
// vue@3.2.5以上, props可以直接解构, 不会失去响应式
'vue/no-setup-props-destructure': 'off',
'space-before-function-paren': 'off',
'no-use-before-define': 'off',
// 目前遇到一个冲突点style="width:100%;"中的分号会引起 Delete ';' prettier/prettier告警
'prettier/prettier': ['error', { vueIndentScriptAndStyle: true }],
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'vue/custom-event-name-casing': 'off',
'vue/component-tags-order': [
'error',
{
order: [['script', 'template', 'style']],
},
],
'vue/multi-word-component-names': 'off',
'vue/script-setup-uses-vars': 'error',
'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/max-attributes-per-line': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/attribute-hyphenation': 'off',
'vue/require-default-prop': 'off',
'vue/html-self-closing': [
'error',
{
html: {
void: 'always',
normal: 'never',
component: 'always',
},
svg: 'always',
math: 'always',
},
],
},
});
配置StyleLint
它可以提示样式书写的一些错误,如样式类名称前后重复,样式类名下的样式属性重复或者后面的属性设置覆盖了前端的属性值,样式属性值书写错误,以及对属性进行排序,保持样式排序风格一致。
这个功能需要安装的npm包比较多,具体如下:
pnpm add stylelint stylelint-config-recess-order stylelint-config-recommended-vue stylelint-config-standard
另外,如果要对vue文件中的style
标签部分的样式进行质量检查,还需要安装下面三个依赖包
pnpm add postless postcss-html postcss-less
有一个不起眼的配置,笔者花了较长的时间,才处理好Prettier和StyleLint相互冲突的问题,Prettier会把style
标签部分需要用引号括起来的样式属性,将引号格式化成单引号,可是StyleLint默认配置的规则是双引号,每次保存代码的时候,总是StyleLint格式化先执行,Prettier格式化后执行,所以你会看到有引号的样式属性值先变成双引号,最后又变成单引号,然后终态引起了StyleLint的警告(如下图所示)。后来通过查找资料,才明白了原因以及找到了解决方法。解决方法就是在StyleLint的配置文件中,添加这条规则 'string-quotes': 'single'
。
.stylelintrc.js配置内容如下:
module.exports = {
extends: ['stylelint-config-standard', 'stylelint-order', 'stylelint-config-recess-order'],
customSyntax: 'postcss-less',
// ===== vue项目要增加的配置项============
overrides: [
{
files: ['**/*.vue'],
customSyntax: 'postcss-html',
},
],
// ======================================
rules: {
// 字符串用单引号括起来--为了兼容vue文件中script部分的prettier格式化规则
'string-quotes': 'single',
indentation: 2,
'declaration-block-trailing-semicolon': null, // 关闭声明块后面需要加分号的规则,避免和prettier冲突
'at-rule-no-unknown': [true, { ignoreAtRules: ['mixin', 'extend', 'content', 'include'] }],
'no-empty-source': null, // null是关闭规则的意思--less文件内容可以为空
'no-descending-specificity': null, //禁止特异性较低的选择器在特异性较高的选择器之后重写
'font-family-no-missing-generic-family-keyword': null, // 关闭必须设置通用字体的规则
// 动画名称前,可以加浏览器前缀 如@-webkit-keyframes bounce
'at-rule-no-vendor-prefix': null,
// id选择器为了兼容#__vconsole, 修改短横线命名
'selector-id-pattern': '^([#_a-z][_a-z0-9]*)(-[a-z0-9]+)*$',
// class选择器修改为同时支持短横线和小驼峰
// 为了兼容css module,.coupon-backCard-modal,.czH5shouye-huodong,.czcommon_36_youjiantou_qianhui这样的类名
'selector-class-pattern': null,
// "selector-class-pattern":
// "(^([#_a-z][a-zA-Z0-9]*)(-[a-zA-Z0-9]+)*$)|(^[a-z][a-zA-Z0-9]+$)|(^([a-z][a-z0-9]*)(_[a-z0-9]+)*$)",
// 动画名称命名,为了兼容这种命名btnScaleAni,添加小驼峰命名规则
'keyframes-name-pattern': '(^([a-z][_a-z0-9]*)(-[a-z0-9]+)*$)|(^([a-z][a-zA-Z0-9]+)*$)',
// url地址不加引号
'function-url-quotes': null,
// 保留各大浏览器不兼容的样式属性名前缀, 如 -moz-user-select: auto;
'property-no-vendor-prefix': null,
// 保留各大浏览器不兼容的样式属性值前缀,display: -webkit-box;
'value-no-vendor-prefix': null,
// 保留各大浏览器不兼容的选择器前缀,如input::-webkit-input-placeholder
'selector-no-vendor-prefix': null,
// 屏蔽background-color: rgba(0, 0, 0, 0.5);这种写法引起的警告
'color-function-notation': 'legacy',
// 屏蔽background-color: rgba(0, 0, 0, 0.5);中0.5引起的警告
'alpha-value-notation': 'number',
// css属性值中小数点之后数字的最大位数
'number-max-precision': 10,
'property-no-unknown': [
true,
{
ignoreProperties: ['box-flex'], // 忽略某些未知属性的检测
},
],
'selector-pseudo-class-no-unknown': null,
'selector-pseudo-element-no-unknown': [
true,
{
ignorePseudoElements: ['v-deep', ':deep', 'input-placeholder'], // 忽略ng-deep这种合法的伪元素选择器报警
},
],
'declaration-colon-newline-after': null, //一个属性过长的话可以写成多行
'media-feature-name-no-unknown': null, // 关闭禁止未知的媒体功能名
},
};
保存代码格式化配置
实现保存代码的时候,自动对vue文件,less/css文件,ts文件,js文件,json文件,html文件进行代码风格统一化。.vscode/setting.json文件配置如下:
{
"files.eol": "\n",
"editor.tabSize": 2,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[less]": {
"editor.defaultFormatter": "stylelint.vscode-stylelint"
},
"[css]": {
"editor.defaultFormatter": "stylelint.vscode-stylelint"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.fixAll.stylelint": true
},
"css.validate": false,
"less.validate": false,
"scss.validate": false,
"stylelint.validate": ["css", "less", "vue"]
}
Eletron运行流程
- 从package.json 中 寻找主进程入口文件
- 运行主进程入口文件
- 主进程中创建渲染进程,加载页面
- 使用IPC在主进程执行任务并获取渲染进程信息
主进程与渲染进程
主进程:Electron 运行 package.json 的main 脚本的进程被称为主进程。在主进程中运行的脚本通过创建web页面来展示用户界面。一个Electron应用有且只有一个主进程。
渲染进程:用户看到的web页面就是由渲染进程描绘出来的。包括html,css和js。由于Electron使用了Chromium ( 谷歌浏览器 ) 来展示web页面,所以Chromium 的多进程架构也被使用到。每个Electron中的Web页面运行在自己的渲染进程中。
主进程使用BrowserWindow 实例创建页面。每个BrowerWindow实例都在自己的渲染进程里运行页面。当一个BrowerWindow实例被销毁后,相应的渲染进程也会被终止。
进程间通信
从主进程到渲染进程:从Main到Renderer的消息传递,使用BrowerWindow.webContents.send()发送消息。 从渲染进程到主进程:从Renderer到Main的消息传递,借助ipcRender和ipcMain发送/接收消息。
无论是BrowerWindow.webContents.send(),还是ipcXX,都是node的事件机制,都是node EventEmitter的实例。
下面重点说一下使用ipcMain
和ipcRenderer
模块,如何实现主进程和渲染进程之间的通信。
1.ipcMain
模块
当在主进程中使用它的时候,它控制着由渲染进程(web page)发送过来的异步或同步消息。从渲染进程发送过来的消息将触发对应的事件。
2.ipcRenderer
模块
使用它可以从渲染进程向主进程发送同步或异步消息。也可以收到主进程相应的消息。
在主进程中,添加如下代码:
// 主进程
import { ipcMain } from 'electron';
// 接收异步消息
ipcMain.on('asynchronous-message', function (event, msg) {
console.log('接收的渲染进程异步消息',msg);
// 回复异步消息,用event.sender.send
event.sender.send('asynchronous-reply', '回复渲染进程异步消息');
});
// 接收同步消息
ipcMain.on('synchronous-message', function (event, msg) {
console.log('接收的渲染进程异步消息',msg);
// 回应同步消息, 用event.returnValue
event.returnValue = '回复渲染进程同步消息';
});
在渲染进程,添加如下代码:
// 渲染进程
import { useIpcRenderer } from '@vueuse/electron';
const ipcRenderer = useIpcRenderer();
ipcRenderer.send('asynchronous-message', '渲染进程发送异步消息');
const syncReply = ipcRenderer.sendSync('synchronous-message', '渲染进程发送同步消息');
console.log('回复同步消息', syncReply.value);
// 接收渲染进程异步消息
ipcRenderer.on('asynchronous-reply', function (event, msg) {
console.log({ msg, event });
});
运行一下,可以看到主进程和渲染进程,可以正确收发同步与异步消息;
结语
至此,我们已经掌握了如何创建,运行,打包,安装一个Electron桌面应用。以及了解了Eletron的运行流程,算是已经入门了。本文的目的就是带你从入门到入门。如果你还想深入了解,可以把这篇文章的效果实现一下。另外,本文的样板项目已经上传到码云,可以点击这里下载学习。
转载自:https://juejin.cn/post/7246777308764241977