tl;dr; the idea is to incrementally start serving particular experiences for each device (i.e: Mobile / Desktop) in the lazy programming way.

In this post, I’ll give a background about the problem to be solved and the first attempt to do it. Although, the post will talk about a concrete feature, I won’t be showing too much code, the following posts will have working examples.

The problem

As every developer knows, problems are created by the product team.

We’ve started having UI requirements that were too specific for each device. Some of these often involved using libraries which only target that device (e.g.: drag & drop capabilities were only needed on desktop, not mobile).

Bundle size started to rise, code started to get dirty and differences between devices got worse and worse.

At the time, the PWA team was a small team (only ~5 devs), we just could fork the platform and treat each device as a particular development. In addition, we had a working product, we needed to make changes incrementally.

Although many teams may have this problem, the developed solution isn’t a silver bullet. It addressed our particular constraints and characteristics. In this post, my objective is to give some insights about the process of how that solution has been reached.

The stack

First of all, i’ll have to do a brief introduction about the stack in which the platform is developed.

PWA technology stack

The PWA uses the usual React+Redux+React router stack bundled with webpack.

The server code is transpiled with babel in order to re use react component to do server side rendering.

Backends are external microservices. The PWA’s server has a proxy in order to communicate with them.

Options

As i’ve said before, the idea was to start optimizing the platform for each device.

We thought of three possible approaches:

Adaptative / Responsive design: serving the same javascript, css bundles and html for all clients and using responsive sizes and media queries in order to change the user experience according to the device’s width.

serving the same javascript, css bundles and html for all clients and using responsive sizes and media queries in order to change the user experience according to the device’s width. Separate Sites : Developing a fork for each device. This would involve to start moving common code to packages or something similar in order to avoid code duplication.

: Developing a fork for each device. This would involve to start moving common code to packages or something similar in order to avoid code duplication. Dynamic Serving: Serving different content (js, css bundles) depending on the requests headers (mainly the UserAgent). The problem of this approach is to investigate a way of building the different bundles without having to do radical changes on the current code.

We were using the Adaptative / Responsive approach which lead to some problems:

Client received the web duplicated, both mobile and desktop components.

Code became a bit messy

Client received unnecessary libraries (ie: mobile clients also received desktop only libraries)

We want to reduce initial learning curve for developers (easy developer’s transition) and the long term maintaining effort. As far as the separate sites approach, we should fork the current codebase and maintain different codebases (a huge long term maintaining effort) or refactor the current codebase in order to extract all the common code and then fork (hard transition, as developers should understand how to write the common code in order to share it between forks). So, this approach was a no op.

Dynamic Serving

We end up going for the dynamic serving approach. In order to do, we should resolve some problems:

find out how to guess what device is the client (ie: is it mobile?, desktop?)

generate device specific bundles

The company already had a DeviceAtlas contract, so it was an easy choice. DeviceAtlas is a device detection solution for parsing User Agent strings in the web environment, so it was just what we needed.

Proposed architecture

The following problem was building device specific bundles without having to make huge changes on the way developer’s work. The idea was trying to resolve everything on compile time, so we don’t get any runtime penalties while also having a easy developer experience.

Ideally, a developer would write a component like this:

While having the following file structure:

And for each device bundle, it would resolve to the appropriate file (ie: DeviceSpecificComponent.mobile.js for the mobile bundle, DeviceSpecificComponent.desktop.js for the desktop one, and so on).

Luckily, Webpack has a plugin called NormalModuleReplacementPlugin which allows to change the resource resolution. But, in order to build a bundle for each device using this approach, it requires to make a compilation for each device. To address this issue, webpack has the multi-compiler feature.