首页 > 系统相关 >Electron-builder 是如何打包 Windows 应用的?

Electron-builder 是如何打包 Windows 应用的?

时间:2024-12-17 11:57:08浏览次数:10  
标签:... 文件 Windows builder await Electron 安装程序 打包

本文首发同名微信公众号:前端徐徐

大家好,我是徐徐。今天我们聊聊 electron-builder 中 windows 是如何打包的。

前言

electron-builder 中 windows 的打包其实也是很复杂的,因为光是使用这个工具去打包就会遇到很多问题,更别说去探究里面的源码了解其原理了。但是之前已经写了 electron-builder 中 macOS 的源码和原理解读,然后为了彻底了解所有平台的打包逻辑,我又开始阅读 windows 的构建源码,这里跟大家分享一下我的一些理解。

涉及的核心源码路径

  • winPackager.ts:Windows 平台打包的核心文件

https://github1s.com/electron-userland/electron-builder/blob/master/packages/app-builder-lib/src/winPackager.ts

  • AppxTarget.ts:用于创建 AppX/MSIX 包

https://github1s.com/electron-userland/electron-builder/blob/master/packages/app-builder-lib/src/targets/AppxTarget.ts

  • MsiTarget.ts:用于创建 MSI 安装包

https://github1s.com/electron-userland/electron-builder/blob/master/packages/app-builder-lib/src/targets/MsiTarget.ts

  • NsisTarget.ts:创建 NSIS 安装程序

https://github1s.com/electron-userland/electron-builder/blob/master/packages/app-builder-lib/src/targets/nsis/NsisTarget.ts

Windows 平台打包的核心流程

我们可以通过 Windows 平台打包的核心文件来分析一下核心的流程。WinPackager 类是 electron-builder 中处理 Windows 平台打包的核心类。它继承自 PlatformPackager,专门处理 Windows 相关的打包逻辑。

export class WinPackager extends PlatformPackager<WindowsConfiguration> {
  // ...
}

主要有以下几个核心的功能流程:

目标创建

createTargets 方法根据用户配置创建不同的打包目标:

createTargets(targets: Array<string>,mapper: (name: string, factory: (outDir: string) => Target) => void): void {
  // ...
  for (const name of targets) {
    if (name === "nsis" || name === "portable") {
      mapper(name, outDir => new NsisTarget(this, outDir, name, getHelper()))
    } else if (name === "nsis-web") {
      mapper(name, outDir => new WebInstallerTarget(this, path.join(outDir, name), name, new AppPackageHelper(getCopyElevateHelper())))
    }
    // ... 其他目标类型
  }
}

这里支持多种打包格式,如 NSIS、AppX、MSI 等,每种格式对应一个特定的 Target 类,这一部分是非常关键和核心的逻辑,在后面会对每种包的生成做一个详细的讲解。

图标处理

图标处理使用懒加载方式:

_iconPath = new Lazy(() => this.getOrConvertIcon("ico"))

这确保了只在需要时才进行图标转换,优化了性能。

代码签名

签名过程主要通过 signdoSign 方法实现:

async sign(file: string): Promise<boolean> {
  const signOptions: WindowsSignOptions = {
    path: file,
    options: this.platformSpecificBuildOptions,
  }
  const didSignSuccessfully = await this.doSign(signOptions)
  // ...
}

private async doSign(options: WindowsSignOptions) {
  return retry(
    () => signWindows(options, this),
    3,
    500,
    500,
    0,
    // ... 错误处理逻辑
  )
}

这里使用了重试机制,提高了签名的可靠性。

资源编辑

signAndEditResources 方法处理可执行文件的资源编辑:

async signAndEditResources(file: string, arch: Arch, outDir: string, internalName?: string | null, requestedExecutionLevel?: RequestedExecutionLevel | null) {
  // ... 准备参数
  if (process.platform === "win32" || process.platform === "darwin") {
    await executeAppBuilder(["rcedit", "--args", JSON.stringify(args)])
  } else if (this.info.framework.name === "electron") {
    // 使用 Wine 在非 Windows 平台上运行 rcedit
    await execWine(path.join(vendorPath, "rcedit-ia32.exe"), path.join(vendorPath, "rcedit-x64.exe"), args)
  }
  // ... 签名和缓存处理
}

这段代码展示了如何跨平台编辑 Windows 可执行文件的资源。

应用签名

signApp 方法处理整个应用的签名过程:

protected async signApp(packContext: AfterPackContext, isAsar: boolean): Promise<boolean> {
  // ... 签名主可执行文件
  if (!isAsar) {
    return true
  }
  // 处理 asar 打包情况下的文件签名
  const filesToSign = await Promise.all([filesPromise(["resources", "app.asar.unpacked"]), filesPromise(["swiftshader"])])
  await BluebirdPromise.map(filesToSign.flat(1), file => this.sign(file), { concurrency: 4 })
  // ...
}

这里特别处理了 asar 打包的情况,确保所有需要的文件都被正确签名。

性能优化

代码中使用了几种性能优化技术:

  • 懒加载: 如 _iconPathvm 的处理
  • 缓存: 使用 BuildCacheManager 缓存构建结果
  • 并发: 使用 BluebirdPromise.map 并行处理文件签名

创建 AppX/MSIX 包

AppX/MSIX 是 Windows 应用程序的现代打包格式,主要用于 Windows Store 分发和企业部署。AppxTarget 类负责将 Electron 应用打包成 AppX/MSIX 格式。以下是这个过程的主要步骤:

初始化和验证

constructor(private readonly packager: WinPackager, readonly outDir: string) {
  super("appx")
  if (process.platform !== "darwin" && (process.platform !== "win32" || isOldWin6())) {
    throw new Error("AppX is supported only on Windows 10 or Windows Server 2012 R2 (version number 6.3+)")
  }
}

这段代码确保 AppX 打包只在支持的系统上进行。

构建过程 (build 方法):

async build(appOutDir: string, arch: Arch): Promise<any> {
  // ... (设置输出路径等)
  const stageDir = await createStageDir(this, packager, arch)
  // ... (准备映射文件)
}

build 方法是整个打包过程的核心,它协调了所有必要的步骤。

资源处理

const assetInfo = await AppXTarget.computeUserAssets(vm, vendorPath, userAssetDir)

这部分处理应用程序的资源文件,包括图标和其他视觉元素。

生成 AppX 清单

await this.writeManifest(manifestFile, arch, await this.computePublisherName(), userAssets)

writeManifest 方法生成 AppxManifest.xml 文件,这是 AppX 包的核心配置文件,定义了应用的身份、能力和资源。

创建资源索引 (resources.pri)

if (isScaledAssetsProvided(userAssets)) {
  // ... (使用 makepri.exe 创建 resources.pri)
}

如果提供了缩放资源,会使用 makepri.exe 工具创建资源索引文件。

打包 AppX

await vm.exec(vm.toVmFile(path.join(vendorPath, "windows-10", signToolArch, "makeappx.exe")), makeAppXArgs)

使用 makeappx.exe 工具将所有文件打包成 .appx 或 .msix 文件。

签名

await packager.sign(artifactPath)

对生成的 AppX 包进行数字签名,这是分发和安装 AppX 包的必要步骤。

清理和完成

await stageDir.cleanup()
await packager.info.callArtifactBuildCompleted({
  // ...
})

清理临时文件并通知打包过程完成。

关键点:

  1. AppX 清单 (AppxManifest.xml) 是整个包的核心,定义了应用的元数据、能力和资源。
  2. 资源处理非常重要,包括图标、启动画面等。
  3. makeappx.exe 工具用于实际创建 AppX 包。
  4. 数字签名是 AppX 分发的必要步骤。
  5. 整个过程支持自定义和扩展,如自定义扩展、文件关联等。

AppX/MSIX 包的优势:

  • 安全性更高,所有内容都经过签名验证。
  • 支持自动更新。
  • 可以利用 Windows 现代特性,如实时磁贴。
  • 更好的应用隔离和清理卸载。

原生参考文档:https://learn.microsoft.com/en-us/windows/msix/package/create-app-package-with-makeappx-tool#mapping-files

创建 MSI 安装程序

MSI(Microsoft Installer)是 Windows 平台上常用的安装包格式,它提供了一种标准化的方式来安装、维护和删除软件。MSI 文件本质上是一个包含安装信息和文件的数据库。

在这个 MsiTarget 类中,MSI 生成的主要步骤如下:

准备阶段

async build(appOutDir: string, arch: Arch) {
  const stageDir = await createStageDir(this, packager, arch)
  // ...
}

这里创建了一个临时目录来存放生成过程中的文件。

生成 WiX 项目文件

const projectFile = stageDir.getTempFile("project.wxs")
await writeFile(projectFile, await this.writeManifest(appOutDir, wixArch, commonOptions))

WiX(Windows Installer XML)是用来创建 MSI 安装程序的工具集。这一步生成了 WiX 需要的 XML 格式的项目文件。

编译 WiX 项目

await vm.exec(vm.toVmFile(path.join(vendorPath, "candle.exe")), candleArgs, {
  cwd: stageDir.dir,
})

使用 WiX 的 candle.exe 工具编译项目文件。

链接并生成 MSI

await this.light(objectFiles, vm, artifactPath, appOutDir, vendorPath, stageDir.dir)

使用 WiX 的 light.exe 工具链接编译后的对象文件,生成最终的 MSI 文件。

签名(可选)

await packager.sign(artifactPath)

对生成的 MSI 文件进行数字签名。

生成原理的核心在于 writeManifest 方法,它定义了 MSI 的结构和内容:

protected async writeManifest(appOutDir: string, wixArch: Arch, commonOptions: FinalCommonWindowsInstallerOptions) {
  const { files, dirs } = await this.computeFileDeclaration(appOutDir)
  // ...
  return (await this.projectTemplate.value)({
    // ... 各种选项
    dirs,
    files,
  })
}

这个方法生成了 WiX 项目文件,定义了安装程序的各个方面,包括:

  • 文件和目录结构
  • 快捷方式
  • 文件关联
  • 注册表项
  • 安装和卸载逻辑

computeFileDeclaration 方法详细定义了文件和目录结构,包括如何处理主可执行文件、创建快捷方式等。

总结一下MSI 程序的优势,大概有如下几个:

  1. 标准化:使用统一的安装、更新和卸载流程。
  2. 回滚能力:安装失败时可以回滚到之前的状态。
  3. 特权分离:支持普通用户安装和管理员安装。
  4. 广泛支持:Windows 原生支持,兼容性好。

总的来说,这个类通过生成 WiX 项目文件,然后使用 WiX 工具集编译和链接,最终创建出一个完整的 MSI 安装程序。这个过程封装了许多复杂的细节,使得创建专业级的 Windows 安装程序变得相对简单,因为 MSI 文件的创作其实里面包含了非常多的东西,不是一篇两篇就能讲完的,更多可参考下面的文档。

原生参考文档:https://learn.microsoft.com/en-us/windows/win32/msi/windows-installer-portal

创建 NSIS 安装程序

NSIS(Nullsoft Scriptable Install System)是一个用于创建 Windows 安装程序的开源系统。它最初由 Nullsoft 公司开发,该公司以创建 Winamp 播放器而闻名。 在 electron-builder 项目中,NSIS 被用来为 Windows 平台生成安装程序。它允许开发者通过配置选项来自定义安装程序,而无需直接编写 NSIS 脚本,大大简化了创建专业安装程序的过程。

在 NsisTarget 中有一下几个核心的关键步骤:

应用程序打包

首先,NsisTarget 类会将应用程序文件打包成一个压缩文件:

async buildAppPackage(appOutDir: string, arch: Arch): Promise<PackageFileInfo> {
  const format = !isBuildDifferentialAware && options.useZip ? "zip" : "7z"
  const archiveFile = path.join(this.outDir, `${packager.appInfo.sanitizedName}-${packager.appInfo.version}-${Arch[arch]}.nsis.${format}`)
  
  await archive(format, archiveFile, appOutDir, archiveOptions)
  // ...
}

这里将应用程序文件打包成 7z 或 zip 格式,这是后续 NSIS 脚本将要使用的主要内容。

生成 NSIS 脚本

接下来,生成 NSIS 脚本。这个过程主要通过 computeFinalScript 方法完成:

private async computeFinalScript(originalScript: string, isInstaller: boolean, archs: Map<Arch, string>): Promise<string> {
  const scriptGenerator = new NsisScriptGenerator()
  // ...生成各种脚本内容
  return scriptGenerator.build() + originalScript
}

这个方法生成了完整的 NSIS 脚本,包括文件关联、预压缩文件处理等。

配置 NSIS 定义和命令

通过 configureDefinesconfigureDefinesForAllTypeOfInstaller 方法,设置各种 NSIS 定义:

protected configureDefines(oneClick: boolean, defines: Defines): Promise<any> {
  // ...设置各种定义
}

private configureDefinesForAllTypeOfInstaller(defines: Defines): void {
  // ...设置通用定义
}

这些定义控制了 NSIS 脚本的行为,如安装目录、快捷方式创建等。

执行 NSIS 编译

最后,使用 executeMakensis 方法调用 NSIS 编译器:

private async executeMakensis(defines: Defines, commands: Commands, script: string): Promise<void> {
  const args: Array<string> = []
  // ...准备参数
  
  await spawnAndWrite(command, args, script, {
    env: { ...process.env, NSISDIR: nsisPath },
    cwd: nsisTemplatesDir,
  })
}

这个方法将生成的脚本、定义和命令传递给 NSIS 编译器 (makensis),编译器然后生成最终的安装程序。

特殊文件处理

对于预压缩的文件,有特殊处理:

async function generateForPreCompressed(preCompressedFileExtensions: Array<string>, dir: string, arch: Arch, scriptGenerator: NsisScriptGenerator): Promise<void> {
  // ...处理预压缩文件
}

这确保了某些已压缩的文件(如 .asar 文件)能够正确地包含在安装程序中。

多架构支持

源码支持为不同架构生成安装程序:

for (const archs of doBuildArchs) {
  await this.buildInstaller(archs)
}

这允许生成适用于不同 CPU 架构的安装程序。
NSIS 打包这个过程高度可配置,允许定制安装程序的各个方面,如文件关联、快捷方式、多语言支持等。通过抽象和模块化,这个实现使得生成复杂的 Windows 安装程序变得相对简单和灵活。

结语

通过深入探讨 electron-builder 的 Windows 打包过程,我们看到了其背后的复杂性和精巧设计。从 winPackager.ts 的整体协调,到 AppxTarget.ts、MsiTarget.ts 和 NsisTarget.ts 等不同目标的具体实现,每一部分都扮演着重要角色。理解这些打包机制不仅有助于解决在使用 electron-builder 时可能遇到的问题,还能让我们在设计跨平台应用时做出更明智的决策,希望这篇文章可以帮助到你

标签:...,文件,Windows,builder,await,Electron,安装程序,打包
From: https://blog.csdn.net/github_39132491/article/details/144531503

相关文章

  • 从架构到API,你真的掌握了Electron的全貌吗?
    本文首发同名微信公众号:前端徐徐大家好,我是徐徐。今天我们来浅析一下Electron的原理。前言Electron的原理是每个开发Electron应用的开发者都需要了解的知识内容,因为知道整个原理全貌后你才能在设计一个应用的时候更加的合理,遇到问题才知道从哪个方面去分析。这篇......
  • 电脑开机或打开程序提示缺少Microsoft.Windows.Storage.Core.dll文件问题
    在大部分情况下出现我们运行或安装软件,游戏出现提示丢失某些DLL文件或OCX文件的原因可能是原始安装包文件不完整造成,原因可能是某些系统防护软件将重要的DLL文件识别为可疑,阻止并放入了隔离单里,还有一些常见的DLL文件缺少是因为系统没有安装齐全的微软运行库,还有部分情况是因为......
  • 在CodeBolcks+Windows API下的C++面向对象的编程教程——用面向对象的方法改写用向导
    0.前言我想通过编写一个完整的游戏程序方式引导读者体验程序设计的全过程。我将采用多种方式编写具有相同效果的应用程序,并通过不同方式形成的代码和实现方法的对比来理解程序开发更深层的知识。了解我编写教程的思路,请参阅体现我最初想法的那篇文章中的“1.编程计划”:学习编程......
  • 在 Windows 下编写 Linux 脚本,传至 Linux 中执行时,会遇到 not found 错误
    在Windows下建立脚本#!/bin/bashechohello传至Linux下执行脚本./test.sh执行出错-bash:./test.sh:Permissiondenied问题原因:未对文件添加可执行权限添加权限chmod+xtest.sh再次执行脚本./test.sh执行出错-bash:./test.sh:/bin/bash^M:badinterpreter:......
  • Windows系统下无头构建Linux系统下的so文件
     (开始执行以下步骤时,确保已经在系统下安装2017版Labview软件,及其驱动软件,压缩包内包含32位java插件安装包与NILinuxReal-TimeEclipseEdition的C/C++开发工具包***建议均使用默认路径安装***C盘空间配置大一些)1.首先我们需要在Windows系统里面安装Java插件(建议使用32位......
  • B4X编程语言:B4A, B4i 字符序列生成器CSBuilder
            B4X为我们提供了一个功能强大的字符串操作工具CSBuilder对象(仅用于B4A、B4i)。        CSBuilder类似 StringBuilder。 但与构建字符串不同,CSBuilder是通过操作字符序列来操作字符串,因此它也叫字符序列生成器,它构建了包含样式信息的字符序列。......
  • Fortify Static Code Analyzer 24.2 for macOS, Linux & Windows - 静态应用安全测试
    FortifyStaticCodeAnalyzer24.2formacOS,Linux&Windows-静态应用安全测试FortifySCA-代码漏洞扫描工具|静态代码测试|代码安全分析请访问原文链接:https://sysin.org/blog/fortify-static-code-analyzer/查看最新版。原创作品,转载请保留出处。作者主页:sysin.......
  • Windows 10 下编译 64 位 OpenJDK 8 并单步调试
    Windows10下编译64位OpenJDK8软件版本操作系统:Windows10Cygwin:3.5.4-1VisualStudio:2010英文版freetype:2.7BootJDK:7编译的OpenJDK版本:8安装bootJDK从下载链接下载JDK,然后在Windows上安装。安装完成后配置JAVE_HOME环境变量,如下......
  • Windows 下将无线网卡变 WiFi 热点
    首先打开cmd运行如下命令确认当前无线网卡是否支持承载网络:netshwlanshowdrivers若显示支持的承载网络:是则可继续,否则代表无线网卡不支持变热点。先设置网络配置:netshwlansethostednetworkmode=allowssid=wifinamekey=password即设置WiFi名称为wifiname,密码......
  • Windows DXGI屏幕捕获实现
    WindowsDXGI方式屏幕捕获实现主要步骤graphTBA[D3D11CreateDevice]-->B[ID3D11Device]A[D3D11CreateDevice]-->C[ID3D11DeviceContext]B-.QueryInterface.->D[IDXGIDevice]B-.GetParent.->E[IDXGIAdapter]E-.EnumOutputs.->F[IDXG......