Efficiency and performance
Performant rendering
Dynamic website content - including JavaScript - has been a part of the web for many years. Websites have long been able to include scripts that manipulate the DOM to add, update or remove content. The origin of the web, however - and what remains one of its key features today - is a foundation on static content. Browsers’ DOM implementations have been optimized over time to render static document content as efficiently and quickly as possible to end users.
As more complex web applications have appeared in recent years, browsers have answered with DOM performance optimizations that favor more dynamic content. However, in order to render their user interfaces, web applications still need to interact with an imperative API that has remained mostly unchanged for decades. Modern web applications designed around reactive data propagation need a more efficient way of translating their user interfaces into a webpage’s DOM.
Dojo abstracts the DOM away from applications and promotes the use of reactive state flows to minimize application boilerplate while also allowing for increased rendering performance. Widgets output virtual nodes from their render functions which represent the widgets' structural representation within a virtualized DOM. The framework then handles the process of rendering changes to the VDOM across renders in the most efficient way possible, only affecting concrete DOM elements that actually require changing.
Dojo provides another DOM abstraction layer through its middleware system for applications that need concrete information from the DOM to implement their requirements. Dojo middleware solves a variety of these concerns in a consistent manner while still supporting reactive data flows across an application.
Application delivery - layering and bundling
As web applications grow in size, it becomes inefficient for users to load all application resources when only a subset may be required for a given task. Every application resource has a cost associated with its size: memory storage requirements, data transfer over the network; all impacting the time a user needs to wait for before they can begin their work. It is in the users’ best interests that this cost be kept to a minimum by having applications only load what is needed, when it is needed.
Fetching an application resource incurs additional overhead around HTTP resource negotiation. Data needs to be requested by the client, after which the client has to wait before the server finishes sending the last byte of the resource. In more severe cases, the overhead can also include DNS resolution, full TCP connection re-establishment and TLS cipher/certificate negotiation.
Browsers are efficient in minimizing this overhead, but they cannot eliminate it entirely - a web application still has its own part in the responsibility of minimizing resource transfer overhead. The overhead associated with fetching application resources is relatively static when compared to the size of a given resource. Fetching a 1 KB file incurs similar overhead to fetching a 100 KB file.
Overhead can therefore be minimized in two ways: by decreasing the total number of resources, and by increasing the size of a single resource. Web applications can achieve both by layering and bundling related resources.
A single layer should contain the set of resources related to particular functionality within an application. When a user accesses the functionality, all resources in the layer are likely to be loaded around the same time. Everything comprising a single layer can then be bundled together into a single file for more efficient delivery to the user.
Automated layering
When using Dojo’s routing system, applications can benefit from automatic layering and bundling. An application’s top-level routes each become a separate layer, and Dojo’s build system will automatically generate bundles for each subsection. This gives layer separation and resource bundling without needing any additional tool chain configuration. There is a tradeoff with this automation in that common dependencies shared across multiple layers are inlined and duplicated within each bundle.
Declarative layering
Complex applications may require more fine-grained control over their layer/bundle definitions. For example, if an application has a set of shared dependencies that are used across multiple routes - rather than inlining/duplicating the dependencies within each route’s bundle, it may be desirable to extract the shared dependencies to their own bundle which can be lazily-loaded on first reference.
Dojo’s build pipeline allows for designating resource bundles within an application’s .dojorc
build configuration file, and can automatically convert module dependencies that cross bundle boundaries into lazily-loaded references.