Vite ❤ Electron——基於Vite搭建Electron+Vue3的開發環境【一】

背景

目前社區兩大Vue+Electron的腳手架:electron-vuevue-cli-plugin-electron-buildercss

都有這樣那樣的問題,且都還不支持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",
    "postcss-scss": "^3.0.2",
 
    "sass": "^1.27.0",

注意:這些依賴所有安裝在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項目

讓它跑在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

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分支,那是下一篇博文的內容了

敬請期待!

相關文章
相關標籤/搜索