A few weeks ago we rolled out major changes to the underlying structure of Prose. We’ve now tested and fixed issues over the last couple of weeks and we’re excited to annouce that Prose has been refactored with established Backbone.js conventions.
This is exciting news that follows up on the announcements of new features and improved media support back in May. In June and July, Tristen and I embarked on a refactor of the underlying structure of Prose with the goal of building a more robust application and we’ve now completed this work.
This work builds on the mission of Prose to be a viable alternative to traditional content management systems, replacing heavy backend infrastructure and databases with text files, leveraging the power of
git for version control and relying on Jekyll for quick static site generation. In addition to concrete performance gains, the revised model layer unlocks the potential for more complex functionality that could be implemented in the future, such as comparing diffs of previous versions of a file.
With dozens of contributions and over 10,000 users, Prose is supported by a huge and growing community. By embracing standard Backbone.js conventions, this refactor opens up Prose to even more contributions. Developers who have worked on other Backbone.js applications should feel comfortable reading through Prose’s codebase and contributing. By lowering the barrier to community contributions, we hope to empower the users of Prose to play a greater role in the continuing development of this project.
Originally, Prose only used views and a router from Backbone.js, communicating with the GitHub API through a handful of functions that wrapped methods from github.js. However, the GitHub API is essentially a CRUD backend and, with a few minor tweaks, Backbone.js offers great support for syncing models between the client and server.
The first task was sketching out the relationships between objects accessible through the GitHub API, and determining how to break up the current large views into a nested structure of subviews, each with its own discrete functionality.
We built Backbone.js models and collections to mirror the API objects, and gradually broke functionality out of
models.js and reimplemented it in the appropriate models. We then broke the large views into smaller pieces, each responsible for presenting a limited amount of information and only handling interaction in its immeadiate context. Throughout this process, we emphasized a cleaner separation of concerns, such that models would only be responsible for managing data, while views would only be responsible for presentation and handling UI interaction. This enabled us to use functionality like Backbone’s
model.isNew rather than parsing content or inferring state from the DOM.
We used a dependency injection pattern to provide references to model dependencies and related UI components to subviews. This helped ensure that views would not render prematurely or re-render themselves unnecessarily (both of which happened when rendering on
To avoid memory leaks caused by failing to unbind event handlers, we used Backbone’s
listenTo method and an object on each view to keep track of all child views. Then, we extended Backbone’s
view.remove method to iterate over all child views and
invoke their respective
We refactored the templates in Prose to not depend on global state, and then defined single variable names for each template to avoid the
with statement, significantly improving rendering speed. We also used
DocumentFragment to improve performance in views responsible for rendering many subviews.
During this refactor, there was an emphasis on making Prose accessible to users around the world. Tristen internationalized the UI using the Transifex platform, which has already attracted translations in German, French, Brazilian Portugese, Romanian, Russian, Vietnamese and Chinese. There are future plans to enable a non-English default language for multilingual sites.
- Rendering several subviews individually rather than waiting for a single large view to render reduces perceived page load time.
- Switching from a custom
$.ajaxseamlessly added support for conditional requests and delegated
_config.ymlcaching and invalidation to jQuery rather than handling this manually through
Repoview navigation now filters and re-renders the
Filessubview, rather than requiring a full page reload, significantly speeding up navigation.