37° 48' 15.7068'' N, 122° 16' 15.9996'' W
cloud-native gis has arrived
37° 48' 15.7068'' N, 122° 16' 15.9996'' W
cloud-native gis has arrived
37° 48' 15.7068'' N, 122° 16' 15.9996'' W
cloud-native gis has arrived
37° 48' 15.7068'' N, 122° 16' 15.9996'' W
cloud-native gis has arrived
37° 48' 15.7068'' N, 122° 16' 15.9996'' W
cloud-native gis has arrived
37° 48' 15.7068'' N, 122° 16' 15.9996'' W
cloud-native gis has arrived
37° 48' 15.7068'' N, 122° 16' 15.9996'' W
cloud-native gis has arrived
37° 48' 15.7068'' N, 122° 16' 15.9996'' W
cloud-native gis has arrived
37° 48' 15.7068'' N, 122° 16' 15.9996'' W
cloud-native gis has arrived
37° 48' 15.7068'' N, 122° 16' 15.9996'' W
cloud-native gis has arrived
Maps
Engineering
Hot reloading: a faster way to develop modern apps
Hi, I'm Jason Axelson and I'm a software engineer at Felt. Felt is a highly interactive multi-player application built for making the best maps on the internet,
Hi, I'm Jason Axelson and I'm a software engineer at Felt. Felt is a highly interactive multi-player application built for making the best maps on the internet,

and while I focus primarily on the backend of our application, I also touch the frontend and work with our frontend developers. That work includes common cycles in frontend development: edit, save, refresh. That "refresh" step can take a lot of engineering time and effort for a complex web application due to the length of the feedback cycle. This post is about the ways Hot Reloading helped our team shorten this cycle, and how to apply it on your React apps that are built with Webpack and are using Phoenix as the backend.

Live Reloading, Hot Reloading and Phoenix

The two general approaches to shortening the refresh feedback cycle are: live reloading and hot reloading.

Live Reloading is when the browser automatically does a full page reload after a change is made. This eliminates the need for the developer to alt-tab over to the browser and manually hit <p-inline>Cmd+r<p-inline>. Phoenix supports live reload out of the box via Phoenix LiveReload. You can configure Phoenix LiveReload to watch a set of file patterns. When any of the files in those locations change (or a new file is added) Phoenix LiveReload initiates a full page reload. Apps created via <p-inline>mix phx.new<p-inline> have Phoenix LiveReload configured by default.

But, there is a downside to those full page reloads. When developing a highly interactive web application, one of the frictions encountered is that it becomes time consuming to reproduce the state that is needed to test a particular feature after a full page reload. For example, imagine that you're implementing an undo/redo stack; you want to change some wording or styling on an element, but that element only shows up after you have already created some data and then hit undo a couple times. It is time intensive to redo those steps each time you want to make a change. This is where hot reloading comes in. Hot Reloading is when parts of the page update without a full page reload which creates a significant time-savings when building complex web applications.

React Terminology and Caveats

Since we're dealing with Hot Reloading for a React app, some react terminology is in order. The term 'Hot Reloading' used in this post is used in the general sense to refer to changing the content on the page but without a full page reload (the definition of Hot Reloading that I'm using here isn't 100% agreed upon, but it seems to be the predominant usage of the term). Previously Hot Reloading for React was done with a technique called Hot Module Replacement, for technical reasons that approach is no longer used and instead React provides an API for "Fast Refresh." Fast Refresh is what we'll be using.

I’d be remiss if I didn’t mention that Hot Reloading has drawbacks as well. Some code does not play well with Hot Reloading, particularly if you’re changing code that initializes state on the page since that code will not be rerun when the Hot Reload executes.

Also the @pmmmwh/react-refresh-webpack-plugin that we’ll be using is marked as experimental. Also sometimes after running for a while the webpack-dev-server ends up eating gobs of memory and will need to be reset. However, if you keep these caveats in mind and do an occasional manual refresh when needed Hot Reloading can be a powerful tool.

Note: The rest of this post assumes that you're using Webpack 5 and (to a lesser extent) Typescript.

Phoenix Caveats

With the release of Phoenix 1.6 many developers are moving on to ESBuild since that is the new default in the Phoenix 1.6 generators. But if you already have a webpack setup that you're happy with it may not be worth it to migrate to ESBuild at this point since ESBuild does not support all of webpack's features (of which Hot Module Reloading is one).

Phoenix generally tries to stay out of the front-end packaging world (hence the move from webpack to ESbuild) and minimize front-end touch points, but there is enough complexity that a blog post about the setup seemed warranted.

Okay, you've sold me, now how do I get this to work?

There's multiple different approaches that can be taken for Fast Refresh but the one that I'll describe today is based on a combination of <p-inline>@pmmmwh/react-refresh-webpack-plugin<p-inline> and the webpack-dev-server.

npm

First in a terminal window in your repo's assets directory run:

webpack config

We’re going to put the webpack-dev-server in front of our phoenix application in development. This will let Webpack inject a websocket into the page so that it can notify the browser when it is time to do a Hot Reload. To make other developers' lives simple we’re going to tell the webpack-dev-server to start on port 4000 and proxy all the requests that it doesn’t know how to handle to the Phoenix server which we’re going to modify to listen on port 4001. So now the browser will still make requests to http://localhost:4000 and they will be transparently routed to Phoenix.

The browser sends requests to webpack-dev-server on port 4000 which handles requests it knows how to handle, and forwards all other requests to the Phoenix server running on port 4001.

Okay, here comes the scary bit. Now we need to open up <p-inline>assets/webpack.config.js<p-inline> and add the following as top-level keys in the webpack configuration:

Yes there are three different websockets listed. The first is for Phoenix LiveReload, the second is for Phoenix LiveView, and the third is for Phoenix Channels. It's possible that you may have customized the locations of these. For the Phoenix LiveView and Phoenix Channels socket look for any <p-inline>socket/3<p-inline> declarations in your MyAppWeb.Endpoint module.

In the loader configuration find your ts-loader (assuming you’re using typescript) configuration and add the <p-inline>getCustomTransformers<p-inline> and <p-inline>transpileOnly<p-inline> settings (note that the <p-inline>isDevelopment<p-inline> variable was set in the earlier snippet):

Then you need to add the ReactRefreshPlugin to the “plugins” section of your webpack configuration:

writeToDisk

Problem: On a fresh build (after nuking <p-inline>priv/static<p-inline>) none of the static assets were rendering in development. So remember how in our original setup Webpack would compile files into <p-inline>priv/static<p-inline> and Phoenix would serve them? Well, now that Webpack is only building everything in memory via the webpack-dev-server, Phoenix is unable to render our static files.

Diagram: When you edit a file in the assets folder, Webpack notices and compiles it into the priv/static folder where Phoenix LiveReload notices it and tells the browser to initiate a full page reload.

In the default Phoenix we used the Webpack CopyPlugin to copy static files from <p-inline>assets/static<p-inline> to <p-inline>priv/static<p-inline>.

But with webpack-dev-server by default everything is served out of memory (and it doesn't serve static files) so we need to enable the <p-inline>writeToDisk<p-inline> option to actually write the static files out so that Phoenix can serve them. This is why we had to add <p-inline>writeToDisk: true<p-inline> to the webpack-dev-server Webpack configuration snippet.

Phoenix Endpoint Configuration

In your Phoenix Endpoint configuration in <p-inline>dev.exs<p-inline> change the http port to 4001 but then set the url to 4000. This will instruct Phoenix to bind to port 4001 where it will actually listen for requests, but generate links to port 4000 so they can be correctly served by webpack-dev-server first.

Feedback

This approach helped our team move quickly through the development process, and get one step closer to being the best place to make a map on the internet. I hope you find it useful in your own pursuits. If you deploy this approach or have an alternative to share, apply for a job and be my coworker.

Bio
Jason is a Senior Software Engineer at Felt. He loves to remove friction from the software development process and is enjoying learning all about maps. He's a member of the ElixirLS and exsync core teams.
LinkedIn
More articles

testing

Simplicity meets power: Felt’s H3 spatial index support has arrived

Understanding spatial indexes: H3 explained