Previously: Some students thought IDL looked a little complicated, not as comfortable as Swagger interface, so I went to study the vscode extended development document and flycode architecture when I just came to Beijing in the summer vacation. Now that I have 🤏🏻 experience, I hope I can use this document to let students who want to develop vscode extensions can get started more quickly.

Speaking of vscode, we have to mention electron, which has three core technologies:

  • Chromium: Through the Web related technology stack to write interface UI, based on chrome kernel to run.
  • Node.js: Responsible for operating the file system and network communication.
  • The nativeAPI: calls the operating system API from node and node addons (a dynamic link library written in C++ that extends node.js capabilities).

Electron is also characterized by multiple processes. There are many kinds of processes. Here are the two most important ones:

  • Main Process: A Electron application has only one main process. Creating guI-specific interfaces can only be called by the main process.
  • Renderer Process: The main process can call the Chromium API to create any number of Web pages, each running in its own renderer process.

To sum up: In Electron, a Web page can forward a message to the main process through the renderer process, which in turn calls the operating system’s native API. The ability to develop extensions is more flexible and rich than ordinary Web applications.

Now that we know the underlying design of vscode, let’s explore vscode extension development step by step with real requirements.

Demand analysis

Right-click on a folder in the vscode menu tree to open the visual interface, perform simple configuration and quickly create a micro front terminal application in its subdirectory.

After seeing this requirement, we distilled several features related to vscode:

  • With vscode instruction system, register a command to the menu bar
  • Create a Web page for configuration
  • Close the web page

Logic implementation

Registration instructions

After a plug-in project is initialized, the outermost exposed file contains the activate and deactvate methods, which are part of the lifecycle of vscode plug-ins and will eventually be exported to vscode for active invocation. Events such as onXXX are Activation Events declared in the plug-in package.json file. After these Activation Events are declared, vscode calls back the activate function in the plug-in at the appropriate time. Vscode is designed to save resources by activating your plugin only when necessary.

  // package.json

  "activationEvents": [

    "onCommand:fly-code.newSubProject". ] ."commands": [{"command": "fly-code.newSubProject"."title": "New subproject"},... ] .Copy the code

We can register commands when the plug-in is activated

import { newProjectCommand } from './commands/new-project';



export function activate(context: vscode.ExtensionContext) {

  // Register the command

    vscode.commands.registerCommand('fly-code.newSubProject'.(info: any) = >{ newProjectCommand(context, info.path); })}Copy the code

NewSubProject will bind the fly-code.newSubProject command to the function, so the newProjectCommand method will do what we need to do.

Create a webview

If you want to create a page, you can use vscode provide API — vscode. Window. CreateWebviewPanel:

export function newProjectCommand(context: vscode.ExtensionContext, dirPath: string,) {

  const panel = vscode.window.createWebviewPanel(

    'newPage'.// viewType

    'New Project'.// View title

    vscode.ViewColumn.One, // Where to display in the editor

    // WebView is hidden to avoid being reset

    { enableScripts: true.retainContextWhenHidden: true}); . }Copy the code

The page to be rendered can be specified using HTML attributes, but the HTML attributes accept strings as arguments!!

So we can’t use vue/ React encoding, we can only write template strings?

Of course not! We can write react code, package it as js, put it in the index.html template and return it.

panel.webview.html = getWebviewContent(context, 'project.js');
Copy the code

Depending on the scene, render the corresponding component -> the corresponding JS file

The thing that handles this is getWebviewContent:

function getWebviewContent(context: vscode.ExtensionContext, page: string) {

  const resourcePath = path.join(

    context.extensionPath,

    './dist/webview/',

    page,

  );

  Const getHTMLDependencies = () => (' <! -- Dependencies --> <script src="${highlightJs}"></script> <script src="${reactJs}"></script> <script src="${reactDomJs}"></script> <script src="${antdJs}"></script> `); * /

  const { getHTMLLinks, getHTMLDependencies } = useWebviewBasic(context);



  return ` <! DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>fly-code! </title>${getHTMLLinks()}</head> <style> body { background-color: transparent ! important; } </style> <body> <div id="root"></div>${getHTMLDependencies()}<! -- Main --> <script src="vscode-resource:${resourcePath}"></script>

      </body>

  </html>

  `;

}
Copy the code

Vscode-resource: Webview does not have direct access to local resources by default for security reasons, it runs in an isolated context. It only allows specific local files to be accessed through absolute paths.

As can be seen from the above code, for a command/function, if it involves webView, focus only on the rendering code (i.e. the SPA JS file), not the specific page implementation, so you can write uI-related logic outside of the main Node process.

React and webpack

For vscode plug-ins, the UI is separate, so we can do the code for the page just like we did for the react project.

// web/src/pages/project/index.tsx



const Template: React.FC = () = > {

  const [loading, setLoading] = useState(false); .return (

    <Spin spinning={loading} tip={loadingText}>

      <div className="template">.</div>

    </Spin>

  );

};



ReactDOM.render(<Template />.document.getElementById('root'));
Copy the code

In terms of packaging, it was mentioned that we need to load different page components according to different commands, that is, different JS, so the packaged entry is multi-entry; In order not to introduce public libraries repeatedly, the react, ANTD and other libraries are imported by CDN.

  const config = {

    mode: env.production ? 'production' : 'development'.entry: {

      template: createPageEntry('page-template'),

      layout: createPageEntry('page-layout'),

      view: createPageEntry('view-idl'),... },output: {

      filename: '[name].js'.path: path.resolve(__dirname, '.. /dist/webview'),},...externals: {

        'react': 'root React'.'react-dom': 'root ReactDOM'.'antd': 'antd',}};Copy the code

Process of communication

After we implement the form page, the next step is to pull the data of the form to the corresponding material library of NPM, and then render it to the corresponding path of the local project. It can be seen that this step requires the support of the operating system API, and we need to use the Node process to do this.

So the question is, UI is passed to vscode processes via HTML strings, how do they communicate with each other?

Highlight!!

The core (and disgusting) thing about developing vscode extensions is communication. The one-way data flow makes it difficult to communicate with the webview and the plugin node process, even though two different webviews in the same react project cannot directly interact with each other.

Take a simple 🌰 :

For Flyidl visualization, open a new page and search for render data by clicking on an API on the left, and a success message is returned to the list page on the left. It’s very simple component communication if it’s normal web, but vscode is…

The process is shown as follows:

Here, vscode provides only the simplest and most crude communication method — acquireVsCodeApi. This object has three and only the following apis that can communicate with plug-ins.

  • Plug-in sends messages:
panel.webview.postMessage // Support sending arbitrary json-formatted data
Copy the code
  • WebView Receiving messages:
window.addEventListener('message'.event= > {

    const message = event.data;

    console.log(message);

})
Copy the code
  • WebView To send a message to the plug-in:
export const vscode = acquireVsCodeApi();

vscode.postMessage('xxx');
Copy the code
  • Plug-in receives messages:
panel.webview.onDidReceiveMessage(message= > {

    console.log('Messages received by plug-ins:', message);

}, undefined, context.subscriptions);
Copy the code
  1. Communication package

Again, if all communication logic is listened for through message events, how do you know which messages to receive in a particular place and how do you send a message with a unique identifier?

Vscode itself does not provide similar functionality, but it can be wrapped itself.

The WebView side:

// Similar to eventEmitter

export function sendMessageToVsCode({ type, data }: SendMessageToVsCodeParams) {

  const listeners = new Set<FunctionType>();

  // Send a message

  const message = {

    type,

    data,

    id: getRandomId(),

  };



  vscode.postMessage({

    text: JSON.stringify(message),

  });



  // Receive the message

  function handleResponse(event: any) {

    if (event.data.id === message.id) {

      // Execute all callbacks in the queue

      listeners.forEach((listener: FunctionType) = > {

        try {

          listener(event.data);

        } catch (e) {

          console.error(e); }}); }}// Listen for message events

  (window as any).addEventListener('message', handleResponse);



  // Return a handler object to which callbacks can be added or cleared

  return {

    listen(listener: (message: any) => void) {

      listeners.add(listener);

    },

    dispose() {

      listeners.clear();

      (window as any).removeEventListener('message', handleResponse); }}; }Copy the code
// Handle communication as if it were an HTTP request

export function sendRequestToVsCode<T> (type: string, data: any) :Promise<T> {

  return new Promise((resolve, reject) = > {

    const handler = sendMessageToVsCode({ type, data });

    // Set a timeout handler

    const timeoutHandler = setTimeout(() = > {

      reject(Error('timeout'));

      handler.dispose();

    }, 10 * 1000);



    handler.listen(res= > {

      resolve(res.data);

      handler.dispose();

      window.clearTimeout(timeoutHandler);

    });

  });

}
Copy the code

The Node side:

  panel.webview.onDidReceiveMessage(

    message= > {

      try {

        const messageBody = JSON.parse(message.text);

        const { type: msgType, id: msgId, data } = messageBody;

        // use type to find the corresponding method

        switch (msgType) {

          case MsgTypes.CREATE_PROJECT:

            ...

            break;

          case MsgTypes.FETCH_TEMPLATE_CONFIG:

            fetchMaterialConfig(data as string).then(res= > {

              // Reply messages to WebView with id

              panel.webview.postMessage({

                id: msgId,

                type: MsgTypes.FETCH_TEMPLATE_CONFIG,

                data: res,

              });

            });

            break;

          default:

            break; }}catch (e) {

        outputChannel.error(`newProject: ${e}`); }},undefined,

    context.subscriptions,

  );
Copy the code
  1. Writing style

Vscode has a lot of light and dark modes, and has a lot of color themes, so how does the color change with the current theme?

There are generally two solutions:

  • variable

Vscode provides color variables such as var(– vscode-sidebar -background), var(–vscode-button-foreground), etc. If your component is consistent with vscode itself, Then you can use the corresponding variable.

  • The namespace

As the vscode theme changes, the name of the class at the top of the page will also change, for example, vscode-light, vscode-dark, we can write different CSS based on different class names.

So that’s it, I hope to help you quickly have a clear understanding of vscode plug-in development 🐱.

Refer to the article

[1] It’s so easy to develop a blockbuster VS Code plug-in!

[2] VSCode plug-in development overview

[3] a look at large IDE technology architectures from VSCode

❤️ Thank you

That is all the content of this sharing. I hope it will help you

Don’t forget to share, like and bookmark your favorite things.

Welcome to pay attention to the public number ELab team receiving factory good article ~

We are from the front end department of Bytedance, responsible for the front end development of all bytedance education products.

We focus on product quality improvement, development efficiency, creativity and cutting-edge technology and other aspects of precipitation and dissemination of professional knowledge and cases, to contribute experience value to the industry. Including but not limited to performance monitoring, component library, multi-terminal technology, Serverless, visual construction, audio and video, artificial intelligence, product design and marketing, etc.

Interested students are welcome to post in the comments section or use the internal tweet code to the author’s section at 🤪

Bytedance correction/social recruitment internal promotion code: 9uqJj2

Post links: jobs.toutiao.com/s/eX9dge