Last week I introduced you to one of the original animating ideas behind Inkling Habitat, treating books as software to transform the eBook production process. Today I'd like to take you behind the scenes and show you the technologies and processes we used to build Inkling Habitat itself. How did we build this software?
First, Inkling Habitat is a client side application that runs inside your web browser, built with JavaScript, HTML, and CSS. The client-side portion is roughly 100,000 lines of JavaScript, which is a big application.
Going deeper, much of the application follows what's called a Model View Controller (MVC) architecture. We use Backbone.js as our general MVC system, while using Handlebars.js as the templating language for rendering our views.
Our philosophy on the team is to only use small targeted micro-libraries like Backbone.js rather than large encompassing frameworks like JQuery or Ember.js. We target plain JavaScript, taking advantage of the fact that the web platform has gotten much stronger the last few years with additions like querySelector
and CSS Flex Box layout.
We are aided by the fact that we only target the latest version of Chrome, making it a joy to develop as we get to target the leading edge of the web rather than the trailing edge, such as old versions of Internet Explorer or old versions of Safari. This lets us stay very nimble and focus on interesting functionality and user experiences rather than suffering from annoying browser bugs. We get to be very modern in our JavaScript and web development practices.
Early in our history we used a little bit of MooTools, but found it frankly unnecessary. We have mostly ripped all of it out of the system and are close to eliminating it.
In order to keep our CSS manageable we use SASS, which makes large scale development with CSS much easier. We also use Compass above that as a kind of reusable set of SASS primitives that we can lean on.
We have a very strong testing culture at Inkling with 100% unit test coverage on the Inkling Habitat team. All code checked in is required to have tests. A rule of thumb is that you will have 2X as many lines of testing code as you will of application code. This has held true for us, and we have roughly 200,000 lines of testing code to our 100,000 lines of application code. This has allowed us to scale the team in terms of people and features in a way that can be difficult with traditional JavaScript. Since JavaScript can be such a loose, run-time language, you really need a strong testing infrastructure to catch development time problems and allow many people to work together on such a large JavaScript application as Inkling Habitat.
Our unit testing framework is Jasmine.js, which I've really enjoyed working with. Our integration testing framework is Selenium driven through Node.js, so all of our integration tests are themselves written in JavaScript.
We lean on a number of other small open source libraries in places, such as URI.js for parsing and modifying URLs; MathJax for rendering MathML; Ace for our HTML code editing; Q for JavaScript promises to make asynchronous code easier to work with; zip.js for efficiently generating client-side ZIP files using Web Workers; Underscore.js as a small helper library for basic functions; the Zynga Scroller for displaying larger images in a way that can be 'zoomed' around in an attractive way; and js-beautify for pretty printing HTML and XML code.
Inkling Habitat on the client side is well encapsulated from the server. The server exposes clean RESTian APIs that have a number of resources we can lean on, creating a nice division of responsibilities. Behind these RESTian APIs is a mix of Python and Scala; I won't go into the server side of the system as I concentrate on the client side, and we are well encapsulated on the client side from those details.
The system itself is divided into several major areas of functionality. We have our blueprints, which are interactive pieces of functionality that a user can add to content, such as slide lines, assessments, audio/video, maps, etc. These blueprints can be previewed and edited. Behind these blueprints is actually an XML-based dialect rather than HTML. We have a simple XML binding infrastructure to pull these different blueprints in and turn them into Backbone models. Backbone views can then manipulate the Backbone models behind these blueprints for editing and previewing; as they do the XML behind them seamlessly update and can be saved when the user is finished. We call this simple binding system XML Templates, as they are roughly similar to Handlebars templates but for XML.
Next, we have a general document infrastructure to deal with our different kinds of documents, whether they are HTML5-based or the XML-based blueprints. The general application shell works with this document layer in order to provide consistent navigation, saving, URL history, etc. independent of the kind of document we are dealing with. Layered on top of our general document types is our proofing infrastructure, which allows us to create tickets on either blueprints or HTML content.
Next, we have our rich text editing system. This includes our code editor based on Ace and the rich text editor itself for visually editing HTML. These two editors are bidirectional, allowing someone to either directly edit the markup or visually edit the preview depending on their needs.
The visual HTML editor itself wraps ContentEditable under the covers and includes several fixes for ContentEditable to work as expected, mostly around ensuring that visually edited markup stays as semantic HTML. We also have a bidirectional undo/redo system that works across both the HTML code editor and visual editor. The rich text editor also includes our pattern picker, which makes it easy to drop in chunks of content.
Finally we have other general parts of the system, such as our automated reports to discover problems in content; advanced find across the ebook being worked on; chapter overview for getting a birds eye view of all the content in the system; templates; the dashboard to view many projects at once; etc. All of these are built with JavaScript on top of Backbone.
Our JavaScript build system is currently based on Ruby; we use Sprockets in order to deal with dependencies and to merge together all of our JavaScript and CSS assets into larger resources to make page loads much quicker. We are actively moving towards Node.js for our build system.
We also have a small middleware layer that is based on Python and Django that allows us to inject all of our resources at page load into the initial page request so that page load performance is much faster. We are also actively moving to Node.js for this as well, so that we can have a single JavaScript stack for our application, our build system, and our very small middleware layer. The server-side RESTian APIs will remain as Python and Scala.
We have a very strong culture of shipping, are action oriented, and work iteratively. We are on two week sprints and are continuously getting code out the door.
At the same time we also have a very strong product and UX culture, so we're constantly asking "are these the right features that serve peoples' needs" and "is this the best user experience possible?" We actively work with prototypes, visual design, and user research as we are developing, creating a great mix of shipping (Real Artists Ship) and continuously refining the direction that we are going. This produces a nimble culture of shipping and doing the right thing at the same time.
We internally use GitHub and have a strong code review culture. Everything that lands on master is reviewed by two people. GitHub is great to work with; our internal revision control system is Git itself for all of our code. The combination of strong tests and code reviews makes for a robust culture in terms of quality.
In all of our sprints we generally try to spend ten to fifteen percent of our time addressing tech debt, and work hard to both ship great features and UX while also addressing tech debt and making sure that we keep our code and architecture elegant and straightforward.
We also have a strong software craftmanship ethos. We think both the little things and the big things are important. If the small things are clean in your code (and in the UX!), then the big things will also be clean. We really take pride in the code we create.
Engineers are a big part of also having empathy with our users and working in conjunction with product managers and user experience folks to craft what's the right thing for our users and customers. Engineers regularly meet with actual customers and work in conjunction with the team to craft the best solution to problems.
Sound like an interesting way to pass the time? Consider joining the Inkling Habitat team and redefining how digital publishing is done! Feel free to email me if you are interested.
Subscribe to my RSS feed and follow me on Twitter to stay up to date on new posts.
Please note that this is my personal blog — the views expressed on these pages are mine alone and not those of my employer.