主要启动时序分析
:::info
ℹ️ 这里的主要分析基于 electron-browser
即使用了 ElectronRenderProcess
的方式渲染的 VSCode 模式。
简述:
code/elctron-main/main
入口,初始化各类基础服务,并启动主进程vs/code/app.ts
接下来实例化CodeApplication
vs/code/electron-browser/sharedProcess/sharedProcess.html
加载 SharedProcess.html 启动共享进程vs/code/electron-sandbox/workbench/workench.html
打开 Workbench.html(工作区)于 RenderProcess 中workbench.html
启动workbench.js
最终初始化 UI、插件等各类系统,完成整体加载
Main.js
主要时序分析 | CodeSample |
---|---|
src/main.js 中,做了 Electron 引入和初步设置,随后,通过异步载入的方式,加载核心模块,即: electron-main/main |
```typescript |
require(‘./bootstrap-amd’).load(‘vs/code/electron-main/main’, () => { perf.mark(‘code/didLoadMainBundle’); });
|
| `vs/code/electron-main/main` 中,启动主要做了下面的逻辑:我们使用简化类来表示结构和含义(100 行代码简化版本),简而言之就是:<br /><br />- 创建和注册初始化所需要的必要服务(`createServices`)<br />- 初始化这些服务(`initServices`)<br />- 最后调用 `instantiationService` 服务下的 `createInstance()` 后启动 `startup()` 整个 VSCode 主进程<br /> | ```typescript
class CodeMain {
main(): void {
this.startup();
}
private async startup(): Promise<void> {
// Create services
const [
instantiationService,
instanceEnvironment,
environmentMainService,
configurationService,
stateMainService] = this.createServices();
await this.initServices(environmentMainService, configurationService, stateMainService);
// Startup
await instantiationService.invokeFunction(async accessor => {
return instantiationService.createInstance(CodeApplication, mainProcessNodeIpcServer, instanceEnvironment).startup();
});
}
private createServices() {
const services = new ServiceCollection();
// Product
const productService = { _serviceBrand: undefined, ...product };
services.set(IProductService, productService);
// Environment
const environmentMainService = new EnvironmentMainService(this.resolveArgs(), productService);
// Patch `process.env` with the instance's environment
const instanceEnvironment = this.patchEnvironment(environmentMainService);
services.set(IEnvironmentMainService, environmentMainService);
// Files
const fileService = new FileService(logService);
services.set(IFileService, fileService);
const diskFileSystemProvider = new DiskFileSystemProvider(logService);
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
// Configuration
const configurationService = new ConfigurationService(environmentMainService.settingsResource, fileService);
services.set(IConfigurationService, configurationService);
// Lifecycle
services.set(ILifecycleMainService, new SyncDescriptor(LifecycleMainService));
// State
const stateMainService = new StateMainService(environmentMainService, logService, fileService);
services.set(IStateMainService, stateMainService);
// Request
services.set(IRequestService, new SyncDescriptor(RequestMainService));
// Themes
services.set(IThemeMainService, new SyncDescriptor(ThemeMainService));
// Signing
services.set(ISignService, new SyncDescriptor(SignService));
// Tunnel
services.set(ITunnelService, new SyncDescriptor(TunnelService));
// Protocol
services.set(IProtocolMainService, new SyncDescriptor(ProtocolMainService));
return [new InstantiationService(services, true), ...otherServices];
}
private initServices(services): Promise<unknown> {
return Promises.settled<unknown>([
// Configuration service
configurationService.initialize(),
// State service
stateMainService.init()
]);
}
}
// Main Startup
const code = new CodeMain();
code.main();
|
接下来我们分析几个关键的服务:
服务 | 代码结构 Sample |
---|---|
LifeCycleMainService 生命周期管理和激活相关的 API 设计。 |
```typescript |
export interface ILifecycleMainService { /**
* Will be true if the program was restarted (e.g. due to explicit request or update).
*/
readonly wasRestarted: boolean;
/**
* Will be true if the program was requested to quit.
*/
readonly quitRequested: boolean;
/**
* A flag indicating in what phase of the lifecycle we currently are.
*/
phase: LifecycleMainPhase;
/**
* An event that fires when the application is about to shutdown before any window is closed.
* The shutdown can still be prevented by any window that vetos this event.
*/
readonly onBeforeShutdown: Event<void>;
/**
* An event that fires after the onBeforeShutdown event has been fired and after no window has
* vetoed the shutdown sequence. At this point listeners are ensured that the application will
* quit without veto.
*/
readonly onWillShutdown: Event<ShutdownEvent>;
/**
* An event that fires when a window is loading. This can either be a window opening for the
* first time or a window reloading or changing to another URL.
*/
readonly onWillLoadWindow: Event<IWindowLoadEvent>;
/**
* An event that fires before a window is about to unload. Listeners can veto this event to prevent
* the window from unloading.
*/
readonly onBeforeUnloadWindow: Event<IWindowUnloadEvent>;
/**
* An event that fires before a window closes. This event is fired after any veto has been dealt
* with so that listeners know for sure that the window will close without veto.
*/
readonly onBeforeCloseWindow: Event<ICodeWindow>;
/**
* Make a `ICodeWindow` known to the lifecycle main service.
*/
registerWindow(window: ICodeWindow): void;
/**
* Reload a window. All lifecycle event handlers are triggered.
*/
reload(window: ICodeWindow, cli?: NativeParsedArgs): Promise<void>;
/**
* Unload a window for the provided reason. All lifecycle event handlers are triggered.
*/
unload(window: ICodeWindow, reason: UnloadReason): Promise<boolean /* veto */>;
/**
* Restart the application with optional arguments (CLI). All lifecycle event handlers are triggered.
*/
relaunch(options?: { addArgs?: string[], removeArgs?: string[] }): Promise<void>;
/**
* Shutdown the application normally. All lifecycle event handlers are triggered.
*/
quit(willRestart?: boolean): Promise<boolean /* veto */>;
/**
* Forcefully shutdown the application. No livecycle event handlers are triggered.
*/
kill(code?: number): Promise<void>;
/**
* Returns a promise that resolves when a certain lifecycle phase
* has started.
*/
when(phase: LifecycleMainPhase): Promise<void>;
}
|
| StateMainService<br /><br />Main 程序下的状态管理服务。<br />值得一提的是,它是基于 `FileStore` 服务进行 IO 操作的。 | ```typescript
export interface IStateMainService {
getItem<T>(key: string, defaultValue: T): T;
getItem<T>(key: string, defaultValue?: T): T | undefined;
setItem(key: string, data?: object | string | number | boolean | undefined | null): void;
setItems(items: readonly { key: string, data?: object | string | number | boolean | undefined | null }[]): void;
removeItem(key: string): void;
close(): Promise<void>;
}
|
| TunnelService
通信通道管理服务。这要是实现本地或者远程的网络通信服务。对 Tunnel 的理解:https://www.cloudflare.com/learning/network-layer/what-is-tunneling/
| ```typescript
export interface ITunnel {
remoteAddress: { port: number; host: string };
/**
* The complete local address(ex. localhost:1234)
*/
localAddress: string;
/**
* @deprecated Use privacy instead
*/
public?: boolean;
privacy?: string;
protocol?: string;
/**
* Implementers of Tunnel should fire onDidDispose when dispose is called.
*/
onDidDispose: Event<void>;
dispose(): Promise<void> | void;
}
export interface ITunnelService { readonly _serviceBrand: undefined;
readonly tunnels: Promise<readonly RemoteTunnel[]>;
readonly canChangePrivacy: boolean;
readonly privacyOptions: TunnelPrivacy[];
readonly onTunnelOpened: Event<RemoteTunnel>;
readonly onTunnelClosed: Event<{ host: string; port: number }>;
readonly canElevate: boolean;
readonly hasTunnelProvider: boolean;
readonly onAddedTunnelProvider: Event<void>;
canTunnel(uri: URI): boolean;
openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number, elevateIfNeeded?: boolean, privacy?: string, protocol?: string): Promise<RemoteTunnel | undefined> | undefined;
closeTunnel(remoteHost: string, remotePort: number): Promise<void>;
setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable;
setTunnelFeatures(features: TunnelProviderFeatures): void;
}
|
| ProtocolService<br /><br />解析 IPC 通信协议,管理这些资源。 | ```typescript
export interface IIPCObjectUrl<T> extends IDisposable {
/**
* A `URI` that a renderer can use to retrieve the
* object via `ipcRenderer.invoke(resource.toString())`
*/
resource: URI;
/**
* Allows to update the value of the object after it
* has been created.
*
* @param obj the object to make accessible to the
* renderer.
*/
update(obj: T): void;
}
export interface IProtocolMainService {
/**
* Allows to make an object accessible to a renderer
* via `ipcRenderer.invoke(resource.toString())`.
*/
createIPCObjectUrl<T>(): IIPCObjectUrl<T>;
/**
* Adds a `URI` as root to the list of allowed
* resources for file access.
*
* @param root the URI to allow for file access
*/
addValidFileRoot(root: URI): IDisposable;
}
|
| InstantiationService
而 createInsance
为关键方法,大致执行时序如下:
- 递归获取解析依赖
- 由源代码分析可以看到 VSCode 的 DI 系统将依赖和 TARGET 埋入到了 ClassConstructor 中
- 将依赖析出注入到对象的 Constructor 构造参数中
| ```typescript
// 专业「析出实例」,DI Resolver,类似 Angular 框架中的 Injector,用于 DI 过程中的实例析出
export interface IInstantiationService {
/**
* Synchronously creates an instance that is denoted by
* the descriptor
*/
createInstance<Ctor extends new (...args: any[]) => any, R extends InstanceType<Ctor>>(t: Ctor, ...args: GetLeadingNonServiceArgs<ConstructorParameters<Ctor>>): R;
/**
*
*/
invokeFunction<R, TS extends any[] = []>(fn: (accessor: ServicesAccessor, ...args: TS) => R, ...args: TS): R;
/**
* Creates a child of this service which inherts all current services
* and adds/overwrites the given services
*/
createChild(services: ServiceCollection): IInstantiationService;
}
export const DI_TARGET = ‘$di$target’;
export const DI_DEPENDENCIES = ‘$di$dependencies’;
export function getServiceDependencies(ctor: any): { id: ServiceIdentifier
|
那么接下来,我们继续看运行时序:进入 CodeApplication 之中开始执行。
```typescript
return instantiationService.createInstance(CodeApplication, mainProcessNodeIpcServer, instanceEnvironment).startup();
App.ts
关键调用 | 代码分析 |
---|---|
vs/code/app.ts 初始化 CodeApplication 实例- configureSession 配置 Electron Session 相关- registerListeners 注册主要的事件监听回调函数- 后续执行 startUp() 初始化程序- 启动 ElectronIPCServer 用于进程通信- 调用 setupSharedProcess 启动共享的进程,该过程会创建 SharedProcess 实例,并在实例中加载 vs/code/electron-browser/sharedProcess/sharedProcess.html 来实现加载 SharedProcessMain.ts - (Async)这个文件将进行 Extension 等依赖 SharedProcess 的场景- 初始化 CodeApplication 所依赖的服务- 初始化各个 Server 之间的事件 Channel 用于消息分发- 调用 openFirstWindow() 方法,真正意义上打开 Electron 窗口- 进入 WindowMainService 调用流程中 |
```typescript |
/**
- The main VS Code application. There will only ever be one instance,
even if the user starts many instances (e.g. from the command line). */ export class CodeApplication extends Disposable { constructor() {
super(); // 配置 Electron Session this.configureSession(); // 注册 Electron / process / lifeCycleMainService 等相关事件 this.registerListeners();
}
private registerListeners(): void {
process.on('uncaughtException', error => onUnexpectedError(error)); process.on('unhandledRejection', (reason: unknown) => onUnexpectedError(reason)); // Dispose on shutdown this.lifecycleMainService.onWillShutdown(() => this.dispose()); // Contextmenu via IPC support registerContextMenuListener(); app.on('new-window-for-tab', () => { this.windowsMainService?.openEmptyWindow({ context: OpenContext.DESKTOP }); }); ipcMain.on('vscode:toggleDevTools', event => event.sender.toggleDevTools()); ipcMain.on('vscode:openDevTools', event => event.sender.openDevTools()); ipcMain.on('vscode:reloadWindow', event => event.sender.reload());
}
async startup(): Promise
{ // Main process server (electron IPC based) const mainProcessElectronServer = new ElectronIPCServer(); const machineId = await this.resolveMachineId(); // Shared process const { sharedProcess, sharedProcessReady, sharedProcessClient } = this.setupSharedProcess(machineId); // Services const appInstantiationService = await this.initServices(machineId, sharedProcess, sharedProcessReady); // Create driver if (this.environmentMainService.driverHandle) { const server = await serveDriver(mainProcessElectronServer, this.environmentMainService.driverHandle, this.environmentMainService, appInstantiationService); this._register(server); } // Setup Auth Handler this._register(appInstantiationService.createInstance(ProxyAuthHandler)); // Init Channels appInstantiationService.invokeFunction(accessor => this.initChannels(accessor, mainProcessElectronServer, sharedProcessClient)); // Open Windows const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, mainProcessElectronServer)); // Post Open Windows Tasks appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor, sharedProcess)); // Tracing: Stop tracing after windows are ready if enabled if (this.environmentMainService.args.trace) { appInstantiationService.invokeFunction(accessor => this.stopTracingEventually(accessor, windows)); }
}
private setupSharedProcess(machineId: string): { sharedProcess: SharedProcess, sharedProcessReady: Promise
, sharedProcessClient: Promise } { const sharedProcess = this._register(this.mainInstantiationService.createInstance(SharedProcess, machineId, this.userEnv)); const sharedProcessClient = (async () => { const port = await sharedProcess.connect(); return new MessagePortClient(port, 'main'); })(); const sharedProcessReady = (async () => { await sharedProcess.whenReady(); return sharedProcessClient; })(); return { sharedProcess, sharedProcessReady, sharedProcessClient };
}
private async initServices(machineId: string, sharedProcess: SharedProcess, sharedProcessReady: Promise
): Promise { const services = new ServiceCollection(); // Windows services.set(IWindowsMainService, new SyncDescriptor(WindowsMainService, [machineId, this.userEnv])); // Dialogs services.set(IDialogMainService, new SyncDescriptor(DialogMainService)); // Launch services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService)); // Native Host services.set(INativeHostMainService, new SyncDescriptor(NativeHostMainService, [sharedProcess])); // Webview Manager services.set(IWebviewManagerService, new SyncDescriptor(WebviewMainService)); // Workspaces services.set(IWorkspacesService, new SyncDescriptor(WorkspacesMainService)); services.set(IWorkspacesManagementMainService, new SyncDescriptor(WorkspacesManagementMainService)); services.set(IWorkspacesHistoryMainService, new SyncDescriptor(WorkspacesHistoryMainService)); // Menubar services.set(IMenubarMainService, new SyncDescriptor(MenubarMainService)); // Extension URL Trust services.set(IExtensionUrlTrustService, new SyncDescriptor(ExtensionUrlTrustService)); // Storage services.set(IStorageMainService, new SyncDescriptor(StorageMainService)); // Init services that require it await backupMainService.initialize(); return this.mainInstantiationService.createChild(services);
}
private initChannels(accessor: ServicesAccessor, mainProcessElectronServer: ElectronIPCServer, sharedProcessClient: Promise
): void { // Launch: this one is explicitly registered to the node.js // server because when a second instance starts up, that is // the only possible connection between the first and the // second instance. Electron IPC does not work across apps. const launchChannel = ProxyChannel.fromService(accessor.get(ILaunchMainService), { disableMarshalling: true }); this.mainProcessNodeIpcServer.registerChannel('launch', launchChannel); // ...
}
private openFirstWindow(accessor: ServicesAccessor, mainProcessElectronServer: ElectronIPCServer): ICodeWindow[] {
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); // check for a pending window to open from URI // e.g. when running code with --open-uri from // a protocol handler if (pendingWindowOpenablesFromProtocolLinks.length > 0) { return windowsMainService.open({ context, cli: args, urisToOpen: pendingWindowOpenablesFromProtocolLinks, gotoLineMode: true, initialStartup: true // remoteAuthority: will be determined based on pendingWindowOpenablesFromProtocolLinks }); } // ...
} }
| | `vs/platform/sharedProcess/electron-main/sharedProcess.ts`<br /><br />在 SharedProcess 服务里面提供了对共享进程的管理机制,里面提供了和 MainProcess 通信的 IPC 服务。<br /> | ```typescript // Load with config this.window.loadURL(FileAccess.asBrowserUri('vs/code/electron-browser/sharedProcess/sharedProcess.html', require).toString(true));
| |
vs/code/electron-browser/sharedProcess.html
-bootstrap.js
通用启动脚本
-vs/loader.js
Loader AMD
-bootstrap-window.js
初始化 Window 上相关的变量和实例,注入window.vscode
,初始化 window 变量相关的配置
-sharedProcess.js
| ```html<!DOCTYPE html> Shared Process
- 创建 SharedProcessMain 实例,然后打开共享进程
- initServices() 初始化相关服务
- initChannels() 创建 IPC 通道
- 注册服务实例
- 至此完成 `electron-browser` 和 Main 相关联的共享进程的创建
| ```typescript export async function main(configuration: ISharedProcessConfiguration): Promise
- 在完成对 CodeWindow 的实例化之后
- 调用了 `CodeWindow.load()` 完成 RenderProcess 的启用
| 实现主要窗体的加载(非 Code 和 UI 部分),主要是服务和逻辑的处理,在其 `doOpen()` 函数中,会使用 `CodeWindow` 创建实例。最终实现对 Window UI 层的加载。```typescript const createdWindow = window = this.instantiationService.createInstance(CodeWindow, { state, extensionDevelopmentPath: configuration.extensionDevelopmentPath, isExtensionTestHost: !!configuration.extensionTestsPath }); ``` | | `vs/platform/windows/electron-main/window.ts`
- `preload.js` 异步调用,定义了 mainProcess 通信的方法,也就是所谓的 electron-browser <-> electron-main
- Preload 在 Electron API 中是在加载 BrowserWindow 前的前置脚本加载
- preload.js 封装了 Electron 和 Browser 的差异,使之让 VSCodeBrowserWindow 可以和 Main 通信
- 这里解决了 MainProcess 和 RenderProcess 的通信问题
- 做一系列的 UI 配置,即修改 `options` 变量中各类属性值
- 使用 Electron 库提供的 `BrowserWindow` 类创建 `BrowserWindow` 实例
| CodeWindow 主要是对 VSCode Window 的 UI 层进行渲染。其主要用于渲染 BrowserWindow。👈 这将属于渲染进程了,而非 MainProcess。具体启用渲染进程的机制:```typescript const options: BrowserWindowConstructorOptions = { width: this.windowState.width, height: this.windowState.height, x: this.windowState.x, y: this.windowState.y, backgroundColor: this.themeMainService.getBackgroundColor(), minWidth: WindowMinimumSize.WIDTH, minHeight: WindowMinimumSize.HEIGHT, show: !isFullscreenOrMaximized, title: this.productService.nameLong, webPreferences: { // 看到这一段对 Preload.js 的异步调用 /** * A minimal set of methods exposed from Electron's `ipcRenderer` * to support communication to main process. * * @typedef {import('../electron-sandbox/electronTypes').IpcRenderer} IpcRenderer * @typedef {import('electron').IpcRendererEvent} IpcRendererEvent * * @type {IpcRenderer} */ preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath, // ... } }; ``` ```typescript // Create the browser window this._win = new BrowserWindow(options); // Open devtools if instructed from command line args if (this.environmentMainService.args['open-devtools'] === true) { this._win.webContents.openDevTools(); } // 甚至还好心支持了 TouchBar 😁 // macOS: touch bar support this.createTouchBar(); ``` | | `CodeWindow.load` 简化版本方法理解:
- 如果当前启动环境是 Sandbox 即加载 `electron-sandbox/workbench/workench.html`
- 反之则加载 `renderProcess` API 下的 `workbench.html`
| ```typescript // Load URL 启动 RenderProcess this._win.loadURL(FileAccess.asBrowserUri(this.environmentMainService.sandbox ? 'vs/code/electron-sandbox/workbench/workbench.html' : 'vs/code/electron-browser/workbench/workbench.html', require ).toString(true)); ``` | | 这里以 renderProcess(最主要的)`workbench.html` 为例:
可以看到,在 HTML 文件中:
- 预加载了 loader.js (AMD Loader)
- 打包之后的 AMD 资源路径存储在了 out 目录下
- 进行 AMD loader 配置
- 启动 workbench.js 做 UI 渲染
- 加载 `vs/workbench/workbench.desktop.main` 的主要服务
| ```html
<!-- Startup via workbench.js -->
<script src="workbench.js"></script>
|
| `workbench.js` 中:<br /><br />- 加载 `vs/workbench/workbench.desktop.main`<br />- 加载 nls 资源(多语种资源)<br />- 加载 css 资源<br /> | ```typescript
// 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 (_, configuration) {
// Mark start of workbench
performance.mark('code/didLoadWorkbenchMain');
// 执行 Workbench 的初始化入口函数 main
return require('vs/workbench/electron-browser/desktop.main').main(configuration);
},
{
// ... load configs
}
|
| workbench.desktop.main
中继续加载 Workbench 所需要的服务和 UI 渲染相关的模块。
接下来 Workbench 和 ExtensionContribution 的插件化我们在下一节做分析。 | ```typescript
import ‘vs/workbench/workbench.sandbox.main’; // for desktop
import ‘vs/workbench/electron-browser/desktop.main’; // for web-browser
// load related services
import ‘vs/workbench/services/search/electron-browser/searchService’;
// Extension Service
import ‘vs/workbench/services/extensions/electron-browser/extensionService’;
// Extensions 扩展注册
import ‘vs/workbench/contrib/extensions/electron-browser/extensions.contribution’;
|
<a name="i8Dbl"></a>
## Browser
有了 ElectronBrowser 的启动为背书,接下来对浏览器版本的 VSCode 启动就清晰多了:
- `src/vs/code/browser/workbench/workbench.html` 中作为入口,简化代码如下
```html
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
<!DOCTYPE html>
<html>
<head>
<!-- hide not necessary details -->
<link data-name="vs/workbench/workbench.web.main" rel="stylesheet" href="{{WORKBENCH_WEB_BASE_URL}}/out/vs/workbench/workbench.web.main.css">
</head>
<body aria-label=""></body>
<!-- Startup (do not modify order of script tags!) -->
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/loader.js"></script>
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/webPackagePaths.js"></script>
<script>
// Loader 配置
const baseUrl = new URL('{{WORKBENCH_WEB_BASE_URL}}', window.location.origin).toString();
Object.keys(self.webPackagePaths).map(function (key, index) {
self.webPackagePaths[key] = `${baseUrl}/node_modules/${key}/${self.webPackagePaths[key]}`;
});
require.config({
baseUrl: `${baseUrl}/out`,
recordStats: true,
trustedTypesPolicy: window.trustedTypes?.createPolicy('amdLoader', {
createScriptURL(value) {
return value;
}
}),
paths: self.webPackagePaths
});
</script>
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/workbench/workbench.web.main.nls.js"></script>
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/workbench/workbench.web.main.js"></script>
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/code/browser/workbench/workbench.js"></script>
</html>
loader.js
VSCode 自实现的模块加载器,类似 AMD / Common.js module loaderwebPackagePaths.js
VSCode 打包时动态生成的,模块路径映射表workbench.web.main.nls
运行时语言包注入workbench.web.main.js
Workbench 依赖的服务、扩展、扩展点、命令、动作 … 进行注册和初始化(单例或服务挂载)workbench.js
Workbench 界面的入口代码,用于启动程序,挂载 DOM 节点
在这里就不赘述细节代码的实现了。
一些其它问题
VSCode 初始化的一些 Tips
防止多个实例妙招:
// Create the main IPC server by trying to be the server // If this throws an error it means we are not the first // instance of VS Code running and so we would quit. const mainProcessNodeIpcServer = await this.claimInstance(logService, environmentMainService, lifecycleMainService, instantiationService, productService, true); // Write a lockfile to indicate an instance is running (https://github.com/microsoft/vscode/issues/127861#issuecomment-877417451) FSPromises.writeFile(environmentMainService.mainLockfile, String(process.pid)).catch(err => { logService.warn(`Error writing main lockfile: ${err.stack}`); });
MultiplexLoggerService 初始化 Tip
// Log: 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 logService = new MultiplexLogService([new ConsoleMainLogger(getLogLevel(environmentMainService)), bufferLogService]); process.once('exit', () => logService.dispose()); services.set(ILogService, logService);
IPCChannel
的设计可以继续深挖,看IPCServer
是如何设计实现的?- WIP
SyncDescriptor
是用来做什么的?理解一番。- 控制 Construtor 的行为
- 静态参数
延迟实例化标记
export class SyncDescriptor<T> { readonly ctor: any; readonly staticArguments: any[]; readonly supportsDelayedInstantiation: boolean; constructor(ctor: new (...args: any[]) => T, staticArguments: any[] = [], supportsDelayedInstantiation: boolean = false) { this.ctor = ctor; this.staticArguments = staticArguments; this.supportsDelayedInstantiation = supportsDelayedInstantiation; } }
理解
preload.js
是如何完成隔离和通信关系 Channel 的建立的?- 该脚本检测是否为 Electron 环境
- Electron 环境:将与主进程通信的 IPC 方法存储到 global 中,类似
self.vscode
作为全局变量 - Browser 环境:直接挂载到 window 上
- Electron 环境:将与主进程通信的 IPC 方法存储到 global 中,类似
- 该脚本检测是否为 Electron 环境
electron-main
/electron-sandbox
/electron-browser
以及的加载时序是怎样的?- 👆 整体时序分析已经纳入,main 最先加载,然后是支持和 mainProcess 通信的 electron-