前言
本文主要是整理,在用electron实现收藏夹功能时解决了的Bug!
Bug
问题1:
main.js is treated as an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which declares all .js files in that package scope as ES modules.
意思:
main.js 被视为 ES 模块文件,因为它是一个 .js 文件,其最近的父 package.json 包含 "type": "module" ,它将该包范围内的所有 .js 文件声明为 ES 模块。
解决方案
将 package.json 中的"type": "module" 删除
问题2:
winState is not a constructor
出现原因
const winState=require("electron-win-state")
使用require引入electron-win-state时,引入的是一个 这样的 { default: \[class WinState] } 对象,所以需要将
const winState=require("electron-win-state").default
这样便解决了这个问题了。
需要注意的是,使用import引入不会出现这样的问题,所以在使用import引入时不用加default!
问题3:**expected "indent", got "outdent" **
使用stylus写css 样式报错:
expected "indent", got "outdent"
问题:
- tab键和空格键混用
- 缩进不正确,父子级和兄弟级混淆;样式没缩进,缩进错误
- 代码没有写完整,存在残缺代码,或者代码编写错误
解决方案: 可以设置文件保存时,自动格式化,这样修复我们的空格和tab键使用错误的问题。
问题4:处理默认样式问题
处理默认样式问题,安装normalize.css插件,并在src下的main文件中引入即可。
import 'normalize.css'
问题4:
主进程与渲染进程之间通信乱码问题。
问题5VM21 sandbox_bundle:2 Unable to load preload script: D:\study\electron-egg\favorites\readit\preload\index.js
主进程加载预加载文件报错
遇到这个问题:
- 检查路径是否正确
- 检查预加载文件是否存在错误
问题6:electron 解除安全警告
两种方法:
- 在src/renderer/main.js中,任意一行代码加入:
process.env\['ELECTRON\_DISABLE\_SECURITY\_WARNINGS'] = 'true' <meta http-equiv="Content-Security-Policy" content="default-src 'self'">,将Content-Security-Policy头设置为'default-src 'self'',表示只允许从同一域名加载资源。
问题7: winState.manage is not a function
出现Bug原因:直接使用了引入的默认类调用manage方法,应该使用实例对象调用manage。 npm 文档有坑:
文档中实例名字直接等于引入类的名字,需要特别注意一下!
问题8:使用electron-win-state进行不同窗口之间状态管理,出现不同窗口之间窗台互相干扰问题。
例如: 调整子窗口大小,关闭子窗口,再关闭主窗口,再打开主窗口,主窗口大小变为了我们设置的子窗口的样式
解决方案:
给每个窗口保存状态时,设置一个单独的名字,用到electronStoreOptions属性。
const winstate = new windowStateKeeper({
defaultWidth: 1200,
defaultHeight: 800,
dev: true,
// 解决窗口之间相互干扰问题
electronStoreOptions: {
name: 'window-state-open'
}
});
问题9:Uncaught SyntaxError: Invalid or unexpected token
const cssText = `top: 50px;
left: 50px;
position:fixed;
`
let code = `const div =document.createElement('div')
div.id = 'readit-button'
div.innerHTML = '关闭窗口'
div.addEventListener('click',()=>{myApi.close()})
div.style.cssText = '${cssText}'
document.body.appendChild(div)`
上面这段代码报这样的错误,出现原因:cssText中的样式不允许换行。
问题10:executeJavaScript向子窗口注入js代码报错:
An object could not be cloned.at t.ipcRendererInternal.send
解决方案:executeJavaScript返回promise对象,使用catch捕获错误,并进行处理。
问题11 在打开的新窗口中注入js代码,并实现与主进程通信。
- 通过
contents.executeJavaScript(code[, userGesture])向一个子窗口中注入js代码 - 通信:
- 主进程只有一个,渲染进程可以有很多个
- 每个渲染进程与主进程通信,都需要有一个预加载文件。文件中定义渲染进程触发的方法。
- 子窗口打开的页面上调用方法.
新的子窗口对应的预加载文件:
const { contextBridge, ipcRenderer, } = require('electron');
const closeWindow = () => {
ipcRenderer.invoke('close-window-event');
}
contextBridge.exposeInMainWorld('myWindowApi', {
closeWindow
})
打开子窗口时,设置预加载文件。
win = new BrowserWindow({
...winstate.winOptions,
webPreferences: {
preload: path.join(__dirname, '../preload/open.js'), // 引入预处理文件
},
});
定义主进程中接受方法:
//关闭窗口事件
ipcMain.handle('close-window-event', async (e) => {
win.close();
})
页面上调用:
const cssText = `bottom: 50px;right: 50px;position:fixed;z-index:1000;width:100px;height:30px;background-color: cornflowerblue;border-radius: 5px;text-align :center;line-height:30px;cursor:pointer;color:#fff;`
let code = `const div =document.createElement('div')
div.id = 'readit-button'
div.innerHTML = '关闭窗口'
div.addEventListener('click',()=>{myWindowApi.closeWindow()})
div.style.cssText = '${cssText}'
document.body.appendChild(div)`
electron 主线程向渲染线程发送消息
- 首先渲染进程和主进程之间建立一个通信通道
- 渲染进程向主进程发送请求
- 主进程收到请求,将event暂存起来。
- 在主进程对应的事件处理完毕之后,调用event.sender.send('channel',data)方法向渲染进程返回数据
- 渲染进程使用ipcRenderer.on('channel',()=>{})接收主进程返回的数据
例如:主进程保存图片成功之后要通知渲染进程更新图片列表。
渲染进程的预加载文件
const getFileListOnMain = (cb) => {
ipcRenderer.send('on-filelist-event');// 先建立与主进程的通信通道
// 主进程向渲染进程发送消息时接收的事件
ipcRenderer.on('message-from-main', (e, data) => {
cb()
});
}
contextBridge.exposeInMainWorld('myApi', {
getFileListOnMain
})
主进程文件
const { Menu, dialog, ipcMain } = require("electron"); //Menu创建原生菜单喝上下文菜单,dialog 模块提供了api来展示原生的系统对话框
const path = require('path');
//避免文件名重复,引入第三方库设置文件名
const randomstring = require("randomstring");
//保存时需要知道图片类型,需要引入第三方库
const imageType = require('image-type')
//需要根据url得到文件内容,使用got
const got = require("got").default;
//引入node文件模块
const { writeFile } = require('fs')
const { readdir } = require('fs/promises') //node读取一个目录下所有的文件和子目录
//建立与渲染进程的通信通道
let event = null
ipcMain.on('on-filelist-event', (e, args) => {
event = e;
// 由于主进程要做别的异步操作之后才向渲染进程发送消息,
//所以先将对应的event暂存起来,等到对应的异步事件处理完毕之后再返回数据给渲染进程
})
const save = (srcURL) => {
const template = [{
label: '图片另存为.....',
click: async () => {
//根据url得到文件内容
let content = await got.get(srcURL);
//将文件内容转成二进制
let bufferContent = Buffer.from(content.rawBody)
// 创建文件名字随机数
let imgNameRandom = randomstring.generate(10);
//判断图片类型
let { ext } = imageType(bufferContent)
//组装文件类型
let fileName = imgNameRandom + '.' + ext
const { canceled, filePath } = await dialog.showSaveDialog({
title: '图片另存为',
defaultPath: path.join(__dirname, `../public/upload/${fileName}`)
})
// 如果被保存了,将文件内容写入
if (!canceled) {
writeFile(filePath, bufferContent, async () => {
event.sender.send('message-from-main', 'change') // 图片保存完毕之后,通知渲染进程
});
}
}
}]
const menu = Menu.buildFromTemplate(template);
menu.popup();
}
module.exports = save