Criando seu primeiro aplicativo
Esta é a parte 2 do tutorial de Electron.
Learning goals
Nesta parte do tutorial, você irá aprender como configurar seu projeto Electron e escrever uma aplicação minimamente funcional. Ao final desta seção, você conseguirá executar um aplicativo Electron funcional em modo de desenvolvimento do seu terminal.
Configurando seu projeto
Se você estiver em uma máquina Windows, por favor não use Subsistema Windows para Linux (WSL) ao seguir este tutorial, pois você terá problemas ao tentar executar o aplicativo.
Inicializando seu projeto npm
Aplicativos Electron são aninhados utilizando npm, com o arquivo package.json como ponto de entrada. Inicie criando um diretório, e inicializando um pacote npm dentro dele com npm init
.
- npm
- Yarn
mkdir my-electron-app && cd my-electron-app
npm init
mkdir my-electron-app && cd my-electron-app
yarn init
Este comando irá lhe pedir para configurar alguns campos no seu package.json. Existem algumas regras a seguir para os propósitos deste tutorial:
- entry point será
main.js
(você estará criando este arquivo em breve). - author, license, e description podem ser de qualquer valor, mas são necessários para o empacotamento mais tarde.
Em seguida, instale o Electron dentro das devDependencies do seu aplicativo, que é a lista de dependências externas que não são necessárias em produção, apenas em desenvolvimento.
Isto pode parecer contraintuitivo já que seu código de produção está executando Electron APIs. Entretanto, aplicativos empacotados virão agrupados com os binários do Electron, eliminando a necessidade de especificá-lo como uma dependência de produção.
- npm
- Yarn
npm install electron --save-dev
yarn add electron --dev
Seu arquivo package.json deve se parecer com algo assim após inicializar seu pacote e instalar o Electron. Você também deve ter um diretório chamado node_modules
contendo o executável do Electron, assim como um arquivo de bloqueio package-lock.json
que especifica as versões dependências a serem instaladas.
{
"name": "my-electron-app",
"version": "1.0.0",
"description": "Hello World!",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Jane Doe",
"license": "MIT",
"devDependencies": {
"electron": "23.1.3"
}
}
Se a instalação direta do Electron falhar, por favor consulte nossa documentação de Instalação Avançada para informações de download mirrors, proxies e passos para solução de problemas.
Adicionando um arquivo .gitignore
O arquivo .gitignore
especifica quais arquivos e diretórios não devem ser rastreados pelo Git. Você deve colocar uma cópia do GitHub's Node.js gitignore template no diretório raiz do seu projeto para evitar realizar commit do diretório node_modules
.
Rodando um aplicativo Electron
Leia a documentação Modelo de processo Electron para entender melhor como múltiplos processos Electron funcionam juntos.
O script main
que você definiu no package.json é o ponto de entrada de qualquer aplicação Electron. Este script controla o processo principal, que é executado em um ambiente Node.js e é responsável por controlar o ciclo de vida do seu aplicativo, exibindo interfaces nativas, realizando todas as operações necessárias e gerenciando os processos de renderização (mais sobre isso mais tarde).
Antes de criar seu aplicativo Electron, você primeiro irá usar um script trivial para garantir que seu processo principal de ponto de entrada está configurado corretamente. Crie um arquivo main.js
no diretório raiz do seu projeto com uma única linha de código:
console.log('Hello from Electron 👋')
Como o processo principal Electron é executado em Node.js, você pode usar código Node.js arbitrário com o comando electron
(você pode até usá-lo como um REPL). Para executar este script, adicione electron .
ao comando start
no campo scripts
do seu package.json. Este comando dirá ao executável Electron para procurar o script principal no diretório atual e executá-lo no modo de desenvolvimento.
{
"name": "my-electron-app",
"version": "1.0.0",
"description": "Hello World!",
"main": "main.js",
"scripts": {
"start": "electron .",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Jane Doe",
"license": "MIT",
"devDependencies": {
"electron": "23.1.3"
}
}
- npm
- Yarn
npm run start
yarn run start
Seu terminal deve imprimir Hello from Electron 👋
. Parabéns, você executou sua primeira linha de código no Electron! Em seguida, você aprenderá como criar interfaces de usuário com HTML e carregá-las em uma janela nativa.
Carregando uma página web com BrowserWindow
No Electron, cada janela exibe uma página web que pode ser carregada de um arquivo HTML local ou de um endereço web remoto. Neste exemplo, você estará carregando em um arquivo local. Start by creating a barebones web page in an index.html
file in the root folder of your project:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<meta
http-equiv="X-Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<title>Hello from Electron renderer!</title>
</head>
<body>
<h1>Hello from Electron renderer!</h1>
<p>👋</p>
</body>
</html>
Now that you have a web page, you can load it into an Electron BrowserWindow. Replace the contents of your main.js
file with the following code. We will explain each highlighted block separately.
const { app, BrowserWindow } = require('electron')
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600
})
win.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
})
Importing modules
const { app, BrowserWindow } = require('electron')
In the first line, we are importing two Electron modules with CommonJS module syntax:
- app, which controls your application's event lifecycle.
- BrowserWindow, which creates and manages app windows.
Module capitalization conventions
You might have noticed the capitalization difference between the app and BrowserWindow modules. Electron follows typical JavaScript conventions here, where PascalCase modules are instantiable class constructors (e.g. BrowserWindow, Tray, Notification) whereas camelCase modules are not instantiable (e.g. app, ipcRenderer, webContents).
Typed import aliases
For better type checking when writing TypeScript code, you can choose to import main process modules from electron/main
.
const { app, BrowserWindow } = require('electron/main')
For more information, see the Process Model docs.
ECMAScript modules (i.e. using import
to load a module) are supported in Electron as of Electron 28. You can find more information about the state of ESM in Electron and how to use them in our app in our ESM guide.
Writing a reusable function to instantiate windows
The createWindow()
function loads your web page into a new BrowserWindow instance:
Calling your function when the app is ready
app.whenReady().then(() => {
createWindow()
})
Many of Electron's core modules are Node.js event emitters that adhere to Node's asynchronous event-driven architecture. The app module is one of these emitters.
In Electron, BrowserWindows can only be created after the app module's ready
event is fired. You can wait for this event by using the app.whenReady()
API and calling createWindow()
once its promise is fulfilled.
You typically listen to Node.js events by using an emitter's .on
function.
+ app.on('ready', () => {
- app.whenReady().then(() => {
createWindow()
})
However, Electron exposes app.whenReady()
as a helper specifically for the ready
event to avoid subtle pitfalls with directly listening to that event in particular. See electron/electron#21972 for details.
At this point, running your Electron application's start
command should successfully open a window that displays your web page!
Each web page your app displays in a window will run in a separate process called a renderer process (or simply renderer for short). Renderer processes have access to the same JavaScript APIs and tooling you use for typical front-end web development, such as using webpack to bundle and minify your code or React to build your user interfaces.
Managing your app's window lifecycle
Application windows behave differently on each operating system. Rather than enforce these conventions by default, Electron gives you the choice to implement them in your app code if you wish to follow them. You can implement basic window conventions by listening for events emitted by the app and BrowserWindow modules.
Checking against Node's process.platform
variable can help you to run code conditionally on certain platforms. Note that there are only three possible platforms that Electron can run in: win32
(Windows), linux
(Linux), and darwin
(macOS).
Encerrar a aplicação quando todas as janelas estiverem fechadas (Windows e Linux)
On Windows and Linux, closing all windows will generally quit an application entirely. To implement this pattern in your Electron app, listen for the app module's window-all-closed
event, and call app.quit()
to exit your app if the user is not on macOS.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
Open a window if none are open (macOS)
In contrast, macOS apps generally continue running even without any windows open. Activating the app when no windows are available should open a new one.
To implement this feature, listen for the app module's activate
event, and call your existing createWindow()
method if no BrowserWindows are open.
Because windows cannot be created before the ready
event, you should only listen for activate
events after your app is initialized. Do this by only listening for activate events inside your existing whenReady()
callback.
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
Final starter code
- main.js
- index.html
const { app, BrowserWindow } = require('electron/main')
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600
})
win.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<meta
http-equiv="X-Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<title>Hello from Electron renderer!</title>
</head>
<body>
<h1>Hello from Electron renderer!</h1>
<p>👋</p>
<p id="info"></p>
</body>
<script src="./renderer.js"></script>
</html>
Optional: Debugging from VS Code
If you want to debug your application using VS Code, you need to attach VS Code to both the main and renderer processes. Here is a sample configuration for you to run. Create a launch.json configuration in a new .vscode
folder in your project:
{
"version": "0.2.0",
"compounds": [
{
"name": "Main + renderer",
"configurations": ["Main", "Renderer"],
"stopAll": true
}
],
"configurations": [
{
"name": "Renderer",
"port": 9222,
"request": "attach",
"type": "chrome",
"webRoot": "${workspaceFolder}"
},
{
"name": "Main",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
},
"args": [".", "--remote-debugging-port=9222"],
"outputCapture": "std",
"console": "integratedTerminal"
}
]
}
The "Main + renderer" option will appear when you select "Run and Debug" from the sidebar, allowing you to set breakpoints and inspect all the variables among other things in both the main and renderer processes.
What we have done in the launch.json
file is to create 3 configurations:
Main
is used to start the main process and also expose port 9222 for remote debugging (--remote-debugging-port=9222
). This is the port that we will use to attach the debugger for theRenderer
. Because the main process is a Node.js process, the type is set tonode
.Renderer
is used to debug the renderer process. Because the main process is the one that creates the process, we have to "attach" to it ("request": "attach"
) instead of creating a new one. The renderer process is a web one, so the debugger we have to use ischrome
.Main + renderer
is a compound task that executes the previous ones simultaneously.
Because we are attaching to a process in Renderer
, it is possible that the first lines of your code will be skipped as the debugger will not have had enough time to connect before they are being executed. You can work around this by refreshing the page or setting a timeout before executing the code in development mode.
If you want to dig deeper in the debugging area, the following guides provide more information:
Sumário
Electron applications are set up using npm packages. The Electron executable should be installed in your project's devDependencies
and can be run in development mode using a script in your package.json file.
The executable runs the JavaScript entry point found in the main
property of your package.json. This file controls Electron's main process, which runs an instance of Node.js and is responsible for your app's lifecycle, displaying native interfaces, performing privileged operations, and managing renderer processes.
Renderer processes (or renderers for short) are responsible for displaying graphical content. You can load a web page into a renderer by pointing it to either a web address or a local HTML file. Renderers behave very similarly to regular web pages and have access to the same web APIs.
In the next section of the tutorial, we will be learning how to augment the renderer process with privileged APIs and how to communicate between processes.