深入浅出Electron:原理、工程与实践
上QQ阅读APP看书,第一时间看更新

2.7 加载应用入口脚本

我们知道每个Electron应用都是从主进程的入口脚本开始执行的,那么Electron是如何找到并执行这个入口脚本的呢?

在Electron通过LoadEmbedderJavaScriptSource方法执行TypeScript逻辑时,有一个脚本文件完成了这个工作,它就是lib\browser\init.ts。在这个文件中有如下一段逻辑:

let packagePath = null;
let packageJson = null;
const searchPaths = ['app', 'app.asar', 'default_app.asar'];
if (process.resourcesPath) {
  for (packagePath of searchPaths) {
    try {
      packagePath = path.join(process.resourcesPath, packagePath);
      packageJson = Module._load(path.join(packagePath, 'package.json'));
      break;
    } catch {
      continue;
    }
  }
}
if (packageJson == null) {
  process.nextTick(function () {
    return process.exit(1);
  });
  throw new Error('Unable to find a valid app');
}

这段代码寻找Electron项目根目录下的resources子目录,检查这个目录下是否包含app子目录,如果没有则检查是否存在app.asar文件,如果还是没有找到,则检查是否存在default_app.asar文件,依旧没有找到则报异常并退出。

如果有一个命中,则读取它内部的package.json文件,关于Electron如何解析并读取asar文件内部的数据,后面会有介绍。

Electron读取package.json文件后,会根据package.json内的信息执行一系列的配置逻辑,比如设置应用的默认版本号、设置应用的默认DesktopName等。

这些工作都设置完之后,Electron把package.json配置的main脚本交给其内置Node.js执行,代码如下所示:

const Module = require('module');
const mainStartupScript = packageJson.main || 'index.js';
if (packagePath) {
  // Finally load app's main.js and transfer control to C++.
  process._firstFileName = Module._resolveFilename(path.join(packagePath, main-StartupScript), null, false);
  Module._load(path.join(packagePath, mainStartupScript), Module, true);
} else {
  console.error('Failed to locate a valid package to load (app, app.asar or default_app.asar)');
  console.error('This normally means you\'ve damaged the Electron package somehow');
}

上述代码中,mainStartupScript变量存放的就是主进程的入口脚本文件,如果开发者没有为package.json配置main属性,那么Electron会默认加载index.js,这和Node.js的行为是一致的。

这段代码里用到的Module对象是Node.js提供的内置模块,只不过Electron使用了它的两个内置方法_resolveFilename和_load。其中_load方法负责加载并执行开发者提供的主进程的入口脚本文件,这就是Electron应用加载入口脚本的逻辑。