目录
title: 【Electron Module】 not found Error Can’t resolve ‘fs’
author:十六进制
date: 2024-4-20 17:11:00
sidebar: auto
tags:
- 学习
- Electron
- Vue.js
- 前端
- 记录问题
categories: - bug
一、发现问题
最近开始上手用electron将vue项目打包成一个可执行程序,其中有个需求,就是electron自带的frame工具栏,以及最大化,最小化,关闭按钮等样式太丑了,于是我就想在自己的vuecli组件中引入electron的ipcRenderer
模块,想通过ipcRenderer.send
方法将关闭窗口的消息发送给主进程,最开始的尝试代码如下:
// 在 Electron 主进程中 (main.js)
const { app, ipcMain } = require('electron');
ipcMain.on('close-app', () => {
// 在此执行关闭窗口的操作
app.quit();
});
// 在 Vue 组件中
methods: {
handleCloseButtonClick() {
// 在 Vue 组件中引入 Electron 模块
const { ipcRenderer } = require('electron');
// 发送消息给主进程,请求退出程序
window.ipcRenderer.send('quit-app'); //方式1
window.electron.ipcRenderer.send('quit-app'); //方式2
}
}
结果:在运行npm run build
尝试打包我的vue项目时就报错了,提示没有安装electron模块,于是我又在我的vue工程下运行如下代码安装该模块:
npm install electron --save-dev
在运行npm run build
时,又报错了,提示:Module not found: Error: Can’t resolve ‘fs’ && Module not found: Error: Can’t resolve ‘path’
我又尝试了在vue.config.js中对webpack进行配置:
configureWebpack: {
externals: {
"fs": 'require("fs")',
"path": 'require("path")',
electron: 'require("electron")' // 避免打包 Electron 模块
},
resolve: {
fallback: {
"fs": false, //Webpack将不再尝试解析Electron模块中的fs模块 无效,无法解决根本问题
"path": require.resolve("path-browserify"), //Webpack会使用path-browserify来替代Node.js中的path模块
}
},
},
但是,无论是externals还是resolve,fallback,我都试过了,都无效,报错都是以下信息:
[electron]fs.existsSync is not a function
或者
ipcRenderer.send is not a function
直到我发现了这篇文章,我以为自己就快要解决问题了,结果最后还是失败了,我在想应该是我和博主使用的electron版本不一致,我使用的是目前对我这个时间来说最新的版本"electron": "^30.0.1"
。所以最后我还是跑到了官网去查最新的api,果然功夫不负有心人,被我找到了最终的解决方案,全网都没找到的方法,结果就在官方文档上,所以下次再遇到类似问题,就请直接去看官方文档!
下面提供官方文档的链接:官方文档
请直接看》》模式 1:渲染器进程到主进程(单向),因为官方提供了一个完整的代码演示,可以直接在Electron Fiddle中打开。
二、官方文档的解决方案
1、首先在主进程中
//main.js
const { app, BrowserWindow, ipcMain } = require('electron/main')
ipcMain.on('set-title', (event, title) => {
const webContents = event.sender
const win = BrowserWindow.fromWebContents(webContents)
win.setTitle(title)
})
还是选择了用ipcMain.on
方法来监听渲染进程发给主进程的消息事件。
2、然后在渲染进程中
//renderer.js
const setButton = document.getElementById('btn')
const titleInput = document.getElementById('title')
setButton.addEventListener('click', () => {
const title = titleInput.value
window.electronAPI.setTitle(title)
})
注意:使用的是window.electronAPI.setTitle(title)
,而不是window.ipcRenderer.send('set-title', title)
3、重点来了,在预加载脚本preload.js中,绑定一个electronAPI对象,用来与主进程通信
//preload.js
const { contextBridge, ipcRenderer } = require('electron/renderer')
contextBridge.exposeInMainWorld('electronAPI', {
setTitle: (title) => ipcRenderer.send('set-title', title)
})
我首先在Electron Fiddle中尝试了官方提供的进程间通信示例,主要是实现在渲染进程中点击一个按钮,在主进程中设置窗口的标题。
于是我在官方的示例上改进了我的代码。
三、我的最终解决方案
1、在主进程中
const createWindow = () => {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 1920,
height: 1080,
fullscreen: true, // 自动全屏
frame: false, // 隐藏默认工具栏
webPreferences: {
preload: path.join(__dirname, 'preload.js'), // 添加预加载脚本
nodeIntegration: true,
// contextIsolation: false,
}
})
}
// 监听自己的关闭按钮事件,然后退出应用程序
ipcMain.on('quit-app', () => {
app.quit();
});
2、在我的渲染进程中,也就是我的vue组件中
methods: {
exitClick() {
console.log(window.electronAPI);
window.electronAPI.quitApp(); //在这里调用主进程的quit-app事件
},
}
3、在预加载脚本中(最主要的一步,作用就是把主进程中的ipcRenderer.send
事件暴露给渲染进程)
const { contextBridge, ipcRenderer } = require('electron/renderer')
contextBridge.exposeInMainWorld('electronAPI', {
quitApp: () => ipcRenderer.send('quit-app') //暴露给渲染进程的quit-app事件
})
4、下面提供我的完整代码
// main.js
// Modules to control application life and create native browser window
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('node:path')
const url = require('url');
const createWindow = () => {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 1920,
height: 1080,
fullscreen: true, // 自动全屏
frame: false, // 隐藏默认工具栏
icon: './myapp/dist/img/logo.8d90e46a.png',
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: true,
// contextIsolation: false,
}
})
// 加载 dist 文件夹中的 index.html
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, 'myapp', 'dist','index.html'),
protocol: 'file:',
slashes: true
}));
// 打开开发工具
// mainWindow.webContents.openDevTools()
}
// 这段程序将会在 Electron 结束初始化
// 和创建浏览器窗口的时候调用
// 部分 API 在 ready 事件触发后才能使用。
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
// 在 macOS 系统内, 如果没有已开启的应用窗口
// 点击托盘图标时通常会重新创建一个新窗口
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
// 除了 macOS 外,当所有窗口都被关闭的时候退出程序。 因此, 通常
// 对应用程序和它们的菜单栏来说应该时刻保持激活状态,
// 直到用户使用 Cmd + Q 明确退出
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
// 在当前文件中你可以引入所有的主进程代码
// 也可以拆分成几个文件,然后用 require 导入。
// 监听自己的关闭按钮事件,然后退出应用程序
ipcMain.on('quit-app', () => {
app.quit();
});
// preload.js
const { contextBridge, ipcRenderer } = require('electron/renderer')
contextBridge.exposeInMainWorld('electronAPI', {
// setTitle: (title) => ipcRenderer.send('set-title', title), //官网示例
quitApp: () => ipcRenderer.send('quit-app')
})
//vue文件
methods: {
exitClick() {
this.$confirm('确定要退出吗?' , {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
console.log(window.electronAPI);
window.electronAPI.quitApp(); //在这里调用主进程的quit-app事件
}).catch(() => {
console.log("取消退出app");
});
},
}