I’m using Pux for an app, and my app has grown large enough to take almost 20 seconds to load and render the initial request. It seems like bundle-splitting and lazy-loading modules or routes is the only way to improve this metric, but I can’t find any examples of how to do this for an application following The Elm Architecture. Does anyone here have any resources or tips which can help?
According to the Elm roadmap It looks like their community hasn’t figured out code-splitting or lazy-loading.
To be frank, I feel like a total fool for adopting an app base built according to “The Elm Architecture”. My app depends on SEO and a big part of that is the speed of first page load & render (in Google at least). When choosing TEA, I thought I could just add a Webpack plugin which “turns on” this ability, but I see now that lazy-loading requires me to re-architect my app a bit to use a lazy-load module syntax (i.e. require.ensure("Abc")
or ES6+'s import("Abc")
).
I find it amazing that TEA has no answer for this – the shortcomings of TEA aren’t enumerated anywhere, but they are in fact quite large. I feel it needs to be said that TEA is really disappointing, and that newcomers should think hard about the pros/cons of TEA vs a more hand-written architecture.
Following, I describe the results of my research on the various ways to accomplish code-splitting and lazy-loading.
One technique is lazy-loading every single UI element, as proposed by the react-loadable people. It looks like Webpack/Rollup can code-split that pretty simply, but to add support server-side rendering requires a special babel plugin for webpack. I hate Webpack with a passion - it’s so opaque - so I’d like to avoid that.
A different technique is to have a “router” which is separate from the app. The router would explicitly lazy-load the corresponding view, shown in the example in this gist. The problem with this is that that routes table is included with the first page response, which means that “secret, admin-only routes” can’t be included in the app.
The last technique is to write each route as a separate app – each route’s page would have a separate Main.purs
and a user’s request for a subsequent route would hit the server which would respond with the JS app which renders that page, and the in-browser app would “activate” that lazy-loaded module by just executing it, like main();
. To me, this seems like the most desirable option. The only problem is build-system becomes a bit obtuse. Also, I would lose the HMR and time-travel debugging gasp! (Actually I find HMR and TTDB to be pretty low-value things, so I’d be fine losing those.)
I minor problem with all of these options is that PureScript doesn’t have a type-safe module importer, as that would require first-class modules. A workaround is to expect each lazy-loaded page to have the same interface, a main
function, which is probably ok.