electron打包vue-cli4项目的解决方案1(项目实测)
背景:
項(xiàng)目已通過vue-cli4框架開發(fā)并運(yùn)行在nginx服務(wù)器上(使用了nginx的反向代理),現(xiàn)需要將該項(xiàng)目轉(zhuǎn)成桌面端應(yīng)用程序,選擇electron來(lái)實(shí)現(xiàn)。
設(shè)想2種解決方案:
1、使用electron-quick-start的loadURL方法讀取服務(wù)的線上url,需要做一個(gè)用戶URL輸入界面便于部署。vue服務(wù)的版本迭代部署都和該electron無(wú)關(guān);
2、vue-cli項(xiàng)目引入electron并取代nginx服務(wù)器的功用
?
electron簡(jiǎn)介:
Electron 使用 web 頁(yè)面作為它的 GUI,所以你能把它看作成一個(gè)被 JavaScript 控制的,精簡(jiǎn)版的 Chromium 瀏覽器。
Electron大家應(yīng)該不陌生了,它提供了一種解決方案,讓我們能夠利用Node.js 和 前端三寶 HTML + JS + CSS 來(lái)實(shí)現(xiàn)客戶端軟件。咋一聽感覺像NW.js。其實(shí)NW.js(node+websocket)可以算是Electron的前身了,都是出自同個(gè)作者之手。關(guān)于Electron和NW.js的區(qū)別官網(wǎng)上是這么說(shuō)的。簡(jiǎn)單講就是Electron優(yōu)化了NW.js中的一些不足。 秉著與時(shí)俱進(jìn)的態(tài)度,我們當(dāng)然要使用Electron。有了Electron作為容器,我們小前端就可以用HTML+JS+CSS來(lái)開發(fā)客戶端了。就像開發(fā)前端頁(yè)面一樣柔順。Electron的使用比較簡(jiǎn)單,提供的API也比較清晰。核心概念就是Main Process 和 Render Process。顧名思義Main Process是主進(jìn)程,用于運(yùn)行Electron的基本操作,如創(chuàng)建窗口,創(chuàng)建菜單等。Render Process是渲染進(jìn)程,我們需要在渲染進(jìn)程中創(chuàng)建軟件界面,每個(gè)渲染進(jìn)程對(duì)應(yīng)的是一個(gè)窗口,主進(jìn)程開啟了多個(gè)窗口就會(huì)有多個(gè)渲染進(jìn)程。
通俗解釋:electron使用nodejs將web應(yīng)用套了一層殼使之變成了桌面應(yīng)用。electron官網(wǎng)
?
electron-quick-start快速啟動(dòng)項(xiàng)目
下載?electron-quick-start?demo 或使用git clone后啟動(dòng)
git clone https://github.com/electron/electron-quick-start cd electron-quick-start cnpm install //這里我用的是cnpm,npm太慢了 npm start啟動(dòng)成功效果
??
?
啟動(dòng)流程:關(guān)注main.js、package.json、index.html這幾個(gè)文件。從?package.json?文件看起,我們運(yùn)行?npm start 命令?其實(shí)是 運(yùn)行了?electron . 命令,該命令會(huì)從當(dāng)前目錄文件中尋找?package.json 文件,找到?package.json 文件?然后讀取?package.json 文件?中的?main 值,main值?指向?main.js文件,然后運(yùn)行 man.js 文件找到index.html文件在主進(jìn)程創(chuàng)建應(yīng)用窗口,創(chuàng)建桌面應(yīng)用實(shí)例。
注意:
1、npm start命令中的 “chcp 65001”表示使用utf-8的編碼格式,解決console打印亂碼的問題
2、main進(jìn)程中“nodeIntegration”配置表示是否集成nodejs模塊
?
?
如何打包成exe桌面應(yīng)用程序
需求分析:loadURL方法讀取服務(wù)的線上url,但需要做一個(gè)URL輸入界面便于用戶輸入vue服務(wù)的地址,因?yàn)閘oadURL方法不管vue服務(wù)是否成功都會(huì)跳轉(zhuǎn),url無(wú)法訪問會(huì)導(dǎo)致electron打包后的桌面一片空白,所以盡量在loadURL跳轉(zhuǎn)頁(yè)面前加以判定
?
解決方案一
流程:
1、打包前需要確認(rèn)已安裝electron和electron-packager、jquery依賴
cnpm install electron --save-dev cnpm install electron-packager --save-dev //這個(gè)是打成exe文件的插件,之后要用,提前下載好 cnpm install jquery --save-dev2、在package.json配置文件中修改啟動(dòng)和打包命令
{"name": "electron-quick-start","version": "1.0.0","description": "A minimal Electron application","main": "main.js","scripts": {"start": "chcp 65001 && electron .", //utf8編碼格式,啟動(dòng)"electron_exe":"electron-packager . --platform=win32 --arch=x64 --overwrite" , //覆蓋打包"electron_all":"electron-packager ./ notes --all --overwrite" //全平臺(tái)打包},"repository": {"type": "git","url": "git+https://github.com/electron/electron-quick-start.git"},"keywords": ["Electron","quick","start","tutorial","demo"],"author": "GitHub","license": "CC0-1.0","devDependencies": {"electron": "^11.2.0","electron-packager": "^15.2.0","jquery": "^3.5.1"},"bugs": {"url": "https://github.com/electron/electron-quick-start/issues"},"homepage": "https://github.com/electron/electron-quick-start#readme","dependencies": {"jquery": "^3.5.1" } }?
3、在mian.js主進(jìn)程中創(chuàng)建窗口配置nodeIntegration允許使用nodejs功能。這里使用了ipcMain和ipcRender(主進(jìn)程和渲染進(jìn)程)通信,見后文或官網(wǎng)
// Modules to control application life and create native browser window var electron = require('electron'); var { app, BrowserWindow, Menu, ipcMain, globalShortcut } = require('electron') var path = require('path') var { dialog } = require('electron')var electronScreen = electron.screen; //window.screen 是一個(gè)預(yù)設(shè)值的 DOM 屬性, 所以這樣寫 var screen = require('electron').screen 將不會(huì)工作function createWindow() {var mainWindow = new BrowserWindow({ //創(chuàng)建窗口// frame:false, //無(wú)邊框width: 1400,height: 800,// fullscreen: true, //默認(rèn)全屏展示webPreferences: {nodeIntegration: true, //false則禁用Node.js的require模塊化引入preload: path.join(__dirname, 'preload.js'),enableRemoteModule: true, //遠(yuǎn)程模塊}})mainWindow.loadFile('index.html') //窗口讀取本地文件// 在開發(fā)環(huán)境和生產(chǎn)環(huán)境均可通過快捷鍵打開devToolsglobalShortcut.register('ctrl+F12', function () {if(mainWindow.webContents.isDevToolsOpened()){mainWindow.webContents.closeDevTools()}else {mainWindow.webContents.openDevTools() // 打開F12調(diào)試頁(yè)面}})//主進(jìn)程監(jiān)聽渲染進(jìn)程發(fā)過來(lái)的數(shù)據(jù)ipcMain.on('asynchronous-message', (event, arg) => {try {// console.log("主進(jìn)程收到有效數(shù)據(jù)-----", arg) // prints "ping"mainWindow.loadURL(arg)} catch (err) {console.error(err)mainWindow.loadFile('index.html')}})Menu.setApplicationMenu(null) // 關(guān)閉菜單欄.//快捷鍵全屏globalShortcut.register('ctrl+f11', () => {if(mainWindow.isFullScreen()){mainWindow.setFullScreen(false);}else {mainWindow.setFullScreen(true);}})}// 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.whenReady().then(() => {createWindow()app.on('activate', function () {// 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) {mouseTimer = null;createWindow()}})})// Quit when all windows are closed, except on macOS. There, it's common // for applications and their menu bar to stay active until the user quits // explicitly with Cmd + Q. app.on('window-all-closed', function () {if (process.platform !== 'darwin') app.quit()mouseTimer = null; })?
4、index.html中可能出現(xiàn)CSP的安全策略,Content-Security-Policy這里可以注釋掉了。
<!DOCTYPE html> <html><head><meta charset="UTF-8"><!-- 關(guān)閉內(nèi)容安全策略 --><!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --><!-- <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'"><meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'"> --><title>Hello Boy!</title><style>body {text-align: center;}.url-setting {height: 20%;min-height: 150px;font-size: 30px;}</style></head><body><h1>Hello World!</h1><hr><div class="url-setting"><input type="url" id="vueUrl" placeholder="格式如http://127.0.0.1:8080"><button id="linkBtn">進(jìn)入系統(tǒng)</button></div><script>//解決在electron-packager打包為exe時(shí)nodejs無(wú)法require加載jquery的問題window.$ = window.jQuery = require("./node_modules/jquery/dist/jquery.min.js");</script><script src="./renderer.js"></script> </body></html>?
5、在renderer.js渲染進(jìn)程中使用nodejs的require引入jquery會(huì)在exe包運(yùn)行時(shí)報(bào)錯(cuò)“$ not defined ”,所以在index.html中重新定義$,要注意jquery依賴的文件路徑是否能在打包好的electron項(xiàng)目下找到
// This file is required by the index.html file and will be executed in the renderer process for that window. // No Node.js APIs are available in this process because `nodeIntegration` is turned off. // Use `preload.js` to selectively enable features needed in the rendering process.// ipcRenderer 用于和主進(jìn)程通信 const ipcRenderer = require('electron').ipcRenderer; //const $ = require('jquery'); //盡管nodeIntegration設(shè)置允許繼承nodejs后可以正常啟動(dòng)electron,但打包成exe時(shí)會(huì)報(bào)錯(cuò)//使用 remote 模塊,可以調(diào)用主進(jìn)程對(duì)象的方法 const remote = require('electron').remote; const dialog = remote.dialog; const app = remote.app;//使用nodejs讀取本地文件數(shù)據(jù),安裝electron-store也可以存儲(chǔ)數(shù)據(jù),但響應(yīng)性能受較大影響//electron-store存儲(chǔ)數(shù)據(jù) // const Store = require('electron-store'); // const store = new Store(); // const currentUrl = store.get('serveUrl');const fs = require('fs')//jquery檢查連接的url服務(wù)是否有效,適用所有瀏覽器 $(document).ready(function () {// 執(zhí)行代碼var filePath = "./serveUrl.txt"if(app.isPackaged){ //app.isPackaged在生產(chǎn)模式則為truefilePath = "./resources/app/serveUrl.txt"}fs.readFile(filePath, function (error, data) { //if (error) {console.log('讀取文件失敗了',error)} else {$("#vueUrl").val(data.toString());}})function NetPing(pingUrl) {$.ajax({type: "GET",cache: false,url: pingUrl,data: "",success: function () {fs.writeFile(filePath, pingUrl,function (error) { //存儲(chǔ)用戶輸入的路徑if (error) {console.log('存儲(chǔ)URL失敗',error)}}) ipcRenderer.send('asynchronous-message', pingUrl); //異步將渲染進(jìn)程的數(shù)據(jù)傳給主進(jìn)程},error: function () {dialog.showErrorBox("錯(cuò)誤提示", "無(wú)效網(wǎng)址,請(qǐng)重新輸入!")$("#vueUrl").val("");}});}//點(diǎn)擊連接vue前端服務(wù)$("#linkBtn").click(function () {let urlVal = $("#vueUrl").val();if(urlVal){NetPing(urlVal);}else {dialog.showErrorBox("錯(cuò)誤提示", "網(wǎng)址不能為空,請(qǐng)重新輸入!")}}) });//接收主進(jìn)程的信息 ipcRenderer.on('asynchronous-reply', function (event, arg) {// console.log(arg); // prints "pong" });注意:沒有使用electron-store存儲(chǔ)持久化數(shù)據(jù),而是使用nodejs讀存到本地文件serveUrl.txt上。引用本地文件時(shí)可以用app.isPackaged判斷是否是生產(chǎn)環(huán)境
?
6、啟動(dòng).\electron-quick-start\electron-quick-start-win32-x64成功
? ?
?
?
electron方案優(yōu)缺點(diǎn):
對(duì)比于Cocoa,Qt等傳統(tǒng)桌面客戶端技術(shù),基于前端技術(shù)的實(shí)現(xiàn)成本較低(C++牛請(qǐng)忽略)跨平臺(tái)支持更好(框架都幫你做好了),且天然支持熱更新。
由于Electron本身包含了chromium和Node.js的代碼, 所以不考慮項(xiàng)目本身體積,打包后的軟件最小仍然有100M+, 這也是Electron最為顯著的缺點(diǎn)之一。所以基本體積是無(wú)法避免的,我們只能盡量減小其他開發(fā)文件的大小,避免將一些無(wú)關(guān)包文件也打包進(jìn)去。為什么要強(qiáng)調(diào)這點(diǎn)呢?因?yàn)?strong>基于Node.js開發(fā)的項(xiàng)目往往會(huì)有一個(gè)龐大的node_modules文件夾,里面包含了一些開發(fā)和生產(chǎn)所用的包,也即對(duì)應(yīng)package.json中的dependencies和devDependencies。而devDependencies中的包是不需要打包到軟件的。這里推薦使用?electron-packager, 能自動(dòng)排除dev依賴包,并支持自定義排除包文件夾。也可以打包出支持不同系統(tǒng)格式的軟件。
對(duì)于NW.js 有的大多數(shù)缺點(diǎn)Electron也有,其中一個(gè)通病就是性能問題,主要是渲染性能方面。基于webkit引擎來(lái)渲染UI界面,跟原生的系統(tǒng)UI還是有一定的差距。畢竟是基于DOM節(jié)點(diǎn)的渲染,每次節(jié)點(diǎn)的重排都是一次大的開銷。這點(diǎn)只能通過在前端框架中來(lái)優(yōu)化,比如利用Virtual DOM等相關(guān)技術(shù)。而視覺上的缺點(diǎn)則可以通過CSS做到竟可能接近原生控件。
?
electron主進(jìn)程和渲染進(jìn)程間通信:
Electron提供了IPC用于進(jìn)程間通信。分別是ipcMain和ipcRender。該通信機(jī)制允許ipcRender向ipcMain發(fā)送信號(hào)請(qǐng)求,并通過ipcMain返回?cái)?shù)據(jù)。反回來(lái)ipcMain無(wú)法向特定的ipcRender發(fā)起請(qǐng)求。而且通信間傳遞的消息會(huì)被格式化為JSON字符串,所以并不支持在兩個(gè)進(jìn)程間傳遞句柄方法等,也就是不支持上下文傳遞。假如要實(shí)現(xiàn)在渲染進(jìn)程中點(diǎn)擊一個(gè)按鈕,則關(guān)閉客戶端窗口,可以通過ipcRender發(fā)送一個(gè)信號(hào)給ipcMain, ipcMain接收到該信號(hào)后調(diào)用Electron的API關(guān)閉窗口。對(duì)于類似這種比較簡(jiǎn)單的指令操作,運(yùn)用IPC實(shí)現(xiàn)就可以了,但是如果操作比較復(fù)雜,并且需要傳遞復(fù)雜數(shù)據(jù)類型,則用IPC就行不通了。Electron提供了另一個(gè)API?remote,用于在Render Process中直接操作主進(jìn)程的方法。這樣就不需要移交Main Process處理,直接在前端頁(yè)面中調(diào)用Electron的API。
// In main process. const ipcMain = require('electron').ipcMain; ipcMain.on('asynchronous-message', function(event, arg) {console.log(arg); // prints "ping"event.sender.send('asynchronous-reply', 'pong'); });ipcMain.on('synchronous-message', function(event, arg) {console.log(arg); // prints "ping"event.returnValue = 'pong'; }); // In renderer process (web page). const ipcRenderer = require('electron').ipcRenderer; console.log(ipcRenderer.sendSync('synchronous-message', 'ping')); // prints "pong"ipcRenderer.on('asynchronous-reply', function(event, arg) {console.log(arg); // prints "pong" }); ipcRenderer.send('asynchronous-message', 'ping');?
?
解決方案二見下文
?
?
?
?
總結(jié)
以上是生活随笔為你收集整理的electron打包vue-cli4项目的解决方案1(项目实测)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: rust(26)-单元类型与never
- 下一篇: rust(28)-具名结构体