从源码踩坑 electron + electron-builder + better-sqlite3 免编译安装

1,819 阅读2分钟

缘由

之前的几次尝试均为手动下载覆盖或者安装 C++ 依赖,一直有网络因素和环境依赖的困扰,这次打算翻翻源码,看一下如何从根源解决自定义预编译二进制地址的问题

结论

在npmrc中添加

ELECTRON_MIRROR=https://registry.npmmirror.com/-/binary/electron/
ELECTRON_BUILDER_BINARIES_MIRROR=https://registry.npmmirror.com/-/binary/electron-builder-binaries/
better_sqlite3_binary_host=https://registry.npmmirror.com/-/binary/better-sqlite3
  • 如果用的是 Bun,请在项目根目录下添加 .env 文件,将上述配置中的 better_sqlite3_binary_host 替换成 npm_config_better_sqlite3_binary_host 并写入,但是 1.1.3 存在 node 兼容问题,不推荐

原理

三者均支持通过读取环境变量从自定义URL下载预编译二进制

npmmirror 的二进制文件下载地址的前缀为 https://registry.npmmirror.com/-/binary/

npmrc 中的环境变量在运行时环境中会自动加上前缀 npm_config_

electron

electron 通过 @electron/get 执行 postInstall 脚本下载二进制,读取 .npmrcpackage.jsonconfig 和 环境变量中ELECTRON_MIRROR 获取下载地址

node_modules/@electron/get/dist/cjs/artifact-utils.js的 23-38 行

function mirrorVar(name, options, defaultValue) {
    // Convert camelCase to camel_case for env var reading
    const snakeName = name.replace(/([a-z])([A-Z])/g, (_, a, b) => `${a}_${b}`).toLowerCase();
    return (
    // .npmrc
    process.env[`npm_config_electron_${name.toLowerCase()}`] ||
        process.env[`NPM_CONFIG_ELECTRON_${snakeName.toUpperCase()}`] ||
        process.env[`npm_config_electron_${snakeName}`] ||
        // package.json
        process.env[`npm_package_config_electron_${name}`] ||
        process.env[`npm_package_config_electron_${snakeName.toLowerCase()}`] ||
        // env
        process.env[`ELECTRON_${snakeName.toUpperCase()}`] ||
        options[name] ||
        defaultValue);
}

electron-builder

electron-builder 是通过 app-builder-lib 下载二进制,读取 .npmrcpackage.jsonconfig 和 环境变量中ELECTRON_BUILDER_BINARIES_MIRROR 获取下载地址

node_modules\app-builder-lib\out\binDownload.js 的 19-40 行

function getBinFromUrl(name, version, checksum) {
    const dirName = `${name}-${version}`;
    let url;
    if (process.env.ELECTRON_BUILDER_BINARIES_DOWNLOAD_OVERRIDE_URL) {
        url = process.env.ELECTRON_BUILDER_BINARIES_DOWNLOAD_OVERRIDE_URL + "/" + dirName + ".7z";
    }
    else {
        const baseUrl = process.env.NPM_CONFIG_ELECTRON_BUILDER_BINARIES_MIRROR ||
            process.env.npm_config_electron_builder_binaries_mirror ||
            process.env.npm_package_config_electron_builder_binaries_mirror ||
            process.env.ELECTRON_BUILDER_BINARIES_MIRROR ||
            "https://github.com/electron-userland/electron-builder-binaries/releases/download/";
        const middleUrl = process.env.NPM_CONFIG_ELECTRON_BUILDER_BINARIES_CUSTOM_DIR ||
            process.env.npm_config_electron_builder_binaries_custom_dir ||
            process.env.npm_package_config_electron_builder_binaries_custom_dir ||
            process.env.ELECTRON_BUILDER_BINARIES_CUSTOM_DIR ||
            dirName;
        const urlSuffix = dirName + ".7z";
        url = `${baseUrl}${middleUrl}/${urlSuffix}`;
    }
    return getBin(dirName, url, checksum);
}

better-sqlite3

使用 prebuild-install 发布预编译二进制。根据其文档,其读取的是 better_sqlite3_binary_host 变量

但是根据源码,其实读取的是 npm_config_better_sqlite3_binary_host

同时,路径结尾不能包含 /,否则会报错

node_modules/prebuild-install/util.js 的 7-36 行

function urlTemplate (opts) {
  if (typeof opts.download === 'string') {
    return opts.download
  }

  const packageName = '{name}-v{version}-{runtime}-v{abi}-{platform}{libc}-{arch}.tar.gz'
  const hostMirrorUrl = getHostMirrorUrl(opts)

  if (hostMirrorUrl) {
    return hostMirrorUrl + '/{tag_prefix}{version}/' + packageName
  }

  if (opts.pkg.binary && opts.pkg.binary.host) {
    return [
      opts.pkg.binary.host,
      opts.pkg.binary.remote_path,
      opts.pkg.binary.package_name || packageName
    ].map(function (path) {
      return trimSlashes(path)
    }).filter(Boolean).join('/')
  }

  return github(opts.pkg) + '/releases/download/{tag_prefix}{version}/' + packageName
}

function getEnvPrefix (pkgName) {
  return 'npm_config_' + (pkgName || '').replace(/[^a-zA-Z0-9]/g, '_').replace(/^_/, '')
}

function getHostMirrorUrl (opts) {
  const propName = getEnvPrefix(opts.pkg.name) + '_binary_host'
  return process.env[propName] || process.env[propName + '_mirror']
}