docs: 内容

This commit is contained in:
杭城小刘
2020-11-08 15:51:47 +08:00
parent a744b950b0
commit e89fe0ca1c
27 changed files with 11290 additions and 10341 deletions

View File

@@ -1,13 +1,31 @@
# ElectronPC 端多端融合方案
> 每天都要写第二天的 todoList。有一天在写的时候突然想到为了让自己清楚知道自己需要做啥、做了多少、还剩多少没做想写一个电脑端程序在技术选型的时候就选了 electron。
> 每天都要写第二天的 todoList。有一天在写的时候突然想到为了让自己清楚知道自己需要做啥、做了多少、还剩多少没做想写一个电脑端程序在技术选型的时候就选了 Electron。
>
> 本篇文章的目的不是讲解 API 如何使用,想知道这些可以直接看[官方文档](https://www.electronjs.org/docs)。本文目的旨在讲明如何技术如何选择、如何快速上手、如何调试、Electron 底层原理、工程体系方面的总结。
> 本篇文章的目的不是讲解 API 如何使用,想知道这些可以直接看[官方文档](https://www.Electronjs.org/docs)。本文目的旨在讲明如何技术如何选择、如何快速上手、如何调试、Electron 底层原理、工程体系方面的总结。
## 一、浅谈 GUI 系统
```shell
浏览器是如何将布局数据计算为像素数据的,你能实现出原理类似的渲染器吗?
浏览器在各个平台上的文字排版渲染结果是否一致,你能解释原因吗?
你所负责的前端应用,其渲染性能还有多大的提升空间,你能量化地证明吗?
你能设计实现出类似 RN 和小程序那样的 Hybrid 方案吗?
你能自己控制 GPU 渲染管线,实现渲染的硬件加速吗?
```
GUI 起源:从 1979 年乔布斯造访施乐 PARC 算起
GUI 架构过程化绘制drawLine、drawRect-> 面向对象抽象时代 -> 界面与样式分离时代 -> MVC、MVVM 时代 -> 声明式、组件式时代Vue、React、RN、Weex、Flutter
我们可以看到不变的是随着计算机科学技术的发展为了实现某个效果一流程序员或者组织不断研发各种技术框架来提高开发效率和效果。Electron 就是这条历史长河中诞生的 PC 端技术框架之一。
## 方案选型
## 技术选型
3天时间写了个 PC 端应用程序。先看看结果吧
@@ -17,33 +35,36 @@
![Todo1](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-05-04-TodoApp3.png)
为什么要选 electron 作为 pc 端开发方案?
**为什么要选 Electron 作为 pc 端开发方案?**
史前时代,以 MFC 为代表的技术栈,开发效率较低,维护成本高。
后来使用 QT 技术,特点是使用 DirectUI + 面向对象 + XML 定义 UI适用于小型软件、性能要求、包大小、UI 复杂度叫高的需求。
再到后来,以 QT Quick 为代表的技术,特点是框架本身提供子控件,基于子控件组合来创建新的控件。类似于 ActionScript 的脚本化界面逻辑代码。
新时代主要是以 [electron](https://electronjs.org) 和 [Cef](https://bitbucket.org/chromiumembedded/cef) 为 代表。特点是界面开发以 Web 技术为主,部分逻辑需要 Native 代码实现。大家都熟悉的 VS Code 就是使用 electron 开发的。适用于 UI 变化较多、体积限制不大、开发效率高的场景。
新时代主要是以 [Electron](https://Electronjs.org) 和 [Cef](https://bitbucket.org/chromiumembedded/cef) 为 代表。特点是界面开发以 Web 技术为主,部分逻辑需要 Native 代码实现。大家都熟悉的 VS Code 就是使用 Electron 开发的。适用于 UI 变化较多、体积限制不大、开发效率高的场景。
拿 C 系列写应用程序的体验不好,累到奔溃。再加上有 Hybrid、React Native、iOS、Vue、React 等开发经验,electron 是不二选择。
拿 C 系列写应用程序的体验不好,累到奔溃。再加上有 Hybrid、React Native、iOS、Vue、React 等开发经验,Electron 是不二选择。
## 、 Quick start
## 、 Quick start
执行下面命令快速体验 Hello world也是官方给的一个 Demo。
```shell
git clone https://github.com/electron/electron-quick-start
cd electron-quick-start
git clone https://github.com/Electron/Electron-quick-start
cd Electron-quick-start
npm install && npm start
```
简单介绍下 Demo 工程,工程目录如下所示
![工程目录](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-04-21-electron-packagejson.png)
![工程目录](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-04-21-Electron-packagejson.png)
在终端执行 `npm start` 执行的是 package.json 中的 `scripts` 节点下的 start 命令,也就是 `electron .``.` 代表执行 main.js 中的逻辑。
在终端执行 `npm start` 执行的是 package.json 中的 `scripts` 节点下的 start 命令,也就是 `Electron .``.` 代表执行 main.js 中的逻辑。
```Javascript
// Modules to control application life and create native browser window
const {app, BrowserWindow} = require('electron')
const {app, BrowserWindow} = require('Electron')
const path = require('path')
function createWindow () {
@@ -84,13 +105,13 @@ app.on('activate', function () {
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
```
写过 Vue、React、Native 的人看代码很容易,因为应用程序的生命周期钩子函数是很重要的,开发者根据需求在钩子函数里面做相应的视图创建、初始化、销毁对象等等。比如 electron 中的 activatewindow-all-closed 等。
写过 Vue、React、Native 的人看代码很容易。比如应用程序的生命周期钩子函数对开发者很重要,也是一个标准的做法,根据需求在钩子函数里面做相应的视图创建、初始化、销毁对象等等。比如 Electron 中的 `activate``window-all-closed` 等。
app 对象在 `whenReady` 的时候执行 createWindow 方法。内部创建了一个 `BrowserWindow` 对象,指定了大小和功能设置。
app 对象在 `whenReady` 的时候执行 `createWindow` 方法。内部创建了一个 `BrowserWindow` 对象,指定了大小和功能设置。
1. webPreferences Object (可选) - 网页功能的设置。
1. webPreferencesObject (可选) - 网页功能的设置。
2. preload String (可选) - 在页面运行其他脚本之前预先加载指定的脚本 无论页面是否集成 Node, 此脚本都可以访问所有 Node API 脚本路径为文件的绝对路径。 当 node integration 关闭时, 预加载的脚本将从全局范围重新引入 node 的全局引用标志。
2. preload String (可选) - 在页面运行其他脚本之前预先加载指定的脚本无论页面是否集成 Node, 此脚本都可以访问所有 Node API 脚本路径为文件的绝对路径。 当 node `integration` 关闭时, 预加载的脚本将从全局范围重新引入 node 的全局引用标志。
`mainWindow.loadFile('index.html')` 加载了同级目录下的 index.html 文件。也可以加载服务器资源(部署好的网页),比如 `win.loadURL('https://github.com/FantasticLBP')`
@@ -108,25 +129,25 @@ window.addEventListener('DOMContentLoaded', () => {
}
console.table(process)
console.info(process.versions)
for (const type of ['chrome', 'node', 'electron']) {
for (const type of ['chrome', 'node', 'Electron']) {
replaceText(`${type}-version`, process.versions[type])
}
})
```
在页面运行其他脚本之前预先加载指定的脚本,无论页面是否集成 Node 此脚本都可以访问所有 Node API 脚本路径为文件的绝对路径。Demo 中的逻辑很简单,就是读取 `process.versions` 对象中的 node、chrome、electron 的版本信息并展示出来。
在页面运行其他脚本之前预先加载指定的脚本,无论页面是否集成 Node 此脚本都可以访问所有 Node API 脚本路径为文件的绝对路径。Demo 中的逻辑很简单,就是读取 `process.versions` 对象中的 node、chrome、Electron 的版本信息并展示出来。
`index.html` 中的内容就是主页面显示的内容。一般不会直接写 html、css、js都会根据技术背景选择前端框架比如 Vue、React 等,或者模版引擎 ejs 等。
`index.html` 中的内容就是主页面显示的内容。一般不会直接写 html、css、js都会根据技术背景选择前端框架比如 Vue、React 等,或者模版引擎 [ejs](https://ejs.co) 等。
## 、 实现原理
## 、 实现原理
electron 分为**渲染进程和主进程**。和 Native 中的概念不一样的是 electron 中主进程只有一个,渲染进程(也就是 UI 进程) 有多个。主进程在后台运行,每次打开一个界面,会新开一个新的渲染进程。
Electron 分为**渲染进程和主进程**。和 Native 中的概念不一样的是 Electron 中主进程只有一个,渲染进程(也就是 UI 进程) 有多个。主进程在后台运行,每次打开一个界面,会新开一个新的渲染进程。
- 渲染进程: 用户看到的 web 界面就是由渲染进程绘制出来的,包括 html、css、js。
- 主进程:electron 运行 package.json 中的 main.js 脚本的进程被称为主进程。在主进程中运行的脚本通过创建 web 页面来展示用户界面。一个 electron 应用程序总是只有一个主进程。
- 主进程:Electron 运行 package.json 中的 main.js 脚本的进程被称为主进程。在主进程中运行的脚本通过创建 web 页面来展示用户界面。一个 Electron 应用程序总是只有一个主进程。
@@ -343,7 +364,11 @@ Electron 架构和 Chromium 架构类似也是具有1个主进程和多个渲
## 四、 如何调试
## 五、 如何调试
调试分为主进程调试和渲染进程调试。
@@ -370,12 +395,12 @@ Electron 架构和 Chromium 架构类似也是具有1个主进程和多个渲
```
- 然后打开浏览器,在地址栏输入 `chrome://inspect`
- 点击 `configure`,在弹出的面板中填写需要调试的端口信息
![chrome inspect](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-04-21-electronChromeInspect.png)
![chrome inspect](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-04-21-ElectronChromeInspect.png)
- 重新开启服务 `npm start`,在 chrome inspect 面板的 `Target` 节点中选择需要调试的页面
- 在面板中可以看到主进程执行的 `main.js`。可以加断点进行调试
![chrome inspect](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-04-21-electron-MainProcessInspect.png)
![chrome inspect](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-04-21-Electron-MainProcessInspect.png)
方法二:利用 VS Code 调试 electron 主进程。
方法二:利用 VS Code 调试 Electron 主进程。
- 在 VS Code 的左侧菜单栏,第四个功能模块就是调试,点击调试,弹出对话框让你添加调试配置文件 `launch.json`
@@ -390,9 +415,9 @@ Electron 架构和 Chromium 架构类似也是具有1个主进程和多个渲
"request": "launch",
"name": "Debug main process",
"cwd": "${workspaceRoot}",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/Electron",
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/Electron.cmd"
},
"args": ["."],
"outputCapture": "std"
@@ -417,7 +442,7 @@ Webpack 有一个 api: `watch-run`,可以针对代码文件检测,有变化
## 、 开发 tips及其优化手段
## 、 开发 tips及其优化手段
### 1. 开发 tips
@@ -431,14 +456,14 @@ Webpack 有一个 api: `watch-run`,可以针对代码文件检测,有变化
Refused to execute inline event handler because it violates the following Content Security Policy directive:
```
4. 利用 electron 进行开发的时候,可以看成是 NodeJS + chromium + Web 前端开发技术。NodeJS 拥有文件访问等后端能力chromium 提供展示功能,以及网络能力(electron 网络能力不是 NodeJS 提供的,而是 chromium 的 net 模块提供的。web 前端开发技术方案都可以应用在 electron 中,比如 Vue、React、Bootstrap、sass 等。
4. 利用 Electron 进行开发的时候,可以看成是 NodeJS + chromium + Web 前端开发技术。NodeJS 拥有文件访问等后端能力chromium 提供展示功能,以及网络能力(Electron 网络能力不是 NodeJS 提供的,而是 chromium 的 net 模块提供的。web 前端开发技术方案都可以应用在 Electron 中,比如 Vue、React、Bootstrap、sass 等。
5. 在工程化角度看,使用 yarn 比 npm 好一些,因为 yarn 会缓存已经安装过的依赖,其他项目只要发现存在缓存,则读取本地的包依赖,会更加快速。
6. 在使用 Vue、React 开发 electron 应用时,可以使用 npm 或 yarn install 包,也可以使用 electron-vue 脚手架工具。
6. 在使用 Vue、React 开发 Electron 应用时,可以使用 npm 或 yarn install 包,也可以使用 Electron-vue 脚手架工具。
```shell
vue init simulatedgreg/electron-vue my-project
vue init simulatedgreg/Electron-vue my-project
cd my-project
npm install
npm run dev
@@ -461,9 +486,9 @@ Webpack 有一个 api: `watch-run`,可以针对代码文件检测,有变化
```
解决办法是在 main/index.dev.js 修改代码
```javascript
- require('electron-debug')({ showDevTools: true });
- require('Electron-debug')({ showDevTools: true });
+ // NB: Don't open dev tools with this, it is causing the error
+ require('electron-debug')();
+ require('Electron-debug')();
```
在 In main/index.js in the createWindow() function:
```javascript
@@ -490,7 +515,7 @@ Webpack 有一个 api: `watch-run`,可以针对代码文件检测,有变化
12. Electron 和 Web 开发相比,各自有侧重点
![electronAndWeb](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-05-04-ElectronAndWeb.png)
![ElectronAndWeb](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-05-04-ElectronAndWeb.png)
### 2. 优化手段
@@ -529,7 +554,7 @@ node --cpu-prof --heap-prof -e "require('request)”“
## 、 技术体系搭建
## 、 技术体系搭建:全景
其实一个技术本身的难易程度并不是能否在自己企业、公司、团队内顺利使用的唯一标尺,其配套的 CI/CD、APM、埋点系统、发布更新、灰度测试等能否与现有的系统以较小成本融合才是很大的决定要素。因为某个技术并不是非常难要是大多数开发者觉得很难那它设计上就是失败的。
@@ -551,10 +576,234 @@ Electron 提供的 crash 信息进行包装。
![crash 分析](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-05-04-ElectronCrash.png)
```js
import { BrowserWindow, app, dialog} from 'Electron';
const mainWindow = BrowserWindow.fromId(global.mainId);
mainWindow.webContents.on('crashed',
const options = {
type: 'error',
title: '进程崩溃了',
message: '这个进程已经崩溃.',
buttons: ['重载', '退出'],
};
recordCrash().then(() => {
dialog.showMessageBox(options, (index) => {
if (index === 0) reloadWindow(mainWindow);
else app.quit();
});
}).catch((e) => {
console.log('err', e);
});
})
function recordCrash() {
return new Promise(resolve => {
// 崩溃日志请求成功....
resolve();
})
}
function reloadWindow(mainWin) {
if (mainWin.isDestroyed()) {
app.relaunch();
app.exit(0);
} else {
BrowserWindow.getAllWindows().forEach((w) => {
if (w.id !== mainWin.id) w.destroy();
});
mainWin.reload();
}
}
```
```javascript
// index.js
app.on('will-finish-launching', () => {
if(!isDev) {
require('./updater.js')
}
require('./crash-reporter').init()
})
// crash-reporter.js
const {crashReporter} = require('Electron')
function init() {
crashReporter.start({
productName: 'ZanPandora',
companyName: 'youzan',
submitURL: 'http://127.0.0.1:33855/crash',
})
}
module.exports = {init}
// server
const Koa = require('koa')
const app = new Koa()
const Router = require('koa-router')
const serve = require('koa-static-server')
const router = new Router()
const compareVersions = require('compare-versions')
const multer = require('koa-multer')
const uploadCrash = multer({dest: 'crash/'})
router.post('/crash', uploadCrash.single('upload_file_minidump'), (ctx, next) => {
console.log(ctx.req.body)
// 存DB
})
```
### 4. 软件更新
```javascript
// package.json
"dependencies": {
"Electron-is-dev": "^1.1.0",
"Electron-squirrel-startup": "^1.0.0",
// ...
},
// index.js
app.on('will-finish-launching', () => {
if(!isDev) {
require('./updater.js')
}
require('./crash-reporter').init()
})
// updater.js
const {autoUpdater, app, dialog} = require('Electron')
if(process.platform == 'darwin') {
autoUpdater.setFeedURL('http://127.0.0.1:33855/darwin?version=' + app.getVersion())
} else {
autoUpdater.setFeedURL('http://127.0.0.1:33855/win32?version=' + app.getVersion())
}
autoUpdater.checkForUpdates() // 定时轮训、服务端推送
autoUpdater.on('update-available', () => {
console.log('update-available')
})
autoUpdater.on('update-downloaded', (e, notes, version) => {
// 提醒用户更新
app.whenReady().then(() => {
let clickId = dialog.showMessageBoxSync({
type: 'info',
title: '升级提示',
message: '已为你升级到最新版,是否立即体验',
buttons: ['马上升级', '手动重启'],
cancelId: 1,
})
if(clickId === 0) {
autoUpdater.quitAndInstall()
app.quit()
}
})
})
autoUpdater.on('error', (err) => {
console.log('error', err)
})
// server
// index.js
const Koa = require('koa')
const app = new Koa()
const Router = require('koa-router')
const serve = require('koa-static-server')
const router = new Router()
const compareVersions = require('compare-versions')
const multer = require('koa-multer')
const uploadCrash = multer({dest: 'crash/'})
router.post('/crash', uploadCrash.single('upload_file_minidump'), (ctx, next) => {
console.log(ctx.req.body)
// 存DB
})
function getNewVersion(version) {
if(!version) return null
let maxVersion = {
name: '1.0.1',
pub_date: '2020-02-01T12:26:53+1:00',
notes: '新增功能AAA',
url: `http://127.0.0.1:33855/public/ZanPandora-1.0.1-mac.zip`
}
if(compareVersions.compare(maxVersion.name , version, '>')) {
return maxVersion
}
return null
}
router.get('/win32/RELEASES', (ctx, next) => {
let newVersion = getNewVersion(ctx.query.version)
if(newVersion) {
ctx.body='BBC6F98A5CD32C675AAB6737A5F67176248B900C ZanPandora-1.0.1-full.nupkg 62177782'
} else {
ctx.status = 204
}
})
router.get('/win32/*.nupkg', (ctx, next) => {
// redirect s3 静态文件服务
ctx.redirect(`/public/${ctx.params[0]}.nupkg`)
})
router.get('/darwin', (ctx, next) => {
// 处理Mac更新, ?version=1.0.0&uid=123
let {version} = ctx.query
let newVersion = getNewVersion(version)
if(newVersion) {
ctx.body = newVersion
} else {
ctx.status = 204
}
})
app.use(serve({rootDir: 'public', rootPath: '/public'}))
app.use(router.routes())
.use(router.allowedMethods())
app.listen(33855)
```
引申资料
- [electron-vue架构解析-开发环境启动流程分析](https://dushaofeng.github.io/2018/06/08/electron-vue架构解析-开发环境启动流程分析/)
- [chromium 进程模型](https://www.chromium.org/developers/design-documents/process-models)
## 八、 Electron 应用场景
### 1. 字节跳动
- Electron做了一个工具能直接查看线上包的函数耗时无任何侵入
- 调试工具的合集
- 研发需求管理
- 代码合并工具MR。区别于 gitlab 的特性,没有多仓合代码的能力。
比如同时在主工程系修改了7个 pod 、1个主工程的代码需要分批次提交不具备同时将8个仓库的代码原子性合入
pod 的 changeLog、版本号等需要设计自动发版的流程
- 把性能调试和效率工具都整合到一起了。
### 2. 阿里
- 沙盒的查看与操作,比如在 PC 端查看移动沙盒内文件内容、数据库文件的查看与 SQL 执行等
- lint 功能、检测无用方法、在线日志解密查看、数据库文件的解密查看、jspatch 的 mock、
- 针对网络请求和响应的操作比如自定义请求、延迟、mock response、
- Mock、查看
- 性能测试cpu、load、fps、启动耗时机房有高速摄像机解帧模拟点击 icon、启动页启动、App 首页出现、首页图片出现、App 可以滚动交互了。不需要 hook 去监控耗时)
- pre-main、main、首页打点
- 数据板:埋点体系的数据,看坑位的点击效果
- 应用数据沙盒、Cookie、数据
- 开发工具CPU、内存、OOM
- 视觉:移动端元素的参考线
- 网络、开关数据、卡顿、内存泄漏
一言以蔽之就是:通过 deviceID、ip 地址等与设备产生连接,将一切可以标准化的流程都抽象、自动化、比如性能调试和效率工具都整合到一起。
一个原则该工具只做数据的查看、Mock 等工作,不做线上数据的干扰和生产

View File

@@ -0,0 +1,24 @@
# weex 优化和原理
![image-20200815004043104](/Users/lbp/Library/Application Support/typora-user-images/image-20200815004043104.png)
![image-20200815004612271](/Users/lbp/Library/Application Support/typora-user-images/image-20200815004612271.png)
![image-20200815004625829](/Users/lbp/Library/Application Support/typora-user-images/image-20200815004625829.png)
![image-20200815004636381](/Users/lbp/Library/Application Support/typora-user-images/image-20200815004636381.png)
![image-20200815004803634](/Users/lbp/Library/Application Support/typora-user-images/image-20200815004803634.png)