Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 亚洲黄色免费视频,国产精品系列在线一区,久久成人亚洲香蕉草草

          整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          Electron+Vue3.2+TypeScript

          Electron+Vue3.2+TypeScript+Vite開發(fā)桌面端

          Electron可以讓程序員重用Web的代碼,使用HTML、CSS、JavaScript來構(gòu)建桌面應(yīng)用,并在不同平臺(tái)上使用。

          Electron官網(wǎng)上說:“比你想象的更簡單”————“如果你可以建一個(gè)網(wǎng)站,你就可以建一個(gè)桌面應(yīng)用程序。 Electron 是一個(gè)使用 JavaScript, HTML 和 CSS 等 Web 技術(shù)創(chuàng)建原生程序的框架,它負(fù)責(zé)比較難搞的部分,你只需把精力放在你的應(yīng)用的核心上即可。”

          Vue 是一套用于構(gòu)建用戶界面的漸進(jìn)式框架。與其它大型框架不同的是,Vue 被設(shè)計(jì)為可以自底向上逐層應(yīng)用。目前 Vue 已經(jīng)成為繼 Rect 之后最流行的前端開發(fā)框架。

          我找到了一個(gè)插件:vite-plugin-electron,可以很方便的幫我們把 electron 和 vue 結(jié)合起來,開發(fā)起來非常方便。

          目錄結(jié)構(gòu)設(shè)計(jì)

          因?yàn)槲覀冃枰褂?Electron 和 vue 進(jìn)行開發(fā),因此我們把它們分開目錄存儲(chǔ),基礎(chǔ)目錄結(jié)構(gòu)如下:

          • electron-main:主進(jìn)程目錄
          • electron-preload:預(yù)加載代碼目錄,主要是定義橋接通信
          • 其他文件:也就是 vue 初始化后的目錄

          目錄結(jié)構(gòu)這么設(shè)計(jì)的原因是因?yàn)槲覀兪褂玫?vite-plugin-electron 插件需要使用這樣的目錄結(jié)構(gòu),目前還沒有提供設(shè)置修改。

          初始化項(xiàng)目目錄

          下面就開始初始項(xiàng)目

          yarn create vite electron-desktop --template vue-ts

          先使用 vite 創(chuàng)建 vue 項(xiàng)目,然后我們?cè)賹?electron 嵌入到里面

          初始化完成后,我們先做一個(gè)本地 yarn 源的配置,防止下載庫的時(shí)候出現(xiàn)異常。

          配置.yarnrc

          registry "https://registry.npm.taobao.org/"
          
          electron_mirror "https://npm.taobao.org/mirrors/electron/"
          electron_builder_binaries_mirror "http://npm.taobao.org/mirrors/electron-builder-binaries/"

          配置完下載源后,就可以安裝 electron 了

          安裝Electron

          yarn add -D electron electron-builder rimraf vite-plugin-electron electron-devtools-installer
          • electron-builder:打包工具
          • rimraf:快速刪除文件或目錄工具
          • vite-plugin-electron:vite 結(jié)合 electron 的庫,關(guān)于這個(gè)插件可以參見 Vite 與 Electron 無縫銜接
          • electron-devtools-installer:electron 開發(fā)工具

          vite-plugin-electron 插件是將 vite 和 electron 結(jié)合在一起的,可以讓我們非常方便的結(jié)合 electron 和 vue,需要做一些指定的配置。

          初始化electron項(xiàng)目

          可以參考 electron 官網(wǎng)的快速開始項(xiàng)目

          • 創(chuàng)建主進(jìn)程目錄和文件
          // electron-main/index.ts
          import { app, BrowserWindow } from 'electron';
          import path from 'path';
          
          const createWindow=()=> {
            const win=new BrowserWindow({
              webPreferences: {
                contextIsolation: false,
                nodeIntegration: true,
                preload: path.join(__dirname, '../electron-preload/index.js'),
              },
            });
          
            if (app.isPackaged) {
              win.loadFile(path.join(__dirname, '../index.html'));
            } else {
              //  Use ['ENV_NAME'] avoid vite:define plugin
              const url=`http://${process.env['VITE_DEV_SERVER_HOST']}:${process.env['VITE_DEV_SERVER_PORT']}`;
          
              win.loadURL(url);
            }
          };
          
          app.whenReady().then(()=> {
            createWindow();
          
            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 (BrowserWindow.getAllWindows().length===0) createWindow();
            });
          });
          
          app.on('window-all-closed', ()=> {
            if (process.platform !=='darwin') {
              app.quit();
            }
          });

          需要注意引入的預(yù)加載文件應(yīng)該是打包后的 js 文件,路徑和 ts 文件路徑相同,只要類型改為 js 即可。

          • 創(chuàng)建預(yù)加載目錄和文件

          在預(yù)加載文件中我們打印一下系統(tǒng)平臺(tái)

          // electron-preload/index.ts
          import os from 'os';
          
          console.log(os.platform());

          配置vite-electron

          tsconfig.json

          在 tsconfig.json 中監(jiān)聽 electron 相關(guān)文件和提示

          "include": [..., "electron-main/**/*.ts", "electron-preload/**/*.ts"],

          vite.config.ts配置

          import { defineConfig } from 'vite';
          import vue from '@vitejs/plugin-vue';
          import * as path from 'path';
          import electron from 'vite-plugin-electron';
          import electronRenderer from 'vite-plugin-electron/renderer';
          import polyfillExports from 'vite-plugin-electron/polyfill-exports';
          
          export default defineConfig({
            plugins: [
              vue(),
              electron({
                main: {
                  entry: 'electron-main/index.ts',
                },
                preload: {
                  // Must be use absolute path, this is the limit of rollup
                  input: path.join(__dirname, './electron-preload/index.ts'),
                },
              }),
              electronRenderer(),
              polyfillExports(),
            ],
            build: {
              emptyOutDir: false, // 必須配置,否則electron相關(guān)文件將不會(huì)生成build后的文件
            },
          });

          配置主進(jìn)程和預(yù)加載腳本地址

          package.json配置

          {
            "name": "electron-desktop",
            "private": true,
            "version": "1.0.0",
            "main": "dist/electron-main/index.js",
            "scripts": {
              "dev": "vite",
              "build": "rimraf dist && vite build && electron-builder"
            },
            "dependencies": {
              "vue": "^3.2.25"
            },
            "devDependencies": {
              "@vitejs/plugin-vue": "^2.3.1",
              "electron": "^18.2.0",
              "electron-builder": "^23.0.3",
              "electron-devtools-installer": "^3.2.0",
              "rimraf": "^3.0.2",
              "typescript": "^4.5.4",
              "vite": "^2.9.5",
              "vite-plugin-electron": "^0.4.3",
              "vue-tsc": "^0.34.7"
            }
          }

          主要是增加入口文件,因?yàn)?electron 還沒有原生支持 ts,因此目前還是必須加載 js 文件,所以入口文件我們配置為解析后的 js 文件路徑:dist/electron-main/index.js,然后修改執(zhí)行腳本,在 build 命令中增加 electron-builder 打包命令。

          electron-builder打包配置

          # package.json
          {
              ......,
              "build": {
                  "appId": "com.electron.desktop",
                  "productName": "ElectronVueVite",
                  "asar": true,
                  "copyright": "Copyright ? 2022 XingXingZaiXian",
                  "directories": {
                    "output": "release/${version}"
                  },
                  "files": [
                    "dist"
                  ],
                  "mac": {
                    "artifactName": "${productName}_${version}.${ext}",
                    "target": [
                      "dmg"
                    ]
                  },
                  "win": {
                    "target": [
                      {
                        "target": "nsis",
                        "arch": [
                          "x64"
                        ]
                      }
                    ],
                    "artifactName": "${productName}_${version}.${ext}"
                  },
                  "nsis": {
                    "oneClick": false,
                    "perMachine": false,
                    "allowToChangeInstallationDirectory": true,
                    "deleteAppDataOnUninstall": false
                  }
                }
            }

          electron-builder 的配置我們有一篇專門的文章介紹,這里并沒有什么特殊的配置,按照需求配置即可。

          到這里就配置好了所有的文件,接下來我們執(zhí)行開發(fā)命令看一看效果。

          yarn run dev

          然后我們執(zhí)行打包命令看一看效果

          yarn run build

          執(zhí)行完后會(huì)生成兩個(gè)目錄:dist 和 release

          dist 目錄中生成的是前端打包文件,release 中生成的是 electron 打包文件,內(nèi)容如下:

          其中 win-uppacked 中生成的是無需安裝的執(zhí)行文件,將此目錄直接壓縮后就可以發(fā)送給別人,解壓即可使用。ExectronVueVite_1.0.0.exe 文件是安裝包,打開會(huì)顯示安裝過程,執(zhí)行完安裝過程后在系統(tǒng)的控制面板中的軟件列表中可以看到該軟件,也可以執(zhí)行卸載。

          打開后就是正常的軟件界面

          我們創(chuàng)建好了項(xiàng)目結(jié)構(gòu),那么在使用 Vue 開發(fā) Electron 桌面應(yīng)用的時(shí)候還有一個(gè)比較重要的知識(shí)點(diǎn)要了解,就是消息通信。在新版本的 electron 中,推薦使用上下文隔離方式進(jìn)行內(nèi)部進(jìn)程通信,electron 官網(wǎng)有很詳細(xì)的介紹和示例,這里我們只介紹一種方式,其他的方式大家可以通過查看官網(wǎng)示例來了解。

          我們的示例是在 Vue 界面中顯示當(dāng)前系統(tǒng)平臺(tái)。

          注冊(cè)上下文隔離接口

          在 electron-preload/index.ts 中添加如下代碼:

          import os from 'os';
          import { contextBridge } from 'electron';
          
          contextBridge.exposeInMainWorld('electronAPI', {
            platform: os.platform(),
          });
          
          

          編寫上下文隔離接口的typescript類型聲明

          通過 electron 注冊(cè)的上下文隔離接口會(huì)添加給 window 對(duì)象,但是原始的 window 對(duì)象并不存在這些接口和屬性,ts 就會(huì)報(bào)錯(cuò),這時(shí)就需要我們?yōu)槠渚帉憈s類型聲明文件.d.ts。

          // src/types/global.d.ts
          export interface IElectronAPI {
            platform: string;
          }
          
          declare global {
            interface Window {
              electronAPI: IElectronAPI;
            }
          }

          在Vue中調(diào)用接口

          我們?cè)?App.vue 中調(diào)用 window.electronAPI.platform 接口,把系統(tǒng)平臺(tái)信息顯示在界面上

          // src/App.vue
          <script setup lang="ts">
          // This starter template is using Vue 3 <script setup> SFCs
          // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
          import HelloWorld from './components/HelloWorld.vue';
          const platform=window.electronAPI.platform;
          </script>
          
          <template>
            <img alt="Vue logo" src="./assets/logo.png" />
            <HelloWorld :msg="`Hello Vue 3 + TypeScript + Vite in ${platform}`" />
          </template>

          執(zhí)行完以上步驟后,運(yùn)行項(xiàng)目看一下效果。

          是否曾受限于瀏覽器的沙盒限制,或者再操作網(wǎng)頁應(yīng)用時(shí)無法滿足下列場(chǎng)景下的使用需求,譬如讀寫本地文件、調(diào)用更多系統(tǒng)資源等。


          因此,你想自行開發(fā)一個(gè)可以跨平臺(tái)的調(diào)動(dòng)資源的程序,但是又擔(dān)心要學(xué)習(xí)新的語言,時(shí)間來不及,開發(fā)預(yù)算不足。


          面對(duì)上述困擾,相信許多前端小伙伴腦中就會(huì)反應(yīng)出一個(gè)應(yīng)用程序——Electron。Electron的官網(wǎng)上說:



          Electron是一個(gè)使用JavaScript、HTML 和 CSS構(gòu)建,支持 Web 技術(shù)的跨平臺(tái)開發(fā)桌面應(yīng)用程序( GUI ),其本身是基于 C++ 開發(fā)的,GUI 核心來自于 Chrome,而 JavaScript 引擎使用 V8。


          01


          Electron誕生


          回顧這項(xiàng)技術(shù)的誕生歷程,堪稱有趣。


          時(shí)間回到2011年,一個(gè)叫王文睿的程序員與同事在討論如何為Tizen WebRuntime增加擴(kuò)展API的時(shí)候,創(chuàng)建了Node-Webkit項(xiàng)目,并打算用Node.js來操作WebKit。幸運(yùn)的是,這樣的嘗試得到了單位的大力支持,當(dāng)時(shí)王文睿就職的公司內(nèi)部有個(gè)鼓勵(lì)創(chuàng)新計(jì)劃,這不僅給王文睿足夠的時(shí)間,同時(shí)還分配了招聘名額,讓他找到協(xié)助完善這個(gè)項(xiàng)目的同行人。


          彼時(shí),另外一位年輕人趙成進(jìn)入了這個(gè)團(tuán)隊(duì),并在實(shí)習(xí)的半年多時(shí)間里開發(fā)了跨平臺(tái)的UI庫功能,以及對(duì)Node-Webkit進(jìn)行了一系列的修改與完善。


          后來,趙成離開了,但是探索的腳步并未停止。之后,趙成幫助github團(tuán)隊(duì)嘗試把Node-Webki應(yīng)用到Atom編輯器上。不過因項(xiàng)目穩(wěn)定性等原因,最終這個(gè)嘗試以失敗告終。直至2013年,GitHub與趙成啟動(dòng)了一個(gè)類似Node-Webkit的項(xiàng)目:Atom Shell。不久后,基于Atom 的框架,Electron被開發(fā)出來。2014年,這兩個(gè)項(xiàng)目正式開源。(此處背景源自網(wǎng)絡(luò)公開資料)



          時(shí)至今日,Electron被Atom編輯器和許多其他應(yīng)用程序使用,并且兼容Mac、Windows 和 Linux,可以構(gòu)建出三個(gè)平臺(tái)的應(yīng)用程序。其中,這兩年爆火的飛書就是基于這個(gè)技術(shù)開發(fā)而來,另外,VS Code、Slack等應(yīng)用也是基于這個(gè)技術(shù)開發(fā)。

          02


          推薦一個(gè)GVP項(xiàng)目


          初步了解這項(xiàng)技術(shù)的魅力及應(yīng)用歷程之后,今天,勤奮好學(xué)的馬建倉就給大家推薦一款以Electron為核心的入門簡單、功能豐富的 js 跨平臺(tái)桌面軟件開發(fā)框架——electron-egg


          項(xiàng)目概述


          • 項(xiàng)目簡介:

          1. 這是一套代碼,可以打包成Windows版、Mac版、Linux版或者以web網(wǎng)站運(yùn)行,你只需要學(xué)習(xí)js語言即可,不過也同時(shí)支持Vue、React、EJS等前端技術(shù)。


          2. 從理論上來說,electron-egg支持任何前端技術(shù),能夠編寫出精美的UI效果。

          3. 同時(shí),該項(xiàng)目也可以用服務(wù)端的開發(fā)思維,來編寫桌面軟件,更重要的是還能同時(shí)啟動(dòng)多個(gè)工作進(jìn)程。


          • 開源許可證:Apache-2.0
          • 項(xiàng)目作者:哆啦好夢(mèng)
          • 項(xiàng)目地址:electron-egg: 一個(gè)入門簡單、功能豐富的 js 跨平臺(tái)桌面軟件開發(fā)框架。

          操作準(zhǔn)備


          正式操作之前,你需要了解以下三個(gè)知識(shí)


          • 進(jìn)程:框架有三種進(jìn)程,即electron主進(jìn)程、electron渲染進(jìn)程及egg服務(wù)進(jìn)程。
          • 業(yè)務(wù)調(diào)用,需要了解從前端-> egg業(yè)務(wù)、前端->electron業(yè)務(wù)等,并自由組合使用
          • 掌握的開發(fā)語言:javascript、Node.js


          使用流程



          向上滑動(dòng)閱覽

          1.選擇開發(fā)模式:打開配置文件:electron-egg/electron/config.js。

          2. 啟動(dòng):

          (1) vue模式【默認(rèn)】;

          (2) react模式,同vue;

          (3) ejs模式,模板渲染

          3. 編寫一個(gè)api,供前端使用。

          4. 如果是ejs模板渲染方式,編寫一個(gè)hello頁面。

          5. 打包成exe、dmg、deb可執(zhí)行文件。


          場(chǎng)景演示

          使用Electron開發(fā)客戶端程序已經(jīng)有一段時(shí)間了,整體感覺還是非常不錯(cuò)的,其中也遇到了一些坑點(diǎn),本文是從【運(yùn)行原理】到【實(shí)際應(yīng)用】對(duì)Electron進(jìn)行一次系統(tǒng)性的總結(jié)。【多圖,長文預(yù)警~】

          本文所有實(shí)例代碼均在我的github electron-react上,結(jié)合代碼閱讀文章效果更佳。另外electron-react還可作為使用Electron + React + Mobx + Webpack技術(shù)棧的腳手架工程。

          github:https://github.com/ConardLi/electron-react

          一、桌面應(yīng)用程序



          桌面應(yīng)用程序,又稱為 GUI 程序(Graphical User Interface),但是和 GUI 程序也有一些區(qū)別。桌面應(yīng)用程序 將 GUI 程序從GUI 具體為“桌面”,使冷冰冰的像塊木頭一樣的電腦概念更具有 人性化,更生動(dòng)和富有活力。

          我們電腦上使用的各種客戶端程序都屬于桌面應(yīng)用程序,近年來WEB和移動(dòng)端的興起讓桌面程序漸漸暗淡,但是在某些日常功能或者行業(yè)應(yīng)用中桌面應(yīng)用程序仍然是必不可少的。

          傳統(tǒng)的桌面應(yīng)用開發(fā)方式,一般是下面兩種:

          1.1 原生開發(fā)

          直接將語言編譯成可執(zhí)行文件,直接調(diào)用系統(tǒng)API,完成UI繪制等。這類開發(fā)技術(shù),有著較高的運(yùn)行效率,但一般來說,開發(fā)速度較慢,技術(shù)要求較高,例如:

          • 使用C++ / MFC開發(fā)Windows應(yīng)用
          • 使用Objective-C開發(fā)MAC應(yīng)用

          1.2 托管平臺(tái)

          一開始就有本地開發(fā)和UI開發(fā)。一次編譯后,得到中間文件,通過平臺(tái)或虛機(jī)完成二次加載編譯或解釋運(yùn)行。運(yùn)行效率低于原生編譯,但平臺(tái)優(yōu)化后,其效率也是比較可觀的。就開發(fā)速度方面,比原生編譯技術(shù)要快一些。例如:

          • 使用C# / .NET Framework(只能開發(fā)Windows應(yīng)用)
          • Java / Swing

          不過,上面兩種對(duì)前端開發(fā)人員太不友好了,基本是前端人員不會(huì)涉及的領(lǐng)域,但是在這個(gè)【大前端】的時(shí)代,前端開發(fā)者正在想方設(shè)法涉足各個(gè)領(lǐng)域,使用WEB技術(shù)開發(fā)客戶端的方式橫空出世。

          1.3 WEB開發(fā)

          使用WEB技術(shù)進(jìn)行開發(fā),利用瀏覽器引擎完成UI渲染,利用Node.js實(shí)現(xiàn)服務(wù)器端JS編程并可以調(diào)用系統(tǒng)API,可以把它想像成一個(gè)套了一個(gè)客戶端外殼的WEB應(yīng)用。

          在界面上,WEB的強(qiáng)大生態(tài)為UI帶來了無限可能,并且開發(fā)、維護(hù)成本相對(duì)較低,有WEB開發(fā)經(jīng)驗(yàn)的前端開發(fā)者很容易上手進(jìn)行開發(fā)。

          本文就來著重介紹使用WEB技術(shù)開發(fā)客戶端程序的技術(shù)之一【electron】

          二、Electron



          Electron是由Github開發(fā),用HTML,CSS和JavaScript來構(gòu)建跨平臺(tái)桌面應(yīng)用程序的一個(gè)開源庫。 Electron通過將Chromium和Node.js合并到同一個(gè)運(yùn)行時(shí)環(huán)境中,并將其打包為Mac,Windows和Linux系統(tǒng)下的應(yīng)用來實(shí)現(xiàn)這一目的。

          2.1 使用Electron開發(fā)的理由:

          • 使用具有強(qiáng)大生態(tài)的Web技術(shù)進(jìn)行開發(fā),開發(fā)成本低,可擴(kuò)展性強(qiáng),更炫酷的UI
          • 跨平臺(tái),一套代碼可打包為Windows、Linux、Mac三套軟件,且編譯快速
          • 可直接在現(xiàn)有Web應(yīng)用上進(jìn)行擴(kuò)展,提供瀏覽器不具備的能力
          • 你是一個(gè)前端~

          當(dāng)然,我們也要認(rèn)清它的缺點(diǎn):性能比原生桌面應(yīng)用要低,最終打包后的應(yīng)用比原生應(yīng)用大很多。

          2.2 開發(fā)體驗(yàn)

          兼容性

          雖然你還在用WEB技術(shù)進(jìn)行開發(fā),但是你不用再考慮兼容性問題了,你只需要關(guān)心你當(dāng)前使用Electron的版本對(duì)應(yīng)Chrome的版本,一般情況下它已經(jīng)足夠新來讓你使用最新的API和語法了,你還可以手動(dòng)升級(jí)Chrome版本。同樣的,你也不用考慮不同瀏覽器帶的樣式和代碼兼容問題。

          Node環(huán)境

          這可能是很多前端開發(fā)者曾經(jīng)夢(mèng)想過的功能,在WEB界面中使用Node.js提供的強(qiáng)大API,這意味著你在WEB頁面直接可以操作文件,調(diào)用系統(tǒng)API,甚至操作數(shù)據(jù)庫。當(dāng)然,除了完整的Node API,你還可以使用額外的幾十萬個(gè)npm模塊。

          跨域

          你可以直接使用Node提供的request模塊進(jìn)行網(wǎng)絡(luò)請(qǐng)求,這意味著你無需再被跨域所困擾。

          強(qiáng)大的擴(kuò)展性

          借助node-ffi,為應(yīng)用程序提供強(qiáng)大的擴(kuò)展性(后面的章節(jié)會(huì)詳細(xì)介紹)。

          2.3 誰在用Electron


          現(xiàn)在市面上已經(jīng)有非常多的應(yīng)用在使用Electron進(jìn)行開發(fā)了,包括我們熟悉的VS Code客戶端、GitHub客戶端、Atom客戶端等等。印象很深的,去年迅雷在發(fā)布迅雷X10.1時(shí)的文案:

          從迅雷X 10.1版本開始,我們采用Electron軟件框架完全重寫了迅雷主界面。使用新框架的迅雷X可以完美支持2K、4K等高清顯示屏,界面中的文字渲染也更加清晰銳利。從技術(shù)層面來說,新框架的界面繪制、事件處理等方面比老框架更加靈活高效,因此界面的流暢度也顯著優(yōu)于老框架的迅雷。至于具體提升有多大?您一試便知。

          你可以打開VS Code,點(diǎn)擊【幫助】【切換開發(fā)人員工具】來調(diào)試VS Code客戶端的界面。


          三、Electron運(yùn)行原理



          Electron 結(jié)合了 Chromium、Node.js 和用于調(diào)用操作系統(tǒng)本地功能的API。

          3.1 Chromium

          Chromium是Google為發(fā)展Chrome瀏覽器而啟動(dòng)的開源項(xiàng)目,Chromium相當(dāng)于Chrome的工程版或稱實(shí)驗(yàn)版,新功能會(huì)率先在Chromium上實(shí)現(xiàn),待驗(yàn)證后才會(huì)應(yīng)用在Chrome上,故Chrome的功能會(huì)相對(duì)落后但較穩(wěn)定。

          Chromium為Electron提供強(qiáng)大的UI能力,可以在不考慮兼容性的情況下開發(fā)界面。

          3.2 Node.js

          Node.js是一個(gè)讓JavaScript運(yùn)行在服務(wù)端的開發(fā)平臺(tái),Node使用事件驅(qū)動(dòng),非阻塞I/O模型而得以輕量和高效。

          單單靠Chromium是不能具備直接操作原生GUI能力的,Electron內(nèi)集成了Nodejs,這讓其在開發(fā)界面的同時(shí)也有了操作系統(tǒng)底層API的能力,Nodejs 中常用的 Path、fs、Crypto 等模塊在 Electron 可以直接使用。

          3.3 系統(tǒng)API

          為了提供原生系統(tǒng)的GUI支持,Electron內(nèi)置了原生應(yīng)用程序接口,對(duì)調(diào)用一些系統(tǒng)功能,如調(diào)用系統(tǒng)通知、打開系統(tǒng)文件夾提供支持。

          在開發(fā)模式上,Electron在調(diào)用系統(tǒng)API和繪制界面上是分離開發(fā)的,下面我們來看看Electron關(guān)于進(jìn)程如何劃分。

          3.4 主進(jìn)程

          Electron區(qū)分了兩種進(jìn)程:主進(jìn)程和渲染進(jìn)程,兩者各自負(fù)責(zé)自己的職能。



          Electron 運(yùn)行package.json的 main 腳本的進(jìn)程被稱為主進(jìn)程。一個(gè) Electron 應(yīng)用總是有且只有一個(gè)主進(jìn)程。

          職責(zé):

          • 創(chuàng)建渲染進(jìn)程(可多個(gè))
          • 控制了應(yīng)用生命周期(啟動(dòng)、退出APP以及對(duì)APP做一些事件監(jiān)聽)
          • 調(diào)用系統(tǒng)底層功能、調(diào)用原生資源

          可調(diào)用的API:

          • Node.js API
          • Electron提供的主進(jìn)程API(包括一些系統(tǒng)功能和Electron附加功能)

          3.5 渲染進(jìn)程

          由于 Electron 使用了 Chromium 來展示 web 頁面,所以 Chromium 的多進(jìn)程架構(gòu)也被使用到。 每個(gè)Electron 中的 web頁面運(yùn)行在它自己的渲染進(jìn)程中。

          主進(jìn)程使用 BrowserWindow 實(shí)例創(chuàng)建頁面。 每個(gè) BrowserWindow 實(shí)例都在自己的渲染進(jìn)程里運(yùn)行頁面。 當(dāng)一個(gè) BrowserWindow 實(shí)例被銷毀后,相應(yīng)的渲染進(jìn)程也會(huì)被終止。

          你可以把渲染進(jìn)程想像成一個(gè)瀏覽器窗口,它能存在多個(gè)并且相互獨(dú)立,不過和瀏覽器不同的是,它能調(diào)用Node API。

          職責(zé):

          • 用HTML和CSS渲染界面
          • 用JavaScript做一些界面交互

          可調(diào)用的API:

          • DOM API
          • Node.js API
          • Electron提供的渲染進(jìn)程API

          四、Electron基礎(chǔ)

          4.1 Electron API

          在上面的章節(jié)我們提到,渲染進(jìn)和主進(jìn)程分別可調(diào)用的Electron API。所有Electron的API都被指派給一種進(jìn)程類型。 許多API只能被用于主進(jìn)程中,有些API又只能被用于渲染進(jìn)程,又有一些主進(jìn)程和渲染進(jìn)程中都可以使用。

          你可以通過如下方式獲取Electron API

          const { BrowserWindow, ... }=require('electron')
          復(fù)制代碼

          下面是一些常用的Electron API:


          在后面的章節(jié)我們會(huì)選擇其中常用的模塊進(jìn)行詳細(xì)介紹。

          4.2 使用 Node.js 的 API



          你可以同時(shí)在Electron的主進(jìn)程和渲染進(jìn)程使用Node.js API,)所有在Node.js可以使用的API,在Electron中同樣可以使用。

          import {shell} from 'electron';
          import os from 'os';
          
          document.getElementById('btn').addEventListener('click', ()=> { 
            shell.showItemInFolder(os.homedir());
          })
          復(fù)制代碼

          有一個(gè)非常重要的提示: 原生Node.js模塊 (即指,需要編譯源碼過后才能被使用的模塊) 需要在編譯后才能和Electron一起使用。

          4.3 進(jìn)程通信

          主進(jìn)程和渲染進(jìn)程雖然擁有不同的職責(zé),然是他們也需要相互協(xié)作,互相通訊。

          例如:在web頁面管理原生GUI資源是很危險(xiǎn)的,會(huì)很容易泄露資源。所以在web頁面,不允許直接調(diào)用原生GUI相關(guān)的API。渲染進(jìn)程如果想要進(jìn)行原生的GUI操作,就必須和主進(jìn)程通訊,請(qǐng)求主進(jìn)程來完成這些操作。



          4.4 渲染進(jìn)程向主進(jìn)程通信

          ipcRenderer 是一個(gè) EventEmitter 的實(shí)例。 你可以使用它提供的一些方法,從渲染進(jìn)程發(fā)送同步或異步的消息到主進(jìn)程。 也可以接收主進(jìn)程回復(fù)的消息。

          在渲染進(jìn)程引入ipcRenderer:

          import { ipcRenderer } from 'electron';
          復(fù)制代碼

          異步發(fā)送:

          通過 channel 發(fā)送同步消息到主進(jìn)程,可以攜帶任意參數(shù)。

          在內(nèi)部,參數(shù)會(huì)被序列化為 JSON,因此參數(shù)對(duì)象上的函數(shù)和原型鏈不會(huì)被發(fā)送。

          ipcRenderer.send('async-render', '我是來自渲染進(jìn)程的異步消息');
          復(fù)制代碼

          同步發(fā)送:

           const msg=ipcRenderer.sendSync('sync-render', '我是來自渲染進(jìn)程的同步消息');
          復(fù)制代碼

          注意: 發(fā)送同步消息將會(huì)阻塞整個(gè)渲染進(jìn)程,直到收到主進(jìn)程的響應(yīng)。

          主進(jìn)程監(jiān)聽消息:

          ipcMain模塊是EventEmitter類的一個(gè)實(shí)例。 當(dāng)在主進(jìn)程中使用時(shí),它處理從渲染器進(jìn)程(網(wǎng)頁)發(fā)送出來的異步和同步信息。 從渲染器進(jìn)程發(fā)送的消息將被發(fā)送到該模塊。

          ipcMain.on:監(jiān)聽 channel,當(dāng)接收到新的消息時(shí) listener 會(huì)以 listener(event, args...) 的形式被調(diào)用。

            ipcMain.on('sync-render', (event, data)=> {
              console.log(data);
            });
          復(fù)制代碼

          4.5 主進(jìn)程向渲染進(jìn)程通信

          在主進(jìn)程中可以通過BrowserWindow的webContents向渲染進(jìn)程發(fā)送消息,所以,在發(fā)送消息前你必須先找到對(duì)應(yīng)渲染進(jìn)程的BrowserWindow對(duì)象。:

          const mainWindow=BrowserWindow.fromId(global.mainId);
           mainWindow.webContents.send('main-msg', `ConardLi]`)
          復(fù)制代碼

          根據(jù)消息來源發(fā)送:

          在ipcMain接受消息的回調(diào)函數(shù)中,通過第一個(gè)參數(shù)event的屬性sender可以拿到消息來源渲染進(jìn)程的webContents對(duì)象,我們可以直接用此對(duì)象回應(yīng)消息。

            ipcMain.on('sync-render', (event, data)=> {
              console.log(data);
              event.sender.send('main-msg', '主進(jìn)程收到了渲染進(jìn)程的【異步】消息!')
            });
          復(fù)制代碼

          渲染進(jìn)程監(jiān)聽:

          ipcRenderer.on:監(jiān)聽 channel, 當(dāng)新消息到達(dá),將通過listener(event, args...)調(diào)用 listener。

          ipcRenderer.on('main-msg', (event, msg)=> {
              console.log(msg);
          })
          復(fù)制代碼

          4.6 通信原理

          ipcMain 和 ipcRenderer 都是 EventEmitter 類的一個(gè)實(shí)例。EventEmitter 類是 NodeJS 事件的基礎(chǔ),它由 NodeJS 中的 events 模塊導(dǎo)出。

          EventEmitter 的核心就是事件觸發(fā)與事件監(jiān)聽器功能的封裝。它實(shí)現(xiàn)了事件模型需要的接口, 包括 addListener,removeListener, emit 及其它工具方法. 同原生 JavaScript 事件類似, 采用了發(fā)布/訂閱(觀察者)的方式, 使用內(nèi)部 _events 列表來記錄注冊(cè)的事件處理器。

          我們通過 ipcMain和ipcRenderer 的 on、send 進(jìn)行監(jiān)聽和發(fā)送消息都是 EventEmitter 定義的相關(guān)接口。

          4.7 remote

          remote 模塊為渲染進(jìn)程(web頁面)和主進(jìn)程通信(IPC)提供了一種簡單方法。 使用 remote 模塊, 你可以調(diào)用 main 進(jìn)程對(duì)象的方法, 而不必顯式發(fā)送進(jìn)程間消息, 類似于 Java 的 RMI 。

          import { remote } from 'electron';
          
          remote.dialog.showErrorBox('主進(jìn)程才有的dialog模塊', '我是使用remote調(diào)用的')
          復(fù)制代碼



          但實(shí)際上,我們?cè)谡{(diào)用遠(yuǎn)程對(duì)象的方法、函數(shù)或者通過遠(yuǎn)程構(gòu)造函數(shù)創(chuàng)建一個(gè)新的對(duì)象,實(shí)際上都是在發(fā)送一個(gè)同步的進(jìn)程間消息。

          在上面通過 remote 模塊調(diào)用 dialog 的例子里。我們?cè)阡秩具M(jìn)程中創(chuàng)建的 dialog 對(duì)象其實(shí)并不在我們的渲染進(jìn)程中,它只是讓主進(jìn)程創(chuàng)建了一個(gè) dialog 對(duì)象,并返回了這個(gè)相對(duì)應(yīng)的遠(yuǎn)程對(duì)象給了渲染進(jìn)程。

          4.8 渲染進(jìn)程間通信

          Electron并沒有提供渲染進(jìn)程之間相互通信的方式,我們可以在主進(jìn)程中建立一個(gè)消息中轉(zhuǎn)站。

          渲染進(jìn)程之間通信首先發(fā)送消息到主進(jìn)程,主進(jìn)程的中轉(zhuǎn)站接收到消息后根據(jù)條件進(jìn)行分發(fā)。

          4.9 渲染進(jìn)程數(shù)據(jù)共享

          在兩個(gè)渲染進(jìn)程間共享數(shù)據(jù)最簡單的方法是使用瀏覽器中已經(jīng)實(shí)現(xiàn)的HTML5 API。 其中比較好的方案是用Storage API, localStorage,sessionStorage 或者 IndexedDB。

          就像在瀏覽器中使用一樣,這種存儲(chǔ)相當(dāng)于在應(yīng)用程序中永久存儲(chǔ)了一部分?jǐn)?shù)據(jù)。有時(shí)你并不需要這樣的存儲(chǔ),只需要在當(dāng)前應(yīng)用程序的生命周期內(nèi)進(jìn)行一些數(shù)據(jù)的共享。這時(shí)你可以用 Electron 內(nèi)的 IPC 機(jī)制實(shí)現(xiàn)。

          將數(shù)據(jù)存在主進(jìn)程的某個(gè)全局變量中,然后在多個(gè)渲染進(jìn)程中使用 remote 模塊來訪問它。



          在主進(jìn)程中初始化全局變量:

          global.mainId=...;
          global.device={...};
          global.__dirname=__dirname;
          global.myField={ name: 'ConardLi' };
          復(fù)制代碼

          在渲染進(jìn)程中讀取:

          import { ipcRenderer, remote } from 'electron';
          
          const { getGlobal }=remote;
          
          const mainId=getGlobal('mainId')
          const dirname=getGlobal('__dirname')
          const deviecMac=getGlobal('device').mac;
          復(fù)制代碼

          在渲染進(jìn)程中改變:

          getGlobal('myField').name='code秘密花園';
          復(fù)制代碼

          多個(gè)渲染進(jìn)程共享同一個(gè)主進(jìn)程的全局變量,這樣即可達(dá)到渲染進(jìn)程數(shù)據(jù)共享和傳遞的效果。

          五、窗口

          5.1 BrowserWindow

          主進(jìn)程模塊BrowserWindow用于創(chuàng)建和控制瀏覽器窗口。

            mainWindow=new BrowserWindow({
              width: 1000,
              height: 800,
              // ...
            });
            mainWindow.loadURL('http://www.conardli.top/');
          復(fù)制代碼

          你可以在這里查看它所有的構(gòu)造參數(shù)。



          5.2 無框窗口

          無框窗口是沒有鑲邊的窗口,窗口的部分(如工具欄)不屬于網(wǎng)頁的一部分。

          在BrowserWindow的構(gòu)造參數(shù)中,將frame設(shè)置為false可以指定窗口為無邊框窗口,將工具欄隱藏后,就會(huì)產(chǎn)生兩個(gè)問題:

          • 1.窗口控制按鈕(最小化、全屏、關(guān)閉按鈕)會(huì)被隱藏
          • 2.無法拖拽移動(dòng)窗口

          可以通過指定titleBarStyle選項(xiàng)來再將工具欄按鈕顯示出來,將其設(shè)置為hidden表示返回一個(gè)隱藏標(biāo)題欄的全尺寸內(nèi)容窗口,在左上角仍然有標(biāo)準(zhǔn)的窗口控制按鈕。

          new BrowserWindow({
              width: 200,
              height: 200,
              titleBarStyle: 'hidden',
              frame: false
            });
          復(fù)制代碼

          5.3 窗口拖拽

          默認(rèn)情況下, 無邊框窗口是不可拖拽的。我們可以在界面中通過CSS屬性-webkit-app-region: drag手動(dòng)制定拖拽區(qū)域。

          在無框窗口中, 拖動(dòng)行為可能與選擇文本沖突,可以通過設(shè)定-webkit-user-select: none;禁用文本選擇:

          .header {
            -webkit-user-select: none;
            -webkit-app-region: drag;
          }
          復(fù)制代碼

          相反的,在可拖拽區(qū)域內(nèi)部設(shè)置 -webkit-app-region: no-drag則可以指定特定不可拖拽區(qū)域。

          5.4 透明窗口

          通過將transparent選項(xiàng)設(shè)置為true, 還可以使無框窗口透明:

          new BrowserWindow({
              transparent: true,
              frame: false
            });
          復(fù)制代碼

          5.5 Webview

          使用 webview 標(biāo)簽在Electron 應(yīng)用中嵌入 "外來" 內(nèi)容。外來內(nèi)容包含在 webview 容器中。 應(yīng)用中的嵌入頁面可以控制外來內(nèi)容的布局和重繪。

          與 iframe 不同, webview 在與應(yīng)用程序不同的進(jìn)程中運(yùn)行。它與您的網(wǎng)頁沒有相同的權(quán)限, 應(yīng)用程序和嵌入內(nèi)容之間的所有交互都將是異步的。

          六、對(duì)話框

          dialog 模塊提供了api來展示原生的系統(tǒng)對(duì)話框,例如打開文件框,alert框,所以web應(yīng)用可以給用戶帶來跟系統(tǒng)應(yīng)用相同的體驗(yàn)。

          注意:dialog是主進(jìn)程模塊,想要在渲染進(jìn)程調(diào)用可以使用remote



          6.1 錯(cuò)誤提示

          dialog.showErrorBox用于顯示一個(gè)顯示錯(cuò)誤消息的模態(tài)對(duì)話框。

           remote.dialog.showErrorBox('錯(cuò)誤', '這是一個(gè)錯(cuò)誤彈框!')
          復(fù)制代碼

          6.2 對(duì)話框

          dialog.showErrorBox用于調(diào)用系統(tǒng)對(duì)話框,可以為指定幾種不同的類型: "none", "info", "error", "question" 或者 "warning"。

          在 Windows 上, "question" 與"info"顯示相同的圖標(biāo), 除非你使用了 "icon" 選項(xiàng)設(shè)置圖標(biāo)。 在 macOS 上, "warning" 和 "error" 顯示相同的警告圖標(biāo)

          remote.dialog.showMessageBox({
            type: 'info',
            title: '提示信息',
            message: '這是一個(gè)對(duì)話彈框!',
            buttons: ['確定', '取消']
          }, (index)=> {
            this.setState({ dialogMessage: `【你點(diǎn)擊了${index ? '取消' : '確定'}!!】` })
          })
          復(fù)制代碼

          6.3 文件框

          dialog.showOpenDialog用于打開或選擇系統(tǒng)目錄。

          remote.dialog.showOpenDialog({
            properties: ['openDirectory', 'openFile']
          }, (data)=> {
            this.setState({ filePath: `【選擇路徑:${data[0]}】 ` })
          })
          復(fù)制代碼

          6.4 信息框

          這里推薦直接使用HTML5 API,它只能在渲染器進(jìn)程中使用。

          let options={
            title: '信息框標(biāo)題',
            body: '我是一條信息~~~',
          }
          let myNotification=new window.Notification(options.title, options)
          myNotification.onclick=()=> {
            this.setState({ message: '【你點(diǎn)擊了信息框!!】' })
          }
          復(fù)制代碼

          七、系統(tǒng)

          7.1 獲取系統(tǒng)信息



          通過remote獲取到主進(jìn)程的process對(duì)象,可以獲取到當(dāng)前應(yīng)用的各個(gè)版本信息:

          • process.versions.electron:electron版本信息
          • process.versions.chrome:chrome版本信息
          • process.versions.node:node版本信息
          • process.versions.v8:v8版本信息

          獲取當(dāng)前應(yīng)用根目錄:

          remote.app.getAppPath()
          復(fù)制代碼

          使用node的os模塊獲取當(dāng)前系統(tǒng)根目錄:

          os.homedir();
          復(fù)制代碼

          7.2 復(fù)制粘貼



          Electron提供的clipboard在渲染進(jìn)程和主進(jìn)程都可使用,用于在系統(tǒng)剪貼板上執(zhí)行復(fù)制和粘貼操作。

          以純文本的形式寫入剪貼板:

          clipboard.writeText(text[, type])
          復(fù)制代碼

          以純文本的形式獲取剪貼板的內(nèi)容:

          clipboard.readText([type])
          復(fù)制代碼

          7.3 截圖

          desktopCapturer用于從桌面捕獲音頻和視頻的媒體源的信息。它只能在渲染進(jìn)程中被調(diào)用。



          下面的代碼是一個(gè)獲取屏幕截圖并保存的實(shí)例:

            getImg=()=> {
              this.setState({ imgMsg: '正在截取屏幕...' })
              const thumbSize=this.determineScreenShotSize()
              let options={ types: ['screen'], thumbnailSize: thumbSize }
              desktopCapturer.getSources(options, (error, sources)=> {
                if (error) return console.log(error)
                sources.forEach((source)=> {
                  if (source.name==='Entire screen' || source.name==='Screen 1') {
                    const screenshotPath=path.join(os.tmpdir(), 'screenshot.png')
                    fs.writeFile(screenshotPath, source.thumbnail.toPNG(), (error)=> {
                      if (error) return console.log(error)
                      shell.openExternal(`file://${screenshotPath}`)
                      this.setState({ imgMsg: `截圖保存到: ${screenshotPath}` })
                    })
                  }
                })
              })
            }
          
            determineScreenShotSize=()=> {
              const screenSize=screen.getPrimaryDisplay().workAreaSize
              const maxDimension=Math.max(screenSize.width, screenSize.height)
              return {
                width: maxDimension * window.devicePixelRatio,
                height: maxDimension * window.devicePixelRatio
              }
            }
          
          復(fù)制代碼

          八、菜單

          應(yīng)用程序的菜單可以幫助我們快捷的到達(dá)某一功能,而不借助客戶端的界面資源,一般菜單分為兩種:

          • 應(yīng)用程序菜單:位于應(yīng)用程序頂部,在全局范圍內(nèi)都能使用
          • 上下文菜單:可自定義任意頁面顯示,自定義調(diào)用,如右鍵菜單

          Electron為我們提供了Menu模塊用于創(chuàng)建本機(jī)應(yīng)用程序菜單和上下文菜單,它是一個(gè)主進(jìn)程模塊。

          你可以通過Menu的靜態(tài)方法buildFromTemplate(template),使用自定義菜單模版來構(gòu)造一個(gè)菜單對(duì)象。

          template是一個(gè)MenuItem的數(shù)組,我們來看看MenuItem的幾個(gè)重要參數(shù):

          • label:菜單顯示的文字
          • click:點(diǎn)擊菜單后的事件處理函數(shù)
          • role:系統(tǒng)預(yù)定義的菜單,例如copy(復(fù)制)、paste(粘貼)、minimize(最小化)...
          • enabled:指示是否啟用該項(xiàng)目,此屬性可以動(dòng)態(tài)更改
          • submenu:子菜單,也是一個(gè)MenuItem的數(shù)組

          推薦:最好指定role與標(biāo)準(zhǔn)角色相匹配的任何菜單項(xiàng),而不是嘗試手動(dòng)實(shí)現(xiàn)click函數(shù)中的行為。內(nèi)置role行為將提供最佳的本地體驗(yàn)。

          下面的實(shí)例是一個(gè)簡單的額菜單template。

          const template=[
            {
              label: '文件',
              submenu: [
                {
                  label: '新建文件',
                  click: function () {
                    dialog.showMessageBox({
                      type: 'info',
                      message: '嘿!',
                      detail: '你點(diǎn)擊了新建文件!',
                    })
                  }
                }
              ]
            },
            {
              label: '編輯',
              submenu: [{
                label: '剪切',
                role: 'cut'
              }, {
                label: '復(fù)制',
                role: 'copy'
              }, {
                label: '粘貼',
                role: 'paste'
              }]
            },
            {
              label: '最小化',
              role: 'minimize'
            }
          ]
          復(fù)制代碼

          8.1 應(yīng)用程序菜單

          使用Menu的靜態(tài)方法setApplicationMenu,可創(chuàng)建一個(gè)應(yīng)用程序菜單,在 Windows 和 Linux 上,menu將被設(shè)置為每個(gè)窗口的頂層菜單。

          注意:必須在模塊ready事件后調(diào)用此 API app。

          我們可以根據(jù)應(yīng)用程序不同的的生命周期,不同的系統(tǒng)對(duì)菜單做不同的處理。


          app.on('ready', function () {
            const menu=Menu.buildFromTemplate(template)
            Menu.setApplicationMenu(menu)
          })
          
          app.on('browser-window-created', function () {
            let reopenMenuItem=findReopenMenuItem()
            if (reopenMenuItem) reopenMenuItem.enabled=false
          })
          
          app.on('window-all-closed', function () {
            let reopenMenuItem=findReopenMenuItem()
            if (reopenMenuItem) reopenMenuItem.enabled=true
          })
          
          if (process.platform==='win32') {
            const helpMenu=template[template.length - 1].submenu
            addUpdateMenuItems(helpMenu, 0)
          }
          復(fù)制代碼

          8.2 上下文菜單

          使用Menu的實(shí)例方法menu.popup可自定義彈出上下文菜單。



              let m=Menu.buildFromTemplate(template)
              document.getElementById('menuDemoContainer').addEventListener('contextmenu', (e)=> {
                e.preventDefault()
                m.popup({ window: remote.getCurrentWindow() })
              })
          復(fù)制代碼

          8.3 快捷鍵

          在菜單選項(xiàng)中,我們可以指定一個(gè)accelerator屬性來指定操作的快捷鍵:

            {
              label: '最小化',
              accelerator: 'CmdOrCtrl+M',
              role: 'minimize'
            }
          復(fù)制代碼

          另外,我們還可以使用globalShortcut來注冊(cè)全局快捷鍵。

              globalShortcut.register('CommandOrControl+N', ()=> {
                dialog.showMessageBox({
                  type: 'info',
                  message: '嘿!',
                  detail: '你觸發(fā)了手動(dòng)注冊(cè)的快捷鍵.',
                })
              })
          復(fù)制代碼

          CommandOrControl代表在macOS上為Command鍵,以及在Linux和Windows上為Control鍵。

          九、打印

          很多情況下程序中使用的打印都是用戶無感知的。并且想要靈活的控制打印內(nèi)容,往往需要借助打印機(jī)給我們提供的api再進(jìn)行開發(fā),這種開發(fā)方式非常繁瑣,并且開發(fā)難度較大。第一次在業(yè)務(wù)中用到Electron其實(shí)就是用到它的打印功能,這里就多介紹一些。

          Electron提供的打印api可以非常靈活的控制打印設(shè)置的顯示,并且可以通過html來書寫打印內(nèi)容。Electron提供了兩種方式進(jìn)行打印,一種是直接調(diào)用打印機(jī)打印,一種是打印到pdf。

          并且有兩種對(duì)象可以調(diào)用打印:

          • 通過window的webcontent對(duì)象,使用此種方式需要單獨(dú)開出一個(gè)打印的窗口,可以將該窗口隱藏,但是通信調(diào)用相對(duì)復(fù)雜。
          • 使用頁面的webview元素調(diào)用打印,可以將webview隱藏在調(diào)用的頁面中,通信方式比較簡單。

          上面兩種方式同時(shí)擁有print和printToPdf方法。



          9.1 調(diào)用系統(tǒng)打印

          contents.print([options], [callback]);
          復(fù)制代碼

          打印配置(options)中只有簡單的三個(gè)配置:

          • silent:打印時(shí)是否不展示打印配置(是否靜默打印)
          • printBackground:是否打印背景
          • deviceName:打印機(jī)設(shè)備名稱

          首先要將我們使用的打印機(jī)名稱配置好,并且要在調(diào)用打印前首先要判斷打印機(jī)是否可用。

          使用webContents的getPrinters方法可獲取當(dāng)前設(shè)備已經(jīng)配置的打印機(jī)列表,注意配置過不是可用,只是在此設(shè)備上安裝過驅(qū)動(dòng)。

          通過getPrinters獲取到的打印機(jī)對(duì)象:electronjs.org/docs/api/st…

          我們這里只管關(guān)心兩個(gè),name和status,status為0時(shí)表示打印機(jī)可用。

          print的第二個(gè)參數(shù)callback是用于判斷打印任務(wù)是否發(fā)出的回調(diào),而不是打印任務(wù)完成后的回調(diào)。所以一般打印任務(wù)發(fā)出,回調(diào)函數(shù)即會(huì)調(diào)用并返回參數(shù)true。這個(gè)回調(diào)并不能判斷打印是否真的成功了。

              if (this.state.curretnPrinter) {
                mainWindow.webContents.print({
                  silent: silent, printBackground: true, deviceName: this.state.curretnPrinter
                }, ()=> { })
              } else {
                remote.dialog.showErrorBox('錯(cuò)誤', '請(qǐng)先選擇一個(gè)打印機(jī)!')
              }
          復(fù)制代碼

          9.2 打印到PDF

          printToPdf的用法基本和print相同,但是print的配置項(xiàng)非常少,而printToPdf則擴(kuò)展了很多屬性。這里翻了一下源碼發(fā)現(xiàn)還有很多沒有被貼進(jìn)文檔的,大概有三十幾個(gè),包括可以對(duì)打印的margin,打印頁眉頁腳等進(jìn)行配置。

          contents.printToPDF(options, callback)
          復(fù)制代碼

          callback函數(shù)在打印失敗或打印成功后調(diào)用,可獲取打印失敗信息或包含PDF數(shù)據(jù)的緩沖區(qū)。

              const pdfPath=path.join(os.tmpdir(), 'webviewPrint.pdf');
              const webview=document.getElementById('printWebview');
              const renderHtml='我是被臨時(shí)插入webview的內(nèi)容...';
              webview.executeJavaScript('document.documentElement.innerHTML=`' + renderHtml + '`;');
              webview.printToPDF({}, (err, data)=> {
                console.log(err, data);
                fs.writeFile(pdfPath, data, (error)=> {
                  if (error) throw error
                  shell.openExternal(`file://${pdfPath}`)
                  this.setState({ webviewPdfPath: pdfPath })
                });
              });
          復(fù)制代碼

          這個(gè)例子中的打印是使用webview完成的,通過調(diào)用executeJavaScript方法可動(dòng)態(tài)向webview插入打印內(nèi)容。

          9.3 兩種打印方案的選擇

          上面提到,使用webview和webcontent都可以調(diào)用打印功能,使用webcontent打印,首先要有一個(gè)打印窗口,這個(gè)窗口不能隨時(shí)打印隨時(shí)創(chuàng)建,比較耗費(fèi)性能。可以將它在程序運(yùn)行時(shí)啟動(dòng)好,并做好事件監(jiān)聽。

          此過程需和調(diào)用打印的進(jìn)行做好通信,大致過程如下:



          可見通信非常繁瑣,使用webview進(jìn)行打印可實(shí)現(xiàn)同樣的效果但是通信方式會(huì)變得簡單,因?yàn)殇秩具M(jìn)程和webview通信不需要經(jīng)過主進(jìn)程,通過如下方式即可:

            const webview=document.querySelector('webview')
            webview.addEventListener('ipc-message', (event)=> {
              console.log(event.channel)
            })
            webview.send('ping');
          
            const {ipcRenderer}=require('electron')
            ipcRenderer.on('ping', ()=> {
              ipcRenderer.sendToHost('pong')
            })
          復(fù)制代碼

          之前專門為ELectron打印寫過一個(gè)DEMO:electron-print-demo有興趣可以clone下來看一下。

          9.4 打印功能封裝

          下面是幾個(gè)針對(duì)常用打印功能的工具函數(shù)封裝。

          /**
           * 獲取系統(tǒng)打印機(jī)列表
           */
          export function getPrinters() {
            let printers=[];
            try {
              const contents=remote.getCurrentWindow().webContents;
              printers=contents.getPrinters();
            } catch (e) {
              console.error('getPrintersError', e);
            }
            return printers;
          }
          /**
           * 獲取系統(tǒng)默認(rèn)打印機(jī)
           */
          export function getDefaultPrinter() {
            return getPrinters().find(element=> element.isDefault);
          }
          /**
           * 檢測(cè)是否安裝了某個(gè)打印驅(qū)動(dòng)
           */
          export function checkDriver(driverMame) {
            return getPrinters().find(element=> (element.options["printer-make-and-model"] || '').includes(driverMame));
          }
          /**
           * 根據(jù)打印機(jī)名稱獲取打印機(jī)對(duì)象
           */
          export function getPrinterByName(name) {
            return getPrinters().find(element=> element.name===name);
          }
          
          復(fù)制代碼

          十、程序保護(hù)



          10.1 崩潰

          崩潰監(jiān)控是每個(gè)客戶端程序必備的保護(hù)功能,當(dāng)程序崩潰時(shí)我們一般期望做到兩件事:

          • 1.上傳崩潰日志,及時(shí)報(bào)警
          • 2.監(jiān)控程序崩潰,提示用戶重啟程序

          electron為我們提供給了crashReporter來幫助我們記錄崩潰日志,我們可以通過crashReporter.start來創(chuàng)建一個(gè)崩潰報(bào)告器:

          const { crashReporter }=require('electron')
          crashReporter.start({
            productName: 'YourName',
            companyName: 'YourCompany',
            submitURL: 'https://your-domain.com/url-to-submit',
            uploadToServer: true
          })
          復(fù)制代碼

          當(dāng)程序發(fā)生崩潰時(shí),崩潰報(bào)日志將被儲(chǔ)存在臨時(shí)文件夾中名為YourName Crashes的文件文件夾中。submitURL用于指定你的崩潰日志上傳服務(wù)器。 在啟動(dòng)崩潰報(bào)告器之前,您可以通過調(diào)用app.setPath('temp', 'my/custom/temp')API來自定義這些臨時(shí)文件的保存路徑。你還可以通過crashReporter.getLastCrashReport()來獲取上次崩潰報(bào)告的日期和ID。

          我們可以通過webContents的crashed來監(jiān)聽渲染進(jìn)程的崩潰,另外經(jīng)測(cè)試有些主進(jìn)程的崩潰也會(huì)觸發(fā)該事件。所以我們可以根據(jù)主window是否被銷毀來判斷進(jìn)行不同的重啟邏輯,下面是整個(gè)崩潰監(jiān)控的邏輯:

          import { BrowserWindow, crashReporter, dialog } from 'electron';
          // 開啟進(jìn)程崩潰記錄
          crashReporter.start({
            productName: 'electron-react',
            companyName: 'ConardLi',
            submitURL: 'http://xxx.com',  // 上傳崩潰日志的接口
            uploadToServer: false
          });
          function reloadWindow(mainWin) {
            if (mainWin.isDestroyed()) {
              app.relaunch();
              app.exit(0);
            } else {
              // 銷毀其他窗口
              BrowserWindow.getAllWindows().forEach((w)=> {
                if (w.id !==mainWin.id) w.destroy();
              });
              const options={
                type: 'info',
                title: '渲染器進(jìn)程崩潰',
                message: '這個(gè)進(jìn)程已經(jīng)崩潰.',
                buttons: ['重載', '關(guān)閉']
              }
              dialog.showMessageBox(options, (index)=> {
                if (index===0) mainWin.reload();
                else mainWin.close();
              })
            }
          }
          export default function () {
            const mainWindow=BrowserWindow.fromId(global.mainId);
            mainWindow.webContents.on('crashed', ()=> {
              const errorMessage=crashReporter.getLastCrashReport();
              console.error('程序崩潰了!', errorMessage); // 可單獨(dú)上傳日志
              reloadWindow(mainWindow);
            });
          }
          復(fù)制代碼

          10.2 最小化到托盤

          有的時(shí)候我們并不想讓用戶通過點(diǎn)關(guān)閉按鈕的時(shí)候就關(guān)閉程序,而是把程序最小化到托盤,在托盤上做真正的退出操作。

          首先要監(jiān)聽窗口的關(guān)閉事件,阻止用戶關(guān)閉操作的默認(rèn)行為,將窗口隱藏。

          function checkQuit(mainWindow, event) {
            const options={
              type: 'info',
              title: '關(guān)閉確認(rèn)',
              message: '確認(rèn)要最小化程序到托盤嗎?',
              buttons: ['確認(rèn)', '關(guān)閉程序']
            };
            dialog.showMessageBox(options, index=> {
              if (index===0) {
                event.preventDefault();
                mainWindow.hide();
              } else {
                mainWindow=null;
                app.exit(0);
              }
            });
          }
          function handleQuit() {
            const mainWindow=BrowserWindow.fromId(global.mainId);
            mainWindow.on('close', event=> {
              event.preventDefault();
              checkQuit(mainWindow, event);
            });
          }
          復(fù)制代碼

          這時(shí)程序就再也找不到了,任務(wù)托盤中也沒有我們的程序,所以我們要先創(chuàng)建好任務(wù)托盤,并做好事件監(jiān)聽。

          windows平臺(tái)使用ico文件可以達(dá)到更好的效果

          export default function createTray() {
            const mainWindow=BrowserWindow.fromId(global.mainId);
            const iconName=process.platform==='win32' ? 'icon.ico' : 'icon.png'
            tray=new Tray(path.join(global.__dirname, iconName));
            const contextMenu=Menu.buildFromTemplate([
              {
                label: '顯示主界面', click: ()=> {
                  mainWindow.show();
                  mainWindow.setSkipTaskbar(false);
                }
              },
              {
                label: '退出', click: ()=> {
                  mainWindow.destroy();
                  app.quit();
                }
              },
            ])
            tray.setToolTip('electron-react');
            tray.setContextMenu(contextMenu);
          }
          復(fù)制代碼

          十一、擴(kuò)展能力



          在很多情況下,你的應(yīng)用程序要和外部設(shè)備進(jìn)行交互,一般情況下廠商會(huì)為你提供硬件設(shè)備的開發(fā)包,這些開發(fā)包基本上都是通過C++ 編寫,在使用electron開發(fā)的情況下,我們并不具備直接調(diào)用C++代碼的能力,我們可以利用node-ffi來實(shí)現(xiàn)這一功能。

          node-ffi提供了一組強(qiáng)大的工具,用于在Node.js環(huán)境中使用純JavaScript調(diào)用動(dòng)態(tài)鏈接庫接口。它可以用來為庫構(gòu)建接口綁定,而不需要使用任何C++代碼。

          注意node-ffi并不能直接調(diào)用C++代碼,你需要將C++代碼編譯為動(dòng)態(tài)鏈接庫:在 Windows下是 Dll ,在 Mac OS下是 dylib ,Linux 是 so 。

          node-ffi 加載 Library是有限制的,只能處理 C風(fēng)格的 Library。

          下面是一個(gè)簡單的實(shí)例:

          const ffi=require('ffi');
          const ref=require('ref');
          const SHORT_CODE=ref.refType('short');
          
          
          const DLL=new ffi.Library('test.dll', {
              Test_CPP_Method: ['int', ['string',SHORT_CODE]], 
            })
          
          testCppMethod(str: String, num: number): void {
            try {
              const result: any=DLL.Test_CPP_Method(str, num);
              return result;
            } catch (error) {
              console.log('調(diào)用失敗~',error);
            }
          }
          
          this.testCppMethod('ConardLi',123);
          復(fù)制代碼

          上面的代碼中,我們用ffi包裝C++接口生成的動(dòng)態(tài)鏈接庫test.dll,并使用ref進(jìn)行一些類型映射。

          使用JavaScript調(diào)用這些映射方法時(shí),推薦使用TypeScript來約定參數(shù)類型,因?yàn)槿躅愋偷腏avaScript在調(diào)用強(qiáng)類型語言的接口時(shí)可能會(huì)帶來意想不到的風(fēng)險(xiǎn)。

          借助這一能力,前端開發(fā)工程師也可以在IOT領(lǐng)域一展身手了~

          十二、環(huán)境選擇

          一般情況下,我們的應(yīng)用程序可能運(yùn)行在多套環(huán)境下(production、beta、uat、moke、development...),不同的開發(fā)環(huán)境可能對(duì)應(yīng)不同的后端接口或者其他配置,我們可以在客戶端程序中內(nèi)置一個(gè)簡單的環(huán)境選擇功能來幫助我們更高效的開發(fā)。


          具體策略如下:



          • 在開發(fā)環(huán)境中,我們直接進(jìn)入環(huán)境選擇頁面,讀取到選擇的環(huán)境后進(jìn)行響應(yīng)的重定向操作
          • 在菜單保留環(huán)境選擇入口,以便在開發(fā)過程中切換
          const envList=["moke", "beta", "development", "production"];
          exports.envList=envList;
          const urlBeta='https://wwww.xxx-beta.com';
          const urlDev='https://wwww.xxx-dev.com';
          const urlProp='https://wwww.xxx-prop.com';
          const urlMoke='https://wwww.xxx-moke.com';
          const path=require('path');
          const pkg=require(path.resolve(global.__dirname, 'package.json'));
          const build=pkg['build-config'];
          exports.handleEnv={
            build,
            currentEnv: 'moke',
            setEnv: function (env) {
              this.currentEnv=env
            },
            getUrl: function () {
              console.log('env:', build.env);
              if (build.env==='production' || this.currentEnv==='production') {
                return urlProp;
              } else if (this.currentEnv==='moke') {
                return urlMoke;
              } else if (this.currentEnv==='development') {
                return urlDev;
              } else if (this.currentEnv==="beta") {
                return urlBeta;
              }
            },
            isDebugger: function () {
              return build.env==='development'
            }
          }
          復(fù)制代碼

          十三、打包

          最后也是最重要的一步,將寫好的代碼打包成可運(yùn)行的.app或.exe可執(zhí)行文件。

          這里我把打包氛圍兩部分來做,渲染進(jìn)程打包和主進(jìn)程打包。

          13.1 渲染進(jìn)程打包和升級(jí)

          一般情況下,我們的大部分業(yè)務(wù)邏輯代碼是在渲染進(jìn)程完成的,在大部分情況下我們僅僅需要對(duì)渲染進(jìn)程進(jìn)行更新和升級(jí)而不需要改動(dòng)主進(jìn)程代碼,我們渲染進(jìn)程的打包實(shí)際上和一般的web項(xiàng)目打包沒有太大差別,使用webpack打包即可。

          這里我說說渲染進(jìn)程單獨(dú)打包的好處:

          打包完成的html和js文件,我們一般要上傳到我們的前端靜態(tài)資源服務(wù)器下,然后告知服務(wù)端我們的渲染進(jìn)程有代碼更新,這里可以說成渲染進(jìn)程單獨(dú)的升級(jí)。

          注意,和殼的升級(jí)不同,渲染進(jìn)程的升級(jí)僅僅是靜態(tài)資源服務(wù)器上html和js文件的更新,而不需要重新下載更新客戶端,這樣我們每次啟動(dòng)程序的時(shí)候檢測(cè)到離線包有更新,即可直接刷新讀取最新版本的靜態(tài)資源文件,即使在程序運(yùn)行過程中要強(qiáng)制更新,我們的程序只需要強(qiáng)制刷新頁面讀取最新的靜態(tài)資源即可,這樣的升級(jí)對(duì)用戶是非常友好的。

          這里注意,一旦我們這樣配置,就意味著渲染進(jìn)程和主進(jìn)程打包升級(jí)的完全分離,我們?cè)趩?dòng)主窗口時(shí)讀取的文件就不應(yīng)該再是本地文件,而是打包完成后放在靜態(tài)資源服務(wù)器的文件。

          為了方便開發(fā),這里我們可以區(qū)分本地和線上加載不同的文件:

          function getVersion (mac,current){
            // 根據(jù)設(shè)備mac和當(dāng)前版本獲取最新版本
          }
          export default function () {
            if (build.env==='production') {
              const version=getVersion (mac,current);
              return 'https://www.xxxserver.html/electron-react/index_'+version+'.html';
            }
            return url.format({
              protocol: 'file:',
              pathname: path.join(__dirname, 'env/environment.html'),
              slashes: true,
              query: { debugger: build.env==="development" }
            });
          }
          復(fù)制代碼

          具體的webpack配置這里就不再貼出,可以到我的github electron-react的/scripts目錄下查看。

          這里需要注意,在開發(fā)環(huán)境下我們可以結(jié)合webpack的devServer和electron命令來啟動(dòng)app:

            devServer: {
              contentBase: './assets/',
              historyApiFallback: true,
              hot: true,
              port: PORT,
              noInfo: false,
              stats: {
                colors: true,
              },
              setup() {
                spawn(
                  'electron',
                  ['.'],
                  {
                    shell: true,
                    stdio: 'inherit',
                  }
                )
                  .on('close', ()=> process.exit(0))
                  .on('error', e=> console.error(e));
              },
            },//...
          復(fù)制代碼

          13.2 主進(jìn)程打包

          主進(jìn)程,即將整個(gè)程序打包成可運(yùn)行的客戶端程序,常用的打包方案一般有兩種,electron-packager和electron-builder。

          electron-packager在打包配置上我覺得有些繁瑣,而且它只能將應(yīng)用直接打包為可執(zhí)行程序。

          這里我推薦使用electron-builder,它不僅擁有方便的配置 protocol 的功能、內(nèi)置的 Auto Update、簡單的配置 package.json 便能完成整個(gè)打包工作,用戶體驗(yàn)非常不錯(cuò)。而且electron-builder不僅能直接將應(yīng)用打包成exe app等可執(zhí)行程序,還能打包成msi dmg等安裝包格式。

          你可以在package.json方便的進(jìn)行各種配置:

           "build": {
             "productName": "electron-react", // app中文名稱
             "appId": "electron-react",// app標(biāo)識(shí)
             "directories": { // 打包后輸出的文件夾
               "buildResources": "resources",
               "output": "dist/"
             }
             "files": [ // 打包后依然保留的源文件
               "main_process/",
               "render_process/",
             ],
             "mac": { // mac打包配置
               "target": "dmg",
               "icon": "icon.ico"
             },
             "win": { // windows打包配置
               "target": "nsis",
               "icon": "icon.ico"
             },
             "dmg": { // dmg文件打包配置
               "artifactName": "electron_react.dmg",
               "contents": [
                 {
                   "type": "link",
                   "path": "/Applications",
                   "x": 410,
                   "y": 150
                 },
                 {
                   "type": "file",
                   "x": 130,
                   "y": 150
                 }
               ]
             },
             "nsis": { // nsis文件打包配置
               "oneClick": false,
               "allowToChangeInstallationDirectory": true,
               "shortcutName": "electron-react"
             },
           }
          復(fù)制代碼

          執(zhí)行electron-builder打包命令時(shí),可指定參數(shù)進(jìn)行打包。

           --mac, -m, -o, --macos   macOS打包
           --linux, -l              Linux打包
           --win, -w, --windows     Windows打包
           --mwl                    同時(shí)為macOS,Windows和Linux打包
           --x64                    x64 (64位安裝包)
           --ia32                   ia32(32位安裝包) 
          復(fù)制代碼

          關(guān)于主進(jìn)程的更新你可以使用electron-builder自帶的Auto Update模塊,在electron-react也實(shí)現(xiàn)了手動(dòng)更新的模塊,由于篇幅原因這里就不再贅述,如果有興趣可以到我的github查看main下的update模塊。

          13.3 打包優(yōu)化

          electron-builder打包出來的App要比相同功能的原生客戶端應(yīng)用體積大很多,即使是空的應(yīng)用,體積也要在100mb以上。原因有很多:

          第一點(diǎn);為了達(dá)到跨平臺(tái)的效果,每個(gè)Electron應(yīng)用都包含了整個(gè)V8引擎和Chromium內(nèi)核。

          第二點(diǎn):打包時(shí)會(huì)將整個(gè)node_modules打包進(jìn)去,大家都知道一個(gè)應(yīng)用的node_module體積是非常龐大的,這也是使得Electron應(yīng)用打包后的體積較大的原因。

          第一點(diǎn)我們無法改變,我們可以從第二點(diǎn)對(duì)應(yīng)用體積進(jìn)行優(yōu)化:Electron在打包時(shí)只會(huì)將denpendencies的依賴打包進(jìn)去,而不會(huì)將 devDependencies 中的依賴進(jìn)行打包。所以我們應(yīng)盡可能的減少denpendencies中的依賴。在上面的進(jìn)程中,我們使用webpack對(duì)渲染進(jìn)程進(jìn)行打包,所以渲染進(jìn)程的依賴全部都可以移入devDependencies。

          另外,我們還可以使用雙packajson.json的方式來進(jìn)行優(yōu)化,把只在開發(fā)環(huán)境中使用到的依賴放在整個(gè)項(xiàng)目的根目錄的package.json下,將與平臺(tái)相關(guān)的或者運(yùn)行時(shí)需要的依賴裝在app目錄下。具體詳見two-package-structure。

          參考

          本項(xiàng)目源碼地址:https://github.com/ConardLi/electron-react

          小結(jié)

          希望你閱讀本篇文章后可以達(dá)到以下幾點(diǎn):

          • 了解Electron的基本運(yùn)行原理
          • 掌握Electron開發(fā)的核心基礎(chǔ)知識(shí)
          • 了解Electron關(guān)于彈框、打印、保護(hù)、打包等功能的基本使用

          文中如有錯(cuò)誤,歡迎在評(píng)論區(qū)指正,如果這篇文章幫助到了你,歡迎點(diǎn)贊和關(guān)注。

          想閱讀更多優(yōu)質(zhì)文章、可關(guān)注我的github博客,你的star?、點(diǎn)贊和關(guān)注是我持續(xù)創(chuàng)作的動(dòng)力!

          github:https://github.com/ConardLi/ConardLi.github.io


          原鏈接:https://juejin.im/post/5cfd2ec7e51d45554877a59f


          主站蜘蛛池模板: 麻豆一区二区99久久久久| 国产激情一区二区三区小说| 国产怡春院无码一区二区| 亚洲国产精品一区二区三区久久| 无码一区二区三区免费| 色视频综合无码一区二区三区| 在线日产精品一区| 国产综合精品一区二区三区| 国产丝袜一区二区三区在线观看 | 女同一区二区在线观看| 亚洲综合色自拍一区| 鲁大师成人一区二区三区| 国产精品久久久久一区二区三区 | 精品国产乱码一区二区三区| 国产在线观看一区二区三区精品| 国产乱码精品一区二区三区四川人 | 欧美激情一区二区三区成人| 亚洲一区中文字幕在线观看| 欧洲无码一区二区三区在线观看| 久久精品国产亚洲一区二区| 亚洲毛片αv无线播放一区 | 色一乱一伦一图一区二区精品| A国产一区二区免费入口| 亚洲国产成人久久一区WWW | 国产一区二区三区乱码网站| 亚洲国产一区在线观看| 91精品一区二区三区久久久久| 精品无码人妻一区二区三区品| 日韩人妻无码一区二区三区99| 亚洲国产成人一区二区精品区| 亚洲日本一区二区| 国产精品综合AV一区二区国产馆 | 99国产精品一区二区| 国产观看精品一区二区三区| 中文字幕日韩一区二区三区不| 精品一区二区三区色花堂| 久久国产高清一区二区三区| 亚洲熟妇av一区二区三区漫画| 亚洲一区精品中文字幕| 亚洲色一区二区三区四区| 精品国产一区二区麻豆|