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

yarn add electron-builder --dev
# or
npm i electron-builder --save-dev

Packaging with electron‑builder

Add a build field to package.json with the required metadata (name, description, version, author, etc.) and a scripts entry:

"scripts": {
  "pack": "electron-builder --dir",
  "dist": "electron-builder"
}

Run the commands:

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

Specify target platform and architecture:

# Windows 64‑bit
electron-builder --win --x64
# Windows and macOS 32‑bit
electron-builder --win --mac --ia32

Installing Dependencies for electron‑packager

npm i electron-packager --save-dev

Packaging with electron‑packager

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

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:

.
├─ 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
└─ ...

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.

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

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.

private async doBuild(outDir: string): Promise<Map<Platform, Map<string, Target>>> {
  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;
}

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).

async pack(outDir: string, arch: Arch, targets: Array<Target>, 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);
}

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

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

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.