这是我参与更文挑战的第9天,活动详情查看: 更文挑战。
起因是把爬了一些美女的照片,想展示一下。用vite脚手架生成了一个Vue的项目,但是由于这些图片是不断更新的,所以不能更改文件夹的位置。在我印象中,一个项目不能引用项目之外的资源。所以想利用NodeJs API去读取本地文件,正好Electron即能满足展示,也能满足加载本地文件的需求。
整体思路
如上图所示,利用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;
},
};
};
复制代码
这时候应该就可以了。
更文总结
参见了更文活动,写了几篇水文,也用心写了几篇,每当想提笔准备写文章的时候,总是担心自己输出不了高质量的文章,害怕误导了其他人,所以鸽了许多天,今天还在还债。同时也反思了一下自身这个情况:还是自己的基础知识薄弱,没有形成体系,思路不够清晰,导致在写文章的过程中废话很多,包含的知识不够纯。所以,还要加油。