Previewing revisions with ember-cli-deploy-s3-index

Previewing revisions/deployments of ember applications before releasing them to the public has long been a huge selling-point of ember-cli-deploy.

As described in the lightning-strategy-guide the idea is pretty simple. As you are basically serving a static asset when delivering your ember application to the public you can have multiple versions of this application available and decide which version to serve based on some backend-server logic. When you want to preview a revision you send a request to your backend-server that decides based on some user-specified information to serve up a different revision of your application than other users see per default.

Most people are using query-parameters (e.g. https://example.com?revision_key=new-version-id) for this but you could use whatever mechanism you see fit to decide which version of your application to serve based on the request a user makes to your servers.

When deploying your ember application's revisions to S3/Cloudfront via the ember-cli-deploy-s3-index-addon this becomes kind of difficult though as you don't have control over the backend-logic when only serving static files via S3.

Fortunately you can still setup revisions previews when using ember-cli-deploy-s3-index. There are two scenarios:

Hash-Location

Accessing non-active revisions that are stored in S3 is pretty straight forward. You can access these index-files directly from the bucket and your application will try to boot up.

The only problem is that when serving up and index.html-file from a path that is not the root-object of your bucket the URL will contain the file-name of the index-file you are trying to access and Ember's router will throw an UnrecognizedURLError because it tries to treat the url (e.g. https://example.com/index.html:7bcfabc323599a8b95d33759c2353996) as a path to an ember-route. Your application most likely does not contain a route with a path of index.html:7bcfabc323599a8b95d33759c2353996 of course thus the error.

You can fix this by configuring a locationType of hash in your application's configuration though:

// config/environment.js
module.exports = function(environments) {  
  var ENV = {
    // ...
    locationType: 'hash'

    // ...
  };

  return ENV;
};

This will give you pretty ugly URLs but your ember-application will now only consider everything after a #-in your url an application-url and will happily boot up your revision-files.

An url of https:://example.com/index.html:7bcfabc323599a8b95d33759c2353996#/about will now try to serve the route that is connected to the about-path.

History-Location

You can also get revision-previews working with the history-location. The default locationType of auto will not work due to the way it detects the location-type it can use.

// config/environment.js
module.exports = function(environments) {  
  var ENV = {
    // ...
    locationType: 'history'

    // ...
  };

  return ENV;
};

History-API is supported by all major browser of today but if you need to support really old browsers you might be out of luck and can't use this workaround.

As discussed before the only problem when serving revision-files directly from a bucket is that Ember's router gets confused by the url-structure.

We can help the router to understand the url-structure by creating an instance-initializer:

export function initialize(appInstance) {  
  const previewUrlMatch = window.location.href.match(/(index\.html\:\w*)((.?).*)/);

  if (previewUrlMatch) {
    const router       = appInstance.lookup('router:main');
    const identifier   = previewUrlMatch[1];
    const trailingChar = previewUrlMatch[3];
    let initialURL     = previewUrlMatch[2];

    if (trailingChar === '#') {
      initialURL = initialURL.replace('#', '');
    }

    router.initialURL = initialURL;
  }
}

export default {  
  name: 'enable-revision-previews-s3-index',
  initialize
};

When accessing https:://example.com/index.html:7bcfabc323599a8b95d33759c2353996 we simply tell the ember-router to ignore the revision-identifier.

The tricky part comes when we want to access nested routes. Due to the way S3 works we can't simply access a url like https:://example.com/index.html:7bcfabc323599a8b95d33759c2353996/about as S3 would try to serve the about-file in the index.html:7bcfabc323599a8b95d33759c2353996-directory and the ember-application would not boot up before we receive back an error from S3.

To make nested routes for previews work we follow the same url-structure as we do for the hash-location. When we access the revision we want to preview via a url like https:://example.com/index.html:7bcfabc323599a8b95d33759c2353996#/about S3 will serve up the index.html:7bcfabc323599a8b95d33759c2353996-file in the bucket and the initializer will run. We will then tell the router to ignore the # in the path and start up the application in the /about-path.

This of course is only necessary for previewing revisions. This does not change the way your application behaves for your normal users.

Conclusion

Previewing revisions is possible when using ember-cli-deploy-s3-index but as you might have already figured out it's kind of messy because you have to change the way the ember router behaves when booting up the application.

Depending on your use case this might be a good-enough-solution for you but if you need to get really fancy with a/b-testing, feature-branch-deployments etc. you might be better off considering the use of other deployment-addons like ember-cli-deploy-redis in combination with backend-servers to serve your application. You just have much more control over how you are serving your application to your users that way.

I hope this was helpful, thanks for reading and as always just drop me a line on twitter (@LevelbossMike) or ping me on the Ember.js-slack if you have questions. I am also available for consulting work and would be happy to help you and your company with your Ember.js-projects.

comments powered by Disqus