Has anyone used lazy loading in a phoenix live view project?

I’m currently in a process of optimising the client part of a liveview app and wondered if anyone had tried using lazy loading with liveview.


My current approach is:

  1. Import heavy dependencies like charting libs with dynamic imports in liveview hooks:
async function createChart(config, el) {
  const [am4core, am4charts] = await Promise.all([
    import(/* webpackChunkName: "amcharts4-core" */ "@amcharts/amcharts4/core"),
    import(/* webpackChunkName: "amcharts4-charts" */ "@amcharts/amcharts4/charts"),
  ]);

  return am4core.createFromConfig(config, el, am4charts.XYChart);
}

const ChartHook = {
  config() {
    return JSON.parse(this.el.dataset.config);
  },

  async mounted() {
    this.chart = await createChart(this.config(), this.el);
  },

  beforeDestroy() {
    this.chart.dispose();
  },
};

export default ChartHook;
  1. Add chunkFilename: "[id].[contenthash].js" option to webpack config to allow for cache busting when the chunks change, note that this doesn’t affect the filenames of the entry points like app.js since the hash there is added by mix phx.digest

  2. (Optional) Compress /\.(js|css|svg)$/ via webpack using brotli, because why not (and in part thanks to #3840). This would mostly affect the chunks.

  3. Update Plug.Static options in endpoint.ex to include gzip: true, brotli: true

5 Likes

I have no experience/knowledge here but it sounds like a great idea! I wonder if there’s even a chance LiveView would welcome the changes upstream as a PR/if it would be easier to accomplish cleanly within LiveView itself if you fork it? Maybe open an issue on LiveView to see if they have thoughts/are open to it in a PR, or as a hook/config option to make it easier to do within a Phoenix app if it adds some complexity? And I’m curious what you find, it seems like a good idea, but I’ve never tried it in general so not sure how much complexity/what the tradeoffs are…

I think it’s easy enough to do without any extensions from liveview so I made a small example which roughly follows the steps outlined in OP: https://github.com/syfgkjasdkn/lazy-live-view/commits/master.

2 Likes

I’ve recently come across asset_import. You might find it interesting.

How do you handle .br (brotli) files since mix phx.digest skips digesting those files? I mean, in the end, you need to have something like:

app.js
app-[hash].js - generated by mix phx.digest
app-[hash].js.gz - generated by mix phx.digest
app.js.br - generated by webpack

What’s the easiest way for the br file generated by Webpack to end up with the same hash as the others?

What’s the easiest way for the br file generated by Webpack to end up with the same hash as the others?

I use chunkFilename: "[id].[contenthash].js" in webpack. I don’t think I need the hash to be the same as from mix phx.digest since I don’t reference these files in phoenix. Webpack does all the lazy loading.

I’ve recently come across asset_import. You might find it interesting.

Thanks, will check it out. But I think I like the dynamic import approach better.

How about your entry file? Or you don’t brotli compress that?

How about your entry file? Or you don’t brotli compress that?

That’s right, the entry file is only gzipped by mix phx.digest.

My entry file as well as the app’s css file would benefit from the brotli compression. I ended up writing a mix task that will add the hash to the .br files generated by Webpack.

1 Like

I’ve just checked and app.js and app.css get returned as brotli compressed in chrome and firefox with the above setup.

In prod or in dev? In dev it should work as is, as Phoenix is not referencing them by their hash name. But in prod, Phoenix will switch to the digested file from cache_manifest.json. In which case, the brotli equivalents will need to have the same filename in order to be used.

Also, my app is behind Nginx and you can configure Nginx to brotli compress on the fly or/and use static already compressed files. If you also use Nginx in front of your app, maybe you’re seeing the files compressed on the fly.

In prod. The file with a hash in its name is compressed. No nginx.

cache_manifest.json includes brotli files. I think it might be thanks to https://github.com/phoenixframework/phoenix/pull/3840