Of course, there are many more important advantages to building it on the web stack:
It’s true that you lose some safety nets by programming in JS or similar languages-strong typing in particular. And without strong typing, you also lose a lot of tooling niceties like compile errors and refactoring. There have certainly been enough times when one of us has facepalmed over making a mistake with
We ended up starting with the following:
We considered a few other kinds of frameworks, but haven’t yet included them in the core app:
$.triggerHandler()) from models in order to notify views and other modules when underlying data changes.
$.Deferredpattern, in which an asynchronous function returns a promise object to which callbacks can be attached in a chained fashion. We’ve also built a set of async utilities on top of $.Deferred that make it easier to manage parallel/sequential asynchronous operations.
Since it’s still early days, there are a number of pieces of the Brackets codebase that aren’t as clean as they could be. For example, the ProjectManager module currently mixes model and view code, and will be refactored eventually. Also, the Document and Editor classes aren’t cleanly separated because they both rely on an underlying CodeMirror instance, and CodeMirror itself is not publicly factored into model and view modules (though it does have a model/view separation internally). We’d like to work on improving this separation in CodeMirror so that we can fully decouple these classes in Brackets, and have Document really be a purely model-only class.
Extensibility has been a key goal of Brackets since day one, but here as elsewhere we’ve been trying not to over-architect things-and one of the nice things about building in HTML/CSS/JS is that we don’t necessarily have to. On a platform like Eclipse that’s built in a standard OO language, every single extension point (especially in the UI) has to be explicitly thought of up front and architected into the system. In HTML, it’s almost the opposite; an extension that we load into Brackets can inject or modify anything it wants in the DOM. This potentially gives extension authors great flexibility, but of course also means that over time we might inadvertently break extensions that make too many assumptions about the structure of the DOM.
Our approach has been to start by defining formal APIs in a few key areas that we know extensions will need to augment, such as menus and keyboard shortcuts, as well as adding extensibility APIs for specific features like Quick Edit and Quick Open, so extensions can define their own inline editors and language-specific logic. (There are still a few key areas missing, notably the ability to add code editor themes and new language-specific modes-though we get a lot of modes for free from CodeMirror itself.)
We’ve also been thinking ahead to other kinds of UI that extension authors will want to create. Our goal with Brackets is to keep the UI simple and clean, so we’d want to encourage extensions to keep within our existing UI patterns as much as possible. To that end, we’ve started a document describing extension UI guidelines that describes where we’d like to go with the UI. Not all of these are implemented yet, so this is a good time to send feedback about extension use cases you think would be valuable to consider that don’t fit into these areas.
In addition to the formal APIs we define, though, we’d like to encourage extension authors to try out other kinds of UI as well. If you find that you want to create a kind of UI that we don’t formally support, and want to prototype it by hacking into the DOM, go ahead and give it a try-and start a conversation with the community on the brackets-dev forum. If it makes sense to generalize what you’re doing, we’ll definitely look into adding formal support in the core.
Aside from the UI, another key area for extensibility is in file access-right now we only access the local filesystem, but over time we’ll need to make it possible to access files hosted elsewhere, in the cloud or in source control systems. We haven’t built this out yet, but will definitely need to do so in order to bring Brackets into the browser, and will have to think about things like local caching and synchronization for offline work.
As we mentioned at the beginning, Brackets is currently packaged as a desktop application, in order to support a traditional, local-file-based development workflow. Because we also want to deploy Brackets through the browser, though, we’ve written as much of Brackets in HTML as possible. In general, our guiding principle has been to implement all functionality in HTML except where absolutely necessary.
The main pieces that we’ve implemented (or expect to implement) natively are:
One difficult call we made was to implement dialogs (other than file and folder open/save dialogs) in HTML only, rather than using native dialogs. This does run the risk of having the application feel less native. However, it’s much more difficult to build an API that abstracts across HTML and native dialog layouts (unlike menus, where it’s fairly easy to abstract across the two).
Our current implementation is based on the first version of the Chromium Embedded Framework (CEF), which is an API for embedding the Chromium browser engine in a standalone application. We’ve currently implemented low-level local filesystem access as V8 extensions to within the CEF shell, with an API based on the Node.js filesystem APIs. From the main Brackets code, we use a JS-based wrapper that mimics the HTML5 NativeFileSystem APIs (though they’re not fully implemented in our shell yet).
We’re in the process of migrating to CEF3, the latest version of CEF, which is based on the Chromium Content API and should provide a more maintainable base going forward. CEF3 is more heavily asynchronous internally-CEF1 was single-process, whereas CEF3 has separate browser and render processes; this is obviously better for performance in general, but makes the native extension implementation more complicated, and exposes some bugs in the core Brackets code where it’s not properly handling asynchronicity. We’ll be cleaning this up over the next few sprints.
We’ve had a great time building Brackets in HTML/CSS/JS so far, but as with any technology stack, there are things we’ve struggled with along the way. Here are a few:
this. ‘Nuff said. It’s still pretty easy for us to accidentally use
thisin a closure without getting it bound properly. For closures nested inside a prototype method, we use the
var self = thistrick in the outer scope, so we can use
selfinside the nested closure. When the handler is a separate method, we
bind()and reassign the handler in the constructor.
Even at this early date in the evolution of Brackets, we’ve learned a lot about building complex HTML/CSS/JS applications, but we know there’s much more to learn, and many ways we could be improving our code. If you’re a web development expert, please check out the Brackets github repo and let us know what you think, good or bad. You can file architectural and feature suggestions as well as bugs in our issue tracker. Even better, please fork us, submit some pull requests or build some extensions, and help us make Brackets even better!
Narciso Jaramillo (@notwebsafe)