Single page app Continuous Delivery?

singlepageapp
ci
cd

#1

:wave:

We’ve just launched https://d.rip. We have a React / Redux front-end that’s currently built, tested and deployed alongside the back-end. That’s very slow and unnecessary, so we’re looking at better options.

I didn’t find much on Continuous Delivery of Single page apps. Single page apps are a dream to package and distribute compared to back-end stuff. A “package” is just a bunch of static css and js files that an html page points to. Since most asset pipelines fingerprint those files, you can just upload them all (including your feature branches) to S3 and make your index.html (or server side rendered page) points to whichever assets you want. QAing a feature branch on production data is just a matter of getting the back-end to render an html page that points to your feature branch package for your session (or user).

Now, with that in mind… are there any solutions out there to do all the plumbing for us? Or do we have to build this ourselves?

I know that Unbounce has a pretty cool pipeline to manage all that for the Landing Page builder. Users are assigned to different channels (stable, beta, etc) and versions can be switched in seconds.

Any pointers / feedback / best practices would be greatly appreciated!


#2

I’m not sure whether this is helpful, but I can talk about it a lil’ bit.

At Unbounce we use our internal feature flagging software in order to distribute front end UIs to our users. It’s been very successful for us especially because, as you said, users can be assigned to different channels or random subgroups.

We treat the reference to a UI bundle (e.g. www.mycdn.com/my-ui/v0.10.0/bundle.js) as a feature flag which means that packaging and releasing of a new UI (i.e. bundling and uploading to S3) is completely separated from its activation (i.e. updating the feature flag to point to the new URL). We can also change flags for individual users, making it easy for a developer to run a local version of the UI via Webpack dev server (which also includes things like hot reloading) against staging or production (i.e. set the UI bundle reference to localhost:9000/bundle.js and it just works™). Because of this fine-grained control, you might be able to guess that rollouts and rollbacks are very low risk. Continuous delivery is simply a matter of a call to update the UI bundle URL.

The biggest trade offs I’ve seen are:

  • We rely on an intermediary API to load UIs.
  • We have to have a team maintaining it.
  • We have to be serious about caching in order to keep things fast.
  • An “activated” UI is sometimes slow to show up because of said caching. Once or twice we’ve had bugs reported because of this.
  • Other weird caching problems sometimes.

Some things that I think would help you out here:

  • Make releases immutable. Keep creating new releases and never override anything. If you release and deploy a new version of the UI to your CDN and there’s a big problem, fix the problem and roll forward. In my opinion immutable releases are a natural outcome of having this kind of control, but it’s worth pointing out.
  • Separate releases by folder. And name the folder after the release version. (Maybe the “version” field of your package.json?) Do not use a Git SHA or MD5 hash because then you’re screwed when debugging. Do not dump all of your releases into a single folder because that also makes debugging hard.
  • You should be able to override the known, stable URL for individual users. To me this is the biggest win: being able to develop against staging while only running webpack on your machine. The environment setup for new frontend developers is literally setting a URL flag on staging and running yarn install && yarn dev.

#3

Those are great insights thank you!

As a proof of concept, I bundled the release and reference together. We have a script that builds the assets, publish them to S3 along side a release manifest.

Ex:

# //mybucket.s3.amazonaws.com/releases/staging.json
{
  "package": "staging",
  "username": "pcreux",
  "timestamp": "2017-11-22T11:56:35-08:00",
  "git_sha": "ade667b5e90f9bcb2032a1a4f3676727a1705f15",
  "git_branch": "master",
  "js": [
    "https://mycdn.com/modules/manifest.fa07b7a0b10640ba2573.js",
    "https://mycdn.com/modules/core.50ee055331791787ca0a.js",
    "https://mycdn.com/modules/drip.bfde8acd8f5893ad0157.js",
    "https://mycdn.com/modules/ksr-uploader.b52973d508da4f25888b.js"
  ]
}

The rails app pulls the release manifest for the current environment (production / staging) or overwrite via GET param (?use-front-end=my-feature-branch). We’re configuring CircleCI to deploy a release for all branches so that we can QA any branch without spinning up a new test environment.

Once we setup a local node server for serving assets, I’ll publish a ‘local’ package that will point to assets on localhost:9000.

We’re likely to not go with semantic versioning. We’ll provide a CLI / slack commands to promote master to staging, and staging to production.

Thanks again @gabe!


#4

I’m a big jenkins fan, so I tend to use that over travis or circle, but they all seem to do the job. So far I havn’t deployed any real solo front end only apps for work, most of what i’ve done so far is coupled with a back end and deployed at the same time as one big docker container.

Personal projects though, i’ve hacked on a number of random ones.

This is an example of the simplest flow for me. Mapped infinicatr.gavinmogan.com to a surge.sh domain (https://surge.sh/) and just deploy all the files that way.