Vscode is a popular ide for front-end engineers, and its implementation is based on front-end technologies. Since it is the front-end technology implementation, so we use the front-end technology mastered, it is completely possible to implement a VScode-like IDE. But before we do that, let’s first figure out how vscode is implemented. In this article, we will explain how vscode runs.
First of all, VSCode is a electron application, and the implementation of functions such as Windows is based on electron. Therefore, to clarify the startup process of VSCode, we need to know about ELECTRON first.
electron
Electron does JS logic execution and page rendering based on Node and Chromium, and provides BrowserWindow to create Windows, provides the ELECTRON package API, to perform some operating system functions, such as opening file selection window, process communication, etc.
The JS in each BrowserWindow window runs in a rendering process, while electron has a main process that communicates with the rendering process in each window.
As shown in the figure, the main process can use nodeJS API and the API provided by electron to the main process, and the rendering process can use the browser API, NodeJS API, and the API provided by electron to the rendering process. In addition, The rendering process can also use HTML and CSS to lay out the page.
Each window of VSCode is a BrowserWindow, and when we start VSCode we start the main process, and then the main process starts a BrowserWindow to load the HTML of the window, and that completes the creation of the VSCode window. (The subsequent new window is also the same to create BrowserWindow, only by the rendering process through IPC to notify the main process, and then the main process to create BrowserWindow, unlike the first time to start the window directly to the main process to create BrowserWindow.)
The VSOCde window starts the process
We know that vscode runs on electron, which loads the js file of the main process, which is declared in the package.json main field of vsCode./out/main.js.
src/main
(The following code is I simplified from the source code to facilitate you clear thinking)
// src/main.js
const { app } = require('electron');
app.once('ready', onReady);
async function onReady() {
try {
startup(xxxConfig);
} catch (error) {
console.error(error); }}function startUp() {
require('./bootstrap-amd').load('vs/code/electron-main/main');
}
Copy the code
As you can see,./ SRC /main.js is just a piece of bootstrap code that starts executing entry JS at the app’s ready event. Vs /code/electron-main/main, which is the entry logic for the main process.
CodeMain
// src/vs/code/electron-main/main.ts
class CodeMain {
main(): void {
try {
this.startup();
} catch (error) {
console.error(error.message);
app.exit(1); }}private async startup(): Promise<void> {
// Create a service
const [
instantiationService,
instanceEnvironment,
environmentMainService,
configurationService,
stateMainService
] = this.createServices();
// Initialize the service
await this.initServices();
/ / start
await instantiationService.invokeFunction(async accessor => {
// Create an object for CodeApplication and call startup
returninstantiationService.createInstance(CodeApplication).startup(); }); }}const code = new CodeMain();
code.main();
Copy the code
As you can see, vsCode creates an object for CodeMain, which is the beginning of the entry logic and the root class. It creates and initializes some services, and then creates the CodeApplication object.
You might say, why not just create an object by calling new instead of xxx.invokeFunction() and xxx.createInstance()?
This is because vscode implements an ioc container, where any object within the container can declare dependencies and be automatically injected by the container.
The original is direct dependency, but by reversing into injection dependency, coupling is avoided. This is the idea of IOC (Invert of Control), or DI (dependency inject).
This CodeApplication is the IOC container.
CodeApplication
//src/vs/code/electron-main/app.ts
export class CodeApplication {
// Dependency injection
constructor(
@IInstantiationService private readonly mainInstantiationService: IInstantiationService,
@IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService
){
super(a); }async startup(): Promise<void> {
const mainProcessElectronServer = new ElectronIPCServer();
this.openFirstWindow(mainProcessElectronServer)
}
private openFirstWindow(mainProcessElectronServer: ElectronIPCServer): ICodeWindow[] {
this.windowsMainService.open({...});
}
}
Copy the code
Dependencies are declared in CodeApplication using decorators that are automatically injected when instances are created through the container.
Startup creates an instance of ElectronIPCServer (which encapsulates the ipc communication) because the main process communicates with the rendering process via IPC.
Finally, the window is created through the service of windowMainService.
Although it is rather confusing, the complexity can be better governed through service and IOC to ensure that the application architecture will not be more iterative and chaotic.
Then let’s look at the specific window creation logic.
windowMainService
//src/vs/platform/windows/electron-main/windowsMainService.ts
export class WindowsMainService {
open(openConfig): ICodeWindow[] {
this.doOpen(openConfig);
}
doOpen(openConfig) {
this.openInBrowserWindow();
}
openInBrowserWindow(options) {
// Create a window
this.instantiationService.createInstance(CodeWindow); }}Copy the code
In windowMainService you end up creating an instance of CodeWindow, which is a wrapper around BrowserWindow, which is vsCode’s window. (xxx.createIntance is created because it is managed by the IOC container)
CodeWindow
//src/vs/platform/windows/electron-main/window.ts
import { BrowserWindow } from 'electron';
export class CodeWindow {
constructor() {
constoptions = {... };this._win = new BrowserWindow(options);
this.registerListeners();
this._win.loadURL('vs/code/electron-browser/workbench/workbench.html'); }}Copy the code
CodeWindow is an encapsulation of Electron’s BrowserWindow, which is vsCode’s Window class.
It creates a BrowserWindow window and listens for a series of window events, finally loading the WORKBENCH HTML. This is the content of the vscode window, which is what we normally see in vscode.
At this point, we have completed the logic to start electron to show the first vscode window, and we can see the vscode interface.
conclusion
Vscode creates Windows and communicates with processes based on electron. When the application starts, it runs the main process, starting from SRC /main, and then creates the CodeMain object.
CodeMain initializes many services and then creates CodeApplication, which is an IOC implementation and is globally unique. The creation of objects is managed by the container, where all objects can be injected interdependent.
You start by creating an instance of CodeWindow using the windowMainSerice service. This is the window object, which is a wrapper around electron’s BrowserWindow.
The workbench.html is loaded inside the window, and this is what vscode looks like.
Vscode implements window creation and interface display based on electron in this way. Those who are interested can refer to this article to see the source code of VScode 1.59.0, which can learn a lot of architectural aspects, such as ioc container to do the unified management of objects. Managing a variety of resources through a variety of services in a centralized manner makes the architecture less iterative and more complex. In addition, learning vscode source code can also improve your grasp of electron.