前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >你不知道的 Electron (二):了解 Electron 打包

你不知道的 Electron (二):了解 Electron 打包

作者头像
用户1097444
发布2022-06-29 16:04:01
4.6K0
发布2022-06-29 16:04:01
举报
文章被收录于专栏:腾讯IMWeb前端团队

本文由 IMWeb 团队成员 laynechen 首发于 IMWeb 社区网站 imweb.io。点击阅读原文查看 IMWeb 社区更多精彩文章。

我们知道 Electron 提供了一个类似浏览器,但有更多权限的环境来运行我们的网页,那么 Electron 是怎么做到将我们的网页代码打包成一个可执行程序的呢?

这篇文章主要介绍如何打包 Electron 应用,以及分析 electron-builder 是如何对我们的应用进行打包的。

如何打包

Electron 目前有两种打包工具:electron-userland/electron-builder 和 electron-userland/electron-packager。

使用 electron-builder 打包

安装依赖:

代码语言:javascript
复制
yarn add electron-builder --dev// 或npm i electron-builder --save-dev

打包:

  1. 在项目的 package.json 文件中定义 name、 description、 version 和 author 信息。
  2. 在项目的 package.json 文件中定义 build 字段:
代码语言:javascript
复制
"build": {  "appId": "your.id",  "mac": {    "category": "your.app.category.type"  }}

全部选项: https://electron.build/configuration/configuration

  1. 添加 scripts 到 package.json 中
代码语言:javascript
复制
"scripts": {  "pack": "electron-builder --dir",  "dist": "electron-builder"}
  1. 打包

生成 package 目录但是没有打包为一个文件

代码语言:javascript
复制
npm run pack

生成一个 exe 或者 dmg 文件

代码语言:javascript
复制
npm run dist
  1. 指定平台和架构
代码语言:javascript
复制
# windows 64bitelectron-builder --win --x64# windows and mac 32bitelectron-builder --win --mac --ia32

详细参数:https://www.electron.build/cli

使用 electron-packager 打包

安装依赖:

代码语言:javascript
复制
npm i electron-packager --save-dev

打包:

代码语言:javascript
复制
electron-packager <sourcedir> <appname> --platform=<platform> --arch=<arch> [optional flags...]

最简单的就是直接运行 electron-packager. 打包。

默认情况下, appname 为 当前项目下的 package.json 文件中的 productName 或者 name 字段的值; platformarch 则与主机一致,在 Windows64位 下打包就是 Windows 64 位的版本。

具体每个字段的值可以看: https://github.com/electron-userland/electron-packager/blob/master/usage.txt

注: OS X 下打包 Windows 的应用需要安装 Wine才行, electron-packager 需要使用 node-rcedit 编辑 Electron.exe 文件。

Building an Electron app for the Windows target platform requires editing the Electron.exe file. Currently, Electron Packager uses node-rcedit to accomplish this. A Windows executable is bundled in that Node package and needs to be run in order for this functionality to work, so on non-Windows host platforms, Wine 1.6 or later needs to be installed. On OS X, it is installable via Homebrew.

electron-builder 打包分析

文件大小分析

因为要达到跨平台的目的,每个 Electron 应用都包含了整个 V8 引擎和 Chromium 内核,以致于一个空的 Electron 项目,使用 electron-builder--dir 打包后没有压缩的项目文件夹,大小也已经到了 121.1 MB。如果使用 electron-builder 进行打包,安装程序的大小为 36MB,这个大小是可以接受。

但是上面是一个空的项目,那么一个实际的项目打包之后有多大呢?一个安装了 30+ 个依赖的项目,未生成安装包前,项目文件夹的大小是 230+ MB,生成安装程序后是 56.3 MB,生成安装程序之后的大小还是可以接受的,比空项目也只大了 20MB 左右。

但其实大了 20MB 也是不太科学的,本身项目并没有什么大的资源文件,如果只是代码的话不打包的大小应该也只有 10MB 以下。那么是什么让项目的大小大了接近 100MB?

打包后的项目结构

我们看下打包后的项目结构 (electron-builder --dir)

加上 --dir 参数,不将整个应用打包成安装文件,来查看一个应用的目录结构:

代码语言:javascript
复制
.├── locales│   ├── am.pak│   └── ... 一堆的 pak 文件├── resources│   ├── app.asar (空项目只有 2KB,一个实际项目有 130MB+)│   └── electron.asar (大小在 250KB 左右)├── electron.exe (67.5MB)└── ...

这里忽略了很多的文件,我们主要看 electron.exe 文件和 resources 文件夹。因此实际项目和空项目多的东西应该就是在 app.asar 上面了。

app.asar

dist/win-unpacked/resources/ 下生成了 app.asar 文件,这是一个用 asar 压缩后的文件。我们可以解压看下里面是什么:

代码语言:javascript
复制
# 安装 asarnpm install -g asar# 解压到 ./app 文件夹下asar extarct app.asar ./app

解压目录如下:

代码语言:javascript
复制
.├── CHANGELOG.md├── README.md├── core├── electron├── icon├── node_modules├── package.json├── test├── view└── webpack.config.js

看到这个目录会不会很熟悉?~实际上是把我们的整个项目的内容都打包进来了。当然对 node_modules 文件夹有特殊处理,这里只打包了 production dependencies,即在 package.jsondependencies 中定义的依赖。

空的项目和一个实际项目的大小差距就出在依赖这里了。

electron.asar

我们再来看下 electron.asar 打包了什么东西:

代码语言:javascript
复制
asar extract electron.asar ./electron
代码语言:javascript
复制
.├── browser│   ├── api│   ├── chrome-extension.js│   ├── desktop-capturer.js│   ├── guest-view-manager.js│   ├── guest-window-manager.js│   ├── init.js│   ├── objects-registry.js│   └── rpc-server.js├── common│   ├── api│   ├── atom-binding-setup.js│   ├── init.js│   ├── parse-features-string.js│   └── reset-search-paths.js├── renderer│   ├── api│   ├── chrome-api.js│   ├── content-scripts-injector.js│   ├── extensions│   ├── init.js│   ├── inspector.js│   ├── override.js│   ├── web-view│   └── window-setup.js└── worker    └── init.js

Electron 相关的源代码被压缩到了 electron.asar 文件中。

打包分析

electron-builder 打包时输出的信息

打包的时候我们可以看到 控制台输出了如下信息:

代码语言:javascript
复制
  • electron-builder version=20.15.1  • loaded configuration file=package.json ("build" field)  • writing effective config file=dist/electron-builder-effective-config.yaml  • rebuilding native production dependencies platform=win32 arch=x64  • packaging       platform=win32 arch=x64 electron=1.8.7 appOutDir=dist/win-unpacked

如果还要打包程序的话,还有以下打印信息:

代码语言:javascript
复制
  • building        target=nsis file=dist/xxx.exe archs=x64 oneClick=true  • building block map blockMapFile=dist/xxx.exe.blockmap

大致可以知道打包主要做了以下事情:

  1. 重新安装依赖
  2. 打包

从这里知道的信息还是比较有限,所以还是得看下从输入 electron-builder 到生成安装程序中间经历了什么。

"bin"

我们从安装的 electron-builder 依赖的 packager.json 文件定义的 "bin" 字段信息可以看到它执行了 ./out/cli/cli.js 这个文件。

代码语言:javascript
复制
"bin": {    "electron-builder": "./out/cli/cli.js",    "build": "./out/cli/cli.js",    "install-app-deps": "./out/cli/install-app-deps.js"}

./out 目录下的文件是已经经过 babel 转译之后的,我们可以去下载 electron-builder 源码来分析。

"packages/electron-builder/src/cli/cli.ts"

从源码中我们不难定位到 packages/electron-builder/src/cli/cli.ts 这个文件就是命令的入口文件。从入口文件往下分析:

  1. packages/electron-builder/src/builder.ts

cli.ts 文件中 import 了上一层目录的 builder.ts 文件导出的 build 方法。 build 方法中创建了一个 Packager 对象,然后又调用了 packages/electron-builder-lib 导出的 build 方法。

cli.ts 中的 build 方法:

代码语言:javascript
复制
export function build(rawOptions?: CliOptions): Promise<Array<string>> {  const buildOptions = normalizeOptions(rawOptions || {})  const packager = new Packager(buildOptions)  let electronDownloader: any = null  packager.electronDownloader = options => {    if (electronDownloader == null) {      electronDownloader = BluebirdPromise.promisify(require("electron-download-tf"))    }    return electronDownloader(options)  }  return _build(buildOptions, packager)}
  1. packages/electron-builder-lib/index.ts
代码语言:javascript
复制
export async function build(options: PackagerOptions & PublishOptions, packager: Packager = new Packager(options)): Promise<Array<string>> {  ...  return await executeFinally(packager.build().then(() => Array.from(artifactPaths)), errorOccurred => {    ...  })}

build 方法中调用了 packagerbuild 方法。

  1. packages/electron-builder-lib/packager.ts

build 方法对一些信息进行处理后又调用了 _build 方法:

代码语言:javascript
复制
async build(): Promise<BuildResult> {    ...  return await this._build(configuration, this._metadata, this._devMetadata)}

_build 方法继续调用了私有方法 doBuild:

代码语言:javascript
复制
async _build(configuration: Configuration, metadata: Metadata, devMetadata: Metadata | null, repositoryInfo?: SourceRepositoryInfo): Promise<BuildResult> {    ...    return {      outDir,      platformToTargets: await executeFinally(this.doBuild(outDir), async () => {        if (this.debugLogger.enabled) {          await this.debugLogger.save(path.join(outDir, "electron-builder-debug.yml"))        }        await this.tempDirManager.cleanup()      }),    }}

doBuild 中负责了要创建哪些平台的安装包、以及如何去打包:

代码语言:javascript
复制
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.platformSpecificBuildOptions, platform)) {        await this.installAppDependencies(platform, arch)        const targetList = createTargets(nameToTarget, targetNames.length === 0 ? packager.defaultTarget : targetNames, outDir, packager)        await createOutDirIfNeed(targetList, createdOutDirs)        await packager.pack(outDir, arch, targetList, taskManager)      }    }    return platformToTarget}

createHelper 实际上就是根据平台去创建相对应的 Packager 对象,另外根据不同架构去安装应用的依赖,最后调用 pack 方法打包。

后面分析下打包 Windows 平台的 WinPackager

WinPackager

实际上 WinPackager 是继承于 PlatformPackager 类, pack 方法也是在这个父类里面定义的:

代码语言:javascript
复制
async pack(outDir: string, arch: Arch, targets: Array<Target>, taskManager: AsyncTaskManager): Promise<any> {    const appOutDir = this.computeAppOutDir(outDir, arch)    await this.doPack(outDir, appOutDir, this.platform.nodeName, arch, this.platformSpecificBuildOptions, targets)    this.packageInDistributableFormat(appOutDir, arch, targets, taskManager)}

这个方法里面又是调用了另一个方法 doPack

代码语言:javascript
复制
protected async doPack(outDir: string, appOutDir: string, platformName: string, arch: Arch, platformSpecificBuildOptions: DC, targets: Array<Target>) {    ...    const computeParsedPatterns = (patterns: Array<FileMatcher> | null) => {      if (patterns != null) {        for (const pattern of patterns) {          pattern.computeParsedPatterns(excludePatterns, this.info.projectDir)        }      }    }    const getFileMatchersOptions: GetFileMatchersOptions = {      macroExpander,      customBuildOptions: platformSpecificBuildOptions,      outDir,    }    const extraResourceMatchers = this.getExtraFileMatchers(true, appOutDir, getFileMatchersOptions)    computeParsedPatterns(extraResourceMatchers)    const extraFileMatchers = this.getExtraFileMatchers(false, appOutDir, getFileMatchersOptions)    computeParsedPatterns(extraFileMatchers)    const packContext: AfterPackContext = {      appOutDir, outDir, arch, targets,      packager: this,      electronPlatformName: platformName,    }    const taskManager = new AsyncTaskManager(this.info.cancellationToken)    const asarOptions = await this.computeAsarOptions(platformSpecificBuildOptions)    const resourcesPath = this.platform === Platform.MAC ? path.join(appOutDir, framework.distMacOsAppName, "Contents", "Resources") : (isElectronBased(framework) ? path.join(appOutDir, "resources") : appOutDir)    this.copyAppFiles(taskManager, asarOptions, resourcesPath, path.join(resourcesPath, "app"), outDir, platformSpecificBuildOptions, excludePatterns, macroExpander)    await taskManager.awaitTasks()    const beforeCopyExtraFiles = this.info.framework.beforeCopyExtraFiles    if (beforeCopyExtraFiles != null) {      await beforeCopyExtraFiles(this, appOutDir, asarOptions == null ? null : await computeData(resourcesPath, asarOptions.externalAllowed ? {externalAllowed: true} : null))    }    await BluebirdPromise.each([extraResourceMatchers, extraFileMatchers], it => copyFiles(it))    await this.info.afterPack(packContext)    await this.sanityCheckPackage(appOutDir, asarOptions != null)    await this.signApp(packContext)    await this.info.afterSign(packContext)}

这里我们知道了, app.asar 文件就是在这个方法中生成的。

在打包的时候,是通过 Matcher 来实现选择性的打包哪些文件。从 FileMatcher 中可以看到相关定义:

代码语言:javascript
复制
export const excludedNames = ".git,.hg,.svn,CVS,RCS,SCCS," +  "__pycache__,.DS_Store,thumbs.db,.gitignore,.gitkeep,.gitattributes,.npmignore," +  ".idea,.vs,.flowconfig,.jshintrc,.eslintrc,.circleci," +  ".yarn-integrity,.yarn-metadata.json,yarn-error.log,yarn.lock,package-lock.json,npm-debug.log," +  "appveyor.yml,.travis.yml,circle.yml,.nyc_output"export const excludedExts = "iml,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,suo,xproj,cc,d.ts"

electron.exe

我们运行的 electron.exe 可执行程序,实际上是早就已经编译好的文件。他的功能就是加载 resources/app.asar 文件中的内容,包括入口文件的位置,也是从 app.asar 中打包的 package.jsonmain 字段来获取加载。

打包工具需要做的事情只是把这个 electron.exe 文件修改下图标、作者、版本等信息即可。

总结

上面简单的对 electron-builder 的打包过程进行了分析。通过分析,我们了解了:

  1. Electron 应用体积的分布情况:

electron.exe 在 67.5MB 左右,electron.asar 在 250KB 左右,app.asar 则根据实际项目差别会比较大,空的项目在 2KB 左右,测试中的一个实际项目在 130MB 左右。app.asar 大的原因在于实际项目依赖上会比较多,而打包工具在打包时是需要将整个 node_modules 文件夹都打包进来的,因此体积上会大很多。

  1. 可执行文件是怎么来的

通过实现一个通用的可执行程序,这个程序做的事情是将 resources/app.asar 作为项目根目录,运行 app.asar/package.jsonmain 指定文件作为入口文件。不同的应用程序只需要重新打包好相应的 app.asar 即可。最后对这个可执行程序的图标等信息进行修改就可以得到我们的应用程序了~

  1. 打包可能存在的问题

electron-builder 打包虽然帮我们把一些文件过滤掉不进行打包,但是我们的项目源码是没有经过任何处理的被打包了进去。

【参考资料】

👇点击阅读原文获取更多参考资料

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-09-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯IMWeb前端团队 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 如何打包
    • 使用 electron-builder 打包
      • 安装依赖:
      • 打包:
    • 使用 electron-packager 打包
      • 安装依赖:
      • 打包:
  • electron-builder 打包分析
    • 文件大小分析
      • 打包后的项目结构
        • app.asar
        • electron.asar
      • 打包分析
        • electron-builder 打包时输出的信息
        • "bin"
        • "packages/electron-builder/src/cli/cli.ts"
        • WinPackager
        • electron.exe
      • 总结
        • 【参考资料】
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档