Learning from the Brackets Open Source Code

Posted on Feb 14, 2013

As any good coder knows, often the best way to learn is by example, using code written by others as a guide. In this way, open source projects offer an especially good place to learn from the practices of others as they often deal with a number of complex issues and are frequently built and maintained by respected experts. Plus, learning techniques from open source project also has the side benefit of helping you become familiar with how that particular project works and, who knows, in the process you might even find yourself a contributor. One such open source project is the Brackets HTML, CSS and JavaScript code editor released by Adobe. In this post, we'll examine some of the various coding techniques and frameworks used within Brackets as a way to examine what you could learn from digging into Brackets' source code.

What Is Brackets? Edge Code?

What makes Brackets interesting is that it is built with the very same technologies it edits - i.e. HTML, CSS and JavaScript. In fact, you can even edit Brackets with Brackets (it sounds so meta and makes me think of the scene in Being John Malkovich, where John Malkovich enters his own mind). Brackets is open source, licensed under the MIT license, and available on GitHub.

Brackets is also built to be extensible. Extensions for Brackets are, predictably, built with HTML, CSS and JavaScript. This means that if you are using the editor, you already have the skills to both help enhance the project but also to extend it with features you want to see. In fact, there are already quite a few community built extensions available.

You may have heard of Edge Code. Edge Code is essentially an Adobe branded release of Brackets that is also free via a free Adobe Creative Cloud subscription. Edge Code does have some small differences from Brackets at the moment, but the primary difference now and, more so, going forward will be integration with Adobe products and services. For instance, the initial preview release of Edge Code came bundled with an extension for Edge Web Fonts integration.

What Makes Brackets Different?

Before we can learn from looking into Brackets' source code, we need to understand how Brackets is different from our own projects. While there is a lot a developer can learn from Brackets, there are also many techniques and concepts that may not translate well beyond their specific use-case within a desktop code editor.

Brackets == Shell + Source + CodeMirror
Broadly speaking, there are three separate projects that make up the Brackets desktop application. The most visible is the Brackets source code available on GitHub that is written in HTML, CSS and JavaScript. For many of the actual editing features, Brackets relies on another open-source application called CodeMirror. Finally, the desktop shell of Brackets is built on the Chromium Embedded Framework (CEF3) and is available as a separate open source project called Brackets Shell. While this arguably complicates the project architecture somewhat, your primary interest (and the focus of this article) probably lays in the main Brackets source code.

Nonetheless, this leads to a number of ways your project will likely be different from Brackets, and that should inform the types of lessons you can take away from looking at the code. This is my (not at all comprehensive) list:

  • Brackets is open source with large number of external contributors - your project probably has a limited number of internal contributors (i.e. your development team).
  • Brackets currently does not run in the browser (as mentioned, it runs in a custom desktop shell) - your application is probably targeting the browser or perhaps a mobile shell like PhoneGap rather than the desktop. Technically speaking, Brackets actually targets only one browser, the version of Chromium embedded in CEF, but your application will need to target multiple browsers.
  • Brackets is optimized for displaying and editing code, not traditional content - unless you are also building some form of code editor, your application probably displays standard text and image content rather than code.
  • Brackets is designed for extensibility by third parties in the form of extensions - your application may want to allow some form of extensibility, but honestly most web applications don't need to account for this.
No MV* Framework
Another way Brackets code may differ from the application you are working on is that it currently does not use any sort of MVC/MVVM or other type of architectural framework. While also intended perhaps to alleviate a potential barrier for entry by external contributors, this is in large part because Brackets does not have a lot of fine-grained UI. For the most part, the UI in Brackets includes three parts: the text/code editor; the menu; and the projects panel. As the project grows, more UI has been added and I know that there are ongoing discussions as to the merits of considering adding a framework. It is important to point out though that the project does follow MVC principles in its architectural organization even if it is not tied to a specific framework.

Classes, Inheritance and Modules

I bring up this topic because it seems there are more "solutions" to this "problem" in the JavaScript community than one could imagine. There are quite a number of frameworks that offer a more classical inheritance model, allowing you to build classes and inheritance without ever really thinking about prototypes. Brackets, however, does not use any of these frameworks and goes for a straight JavaScript implementation, prototypes and all.

If you look at the guts of Brackets, you'll actually find that it is made up largely of JavaScript modules and classes - there is actually very little straight HTML. Let's take a look at a little of how one of these classes, InlineTextEditor, is constructed.

/**
* @constructor
* @extends {InlineWidget}
*/
function InlineTextEditor() {
   InlineWidget.call(this);

   /* @type {Array.<{Editor}>}*/
   this.editors = [];
}
InlineTextEditor.prototype = Object.create(InlineWidget.prototype);
InlineTextEditor.prototype.constructor = InlineTextEditor;
InlineTextEditor.prototype.parentClass = InlineWidget.prototype;

The initial commenting is actually JSDoc with Closure Compiler type annotations. Next we have a standard constructor function which chains constructors using the call() method of the class being extended, in this case being InlineWidget. InlineTextEditor is also composed of an array of Editors. Following the constructor, we set the prototype of InlineTextEditor so that it extends InlineWidget, we set the contructor method to our InlineTextEditor() method and finally set a property called parentClass to allow easy reference to the super class.

AMD and RequireJS

Managing dependencies in a large project with a lot of components is a difficult task. To tackle that task, Brackets creates all the components and classes as AMD-compliant CommonJS modules that are managed with RequireJS. If you don't already know what I am talking about, that probably sounds complicated, so let me explain.

The CommonJS API is a set of specifications and proposed standards for building and loading server-side modules asynchronously using JavaScript. This allows you to break up the JavaScript for your application into smaller, functional pieces that are both easier to maintain and reuse (if you've done any type of server-side development, this concept probably sounds very familiar).

However, it became obvious that these concepts were not specific to server-side JavaScript development but were also useful to client-side development. Thus, the Asynchronous Module Definition (AMD) API was created to solve this problem. RequireJS is a JavaScript file and module loading framework for both the client and the server that implements AMD. However, because many projects already implemented their modules in a CommonJS format, RequireJS also supports loading CommonJS modules. This is how modules are implemented and loaded in Brackets. Let's look at some code.

define(function (require, exports, module) {
   "use strict";
   
   // Load dependent modules
   var TextRange       = require("document/TextRange").TextRange,
      InlineTextEditor   = require("editor/InlineTextEditor").InlineTextEditor,
      EditorManager    = require("editor/EditorManager"),
      Commands         = require("command/Commands"),
      Strings          = require("strings"),
      CommandManager    = require("command/CommandManager"),
      PerfUtils       = require("utils/PerfUtils");
      ...

      exports.MultiRangeInlineEditor = MultiRangeInlineEditor;
});

The above snippet of code comes from the MultiRangeInlineEditor class. As you might notice, a definition function is used in the format specified for a simplified CommonJS wrapper. Unlike the standard RequireJS method of defining dependencies within define() as an array of strings, CommonJS modules are loaded individually using the require() method.

An interesting aside to the RequireJS conversation as it relates to Brackets has to do with the fact that Brackets is extensible. Since Brackets cannot guarantee the contents of any given extension, Brackets prevents module conflicts by giving each extension a private RequireJS "context" for accessing Brackets core modules via the brackets.getModule() method. If you'd like to understand how this works, take a look at the ExtensionLoader.js file.

JSLint

JSLint is a code quality tool for JavaScript created by Douglas Crockford that can help find problems in your code as well as enforce code style guidelines. If you open Brackets, you will find that it has a JSLint panel open by default which will display any issues on HTML and JavaScript files when they are open. However, JSLint is also used for the same purposes throughout the Brackets source code.

You'll probably notice by the nature of the issues listed in the JSLint panel, JSLint is extremely picky (to put it nicely) - particularly about style. For this reason, many people in the community seem to prefer JSHint. When the Brackets team began work on the project, many of the developers, while highly experienced developers, were relatively new to JavaScript development and, I've been told, a stricter code quality tool was considered desirable. While Brackets may continue to use JSLint internally, future versions may allow you to swap out JSLint for JSHint or any other code quality tool.

Although, JSLint is picky by default, it is also highly configurable via comments. You can not only make it behave less strictly towards certain style preferences, but also make sure it is aware of variables and browser globals that may be available within your page that it does not by default detect, thereby preventing it from over-identifying issues. Let's look at a sample JSLint configuration from the InlineTextEditor class (you'll generally find the JSLint configuration directly below the copyright notice at the top of the file).

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, $, CodeMirror, window */

In this case, the first set of instructions are making JSLint a little less strict in style. For example, it is saying multiple var statements are allowed (vars), ++ and -- are allows (plusplus), browser globals are assumed (devel), variable names should not be checked for leading or trailing underscores (nomen),  indent is 4 spaces (indent) and the maximum number of errors that will be reported is 50 (maxerr).

The next comment line defines global variables that are available within this file including things like jQuery ($) and CodeMirror, for example.

One important item to point out is that you can configure your HTML and JavaScript files with the same type of JSLint configuration, ensuring that the JSLint panel within Brackets will give you more meaningful results.

Jasmine

Jasmine is a behavior-driven development testing framework for JavaScript code. Put simply, Behavior-driven development (BDD) is based on test-driven development (TDD) where tests are written as sentences based upon Agile user stories. Unit tests for the Brackets source code are written using the Jasmine framework.

If you follow the instructions on how to hack on Brackets you'll see that it instructs you to run the unit tests via the Debug > Run Tests menu option. This option actually runs the test runner that is included in the test folder (I should note that if you didn't download the source from GitHub, you may not have the test folder in your source and if you haven't set up Brackets for modifying the core code then the Run Tests option is disabled). Let's take a look at some of the tests run by examining the CodeHint-test.js (note, I have taken out much of the actual implementation for brevity's sake, but you can view the full source on GitHub).

beforeEach(function () {
   initCodeHintTest = _initCodeHintTest.bind(this);
   ...
});

afterEach(function () {
   ...
});

describe("HTML Tests", function () {
   it("should show code hints menu and insert text at IP", function () {
      var editor,
      pos = {line: 3, ch: 1},
      lineBefore,
      lineAfter;

      ...
      // simulate Ctrl+space keystroke to invoke code hints menu
          runs(function () {
         ...
         expect(codeHintList).toBeTruthy();
         expect(codeHintList.isOpen()).toBe(true);
      });

      it("should dismiss code hints menu with Esc key", function () {
         var editor,
         pos = {line: 3, ch: 1};
         ...
      });
   });
});

The key items I want to point out are how Jasmine defines a suite of tests and each individual test. First, beforeEach() and afterEach() do what you might expect and are used for setup and teardown of the tests. The describe() function defines a suite of tests which, in this case, are simply entitled "HTML tests." Individual specs are described within the it() function and generally start with "should" followed by an easy to read description of the expectation. Expectations are laid out using the expect() function which has a chainable API designed to be human readable (for example, it is pretty easy to read and understand the expectations being set in the code above). Finally, the run() function is used here because the we are testing asynchronous operations; for other types of tests the run() function is not required.

For a good beginner's tutorial on Jasmine, I recommend Dustin Butler's article on the ADC, "Unit Test JavaScript Applications with Jasmine."

Mustache

Mustache is a minimal templating framework for creating "logic-less" templates that is available in a lot of languages, including JavaScript of course. The framework got its name because the tags to be replaced are surrounded with "mustaches" (i.e. {{ and }}).The templates are called logic-less because they cannot contain things like if/else or for loops. Brackets uses Mustache for handling localizing strings within the UI.

Let's look at how Brackets uses Mustache by examining the save modal dialog template contained in main-view.html.

<div class="save-close-dialog template modal hide">
   <div class="modal-header">
      <a href="#" class="close">&times;</a>
      <h1 class="dialog-title">{{SAVE_CHANGES}}</h1>
   </div>
   <div class="modal-body">
      <p class="dialog-message">Message goes here</p>
   </div>
   <div class="modal-footer">
      <a href="#" class="dialog-button btn left" data-button-id="dontsave">{{DONT_SAVE}}</a>
      <a href="#" class="dialog-button btn primary" data-button-id="ok">{{SAVE}}</a>
      <a href="#" class="dialog-button btn" data-button-id="cancel">{{CANCEL}}</a>
   </div>
</div>

As you can see the items being replaced by Mustache are localized strings for the dialog title and the buttons. The strings that are being replaced are contained within the strings.js files organized by locale inside the nls folder. For example, here are the Spanish strings that will replace the button text.

"DONT_SAVE"                            : "No guardar",
"SAVE"                                 : "Guardar",
"CANCEL"                               : "Cancelar",

The actual string replacement using Mustache is done inside brackets.js during startup. The strings file is loaded via RequireJS's i18n plugin.

// Localize MainViewHTML and inject into <BODY> tag
var templateVars = $.extend({
ABOUT_ICON : brackets.config.about_icon,
APP_NAME_ABOUT_BOX : brackets.config.app_name_about,
VERSION : brackets.metadata.version
}, Strings);

$("body").html(Mustache.render(MainViewHTML, templateVars));

The first part is simply adding some build-specific strings to the Strings localization object we defined earlier (loaded in via RequireJS). The second part is then using Mustache to replace out the tags with the proper strings and placing that in the body of the main view.

LESS

LESS is a stylesheet language that extends CSS in a number of useful ways, allowing you to do things like avoid repeating yourself (in code) and better organize your CSS. For example, LESS includes things like variables within CSS, mixins for splitting out files and even functions and operations. Brackets, as a code editor, handles LESS files, but also uses LESS in all of it's internal stylesheets.

Let's look at working with variables in LESS by examining Brackets code. If you open brackets_colors.less for example, it contains simply a block of LESS variable declarations.

// use the "lighten" or "darken" function with integer multiples of this
@bc-color-step-size: 10%;

@bc-black: #000000;
@bc-grey: #808080;
@bc-gray: @bc-grey; // because people spell it both ways
@bc-white: #ffffff;

@bc-yellow: #b58900;
@bc-orange: #cb4b16;
@bc-red: #dc322f;
@bc-magenta: #d33682;
@bc-violet: #6c71c4;
@bc-blue: #268bd2;
@bc-light-blue: #d8e0e8;
@bc-cyan: #2aa198;
@bc-green: #859900;

You might even note that one variable is, bc-grey, is declared as equal to another, bc-gray. If you look through styles in brackets.less, for example, you'll see these variables referenced as they are below.

#indent-width-input {
   font-size: 1em;
   height: 12px;
   line-height: 1;
   vertical-align: -2px;
   border: 1px solid darken(@background-color-3, @bc-color-step-size / 2);
   color: @bc-black;
   margin: 0;
   padding: 0;
   width: 14px;
   position: relative;
   left: -1px;
   top: -2px;
   -webkit-border-radius: 2px;
   border-radius: 2px;
   box-shadow: inset 0 1px 0 rgba(0,0,0,0.1);
}

As you can see, bc-black, among other variables, is referenced in this class as in many others, meaning that if the color scheme changed, you'd only need to make the changes in a single location. It is worth noting here that variables in LESS have can be scoped globally, as these are, or locally within a class definition. As mentioned, LESS also includes mixins which work much like variables except they contain snippets of CSS and can even take parameters.

One thing you'll notice if you look through the Brackets source is that the import functionality is heavily used to break up the complex stylesheets into more manageable pieces. Much of this is done via the brackets_shared.less file which only contains import statements. This file is then imported into brackets.less.

@import "brackets_shared.less";

Brackets also makes use of functions and operations in LESS as can be seen in this snippet from brackets_theme_default.less.

@background-color-1: darken(@bc-white, @bc-color-step-size*2);
@background-color-2: darken(@bc-white, @bc-color-step-size);

In this case, we are using a function to darken the white theme color to create two variations. The first actually uses an operation to darken by two-times the value of the step size variable.

You can also see how LESS makes use of nesting in LESS, which allows you to write more readable CSS, often in fewer lines. For example, this snippet from brackets.less...

.resizing-container {
   position: absolute;
   top: 0;
   width: 100%;
   height: 100%;
   z-index: @z-index-brackets-panel-resizer;
   
   &.horz-resizing {
      cursor: col-resize;
   }
   
   &.vert-resizing {
      cursor: row-resize;
   }
}

...would be the equivalent of the following, where the nested definitions would each need to be broken out into separate class definitions, making the relationship between the classes less obvious.

.resizing-container {
   position: absolute;
   top: 0;
   width: 100%;
   height: 100%;
   z-index: 18;
}
.resizing-container.horz-resizing {
   cursor: col-resize;
}
.resizing-container.vert-resizing {
   cursor: row-resize;
}

Where to Go From Here

Obviously, there's a lot more to Brackets than I covered here. For example, if your development team is looking to define some code conventions, you might be interested in taking a look at the Brackets code conventions and borrowing some ideas. There is also the use of Twitter Bootstrap for UI elements like buttons and modal dialogs. You might also be interested in how Brackets uses WebSockets to handle the Live Preview functionality in Chrome (a topic that seemed well over my head). I also highly recommend David Deraedt's article on Brackets architecture from the ADC - while it is from a much earlier version of the Brackets source, many of the topics covered are still relevant and not covered here.

Comments

There are currently no comments for this entry...be the first!

Write your comment



(it will not be displayed)







About

My name is Brian Rinaldi and I am the Web Community Manager for Flash Platform at Adobe. I am a regular blogger, speaker and author. I also founded RIA Unleashed conference in Boston. The views expressed on this site are my own & not those of my employer.