主要启动时序分析

:::info ℹ️ 这里的主要分析基于 electron-browser 即使用了 ElectronRenderProcess 的方式渲染的 VSCode 模式。

  • 对于 Web 架构的,入口在 vs/code/browser/workbench/workbench.html 作为入口文件分析,这里不赘述了。 :::

    ElectronMain

简述:

  1. code/elctron-main/main 入口,初始化各类基础服务,并启动主进程
  2. vs/code/app.ts 接下来实例化 CodeApplication
    1. vs/code/electron-browser/sharedProcess/sharedProcess.html 加载 SharedProcess.html 启动共享进程
    2. vs/code/electron-sandbox/workbench/workench.html 打开 Workbench.html(工作区)于 RenderProcess 中
    3. 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’); });

  1. |
  2. | `vs/code/electron-main/main` 中,启动主要做了下面的逻辑:我们使用简化类来表示结构和含义(100 行代码简化版本),简而言之就是:<br /><br />- 创建和注册初始化所需要的必要服务(`createServices`)<br />- 初始化这些服务(`initServices`)<br />- 最后调用 `instantiationService` 服务下的 `createInstance()` 后启动 `startup()` 整个 VSCode 主进程<br /> | ```typescript
  3. class CodeMain {
  4. main(): void {
  5. this.startup();
  6. }
  7. private async startup(): Promise<void> {
  8. // Create services
  9. const [
  10. instantiationService,
  11. instanceEnvironment,
  12. environmentMainService,
  13. configurationService,
  14. stateMainService] = this.createServices();
  15. await this.initServices(environmentMainService, configurationService, stateMainService);
  16. // Startup
  17. await instantiationService.invokeFunction(async accessor => {
  18. return instantiationService.createInstance(CodeApplication, mainProcessNodeIpcServer, instanceEnvironment).startup();
  19. });
  20. }
  21. private createServices() {
  22. const services = new ServiceCollection();
  23. // Product
  24. const productService = { _serviceBrand: undefined, ...product };
  25. services.set(IProductService, productService);
  26. // Environment
  27. const environmentMainService = new EnvironmentMainService(this.resolveArgs(), productService);
  28. // Patch `process.env` with the instance's environment
  29. const instanceEnvironment = this.patchEnvironment(environmentMainService);
  30. services.set(IEnvironmentMainService, environmentMainService);
  31. // Files
  32. const fileService = new FileService(logService);
  33. services.set(IFileService, fileService);
  34. const diskFileSystemProvider = new DiskFileSystemProvider(logService);
  35. fileService.registerProvider(Schemas.file, diskFileSystemProvider);
  36. // Configuration
  37. const configurationService = new ConfigurationService(environmentMainService.settingsResource, fileService);
  38. services.set(IConfigurationService, configurationService);
  39. // Lifecycle
  40. services.set(ILifecycleMainService, new SyncDescriptor(LifecycleMainService));
  41. // State
  42. const stateMainService = new StateMainService(environmentMainService, logService, fileService);
  43. services.set(IStateMainService, stateMainService);
  44. // Request
  45. services.set(IRequestService, new SyncDescriptor(RequestMainService));
  46. // Themes
  47. services.set(IThemeMainService, new SyncDescriptor(ThemeMainService));
  48. // Signing
  49. services.set(ISignService, new SyncDescriptor(SignService));
  50. // Tunnel
  51. services.set(ITunnelService, new SyncDescriptor(TunnelService));
  52. // Protocol
  53. services.set(IProtocolMainService, new SyncDescriptor(ProtocolMainService));
  54. return [new InstantiationService(services, true), ...otherServices];
  55. }
  56. private initServices(services): Promise<unknown> {
  57. return Promises.settled<unknown>([
  58. // Configuration service
  59. configurationService.initialize(),
  60. // State service
  61. stateMainService.init()
  62. ]);
  63. }
  64. }
  65. // Main Startup
  66. const code = new CodeMain();
  67. 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, index: number, optional: boolean }[] { return ctor[DI_DEPENDENCIES] || []; }

 |

那么接下来,我们继续看运行时序:进入 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

``` ```javascript // sharedProcess.js 中关键调用 // Load shared process into window bootstrapWindow.load(['vs/code/electron-browser/sharedProcess/sharedProcessMain'], function (sharedProcess, configuration) { return sharedProcess.main(configuration); }, { configureDeveloperSettings: function () { return { disallowReloadKeybinding: true }; } } ); ``` | | `vs/code/electron-browser/sharedProcess/sharedProcessMain.ts`

- 创建 SharedProcessMain 实例,然后打开共享进程
- initServices() 初始化相关服务
- initChannels() 创建 IPC 通道
- 注册服务实例
- 至此完成 `electron-browser` 和 Main 相关联的共享进程的创建



| ```typescript export async function main(configuration: ISharedProcessConfiguration): Promise { // create shared process and signal back to main that we are // ready to accept message ports as client connections const sharedProcess = new SharedProcessMain(configuration); ipcRenderer.send('vscode:shared-process->electron-main=ipc-ready'); // await initialization and signal this back to electron-main await sharedProcess.open(); ipcRenderer.send('vscode:shared-process->electron-main=init-done'); } ``` | | `vs/platform/windows/electron-main/windowMainService.ts`
- 在完成对 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 loader
  • webPackagePaths.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-main / electron-sandbox / electron-browser 以及的加载时序是怎样的?
    • 👆 整体时序分析已经纳入,main 最先加载,然后是支持和 mainProcess 通信的 electron-