Recently I had the pleasure of trying HyperApp.js and described the experience in Pure programming with Hyper App. One of the first things I tried was a simple example. In the HyperApp architecture the app is decoupled from the "view" generation. You can use any virtual DOM library to actually produce the page elements.
In my case I wanted to use hyperx so my HTML example page looked like this
1 | <body> |
The first script works reliably - unpkg.com
is solid "NPM CDN". The second script https://wzrd.in
worked at first, but
then started returning 502 error. The https://wzrd.in
is "Browserify as a service". It is a valuable service, but I wanted something
that I could control in case it went down.
My second consideration was trying examples where several libraries had to be packed together to allow me to quickly try something. For example Cycle.js framework allows one to mix and match 3 different parts in one application: stream library, DOM layer and runner function. But there is no easy way to load these from a single script tag, thus one has to either pack these NPM modules locally and serve; or use one of the prepacked online playgrounds, like webpackbin.com. I do not like iframed playgrounds like this (jsfiddle, codepen, etc) because there is one level of iframed DOM indirection which makes exploring and working with the code harder for me.
Both choices require too much effort when all I want is to explore an example application. It is especially hard to justify when I want to include a new framework in an existing application to see / show how the two can inter-operate. Remember how simple it was to show Angular 1 in an existing static page or web application?
1 | <script src="cdn/angular.js"></script> |
A single script needs to be included and everything is working, even in a static page. I want the same for modern frameworks that require bundling first.
Lib bundle
webpackbin.com
does something interesting. It packs a specific
list of modules for Cycle for example as a bundle "dll.js". For example
https://www.webpackbin.com/bins/-KfROsIlNDdauJZGG8ep
has all Cycle dependencies packed into
https://cdn.jsdelivr.net/webpack/%40cycle%2Fdom%4016.0.0%2B%40cycle%2Frun%403.0.0%2Bxstream%4010.3.0/dll.js
.
Note that the bundle should be immutable, since the versions of the dependencies
is specified in the url. Unfortunately, I could not use the bundle due to
a WebPack limitation - I do not know where these three libs are in the
packed list. I know they are there, but their specific indices are hard to
figure out!
At this point, I stopped trying to load the webpackbin
"dll.js" and instead
decided to write my own webpacking micro-service.
Webpack as a service
I wrote web-packing - a tiny service one can easily host (Zeit.co Now works really well). The service allows playing with apps like Cycle.js without any problems.
Just give it a list of NPM modules to bundle and you will get an object
window.packs
. Every listed library will be there, camel cased. For example,
1 | <script src="http://where-is-web-packing/hyperx"></script> |
or
1 | <script src="http://where-is-web-packing/@cycle/run&@cycle/dom"></script> |
Pretty simple.
Speed
Installing NPM modules and bundling takes time, usually several seconds. Doing this every time someone requests a bundle is very wasteful. We can take a few shortcuts to save time and avoid repetitive work. For example, we can cache created bundles to quickly return them.
We can go one step further. We can return the bundle with the following response header to store the created bundle in the browser's cache and every proxy in between. This ensures that a client gets the bundle instantly on reload.
1 | cache-control:max-age=99999999, public, immutable |
See Mozilla docs for info on each property.
And just for kicks, I return the new ServerTiming header that shows how long the service spent creating the bundle.
1 | server-timing:install=2.024; "NPM install", bundle=0.136; "Webpack bundling" |
In Chrome this looks like this
We could even push the bundles to a CDN if necessary, but that would require a little more work.
Time to hack!
Special note on Cycle.js
One can run Cycle.js application directly using universal bundles. See Install without npm This approach can be a little bit user unfriendly. For example the first example:
1 | <script src="https://unpkg.com/@cycle/[email protected]/dist/cycle-run.js"></script> |
The execution crashes on startup with the stack trace
1 | Uncaught TypeError: Cannot read property 'default' of undefined |
Only by looking at the code we can find the offending line:
1 | sinkProxies[name_1] = xstream_1.default.createWithMemory(); |
We need the library xstream
and turns out it needs to go first in the
list of scripts
1 | <script src="https://unpkg.com/[email protected]/dist/xstream.js"></script> |
Good, but not great.