Электрон, которого вы не знаете (2): понимание электронной упаковки

внешний интерфейс Ресурсы изображений Windows Electron

Из сообщества IMWeb, автор: лайнехен,Оригинальная ссылка

Мы знаем, что Electron предоставляет среду, подобную браузеру, с большим количеством разрешений для запуска наших веб-страниц, так как же Electron упаковывает код нашей веб-страницы в исполняемую программу?

В этой статье в основном рассказывается, как упаковывать приложения Electron и анализироватьelectron-builderКак упаковать наше приложение.

Как упаковать

В настоящее время у Electron есть два инструмента для упаковки:electron-userland/electron-builderа такжеelectron-userland/electron-packager.

Упаковка с электрон-строителем

Установите зависимости:

yarn add electron-builder --dev
// 或
npm i electron-builder --save-dev

Пакет:

  1. в проектеpackage.jsonопределено в файлеname,description,versionа такжеauthorИнформация.
  2. в проектеpackage.jsonопределено в файлеbuildПоле:
"build": {
  "appId": "your.id",
  "mac": {
    "category": "your.app.category.type"
  }
}

(Все варианты)

  1. Добавить кscriptsприбытьpackage.jsonсередина
"scripts": {
  "pack": "electron-builder --dir",
  "dist": "electron-builder"
}
  1. Пакет

Каталог пакета создается, но не упаковывается в виде файла

npm run pack

Создать файл exe или dmg

npm run dist
  1. Укажите платформу и архитектуру
# windows 64bit
electron-builder --win --x64
# windows and mac 32bit
electron-builder --win --mac --ia32

Технические характеристики:Command Line Interface (CLI)

Упаковка с электрон-упаковщиком

Установите зависимости:

npm i electron-packager --save-dev

Пакет:

electron-packager <sourcedir> <appname> --platform=<platform> --arch=<arch> [optional flags...]

Проще всего запустить напрямуюelectron-packager .Пакет.

по умолчанию,appnameдля текущего проектаpackage.jsonв файлеproductNameилиnameзначение поля;platformа такжеarchсогласуется с хозяином, вWindows 64位Следующий пакет — 64-битная версия Windows.

Конкретное значение каждого поля можно увидетьelectron-packager/usage.txt

Примечание:Приложения, которые упаковывают Windows под OS X, должны быть установлены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 содержит весь движок V8 и ядро ​​​​Chromium, так что пустой проект Electron используетelectron-builder --dirПосле упаковки несжатая папка проекта достигла размера 121,1 МБ. При использованииelectron-builderДля упаковки размер установщика составляет 36 МБ, что приемлемо.

Но это пустой проект, так насколько большим будет фактический проект после упаковки? Проект с установленными 30+ зависимостями, размер папки проекта 230+ МБ до генерации инсталляционного пакета и 56,3 МБ после генерации инсталляционной программы.Более около 20МБ.

Но на самом деле 20Мб это не очень научно.В самом проекте нет больших файлов ресурсов.Если это просто код, то размер в распакованном виде должен быть меньше 10Мб. Так что же заставило размер проекта приблизиться к 100 МБ?

Пакетная структура проекта

Давайте посмотрим на пакетную структуру проекта (electron-builder --dir)

плюс--dirпараметр, чтобы просмотреть структуру каталогов приложения без упаковки всего приложения в установочный файл:

.
├── 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. Мы можем разархивировать его и посмотреть, что внутри:

# 安装 asar
npm install -g asar
# 解压到 ./app 文件夹下
asar extarct app.asar ./app

Каталог распаковки выглядит следующим образом:

.
├── CHANGELOG.md
├── README.md
├── core
├── electron
├── icon
├── node_modules
├── package.json
├── test
├── view
└── webpack.config.js

Будет ли этот каталог выглядеть знакомо? ~ фактически упаковал содержимое всего нашего проекта. Да конечноnode_modulesПапки имеют специальную обработку, упакованы только здесьproduction dependencies, то есть вpackage.jsonизdependenciesОпределяется в зависимости.

Пустые проекты и разрыв в размерах между фактическим проектом зависят.

electron.asar

Давайте посмотрим, что упаковывает электрон.асар:

asar extract electron.asar ./electron
.
├── 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, сжат в файл electronic.asar.

Анализ упаковки

Вывод информации электронщиком при упаковке

При упаковке мы видим, что консоль выводит следующую информацию:

  • 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

Если вы также хотите упаковать программу, есть также следующая информация для печати:

  • building        target=nsis file=dist/xxx.exe archs=x64 oneClick=true
  • building block map blockMapFile=dist/xxx.exe.blockmap

Можно примерно сказать, что упаковка в основном выполняет следующие функции:

  1. переустановить зависимости
  2. Пакет

Информация, которую я знаю отсюда, по-прежнему относительно ограничена, поэтому мне все еще нужно просмотреть входные данные изelectron-builderЧто произошло в середине генерации файла installer.

"bin"

мы установили изelectron-builderзависимыйpackager.jsonИнформация о поле «bin» определения файла может видеть, что он выполняется./out/cli/cli.jsэтот файл.

"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Файл, который импортирует предыдущий каталогbuilder.tsэкспорт файлаbuildметод.buildметод создаетPackagerобъект, а затем вызватьpackages/electron-builder-libэкспортируетсяbuildметод.

cli.tsсерединаbuildметод:

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
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метод называетсяpackagerизbuildметод.

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

Метод сборки вызывается после обработки некоторой информации_buildметод:

async build(): Promise<BuildResult> {
    ...
  return await this._build(configuration, this._metadata, this._devMetadata)
}

_buildМетод продолжает вызывать закрытый методdoBuild:

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Он отвечает за то, какие установочные пакеты платформы создавать и как их упаковывать:

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способ упаковки.

Более поздний анализ упаковки платформы WindowsWinPackager

WinPackager

ФактическиWinPackagerунаследовано отPlatformPackagerДобрый,packМетод также определен в этом родительском классе:

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:

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Соответствующие определения можно увидеть в:

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.jsonизmainполе для загрузки.

Все, что нужно сделать упаковщику, это просто поместить этоelectron.exeВы можете изменить значок, автора, версию и другую информацию о файле.

Суммировать

Вышеупомянутое простоelectron-builderПроцесс упаковки был проанализирован. Благодаря анализу мы узнали:

  1. Распределение объема приложений Electron:

electron.exeОколо 67,5 МБ, Electron.Asar Около 250 КБ, App.asar Project основана на фактической разнице, будет относительно большой, пустой проект составляет около 2 КБ, тестирование в реальном проекте составляет около 130 МБ. Приложение. Сасар Большая причина, которая будет больше зависеть от фактического проекта, а инструменты упаковки требуются при упаковке всегоnode_modulesПапки все запакованы, поэтому объем будет намного больше.

  1. как появился исполняемый файл

Реализуя универсальную исполняемую программу, эта программа делает следующее:resources/app.asarВ качестве корневого каталога проекта запуститеapp.asar/package.jsonсерединаmainУказывает файл в качестве файла записи. Различные приложения должны быть только соответствующим образом переупакованыapp.asarВот и все. Наконец, мы можем получить наше приложение, изменив значок и другую информацию этой исполняемой программы~

  1. Возможные проблемы с упаковкой

electron-builderХотя упаковка помогает нам отфильтровать некоторые файлы и не упаковывать их, исходный код нашего проекта упакован без какой-либо обработки.

【Использованная литература】

PS: Для оптимизации упаковки Electron вы можете обратиться к другой статье автора.«Оптимизация электронной упаковки — с 393 МБ до 161 МБ»