记Electron初体验的踩坑之旅
背景
为什么要写electron的背景就是,在平常的业务中因为项目需要做国际化,所以经常需要反复跳来跳去来复制i18n的key,然后又要在几千行的json文件中去找这个key在哪个对象里面。最重要的是这种工作是你要兼顾几种语言就要重复几次的。
这时候我就想,有没有可能做一个图形化工具来实现自动将所输入的内容自动插入到json文件的对应位置。
具体的需求是:
第一个输入框输入json文件的对应键,比方说输入a.b.c
;
第二个输入框输入英文文本,这个文本会插入到json_En.json
文件的对应位置a:{b:{c:{...}}}
中;
第三个输入框输入中文文本,这个文本会插入到json_Zh.json
文件的对应位置a:{b:{c:{...}}}
中。
那就开干
因为此前没有任何Electron的基础,所以第一时间是找到了大名鼎鼎的GPT先生,我向它具体阐述了我的需求,它很快的给出了它的答案了。
初版代码
下面是一个简单的步骤指南(其实已经结合了我自己的修改了):
-
创建项目文件夹:首先,创建一个新的文件夹作为项目的根目录。你可以选择一个合适的名称,比如
json-insert-tool
。 -
初始化项目:在项目文件夹中打开命令行终端,并运行以下命令来初始化npm项目:
npm init
这将引导你完成npm项目的初始化过程,并生成一个package.json
文件。 -
安装依赖:我们需要安装Electron作为项目的依赖项。在命令行终端中运行以下命令:
npm install electron -S
-
创建主进程文件:在项目文件夹中创建一个名为
main.js
的文件。这将是Electron应用的主进程文件,负责启动应用和管理窗口。 -
编写主进程代码:在
main.js
文件中,添加以下代码:const { app, BrowserWindow } = require('electron'); const path = require('path'); const url = require('url'); function createWindow() { const mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: true } }); const indexPath = url.format({ protocol: 'file', pathname: path.join(__dirname, 'index.html'), slashes: true }); mainWindow.loadURL(indexPath); // 打开开发者工具(可选) // mainWindow.webContents.openDevTools(); mainWindow.on('closed', () => { app.quit(); }); } app.on('ready', createWindow); app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } }); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } });
这段代码用于创建Electron窗口,加载
index.html
文件,并处理窗口关闭事件。 -
创建HTML文件:在项目文件夹中创建一个名为
index.html
的文件,作为应用的主界面。 -
编写HTML代码:在
index.html
文件中,添加以下HTML代码:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>JSON插入工具</title> <link rel="stylesheet" href="style.css" /> </head> <body> <h1>JSON插入工具</h1> <label for="jsonFile">选择英文JSON文件:</label> <input type="file" id="jsonFileEn" accept=".json" /> <br /> <label for="jsonFile">选择中文JSON文件:</label> <input type="file" id="jsonFileZh" accept=".json" /> <br /> <label for="keyInput">JSON键:</label> <input type="text" id="keyInput" /> <br /> <label for="englishInput">英文文本:</label> <input type="text" id="englishInput" /> <br /> <label for="chineseInput">中文文本:</label> <input type="text" id="chineseInput" /> <br /> <button id="insertButton">插入</button> <div id="message"></div> <script src="./renderer.js"></script> </body> </html>
-
创建渲染进程脚本:在项目文件夹中创建一个名为
renderer.js
的文件,作为应用的渲染进程脚本。 -
编写渲染进程脚本代码:在
renderer.js
文件中,添加以下JavaScript代码:const fs = require("fs"); const jsonFileEnInput = document.getElementById("jsonFileEn"); const jsonFileZhInput = document.getElementById("jsonFileZh"); const keyInput = document.getElementById("keyInput"); const englishInput = document.getElementById("englishInput"); const chineseInput = document.getElementById("chineseInput"); const insertButton = document.getElementById("insertButton"); const message = document.getElementById("message"); insertButton.addEventListener("click", () => { const jsonFileEnPath = jsonFileEnInput.files[0].path; const jsonFileZhPath = jsonFileZhInput.files[0].path; const key = keyInput.value; const englishText = englishInput.value; const chineseText = chineseInput.value; insertTextToJSONFile(jsonFileEnPath, key, englishText); insertTextToJSONFile(jsonFileZhPath, key, chineseText); }); function insertTextToJSONFile(filePath, key, text) { // 读取JSON文件 fs.readFile(filePath, "utf8", (err, data) => { if (err) throw err; // 解析JSON数据 const jsonData = JSON.parse(data); // 构建键路径数组 const keys = key.split("."); // 获取最后一个键 const lastKey = keys.pop(); // 获取要插入文本的目标对象 let targetObj = jsonData; for (const k of keys) { if (!targetObj[k]) { targetObj[k] = {}; } targetObj = targetObj[k]; } // 插入文本到目标对象的最后一个键 targetObj[lastKey] = text; // 将更新后的JSON数据写入文件 fs.writeFile(filePath, JSON.stringify(jsonData, null, 2), "utf8", (err) => { if (err) { message.innerText = "插入失败!" + err; throw err; } message.innerText = "插入成功!"; }); }); }
-
创建CSS文件:在项目文件夹中创建一个名为
style.css
的文件,作为应用的样式表。 -
编写CSS代码:在
style.css
文件中,添加自定义的CSS样式。 -
启动应用:在命令行终端中,进入项目文件夹,并运行以下命令来启动应用:
electron .
这将启动Electron应用,并打开一个窗口显示应用的界面。
坑1 - 点击按钮无反应
正当我以为程序就这么成功的运行起来的时候,却发现怎么点都没反应,启动应用的终端也没有任何报错信息。对于还是Electron小白的我,感受到了一丝无助,于是还是求助了GPT大人。
他给出了这样的回答:
如果点击按钮没有任何反应,可能有几个可能的原因。以下是一些常见的故障排除步骤:
确保HTML文件中的按钮和输入字段的
id
属性与渲染进程脚本中的选择器匹配。请检查renderer.js
中getElementById
方法的参数与HTML文件中的元素id
是否一致。确保渲染进程脚本
renderer.js
正确加载到HTML文件中。你可以在HTML文件中使用<script>
标签将renderer.js
脚本引入,确保路径和文件名正确。示例:
<script src="renderer.js"></script>
检查浏览器控制台的错误信息。在应用程序运行时,打开开发者工具,并切换到控制台选项卡,查看是否有任何错误消息。错误消息可以提供有关问题的更多信息。
确保JSON文件的路径和文件名正确。检查
insertTextToJSONFile
函数中指定的JSON文件名是否与实际文件名匹配,并且文件位于正确的位置。你可以使用console.log
语句在函数中打印文件路径,以确保它们是正确的。示例:
console.log(filePath);
检查文件读取和写入操作的权限。确保应用程序具有读取JSON文件和写入更新后的JSON数据的权限。如果文件位于受限制的目录中,可能需要更改文件的权限或将其移动到应用程序具有访问权限的位置。
如果你仍然遇到问题,请提供更多详细信息,例如浏览器控制台中的错误消息或完整的代码示例,以便我可以更好地帮助你解决问题。
Really bad,没有任何帮助。它指出的那些错误都是不存在的也是不会犯的,那么就只剩下第三点,我该如何调试这个Electron应用了。
答案是:Ctrl + Shift + I
可以在应用里面呼出一个最熟悉的浏览器控制台!我终于发现了按钮点击无响应的原因了。
坑2 - Uncaught ReferenceError: require is not defined
没道理啊,这Electron用的不是node服务吗,怎么会没有require
!?
于是我尝试用了ES模块的import
来替代,依旧不行。
据说如果要用import
那么html
文件的script
那里的type
要改成module
,还是不行。
没有办法还是求助于GPT,他是这么说的:
如果你在渲染进程的 JavaScript 代码中遇到 “
Uncaught ReferenceError: require is not defined
” 错误,这通常是因为在渲染进程中直接使用了 Node.js 的模块系统。在 Electron 中,主进程和渲染进程有不同的上下文环境。在主进程中,你可以使用 Node.js 的模块系统(如
require
)来访问系统资源和执行底层操作。然而,在渲染进程中默认情况下是无法直接使用 Node.js 的模块系统的。如果你需要在渲染进程中使用 Node.js 的模块,需要通过 Electron 提供的
remote
模块来访问主进程的功能。remote
模块可以让你在渲染进程中以同步或异步方式调用主进程的模块和方法。以下是一些解决该错误的方法:
使用
remote
模块:在渲染进程的代码中,确保你使用了remote
模块来访问主进程的功能。例如,如果你想在渲染进程中使用fs
模块,可以使用以下代码:const { remote } = require('electron'); const fs = remote.require('fs'); // 现在你可以在渲染进程中使用 fs 模块了
注意,在使用
remote.require
加载模块时,需要确保在主进程中已经正确地使用了require
。使用
preload
脚本:你可以创建一个preload
脚本,在其中预加载一些模块,以便在渲染进程中直接使用它们。在preload
脚本中,你可以使用 Node.js 的模块系统,而无需使用remote
模块。在主进程创建 BrowserWindow 时,使用
webPreferences
选项指定preload
脚本的路径,如下所示:const mainWindow = new BrowserWindow({ webPreferences: { preload: path.join(__dirname, 'preload.js') } });
在
preload.js
脚本中,你可以使用require
来加载需要的模块,然后通过contextBridge
将它们公开给渲染进程。const { contextBridge, ipcRenderer } = require('electron'); contextBridge.exposeInMainWorld('api', { // 在此添加需要公开给渲染进程的模块和方法 fs: require('fs') });
然后,在渲染进程中,你可以直接访问
window.api
来使用预加载的模块:const fs = window.api.fs; // 现在你可以在渲染进程中使用 fs 模块了
使用
preload
脚本可以更好地控制哪些模块可以在渲染进程中使用,并提供了一种更安全的方式来访问主进程功能。请注意,无论你选择哪种方法,都应该谨慎控制渲染进程对主进程资源的访问,并遵循安全性最佳实践,以防止潜在的安全漏洞。
希望这些解决方案能帮助你解决 “Uncaught ReferenceError: require is not defined”
错误。如果你有其他问题,请随时提问!
BAD! BAD! BAD! 我尝试了方法1,不行。这让我对它失去了信息,我转而求助搜索引擎了。
于是我找到了如下的解决方案:Browser: Uncaught ReferenceError: require is not defined
就是在main.js
的这里加上这一行 contextIsolation: false
坑3 - 打包EXE失败
直接贴GPT回复吧,过去有一段时间了,竟然忘记那时候是为啥一开始打不了包了。(记起来似乎是需要把electron
安装成devDependencies)
要将 Electron 应用程序打包成一个随时运行的软件,你可以使用 Electron 提供的打包工具。以下是一种常见的方法,使用 Electron Builder 来打包应用程序:
-
安装 Electron Builder:在命令行或终端中,进入你的应用程序的根目录,并运行以下命令来安装 Electron Builder:
npm install electron-builder --save-dev
-
配置打包选项:在你的应用程序的根目录中创建一个
electron-builder.json
文件,并配置打包选项。你可以指定目标平台、文件名称、应用程序元数据等。示例
electron-builder.json
文件:{ "appId": "com.example.myapp", "productName": "MyApp", "directories": { "output": "dist" }, "win": { "target": "portable" } }
在上述示例中,我们指定了应用程序的 ID、产品名称和输出目录。
"target": "portable"
表示我们希望生成一个可随时运行的可执行文件。 -
配置打包脚本:在
package.json
文件中,添加一个打包脚本命令,以便轻松地执行打包操作。示例
package.json
文件:{ "name": "my-electron-app", "version": "1.0.0", "scripts": { "package": "electron-builder" }, "devDependencies": { "electron": "^x.x.x", "electron-builder": "^y.y.y" } }
在上述示例中,我们添加了一个名为
"package"
的脚本命令,并将其设置为执行electron-builder
。 -
执行打包:在命令行或终端中运行以下命令来执行打包操作:
npm run package
执行完毕后,你将在
dist
目录中找到打包后的应用程序文件。根据你的配置,可能会生成不同平台的可执行文件。
请注意,上述步骤只是一个基本的示例,你可以根据自己的需求进行更多的配置和定制。你可以查阅 Electron Builder 的文档以获取更多详细信息和配置选项。
希望这可以帮助你将 Electron 应用程序打包成一个随时运行的软件。如果你有其他问题,请随时提问!