Building Desktop Apps with Electron + Next.JS (without Nextron)使用Electron + Next.JS构建桌面应用程序(无需Nextron)
Building Desktop Apps with Electron + Next.JS (without Nextron)使用Electron + Next.JS构建桌面应用程序(无需Nextron)
[
7 min read
Dec 3, 2023 2023年12月3日
A bit of story first
先讲一个小故事
Some time ago I’ve started a personal project called Local.Host, which was a replacement alternative for XAMPP, for PHP developers using Windows. Due to my extensive knowledge of web development, I decided to use Electron for building the desktop app, alongside with some Windows’ CMD commands and tricks.
一段时间前,我开始了一个名为Local.Host的个人项目,它是针对使用Windows的PHP开发人员的XAMPP替代选择。由于我对Web开发有着丰富的知识,我决定使用Electron来构建桌面应用程序,同时结合一些Windows的CMD命令和技巧。
After some search, I’ve found Nextron, a good Next.JS + Electron boilerplate. But, today, I’ve noticed by some friends that my software is a bit heavy, sometimes freezing. Understanding more about Electron, I’ve noticed that Nextron comes with an old version of both Electron and Next.JS, and some “internal” mechanisms used are causing slow downs on my application.
经过一番搜索,我找到了Nextron,一个很好的Next.JS + Electron样板。但是,今天,我注意到一些朋友说我的软件有点重,有时会冻结。对Electron的了解更多后,我注意到Nextron使用了旧版本的Electron和Next.JS,并且一些“内部”机制导致了我的应用程序变慢。
To not fully rewrite the app, I tried to “create” my own Next.JS + Electron project, with latest versions of both and, obviously, more lightweight than Nextron.
为了不完全重写应用程序,我尝试“创建”自己的Next.JS + Electron项目,使用最新版本的两者,并且显然比Nextron更轻量化。
So, now, I have a (I personally consider it) very solid guide for creating desktop apps with these techs. And, of couse, I decided to share it with you, because I’ve found a lot of people that are searching for something like that and are all with the same opinion about Nextron.
所以,现在,我有一个(我个人认为)非常实用的使用这些技术创建桌面应用程序的指南。当然,我决定与你分享,因为我发现很多人都在寻找类似的东西,并且对Nextron持有相同的观点。
The guide 指南
Step 1 — Start a Next.JS project
第一步 - 开始一个Next.JS项目
Start with Next.JS. First, run the traditional command
npx create-next-app@latest <your_project_name>
. Set the preferences you want durint setup. The only setting you need to take care is about using App Router. Currently, I personally choose "No" to keep using the old-but-gold Pages Router, and it's the way I've used.从Next.JS开始。首先,运行传统命令
npx create-next-app@latest <your_project_name>
。在设置过程中设置您想要的首选项。您需要注意的唯一设置是关于使用App Router。目前,我个人选择“否”以继续使用旧但经典的Pages Router,这是我一直使用的方式。<span id="21f2" data-selectable-paragraph="">npx create-next-app@latest electron-nextjs-project</span>
Step 2 — Install Electron and other dependencies
第二步 - 安装Electron和其他依赖项
cd
into your project folder (in this example, electron-nextjs-project
) and install the following dependencies, with both following commands:将
cd
文件夹复制到您的项目文件夹中(在本示例中, electron-nextjs-project
),并使用以下两个命令安装以下依赖项:Dev dependencies: 开发依赖
<span id="781b" data-selectable-paragraph="">npm install --save-dev electron electron-builder concurrently</span>
Project dependencies: 项目依赖
<span id="354d" data-selectable-paragraph="">npm install electron-serve</span>
Step 3 — Setting up package.json
第三步 - 设置package.json
Start opening the
package.json
file with your preferred text editor or IDE. You need to modify the build
and dev
scripts, and add the main
property. The concurrently we installed before will be used to run both Next.JS and Electron in parallel during the development, and you need to point the main
script to the entrypoint of your Electron application. Also, we need to add "author" and "description" attributes, both needed when building the application executables.开始使用您喜欢的文本编辑器或IDE打开
package.json
文件。您需要修改 build
和 dev
脚本,并添加 main
属性。我们之前安装的concurrently将在开发过程中同时运行Next.JS和Electron,并且您需要将 main
脚本指向您的Electron应用程序的入口点。此外,我们还需要添加"author"和"description"属性,在构建应用程序可执行文件时都是必需的。<span id="f59d" data-selectable-paragraph=""><span>{</span><br> ...<br> <span>"main"</span><span>:</span> <span>"main/main.js"</span><span>,</span><br> <span>"author"</span><span>:</span> <span>"RBFraphael"</span><span>,</span><br> <span>"description"</span><span>:</span> <span>"Electron + NextJS example project"</span><span>,</span><br> <span>"scripts"</span><span>:</span> <span>{</span><br> <span>"dev"</span><span>:</span> <span>"concurrently -n \\"NEXT,ELECTRON\\" -c \\"yellow,blue\\" --kill-others \\"next dev\\" \\"electron .\\""</span><span>,</span><br> <span>"build"</span><span>:</span> <span>"next build && electron-builder"</span><span>,</span><br> ...<br> <span>}</span><br> ...<br><span>}</span></span>
As you can see, when developing with
npm run dev
will start both Next.JS and Electron (you can find more details about Concurrently here), and, when building your application with npm run build
it will build the Next.JS files, then the Electron application (find more information about Electron Builder here).如您所见,使用
npm run dev
进行开发时,将同时启动Next.JS和Electron(您可以在此处找到有关Concurrently的更多详细信息),而使用 npm run build
构建应用程序时,将先构建Next.JS文件,然后构建Electron应用程序(在此处找到有关Electron Builder的更多信息)。Step 4 — Configuring Next.JS
第四步 - 配置Next.JS
Since we’re using Next.JS as the starting point, we should have a
next.config.js
file in the root of our project. In this file, we need to set the output mode to export
, and disable the image optimization. So, inside this file, add the following params to the exported nextConfig
object.由于我们使用Next.JS作为起点,所以我们的项目根目录中应该有一个
next.config.js
文件。在这个文件中,我们需要将输出模式设置为 export
,并禁用图像优化。因此,在这个文件中,将以下参数添加到导出的 nextConfig
对象中。<span id="d741" data-selectable-paragraph=""><span>const</span> nextConfig = {<br> ...<br> <span>output</span>: <span>"export"</span>,<br> <span>images</span>: {<br> <span>unoptimized</span>: <span>true</span><br> }<br> ...<br>}</span>
Step 5 — Electron bootstrap
第五步 — Electron引导
Now, we will code the Electron part of our application. Start creating a folder called
main
, and two files inside that folder: main.js
(as we set on the main
key of our package.json
) and preload.js
(for exposing Electron to front-end), and insert the following content in the main.js
file.现在,我们将编写应用程序的Electron部分。开始创建一个名为
main
的文件夹,并在该文件夹中创建两个文件: main.js
(如我们在 package.json
的 main
键中设置的)和 preload.js
(用于将Electron暴露给前端),并将以下内容插入 main.js
文件中。<span id="89cd" data-selectable-paragraph=""><span>const</span> { app, <span>BrowserWindow</span> } = <span>require</span>(<span>"electron"</span>);<br><span>const</span> serve = <span>require</span>(<span>"electron-serve"</span>);<br><span>const</span> path = <span>require</span>(<span>"path"</span>);<br><br><span>const</span> appServe = app.<span>isPackaged</span> ? <span>serve</span>({<br> <span>directory</span>: path.<span>join</span>(__dirname, <span>"../out"</span>)<br>}) : <span>null</span>;<br><br><span>const</span> <span>createWindow</span> = () => {<br> <span>const</span> win = <span>new</span> <span>BrowserWindow</span>({<br> <span>width</span>: <span>800</span>,<br> <span>height</span>: <span>600</span>,<br> <span>webPreferences</span>: {<br> <span>preload</span>: path.<span>join</span>(__dirname, <span>"preload.js"</span>)<br> }<br> });<br><br> <span>if</span> (app.<span>isPackaged</span>) {<br> <span>appServe</span>(win).<span>then</span>(<span>() =></span> {<br> win.<span>loadURL</span>(<span>"app://-"</span>);<br> });<br> } <span>else</span> {<br> win.<span>loadURL</span>(<span>"<http://localhost:3000>"</span>);<br> win.<span>webContents</span>.<span>openDevTools</span>();<br> win.<span>webContents</span>.<span>on</span>(<span>"did-fail-load"</span>, <span>(<span>e, code, desc</span>) =></span> {<br> win.<span>webContents</span>.<span>reloadIgnoringCache</span>();<br> });<br> }<br>}<br><br>app.<span>on</span>(<span>"ready"</span>, <span>() =></span> {<br> <span>createWindow</span>();<br>});<br><br>app.<span>on</span>(<span>"window-all-closed"</span>, <span>() =></span> {<br> <span>if</span>(process.<span>platform</span> !== <span>"darwin"</span>){<br> app.<span>quit</span>();<br> }<br>});</span>
As we can analyse, this code will use electron-serve to properly serve static files from the
out/
folder, but only when our app is packaged (builded, in production, compiled, whatever you want). While not packaged, we will run our app through the http://localhost:3000
URL, which is the default URL for Next.JS projects. Also, we already prepared the script to load our preload.js
file. Another point of this scripts is the "did-fail-load" event while development. We added it because sometimes Electron may start faster than Next.JS (remember they are called together with concurrently), so, in those moments, it will trigger an error that "URL cannot be loaded" or someting like that, and will give us only a blank screen. With this event implemented on our script, when this occurs, it will automatically reload the app content, until success when showing Next.JS content.根据我们的分析,这段代码将使用electron-serve从
out/
文件夹正确地提供静态文件,但仅在我们的应用程序打包(构建、生产、编译,任何你想要的)时才会这样做。在未打包的情况下,我们将通过 http://localhost:3000
URL运行我们的应用程序,这是Next.JS项目的默认URL。此外,我们已经准备好加载我们的 preload.js
文件的脚本。这些脚本的另一个重点是开发过程中的"did-fail-load"事件。我们添加了它,因为有时Electron可能比Next.JS启动得更快(请记住它们是同时调用的),所以在这些时刻,它会触发一个"URL无法加载"或类似的错误,并且只会给我们一个空白屏幕。通过在我们的脚本中实现这个事件,当出现这种情况时,它将自动重新加载应用程序内容,直到成功显示Next.JS内容。Step 6 — Setting up the preload script
第六步 - 设置预加载脚本
Now, we just need to expose the Electron for the React/Next.JS part of our application. It’s very useful for using IPC messages, for example.
现在,我们只需要将Electron暴露给我们应用程序的React/Next.JS部分。例如,使用IPC消息非常有用。
Inside the
main/preload.js
file we created before, insert the following content:在我们之前创建的
main/preload.js
文件中,插入以下内容:<span id="74b8" data-selectable-paragraph=""><span>const</span> { contextBridge, ipcRenderer } = <span>require</span>(<span>"electron"</span>);<br><br>contextBridge.<span>exposeInMainWorld</span>(<span>"electronAPI"</span>, {<br> <span>on</span>: <span>(<span>channel, callback</span>) =></span> {<br> ipcRenderer.<span>on</span>(channel, callback);<br> },<br> <span>send</span>: <span>(<span>channel, args</span>) =></span> {<br> ipcRenderer.<span>send</span>(channel, args);<br> }<br>});</span>
This way, we can call
window.electronAPI.on
on Next.JS components to handle IPC data that comes from the "back-end" of our application, and window.electronAPI.send
to send data to the "back-end" of our application.这样,我们可以在Next.JS组件上调用
window.electronAPI.on
来处理来自应用程序“后端”的IPC数据,并调用 window.electronAPI.send
将数据发送到应用程序的“后端”。Step 7 — Testing 第七步 - 测试
Finally, we can just run
npm run dev
on our terminal. If all things are well configured, we should see a beautiful Electron window with the starter content of Next.JS. Also, the built-in DevTools shoud appear. And, in the running terminal, we can see the debug from both Next and Electron, tagged with different colors too. Now is time to develop our application. Remember that we have hot reload for front-end (the renderer/Next part of our app), but, when making changes into the "back-end" (the main/Electron part of our application) we need to stop the whole application (a simple Ctrl+C on the terminal do the job) then run it again.最后,我们只需在终端上运行
npm run dev
。如果所有配置都正确,我们应该能看到一个漂亮的Electron窗口,其中包含Next.JS的初始内容。同时,内置的DevTools也应该出现。在运行的终端中,我们还可以看到来自Next和Electron的调试信息,标记有不同的颜色。现在是开发我们的应用程序的时候了。请记住,我们的前端(渲染器/Next部分)支持热重载,但是当我们对“后端”(应用程序的主要/Electron部分)进行更改时,我们需要停止整个应用程序(在终端上简单地按下Ctrl+C即可完成),然后再次运行它。Step 8 — Building the executables
第八步 - 构建可执行文件
Now it’s time to build our application executables. We’re using electron-builder to handle that for us. Start creating a file called
electron-builder.yaml
on the root of our project. Then, inside this file, put the configuration for building the application, according to the official electron-builder documentation. You can find an example below:现在是构建我们的应用程序可执行文件的时候了。我们使用electron-builder来处理这个过程。开始在我们项目的根目录下创建一个名为
electron-builder.yaml
的文件。然后,在这个文件中,根据官方的electron-builder文档,放置构建应用程序的配置。下面是一个示例:<span id="4e02" data-selectable-paragraph=""><span>appId:</span> <span>"io.github.rbfraphael.electron-next"</span><br><span>productName:</span> <span>"Electron Next.JS"</span><br><span>copyright:</span> <span>"Copyright (c) 2023 RBFraphael"</span><br><span>win:</span><br> <span>target:</span> [<span>"dir"</span>, <span>"portable"</span>, <span>"zip"</span>]<br> <span>icon:</span> <span>"resources/icon.ico"</span><br><span>linux:</span><br> <span>target:</span> [<span>"dir"</span>, <span>"appimage"</span>, <span>"zip"</span>]<br> <span>icon:</span> <span>"resources/icon.png"</span><br><span>mac:</span><br> <span>target:</span> [<span>"dir"</span>, <span>"dmg"</span>, <span>"zip"</span>]<br> <span>icon:</span> <span>"resources/icon.icns"</span></span>
After properly specifying the needed options on
electron-builder.yaml
file, you just need to run npm run build
on your terminal. The Next.JS static files will be generated and exported to the out/
directory, then the Electron application will be compiled, and saved to the dist/
directory.在
electron-builder.yaml
文件上正确指定所需选项后,只需在终端上运行 npm run build
。Next.JS静态文件将被生成并导出到 out/
目录,然后Electron应用程序将被编译并保存到 dist/
目录中。Now, when you run your application with the exported files, you should see a Electron window with your Next.JS app, now without the built-in DevTools.
现在,当您使用导出的文件运行应用程序时,您应该看到一个带有您的Next.JS应用程序的Electron窗口,现在没有内置的DevTools。
Conclusion 结论
Using Next.JS to build desktop application with Electron is a good idea, specially when you want to use React alongside with Next.JS features, such as easy routing. And it shouldn’t be a pain, and using Nextron it could be. So, the best approach (I personally believe) is to start from scratch. In this guide we learned an approach to handle that.
使用Next.JS来构建Electron桌面应用是一个好主意,特别是当你想要在Next.JS的特性中使用React,比如简单的路由时。而且这不应该是一件痛苦的事情,使用Nextron可以实现。所以,我个人认为最好的方法是从头开始。在这个指南中,我们学习了一种处理这个问题的方法。
Hope this guide may help somebody :)
希望这个指南能帮助到某人 :)
Thanks! 谢谢!
[
7 min read
Dec 3, 2023 2023年12月3日
A bit of story first
先讲一个小故事
Some time ago I’ve started a personal project called Local.Host, which was a replacement alternative for XAMPP, for PHP developers using Windows. Due to my extensive knowledge of web development, I decided to use Electron for building the desktop app, alongside with some Windows’ CMD commands and tricks.
一段时间前,我开始了一个名为Local.Host的个人项目,它是针对使用Windows的PHP开发人员的XAMPP替代选择。由于我对Web开发有着丰富的知识,我决定使用Electron来构建桌面应用程序,同时结合一些Windows的CMD命令和技巧。
After some search, I’ve found Nextron, a good Next.JS + Electron boilerplate. But, today, I’ve noticed by some friends that my software is a bit heavy, sometimes freezing. Understanding more about Electron, I’ve noticed that Nextron comes with an old version of both Electron and Next.JS, and some “internal” mechanisms used are causing slow downs on my application.
经过一番搜索,我找到了Nextron,一个很好的Next.JS + Electron样板。但是,今天,我注意到一些朋友说我的软件有点重,有时会冻结。对Electron的了解更多后,我注意到Nextron使用了旧版本的Electron和Next.JS,并且一些“内部”机制导致了我的应用程序变慢。
To not fully rewrite the app, I tried to “create” my own Next.JS + Electron project, with latest versions of both and, obviously, more lightweight than Nextron.
为了不完全重写应用程序,我尝试“创建”自己的Next.JS + Electron项目,使用最新版本的两者,并且显然比Nextron更轻量化。
So, now, I have a (I personally consider it) very solid guide for creating desktop apps with these techs. And, of couse, I decided to share it with you, because I’ve found a lot of people that are searching for something like that and are all with the same opinion about Nextron.
所以,现在,我有一个(我个人认为)非常实用的使用这些技术创建桌面应用程序的指南。当然,我决定与你分享,因为我发现很多人都在寻找类似的东西,并且对Nextron持有相同的观点。
The guide 指南
Step 1 — Start a Next.JS project
第一步 - 开始一个Next.JS项目
Start with Next.JS. First, run the traditional command
npx create-next-app@latest <your_project_name>
. Set the preferences you want durint setup. The only setting you need to take care is about using App Router. Currently, I personally choose "No" to keep using the old-but-gold Pages Router, and it's the way I've used.从Next.JS开始。首先,运行传统命令
npx create-next-app@latest <your_project_name>
。在设置过程中设置您想要的首选项。您需要注意的唯一设置是关于使用App Router。目前,我个人选择“否”以继续使用旧但经典的Pages Router,这是我一直使用的方式。<span id="21f2" data-selectable-paragraph="">npx create-next-app@latest electron-nextjs-project</span>
Step 2 — Install Electron and other dependencies
第二步 - 安装Electron和其他依赖项
cd
into your project folder (in this example, electron-nextjs-project
) and install the following dependencies, with both following commands:将
cd
文件夹复制到您的项目文件夹中(在本示例中, electron-nextjs-project
),并使用以下两个命令安装以下依赖项:Dev dependencies: 开发依赖
<span id="781b" data-selectable-paragraph="">npm install --save-dev electron electron-builder concurrently</span>
Project dependencies: 项目依赖
<span id="354d" data-selectable-paragraph="">npm install electron-serve</span>
Step 3 — Setting up package.json
第三步 - 设置package.json
Start opening the
package.json
file with your preferred text editor or IDE. You need to modify the build
and dev
scripts, and add the main
property. The concurrently we installed before will be used to run both Next.JS and Electron in parallel during the development, and you need to point the main
script to the entrypoint of your Electron application. Also, we need to add "author" and "description" attributes, both needed when building the application executables.开始使用您喜欢的文本编辑器或IDE打开
package.json
文件。您需要修改 build
和 dev
脚本,并添加 main
属性。我们之前安装的concurrently将在开发过程中同时运行Next.JS和Electron,并且您需要将 main
脚本指向您的Electron应用程序的入口点。此外,我们还需要添加"author"和"description"属性,在构建应用程序可执行文件时都是必需的。<span id="f59d" data-selectable-paragraph=""><span>{</span><br> ...<br> <span>"main"</span><span>:</span> <span>"main/main.js"</span><span>,</span><br> <span>"author"</span><span>:</span> <span>"RBFraphael"</span><span>,</span><br> <span>"description"</span><span>:</span> <span>"Electron + NextJS example project"</span><span>,</span><br> <span>"scripts"</span><span>:</span> <span>{</span><br> <span>"dev"</span><span>:</span> <span>"concurrently -n \\"NEXT,ELECTRON\\" -c \\"yellow,blue\\" --kill-others \\"next dev\\" \\"electron .\\""</span><span>,</span><br> <span>"build"</span><span>:</span> <span>"next build && electron-builder"</span><span>,</span><br> ...<br> <span>}</span><br> ...<br><span>}</span></span>
As you can see, when developing with
npm run dev
will start both Next.JS and Electron (you can find more details about Concurrently here), and, when building your application with npm run build
it will build the Next.JS files, then the Electron application (find more information about Electron Builder here).如您所见,使用
npm run dev
进行开发时,将同时启动Next.JS和Electron(您可以在此处找到有关Concurrently的更多详细信息),而使用 npm run build
构建应用程序时,将先构建Next.JS文件,然后构建Electron应用程序(在此处找到有关Electron Builder的更多信息)。Step 4 — Configuring Next.JS
第四步 - 配置Next.JS
Since we’re using Next.JS as the starting point, we should have a
next.config.js
file in the root of our project. In this file, we need to set the output mode to export
, and disable the image optimization. So, inside this file, add the following params to the exported nextConfig
object.由于我们使用Next.JS作为起点,所以我们的项目根目录中应该有一个
next.config.js
文件。在这个文件中,我们需要将输出模式设置为 export
,并禁用图像优化。因此,在这个文件中,将以下参数添加到导出的 nextConfig
对象中。<span id="d741" data-selectable-paragraph=""><span>const</span> nextConfig = {<br> ...<br> <span>output</span>: <span>"export"</span>,<br> <span>images</span>: {<br> <span>unoptimized</span>: <span>true</span><br> }<br> ...<br>}</span>
Step 5 — Electron bootstrap
第五步 — Electron引导
Now, we will code the Electron part of our application. Start creating a folder called
main
, and two files inside that folder: main.js
(as we set on the main
key of our package.json
) and preload.js
(for exposing Electron to front-end), and insert the following content in the main.js
file.现在,我们将编写应用程序的Electron部分。开始创建一个名为
main
的文件夹,并在该文件夹中创建两个文件: main.js
(如我们在 package.json
的 main
键中设置的)和 preload.js
(用于将Electron暴露给前端),并将以下内容插入 main.js
文件中。<span id="89cd" data-selectable-paragraph=""><span>const</span> { app, <span>BrowserWindow</span> } = <span>require</span>(<span>"electron"</span>);<br><span>const</span> serve = <span>require</span>(<span>"electron-serve"</span>);<br><span>const</span> path = <span>require</span>(<span>"path"</span>);<br><br><span>const</span> appServe = app.<span>isPackaged</span> ? <span>serve</span>({<br> <span>directory</span>: path.<span>join</span>(__dirname, <span>"../out"</span>)<br>}) : <span>null</span>;<br><br><span>const</span> <span>createWindow</span> = () => {<br> <span>const</span> win = <span>new</span> <span>BrowserWindow</span>({<br> <span>width</span>: <span>800</span>,<br> <span>height</span>: <span>600</span>,<br> <span>webPreferences</span>: {<br> <span>preload</span>: path.<span>join</span>(__dirname, <span>"preload.js"</span>)<br> }<br> });<br><br> <span>if</span> (app.<span>isPackaged</span>) {<br> <span>appServe</span>(win).<span>then</span>(<span>() =></span> {<br> win.<span>loadURL</span>(<span>"app://-"</span>);<br> });<br> } <span>else</span> {<br> win.<span>loadURL</span>(<span>"<http://localhost:3000>"</span>);<br> win.<span>webContents</span>.<span>openDevTools</span>();<br> win.<span>webContents</span>.<span>on</span>(<span>"did-fail-load"</span>, <span>(<span>e, code, desc</span>) =></span> {<br> win.<span>webContents</span>.<span>reloadIgnoringCache</span>();<br> });<br> }<br>}<br><br>app.<span>on</span>(<span>"ready"</span>, <span>() =></span> {<br> <span>createWindow</span>();<br>});<br><br>app.<span>on</span>(<span>"window-all-closed"</span>, <span>() =></span> {<br> <span>if</span>(process.<span>platform</span> !== <span>"darwin"</span>){<br> app.<span>quit</span>();<br> }<br>});</span>
As we can analyse, this code will use electron-serve to properly serve static files from the
out/
folder, but only when our app is packaged (builded, in production, compiled, whatever you want). While not packaged, we will run our app through the http://localhost:3000
URL, which is the default URL for Next.JS projects. Also, we already prepared the script to load our preload.js
file. Another point of this scripts is the "did-fail-load" event while development. We added it because sometimes Electron may start faster than Next.JS (remember they are called together with concurrently), so, in those moments, it will trigger an error that "URL cannot be loaded" or someting like that, and will give us only a blank screen. With this event implemented on our script, when this occurs, it will automatically reload the app content, until success when showing Next.JS content.根据我们的分析,这段代码将使用electron-serve从
out/
文件夹正确地提供静态文件,但仅在我们的应用程序打包(构建、生产、编译,任何你想要的)时才会这样做。在未打包的情况下,我们将通过 http://localhost:3000
URL运行我们的应用程序,这是Next.JS项目的默认URL。此外,我们已经准备好加载我们的 preload.js
文件的脚本。这些脚本的另一个重点是开发过程中的"did-fail-load"事件。我们添加了它,因为有时Electron可能比Next.JS启动得更快(请记住它们是同时调用的),所以在这些时刻,它会触发一个"URL无法加载"或类似的错误,并且只会给我们一个空白屏幕。通过在我们的脚本中实现这个事件,当出现这种情况时,它将自动重新加载应用程序内容,直到成功显示Next.JS内容。Step 6 — Setting up the preload script
第六步 - 设置预加载脚本
Now, we just need to expose the Electron for the React/Next.JS part of our application. It’s very useful for using IPC messages, for example.
现在,我们只需要将Electron暴露给我们应用程序的React/Next.JS部分。例如,使用IPC消息非常有用。
Inside the
main/preload.js
file we created before, insert the following content:在我们之前创建的
main/preload.js
文件中,插入以下内容:<span id="74b8" data-selectable-paragraph=""><span>const</span> { contextBridge, ipcRenderer } = <span>require</span>(<span>"electron"</span>);<br><br>contextBridge.<span>exposeInMainWorld</span>(<span>"electronAPI"</span>, {<br> <span>on</span>: <span>(<span>channel, callback</span>) =></span> {<br> ipcRenderer.<span>on</span>(channel, callback);<br> },<br> <span>send</span>: <span>(<span>channel, args</span>) =></span> {<br> ipcRenderer.<span>send</span>(channel, args);<br> }<br>});</span>
This way, we can call
window.electronAPI.on
on Next.JS components to handle IPC data that comes from the "back-end" of our application, and window.electronAPI.send
to send data to the "back-end" of our application.这样,我们可以在Next.JS组件上调用
window.electronAPI.on
来处理来自应用程序“后端”的IPC数据,并调用 window.electronAPI.send
将数据发送到应用程序的“后端”。Step 7 — Testing 第七步 - 测试
Finally, we can just run
npm run dev
on our terminal. If all things are well configured, we should see a beautiful Electron window with the starter content of Next.JS. Also, the built-in DevTools shoud appear. And, in the running terminal, we can see the debug from both Next and Electron, tagged with different colors too. Now is time to develop our application. Remember that we have hot reload for front-end (the renderer/Next part of our app), but, when making changes into the "back-end" (the main/Electron part of our application) we need to stop the whole application (a simple Ctrl+C on the terminal do the job) then run it again.最后,我们只需在终端上运行
npm run dev
。如果所有配置都正确,我们应该能看到一个漂亮的Electron窗口,其中包含Next.JS的初始内容。同时,内置的DevTools也应该出现。在运行的终端中,我们还可以看到来自Next和Electron的调试信息,标记有不同的颜色。现在是开发我们的应用程序的时候了。请记住,我们的前端(渲染器/Next部分)支持热重载,但是当我们对“后端”(应用程序的主要/Electron部分)进行更改时,我们需要停止整个应用程序(在终端上简单地按下Ctrl+C即可完成),然后再次运行它。Step 8 — Building the executables
第八步 - 构建可执行文件
Now it’s time to build our application executables. We’re using electron-builder to handle that for us. Start creating a file called
electron-builder.yaml
on the root of our project. Then, inside this file, put the configuration for building the application, according to the official electron-builder documentation. You can find an example below:现在是构建我们的应用程序可执行文件的时候了。我们使用electron-builder来处理这个过程。开始在我们项目的根目录下创建一个名为
electron-builder.yaml
的文件。然后,在这个文件中,根据官方的electron-builder文档,放置构建应用程序的配置。下面是一个示例:<span id="4e02" data-selectable-paragraph=""><span>appId:</span> <span>"io.github.rbfraphael.electron-next"</span><br><span>productName:</span> <span>"Electron Next.JS"</span><br><span>copyright:</span> <span>"Copyright (c) 2023 RBFraphael"</span><br><span>win:</span><br> <span>target:</span> [<span>"dir"</span>, <span>"portable"</span>, <span>"zip"</span>]<br> <span>icon:</span> <span>"resources/icon.ico"</span><br><span>linux:</span><br> <span>target:</span> [<span>"dir"</span>, <span>"appimage"</span>, <span>"zip"</span>]<br> <span>icon:</span> <span>"resources/icon.png"</span><br><span>mac:</span><br> <span>target:</span> [<span>"dir"</span>, <span>"dmg"</span>, <span>"zip"</span>]<br> <span>icon:</span> <span>"resources/icon.icns"</span></span>
After properly specifying the needed options on
electron-builder.yaml
file, you just need to run npm run build
on your terminal. The Next.JS static files will be generated and exported to the out/
directory, then the Electron application will be compiled, and saved to the dist/
directory.在
electron-builder.yaml
文件上正确指定所需选项后,只需在终端上运行 npm run build
。Next.JS静态文件将被生成并导出到 out/
目录,然后Electron应用程序将被编译并保存到 dist/
目录中。Now, when you run your application with the exported files, you should see a Electron window with your Next.JS app, now without the built-in DevTools.
现在,当您使用导出的文件运行应用程序时,您应该看到一个带有您的Next.JS应用程序的Electron窗口,现在没有内置的DevTools。
Conclusion 结论
Using Next.JS to build desktop application with Electron is a good idea, specially when you want to use React alongside with Next.JS features, such as easy routing. And it shouldn’t be a pain, and using Nextron it could be. So, the best approach (I personally believe) is to start from scratch. In this guide we learned an approach to handle that.
使用Next.JS来构建Electron桌面应用是一个好主意,特别是当你想要在Next.JS的特性中使用React,比如简单的路由时。而且这不应该是一件痛苦的事情,使用Nextron可以实现。所以,我个人认为最好的方法是从头开始。在这个指南中,我们学习了一种处理这个问题的方法。
Hope this guide may help somebody :)
希望这个指南能帮助到某人 :)
Thanks! 谢谢!
[
7 min read
Dec 3, 2023 2023年12月3日
A bit of story first
先讲一个小故事
Some time ago I’ve started a personal project called Local.Host, which was a replacement alternative for XAMPP, for PHP developers using Windows. Due to my extensive knowledge of web development, I decided to use Electron for building the desktop app, alongside with some Windows’ CMD commands and tricks.
一段时间前,我开始了一个名为Local.Host的个人项目,它是针对使用Windows的PHP开发人员的XAMPP替代选择。由于我对Web开发有着丰富的知识,我决定使用Electron来构建桌面应用程序,同时结合一些Windows的CMD命令和技巧。
After some search, I’ve found Nextron, a good Next.JS + Electron boilerplate. But, today, I’ve noticed by some friends that my software is a bit heavy, sometimes freezing. Understanding more about Electron, I’ve noticed that Nextron comes with an old version of both Electron and Next.JS, and some “internal” mechanisms used are causing slow downs on my application.
经过一番搜索,我找到了Nextron,一个很好的Next.JS + Electron样板。但是,今天,我注意到一些朋友说我的软件有点重,有时会冻结。对Electron的了解更多后,我注意到Nextron使用了旧版本的Electron和Next.JS,并且一些“内部”机制导致了我的应用程序变慢。
To not fully rewrite the app, I tried to “create” my own Next.JS + Electron project, with latest versions of both and, obviously, more lightweight than Nextron.
为了不完全重写应用程序,我尝试“创建”自己的Next.JS + Electron项目,使用最新版本的两者,并且显然比Nextron更轻量化。
So, now, I have a (I personally consider it) very solid guide for creating desktop apps with these techs. And, of couse, I decided to share it with you, because I’ve found a lot of people that are searching for something like that and are all with the same opinion about Nextron.
所以,现在,我有一个(我个人认为)非常实用的使用这些技术创建桌面应用程序的指南。当然,我决定与你分享,因为我发现很多人都在寻找类似的东西,并且对Nextron持有相同的观点。
The guide 指南
Step 1 — Start a Next.JS project
第一步 - 开始一个Next.JS项目
Start with Next.JS. First, run the traditional command
npx create-next-app@latest <your_project_name>
. Set the preferences you want durint setup. The only setting you need to take care is about using App Router. Currently, I personally choose "No" to keep using the old-but-gold Pages Router, and it's the way I've used.从Next.JS开始。首先,运行传统命令
npx create-next-app@latest <your_project_name>
。在设置过程中设置您想要的首选项。您需要注意的唯一设置是关于使用App Router。目前,我个人选择“否”以继续使用旧但经典的Pages Router,这是我一直使用的方式。<span id="21f2" data-selectable-paragraph="">npx create-next-app@latest electron-nextjs-project</span>
Step 2 — Install Electron and other dependencies
第二步 - 安装Electron和其他依赖项
cd
into your project folder (in this example, electron-nextjs-project
) and install the following dependencies, with both following commands:将
cd
文件夹复制到您的项目文件夹中(在本示例中, electron-nextjs-project
),并使用以下两个命令安装以下依赖项:Dev dependencies: 开发依赖
<span id="781b" data-selectable-paragraph="">npm install --save-dev electron electron-builder concurrently</span>
Project dependencies: 项目依赖
<span id="354d" data-selectable-paragraph="">npm install electron-serve</span>
Step 3 — Setting up package.json
第三步 - 设置package.json
Start opening the
package.json
file with your preferred text editor or IDE. You need to modify the build
and dev
scripts, and add the main
property. The concurrently we installed before will be used to run both Next.JS and Electron in parallel during the development, and you need to point the main
script to the entrypoint of your Electron application. Also, we need to add "author" and "description" attributes, both needed when building the application executables.开始使用您喜欢的文本编辑器或IDE打开
package.json
文件。您需要修改 build
和 dev
脚本,并添加 main
属性。我们之前安装的concurrently将在开发过程中同时运行Next.JS和Electron,并且您需要将 main
脚本指向您的Electron应用程序的入口点。此外,我们还需要添加"author"和"description"属性,在构建应用程序可执行文件时都是必需的。<span id="f59d" data-selectable-paragraph=""><span>{</span><br> ...<br> <span>"main"</span><span>:</span> <span>"main/main.js"</span><span>,</span><br> <span>"author"</span><span>:</span> <span>"RBFraphael"</span><span>,</span><br> <span>"description"</span><span>:</span> <span>"Electron + NextJS example project"</span><span>,</span><br> <span>"scripts"</span><span>:</span> <span>{</span><br> <span>"dev"</span><span>:</span> <span>"concurrently -n \\"NEXT,ELECTRON\\" -c \\"yellow,blue\\" --kill-others \\"next dev\\" \\"electron .\\""</span><span>,</span><br> <span>"build"</span><span>:</span> <span>"next build && electron-builder"</span><span>,</span><br> ...<br> <span>}</span><br> ...<br><span>}</span></span>
As you can see, when developing with
npm run dev
will start both Next.JS and Electron (you can find more details about Concurrently here), and, when building your application with npm run build
it will build the Next.JS files, then the Electron application (find more information about Electron Builder here).如您所见,使用
npm run dev
进行开发时,将同时启动Next.JS和Electron(您可以在此处找到有关Concurrently的更多详细信息),而使用 npm run build
构建应用程序时,将先构建Next.JS文件,然后构建Electron应用程序(在此处找到有关Electron Builder的更多信息)。Step 4 — Configuring Next.JS
第四步 - 配置Next.JS
Since we’re using Next.JS as the starting point, we should have a
next.config.js
file in the root of our project. In this file, we need to set the output mode to export
, and disable the image optimization. So, inside this file, add the following params to the exported nextConfig
object.由于我们使用Next.JS作为起点,所以我们的项目根目录中应该有一个
next.config.js
文件。在这个文件中,我们需要将输出模式设置为 export
,并禁用图像优化。因此,在这个文件中,将以下参数添加到导出的 nextConfig
对象中。<span id="d741" data-selectable-paragraph=""><span>const</span> nextConfig = {<br> ...<br> <span>output</span>: <span>"export"</span>,<br> <span>images</span>: {<br> <span>unoptimized</span>: <span>true</span><br> }<br> ...<br>}</span>
Step 5 — Electron bootstrap
第五步 — Electron引导
Now, we will code the Electron part of our application. Start creating a folder called
main
, and two files inside that folder: main.js
(as we set on the main
key of our package.json
) and preload.js
(for exposing Electron to front-end), and insert the following content in the main.js
file.现在,我们将编写应用程序的Electron部分。开始创建一个名为
main
的文件夹,并在该文件夹中创建两个文件: main.js
(如我们在 package.json
的 main
键中设置的)和 preload.js
(用于将Electron暴露给前端),并将以下内容插入 main.js
文件中。<span id="89cd" data-selectable-paragraph=""><span>const</span> { app, <span>BrowserWindow</span> } = <span>require</span>(<span>"electron"</span>);<br><span>const</span> serve = <span>require</span>(<span>"electron-serve"</span>);<br><span>const</span> path = <span>require</span>(<span>"path"</span>);<br><br><span>const</span> appServe = app.<span>isPackaged</span> ? <span>serve</span>({<br> <span>directory</span>: path.<span>join</span>(__dirname, <span>"../out"</span>)<br>}) : <span>null</span>;<br><br><span>const</span> <span>createWindow</span> = () => {<br> <span>const</span> win = <span>new</span> <span>BrowserWindow</span>({<br> <span>width</span>: <span>800</span>,<br> <span>height</span>: <span>600</span>,<br> <span>webPreferences</span>: {<br> <span>preload</span>: path.<span>join</span>(__dirname, <span>"preload.js"</span>)<br> }<br> });<br><br> <span>if</span> (app.<span>isPackaged</span>) {<br> <span>appServe</span>(win).<span>then</span>(<span>() =></span> {<br> win.<span>loadURL</span>(<span>"app://-"</span>);<br> });<br> } <span>else</span> {<br> win.<span>loadURL</span>(<span>"<http://localhost:3000>"</span>);<br> win.<span>webContents</span>.<span>openDevTools</span>();<br> win.<span>webContents</span>.<span>on</span>(<span>"did-fail-load"</span>, <span>(<span>e, code, desc</span>) =></span> {<br> win.<span>webContents</span>.<span>reloadIgnoringCache</span>();<br> });<br> }<br>}<br><br>app.<span>on</span>(<span>"ready"</span>, <span>() =></span> {<br> <span>createWindow</span>();<br>});<br><br>app.<span>on</span>(<span>"window-all-closed"</span>, <span>() =></span> {<br> <span>if</span>(process.<span>platform</span> !== <span>"darwin"</span>){<br> app.<span>quit</span>();<br> }<br>});</span>
As we can analyse, this code will use electron-serve to properly serve static files from the
out/
folder, but only when our app is packaged (builded, in production, compiled, whatever you want). While not packaged, we will run our app through the http://localhost:3000
URL, which is the default URL for Next.JS projects. Also, we already prepared the script to load our preload.js
file. Another point of this scripts is the "did-fail-load" event while development. We added it because sometimes Electron may start faster than Next.JS (remember they are called together with concurrently), so, in those moments, it will trigger an error that "URL cannot be loaded" or someting like that, and will give us only a blank screen. With this event implemented on our script, when this occurs, it will automatically reload the app content, until success when showing Next.JS content.根据我们的分析,这段代码将使用electron-serve从
out/
文件夹正确地提供静态文件,但仅在我们的应用程序打包(构建、生产、编译,任何你想要的)时才会这样做。在未打包的情况下,我们将通过 http://localhost:3000
URL运行我们的应用程序,这是Next.JS项目的默认URL。此外,我们已经准备好加载我们的 preload.js
文件的脚本。这些脚本的另一个重点是开发过程中的"did-fail-load"事件。我们添加了它,因为有时Electron可能比Next.JS启动得更快(请记住它们是同时调用的),所以在这些时刻,它会触发一个"URL无法加载"或类似的错误,并且只会给我们一个空白屏幕。通过在我们的脚本中实现这个事件,当出现这种情况时,它将自动重新加载应用程序内容,直到成功显示Next.JS内容。Step 6 — Setting up the preload script
第六步 - 设置预加载脚本
Now, we just need to expose the Electron for the React/Next.JS part of our application. It’s very useful for using IPC messages, for example.
现在,我们只需要将Electron暴露给我们应用程序的React/Next.JS部分。例如,使用IPC消息非常有用。
Inside the
main/preload.js
file we created before, insert the following content:在我们之前创建的
main/preload.js
文件中,插入以下内容:<span id="74b8" data-selectable-paragraph=""><span>const</span> { contextBridge, ipcRenderer } = <span>require</span>(<span>"electron"</span>);<br><br>contextBridge.<span>exposeInMainWorld</span>(<span>"electronAPI"</span>, {<br> <span>on</span>: <span>(<span>channel, callback</span>) =></span> {<br> ipcRenderer.<span>on</span>(channel, callback);<br> },<br> <span>send</span>: <span>(<span>channel, args</span>) =></span> {<br> ipcRenderer.<span>send</span>(channel, args);<br> }<br>});</span>
This way, we can call
window.electronAPI.on
on Next.JS components to handle IPC data that comes from the "back-end" of our application, and window.electronAPI.send
to send data to the "back-end" of our application.这样,我们可以在Next.JS组件上调用
window.electronAPI.on
来处理来自应用程序“后端”的IPC数据,并调用 window.electronAPI.send
将数据发送到应用程序的“后端”。Step 7 — Testing 第七步 - 测试
Finally, we can just run
npm run dev
on our terminal. If all things are well configured, we should see a beautiful Electron window with the starter content of Next.JS. Also, the built-in DevTools shoud appear. And, in the running terminal, we can see the debug from both Next and Electron, tagged with different colors too. Now is time to develop our application. Remember that we have hot reload for front-end (the renderer/Next part of our app), but, when making changes into the "back-end" (the main/Electron part of our application) we need to stop the whole application (a simple Ctrl+C on the terminal do the job) then run it again.最后,我们只需在终端上运行
npm run dev
。如果所有配置都正确,我们应该能看到一个漂亮的Electron窗口,其中包含Next.JS的初始内容。同时,内置的DevTools也应该出现。在运行的终端中,我们还可以看到来自Next和Electron的调试信息,标记有不同的颜色。现在是开发我们的应用程序的时候了。请记住,我们的前端(渲染器/Next部分)支持热重载,但是当我们对“后端”(应用程序的主要/Electron部分)进行更改时,我们需要停止整个应用程序(在终端上简单地按下Ctrl+C即可完成),然后再次运行它。Step 8 — Building the executables
第八步 - 构建可执行文件
Now it’s time to build our application executables. We’re using electron-builder to handle that for us. Start creating a file called
electron-builder.yaml
on the root of our project. Then, inside this file, put the configuration for building the application, according to the official electron-builder documentation. You can find an example below:现在是构建我们的应用程序可执行文件的时候了。我们使用electron-builder来处理这个过程。开始在我们项目的根目录下创建一个名为
electron-builder.yaml
的文件。然后,在这个文件中,根据官方的electron-builder文档,放置构建应用程序的配置。下面是一个示例:<span id="4e02" data-selectable-paragraph=""><span>appId:</span> <span>"io.github.rbfraphael.electron-next"</span><br><span>productName:</span> <span>"Electron Next.JS"</span><br><span>copyright:</span> <span>"Copyright (c) 2023 RBFraphael"</span><br><span>win:</span><br> <span>target:</span> [<span>"dir"</span>, <span>"portable"</span>, <span>"zip"</span>]<br> <span>icon:</span> <span>"resources/icon.ico"</span><br><span>linux:</span><br> <span>target:</span> [<span>"dir"</span>, <span>"appimage"</span>, <span>"zip"</span>]<br> <span>icon:</span> <span>"resources/icon.png"</span><br><span>mac:</span><br> <span>target:</span> [<span>"dir"</span>, <span>"dmg"</span>, <span>"zip"</span>]<br> <span>icon:</span> <span>"resources/icon.icns"</span></span>
After properly specifying the needed options on
electron-builder.yaml
file, you just need to run npm run build
on your terminal. The Next.JS static files will be generated and exported to the out/
directory, then the Electron application will be compiled, and saved to the dist/
directory.在
electron-builder.yaml
文件上正确指定所需选项后,只需在终端上运行 npm run build
。Next.JS静态文件将被生成并导出到 out/
目录,然后Electron应用程序将被编译并保存到 dist/
目录中。Now, when you run your application with the exported files, you should see a Electron window with your Next.JS app, now without the built-in DevTools.
现在,当您使用导出的文件运行应用程序时,您应该看到一个带有您的Next.JS应用程序的Electron窗口,现在没有内置的DevTools。
Conclusion 结论
Using Next.JS to build desktop application with Electron is a good idea, specially when you want to use React alongside with Next.JS features, such as easy routing. And it shouldn’t be a pain, and using Nextron it could be. So, the best approach (I personally believe) is to start from scratch. In this guide we learned an approach to handle that.
使用Next.JS来构建Electron桌面应用是一个好主意,特别是当你想要在Next.JS的特性中使用React,比如简单的路由时。而且这不应该是一件痛苦的事情,使用Nextron可以实现。所以,我个人认为最好的方法是从头开始。在这个指南中,我们学习了一种处理这个问题的方法。
Hope this guide may help somebody :)
希望这个指南能帮助到某人 :)
Thanks! 谢谢!
A bit of story first
先讲一个小故事
Some time ago I’ve started a personal project called Local.Host, which was a replacement alternative for XAMPP, for PHP developers using Windows. Due to my extensive knowledge of web development, I decided to use Electron for building the desktop app, alongside with some Windows’ CMD commands and tricks.
一段时间前,我开始了一个名为Local.Host的个人项目,它是针对使用Windows的PHP开发人员的XAMPP替代选择。由于我对Web开发有着丰富的知识,我决定使用Electron来构建桌面应用程序,同时结合一些Windows的CMD命令和技巧。
After some search, I’ve found Nextron, a good Next.JS + Electron boilerplate. But, today, I’ve noticed by some friends that my software is a bit heavy, sometimes freezing. Understanding more about Electron, I’ve noticed that Nextron comes with an old version of both Electron and Next.JS, and some “internal” mechanisms used are causing slow downs on my application.
经过一番搜索,我找到了Nextron,一个很好的Next.JS + Electron样板。但是,今天,我注意到一些朋友说我的软件有点重,有时会冻结。对Electron的了解更多后,我注意到Nextron使用了旧版本的Electron和Next.JS,并且一些“内部”机制导致了我的应用程序变慢。
To not fully rewrite the app, I tried to “create” my own Next.JS + Electron project, with latest versions of both and, obviously, more lightweight than Nextron.
为了不完全重写应用程序,我尝试“创建”自己的Next.JS + Electron项目,使用最新版本的两者,并且显然比Nextron更轻量化。
So, now, I have a (I personally consider it) very solid guide for creating desktop apps with these techs. And, of couse, I decided to share it with you, because I’ve found a lot of people that are searching for something like that and are all with the same opinion about Nextron.
所以,现在,我有一个(我个人认为)非常实用的使用这些技术创建桌面应用程序的指南。当然,我决定与你分享,因为我发现很多人都在寻找类似的东西,并且对Nextron持有相同的观点。
The guide 指南
Step 1 — Start a Next.JS project
第一步 - 开始一个Next.JS项目
Start with Next.JS. First, run the traditional command
npx create-next-app@latest <your_project_name>
. Set the preferences you want durint setup. The only setting you need to take care is about using App Router. Currently, I personally choose "No" to keep using the old-but-gold Pages Router, and it's the way I've used.从Next.JS开始。首先,运行传统命令
npx create-next-app@latest <your_project_name>
。在设置过程中设置您想要的首选项。您需要注意的唯一设置是关于使用App Router。目前,我个人选择“否”以继续使用旧但经典的Pages Router,这是我一直使用的方式。<span id="21f2" data-selectable-paragraph="">npx create-next-app@latest electron-nextjs-project</span>
Step 2 — Install Electron and other dependencies
第二步 - 安装Electron和其他依赖项
cd
into your project folder (in this example, electron-nextjs-project
) and install the following dependencies, with both following commands:将
cd
文件夹复制到您的项目文件夹中(在本示例中, electron-nextjs-project
),并使用以下两个命令安装以下依赖项:Dev dependencies: 开发依赖
<span id="781b" data-selectable-paragraph="">npm install --save-dev electron electron-builder concurrently</span>
Project dependencies: 项目依赖
<span id="354d" data-selectable-paragraph="">npm install electron-serve</span>
Step 3 — Setting up package.json
第三步 - 设置package.json
Start opening the
package.json
file with your preferred text editor or IDE. You need to modify the build
and dev
scripts, and add the main
property. The concurrently we installed before will be used to run both Next.JS and Electron in parallel during the development, and you need to point the main
script to the entrypoint of your Electron application. Also, we need to add "author" and "description" attributes, both needed when building the application executables.开始使用您喜欢的文本编辑器或IDE打开
package.json
文件。您需要修改 build
和 dev
脚本,并添加 main
属性。我们之前安装的concurrently将在开发过程中同时运行Next.JS和Electron,并且您需要将 main
脚本指向您的Electron应用程序的入口点。此外,我们还需要添加"author"和"description"属性,在构建应用程序可执行文件时都是必需的。<span id="f59d" data-selectable-paragraph=""><span>{</span><br> ...<br> <span>"main"</span><span>:</span> <span>"main/main.js"</span><span>,</span><br> <span>"author"</span><span>:</span> <span>"RBFraphael"</span><span>,</span><br> <span>"description"</span><span>:</span> <span>"Electron + NextJS example project"</span><span>,</span><br> <span>"scripts"</span><span>:</span> <span>{</span><br> <span>"dev"</span><span>:</span> <span>"concurrently -n \\"NEXT,ELECTRON\\" -c \\"yellow,blue\\" --kill-others \\"next dev\\" \\"electron .\\""</span><span>,</span><br> <span>"build"</span><span>:</span> <span>"next build && electron-builder"</span><span>,</span><br> ...<br> <span>}</span><br> ...<br><span>}</span></span>
As you can see, when developing with
npm run dev
will start both Next.JS and Electron (you can find more details about Concurrently here), and, when building your application with npm run build
it will build the Next.JS files, then the Electron application (find more information about Electron Builder here).如您所见,使用
npm run dev
进行开发时,将同时启动Next.JS和Electron(您可以在此处找到有关Concurrently的更多详细信息),而使用 npm run build
构建应用程序时,将先构建Next.JS文件,然后构建Electron应用程序(在此处找到有关Electron Builder的更多信息)。Step 4 — Configuring Next.JS
第四步 - 配置Next.JS
Since we’re using Next.JS as the starting point, we should have a
next.config.js
file in the root of our project. In this file, we need to set the output mode to export
, and disable the image optimization. So, inside this file, add the following params to the exported nextConfig
object.由于我们使用Next.JS作为起点,所以我们的项目根目录中应该有一个
next.config.js
文件。在这个文件中,我们需要将输出模式设置为 export
,并禁用图像优化。因此,在这个文件中,将以下参数添加到导出的 nextConfig
对象中。<span id="d741" data-selectable-paragraph=""><span>const</span> nextConfig = {<br> ...<br> <span>output</span>: <span>"export"</span>,<br> <span>images</span>: {<br> <span>unoptimized</span>: <span>true</span><br> }<br> ...<br>}</span>
Step 5 — Electron bootstrap
第五步 — Electron引导
Now, we will code the Electron part of our application. Start creating a folder called
main
, and two files inside that folder: main.js
(as we set on the main
key of our package.json
) and preload.js
(for exposing Electron to front-end), and insert the following content in the main.js
file.现在,我们将编写应用程序的Electron部分。开始创建一个名为
main
的文件夹,并在该文件夹中创建两个文件: main.js
(如我们在 package.json
的 main
键中设置的)和 preload.js
(用于将Electron暴露给前端),并将以下内容插入 main.js
文件中。<span id="89cd" data-selectable-paragraph=""><span>const</span> { app, <span>BrowserWindow</span> } = <span>require</span>(<span>"electron"</span>);<br><span>const</span> serve = <span>require</span>(<span>"electron-serve"</span>);<br><span>const</span> path = <span>require</span>(<span>"path"</span>);<br><br><span>const</span> appServe = app.<span>isPackaged</span> ? <span>serve</span>({<br> <span>directory</span>: path.<span>join</span>(__dirname, <span>"../out"</span>)<br>}) : <span>null</span>;<br><br><span>const</span> <span>createWindow</span> = () => {<br> <span>const</span> win = <span>new</span> <span>BrowserWindow</span>({<br> <span>width</span>: <span>800</span>,<br> <span>height</span>: <span>600</span>,<br> <span>webPreferences</span>: {<br> <span>preload</span>: path.<span>join</span>(__dirname, <span>"preload.js"</span>)<br> }<br> });<br><br> <span>if</span> (app.<span>isPackaged</span>) {<br> <span>appServe</span>(win).<span>then</span>(<span>() =></span> {<br> win.<span>loadURL</span>(<span>"app://-"</span>);<br> });<br> } <span>else</span> {<br> win.<span>loadURL</span>(<span>"<http://localhost:3000>"</span>);<br> win.<span>webContents</span>.<span>openDevTools</span>();<br> win.<span>webContents</span>.<span>on</span>(<span>"did-fail-load"</span>, <span>(<span>e, code, desc</span>) =></span> {<br> win.<span>webContents</span>.<span>reloadIgnoringCache</span>();<br> });<br> }<br>}<br><br>app.<span>on</span>(<span>"ready"</span>, <span>() =></span> {<br> <span>createWindow</span>();<br>});<br><br>app.<span>on</span>(<span>"window-all-closed"</span>, <span>() =></span> {<br> <span>if</span>(process.<span>platform</span> !== <span>"darwin"</span>){<br> app.<span>quit</span>();<br> }<br>});</span>
As we can analyse, this code will use electron-serve to properly serve static files from the
out/
folder, but only when our app is packaged (builded, in production, compiled, whatever you want). While not packaged, we will run our app through the http://localhost:3000
URL, which is the default URL for Next.JS projects. Also, we already prepared the script to load our preload.js
file. Another point of this scripts is the "did-fail-load" event while development. We added it because sometimes Electron may start faster than Next.JS (remember they are called together with concurrently), so, in those moments, it will trigger an error that "URL cannot be loaded" or someting like that, and will give us only a blank screen. With this event implemented on our script, when this occurs, it will automatically reload the app content, until success when showing Next.JS content.根据我们的分析,这段代码将使用electron-serve从
out/
文件夹正确地提供静态文件,但仅在我们的应用程序打包(构建、生产、编译,任何你想要的)时才会这样做。在未打包的情况下,我们将通过 http://localhost:3000
URL运行我们的应用程序,这是Next.JS项目的默认URL。此外,我们已经准备好加载我们的 preload.js
文件的脚本。这些脚本的另一个重点是开发过程中的"did-fail-load"事件。我们添加了它,因为有时Electron可能比Next.JS启动得更快(请记住它们是同时调用的),所以在这些时刻,它会触发一个"URL无法加载"或类似的错误,并且只会给我们一个空白屏幕。通过在我们的脚本中实现这个事件,当出现这种情况时,它将自动重新加载应用程序内容,直到成功显示Next.JS内容。Step 6 — Setting up the preload script
第六步 - 设置预加载脚本
Now, we just need to expose the Electron for the React/Next.JS part of our application. It’s very useful for using IPC messages, for example.
现在,我们只需要将Electron暴露给我们应用程序的React/Next.JS部分。例如,使用IPC消息非常有用。
Inside the
main/preload.js
file we created before, insert the following content:在我们之前创建的
main/preload.js
文件中,插入以下内容:<span id="74b8" data-selectable-paragraph=""><span>const</span> { contextBridge, ipcRenderer } = <span>require</span>(<span>"electron"</span>);<br><br>contextBridge.<span>exposeInMainWorld</span>(<span>"electronAPI"</span>, {<br> <span>on</span>: <span>(<span>channel, callback</span>) =></span> {<br> ipcRenderer.<span>on</span>(channel, callback);<br> },<br> <span>send</span>: <span>(<span>channel, args</span>) =></span> {<br> ipcRenderer.<span>send</span>(channel, args);<br> }<br>});</span>
This way, we can call
window.electronAPI.on
on Next.JS components to handle IPC data that comes from the "back-end" of our application, and window.electronAPI.send
to send data to the "back-end" of our application.这样,我们可以在Next.JS组件上调用
window.electronAPI.on
来处理来自应用程序“后端”的IPC数据,并调用 window.electronAPI.send
将数据发送到应用程序的“后端”。Step 7 — Testing 第七步 - 测试
Finally, we can just run
npm run dev
on our terminal. If all things are well configured, we should see a beautiful Electron window with the starter content of Next.JS. Also, the built-in DevTools shoud appear. And, in the running terminal, we can see the debug from both Next and Electron, tagged with different colors too. Now is time to develop our application. Remember that we have hot reload for front-end (the renderer/Next part of our app), but, when making changes into the "back-end" (the main/Electron part of our application) we need to stop the whole application (a simple Ctrl+C on the terminal do the job) then run it again.最后,我们只需在终端上运行
npm run dev
。如果所有配置都正确,我们应该能看到一个漂亮的Electron窗口,其中包含Next.JS的初始内容。同时,内置的DevTools也应该出现。在运行的终端中,我们还可以看到来自Next和Electron的调试信息,标记有不同的颜色。现在是开发我们的应用程序的时候了。请记住,我们的前端(渲染器/Next部分)支持热重载,但是当我们对“后端”(应用程序的主要/Electron部分)进行更改时,我们需要停止整个应用程序(在终端上简单地按下Ctrl+C即可完成),然后再次运行它。Step 8 — Building the executables
第八步 - 构建可执行文件
Now it’s time to build our application executables. We’re using electron-builder to handle that for us. Start creating a file called
electron-builder.yaml
on the root of our project. Then, inside this file, put the configuration for building the application, according to the official electron-builder documentation. You can find an example below:现在是构建我们的应用程序可执行文件的时候了。我们使用electron-builder来处理这个过程。开始在我们项目的根目录下创建一个名为
electron-builder.yaml
的文件。然后,在这个文件中,根据官方的electron-builder文档,放置构建应用程序的配置。下面是一个示例:<span id="4e02" data-selectable-paragraph=""><span>appId:</span> <span>"io.github.rbfraphael.electron-next"</span><br><span>productName:</span> <span>"Electron Next.JS"</span><br><span>copyright:</span> <span>"Copyright (c) 2023 RBFraphael"</span><br><span>win:</span><br> <span>target:</span> [<span>"dir"</span>, <span>"portable"</span>, <span>"zip"</span>]<br> <span>icon:</span> <span>"resources/icon.ico"</span><br><span>linux:</span><br> <span>target:</span> [<span>"dir"</span>, <span>"appimage"</span>, <span>"zip"</span>]<br> <span>icon:</span> <span>"resources/icon.png"</span><br><span>mac:</span><br> <span>target:</span> [<span>"dir"</span>, <span>"dmg"</span>, <span>"zip"</span>]<br> <span>icon:</span> <span>"resources/icon.icns"</span></span>
After properly specifying the needed options on
electron-builder.yaml
file, you just need to run npm run build
on your terminal. The Next.JS static files will be generated and exported to the out/
directory, then the Electron application will be compiled, and saved to the dist/
directory.在
electron-builder.yaml
文件上正确指定所需选项后,只需在终端上运行 npm run build
。Next.JS静态文件将被生成并导出到 out/
目录,然后Electron应用程序将被编译并保存到 dist/
目录中。Now, when you run your application with the exported files, you should see a Electron window with your Next.JS app, now without the built-in DevTools.
现在,当您使用导出的文件运行应用程序时,您应该看到一个带有您的Next.JS应用程序的Electron窗口,现在没有内置的DevTools。
Conclusion 结论
Using Next.JS to build desktop application with Electron is a good idea, specially when you want to use React alongside with Next.JS features, such as easy routing. And it shouldn’t be a pain, and using Nextron it could be. So, the best approach (I personally believe) is to start from scratch. In this guide we learned an approach to handle that.
使用Next.JS来构建Electron桌面应用是一个好主意,特别是当你想要在Next.JS的特性中使用React,比如简单的路由时。而且这不应该是一件痛苦的事情,使用Nextron可以实现。所以,我个人认为最好的方法是从头开始。在这个指南中,我们学习了一种处理这个问题的方法。
Hope this guide may help somebody :)
希望这个指南能帮助到某人 :)
Thanks! 谢谢!