上一篇文章:https://www.cnblogs.com/liulun/
(小广告:我作的开源免费的,我的知识管理及自媒体营销工具“想学吗”:https://github.com/xland/xiangxuema)
咱们在package.json里能找到他的入口文件;javascript
"main": "./out/main",
electron是分主进程和渲染进程的;
渲染进程是主进程启动的;
./out/main.js显然这就是主进程的入口程序;
确实不假
但别着急去分析这个文件;
由于它是在out目录下,明显是什么东西输出出来的;
咱们先打扫一遍src目录下的东西;
发现了tsconfig.jsonjava
"outDir": "../out",
哈,这是typescript代码,编译后输出到./out/目录下的;
那么咱们来看src下的main.js
分析代码最主要的就是目的明确,咱们的目的是看看他的启动逻辑(主窗口是怎么打开的)
无关的东西先无论,要否则很容易迷失...;
咱们在main.js里找electron的ready事件node
app.once('ready', function () { if (args['trace']) { // @ts-ignore const contentTracing = require('electron').contentTracing; const traceOptions = { categoryFilter: args['trace-category-filter'] || '*', traceOptions: args['trace-options'] || 'record-until-full,enable-sampling' }; contentTracing.startRecording(traceOptions, () => onReady()); } else { onReady(); } });
先去看onReady方法
onReady里主要就是执行这个方法:git
const startup = nlsConfig => { nlsConfig._languagePackSupport = true; process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfig); process.env['VSCODE_NODE_CACHED_DATA_DIR'] = cachedDataDir || ''; // Load main in AMD perf.mark('willLoadMainBundle'); require('./bootstrap-amd').load('vs/code/electron-main/main', () => { perf.mark('didLoadMainBundle'); }); };
到这里,咱们先看看bootstrap-amd都干了啥
发现他其实调用了/vs/loader里的方法(下面这行代码里面entrypoint就是:vs/code/electron-main/main)github
loader([entrypoint], onLoad, onError);
loader是微软自家的AMD模块加载开源项目:https://github.com/Microsoft/vscode-loader/
没啥好说的,咱们接着来看vs/code/electron-main/main.ts的代码,
发现它一开始就加载了一大堆模块,头大!
先无论它加载的这些模块都是干吗的,咱们看它自己的入口,代码拉到末尾,发现:typescript
const code = new CodeMain(); code.main();
立刻去看这个模块的main函数;发现main函数对于咱们惟一有用的就是:json
this.startup(args);
这个函数启动了一堆服务以后,就执行了:bootstrap
const mainIpcServer = yield this.doStartup(logService, environmentService, lifecycleService, instantiationService, true);
和windows
return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup();
咱们先看第一行,在doStartup里,只有这行代码看起来有用:app
server = await serve(environmentService.mainIPCHandle);
serve是上面加载的那一大堆模块之一:vs/base/parts/ipc/node/ipc.net
发现它的serve其实就是启动了一个服务:
function serve(hook) { return new Promise((c, e) => { const server = net_1.createServer(); server.on('error', e); server.listen(hook, () => { server.removeListener('error', e); c(new Server(server)); }); }); }
对咱们目前的分析,帮助不大!
咱们再返回头看第二行代码:
instantiationService.ts在vs/platform/instantiation/common/instantiationService.ts
他的createInstance是个工厂函数,第一个参数是类型(或构造函数),后面的参数都是这个类型的构造函数所须要的参数。
那么咱们主要看第一个参数CodeApplication,这个类型的代码在这里:vs/code/electron-main/app.ts
咱们找到CodeApplication的startup方法,看到这一句:
// Open Windows const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient));
这应该就是咱们找的启动主窗口的方法了,跟进去看看:
一开始是一大堆IPC通讯相关的代码(主线程和渲染线程通讯的代码)
以后建立了IWindowsMainservice的实例
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService);
而后用这个实例建立了窗口
return windowsMainService.open({ context, cli: args, forceNewWindow: args['new-window'] || (!hasCliArgs && args['unity-launch']), diffMode: args.diff, noRecentEntry, waitMarkerFileURI, initialStartup: true });
IWindowsMainservice接口具体实例的类型是WindowsManager(能够在app.ts文件中找到下面的代码)
services.set(IWindowsMainService, new SyncDescriptor(WindowsManager, [machineId, this.userEnv]));
(IWindowsMainservice接口的描述文件在这里:vs\platform\windows\electron-main\windows.ts)
WindowsManager在vs/code/electron-main/windows.ts文件中定义,
那咱们去看看WindowsManager的open方法,发现了:
const usedWindows = this.doOpen(openConfig, workspacesToOpen, foldersToOpen, emptyToRestore, emptyToOpen, fileInputs, foldersToAdd);
好,再去看doOpen,发现最后的:
// Finally, if no window or folder is found, just open the files in an empty window else { usedWindows.push(this.openInBrowserWindow({ userEnv: openConfig.userEnv, cli: openConfig.cli, initialStartup: openConfig.initialStartup, fileInputs, forceNewWindow: true, remoteAuthority: fileInputs.remoteAuthority, forceNewTabbedWindow: openConfig.forceNewTabbedWindow })); // Reset these because we handled them fileInputs = undefined; }
注意:这两个方法有一个重要的逻辑就是:若是已经有一个窗口了,那么就用现成的窗口打开目录(或文件)
再去看openInBrowserWindow
// Create the window window = this.instantiationService.createInstance(CodeWindow, { state, extensionDevelopmentPath: configuration.extensionDevelopmentPath, isExtensionTestHost: !!configuration.extensionTestsPath });
它建立了一个CodeWindow的实例,这个类型在:vs/code/electron-main/window.ts中定义
这个类型的构造函数里调用了这个方法:
this.createBrowserWindow(config);
在这个方法里完成了窗口的建立:
// Create the browser window. this._win = new BrowserWindow(options);
至此:VSCode窗口建立出来了