Frontend Development 15 min read

How Does Electron‑Builder Turn Your Web App into a Standalone Executable?

This article explains step‑by‑step how to package an Electron application using electron‑builder and electron‑packager, analyzes the resulting file sizes and project structure, and dives into the core source code of electron‑builder to reveal how the packaging process creates the final executable and asar archives.

Tencent IMWeb Frontend Team
Tencent IMWeb Frontend Team
Tencent IMWeb Frontend Team
How Does Electron‑Builder Turn Your Web App into a Standalone Executable?

How to Package an Electron Application

Electron provides a browser‑like environment with extra permissions for running web pages as desktop apps. This article explains how to package Electron apps and analyses the inner workings of

electron‑builder

and

electron‑packager

.

Packaging Tools

There are two main packaging tools for Electron:

electron‑builder
electron‑packager

Installing Dependencies for electron‑builder

<code>yarn add electron-builder --dev
# or
npm i electron-builder --save-dev</code>

Packaging with electron‑builder

Add a

build

field to

package.json

with the required metadata (name, description, version, author, etc.) and a

scripts

entry:

<code>"scripts": {
  "pack": "electron-builder --dir",
  "dist": "electron-builder"
}</code>

Run the commands:

<code>npm run pack   # creates a directory without an installer
npm run dist   # creates an installer (e.g., .exe or .dmg)</code>

Specify target platform and architecture:

<code># Windows 64‑bit
electron-builder --win --x64
# Windows and macOS 32‑bit
electron-builder --win --mac --ia32</code>

Installing Dependencies for electron‑packager

<code>npm i electron-packager --save-dev</code>

Packaging with electron‑packager

<code>electron-packager <sourcedir> <appname> --platform=<platform> --arch=<arch> [options]</code>

The simplest way is to run

electron-packager .

which uses the

productName

or

name

field from

package.json

as the app name and defaults to the host platform and architecture.

File Size Analysis

An empty Electron project packaged with

electron‑builder --dir

is about 121 MB because it includes the full V8 engine and Chromium. When the installer is built, the size drops to around 36 MB, which is acceptable.

A real project with 30+ dependencies can reach 230 MB before packaging and about 56 MB after creating the installer; the increase is mainly due to the

node_modules

that are bundled.

Project Structure After Packaging

When using

electron‑builder --dir

, the output directory contains:

<code>.
├─ locales
├─ resources
│  ├─ app.asar   # contains the whole project (2 KB for an empty project, ~130 MB for a real one)
│  └─ electron.asar   # Electron runtime (≈250 KB) and <code>electron.exe</code> (≈67.5 MB)
├─ electron.exe
└─ ...</code>

The

app.asar

archive holds the project's source files and only the production dependencies listed in

package.json

are included.

Analyzing electron‑builder

The packaging process can be broken down into several steps performed by the

Packager

class:

Normalize options from

package.json

.

Create a

Packager

instance.

Install native dependencies for the target platform.

Call

packager.pack()

for each platform/arch/target.

The main entry point is

packages/electron‑builder/src/cli/cli.ts

which exports the

build

function. It creates a

Packager

and invokes its

build

method.

<code>export async function build(rawOptions?: CliOptions): Promise&lt;Array&lt;string&gt;&gt; {
  const buildOptions = normalizeOptions(rawOptions || {});
  const packager = new Packager(buildOptions);
  // ...set up electronDownloader if needed
  return _build(buildOptions, packager);
}</code>

The

Packager.build()

method eventually calls the private

_build

method, which iterates over the configured targets and architectures, installs app dependencies, creates output directories, and invokes

packager.pack()

for each target.

<code>private async doBuild(outDir: string): Promise&lt;Map&lt;Platform, Map&lt;string, Target&gt;&gt;&gt; {
  for (const [platform, archToType] of this.options.targets) {
    const packager = this.createHelper(platform);
    for (const [arch, targetNames] of computeArchToTargetNamesMap(archToType, packager)) {
      await this.installAppDependencies(platform, arch);
      const targetList = createTargets(nameToTarget, targetNames.length === 0 ? packager.defaultTarget : targetNames, outDir);
      await createOutDirIfNeed(targetList, createdOutDirs);
      await packager.pack(outDir, arch, targetList, taskManager);
    }
  }
  return platformToTarget;
}</code>

Platform‑specific packagers (e.g.,

WinPackager

) inherit from

PlatformPackager

. The

pack

method computes the application output directory, copies app files, creates

app.asar

, signs the app (if required), and runs any user‑defined hooks (

beforeCopyExtraFiles

,

afterPack

,

afterSign

).

<code>async pack(outDir: string, arch: Arch, targets: Array&lt;Target&gt;, taskManager: AsyncTaskManager) {
  const appOutDir = this.computeAppOutDir(outDir, arch);
  await this.doPack(outDir, appOutDir, this.platform.nodeName, arch, this.platformSpecificBuildOptions, targets);
  await this.packageInDistributableFormat(appOutDir, arch, targets, taskManager);
}</code>

During

doPack

, the builder creates file matchers to include or exclude files, computes

asar

options, copies resources, runs user hooks, performs a sanity check, signs the app, and finally runs

afterSign

.

WinPackager Details

The Windows packager creates the

electron.exe

executable, modifies its icon, author, and version information, and bundles the

resources/app.asar

archive. The executable loads the

main

field from

package.json

inside the

app.asar

file.

Summary

Electron‑builder packages an app by:

Re‑installing native dependencies for the target platform.

Copying project files (filtered by matchers) into a temporary directory.

Creating

app.asar

(which contains the whole project) and

electron.asar

(the Electron runtime).

Generating the final executable (

electron.exe

on Windows) and optionally signing it.

The size of the final installer is dominated by the bundled

node_modules

and the Electron runtime. An empty project yields a ~36 MB installer, while a real project can reach ~56 MB.

Potential issues include the fact that source files are packaged without additional minification or obfuscation, so the final binary contains the original JavaScript code.

References

Click the link in the original article to read the full source and additional documentation.

Electronnode.jspackagingelectron-builderDesktop Appsasar
Tencent IMWeb Frontend Team
Written by

Tencent IMWeb Frontend Team

IMWeb Frontend Community gathering frontend development enthusiasts. Follow us for refined live courses by top experts, cutting‑edge technical posts, and to sharpen your frontend skills.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.