上一篇文章:https://www.cnblogs.com/liulun/
(小廣告:我做的開源免費的,個人知識管理及自媒體營銷工具“想學嗎”:https://github.com/xland/xiangxuema)
我們在package.json裡能找到他的入口檔案;
"main": "./out/main",
electron是分主程式和渲染程式的;
渲染程式是主程式啟動的;
./out/main.js顯然這就是主程式的入口程式;
確實不假
但彆著急去分析這個檔案;
因為它是在out目錄下,明顯是什麼東西輸出出來的;
我們先打掃一遍src目錄下的東西;
發現了tsconfig.json
"outDir": "../out",
哈,這是typescript程式碼,編譯後輸出到./out/目錄下的;
那麼我們來看src下的main.js
分析程式碼最主要的就是目的明確,我們的目的是看看他的啟動邏輯(主視窗是怎麼開啟的)
無關的東西先不管,要不然很容易迷失...;
我們在main.js裡找electron的ready事件
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裡主要就是執行這個方法:
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)
loader([entrypoint], onLoad, onError);
loader是微軟自家的AMD模組載入開源專案:https://github.com/Microsoft/vscode-loader/
沒啥好說的,我們接著來看vs/code/electron-main/main.ts的程式碼,
發現它一開始就載入了一大堆模組,頭大!
先不管它載入的這些模組都是幹嘛的,我們看它本身的入口,程式碼拉到末尾,發現:
const code = new CodeMain();
code.main();
馬上去看這個模組的main函式;發現main函式對於我們唯一有用的就是:
this.startup(args);
這個函式啟動了一堆服務之後,就執行了:
const mainIpcServer = yield this.doStartup(logService, environmentService, lifecycleService, instantiationService, true);
和
return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup();
我們先看第一行,在doStartup裡,只有這行程式碼看起來有用:
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視窗建立出來了