Skip to content
微信公众号

VSCode启动流程

由于VSCode是基于Electron开发的,Electron的启动入口在package.json中,其中的 main 字段所表示的脚本为应用的启动脚本,它将会在主进程中执行。 vscode-start-one ./out/main.js显然这就是主进程的入口程序,但是main.js是在out文件夹下,很明显是编译输出出来的,然后找到src下tsconfig.json文件中有以下配置:

java
"outDir": "../out",

所以很明显是将src下代码编译后输出到out文件夹中。所以真实入口在src下main.js中,接下来只需从main.js文件分析即可。

在main.js中,我们可以看到下面一行引入

java
const app = require('electron').app;

electron.app负责管理Electron 应用程序的生命周期,运行在主进程中,然后找到 ready 监听事件

java
// Load our code once 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();
	}
});

这个ready监听表示,Electron 会在初始化后并准备,部分 API 在 ready 事件触发后才能使用。创建窗口也需要在ready后创建。最后这个函数中调用 onReady() 函数。

java
function onReady() {
	perf.mark('main:appReady');

	Promise.all([nodeCachedDataDir.ensureExists(), userDefinedLocale]).then(([cachedDataDir, locale]) => {
		if (locale && !nlsConfiguration) {
			nlsConfiguration = lp.getNLSConfiguration(product.commit, userDataPath, metaDataFile, locale);
		}

		if (!nlsConfiguration) {
			nlsConfiguration = Promise.resolve(undefined);
		}

		// First, we need to test a user defined locale. If it fails we try the app locale.
		// If that fails we fall back to English.
		nlsConfiguration.then(nlsConfig => {

			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');
				});
			};

			// We received a valid nlsConfig from a user defined locale
			if (nlsConfig) {
				startup(nlsConfig);
			}

			// Try to use the app locale. Please note that the app locale is only
			// valid after we have received the app ready event. This is why the
			// code is here.
			else {
				let appLocale = app.getLocale();
				if (!appLocale) {
					startup({ locale: 'en', availableLanguages: {} });
				} else {

					// See above the comment about the loader and case sensitiviness
					appLocale = appLocale.toLowerCase();

					lp.getNLSConfiguration(product.commit, userDataPath, metaDataFile, appLocale).then(nlsConfig => {
						if (!nlsConfig) {
							nlsConfig = { locale: appLocale, availableLanguages: {} };
						}

						startup(nlsConfig);
					});
				}
			}
		});
	}, console.error);
}

整个函数读取了用户语言设置,然后最终调用了 startup()

java
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');
    });
};

startup中主要是引入了 boostrap-amd ,这个bootstrap-amd引入了/vs/loader,并创建了一个loader。

java
const loader = require('./vs/loader');

loader是微软自家的AMD模块加载开源项目:https://github.com/Microsoft/vscode-loader/

然后通过loader加载 vs/code/electron-main/main 模块,这是 VSCode 真正的入口,然后在 vs/code/electron-main/main.ts 中可以看到定义了一个 CodeMain 类,然后初始化这个CodeMain类,并调用了 main 函数。

java
// src/vs/code/electron-main/main
class CodeMain {
	main(): void {
		...
		// Launch
		this.startup(args);
	}

	private async startup(args: ParsedArgs): Promise<void> {

		// We need to buffer the spdlog logs until we are sure
		// we are the only instance running, otherwise we'll have concurrent
		// log file access on Windows (https://github.com/Microsoft/vscode/issues/41218)
		const bufferLogService = new BufferLogService();

		const [instantiationService, instanceEnvironment] = this.createServices(args, bufferLogService);
		try {

			// Init services
			await instantiationService.invokeFunction(async accessor => {
				const environmentService = accessor.get(IEnvironmentService);
				const configurationService = accessor.get(IConfigurationService);
				const stateService = accessor.get(IStateService);

				try {
					await this.initServices(environmentService, configurationService as ConfigurationService, stateService as StateService);
				} catch (error) {

					// Show a dialog for errors that can be resolved by the user
					this.handleStartupDataDirError(environmentService, error);

					throw error;
				}
			});

			// Startup
			await instantiationService.invokeFunction(async accessor => {
				const environmentService = accessor.get(IEnvironmentService);
				const logService = accessor.get(ILogService);
				const lifecycleMainService = accessor.get(ILifecycleMainService);
				const configurationService = accessor.get(IConfigurationService);

				const mainIpcServer = await this.doStartup(logService, environmentService, lifecycleMainService, instantiationService, true);

				bufferLogService.logger = new SpdLogService('main', environmentService.logsPath, bufferLogService.getLevel());
				once(lifecycleMainService.onWillShutdown)(() => (configurationService as ConfigurationService).dispose());

				return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup();
			});
		} catch (error) {
			instantiationService.invokeFunction(this.quit, error);
		}
	}

	private createServices(args: ParsedArgs, bufferLogService: BufferLogService): [IInstantiationService, typeof process.env] {
		const services = new ServiceCollection();

		const environmentService = new EnvironmentService(args, process.execPath);
		const instanceEnvironment = this.patchEnvironment(environmentService); // Patch `process.env` with the instance's environment
		services.set(IEnvironmentService, environmentService);

		const logService = new MultiplexLogService([new ConsoleLogMainService(getLogLevel(environmentService)), bufferLogService]);
		process.once('exit', () => logService.dispose());
		services.set(ILogService, logService);

		services.set(IConfigurationService, new ConfigurationService(environmentService.settingsResource));
		services.set(ILifecycleMainService, new SyncDescriptor(LifecycleMainService));
		services.set(IStateService, new SyncDescriptor(StateService));
		services.set(IRequestService, new SyncDescriptor(RequestMainService));
		services.set(IThemeMainService, new SyncDescriptor(ThemeMainService));
		services.set(ISignService, new SyncDescriptor(SignService));

		return [new InstantiationService(services, true), instanceEnvironment];
	}
	...
}

// Main Startup
const code = new CodeMain();
code.main();

可以看到 main() 函数最终调用了 startup() 函数。

startup() 函数中,先调用了 this.createServices() 函数来创建依赖的Services。 Services(服务) 是 VSCode 中一系列可以被注入的公共模块,这些 Services 分别负责不同的功能,在这里创建了几个基本服务。除了这些基本服务,VSCode 内还包含了大量的服务,如 IModeService、ICodeEditorService、IPanelService 等,通过 VSCode 实现的「依赖注入」模式,可以在需要用到这些服务的地方以 Decorator 的方式做为构造函数参数声明依赖,会被自动注入到类中。关于服务的依赖注入,后面的章节会重点讲解。

js
private createServices(args: ParsedArgs, bufferLogService: BufferLogService): [IInstantiationService, typeof process.env] {
		const services = new ServiceCollection();

		const environmentService = new EnvironmentService(args, process.execPath);
		const instanceEnvironment = this.patchEnvironment(environmentService); // Patch `process.env` with the instance's environment
		// environmentService 一些基本配置,包括运行目录、用户数据目录、工作区缓存目录等
		services.set(IEnvironmentService, environmentService);

		const logService = new MultiplexLogService([new ConsoleLogMainService(getLogLevel(environmentService)), bufferLogService]);
		process.once('exit', () => logService.dispose());
		// logService 日志服务
		services.set(ILogService, logService);

		// ConfigurationService 配置项
		services.set(IConfigurationService, new ConfigurationService(environmentService.settingsResource));
		// LifecycleService 生命周期相关的一些方法
		services.set(ILifecycleMainService, new SyncDescriptor(LifecycleMainService));
		// StateService 持久化数据
		services.set(IStateService, new SyncDescriptor(StateService));
		// RequestService 请求服务
		services.set(IRequestService, new SyncDescriptor(RequestMainService));
		services.set(IThemeMainService, new SyncDescriptor(ThemeMainService));
		services.set(ISignService, new SyncDescriptor(SignService));

		return [new InstantiationService(services, true), instanceEnvironment];
	}

代码中可以看到 createServices() 最终实例化了一个 InstantiationService 实例并return回去,然后在 startup() 中调用 InstantiationService的createInstance方法并传参数CodeApplication,表示初始化CodeApplication实例,然后调用实例的 startup() 方法。

js
return instantiationService.createInstance(CodeApplication, mainIpcServer,instanceEnvironment).startup();

接下来我们去看CodeApplication中的startup方法。

js
//src/vs/code/electron-main/app.ts
export class CodeApplication extends Disposable {
	...
	async startup(): Promise<void> {
		this.logService.debug('Starting VS Code');
		this.logService.debug(`from: ${this.environmentService.appRoot}`);
		this.logService.debug('args:', this.environmentService.args);

		// Make sure we associate the program with the app user model id
		// This will help Windows to associate the running program with
		// any shortcut that is pinned to the taskbar and prevent showing
		// two icons in the taskbar for the same app.
		const win32AppUserModelId = product.win32AppUserModelId;
		if (isWindows && win32AppUserModelId) {
			app.setAppUserModelId(win32AppUserModelId);
		}

		// Fix native tabs on macOS 10.13
		// macOS enables a compatibility patch for any bundle ID beginning with
		// "com.microsoft.", which breaks native tabs for VS Code when using this
		// identifier (from the official build).
		// Explicitly opt out of the patch here before creating any windows.
		// See: https://github.com/Microsoft/vscode/issues/35361#issuecomment-399794085
		try {
			if (isMacintosh && this.configurationService.getValue<boolean>('window.nativeTabs') === true && !systemPreferences.getUserDefault('NSUseImprovedLayoutPass', 'boolean')) {
				systemPreferences.setUserDefault('NSUseImprovedLayoutPass', 'boolean', true as any);
			}
		} catch (error) {
			this.logService.error(error);
		}

		// Create Electron IPC Server
		const electronIpcServer = new ElectronIPCServer();

		// Resolve unique machine ID
		this.logService.trace('Resolving machine identifier...');
		const { machineId, trueMachineId } = await this.resolveMachineId();
		this.logService.trace(`Resolved machine identifier: ${machineId} (trueMachineId: ${trueMachineId})`);

		// Spawn shared process after the first window has opened and 3s have passed
		const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv);
		const sharedProcessClient = sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main'));
		this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => {
			this._register(new RunOnceScheduler(async () => {
				const userEnv = await getShellEnvironment(this.logService, this.environmentService);

				sharedProcess.spawn(userEnv);
			}, 3000)).schedule();
		});

		// Services
		const appInstantiationService = await this.createServices(machineId, trueMachineId, sharedProcess, sharedProcessClient);

		// Create driver
		if (this.environmentService.driverHandle) {
			const server = await serveDriver(electronIpcServer, this.environmentService.driverHandle!, this.environmentService, appInstantiationService);

			this.logService.info('Driver started at:', this.environmentService.driverHandle);
			this._register(server);
		}

		// Setup Auth Handler
		this._register(new ProxyAuthHandler());

		// Open Windows
		const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient));

		// Post Open Windows Tasks
		this.afterWindowOpen();

		// Tracing: Stop tracing after windows are ready if enabled
		if (this.environmentService.args.trace) {
			this.stopTracingEventually(windows);
		}
	}

	...

	private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise<Client<string>>): ICodeWindow[] {

		// Register more Main IPC services
		const launchMainService = accessor.get(ILaunchMainService);
		const launchChannel = createChannelReceiver(launchMainService, { disableMarshalling: true });
		this.mainIpcServer.registerChannel('launch', launchChannel);

		// Register more Electron IPC services
		const updateService = accessor.get(IUpdateService);
		const updateChannel = new UpdateChannel(updateService);
		electronIpcServer.registerChannel('update', updateChannel);

		const issueService = accessor.get(IIssueService);
		const issueChannel = createChannelReceiver(issueService);
		electronIpcServer.registerChannel('issue', issueChannel);

		const electronService = accessor.get(IElectronService);
		const electronChannel = createChannelReceiver(electronService);
		electronIpcServer.registerChannel('electron', electronChannel);
		sharedProcessClient.then(client => client.registerChannel('electron', electronChannel));

		const sharedProcessMainService = accessor.get(ISharedProcessMainService);
		const sharedProcessChannel = createChannelReceiver(sharedProcessMainService);
		electronIpcServer.registerChannel('sharedProcess', sharedProcessChannel);

		const workspacesService = accessor.get(IWorkspacesService);
		const workspacesChannel = createChannelReceiver(workspacesService);
		electronIpcServer.registerChannel('workspaces', workspacesChannel);

		const menubarService = accessor.get(IMenubarService);
		const menubarChannel = createChannelReceiver(menubarService);
		electronIpcServer.registerChannel('menubar', menubarChannel);

		const urlService = accessor.get(IURLService);
		const urlChannel = createChannelReceiver(urlService);
		electronIpcServer.registerChannel('url', urlChannel);

		const storageMainService = accessor.get(IStorageMainService);
		const storageChannel = this._register(new GlobalStorageDatabaseChannel(this.logService, storageMainService));
		electronIpcServer.registerChannel('storage', storageChannel);

		const loggerChannel = new LoggerChannel(accessor.get(ILogService));
		electronIpcServer.registerChannel('logger', loggerChannel);
		sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel));

		// ExtensionHost Debug broadcast service
		electronIpcServer.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ExtensionHostDebugBroadcastChannel());

		// Signal phase: ready (services set)
		this.lifecycleMainService.phase = LifecycleMainPhase.Ready;

		// Propagate to clients
		const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService);
		this.dialogMainService = accessor.get(IDialogMainService);

		// Create a URL handler to open file URIs in the active window
		const environmentService = accessor.get(IEnvironmentService);
		urlService.registerHandler({
			async handleURL(uri: URI, options?: IOpenURLOptions): Promise<boolean> {

				// Catch file URLs
				if (uri.authority === Schemas.file && !!uri.path) {
					const cli = assign(Object.create(null), environmentService.args);
					const urisToOpen = [{ fileUri: URI.file(uri.fsPath) }];

					windowsMainService.open({ context: OpenContext.API, cli, urisToOpen, gotoLineMode: true });

					return true;
				}

				return false;
			}
		});

		// Create a URL handler which forwards to the last active window
		const activeWindowManager = new ActiveWindowManager(electronService);
		const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id));
		const urlHandlerRouter = new URLHandlerRouter(activeWindowRouter);
		const urlHandlerChannel = electronIpcServer.getChannel('urlHandler', urlHandlerRouter);
		const multiplexURLHandler = new URLHandlerChannelClient(urlHandlerChannel);

		// On Mac, Code can be running without any open windows, so we must create a window to handle urls,
		// if there is none
		if (isMacintosh) {
			urlService.registerHandler({
				async handleURL(uri: URI, options?: IOpenURLOptions): Promise<boolean> {
					if (windowsMainService.getWindowCount() === 0) {
						const cli = { ...environmentService.args };
						const [window] = windowsMainService.open({ context: OpenContext.API, cli, forceEmpty: true, gotoLineMode: true });

						await window.ready();

						return urlService.open(uri);
					}

					return false;
				}
			});
		}

		// Register the multiple URL handler
		urlService.registerHandler(multiplexURLHandler);

		// Watch Electron URLs and forward them to the UrlService
		const args = this.environmentService.args;
		const urls = args['open-url'] ? args._urls : [];
		const urlListener = new ElectronURLListener(urls || [], urlService, windowsMainService);
		this._register(urlListener);

		// Open our first window
		const macOpenFiles: string[] = (<any>global).macOpenFiles;
		const context = !!process.env['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP;
		const hasCliArgs = args._.length;
		const hasFolderURIs = !!args['folder-uri'];
		const hasFileURIs = !!args['file-uri'];
		const noRecentEntry = args['skip-add-to-recently-opened'] === true;
		const waitMarkerFileURI = args.wait && args.waitMarkerFilePath ? URI.file(args.waitMarkerFilePath) : undefined;

		// new window if "-n" was used without paths
		if (args['new-window'] && !hasCliArgs && !hasFolderURIs && !hasFileURIs) {
			return windowsMainService.open({
				context,
				cli: args,
				forceNewWindow: true,
				forceEmpty: true,
				noRecentEntry,
				waitMarkerFileURI,
				initialStartup: true
			});
		}

		// mac: open-file event received on startup
		if (macOpenFiles && macOpenFiles.length && !hasCliArgs && !hasFolderURIs && !hasFileURIs) {
			return windowsMainService.open({
				context: OpenContext.DOCK,
				cli: args,
				urisToOpen: macOpenFiles.map(file => this.getWindowOpenableFromPathSync(file)),
				noRecentEntry,
				waitMarkerFileURI,
				gotoLineMode: false,
				initialStartup: true
			});
		}

		// default: read paths from cli
		return windowsMainService.open({
			context,
			cli: args,
			forceNewWindow: args['new-window'] || (!hasCliArgs && args['unity-launch']),
			diffMode: args.diff,
			noRecentEntry,
			waitMarkerFileURI,
			gotoLineMode: args.goto,
			initialStartup: true
		});
	}
	...
}

在CodeApplication.startup 中首先会启动 SharedProcess 共享进程,同时也创建了一些窗口相关的服务,包括 WindowsManager、WindowsService、MenubarService 等,负责窗口、多窗口管理及菜单等功能。然后调用 openFirstWindow 方法来开启窗口。

在openFirstWindow中,先创建一系列 Electron 的 IPC 频道,用于主进程和渲染进程间通信,其中 window 和 logLevel 频道还会被注册到 sharedProcessClient ,sharedProcessClient 是主进程与共享进程(SharedProcess)进行通信的 client,之后根据 environmentService 提供的相关参数(file_uri、folder_uri)调用了 windowsMainService.open 方法。

windowsMainService是WindowsManager实例化的服务,而WindowsManager是多窗体管理类(src/vs/code/electron-main/windows.ts)。接下来我们看windowsMainService.open 方法,可以看到其调用了doOpen方法。

js
open(openConfig: IOpenConfiguration): ICodeWindow[] {
		...

		// Open based on config
		const usedWindows = this.doOpen(openConfig, workspacesToOpen, foldersToOpen, 
		...
	}

doOpen方法最终调用了openInBrowserWindow方法。

js
private doOpen(
		openConfig: IOpenConfiguration,
		workspacesToOpen: IWorkspacePathToOpen[],
		foldersToOpen: IFolderPathToOpen[],
		emptyToRestore: IEmptyWindowBackupInfo[],
		emptyToOpen: number,
		fileInputs: IFileInputs | undefined,
		foldersToAdd: IFolderPathToOpen[]
	) {
		const usedWindows: ICodeWindow[] = [];

		...

		// Handle empty to open (only if no other window opened)
		if (usedWindows.length === 0 || fileInputs) {
			if (fileInputs && !emptyToOpen) {
				emptyToOpen++;
			}

			const remoteAuthority = fileInputs ? fileInputs.remoteAuthority : (openConfig.cli && openConfig.cli.remote || undefined);

			for (let i = 0; i < emptyToOpen; i++) {
				usedWindows.push(this.openInBrowserWindow({
					userEnv: openConfig.userEnv,
					cli: openConfig.cli,
					initialStartup: openConfig.initialStartup,
					remoteAuthority,
					forceNewWindow: openFolderInNewWindow,
					forceNewTabbedWindow: openConfig.forceNewTabbedWindow,
					fileInputs
				}));

				// Reset these because we handled them
				fileInputs = undefined;
				openFolderInNewWindow = true; // any other window to open must open in new window then
			}
		}

		return arrays.distinct(usedWindows);
	}

在openInBrowserWindow中,创建一个CodeWindow实例并返回,并且还调用了doOpenInBrowserWindow这个方法,这个方法看下文介绍。

js
private openInBrowserWindow(options: IOpenBrowserWindowOptions): ICodeWindow {

		...

		// Create the window
		window = this.instantiationService.createInstance(CodeWindow, {
			state,
			extensionDevelopmentPath: configuration.extensionDevelopmentPath,
			isExtensionTestHost: !!configuration.extensionTestsPath
		});
		...

		// If the window was already loaded, make sure to unload it
		// first and only load the new configuration if that was
		// not vetoed
		if (window.isReady) {
			this.lifecycleMainService.unload(window, UnloadReason.LOAD).then(veto => {
				if (!veto) {
					this.doOpenInBrowserWindow(window!, configuration, options);
				}
			});
		} else {
			this.doOpenInBrowserWindow(window, configuration, options);
		}

		return window;
	}

接下来我们找到CodeWindow定义在src/vs/code/electron-main/window.ts中,在CodeWindow的构造函数中调用了createBrowserWindow方法,然后在createBrowserWindow方法中看到实例化了一个BrowserWindow,这是Electron中浏览器窗口的定义。

js
//src/vs/code/electron-main/window.ts
export class CodeWindow extends Disposable implements ICodeWindow {

	...

	constructor(
		config: IWindowCreationOptions,
		@ILogService private readonly logService: ILogService,
		@IEnvironmentService private readonly environmentService: IEnvironmentService,
		@IFileService private readonly fileService: IFileService,
		@IConfigurationService private readonly configurationService: IConfigurationService,
		@IThemeMainService private readonly themeMainService: IThemeMainService,
		@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
		@IBackupMainService private readonly backupMainService: IBackupMainService,
	) {
		super();

		this.touchBarGroups = [];
		this._lastFocusTime = -1;
		this._readyState = ReadyState.NONE;
		this.whenReadyCallbacks = [];

		// create browser window
		this.createBrowserWindow(config);

		// respect configured menu bar visibility
		this.onConfigurationUpdated();

		// macOS: touch bar support
		this.createTouchBar();

		// Request handling
		this.handleMarketplaceRequests();

		// Eventing
		this.registerListeners();
	}

	private createBrowserWindow(config: IWindowCreationOptions): void {

		...
		// Create the browser window.
		this._win = new BrowserWindow(options);
		...
	}
}

现在窗口有了,那么什么时候加载页面呢?刚刚我们在上文提到,在openInBrowserWindow中,创建一个CodeWindow实例并返回,并且还调用了doOpenInBrowserWindow这个方法,那么我们看一下这个方法的定义。

js
private doOpenInBrowserWindow(window: ICodeWindow, configuration: IWindowConfiguration, options: IOpenBrowserWindowOptions): void {
		...
		// Load it
		window.load(configuration);
		...
	}

这个方法有调用CodeWindow的load方法,然后看一下load方法的定义。会看到调用了this._win.loadURL,这个this._win就是CodeWindow创建的BrowserWindow窗口,这就找到了窗口加载的URL时机。

js
load(config: IWindowConfiguration, isReload?: boolean, disableExtensions?: boolean): void {
	...
	// Load URL
	perf.mark('main:loadWindow');
	this._win.loadURL(this.getUrl(configuration));
	...
}

然后看一下getUrl方法的定义,最终返回的configUrl是调用doGetUrl获取的。

js
private getUrl(windowConfiguration: IWindowConfiguration): string {

		...
		let configUrl = this.doGetUrl(config);
		...

		return configUrl;
	}

然后看一下doGetUrl方法,可以看到返回的Url路径为vs/code/electron-browser/workbench/workbench.html。

js
private doGetUrl(config: object): string {
	return `${require.toUrl('vs/code/electron-browser/workbench/workbench.html')}?config=${encodeURIComponent(JSON.stringify(config))}`;
}

这是整个 Workbench 的入口,HTML出现了,主进程的使命完成,渲染进程登场。

js
//src/vs/code/electron-browser/workbench/workbench.html
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data: blob: vscode-remote-resource:; media-src 'none'; frame-src 'self' https://*.vscode-webview-test.com; object-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' https:; font-src 'self' https: vscode-remote-resource:;">
	</head>
	<body class="vs-dark" aria-label="">
	</body>

	<!-- Startup via workbench.js -->
	<script src="workbench.js"></script>
</html>

workbench.html中加载了workbench.js文件,这个文件负责加载真正的 Workbench 模块并调用其 main 方法初始化主界面。

js
// src/vs/code/electron-browser/workbench/workbench.js
const bootstrapWindow = require('../../../../bootstrap-window');

// Setup shell environment
process['lazyEnv'] = getLazyEnv();

// Load workbench main JS, CSS and NLS all in parallel. This is an
// optimization to prevent a waterfall of loading to happen, because
// we know for a fact that workbench.desktop.main will depend on
// the related CSS and NLS counterparts.
bootstrapWindow.load([
	'vs/workbench/workbench.desktop.main',
	'vs/nls!vs/workbench/workbench.desktop.main',
	'vs/css!vs/workbench/workbench.desktop.main'
],
	function (workbench, configuration) {
		perf.mark('didLoadWorkbenchMain');

		return process['lazyEnv'].then(function () {
			perf.mark('main/startup');

			// @ts-ignore
			//加载 Workbench 并初始化主界面
			return require('vs/workbench/electron-browser/desktop.main').main(configuration);
		});
	}, {
		removeDeveloperKeybindingsAfterLoad: true,
		canModifyDOM: function (windowConfig) {
			showPartsSplash(windowConfig);
		},
		beforeLoaderConfig: function (windowConfig, loaderConfig) {
			loaderConfig.recordStats = true;
		},
		beforeRequire: function () {
			perf.mark('willLoadWorkbenchMain');
		}
	});

我们可以看到加载了vs/workbench/electron-browser/desktop.main模块,并调用了模块的main方法。main方法中实例化了一个DesktopMain,并调用了DesktopMain的open方法。

js
class DesktopMain extends Disposable {

	async open(): Promise<void> {
		...

		// Create Workbench
		const workbench = new Workbench(document.body, services.serviceCollection, services.logService);

		// Listeners
		this.registerListeners(workbench, services.storageService);

		// Startup
		const instantiationService = workbench.startup();
		...
	}
	...
}

export function main(configuration: IWindowConfiguration): Promise<void> {
	const renderer = new DesktopMain(configuration);
	return renderer.open();
}

我们看到DesktopMain的open方法中实例化了Workbench类,并调用了Workbench的startup方法。接下来我们看一下这个Workbench类。

js
export class Workbench extends Layout {

	...
	startup(): IInstantiationService {
		try {

			// Configure emitter leak warning threshold
			setGlobalLeakWarningThreshold(175);

			// ARIA
			setARIAContainer(document.body);

			// Services
			const instantiationService = this.initServices(this.serviceCollection);

			instantiationService.invokeFunction(async accessor => {
				const lifecycleService = accessor.get(ILifecycleService);
				const storageService = accessor.get(IStorageService);
				const configurationService = accessor.get(IConfigurationService);

				// Layout
				this.initLayout(accessor);

				// Registries
				this.startRegistries(accessor);

				// Context Keys
				this._register(instantiationService.createInstance(WorkbenchContextKeysHandler));

				// Register Listeners
				this.registerListeners(lifecycleService, storageService, configurationService);

				// Render Workbench
				this.renderWorkbench(instantiationService, accessor.get(INotificationService) as NotificationService, storageService, configurationService);

				// Workbench Layout
				this.createWorkbenchLayout(instantiationService);

				// Layout
				this.layout();

				// Restore
				try {
					await this.restoreWorkbench(accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IViewletService), accessor.get(IPanelService), accessor.get(ILogService), lifecycleService);
				} catch (error) {
					onUnexpectedError(error);
				}
			});

			return instantiationService;
		} catch (error) {
			onUnexpectedError(error);

			throw error; // rethrow because this is a critical issue we cannot handle properly here
		}
	}
	...
}

我们可以看到Workbench继承Layout布局类,在 workbench.startup 方法中构建主界面布局、创建全局事件监听以及实例化一些依赖的服务,全部完成后会还原之前打开的编辑器,整个 Workbench 加载完成。

所以前文中的大量代码只是为这里最终创建主界面做铺垫,Workbench 模块主要代码都在 vs/workbench 目录下,主要负责界面元素的创建和具体业务功能的实现。

至此,从启动到加载到html,再到构建主界面布局,整个流程很清晰。

本站总访问量次,本站总访客数人次
Released under the MIT License.