跪拜 Guibai
← Back to the summary

VS Code Is Not a React App: The Multi-Process Desktop Architecture Beneath the UI


theme: v-green

Foreword

Many people, when first looking at VS Code, see its interface: the sidebar, editor, terminal, panels. It looks like a slightly more complex frontend application.

But after diving deeper, you discover that VS Code is not an ordinary React App. It is a desktop IDE architecture based on Electron, with multiple processes like main, preload, renderer, and extensionHost collaborating behind the scenes, along with a whole set of designs including a command system, Services, RPC, and plugin isolation.

To better understand these designs, I wrote a mini-vscode project based on React + Vite + Electron. This series is not about replicating all of VS Code's functionality, but about clearly explaining the most core architectural skeleton of VS Code through a smaller implementation.

mini-vscode: https://github.com/zenoskongfu/mini-vscode

Who is this series most intended for?

This series is primarily a summary of my own learning of the VS Code architecture, and also a form of technical consolidation. It also serves as a guide for those who want to learn the VS Code architecture. Based on the study of VS Code, I wrote a mini-vscode project. The tech stack for mini-vscode is React + Vite + Electron.

This series is first and foremost a summary and technical consolidation of my process of learning the VS Code architecture. Behind it lies a very clear desktop IDE architecture: multi-process isolation, service-oriented capabilities, a command system, a plugin host, RPC communication, an editor model, and more.

To better understand these designs, I wrote a mini-vscode project based on React + Vite + Electron. It is not meant to fully replicate all of VS Code's features, but rather to use a smaller, easier-to-read project to break down and clearly explain the core architectural skeleton of VS Code.

So, this series is both my own learning record and a hope to provide an easier entry path for those who want to learn the VS Code architecture.

What one sentence should a reader remember after finishing the first article?

VS Code is not a React App, but a desktop multi-process IDE architecture.

Many people, when first looking at VS Code or mini-vscode, might see pages, components, and the editor area, and thus easily understand it as a React page.

But in reality, VS Code is not an ordinary Web App running in a browser. It is a desktop application based on Electron, possessing both the ability of a browser to render pages and the ability of Node.js to operate the local system.

That is to say, its complexity does not lie in how the pages are drawn, but in how a desktop IDE safely, stably, and extensibly organizes local files, terminals, plugins, editors, and various system capabilities.

If it's just about writing an interface, why is the Electron main process needed?

If mini-vscode were just displaying a static page, then indeed only React would be needed. But an IDE is far more than just a page.

It needs to open local files, read directory structures, save file content, monitor file changes, launch terminals, execute commands, manage windows, and even interact with system capabilities like Git, debuggers, and language services. These capabilities are not something an ordinary browser page can directly accomplish.

Electron's main process takes on this part of the responsibility. It runs in a Node.js environment, has access to the local system, and is the part of the application truly close to the operating system layer.

The Renderer is responsible for connecting the main process and user interaction, while the main process handles local capabilities and system resources. The two collaborate through IPC communication to form a truly usable desktop IDE.

What exactly does preload protect? Why can't the renderer directly access Node?

Preload protects the boundary of low-level capabilities.

In Electron, the renderer is responsible for rendering pages. If the renderer were allowed to directly access Node.js, then the page's code could directly read and write local files, execute system commands, and access any low-level API. While convenient, this is extremely dangerous.

Because the renderer layer is still essentially a browser environment, it may load complex frontend code and could also be affected by issues like XSS. Once the renderer can directly use Node capabilities, any vulnerability at the frontend level could become a risk at the local system level.

A more reasonable approach is: do not let the renderer directly touch Node, but instead expose a controlled layer through preload.

preload uses contextBridge to mount a small number of designed capabilities onto window.electronAPI. The renderer can only call these permitted APIs, such as reading files, creating files, and saving content, but cannot arbitrarily invoke all of Node.js's capabilities.

This is the significance of preload: it is not to add complexity, but to allow low-level capabilities to be used in a bounded and planned manner.

Why does VS Code make "capabilities" into Services, instead of calling them directly within components?

VS Code has two types of services, one at the renderer layer and one at the main layer.

Renderer Layer

From a code perspective, the service layer here wraps the APIs exposed by preload: i.e., window.electronAPI.fs.createFile. From a design perspective, the benefit is that calls from the renderer layer only need to face the abstraction of capabilities, without needing to be immersed in specific APIs. This increases code readability and maintainability.

Main Layer

In mini-vscode, services are key to understanding the architecture.

They can be viewed on two levels: Services at the renderer layer, and Services at the main layer. They are not in the same process, but the design philosophy is consistent: both encapsulate specific capabilities into clearer business interfaces.

At the renderer layer, Services are oriented towards workbench logic. Examples include file services, editor services, and command services. Components do not need to directly concern themselves with the details of low-level calls, but instead call Service methods that are closer to business semantics.

The benefit of this is that components only need to face abstract capabilities like "I want to create a file," "I want to open an editor," "I want to execute a command," without needing to be immersed in details like IPC, preload APIs, and data transformation.

At the main layer, there is also a set of services divided by capability, responsible for encapsulating calls to low-level capabilities like Node.js APIs, the local file system, and terminal processes.

Thus, when preload exposes capabilities, it does not simply expose Node APIs as they are, but exposes capability interfaces that have been organized through business logic.

So, the value of Services lies in this: they separate what can be done from how it is specifically done. Components face capability abstractions, and the underlying implementation can be replaced, extended, and maintained.

Why does the command system use a command id as an intermediary layer?

This is a very clever design in VS Code.

In an IDE, the same function might be triggered from many places. For example, saving a file can be triggered by a keyboard shortcut, by a menu, by the command palette, or by a plugin.

If all these entry points directly called the specific implementation, the system would quickly become chaotic: the shortcut would need to know where the save logic is, the menu would need to know as well, and the plugin would need yet another way to call it. This would create a lot of coupling between modules.

VS Code's approach is to introduce a command id as an intermediary layer. A function first registers a command, such as workbench.action.files.save. Other triggers do not need to know who specifically implements this function; they only need to know this command ID.

Ultimately, all entry points converge to:

executeCommand(commandId, ...args)

In this way, shortcuts, menus, the command palette, and plugins can all trigger capabilities through the same method.

The essence of the command system is to decouple who triggers from who implements. It allows different modules to call each other's capabilities without needing to directly depend on each other.

Why can't plugins directly manipulate the interface, but must go through extensionHost and RPC?

Because plugins are untrusted.

If a plugin could directly access React components, the DOM, Services in the Renderer, or even directly manipulate the local file system, then a poorly written plugin could affect the stability and security of the entire editor.

Therefore, VS Code runs plugins in an independent extensionHost process.

When a plugin wants to execute commands, register capabilities, or access editor state, it cannot directly modify the main interface. Instead, it communicates through a layer of RPC to tell the renderer:

I want to register a command, I want to execute a command.

I want to get the current editor information.

I want to provide some language capability.

The renderer then decides how to handle it.

The idea behind this is: plugins can extend capabilities, but cannot intrude into the main program.

This is also key to why the VS Code plugin system can run stably.

Why can't editor content just be placed in React state?

Because an editor is not an ordinary form.

In an ordinary React page, putting the content of an input box in useState is fine. But a code editor is much more complex; it needs to handle:

If all these states were crammed into React State, each component render could potentially affect the editor instance, even leading to the loss of content, cursor position, and history records.

Therefore, in mini-vscode, what truly carries the editor content is the Monaco Model. Each file path corresponds to an independent model, and models are reused when switching files, rather than creating new editor content each time.

React here acts more like a projection view, responsible for displaying the frontend state. The true editor state should be maintained by the EditorService and the Monaco model.

This is also a very important idea in the VS Code architecture: UI components should not be the sole source of core state; core state should sink down to the service layer and model layer.

So, which skeleton of VS Code is mini-vscode actually replicating?

mini-vscode replicates not all of VS Code's features, but its most core architectural skeleton.

What it truly aims to express are these few things:

First: VS Code is a multi-process desktop program.

main / preload / renderer / extensionHost each have clear boundaries; it is not a simple browser page.

Second: The renderer is the core of the workbench.

The editor, command system, Services, and state flow mostly occur on the renderer side.

Third: Capabilities must be exposed through Services.

Components do not directly touch low-level APIs but face abstract capabilities. Only such a system can continue to be extended.

Fourth: All operations should converge into the command system as much as possible.

Shortcuts, menus, the command palette, and plugins ultimately find the corresponding capability through a command id.

Fifth: Plugins run in an isolated environment.

The extensionHost communicates with the renderer via RPC, ensuring both extensibility and the stability of the main program.

So, the value of mini-vscode is not in "how many features it has," but in that it breaks down the most important design ideas of VS Code into a small model that can be read, debugged, and learned.

Summary

So, the first article of mini-vscode aims to answer a most fundamental question: Why is VS Code not an ordinary React App?

The answer lies in the fact that it is not just a page, but a desktop IDE. React is only responsible for rendering the interface. What truly supports the editor's operation is the multi-process model, controlled local capabilities, Service abstraction, the command system, and the plugin isolation mechanism.

Only by first understanding this point will the subsequent looks at VS Code's process model, command system, plugin system, editor model, and debugging architecture not seem like designs that are "complex for complexity's sake." They are all essentially there to allow a desktop IDE to run safely, stably, and extensibly.