<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          基于 Vite 搭建 Electron + Vue3 的開發(fā)環(huán)境

          共 5843字,需瀏覽 12分鐘

           ·

          2021-02-18 16:01

          作者 | liulun

          目前社區(qū)兩大 Vue+Electron 的腳手架:electron-vue 和 vue-cli-plugin-electron-builder。

          都有這樣那樣的問題,且都還不支持 Vue3,然而 Vue3 已是大勢所趨,Vite 勢必也將成為官方 Vue 腳手架,下圖是尤雨溪在開發(fā)好 Vite 之后與 webpack 之父的對話:

          所以開發(fā)一個 Vite+Vue3+Electron 的腳手架的需求日趨強(qiáng)烈。

          我前段時間做了一個,但是發(fā)現(xiàn)了一些與 Vite 有關(guān)的問題,比如:Vite 會把開發(fā)環(huán)境的 process 對象吃掉的問題。

          這對于 web 項目來說問題不大,但對于我們的 Electron 項目來說,就影響很大了。

          今天我就把這個思路和實現(xiàn)方式的關(guān)鍵代碼發(fā)出來供大家參考,同時也希望 Vue 社區(qū)的貢獻(xiàn)者們,能注意到這個問題(給 Vue 官方的各個項目提 issue 真的是太難了,Electron 官方項目在這方面就做的很好,很 open、很包容)。

          ? ? 環(huán)境? ??
          先用 Vite 創(chuàng)建一個 Vue3 的工程,這就是你的實際項目工程。接著安裝幾個 Electron 相關(guān)的依賴,最終我的工程下的依賴情況如下:
          "@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 下

          各個庫的版本發(fā)文時應(yīng)該是最新的了,不過如果有更新的版本,你完全可以用,沒影響。工程的目錄結(jié)構(gòu)大概是如下這樣:

          接著在 package.json 中,增加兩個命令:
          "scripts": {
          ????"start":?"node ./script/dev.js",
          ????"release":?"node ./script/release.js"
          ??},

          同時在 script 目錄下創(chuàng)建相應(yīng)的文件,接著我們就開始撰寫者兩個文件的代碼了

          調(diào)試腳本
          通過 Vite 啟動 Web 項目

          調(diào)試腳本首先要做的工作就是啟動 Vue 項目,讓它跑在 http://localhost 下,這樣我們修改渲染進(jìn)程的代碼時,會通過 Vite 的熱更新機(jī)制實時反饋到界面上。

          Vite 除了提供 cli 的指令啟動項目外,也提供了 API,我這里就是直接調(diào)它的 API 來啟動項目的,關(guān)鍵代碼如下:
          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 是綁定在當(dāng)前對象上的一個變量,意義是指定 vite 項目啟動時使用的端口號。啟動成功后 http server 對象綁定到當(dāng)前對象的 server 變量上,如果啟動過程中報錯,則很有可能是端口占用,將執(zhí)行如下邏輯:
          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。

          設(shè)置環(huán)境變量

          往往每個開發(fā)人員的環(huán)境變量都是不一樣的,有的開發(fā)人員需要連開發(fā)服務(wù)器 A,有的開發(fā)人員需要連開發(fā)服務(wù)器 B,而且開發(fā)環(huán)境的環(huán)境變量、測試環(huán)境、生產(chǎn)環(huán)境的環(huán)境變量也不一樣,所以我把環(huán)境變量設(shè)置到幾個單獨的文件中,方便區(qū)分不同的環(huán)境,也方便 gitignore,避免不同開發(fā)人員的環(huán)境變量互相沖突。

          開發(fā)環(huán)境的環(huán)境變量保存在 src/script/dev.env.js 中:
          let?env =?require("./dev.env.js")
          生產(chǎn)環(huán)境的環(huán)境變量則為 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。這個環(huán)境變量是為了屏蔽 Electron 開發(fā)者調(diào)試工具那一大堆警告的(你如果開發(fā)過 Electron 應(yīng)用,你應(yīng)該知道我說的是什么),APP_VERSION 是從項目的 package.json 中取的版本號,你當(dāng)然可以不設(shè)置這個環(huán)境變量,通過 Electron 的 API 獲取版本號:

          app.getVersion()?// 主進(jìn)程可用

          但通過 ElectronAPI 獲取到的版本號,在開發(fā)環(huán)境下,是 Electron.exe 的版本號,不是你的項目的版本號,打包編譯后,這個問題是不存在的。

          ENV_NOW 是當(dāng)前的環(huán)境,開發(fā)環(huán)境下它的值為 dev,打包編譯后的生產(chǎn)環(huán)境它的值應(yīng)為 product,因為現(xiàn)在我們是講如何構(gòu)建開發(fā)環(huán)境,引用的是 dev.env.js,等下一篇文章講如何構(gòu)建編譯環(huán)境時,引用的就是 release.env.js 了。

          編譯主進(jìn)程代碼
          Vite 之所以快,有一個很重要的原因是它使用了 esbuild 模塊來編譯代碼,這里我們也使用 esbuild 來編譯我們的主進(jìn)程的代碼。前面說了主進(jìn)程是放在 src/main/ 目錄下的,這里我使用的是 TypeScript 開發(fā),入口程序是 app.ts,你完全可以使用 Js 開發(fā),文件名也隨你自定義:
          buildMain () {
          ????let?outfile = path.join(this.bundledDir,?"entry.js");
          ????let?entryFilePath = path.join(process.cwd(),?"src/main/app.ts");
          ????// 這個方法得到的結(jié)果:{outputFiles: [ { contents: [Uint8Array], path: '' } ]}
          ????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 機(jī)制保證你不會把無用的代碼打包到輸出目錄。我把 sourcemap 關(guān)掉了,因為調(diào)試主進(jìn)程很困難,基本都是手動 console.log 信息調(diào)試的,朋友們有好的建議請賜教一下。platform 要指定成 node,要不然 esbuild 會嘗試幫你去找 node.js 內(nèi)置的包,肯定找不到,就報錯了。

          同理,還要把 electron 設(shè)置成 external,在上一節(jié)設(shè)置的環(huán)境變量的基礎(chǔ)上,我們又增加了一個 WEB_PORT 的環(huán)境變量,Electron 啟動后,要根據(jù)這個變量去加載 localhost 的頁面,這個變量是應(yīng)用啟動時確定的,是動態(tài)的,所以沒辦法設(shè)置到 dev.env.js 中,輸出代碼前,我們把環(huán)境變量的值也附加在輸出代碼中了。

          這樣 Electron 進(jìn)程啟動時,會先設(shè)置好環(huán)境變量,再執(zhí)行具體的業(yè)務(wù)代碼(我們當(dāng)然也可以通過其他方式設(shè)置環(huán)境變量,但這樣做主要是為了和生產(chǎn)環(huán)境保持一致,看到下一篇文章你就會知道了),最終生成的代碼會被輸出到這個目錄下面:
          bundledDir: path.join(process.cwd(),?"release/bundled")

          稍后我們啟動 Electron 時,也會讓 Electron 加載這個目錄下的入口程序。

          啟動 Electron
          Electron 的 node module 并沒有提供 API 給開發(fā)者調(diào)用以啟動進(jìn)程,所以我們只能通過 node 的 child_process 模塊來啟動 Electron 的進(jìn)程,代碼如下:
          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 的可執(zhí)行文件的路徑:

          • Windows 環(huán)境下為:

            • node_modules\electron\dist\electron.exe

          • Mac 環(huán)境下為:

            • node_modules/electron/dist/Electron.app/Contents/MacOS/Electron

          path.join(this.bundledDir, "entry.js") 為 Electron 進(jìn)程指定了入口程序文件的地址,cwd: process.cwd() 是為 Electron 指定當(dāng)前工作目錄(此處又為 Electron 指定了一次環(huán)境變量,其實不指定也沒關(guān)系),當(dāng) Electron 進(jìn)程退出時,我們也關(guān)閉了 Vite 創(chuàng)建的 http server。

          主進(jìn)程加載渲染進(jìn)程頁面
          此處最關(guān)鍵的邏輯就是這一句:
          if?(process.env.ENV_NOW ===?"dev") {
          ??????await?win.loadURL(`http://localhost:${process.env.WEB_PORT}/`);
          ????}

          process.env.WEB_PORT 就是我們上文中設(shè)置的 WEB_PORT 變量。

          這個邏輯當(dāng)然還有 else 分支,那是下一篇博文的內(nèi)容了。

          敬請期待!

          最后

          歡迎關(guān)注【前端瓶子君】??ヽ(°▽°)ノ?
          回復(fù)「算法」,加入前端算法源碼編程群,每日一刷(工作日),每題瓶子君都會很認(rèn)真的解答喲
          回復(fù)「交流」,吹吹水、聊聊技術(shù)、吐吐槽!
          回復(fù)「閱讀」,每日刷刷高質(zhì)量好文!
          如果這篇文章對你有幫助,在看」是最大的支持
          》》面試官也在看的算法資料《《
          “在看和轉(zhuǎn)發(fā)”就是最大的支持
          瀏覽 65
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成年视频在线 | 狠狠操婷婷 | 操操操操操操操 | 亚洲逼逼 | 欧美生活片18 |