使用Vite从零开始构建Electron项目

Vite是一种相对于webpack更现代化,更高效的前端框架构建工具,具有以下的特点:

        不需捆绑:服务器冷启动非常快。

        按需编译代码: 只编译当前界面实际导入的代码,不必整个应用被捆绑后才开始开发,利于多页面应用开发。

        热更新(Hot Module Replacement, HWR)的性能与模块总数解耦,速度快无关应用大小。

注意:1. 项目整个页面重载的比基于绑定的webpack项目稍慢,因基于<script module>导入方式易引起大量网络请求,本地开发忽略不计。

           2.默认内置的esbuild编译器,在将TypeScript转换为javascript的工作上的性能表现优异,比常用的tsc工具快20-30倍,HMR更新可以在50ms内反映在浏览器中。

一、创建项目

首先通过如下命令创建一个vite项目,并安装好Electron依赖,相应的npm指令如下:

npm init vite  viteElectron
cd viteElectron
npm install
npm install electron  --save-dev
yarn add @vitejs/plugin-vue --dev

一般情况下为一个工程安装依赖时,都是安装为开发依赖(--save-dev),因为生产依赖都会在Electron-builder打包时被放到安装包内。但Electron-builder会另外准备Electron,所以不需要自己的生产依赖。

二、定义启动脚本

在项目的package.json文件中添加启动脚本如下:

#package.json
"script" {
      "start": "node ./script/dev",
}

项目开发成功后,开发者可以通过npm run start 方式启动应用。

在根目录的script/dev/下, 创建文件index.js,它是启动开发环境的脚本,它主要完成如下四项工作:

(1)启动Vue项目的开发服务。

(2)设置环境变量

(3)编译主进程代码

(4)启动Electron子进程,并加载Vue项目的首页。

三、启动开发任务

主要工作是通过vite内置koa服务加载Vue项目,让它运行在http://localhost下,有这个服务,我们修改界面代码时,Vite的热更新机制会使修改的内容实时反馈到界面。Vite提供了命令行启动项目外,也提供了javascript API供开发人员通过编码方式启动项目。在index.js文件内直接调API来启动项目,关键代码如下:

let vite=require("vite")
let vue=require("@vitejs/plugin-vue")

let dev = {
    server: null,
    serverPort: 1600,
    electronProcess: null,
     /*
    async createServer() {
        let options = {
            configFile: false,
            root: process.cwd(),
            server: {
                port: this.serverPort,
            },
            plugins: [vue()],
        };
        this.server = await vite.createServer(options);
        await this.server.listen();
    },
    */
   
    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();
          });
        });
      }, 
   
    async start(){
        await this.createServer();
         
    },
};
dev.start();

在以上代码中,首先定义dev对象,使用dev对象的start方法启动开发环境的http服务,在createServer中,首先在vite.createServer创建http服务,并用server.listen()方法启动服务,两个createServer函数,经验证第二个函数可以正常启动,第一个函数无法启动http服务,代码如上,原因不明。

四、设置环境变量

本节提到的环境变量并不是操作系统的环境变量,也不是npm的环境变量,而是应用程序的环境变量,是process.env对象内的各个属性和值。开发人员环境变量保存在script/dev/env.js中,代码如下:

module.exports={
    APP_VERSION: require("../../package.json").vesion,
    ENV_NOW: 'development',
    HTTP_SERVER: "******.com",
    SENTRY_SERVICE: "https://******.com/34",
    ELECTRON_DISABLE_SECURITY_WARNINGS: true,
};

APP_VERSION:项目的版本号

ENV_NOW: 在开发环境中被设置成develoment,在生产中被设置成production

HTTP_SERVER: 服务器域名

SENTRY_SERVICE:  日志服务

ELECTRON_DISABLE_SECURITY_WARINGES: 屏蔽Electron开发者调试工具安全警告。

自定义获取环境变量的函数代码如下:

//设置环境变量
    getEnvScript(){
        let env=require("./env.js");
        env.WEB_PORT=this.serverPort;
        env.RES_DIR=path.join(process.cwd(),"resource/release");
        let script="";
        for(let v in env){
            script += `process.env.${v}="${env[v]}";`;
        }
        return script;
    }

上述代码返回一段javascript字符串,这段javascript代码是由一系列的设置process.env属性的语句组成,这些设置process.env的语句包含env.js中定义的属性,还额外包含WEB_PORT和RES_DIR两个环境变量。WEB_PORT是通过vite启动的http服务端口号。RES_DIR是指存放外部资源的目录,在运行期间指向当前工程的resource/release子目录。

五、构建主进程代码

启动了开发环境http服务,准备环境变量后,然后就是编译主进程代码,因为Electron应用启动后首先执行的是主进程代码,所以先把此工作完成,Vite自带的esbuild编译代码快,所以使用esbuild编译,代码如下:

buildMain(){
        let entryFilePath=path.join(process.cwd(),"src/main/app.ts");
        let outfile=path.join(process.cwd(),"release/bundled/entry.js")
        esbuild.buildSync({
            entryPoints: [entryFilePath],
            outfile,
            minify: false,
            bundle: true,
            platform: "node",
            sourcemap: true,
            external: ["electron"],
        });
        let envScript=this.getEnvScript();
        let js=`${envScript}${os.EOL}${fs.readFileSync(outfile)}`;
        fs.writeFileSync(outfile,js);      
    },

在代码中,首先获取 到主进程入口文件的绝对路径entryFilePath,这个文件在当前工程的src/main子目录下,其次定义了主进程代码编译完成后输出文件的路径outfile,这个文件路径指向当前工程的release/bundled子目录。

接着通过esbuild.buildSync方法编译主进程代码逻辑,因并不需要压缩代码,所以minify配置为false。把external配置为electron是为了不让编译工具尝试加载Electron模块内容,因为运行环境自带此模块,所以编译工具不必编译此模块代码。

最后通过getEnvScript方法得到设置环境变量的javascript代码字符串,并通过fs模块writeFileSync方法把它写到主进程代码文件的第一行,即在主进程代码逻辑执行前,先完成环境变量的设置工作。

此段逻辑有三个关键点需注意:

(1)必须逐一附加环境变量到process.env对象上,而不能如下批量更改

process.env={...process.env,...${JSON.stringify(env)}}

因应用如果加载第三方原生addon,而第三方addon依赖当前应用的环境变量,批量更改将使它无力获取当前应用环境变量。

(2)设置http服务端口到环境变量,可以在主进程中通过如下方式加载页面:

if(process.env.ENV_NOW==="development"){
        return 'http://localhost:${process.env.WEB_PORT}/#${url}';
} else {
      return 'app:// ./index.html/#${url}';
}

(3) 设置RES_DIR环境变量目的是使代码能方便地访问到这些外部资源。

(4)  在渲染进程中使用环境变量,必须写作process["env"].ENV_NOW,而不能写作process.env.ENV_NOW。

六、启动Electron子进程

启动Electron必须在准备好http服务,编译主进程代码后执行,模仿electron包内的cli.js实现如下逻辑。

createElectronProcess() {
        this.electronProcess = spawn(
          require("electron").toString(),
          [path.join(process.cwd(), "release/bundled/entry.js")],
          { cwd: process.cwd() }
        );
        this.electronProcess.on("close", () => {
          this.server.close();
          process.exit();
        });
        this.electronProcess.stdout.on("data", (data) => {
          data = data.toString();
          console.log(data);
        });
      },

此段代码首先通过nodejs的child_process模块的spawn方法启动Electron子进程,启动这个子进程时,传递命令行参数:path.join(this.bundleDir,"entry.js"),此参数是编译主进程后生成的入口文件,这样Electron启动时会自动加载并执行主进程逻辑。Electron子进程启动后监听退出事件close,一旦Electron子进程退出,vite启动的http服务也退出:this.server.close(),同时整个开发环境也退出 。

至此,Electron开发环境搭建完成,下面是主进程代码文件的演示性代码:

let start = Date.now();
import { app, BrowserWindow, protocol } from "electron";
import path from "path";
import fs from "fs";
protocol.registerSchemesAsPrivileged([
  {
    scheme: "app",
    privileges: {
      standard: true,
      supportFetchAPI: true,
      secure: true,
      corsEnabled: true,
    },
  },
]);
let mainWindow: BrowserWindow;
app.on("ready", () => {
  protocol.registerBufferProtocol("app", (request, response) => {
    let pathName = new URL(request.url).pathname;
    let extension = path.extname(pathName).toLowerCase();
    if (!extension) return;
    pathName = decodeURI(pathName);
    let filePath = path.join(__dirname, pathName);
    fs.readFile(filePath, (error, data) => {
      if (error) return;
      let mimeType = "";
      if (extension === ".js") {
        mimeType = "text/javascript";
      } else if (extension === ".html") {
        mimeType = "text/html";
      } else if (extension === ".css") {
        mimeType = "text/css";
      } else if (extension === ".svg") {
        mimeType = "image/svg+xml";
      } else if (extension === ".json") {
        mimeType = "application/json";
      }
      response({ mimeType, data });
    });
  });
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      webSecurity: false,
      nodeIntegration: true,
      contextIsolation: false,
    },
  });
  if (app.isPackaged) {
    console.log(start, Date.now() - start,'index.html');
    mainWindow.loadURL(`app://./index.html`);
  } else {
    console.log(start, Date.now() - start,`http://localhost:${process.env.WEB_PORT}/`);
    mainWindow.loadURL(`http://localhost:${process.env.WEB_PORT}/`);
  }
});

在当前工程目录下启动命令行,执行npm run start命令,效果如下:

七、配置调试环境

配置VSCode的调试环境,主要是在工程根目录下创建.vscode子目录,并在此目录下创建名为launch.json文件,代码如下:

{
    "version": "0.2.0",
    "configurations": [
        {
            "type":"node",
            "request": "launch",
            "name": "Start",
            "program": "${workspaceFolder}/script/dev/index.js",
            "cwd": "${workspaceFolder}"
        }
    ]
}

配置完成后点击“运行”图标,将出现名为Start的开始调试启动项。

八、打包源码

首先,在script\release目录下创建名为index.js 的文件,通过脚本文件执行如下四项任务以完成安装包的制作工作。

(1)构建渲染进程代码

(2)构建主进程代码

(3)安装工程依赖包

(4)制成安装文件

index.js代码如下:

let vite = require("vite");
let path = require("path");
let esbuild = require("esbuild");
let os = require("os");
let fs = require("fs");
let vue = require("@vitejs/plugin-vue");

let dev = {
  getEnvScript() {
    let env = require("./env.js");
    let script = "";
    for (let v in env) {
      script += `process.env.${v}="${env[v]}";`;
    }
    script += `process.env.RES_DIR = process.resourcesPath;`;
    return script;
  },
  buildMain() {
    let entryFilePath = path.join(process.cwd(), "src/main/app.ts");
    let outfile = path.join(process.cwd(), "release/bundled/entry.js");
    esbuild.buildSync({
      entryPoints: [entryFilePath],
      outfile,
      minify: true,
      bundle: true,
      platform: "node",
      sourcemap: false,
      external: ["electron"],
    });
    let envScript = this.getEnvScript();
    let js = `${envScript}${os.EOL}${fs.readFileSync(outfile)}`;
    fs.writeFileSync(outfile, js);
  },
  async buildRender() {
    let options = {
      root: process.cwd(),
      build: {
        enableEsbuild: true,
        minify: true,
        outDir: path.join(process.cwd(), "release/bundled"),
      },
      plugins: [vue()],
    };
    await vite.build(options);
  },
  buildModule() {
    let pkgJsonPath = path.join(process.cwd(), "package.json");
    let localPkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
    let electronConfig = localPkgJson.devDependencies.electron.replace("^", "");
    delete localPkgJson.scripts;
    delete localPkgJson.devDependencies;
    localPkgJson.main = "entry.js";
    localPkgJson.devDependencies = { electron: electronConfig };
    fs.writeFileSync(
      path.join(process.cwd(), "release/bundled/package.json"),
      JSON.stringify(localPkgJson)
    );
    fs.mkdirSync(path.join(process.cwd(), "release/bundled/node_modules"));
  },
  buildInstaller() {
    let options = {
      config: {
        directories: {
          output: path.join(process.cwd(), "release"),
          app: path.join(process.cwd(), "release/bundled"),
        },
        files: ["**"],
        extends: null,
        productName: "yourProductName",
        appId: "com.yourComp.yourProduct",
        asar: true,
        extraResources: require("../common/extraResources.js"),
        win: require("../common/winConfig.js"),
        mac: require("../common/macConfig.js"),
        nsis: require("../common/nsisConfig.js"),
        publish: [{ provider: "generic", url: "" }],
      },
      project: process.cwd(),
    };
    let builder = require("electron-builder");
    return builder.build(options);
  },
  async start() {
    await this.buildRender();
    await this.buildMain();
    await this.buildModule();
    this.buildInstaller();
  },
};
dev.start();

此env.js文件是release目录下的env.js,而不是dev目录下env.js,此种做法区分了开发环境与生产环境的环境变量,最重要区别是ENV_NOW环境变量,一为production,另一个为development。

九、打包依赖

Electron依赖包安装为开发依赖,目的是为了在制作安装包时,避免其被安装两次。

主进程代码与渲染进程代码被编译后放置在[ypurProject]\release\bundled目录下,不需要另外安装 ,一些特殊的库,则需要特殊的打包依赖操作。

  • 34
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Vite 和 TypeScript 项目使用 Electron,可以按照以下步骤进行操作: 1. 创建一个新的 Vite 项目:可以使用 `npm init vite` 命令或者手动创建一个 Vite 项目。 2. 安装 Electron:可以使用 `npm install electron` 命令来安装 Electron。 3. 修改 Vite 配置文件:在 `vite.config.ts` 文件中配置 Electron 的入口文件路径,并设置 Vite 的输出目录为 Electron 的渲染进程目录。 ```typescript import { defineConfig } from 'vite'; import path from 'path'; export default defineConfig({ build: { outDir: 'dist/renderer', }, server: { port: 8000, }, resolve: { alias: { '@': path.resolve(__dirname, 'src'), }, }, plugins: [], optimizeDeps: { include: [ 'electron', ], }, }); ``` 4. 创建 Electron 入口文件:在项目根目录下创建一个 `main.ts` 文件,作为 Electron 的主进程入口文件。 ```typescript import { app, BrowserWindow } from 'electron'; import path from 'path'; function createWindow() { const win = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js'), }, }); win.loadURL('http://localhost:8000'); } app.whenReady().then(() => { createWindow(); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } }); }); app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } }); ``` 5. 创建 Electron 预加载脚本:在项目根目录下创建一个 `preload.js` 文件,作为 Electron 的渲染进程预加载脚本。 ```javascript const { ipcRenderer } = require('electron'); window.addEventListener('DOMContentLoaded', () => { ipcRenderer.send('renderer-ready'); }); ``` 6. 在 `package.json` 中添加启动脚本:在 `package.json` 文件中添加一个启动脚本,用于启动 Electron 应用。 ```json "scripts": { "start": "vite && electron ." }, ``` 7. 运行应用:运行 `npm start` 命令启动应用即可。 以上就是在 Vite 和 TypeScript 项目使用 Electron 的基本步骤,希望对你有帮助。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值