Express Prep Client
Express extension to patch the client runtime based on feature detection.
TL;DR: With the traction behind evergreen browsers, and the new process behind ECMA TC39 committee to update the language more frequently, you should have a reliable way to patch the client runtime before executing any application code.
Goals & Design
This component is a conventional express
extension, which means it will extend the functionalities provided on express
by augmenting the express app with new features. express-prep-client
augments the express app instance with two new method: app.prepClient()
and res.prepClient()
.
These two methods help you to set up a set of rules at the app level, and at the request level that should be applied to the client runtime before any code gets executed in the browser. In fact, it acts like a shim for a promise based API to guarantee that the runtime is ready before releasing the control to the real promise.
How does this work
This package relies on express-prep-client package to serialize configuration and bootstrap methods to the client side. It provides a simple API to define async methods (that might or might not be promise based), and these methods (one or more) can be global methods or under a particular namespace. This package will shim those methods to provide a reliable way to call them from the browser but holding the actual execution of those methods until all the necessary patches to be applied to the runtime to execute the real methods under the hood.
Why doing all this on the server side? The reality is that you could do all this manually on your templates directly, but when you have multiple pages, multiple variations of the things you want to patch, things become a little bit more complex. While using the server and monadic methods that can be serialized and executed on the client side can provide you the right level of abstraction.
Installation
Install using npm:
$ npm install express-prep-client
Usage
Extending express functionalities
Here is an example of how to extend an express
app with express-prep-client
:
var express = app = ; // extending the `express` app instance using extension pattern described here:// https://gist.github.com/ericf/6133744; // using the new `prepClient` method off the app instanceapp;
Keep in mind that you can add as many prepClient()
conditions as you want.
note #1: we don't use yepnope component explicitly because it is too big to be embedded in every page, instead we implement a narrow version of its API.
note #2: we expanded the yepnope API to support testFn
as a method that can be serialized and execute on the client side, the result of this method will have precedence over the test
value if specified.
Setting up the page
Any patch applied thru app.prepClient()
and/or req.prepClient()
will automatically be exposed into the state
variable in your template engine (see more details on express-prep-client documentation). If you use handlebars
you will do this:
And this is really the only thing you need to do in your templates. In the example above, we are reling on the default trigger (app.init()
, which returns a promise) to initialize the application in the client side, but you can customize that as well.
Custom initialization
As you saw in the previous example, by default, this package will bootstrap the initialization thru a app.init()
method. But it doesn't have to be that way. You have full control on how your application code will try to initialize the application itself. Here is an example of how to define custom shims when extending the express application:
;
Then, in your template, you will have to use the new namespace:
This means that express-prep-client
will shim the call to Foo.Bar.init()
, System.import()
and MyApp.ready()
methods until all the async patches are applied, then it will release the control, calling them in the same order they were called the first time, with the exact same arguments.
app.prepClient() vs res.prepClient()
This is an example of polyfilling the global.Intl
when missing:
app;
Since the polyfill requires additional locale-data
to function for a specific locale, applying the fr-FR
locale data to support number and date formats can be done conditionally at the request level:
app.get('/french-page', function (req, res, next) {
// patching IntlPolyfill with `fr-FR` locale-data for a specific req
res.prepClient({
testFn: function () {
return !!window.Intl;
},
yep: 'https://rawgithub.com/andyearnshaw/Intl.js/master/locale-data/jsonp/fr-FR.js'
});
res.render('page');
});
In the example above, there are two small details that should be taken in consideration:
locale-data
is conditionally loaded based on whether or not thewindow.Intl
is available, that means the polyfill itself and the corresponding locale date will be based off the same condition.- All patches are loaded in parallel, at least for all modern browsers, while the execution of the code is preserved based on the other of patches definition. This means that
locale-data
will be set thruwindow.IntlPolyfill.__addLocaleData()
API without triggering a race condition.
License
This software is free to use under the Yahoo! Inc. BSD license. See the LICENSE file for license text and copyright information.