目前社區兩大Vue+Electron的腳手架:electron-vue和vue-cli-plugin-electron-builder,css
都有這樣那樣的問題,且都還不支持Vue3,然而Vue3已經是大勢所趨,html
Vite勢必也將成爲官方Vue腳手架,vue
下圖是尤雨溪在開發好Vite以後與webpack之父的對話node
因此開發一個Vite+Vue3+Electron的腳手架的需求日趨強烈webpack
我前段時間作了一個,git
可是發現了一些與Vite有關的問題,github
好比:Vite會把開發環境的process對象吃掉的問題web
這對於web項目來講問題不大,但對於咱們的Electron項目來講,就影響很大了vue-router
今天我就把這個思路和實現方式的關鍵代碼發出來供你們參考,vue-cli
同時也但願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目錄下建立相應的文件,接着咱們就開始撰寫者兩個文件的代碼了
調試腳本首先要作的工做就是啓動Vue項目
讓它跑在http://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: "https://******.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的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分支,那是下一篇博文的內容了
敬請期待!