如何在vite中优雅的使用Electron

这是我参与更文挑战的第9天,活动详情查看: 更文挑战

起因是把爬了一些美女的照片,想展示一下。用vite脚手架生成了一个Vue的项目,但是由于这些图片是不断更新的,所以不能更改文件夹的位置。在我印象中,一个项目不能引用项目之外的资源。所以想利用NodeJs API去读取本地文件,正好Electron即能满足展示,也能满足加载本地文件的需求。

整体思路

image.png

如上图所示,利用electron的ipcMain和ipcRenderer通信能力,进行数据交换。所以前端项目也需要引electron的ipcRenderer。

搭建项目

vite初始化项目

npm init @vitejs/app show-avatar -- --template vue-ts
复制代码

Electron代码生成

新建electron目录,并新建main.ts文件(部分)

function createWindow() {
  const mainWindow = new BrowserWindow({
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, "preload.js"),
      nodeIntegration: true,
      contextIsolation: false,
    },
    width: 800,
  });
  const isDev = process.env.NODE_ENV === "development";
  if (isDev) {
    mainWindow.loadURL("http://localhost:8080/");
  } else {
    mainWindow.loadFile(path.join(__dirname, "..", "dist/index.html"));
  }

  mainWindow.webContents.openDevTools();
}
app.on("ready", () => {
  createWindow();

  app.on("activate", function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });
});
app.on("window-all-closed", () => {
  if (process.platform !== "darwin") {
    app.quit();
  }
});
复制代码

这里有几个问题需要解决:

1、怎么在开发环境使用vite热更新能力
2、怎么处理electron主进程获取图片的二进制
3、怎么在electron中使用typescript

在electron中使用typescript

创建一个新的tsconfig.json,因为和vite生成的文件本质上是有区别的,比如输出的目录等;

{
    "extends": "./tsconfig.json",
    "compilerOptions": {
        "outDir": "build",
        "noEmit": false,
        "module": "commonjs",
        "baseUrl": ".",
        "sourceMap": false,
    },
    "include": ["electron"]
}
复制代码

因为想实现的效果是和热更新差不多的功能,只要main.ts入口文件变化,就会重新编译,构建出现的electron应用。所以需要nodemon的帮助:

npm i nodemon -D
复制代码

同时,vite应用在开发环境和生产环境是有区分的,在开发环境利用本地server来开启服务,实时编译来展示html文件内容的,但是在生产环境,是加在编译好可直接运行的文件。所以在electron这边也需要区分一下环境,这时候就需要cross-env来帮忙:

npm i cross-env -D
复制代码

安装完成后,更改package.json文件。

"scripts": {
    "dev": "vite",
    "build": "vite build",
    "serve": "vite preview",
    "dev:electron": "tsc -p tsconfig.electron.json && cross-env NODE_ENV=development electron ./build/main.js",
    "dev:tsc": "nodemon --watch electron/main.ts --exec 'npm run dev:electron'"
  },
复制代码

运行命令启动:

npm run dev:tsc
复制代码

这时候可以看到程序已经运行起来了

在electron中主进程中获取数据

在main.ts新增:

const str = path.resolve('xxx');
let avaterArr: any[];
glob(
  "**/*.png",
  {
    cwd: str,
  },
  (err, files) => {
    avaterArr = files;
  }
);
ipcMain.on("request-data", async (event: IpcMainEvent, ...arg: any[]) => {
  const avaterBuffer = avaterArr.splice(0, 10).map(async (v: string) => {
    const file = await fsPromise(path.join(str, v));
    const arrayBufferView = new Uint8Array(file);
    return arrayBufferView;
  });
  const data = await Promise.all(avaterBuffer);
  await event.reply("data", { root: str, avaterArr: data });
});
复制代码

在前端使用electron的ipcRenderer

修改HelloWorld.vue文件,引入import { ipcRenderer } from "electron";,这时候会发现vite更新后,electron的deguer报错了,发现无法使用ipcRenderer,这里有两个问题,

第一,在前端使用electron是需要node环境的,所以在main.ts文件中有这两个配置:

nodeIntegration: true,
contextIsolation: false,
复制代码

第二个问题,improt这种引入方式导致在node环境无法使用,必须改为require,所以需要开发一个vite的plugin来帮助解决这个问题

vite的插件编写

利用vite暴露的钩子transform来解决这个问题,这里需要注意,在开发环境是需要我们转换import到require,但是在生产环境,我们不需要将electron打包到HTML中,需要利用rollupOptions.external将其排除就好,安装vite plugin模板的写法我们照葫芦画瓢,将文件的字符串转化AST,匹配出electron,转换成require。

const reloadElectornPlugin = (): VitePlugin => {
  let configEnv: ConfigEnv;
  return {
    name: "reload-electorn-plugin",
    config(conf, env) {
      configEnv = env;
    },
    transform: (code: string, id: string) => {
      const opts = {
        excludes: ["electron"],
      };
      if (configEnv.command !== "serve") return code;

      const parsed = path.parse(id);
      if (!extensions.includes(parsed.ext)) return code;
      // 后续换成babel
      const node: any = acorn.parse(code, {
        ecmaVersion: "next",
        sourceType: "module",
      });

      let codeRet = code;
      node.body.reverse().forEach((item) => {
        if (item.type !== "ImportDeclaration") return;
        if (!opts.excludes.includes(item.source.value)) return;

        const statr = codeRet.substring(0, item.start);
        const end = codeRet.substring(item.end);
        const deft = item.specifiers.find(
          ({ type }) => type === "ImportDefaultSpecifier"
        );
        const deftModule = deft ? deft.local.name : "";
        const nameAs = item.specifiers.find(
          ({ type }) => type === "ImportNamespaceSpecifier"
        );
        const nameAsModule = nameAs ? nameAs.local.name : "";
        const modules = item.specifiers
          .filter(({ type }) => type === "ImportSpecifier")
          .reduce((acc, cur) => acc.concat(cur.imported.name), []);
        
      codeRet = `${statr}const { ${modules.join(", ")} } = require(${
        item.source.raw
      })${end}`;
      });
      return codeRet;
    },
  };
};
复制代码

这时候应该就可以了。

更文总结

参见了更文活动,写了几篇水文,也用心写了几篇,每当想提笔准备写文章的时候,总是担心自己输出不了高质量的文章,害怕误导了其他人,所以鸽了许多天,今天还在还债。同时也反思了一下自身这个情况:还是自己的基础知识薄弱,没有形成体系,思路不够清晰,导致在写文章的过程中废话很多,包含的知识不够纯。所以,还要加油。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享