美文网首页
VSCode源码自定义笔记-VSCode启动流程分析

VSCode源码自定义笔记-VSCode启动流程分析

作者: 黑山老雕 | 来源:发表于2019-08-05 21:28 被阅读0次

从开始到窗口加载

与所有的Electron应用一样,入口点在 package.json文件中定义。

  "main": "./out/main",

说明了入口文件在 out/main.js. 这个是编译出来的文件,源文件在 src/main.js。
注意对于TS文件,由于有sourcemap的映射,我们在ts中打断点就可以跳转过来。这里的js应该是编译时候直接拷贝到out目录下的,我们在src下面的文件打断点无效,应该直接在out/main.js中断点。这样使用launch vscode (Main process)的时候,断点就可以命中。这个是程序的主要入口。

类似于Electron的启动,看app的定义:

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

还有对于Ready的事件监听函数:

app.once('ready', function () {

其中会调用 onReady() 函数。其中定义了一个startup函数,后续会根据配置的不同用不同的参数调用这个startup,但只是不同方式的启动而已。我们只需要关注 startup中的启动代码:

require('./bootstrap-amd').load('vs/code/electron-main/main', () => {
                    perf.mark('didLoadMainBundle');
                });

这里用代码调用amd的方式来加载 vs/code/electron-main/main 模块。查看这个文件,

开头有一行import 'vs/code/code.main';,狐疑地查看了一下,只有一行import 'vs/platform/update/node/update.config.contribution'; 其中似乎是一个配置update的东西,看起来与主流程无关,先不管。奇怪这里命名为什么也叫 code.main,搞得人很紧张。

继续来看 main.ts. 文末都写了,这是主入口:

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

在main.ts的startup中,会注册一个instantiationService 初始化服务。

const instantiationService = this.createServices(args, bufferLogService);

从这个函数中可以看到它注册了一系列的服务,来把IService的interface映射到具体的类上面。这个初始化服务本身的实现类,通过import可以看到,来自于import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
然后用这个服务来调用方法:

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

这里这个createInstance会返回一个app.ts中的类,然后调用src\vs\code\electron-main\app.ts 的startup(),其中的

const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor));

来创建第一个窗口。

从这个初始化服务的用法来看,看起来像一个反射的具体实现。

同时,还要注意这个初始化服务调用的上面几行:

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

这里应该是启动ipc的server,与render的进程进行互相通信。 先记下,可能以后会深入研究。

接着看openFirstWindow, 它会调用windows.ts,注意最后是有s的,调用其中的open方法。注意这里它调用的方式:

return this.windowsMainService.open({

这里的windowsMainService的真实值来自于依赖注入,在vscode中,使用accessor来得到实际的服务是很常见的一种用法:

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

在windows.ts的open中,会处理一系列需要打开的folder和files之类的参数。在createBrowserWindow中,这句:

        this._win = new BrowserWindow(options);

调用了Electron的类 BrowserWindow,创建了一个新的窗口。
然后在CoreWindow的load方法中,其中调用的getUrl方法会指定加载workbench.html,并把许多的config作为parameter缀在后面。
自此,浏览器窗口被调起。任务栏上会看到新的窗口产生。

查看一下workbench.html的内容,源文件地址在src\vs\code\browser\workbench\workbench.html
其中会包含

    <!-- Startup via workbench.js -->
    <script src="./out/vs/code/browser/workbench/workbench.js"></script>

这个js就是render脚本的入口点。这个脚本里启动的逻辑位于浏览器进程中(render进程)

Render进程

workbench.js会定义一个loadScript函数,其作用主要是在workbench.html页面中的head中放置给定的脚本,并在加载完毕的时候调用相应的callback。
然后,该脚本用这个函数加载了一下loader,加载成功后,对require进行了一下配置。(这里不太明白为什么这里require不用导入也能用,也许是因为electron默认就加载了这个模块?希望有明白的人可以给解释一下。)
然后require 'vs/workbench/workbench.web.main'之后,对其进行调用其中的main方法。 (到处都是main!!! 看得有点乱)
其中,创建render类并启动。

export function main(): Promise<void> {
    const renderer = new CodeRendererMain();

    return renderer.open();
}

注意这里的perform.mark 似乎是用于打log的,很常见。

继续看main,逻辑很简单,创建一个workbench,并startup。代码跳转到src\vs\workbench\browser\workbench.ts
startup中一些set开头的东东,直接跳过。又看到了熟悉的instantiationService.invokeFunction的用法。调用了一个函数。

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

里面有注册,注册,注册,服务,服务,服务。
renderWorkbench函数似乎会控制DOM,添加不同的页面元素。 createWorkbenchLayout 和 layout 来创建不同的部分,比如 activitybar, sidebar等,并进行位置布局。代码在src\vs\workbench\browser\layout.ts中

关于render的修正

又仔细查看了一下,实际创建的浏览器加载的html页面的地址是vs/code/electron-browser/workbench/workbench.html?config=%7B%22_%22%3A%5B%5D%2C%22inspect-brk.... , 后面带一堆配置。
而不是之前提到的src\vs\code\browser\workbench\workbench.html , 那什么时候调用这个workbench.html,有待后续研究。

来看这个vs/code/electron-browser/workbench/workbench.html. 它和之前看过的那个页面类似,也会加载一个同级目录下的workbench.js
在这个workbench.js中,会调用bootstrap-window并加载一些脚本:

bootstrapWindow.load([
    'vs/workbench/workbench.main',
    'vs/nls!vs/workbench/workbench.main',
    'vs/css!vs/workbench/workbench.main'
],

关于 vs/workbench/workbench.main, 后续再专门分析。

加载完这些脚本之后,会require vs/workbench/electron-browser/main 并调用其中的main方法。
和之前看过的main类似,创建一个CodeRenderMain并open。并创建workbench,调用startup。到这里就和之前分析的代码重合上了。注册服务并最终布局。

转载请注明出处。觉得有用请点赞。
更多教程请在网易云课堂优酷腾讯视频搜索黑山老雕。

相关文章

网友评论

      本文标题:VSCode源码自定义笔记-VSCode启动流程分析

      本文链接:https://www.haomeiwen.com/subject/eybvdctx.html