Writing a single-page application | Up | Optimizing Big Applications with Reference Elements
As seen in the previous chapters a webapplication written with the help of Fmlib_browser has the following essential components.
type state
type msg
val view: state -> msg Html.t
val subscription: state -> msg Subscription.t
val update: state -> msg -> state -> msg Command.tThe type state contains all data the application needs to operate. All changes are done via message passing by using object of type msg.
The view function maps the state of the application to a virtual dom of type msg Html.t (see Fmlib_browser.Html). Each element in the virtual dom might contain interactive attributes which are clickable, can enter text, ... (see Fmlib_browser.Attribute). Each interaction with an interactive element in the virtual dom creates an object of type msg which is dispatched to the application via its update function.
The view function is called usually on each animation which is typically 60 times per second. It is called at each animation only if the state has changed.
A second way for the application to get messages is via subscriptions (see Fmlib_browser.Subscription). Via the subscription function the application can depending on the state subscribe to global events like interval timers, windows resize, mouse clicks an keyboard activities which bubble up to the windows object of the browser, ...
Each time the state changes the function subscription will be called to get all the subscriptions needed in the new state.
All messages passed to the application go through the update function. The function receives the current state and a message and it produces a new state and a possibly empty set of commands.
Commands can be used to send http requests, get the current time, generate a random number, focus an element, put something into or get something from the browser local or session storage (see Fmlib_browser.Command). Very complex commands (e.g. get a random number, then make a http request, then get the system time and finally put all results into one message) can be structured via tasks (see Fmlib_browser.Task) which are triggered via a command.
In many applications the most expensive part is accessing the DOM. In the library there are several mechanisms to make DOM changes cheap.
In the browser there is an event loop.
+------------------------------>+-------->+
^ | |
| | rAF |
| | |
+--+--+--+ | | style |
| | | | | event loop | |
+--+--+--+ | | layout |
| | |
Action Queue | | render |
| v v
+<------------------------------+<--------+
The event loop runs very fast. The event loop pulls out the next atomic action from the action queue, runs it and then enters the next iteration.
If a user interaction (e.g. a mouse click) happens on an element of the dom, the corresponding message is generated, the message is fed into the update function, the new state is recorded and the first action of the corresponding command is entered into the action queue. The actions are executed in the next iterations of the event loop.
The same happens if message is generated from a subscription.
The inner event loop iterates until the next animation frame happens. Each animation frame the event loop runs the extended event loop before pulling out the next action from the action queue. Usually the browser has 60 animations per second i.e. the animation frame is executed every 16ms approximately.
If your application has subscribed to the animation frame (see Fmlib_browser.Subscription.on_animation rAF is run i.e. the message for this subscription is generated and fed into the update function.
Even if your application is not subscribed to the animation frame, the Fmlib_browser accesses during rAF the dom.
old_state == new_state.view function.The Fmlib_browser minimizes accesses to the dom and accesses the dom only during animation frames.
In a previous chapter it has been stated that the state should be immutable. I.e. state changes happen by the expression
{state with field1 = ...; fieldn = ... ; ... }This means that the OCaml compiler generates a new state object which has the content of the old object with some modifications. If the state contains a mutable field5 an update of the state could happen by the expression
state.field5 <- ...;In this case the compiler does no create a new object but modifies the object in place. If this is the only modification of the state, then Fmlib_browser does not recognize the state as changed and does not update the dom in the next animation frame.
Therefore the first recommendation in the introduction has been, Do not do that!. However this rule is too strong. The library Fmlib_browser holds only one reference to the state. Therefore in principle it could be modified in place as long as the modification does not affect the view function.
Now the more precise rule:
Every update of the state which affects the result of the view function must have the form
{state with field1 = ...; field2 = ...; ...}Modifications of the state which do not change the result of the view function can have the form
state.field5 <- ...;
This opens up the room for more optimizations. Your application might have messages which do not change the rendering of the page. For these cases it is possible to record the changes caused by these messages in mutable fields in the state. Then nothing is done in the next animation frame.
Example: You have a complex computation to do in your application but you don't want to block the event loop for the whole computation. So you split the computation into small steps. Each step creates at the end a message which is fed into the update function. The update function just creates a command for the next step and records the intermediate state of the computation in some mutable fields of the application state. Then nothing is done in the next animation frame. Only the last step of the computation is recorded in the application state in immutables fields, a new object is created by the OCaml runtime system and the view function will be called in the next animation frame and the result is displayed.
A word of caution: Such optimizations only make sense if your virtual dom is really huge. For reasonably sized virtual doms the optimization effect is negligible.
If the dom of an application is huge such that virtual dom diffing can have a significant impact on performance (i.e. blocking the event loop in the animation frame too long) consider the use of reference elements where the part of the application state visible in reference elements can be stored in mutable fields.
The library Fmlib_browser offers different types of applications.
Sandbox applications: sandbox and sandbox_plus
sandbox_plus has the additional possibility to subscribe to events.Element applications:
The purpose of element applications is to introduce the library Fmlib_browser smoothly into applications already written in javascript.
push_url etc.Web applications: basic_application and application
basic_application cannot be initialized by javascript code, cannot send and receive messages from and to javascript code and cannot access the browser history.application can be initialized by javascript code, it can communicate with the javascript code and has access to the browser history via Fmlib_browser.Command.push_url etc.application all user clicks on anchor elements with hrefs are intercepted and the application has full control on what happens with these clicks (see doc_single_page_application).Full web applications can do everything what can be done with pure javascript code in the browser. Anything no directly supported by the library Fmlib_browser can be done by writing javascript code in the browser and trigger the corresponding operations by sending messages to javascript and receive messages from javascript.
There are commands to send a message to javascript and the application can subscribe to messages received from javascript.
Element applications and full web applications can communicate with javascript code in the browser.
In order to receive initialisation data the web application is given unique name which is visible in the namespace of a browser window.
This name refers to an object which has a method to receive javascript initialisation object.
On the application side a decoder object is needed to transform the javascript object into the OCaml word.
See Fmlib_browser.element and Fmlib_browser.application for details.
The can issue the command send_to_javascript which sends a javascript object made by Fmlib_browser.Value.
The javascript code can subscribe to the messages from the application via the unique name of the application. The name refers to an object which has a corresponding method.
Furthermore object representing the application has a method to post a message on the javascript side to the application. The application can subscribe to the messages from the javascript side by providing a decoder to transform the javascript object to the OCaml world.
See Fmlib_browser.element and Fmlib_browser.application for details.
Writing a single-page application | Up | Optimizing Big Applications with Reference Elements