Vite ❤ Electron——基於Vite搭建Electron+Vue3的開發環境【一】
- 2020 年 12 月 2 日
- 筆記
- javascript/jQuery/ExtJs, Node.js
背景
目前社區兩大Vue+Electron的腳手架:electron-vue和vue-cli-plugin-electron-builder,
都有這樣那樣的問題,且都還不支援Vue3,然而Vue3已是大勢所趨,
Vite勢必也將成為官方Vue腳手架,
下圖是尤雨溪在開發好Vite之後與webpack之父的對話
所以開發一個Vite+Vue3+Electron的腳手架的需求日趨強烈
我前段時間做了一個,
但是發現了一些與Vite有關的問題,
比如:Vite會把開發環境的process對象吃掉的問題
這對於web項目來說問題不大,但對於我們的Electron項目來說,就影響很大了
今天我就把這個思路和實現方式的關鍵程式碼發出來供大家參考,
同時也希望Vue社區的貢獻者們,能注意到這個問題
(給Vue官方的各個項目提issue真的是太難了,Electron官方項目在這方面就做的很好,很open、很包容)
環境
先用Vite創建一個Vue3的工程,這就是你的實際項目工程
接著安裝幾個Electron相關的依賴,最終我的工程下的依賴情況如下:
"@vue/compiler-sfc": "^3.0.0", "vite": "^1.0.0-rc.9", "vue": "^3.0.2", "vue-router": "^4.0.0-rc.1", "electron": "^11.0.2", "electron-builder": "^22.9.1", "electron-updater": "^4.3.5",
注意:這些依賴全部安裝在devDependencies下
各個庫的版本發文時應該是最新的了,不過如果有更新的版本,你完全可以用,沒影響。
工程的目錄結構大概是如下這樣:
[yourProject]
node_modules 依賴包
public vite創建的目錄,為vue服務的,實際沒多大用
release 打包後編譯輸出的目錄,該目錄的根目錄下存放打包後的安裝包
bundled 該目錄存放vue打包後的文件(html js css img等)
win-unpacked 該目錄存放編譯後生成的可執行文件及相關的dll,不包含安裝包
resource 資源目錄
unrelease 該目錄存放編譯期需要的資源
release 該目錄存放編譯後需要隨安裝包分發給客戶的資源
script 此目錄存放各種腳本,比如編譯腳本,啟動腳本,簽名腳本等
src 源碼目錄
render 渲染進程源碼目錄
main 主進程源碼目錄
common 兩個進程都會用到的共用源碼目錄
package.json 項目配置文件
index.html vue3的入口頁面
.gitignore
接著在package.json中,增加兩個命令:
"scripts": { "start": "node ./script/dev.js", "release": "node ./script/release.js" },
同時在script目錄下創建相應的文件,接著我們就開始撰寫者兩個文件的程式碼了
調試腳本
通過Vite啟動Web項目
調試腳本首先要做的工作就是啟動Vue項目
讓它跑在//localhost下,這樣我們修改渲染進程的程式碼時,
會通過Vite的熱更新機制實時回饋到介面上
Vite除了提供cli的指令啟動項目外,也提供了API,我這裡就是直接調它的API來啟動項目的
關鍵程式碼如下:
let vite = require("vite") createServer () { return new Promise((resolve, reject) => { let options = { root:process.cwd(), enableEsbuild: true }; this.server = vite.createServer(options); this.server.on("error", (e) => this.serverOnErr(e)); this.server.on("data", (e) => console.log(e.toString())); this.server.listen(this.serverPort, () => { console.log(`http://localhost:${this.serverPort}`); resolve(); }); }); },
其中this.serverPort是綁定在當前對象上的一個變數,意義是指定vite項目啟動時使用的埠號
啟動成功後http server對象綁定到當前對象的server變數上
如果啟動過程中報錯,則很有可能是埠佔用,將執行如下邏輯:
serverOnErr (err) { if (err.code === "EADDRINUSE") { console.log( `Port ${this.viteServerPort} is in use, trying another one...` ); setTimeout(() => { this.server.close(); this.serverPort += 1; this.server.listen(this.viteServerPort); }, 100); } else { console.error(chalk.red(`[vite] server error:`)); console.error(err); } },
這段邏輯就是遞增埠號,再次嘗試啟動http server
設置環境變數
往往每個開發人員的環境變數都是不一樣的
有的開發人員需要連開發伺服器A,有的開發人員需要連開發伺服器B
而且開發環境的環境變數、測試環境、生產環境的環境變數也不一樣
所以我把環境變數設置到幾個單獨的文件中
方便區分不同的環境,也方便gitignore,避免不同開發人員的環境變數互相衝突
開發環境的環境變數保存在src/script/dev.env.js中
let env = require("./dev.env.js")
生產環境的環境變數則為release.env.js
這個文件的程式碼非常簡單,如下:
module.exports = {
APP_VERSION: require("../package.json").version,
ENV_NOW: "dev",
PROTOBUF_SERVER: "******.com",
SENTRY_SERVICE: "//******.com/34",
ELECTRON_DISABLE_SECURITY_WARNINGS: true
}
需要注意的是:ELECTRON_DISABLE_SECURITY_WARNINGS,
這個環境變數是為了屏蔽Electron開發者調試工具那一大堆警告的
(你如果開發過Electron應用,你應該知道我說的是什麼)
APP_VERSION是從項目的package.json中取的版本號,
你當然可以不設置這個環境變數,通過Electron的API獲取版本號
app.getVersion() //主進程可用
但通過ElectronAPI獲取到的版本號,在開發環境下,是Electron.exe的版本號,不是你的項目的版本號
打包編譯後,這個問題是不存在的。
ENV_NOW是當前的環境,開發環境下它的值為dev,打包編譯後的生產環境它的值應為product,
因為現在我們是講如何構建開發環境,引用的是dev.env.js,
等下一篇文章講如何構建編譯環境時,引用的就是release.env.js了,
編譯主進程程式碼
Vite之所以快,有一個很重要的原因是它使用了esbuild模組來編譯程式碼
這裡我們也使用esbuild來編譯我們的主進程的程式碼
前面說了主進程是放在src/main/目錄下的
這裡我使用的是TypeScript開發,入口程式是app.ts,你完全可以使用Js開發,文件名也隨你自定義
buildMain () { let outfile = path.join(this.bundledDir, "entry.js"); let entryFilePath = path.join(process.cwd(), "src/main/app.ts"); //這個方法得到的結果:{outputFiles: [ { contents: [Uint8Array], path: '<stdout>' } ]} esbuild.buildSync({ entryPoints: [entryFilePath], outfile, minify: false, bundle: true, platform: "node", sourcemap: false, external: ["electron"], }); env.WEB_PORT = this.serverPort; let envScript = `process.env={...process.env,...${JSON.stringify(env)}};` let js = `${envScript}${os.EOL}${fs.readFileSync(outfile)}`; fs.writeFileSync(outfile, js) },
esbuild會自動查找app.ts引用的其他程式碼,
還有treeshaking機制保證你不會把無用的程式碼打包到輸出目錄
我把sourcemap關掉了,因為調試主進程很困難,
基本都是手動console.log資訊調試的,朋友們有好的建議請賜教一下
platform要指定成node,要不然esbuild會嘗試幫你去找node.js內置的包,肯定找不到,就報錯了
同理,還要把electron設置成external
在上一節設置的環境變數的基礎上
我們又增加了一個WEB_PORT的環境變數,
Electron啟動後,要根據這個變數去載入localhost的頁面,
這個變數是應用啟動時確定的,是動態的,所以沒辦法設置到dev.env.js中
輸出程式碼前,我們把環境變數的值也附加在輸出程式碼中了
這樣Electron進程啟動時,會先設置好環境變數,再執行具體的業務程式碼
(我們當然也可以通過其他方式設置環境變數,但這樣做主要是為了和生產環境保持一致,看到下一篇文章你就會知道了)
最終生成的程式碼會被輸出到這個目錄下面:
bundledDir: path.join(process.cwd(), "release/bundled")
稍後我們啟動Electron時,也會讓Electron載入這個目錄下的入口程式。
啟動Electron
Electron的node module並沒有提供API給開發者調用以啟動進程
所以我們只能通過node的child_process模組來啟動Electron的進程
程式碼如下:
createElectronProcess () { this.electronProcess = spawn( require("electron").toString(), [path.join(this.bundledDir, "entry.js")], { cwd: process.cwd(), env, } ); this.electronProcess.on("close", () => { this.server.close(); process.exit(); }); this.electronProcess.stdout.on("data", (data) => { data = data.toString(); console.log(data); }); },
require(“electron”).toString()得到的是Electron的可執行文件的路徑
Windows環境下為:node_modules\electron\dist\electron.exe
Mac環境下為:node_modules/electron/dist/Electron.app/Contents/MacOS/Electron
path.join(this.bundledDir, “entry.js”)為Electron進程指定了入口程式文件的地址
cwd: process.cwd()是為Electron指定當前工作目錄(此處又為Electron指定了一次環境變數,其實不指定也沒關係)
當Electron進程退出時,我們也關閉了Vite創建的http server
主進程載入渲染進程頁面
此處最關鍵的邏輯就是這一句
if (process.env.ENV_NOW === "dev") { await win.loadURL(`http://localhost:${process.env.WEB_PORT}/`); }
process.env.WEB_PORT就是我們上文中設置的WEB_PORT變數
這個邏輯當然還有else分支,那是下一篇博文的內容了
敬請期待!