上一篇文章: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窗口建立出來了