Alex dima shipping largest javascript cover

Visual Studio: Shipping a Massive Microsoft JavaScript App

Introduction

My name is Alex Dima, and I’m a senior software engineer on the Visual Studio Code team at Microsoft. We work out of Zurich, and today I would like to cover about some of the lessons we’ve learned while developing and shipping Visual Studio Code.

Visual Studio Code

Visual Studio Code comes in two parts. Firstly it’s a web app built using JavaScript, specifically using the Node.js API on top of a browser API. The browser comes in the shape of Electron, which is a merger between Chromium and Node.js.

Secondly, we ship a library which called the Monaco Editor. We take it out of our product and we ship it separately as a JavaScript library that can run in any modern web browser.

Growing the Code

We have been working on the code since the autumn of 2011. At the time, we were given the mission to redefine and create a better developer experience; development tools that work in the browser.

We started in JavaScript - our patterns consisted of creating classes using prototypical inheritance, and wrote idiomatic JavaScript to try to create classes, patterns, and types in our source code.

One thing we got correct from the start was to use promises as a way to abstract away asynchronicity. As an example, in the UI when a computation had to be completed behind the scenes, it makes a call, and that call returns a promise with the actual thing to show.

Get more development news like this

This was a big win for us because whenever we changed architectures, we could just substitute the implementation of the hover providers. Instead of executing them in a web worker, we could spawn a process and do it in a separate process, as a result, there were no changes to the UI code.

TypeScript to the Rescue

TypeScript provides an extra layer of security. More specifically, it protects you against your own mistakes.

TypeScript compiles down to JavaScript, but it is a superset of JavaScript. What this means is that any JavaScript you write is actually TypeScript as well. TypeScript adds optional syntactical constructs, such as type annotations and interfaces, and generally makes authoring JavaScript more pleasant.

TypeScript Demo

Code Organization

One of the things which we did not get correct from the beginning were dependencies. We failed to keep track of what each JavaScript file needs and what it provides.

One of the reasons for that is because we were using Namespaces, which is a fancy word for globals. For example, in a certain file, we would reach on to its Namespace.Util.Strings, and we define a new method on this Namespace. The problem here is that there’s no correlation between the file that does this and the Namespace that’s being defined.


var Namespace = {};
Namespace.Util = {};
Namespace.Util.Strings = {};
Namespace.Util.Strings.trim = function() {
/* etc */
};

I could name the file Strings.js or Utils.js, but nobody in my team would know where to find the implementation of the trim function. They might be using it everywhere, but they wouldn’t necessarily know which file on disk they should go open and change.

Renaming files was difficulty. Renaming Namespaces was something we never did because we wouldn’t know where they are all used.

Module Systems

CommonJS is great because you know what’s in a file, and what will be provided in the form of the export. The problem with it is that it’s very suitable for running on a server where there is local disk access, but you cannot do this in a browser. You cannot expect to ship this code, then do an asynchronous XHR request to try to fetch the dependency.


my_module.js

var dependency = require('dependency_id');

// code

exports.myExports = {
  // exports
}

AMD stands for Asynchronous Module Definition, and AMD was release right around the time we were exploring this issue. The real difference in the code is that all the dependencies of the code are extracted outside in an array.

define('module_id', ['dependency_id'], function(dependency) {

// code

  return {
    // exports
  
  };
}

As a result, I will know the dependencies I need to fulfill before executing that code.

TypeScript: First Class Module Support

Typescript supports both AMD and CommonJS with on syntax, making code sharing between AMD and CommonJS easy.

import { Dependency } from "./dependency_id";

// code

export function helper() {
  // export
}

Lazy Code Loading

One of the advantages of AMD, is the lazy code loading. We ship the app with about 30 languages. If you open a .txt file, and there are 30 different languages on it, it would all load.

The current approach we use is to have a contribution system that describes the type of language that exists.

When a text file is opened, the only thing that is loaded is this piece. Later, when you open a PHP file, PHP get loaded.


// php.contribution.ts

ModesRegistry.registerMode({
  id: 'php',
  extensions: ['.php', '.php4', '.php5'],
  aliases: ['PHP', php'],
  minetypes: ['application/x-php'],
  moduleId: 'vs/languages/php/common/php',
  ctorName: 'PHPMode'
});


// php.ts

export class PHPMode extends AbstractMode {
  
  constructor() {
    super('php');
  }
}

CSS Dependencies

We also take advantage of AMD loader plugins. When we write our code, we write a dependency to the CSS that this file needs.


// currentLineHeight.ts

import 'vs/css!./currentLineHightlight';
import {ViewOverlay} from '...';

export class CurrentLineHightlight extends ViewOverlay {

}

How this highlight works is that it creates a DOM node, then sets a certain class name on it. If you move your cursor up and down, it moves up and down.

Performance: Bundle and Minify your Code

You should look into bundling and minifying your code, even if it runs on the server side. In Visual Code Studio, the startup process is split into three: Electron Startup, Modules, and Shell/Viewlet and Editor.

What we found by bundling all the files into one, you get a performance benefit.

Components

It’s possible to compile with TypeScript. Imagine there are two teams, or two different projects. For example, from the TypeScript guys, we consume this typescriptServices.js that they produce.

This is how the editor work: you take the buffer that you’re typing, and it returns to offer suggestions. To accomplish that, they ship us a bundled minified JavaScript file, accompanied by a DTS, which describes what is inside that JavaScript.

Conceptually, DTS is the API that we have between our projects. If they make up a new version, we get both new files, and we compile against the new DTS.

Dependency Injection

TypeScript supports constructor decorators, and we use them for service injection. This is an action in the editor that is triggered by pressing F12. After the promise returns, it jumps to the definition of a certain symbol.

You would do this on a function, and it would take you to where the function is implemented. We use these decorators to signal that we should inject some services to this action.

If there is an error, this action calls inside this message service and says ‘show error’. We have an implementation of the message service that shows a little drop-down at the top.

Electron Demo

Electron is a combination of Chromium and Node.js. How they work together is through a script, in this case, main.js, which provides the entry point.

A new browser window that shows the width and height is created and it loads the file index.html. Inside the index.html is where there is a browser and Node API.

The main.js is a driver of these windows. That process is always there and communicates to the OS. It owns things like the buttons and the menus.

Main.js is a very important process. Inside, you should not do what I’m about to do now, which is a set interval. In this example, we’re computing Fibonacci.

If you run this, it will be non-responsive because Electron inherits the process architecture of Chromium. If the main process becomes busy, it will bring down everything with it.

Execution Environments

New processes are created when doing things like checking for updates or managing extensions, as it will impact the responsiveness.

For each renderer process, we create an extension host, and also launch on-demand processes. For instance, if you do a search, we will launch a process and it will search on the folder open and return the results.

Each one of these processes has a different execution environment.

Next Up: More talks to help you grow your company or app!

General link arrow white

About the content

This talk was delivered live in June 2016 at goto; Amsterdam. The video was transcribed by Realm and is published here with the permission of the conference organizers.

Alex Dima

Alex works in the VS Code team at Microsoft, Zurich. He is the main developer of the Monaco code editor that powers VS Code, and is passionate about making developers productive. Before Microsoft, Alex worked for IBM, studied at ETH Zurich and hacked at his own C++ 3D game engine. He was so fond of JavaScript that he wrote an Eiffel-to-JavaScript transpiler, but now likes TypeScript much more.

4 design patterns for a RESTless mobile integration »

close