|
@@ -0,0 +1,480 @@
|
|
|
+---
|
|
|
+title: 'Building your First App'
|
|
|
+description: 'This guide will step you through the process of creating a barebones Hello World app in Electron, similar to electron/electron-quick-start.'
|
|
|
+slug: tutorial-first-app
|
|
|
+hide_title: false
|
|
|
+---
|
|
|
+
|
|
|
+:::info Follow along the tutorial
|
|
|
+
|
|
|
+This is **part 2** of the Electron tutorial.
|
|
|
+
|
|
|
+1. [Prerequisites][prerequisites]
|
|
|
+1. **[Building your First App][building your first app]**
|
|
|
+1. [Using Preload Scripts][preload]
|
|
|
+1. [Adding Features][features]
|
|
|
+1. [Packaging Your Application][packaging]
|
|
|
+1. [Publishing and Updating][updates]
|
|
|
+
|
|
|
+:::
|
|
|
+
|
|
|
+## Learning goals
|
|
|
+
|
|
|
+In this part of the tutorial, you will learn how to set up your Electron project
|
|
|
+and write a minimal starter application. By the end of this section,
|
|
|
+you should be able to run a working Electron app in development mode from
|
|
|
+your terminal.
|
|
|
+
|
|
|
+## Setting up your project
|
|
|
+
|
|
|
+:::caution Avoid WSL
|
|
|
+
|
|
|
+If you are on a Windows machine, please do not use [Windows Subsystem for Linux][wsl] (WSL)
|
|
|
+when following this tutorial as you will run into issues when trying to execute the
|
|
|
+application.
|
|
|
+
|
|
|
+<!--https://www.electronforge.io/guides/developing-with-wsl-->
|
|
|
+
|
|
|
+:::
|
|
|
+
|
|
|
+### Initializing your npm project
|
|
|
+
|
|
|
+Electron apps are scaffolded using npm, with the package.json file
|
|
|
+as an entry point. Start by creating a folder and initializing an npm package
|
|
|
+within it with `npm init`.
|
|
|
+
|
|
|
+```sh npm2yarn
|
|
|
+mkdir my-electron-app && cd my-electron-app
|
|
|
+npm init
|
|
|
+```
|
|
|
+
|
|
|
+This command will prompt you to configure some fields in your package.json.
|
|
|
+There are a few rules to follow for the purposes of this tutorial:
|
|
|
+
|
|
|
+- _entry point_ should be `main.js` (you will be creating that file soon).
|
|
|
+- _author_, _license_, and _description_ can be any value, but will be necessary for
|
|
|
+ [packaging][packaging] later on.
|
|
|
+
|
|
|
+Then, install Electron into your app's **devDependencies**, which is the list of external
|
|
|
+development-only package dependencies not required in production.
|
|
|
+
|
|
|
+:::info Why is Electron a devDependency?
|
|
|
+
|
|
|
+This may seem counter-intuitive since your production code is running Electron APIs.
|
|
|
+However, packaged apps will come bundled with the Electron binary, eliminating the need to specify
|
|
|
+it as a production dependency.
|
|
|
+
|
|
|
+:::
|
|
|
+
|
|
|
+```sh npm2yarn
|
|
|
+npm install electron --save-dev
|
|
|
+```
|
|
|
+
|
|
|
+Your package.json file should look something like this after initializing your package
|
|
|
+and installing Electron. You should also now have a `node_modules` folder containing
|
|
|
+the Electron executable, as well as a `package-lock.json` lockfile that specifies
|
|
|
+the exact dependency versions to install.
|
|
|
+
|
|
|
+```json title='package.json'
|
|
|
+{
|
|
|
+ "name": "my-electron-app",
|
|
|
+ "version": "1.0.0",
|
|
|
+ "description": "Hello World!",
|
|
|
+ "main": "main.js",
|
|
|
+ "author": "Jane Doe",
|
|
|
+ "license": "MIT",
|
|
|
+ "devDependencies": {
|
|
|
+ "electron": "19.0.0"
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+:::info Advanced Electron installation steps
|
|
|
+
|
|
|
+If installing Electron directly fails, please refer to our [Advanced Installation][installation]
|
|
|
+documentation for instructions on download mirrors, proxies, and troubleshooting steps.
|
|
|
+
|
|
|
+:::
|
|
|
+
|
|
|
+### Adding a .gitignore
|
|
|
+
|
|
|
+The [`.gitignore`][gitignore] file specifies which files and directories to avoid tracking
|
|
|
+with Git. You should place a copy of [GitHub's Node.js gitignore template][gitignore-template]
|
|
|
+into your project's root folder to avoid committing your project's `node_modules` folder.
|
|
|
+
|
|
|
+## Running an Electron app
|
|
|
+
|
|
|
+:::tip Further reading
|
|
|
+
|
|
|
+Read [Electron's process model][process-model] documentation to better
|
|
|
+understand how Electron's multiple processes work together.
|
|
|
+
|
|
|
+:::
|
|
|
+
|
|
|
+The [`main`][package-json-main] script you defined in package.json is the entry point of any
|
|
|
+Electron application. This script controls the **main process**, which runs in a Node.js
|
|
|
+environment and is responsible for controlling your app's lifecycle, displaying native
|
|
|
+interfaces, performing privileged operations, and managing renderer processes
|
|
|
+(more on that later).
|
|
|
+
|
|
|
+Before creating your first Electron app, you will first use a trivial script to ensure your
|
|
|
+main process entry point is configured correctly. Create a `main.js` file in the root folder
|
|
|
+of your project with a single line of code:
|
|
|
+
|
|
|
+```js title='main.js'
|
|
|
+console.log(`Hello from Electron 👋`)
|
|
|
+```
|
|
|
+
|
|
|
+Because Electron's main process is a Node.js runtime, you can execute arbitrary Node.js code
|
|
|
+with the `electron` command (you can even use it as a [REPL]). To execute this script,
|
|
|
+add `electron .` to the `start` command in the [`scripts`][package-scripts]
|
|
|
+field of your package.json. This command will tell the Electron executable to look for the main
|
|
|
+script in the current directory and run it in dev mode.
|
|
|
+
|
|
|
+```json {8-10} title='package.json'
|
|
|
+{
|
|
|
+ "name": "my-electron-app",
|
|
|
+ "version": "1.0.0",
|
|
|
+ "description": "Hello World!",
|
|
|
+ "main": "main.js",
|
|
|
+ "author": "Jane Doe",
|
|
|
+ "license": "MIT",
|
|
|
+ "scripts": {
|
|
|
+ "start": "electron ."
|
|
|
+ },
|
|
|
+ "devDependencies": {
|
|
|
+ "electron": "^19.0.0"
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+```sh npm2yarn
|
|
|
+npm run start
|
|
|
+```
|
|
|
+
|
|
|
+Your terminal should print out `Hello from Electron 👋`. Congratulations,
|
|
|
+you have executed your first line of code in Electron! Next, you will learn
|
|
|
+how to create user interfaces with HTML and load that into a native window.
|
|
|
+
|
|
|
+## Loading a web page into a BrowserWindow
|
|
|
+
|
|
|
+In Electron, each window displays a web page that can be loaded either from a local HTML
|
|
|
+file or a remote web address. For this example, you will be loading in a local file. Start
|
|
|
+by creating a barebones web page in an `index.html` file in the root folder of your project:
|
|
|
+
|
|
|
+```html title='index.html'
|
|
|
+<!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][browser-window].
|
|
|
+Replace the contents your `main.js` file with the following code. We will explain each
|
|
|
+highlighted block separately.
|
|
|
+
|
|
|
+```js {1,3-10,12-14} title='main.js' showLineNumbers
|
|
|
+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
|
|
|
+
|
|
|
+```js title='main.js (Line 1)'
|
|
|
+const { app, BrowserWindow } = require('electron')
|
|
|
+```
|
|
|
+
|
|
|
+In the first line, we are importing two Electron modules
|
|
|
+with CommonJS module syntax:
|
|
|
+
|
|
|
+- [app][app], which controls your application's event lifecycle.
|
|
|
+- [BrowserWindow][browser-window], which creates and manages app windows.
|
|
|
+
|
|
|
+:::info Capitalization conventions
|
|
|
+
|
|
|
+You might have noticed the capitalization difference between the **a**pp
|
|
|
+and **B**rowser**W**indow 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).
|
|
|
+
|
|
|
+:::
|
|
|
+
|
|
|
+:::warning ES Modules in Electron
|
|
|
+
|
|
|
+[ECMAScript modules](https://nodejs.org/api/esm.html) (i.e. using `import` to load a module)
|
|
|
+are currently not directly supported in Electron. You can find more information about the
|
|
|
+state of ESM in Electron in [electron/electron#21457](https://github.com/electron/electron/issues/21457).
|
|
|
+
|
|
|
+:::
|
|
|
+
|
|
|
+### Writing a reusable function to instantiate windows
|
|
|
+
|
|
|
+The `createWindow()` function loads your web page into a new BrowserWindow instance:
|
|
|
+
|
|
|
+```js title='main.js (Lines 3-10)'
|
|
|
+const createWindow = () => {
|
|
|
+ const win = new BrowserWindow({
|
|
|
+ width: 800,
|
|
|
+ height: 600,
|
|
|
+ })
|
|
|
+
|
|
|
+ win.loadFile('index.html')
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### Calling your function when the app is ready
|
|
|
+
|
|
|
+```js title='main.js (Lines 12-14)'
|
|
|
+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`][app-ready] event
|
|
|
+is fired. You can wait for this event by using the [`app.whenReady()`][app-when-ready] API and
|
|
|
+calling `createWindow()` once its promise is fulfilled.
|
|
|
+
|
|
|
+:::info
|
|
|
+
|
|
|
+You typically listen to Node.js events by using an emitter's `.on` function.
|
|
|
+
|
|
|
+```diff
|
|
|
++ app.on('ready').then(() => {
|
|
|
+- 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](https://github.com/electron/electron/pull/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][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.
|
|
|
+
|
|
|
+:::tip Process-specific control flow
|
|
|
+
|
|
|
+Checking against Node's [`process.platform`][node-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).
|
|
|
+
|
|
|
+:::
|
|
|
+
|
|
|
+### Quit the app when all windows are closed (Windows & 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`][window-all-closed] event, and call [`app.quit()`][app-quit]
|
|
|
+to exit your app if the user is not on macOS.
|
|
|
+
|
|
|
+```js
|
|
|
+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`][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.
|
|
|
+
|
|
|
+```js
|
|
|
+app.whenReady().then(() => {
|
|
|
+ createWindow()
|
|
|
+
|
|
|
+ app.on('activate', () => {
|
|
|
+ if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
|
|
+ })
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+## Final starter code
|
|
|
+
|
|
|
+```fiddle docs/fiddles/tutorial-first-app
|
|
|
+
|
|
|
+```
|
|
|
+
|
|
|
+## Optional: Debugging from VS Code
|
|
|
+
|
|
|
+If you want to debug your application using VS Code, you have need 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:
|
|
|
+
|
|
|
+```json title='.vscode/launch.json'
|
|
|
+{
|
|
|
+ "version": "0.2.0",
|
|
|
+ "compounds": [
|
|
|
+ {
|
|
|
+ "name": "Main + renderer",
|
|
|
+ "configurations": ["Main", "Renderer"],
|
|
|
+ "stopAll": true
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ "configurations": [
|
|
|
+ {
|
|
|
+ "name": "Renderer",
|
|
|
+ "port": 9222,
|
|
|
+ "request": "attach",
|
|
|
+ "type": "pwa-chrome",
|
|
|
+ "webRoot": "${workspaceFolder}"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "name": "Main",
|
|
|
+ "type": "pwa-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 the `Renderer`. Because the main process is a Node.js process, the type is set to
|
|
|
+ `pwa-node` (`pwa-` is the prefix that tells VS Code to use the latest JavaScript debugger).
|
|
|
+- `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 is `pwa-chrome`.
|
|
|
+- `Main + renderer` is a [compound task] that executes the previous ones simultaneously.
|
|
|
+
|
|
|
+:::caution
|
|
|
+
|
|
|
+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.
|
|
|
+
|
|
|
+:::
|
|
|
+
|
|
|
+:::info Further reading
|
|
|
+
|
|
|
+If you want to dig deeper in the debugging area, the following guides provide more information:
|
|
|
+
|
|
|
+- [Application Debugging]
|
|
|
+- [DevTools Extensions][devtools extension]
|
|
|
+
|
|
|
+:::
|
|
|
+
|
|
|
+## Summary
|
|
|
+
|
|
|
+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 display 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.
|
|
|
+
|
|
|
+<!-- Links -->
|
|
|
+
|
|
|
+[activate]: ../api/app.md#event-activate-macos
|
|
|
+[advanced-installation]: installation.md
|
|
|
+[app]: ../api/app.md
|
|
|
+[app-quit]: ../api/app.md#appquit
|
|
|
+[app-ready]: ../api/app.md#event-ready
|
|
|
+[app-when-ready]: ../api/app.md#appwhenready
|
|
|
+[application debugging]: ./application-debugging.md
|
|
|
+[browser-window]: ../api/browser-window.md
|
|
|
+[commonjs]: https://nodejs.org/docs/../api/modules.html#modules_modules_commonjs_modules
|
|
|
+[compound task]: https://code.visualstudio.com/Docs/editor/tasks#_compound-tasks
|
|
|
+[devtools extension]: ./devtools-extension.md
|
|
|
+[event emitters]: https://nodejs.org/api/events.html#events
|
|
|
+[gitignore]: https://git-scm.com/docs/gitignore
|
|
|
+[gitignore-template]: https://github.com/github/gitignore/blob/main/Node.gitignore
|
|
|
+[installation]: ./installation.md
|
|
|
+[node-platform]: https://nodejs.org/api/process.html#process_process_platform
|
|
|
+[package-json-main]: https://docs.npmjs.com/cli/v7/configuring-npm/package-json#main
|
|
|
+[package-scripts]: https://docs.npmjs.com/cli/v7/using-npm/scripts
|
|
|
+[process-model]: process-model.md
|
|
|
+[react]: https://reactjs.org
|
|
|
+[repl]: ./repl.md
|
|
|
+[sandbox]: ./sandbox.md
|
|
|
+[webpack]: https://webpack.js.org
|
|
|
+[window-all-closed]: ../api/app.md#event-window-all-closed
|
|
|
+[wsl]: https://docs.microsoft.com/en-us/windows/wsl/about#what-is-wsl-2
|
|
|
+
|
|
|
+<!-- Tutorial links -->
|
|
|
+
|
|
|
+[prerequisites]: tutorial-1-prerequisites.md
|
|
|
+[building your first app]: tutorial-2-first-app.md
|
|
|
+[preload]: tutorial-3-preload.md
|
|
|
+[features]: tutorial-4-adding-features.md
|
|
|
+[packaging]: tutorial-5-packaging.md
|
|
|
+[updates]: tutorial-6-publishing-updating.md
|