1"use strict";(self.webpackChunkelectronjs=self.webpackChunkelectronjs||[]).push([["34989"],{89733(e,n,i){i.r(n),i.d(n,{metadata:()=>t,default:()=>m,frontMatter:()=>c,contentTitle:()=>d,toc:()=>p,assets:()=>h});var t=JSON.parse('{"id":"latest/tutorial/tutorial-2-first-app","title":"Building your First App","description":"This guide will step you through the process of creating a barebones Hello World app in Electron.","source":"@site/docs/latest/tutorial/tutorial-2-first-app.md","sourceDirName":"latest/tutorial","slug":"/latest/tutorial/tutorial-first-app","permalink":"/docs/latest/tutorial/tutorial-first-app","draft":false,"unlisted":false,"editUrl":"https://github.com/electron/electron/edit/main/docs/tutorial/tutorial-2-first-app.md","tags":[],"version":"current","frontMatter":{"title":"Building your First App","description":"This guide will step you through the process of creating a barebones Hello World app in Electron.","slug":"tutorial-first-app","hide_title":false},"sidebar":"docs","previous":{"title":"Prerequisites","permalink":"/docs/latest/tutorial/tutorial-prerequisites"},"next":{"title":"Using Preload Scripts","permalink":"/docs/latest/tutorial/tutorial-preload"}}'),r=i(74848),o=i(28453),s=i(50773),a=i(57250),l=i(78476);let c={title:"Building your First App",description:"This guide will step you through the process of creating a barebones Hello World app in Electron.",slug:"tutorial-first-app",hide_title:!1},d,h={},p=[{value:"Learning goals",id:"learning-goals",level:2},{value:"Setting up your project",id:"setting-up-your-project",level:2},{value:"Initializing your npm project",id:"initializing-your-npm-project",level:3},{value:"Adding a .gitignore",id:"adding-a-gitignore",level:3},{value:"Running an Electron app",id:"running-an-electron-app",level:2},{value:"Loading a web page into a BrowserWindow",id:"loading-a-web-page-into-a-browserwindow",level:2},{value:"Importing modules",id:"importing-modules",level:3},{value:"Writing a reusable function to instantiate windows",id:"writing-a-reusable-function-to-instantiate-windows",level:3},{value:"Calling your function when the app is ready",id:"calling-your-function-when-the-app-is-ready",level:3},{value:"Managing your app's window lifecycle",id:"managing-your-apps-window-lifecycle",level:2},{value:"Quit the app when all windows are closed (Windows & Linux)",id:"quit-the-app-when-all-windows-are-closed-windows--linux",level:3},{value:"Open a window if none are open (macOS)",id:"open-a-window-if-none-are-open-macos",level:3},{value:"Final starter code",id:"final-starter-code",level:2},{value:"Optional: Debugging from VS Code",id:"optional-debugging-from-vs-code",level:2},{value:"Summary",id:"summary",level:2}];function u(e){let n={a:"a",admonition:"admonition",code:"code",em:"em",h2:"h2",h3:"h3",li:"li",mdxAdmonitionTitle:"mdxAdmonitionTitle",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.R)(),...e.components},{Details:i}=n;return i||function(e,n){throw Error("Expected "+(n?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("Details",!0),(0,r.jsxs)(r.Fragment,{children:[(0,r.jsxs)(n.admonition,{title:"Follow along the tutorial",type:"info",children:[(0,r.jsxs)(n.p,{children:["This is ",(0,r.jsx)(n.strong,{children:"part 2"})," of the Electron tutorial."]}),(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"/docs/latest/tutorial/tutorial-prerequisites",children:"Prerequisites"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"/docs/latest/tutorial/tutorial-first-app",children:"Building your First App"})})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"/docs/latest/tutorial/tutorial-preload",children:"Using Preload Scripts"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"/docs/latest/tutorial/tutorial-adding-features",children:"Adding Features"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"/docs/latest/tutorial/tutorial-packaging",children:"Packaging Your Application"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"/docs/latest/tutorial/tutorial-publishing-updating",children:"Publishing and Upd
1ating"})}),"\n"]})]}),"\n",(0,r.jsx)(n.h2,{id:"learning-goals",children:"Learning goals"}),"\n",(0,r.jsx)(n.p,{children:"In this part of the tutorial, you will learn how to set up your Electron project\nand write a minimal starter application. By the end of this section,\nyou should be able to run a working Electron app in development mode from\nyour terminal."}),"\n",(0,r.jsx)(n.h2,{id:"setting-up-your-project",children:"Setting up your project"}),"\n",(0,r.jsx)(n.admonition,{title:"Avoid WSL",type:"caution",children:(0,r.jsxs)(n.p,{children:["If you are on a Windows machine, please do not use ",(0,r.jsx)(n.a,{href:"https://learn.microsoft.com/en-us/windows/wsl/about#what-is-wsl-2",children:"Windows Subsystem for Linux"})," (WSL)\nwhen following this tutorial as you will run into issues when trying to execute the\napplication."]})}),"\n",(0,r.jsx)(n.h3,{id:"initializing-your-npm-project",children:"Initializing your npm project"}),"\n",(0,r.jsxs)(n.p,{children:["Electron apps are scaffolded using npm, with the package.json file\nas an entry point. Start by creating a folder and initializing an npm package\nwithin it with ",(0,r.jsx)(n.code,{children:"npm init"}),"."]}),"\n",(0,r.jsxs)(s.A,{groupId:"npm2yarn",children:[(0,r.jsx)(a.A,{value:"npm",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-sh",children:"mkdir my-electron-app && cd my-electron-app\nnpm init\n"})})}),(0,r.jsx)(a.A,{value:"yarn",label:"Yarn",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-sh",children:"mkdir my-electron-app && cd my-electron-app\nyarn init\n"})})})]}),"\n",(0,r.jsx)(n.p,{children:"This command will prompt you to configure some fields in your package.json.\nThere are a few rules to follow for the purposes of this tutorial:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.em,{children:"entry point"})," should be ",(0,r.jsx)(n.code,{children:"main.js"})," (you will be creating that file soon)."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.em,{children:"author"}),", ",(0,r.jsx)(n.em,{children:"license"}),", and ",(0,r.jsx)(n.em,{children:"description"})," can be any value, but will be necessary for\n",(0,r.jsx)(n.a,{href:"/docs/latest/tutorial/tutorial-packaging",children:"packaging"})," later on."]}),"\n"]}),"\n",(0,r.jsxs)(n.admonition,{type:"caution",children:[(0,r.jsxs)(n.mdxAdmonitionTitle,{children:["Install dependencies with a regular ",(0,r.jsx)(n.code,{children:"node_modules"})," folder"]}),(0,r.jsxs)(n.p,{children:["Electron's packaging toolchain requires the ",(0,r.jsx)(n.code,{children:"node_modules"})," folder to be physically on disk in the\nway that npm installs Node dependencies. By default, ",(0,r.jsx)(n.a,{href:"https://yarnpkg.com/",children:"Yarn Berry"})," and\n",(0,r.jsx)(n.a,{href:"http://pnpm.io/",children:"pnpm"})," both use alternative installation strategies."]}),(0,r.jsxs)(n.p,{children:["Therefore, you must set ",(0,r.jsx)(n.a,{href:"https://yarnpkg.com/configuration/yarnrc#nodeLinker",children:(0,r.jsx)(n.code,{children:"nodeLinker: node-modules"})}),"\nin Yarn or ",(0,r.jsx)(n.a,{href:"https://pnpm.io/settings#nodelinker",children:(0,r.jsx)(n.code,{children:"nodeLinker: hoisted"})})," in pnpm if you are using\nthose package managers."]})]}),"\n",(0,r.jsxs)(n.p,{children:["Then, install Electron into your app's ",(0,r.jsx)(n.strong,{children:"devDependencies"}),", which is the list of external\ndevelopment-only package dependencies not required in production."]}),"\n",(0,r.jsx)(n.admonition,{title:"Why is Electron a dev dependency?",type:"info",children:(0,r.jsx)(n.p,{children:"This may seem counter-intuitive since your production code is running Electron APIs. Under the hood,\nElectron's JavaScript API binds to a binary that contains its implementations. The packaging step for\nElectron handles the bundling of this binary, eliminating the need to specify it as a production\ndependency."})}),"\n",(0,r.jsxs)(s.A,{groupId:"npm2yarn",children:[(0,r.jsx)(a.A,{value:"npm",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-sh",children:"npm install electron --save-dev\n"})})}),(0,r.jsx)(a.A,{value:"yarn",label:"Yarn",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-sh",children:"yarn add electron --dev\n"})})})]}),"\n",(0,r.jsxs)(n.p,{children:["Your package.json file should look something like this after initializing your package\nand installing Electron. You should also now have a ",(0,r.jsx)(n.code,{children:"node_modules"})," folder containing\nthe Electron executable, as well as a ",(0,r.jsx)(n.code,{children:"package-lock.json"})," lockfile that specifies\nthe exact dependency versions to install."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-json",metastring:"title='package.json'",children:'{\n "name": "my-electron-app",\n "version": "1.0.0",\n "description": "Hello World!",\n "main": "main.js",\n "scripts": {\n "test": "echo \\"Error: no test specified\\" && exit 1"\n },\n "author": "Jane Doe",\n "license": "MIT",\n "devDependencies": {\n "electron": "23.1.3"\n }\n}\n'})}),"\n",(0,r.jsx)(n.admonition,{title:"Advanced Electron installation steps",type:"info",children:(0,r.jsxs)(n.p,{children:["If installing Electron directly fails, please refer to our ",(0,r.jsx)(n.a,{href:"/docs/latest/tutorial/installation",children:"Advanced Installation"}),"\ndocumentation for instructions on download mirrors, proxies, and troubleshooting steps."]})}),"\n",(0,r.jsx)(n.h3,{id:"adding-a-gitignore",children:"Adding a .gitignore"}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.a,{href:"https://git-scm.com/docs/gitignore",children:(0,r.jsx)(n.code,{children:".gitignore"})})," file specifies which files and directories to avoid tracking\nwith Git. You should place a copy of ",(0,r.jsx)(n.a,{href:"https://github.com/github/gitignore/blob/main/Node.gitignore",children:"GitHub's Node.js gitignore template"}),"\ninto your project's root folder to avoid committing your project's ",(0,r.jsx)(n.code,{children:"node_modules"})," folder."]}
1),"\n",(0,r.jsx)(n.h2,{id:"running-an-electron-app",children:"Running an Electron app"}),"\n",(0,r.jsx)(n.admonition,{title:"Further reading",type:"tip",children:(0,r.jsxs)(n.p,{children:["Read ",(0,r.jsx)(n.a,{href:"/docs/latest/tutorial/process-model",children:"Electron's process model"})," documentation to better\nunderstand how Electron's multiple processes work together."]})}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.a,{href:"https://docs.npmjs.com/cli/v7/configuring-npm/package-json#main",children:(0,r.jsx)(n.code,{children:"main"})})," script you defined in package.json is the entry point of any\nElectron application. This script controls the ",(0,r.jsx)(n.strong,{children:"main process"}),", which runs in a Node.js\nenvironment and is responsible for controlling your app's lifecycle, displaying native\ninterfaces, performing privileged operations, and managing renderer processes\n(more on that later)."]}),"\n",(0,r.jsxs)(n.p,{children:["Before creating your first Electron app, you will first use a trivial script to ensure your\nmain process entry point is configured correctly. Create a ",(0,r.jsx)(n.code,{children:"main.js"})," file in the root folder\nof your project with a single line of code:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-js",metastring:"title='main.js'",children:"console.log('Hello from Electron \u{1F44B}')\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Because Electron's main process is a Node.js runtime, you can execute arbitrary Node.js code\nwith the ",(0,r.jsx)(n.code,{children:"electron"})," command (you can even use it as a ",(0,r.jsx)(n.a,{href:"/docs/latest/tutorial/repl",children:"REPL"}),"). To execute this script,\nadd ",(0,r.jsx)(n.code,{children:"electron ."})," to the ",(0,r.jsx)(n.code,{children:"start"})," command in the ",(0,r.jsx)(n.a,{href:"https://docs.npmjs.com/cli/v7/using-npm/scripts",children:(0,r.jsx)(n.code,{children:"scripts"})}),"\nfield of your package.json. This command will tell the Electron executable to look for the main\nscript in the current directory and run it in dev mode."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-json",metastring:"{7} title='package.json'",children:'{\n "name": "my-electron-app",\n "version": "1.0.0",\n "description": "Hello World!",\n "main": "main.js",\n "scripts": {\n "start": "electron .",\n "test": "echo \\"Error: no test specified\\" && exit 1"\n },\n "author": "Jane Doe",\n "license": "MIT",\n "devDependencies": {\n "electron": "23.1.3"\n }\n}\n'})}),"\n",(0,r.jsxs)(s.A,{groupId:"npm2yarn",children:[(0,r.jsx)(a.A,{value:"npm",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-sh",children:"npm run start\n"})})}),(0,r.jsx)(a.A,{value:"yarn",label:"Yarn",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-sh",children:"yarn run start\n"})})})]}),"\n",(0,r.jsxs)(n.p,{children:["Your terminal should print out ",(0,r.jsx)(n.code,{children:"Hello from Electron \u{1F44B}"}),". Congratulations,\nyou have executed your first line of code in Electron! Next, you will learn\nhow to create user interfaces with HTML and load that into a native window."]}),"\n",(0,r.jsx)(n.h2,{id:"loading-a-web-page-into-a-browserwindow",children:"Loading a web page into a BrowserWindow"}),"\n",(0,r.jsxs)(n.p,{children:["In Electron, each window displays a web page that can be loaded either from a local HTML\nfile or a remote web address. For this example, you will be loading in a local file. Start\nby creating a barebones web page in an ",(0,r.jsx)(n.code,{children:"index.html"})," file in the root folder of your project:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-html",metastring:"title='index.html'",children:'<!DOCTYPE html>\n<html>\n <head>\n <meta charset="UTF-8" />\n \x3c!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --\x3e\n <meta\n http-equiv="Content-Security-Policy"\n content="default-src \'self\'; script-src \'self\'"\n />\n <meta\n http-equiv="X-Content-Security-Policy"\n content="default-src \'self\'; script-src \'self\'"\n />\n <title>Hello from Electron renderer!</title>\n </head>\n <body>\n <h1>Hello from Electron renderer!</h1>\n <p>\u{1F44B}</p>\n </body>\n</html>\n'})}),"\n",(0,r.jsxs)(n.p,{children:["Now that you have a web page, you can load it into an Electron ",(0,r.jsx)(n.a,{href:"/docs/latest/api/browser-window",children:"BrowserWindow"}),".\nReplace the contents of your ",(0,r.jsx)(n.code,{children:"main.js"})," file with the following code. We will explain each\nhighlighted block separately."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-js",metastring:"{1,3-10,12-14} title='main.js' showLineNumbers",children:"const { app, BrowserWindow } = require('electron')\n\nconst createWindow = () => {\n const win = new BrowserWindow({\n width: 800,\n height: 600\n })\n\n win.loadFile('index.html')\n}\n\napp.whenReady().then(() =>
1 {\n createWindow()\n})\n"})}),"\n",(0,r.jsx)(n.h3,{id:"importing-modules",children:"Importing modules"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-js",metastring:"title='main.js (Line 1)'",children:"const { app, BrowserWindow } = require('electron')\n"})}),"\n",(0,r.jsx)(n.p,{children:"In the first line, we are importing two Electron modules\nwith CommonJS module syntax:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"/docs/latest/api/app",children:"app"}),", which controls your application's event lifecycle."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"/docs/latest/api/browser-window",children:"BrowserWindow"}),", which creates and manages app windows."]}),"\n"]}),"\n",(0,r.jsxs)(i,{children:[(0,r.jsx)("summary",{children:"Module capitalization conventions"}),(0,r.jsxs)(n.p,{children:["You might have noticed the capitalization difference between the ",(0,r.jsx)(n.strong,{children:"a"}),"pp\nand ",(0,r.jsx)(n.strong,{children:"B"}),"rowser",(0,r.jsx)(n.strong,{children:"W"}),"indow modules. Electron follows typical JavaScript conventions here,\nwhere PascalCase modules are instantiable class constructors (e.g. BrowserWindow, Tray,\nNotification) whereas camelCase modules are not instantiable (e.g. app, ipcRenderer, webContents)."]})]}),"\n",(0,r.jsxs)(i,{children:[(0,r.jsx)("summary",{children:"Typed import aliases"}),(0,r.jsxs)(n.p,{children:["For better type checking when writing TypeScript code, you can choose to import\nmain process modules from ",(0,r.jsx)(n.code,{children:"electron/main"}),"."]}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-js",children:"const { app, BrowserWindow } = require('electron/main')\n"})}),(0,r.jsxs)(n.p,{children:["For more information, see the ",(0,r.jsx)(n.a,{href:"/docs/latest/tutorial/process-model#process-specific-module-aliases-typescript",children:"Process Model docs"}),"."]})]}),"\n",(0,r.jsx)(n.admonition,{title:"ES Modules in Electron",type:"info",children:(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://nodejs.org/api/esm.html",children:"ECMAScript modules"})," (i.e. using ",(0,r.jsx)(n.code,{children:"import"})," to load a module)\nare supported in Electron as of Electron 28. You can find more information about the\nstate of ESM in Electron and how to use them in our app in ",(0,r.jsx)(n.a,{href:"/docs/latest/tutorial/esm",children:"our ESM guide"}),"."]})}),"\n",(0,r.jsx)(n.h3,{id:"writing-a-reusable-function-to-instantiate-windows",children:"Writing a reusable function to instantiate windows"}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"createWindow()"})," function loads your web page into a new BrowserWindow instance:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-js",metastring:"title='main.js (Lines 3-10)'",children:"const createWindow = () => {\n const win = new BrowserWindow({\n width: 800,\n height: 600\n })\n\n win.loadFile('index.html')\n}\n"})}),"\n",(0,r.jsx)(n.h3,{id:"calling-your-function-when-the-app-is-ready",children:"Calling your function when the app is ready"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-js",metastring:"title='main.js (Lines 12-14)' @ts-type={createWindow:()=>void}",children:"app.whenReady().then(() => {\n createWindow()\n})\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Many of Electron's core modules are Node.js ",(0,r.jsx)(n.a,{href:"https://nodejs.org/api/events.html#events",children:"event emitters"})," that adhere to Node's asynchronous\nevent-driven architecture. The app module is one of these emitters."]}),"\n",(0,r.jsxs)(n.p,{children:["In Electron, BrowserWindows can only be created after the app module's ",(0,r.jsx)(n.a,{href:"/docs/latest/api/app#event-ready",children:(0,r.jsx)(n.code,{children:"ready"})})," event\nis fired. You can wait for this event by using the ",(0,r.jsx)(n.a,{href:"/docs/latest/api/app#appwhenready",children:(0,r.jsx)(n.code,{children:"app.whenReady()"})})," API and\ncalling ",(0,r.jsx)(n.code,{children:"createWindow()"})," once its promise is fulfilled."]}),"\n",(0,r.jsxs)(n.admonition,{type:"info",children:[(0,r.jsxs)(n.p,{children:["You typically listen to Node.js events by using an emitter's ",(0,r.jsx)(n.code,{children:".on"})," function."]}),(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-diff",children:"+ app.on('ready', () => {\n- app.whenReady().then(() =>
1 {\n createWindow()\n})\n"})}),(0,r.jsxs)(n.p,{children:["However, Electron exposes ",(0,r.jsx)(n.code,{children:"app.whenReady()"})," as a helper specifically for the ",(0,r.jsx)(n.code,{children:"ready"})," event to\navoid subtle pitfalls with directly listening to that event in particular.\nSee ",(0,r.jsx)(n.a,{href:"https://github.com/electron/electron/pull/21972",children:"electron/electron#21972"})," for details."]})]}),"\n",(0,r.jsxs)(n.p,{children:["At this point, running your Electron application's ",(0,r.jsx)(n.code,{children:"start"})," command should successfully\nopen a window that displays your web page!"]}),"\n",(0,r.jsxs)(n.p,{children:["Each web page your app displays in a window will run in a separate process called a\n",(0,r.jsx)(n.strong,{children:"renderer"})," process (or simply ",(0,r.jsx)(n.em,{children:"renderer"})," for short). Renderer processes have access\nto the same JavaScript APIs and tooling you use for typical front-end web\ndevelopment, such as using ",(0,r.jsx)(n.a,{href:"https://webpack.js.org",children:"webpack"})," to bundle and minify your code or ",(0,r.jsx)(n.a,{href:"https://reactjs.org",children:"React"}),"\nto build your user interfaces."]}),"\n",(0,r.jsx)(n.h2,{id:"managing-your-apps-window-lifecycle",children:"Managing your app's window lifecycle"}),"\n",(0,r.jsx)(n.p,{children:"Application windows behave differently on each operating system. Rather than\nenforce these conventions by default, Electron gives you the choice to implement\nthem in your app code if you wish to follow them. You can implement basic window\nconventions by listening for events emitted by the app and BrowserWindow modules."}),"\n",(0,r.jsx)(n.admonition,{title:"Process-specific control flow",type:"tip",children:(0,r.jsxs)(n.p,{children:["Checking against Node's ",(0,r.jsx)(n.a,{href:"https://nodejs.org/api/process.html#process_process_platform",children:(0,r.jsx)(n.code,{children:"process.platform"})})," variable can help you\nto run code conditionally on certain platforms. Note that there are only three\npossible platforms that Electron can run in: ",(0,r.jsx)(n.code,{children:"win32"})," (Windows), ",(0,r.jsx)(n.code,{children:"linux"})," (Linux),\nand ",(0,r.jsx)(n.code,{children:"darwin"})," (macOS)."]})}),"\n",(0,r.jsx)(n.h3,{id:"quit-the-app-when-all-windows-are-closed-windows--linux",children:"Quit the app when all windows are closed (Windows & Linux)"}),"\n",(0,r.jsxs)(n.p,{children:["On Windows and Linux, closing all windows will generally quit an application entirely.\nTo implement this pattern in your Electron app, listen for the app module's\n",(0,r.jsx)(n.a,{href:"/docs/latest/api/app#event-window-all-closed",children:(0,r.jsx)(n.code,{children:"window-all-closed"})})," event, and call ",(0,r.jsx)(n.a,{href:"/docs/latest/api/app#appquit",children:(0,r.jsx)(n.code,{children:"app.quit()"})}),"\nto exit your app if the user is not on macOS."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-js",children:"app.on('window-all-closed', () => {\n if (process.platform !== 'darwin') app.quit()\n})\n"})}),"\n",(0,r.jsx)(n.h3,{id:"open-a-window-if-none-are-open-macos",children:"Open a window if none are open (macOS)"}),"\n",(0,r.jsx)(n.p,{children:"In contrast, macOS apps generally continue running even without any windows open.\nActivating the app when no windows are available should open a new one."}),"\n",(0,r.jsxs)(n.p,{children:["To implement this feature, listen for the app module's ",(0,r.jsx)(n.a,{href:"/docs/latest/api/app#event-activate-macos",children:(0,r.jsx)(n.code,{children:"activate"})}),"\nevent, and call your existing ",(0,r.jsx)(n.code,{children:"createWindow()"})," method if no BrowserWindows are open."]}),"\n",(0,r.jsxs)(n.p,{children:["Because windows cannot be created before the ",(0,r.jsx)(n.code,{children:"ready"})," event, you should only listen for\n",(0,r.jsx)(n.code,{children:"activate"})," events after your app is initialized. Do this by only listening for activate\nevents inside your existing ",(0,r.jsx)(n.code,{children:"whenReady()"})," callback."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-js",metastring:"@ts-type={createWindow:()=>void}",children:"app.whenReady().then(() =>
1 {\n createWindow()\n\n app.on('activate', () => {\n if (BrowserWindow.getAllWindows().length === 0) createWindow()\n })\n})\n"})}),"\n",(0,r.jsx)(n.h2,{id:"final-starter-code",children:"Final starter code"}),"\n",(0,r.jsx)(l.A,{files:{"index.html":'<!DOCTYPE html>\n<html>\n <head>\n <meta charset="UTF-8" />\n <meta\n http-equiv="Content-Security-Policy"\n content="default-src \'self\'; script-src \'self\'"\n />\n <meta\n http-equiv="X-Content-Security-Policy"\n content="default-src \'self\'; script-src \'self\'"\n />\n <title>Hello from Electron renderer!</title>\n </head>\n <body>\n <h1>Hello from Electron renderer!</h1>\n <p>\u{1F44B}</p>\n <p id="info"></p>\n </body>\n <script src="./renderer.js"><\/script>\n</html>\n',"main.js":"const { app, BrowserWindow } = require('electron/main')\n\nconst createWindow = () => {\n const win = new BrowserWindow({\n width: 800,\n height: 600\n })\n\n win.loadFile('index.html')\n}\n\napp.whenReady().then(() => {\n createWindow()\n\n app.on('activate', () => {\n if (BrowserWindow.getAllWindows().length === 0) {\n createWindow()\n }\n })\n})\n\napp.on('window-all-closed', () => {\n if (process.platform !== 'darwin') {\n app.quit()\n }\n})\n"},dir:"docs/latest/fiddles/tutorial-first-app",version:"44.4.5",focus:"main.js"}),"\n",(0,r.jsx)(n.h2,{id:"optional-debugging-from-vs-code",children:"Optional: Debugging from VS Code"}),"\n",(0,r.jsxs)(n.p,{children:["If you want to debug your application using VS Code, you need to attach VS Code to\nboth the main and renderer processes. Here is a sample configuration for you to\nrun. Create a launch.json configuration in a new ",(0,r.jsx)(n.code,{children:".vscode"})," folder in your project:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-json",metastring:"title='.vscode/launch.json'",children:'{\n "version": "0.2.0",\n "compounds": [\n {\n "name": "Main + renderer",\n "configurations": ["Main", "Renderer"],\n "stopAll": true\n }\n ],\n "configurations": [\n {\n "name": "Renderer",\n "port": 9222,\n "request": "attach",\n "type": "chrome",\n "webRoot": "${workspaceFolder}"\n },\n {\n "name": "Main",\n "type": "node",\n "request": "launch",\n "cwd": "${workspaceFolder}",\n "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",\n "windows": {\n "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"\n },\n "args": [".", "--remote-debugging-port=9222"],\n "outputCapture": "std",\n "console": "integratedTerminal"\n }\n ]\n}\n'})}),"\n",(0,r.jsx)(n.p,{children:'The "Main + renderer" option will appear when you select "Run and Debug"\nfrom the sidebar, allowing you to set breakpoints and inspect all the variables among\nother things in both the main and renderer processes.'}),"\n",(0,r.jsxs)(n.p,{children:["What we have done in the ",(0,r.jsx)(n.code,{children:"launch.json"})," file is to create 3 configurations:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"Main"})," is used to start the main process and also expose port 9222 for remote debugging\n(",(0,r.jsx)(n.code,{children:"--remote-debugging-port=9222"}),"). This is the port that we will use to attach the debugger\nfor the ",(0,r.jsx)(n.code,{children:"Renderer"}),". Because the main process is a Node.js process, the type is set to\n",(0,r.jsx)(n.code,{children:"node"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"Renderer"}),' is used to debug the renderer process. Because the main process is the one\nthat creates the process, we have to "attach" to it (',(0,r.jsx)(n.code,{children:'"request": "attach"'}),") instead of\ncreating a new one.\nThe renderer process is a web one, so the debugger we have to use is ",(0,r.jsx)(n.code,{children:"chrome"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"Main + renderer"})," is a ",(0,r.jsx)(n.a,{href:"https://code.visualstudio.com/Docs/editor/tasks#_compound-tasks",children:"compound task"})," that executes the previous ones simultaneously."]}),"\n"]}),"\n",(0,r.jsx)(n.admonition,{type:"caution",children:(0,r.jsxs)(n.p,{children:["Because we are attaching to a process in ",(0,r.jsx)(n.code,{children:"Renderer"}),", it is possible that the first lines of\nyour code will be skipped as the debugger will not have had enough time to connect before they are\nbeing executed.\nYou can work around this by refreshing the page or setting a timeout before executing the code\nin development mode."]})}),"\n",(0,r.jsxs)(n.admonition,{title:"Further reading",type:"info",children:[(0,r.jsx)(n.p,{children:"If you want to dig deeper in the debugging area, the following guides provide more information:"}),(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"/docs/latest/tutorial/application-debugging",children:"Application Debugging"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"/docs/latest/tutorial/devtools-extension",children:"DevTools Extensions"})}),"\n"]})]}),"\n",(0,r.jsx)(n.h2,{id:"summary",children:"Summary"}),"\n",(0,r.jsxs)(n.p,{children:["Electron applications are set up using npm packages. The Electron executable should be installed\nin your project's ",(0,r.jsx)(n.code,{children:"devDependencies"})," and can be run in development mode using a script in your\npackage.json file."]}),"\n",(0,r.jsxs)(n.p,{children:["The executable runs the JavaScript entry point found in the ",(0,r.jsx)(n.code,{children:"main"})," property of your package.json.\nThis file controls Electron's ",(0,r.jsx)(n.strong,{children:"main process"}),", which runs an instance of Node.js and is\nresponsible for your app's lifecycle, displaying native interfaces, performing privileged operations,\nand managing renderer processes."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Renderer processes"})," (or renderers for short) are responsible for displaying graphical content. You can\nload a web page into a renderer by pointing it to either a web address or a local HTML file.\nRenderers behave very similarly to regular web pages and have access to the same web APIs."]}),"\n",(0,r.jsx)(n.p,{children:"In the next section of the tutorial, we will be learning how to augment the renderer process with\nprivileged APIs and how to communicate between processes."})]})}function m(e={}){let{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(u,{...e})}):u(e)}},57250(e,n,i){i.d(n,{A:()=>a});var t=i(74848);i(96540);var r=i(18215),o=i(57663);function s({children:e,className:n,hidden:i}){return(0,t.jsx)("div",{role:"tabpanel",className:(0,r.A)("tabItem_Ymn6",n),hidden:i,children:e})}function a({children:e,className:n,value:i}){let{selectedValue:r,lazy:l}=(0,o.uc)(),c=i===r;return!c&&l?null:(0,t.jsx)(s,{className:n,hidden:!c,children:e})}},50773(e,n,i){i.d(n,{A:()=>p});var t=i(74848);i(96540);var r=i(18215),o=i(88287),s=i(57663),a=i(28584),l=i(19863);function c({className:e}){let{selectedValue:n,selectValue:i,tabValues:o,block:l}=(0,s.uc)(),d=[],{blockElementScrollPositionUntilNextRender:h}=(0,a.a_)(),p=e=>{let t=e.currentTarget,r=o[d.indexOf(t)].value;r!==n&&(h(t),i(r))},u=e=>{let n=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{let i=d.indexOf(e.currentTarget)+1;n=d[i]??d[0];break}case"ArrowLeft":{let i=d.indexOf(e.currentTarget)-1;n=d[i]??d[d.length-1]}}n?.focus()};return(0,t.jsx)("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":l}
1,e),children:o.map(({value:e,label:i,attributes:o})=>(0,t.jsx)("li",{role:"tab",tabIndex:n===e?0:-1,"aria-selected":n===e,ref:e=>{d.push(e)},onKeyDown:u,onClick:p,...o,className:(0,r.A)("tabs__item","tabItem_LNqP",o?.className,{"tabs__item--active":n===e}),children:i??e},e))})}function d({children:e}){return(0,t.jsx)("div",{className:"margin-top--md",children:e})}function h({className:e,children:n}){return(0,t.jsxs)("div",{className:(0,r.A)(o.G.tabs.container,"tabs-container","tabList__CuJ"),children:[(0,t.jsx)(c,{className:e}),(0,t.jsx)(d,{children:n})]})}function p(e){let n=(0,l.A)(),i=(0,s.OC)(e);return(0,t.jsx)(s.O_,{value:i,children:(0,t.jsx)(h,{className:e.className,children:(0,s.vT)(e.children)})},String(n))}},57663(e,n,i){i.d(n,{OC:()=>p,O_:()=>g,uc:()=>m,vT:()=>d});var t=i(74848),r=i(96540),o=i(56347),s=i(99989),a=i(96629),l=i(80618),c=i(41367);function d(e){return r.Children.toArray(e).filter(e=>"\n"!==e)}function h({value:e,tabValues:n}){return n.some(n=>n.value===e)}function p(e){let n,{defaultValue:i,queryString:t=!1,groupId:d}=e,p=function(e){let{values:n,children:i}=e;return(0,r.useMemo)(()=>{let e=n??r.Children.toArray(i).flatMap(e=>{if(!e)return[];if((0,r.isValidElement)(e)&&function(e){let{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return[e];let n="string"==typeof e.type?e.type:e.type.name;throw Error(`Docusaurus error: Bad <Tabs> child <${n}>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop. 2If you do not want to pass on a "value" prop to the direct children of <Tabs>, you can also pass an explicit <Tabs values={...}> prop.`)}).map(({props:{value:e,label:n,attributes:i,default:t}})=>({value:e,label:n,attributes:i,default:t})),t=(0,l.XI)(e,(e,n)=>e.value===n.value);if(t.length>0)throw Error(`Docusaurus error: Duplicate values "${t.map(e=>`'${e.value}'`).join(", ")}" found in <Tabs>. Every value needs to be unique.`);return e},[n,i])}(e),[u,m]=(0,r.useState)(()=>(function({defaultValue:e,tabValues:n}){if(0===n.length)throw Error("Docusaurus error: the <Tabs> component requires at least one <TabItem> children component");if(e){if(!h({value:e,tabValues:n}))throw Error(`Docusaurus error: The <Tabs> has a defaultValue "${e}" but none of its children has the corresponding value. Available values are: ${n.map(e=>e.value).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return e}let i=n.find(e=>e.default)??n[0];if(!i)throw Error("Unexpected error: 0 tabValues");return i.value})({defaultValue:i,tabValues:p})),[g,f]=function({queryString:e=!1,groupId:n}){let i=(0,o.W6)(),t=function({queryString:e=!1,groupId:n}){if("string"==typeof e)return e;if(!1===e)return null;if(!0===e&&!n)throw Error('Docusaurus error: The <Tabs> component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:e,groupId:n});return[(0,a.aZ)(t),(0,r.useCallback)(e=>{if(!t)return;let n=new URLSearchParams(i.location.search);n.set(t,e),i.replace({...i.location,search:n.toString()})},[t,i])]}({queryString:t,groupId:d}),[j,x]=function({groupId:e}){let n=e?`docusaurus.tab.${e}`:null,[i,t]=(0,c.Dv)(n);return[i,(0,r.useCallback)(e=>{n&&t.set(e)},[n,t])]}({groupId:d}),w=h({value:n=g??j,tabValues:p})?n:null;return(0,s.A)(()=>{w&&m(w)},[w]),{selectedValue:u,selectValue:(0,r.useCallback)(e=>{if(!h({value:e,tabValues:p}))throw Error(`Can't select invalid tab value=${e}`);m(e),f(e),x(e)},[f,x,p]),tabValues:p,lazy:e.lazy??!1,block:e.block??!1}}let u=(0,r.createContext)(null);function m(){let e=r.useContext(u);if(!e)throw Error("useTabsContext() must be used within a Tabs component");return e}function g(e){return(0,t.jsx)(u.Provider,{value:e.value,children:e.children})}},78476(e,n,i){i.d(n,{A:()=>d});var t=i(74848);i(96540);var r=i(50773),o=i(57250),s=i(1113);let a="dot_YdbR",l={"main.js":4,"preload.js":3,"index.html":2,"renderer.js":1};function c({children:e,version:n,gitDir:i}){return(0,t.jsxs)("div",{className:"editorWindow_ZQWz",children:[(0,t.jsxs)("div",{className:"editorWindowHeader_GqG7",children:[(0,t.jsxs)("div",{className:"buttons_msmS",children:[(0,t.jsx)("span",{className:a,style:{background:"#f25f58"}}),(0,t.jsx)("span",{className:a,style:{background:"#fbbe3c"}}),(0,t.jsx)("span",{className:a,style:{background:"#58cb42"}})]}),(0,t.jsxs)("a",{className:"editorTitle_buME",href:`https://github.com/electron/electron/tree/v${n}/${i}`,children:[i," (",n,")"]}),(0,t.jsx)("a",{target:"_blank",className:"button button--primary button--md",href:`https://fiddle.electronjs.org/launch?target=electron/v${n}/${i}`,rel:"noreferrer",children:"Open in Fiddle"})]}),(0,t.jsx)("div",{className:"editorWindowBody_R11m",children:e})]})}let d=e=>{let{files:n,focus:i,version:a,dir:d}=e,h=d.replace(/docs\/[a-zA-Z0-9-]+\/fiddles/,"docs/fiddles"),p=Object.keys(n).sort((e,n)=>l[n]-l[e]),u=p.map(e=>({label:e,value:e}));return(0,t.jsx)(c,{version:a,gitDir:h,children:(0,t.jsx)(r.A,{values:u,defaultValue:i||"main.js",children:p.map(e=>(0,t.jsx)(o.A,{value:e,children:(0,t.jsx)(s.A,{className:`language-${e.split(".").pop()}`,children:n[e]})},e))})})}},28453(e,n,i){i.d(n,{R:()=>s,x:()=>a});var t=i(96540);let r={},o=t.createContext(r);function s(e){let n=t.useContext(o);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:s(e.components),t.createElement(o.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.