Creating Multipage Apps using Aurelia

Prior to starting my work with Aurelia I'd never heard of a multipage app with a front-end JavaScript framework but the concept is pretty straight-forward - a developer wants to have the server control routing and permissioning but have their JavaScript framework take over once the page is served to create a really nice user experience. It's basically a way of augmenting our server-side templates with client-side technology.

At the moment (pre-beta) Aurelia has a few different ways for augmenting your page like this - the first is to use Aurelia's new 'enhance' feature. This gives you the ability to augment any page even in multiple locations. I'll follow up with a post that details that method, but for now let's look at the second way - by creating a multipage app.

What is a Multipage App?

It's hard to find a good definition, but for our purposes let's stick with this one - A set of re-usable modules and configuration that can be loaded up from any page without relying on client-side routing.

For reference, here seems to be a great example of a multipage app built using require.js.

Sharing configuration

First, we want to make sure we don't have to duplicate configuration across our app. In keeping with the skeleton-navigation app, let's use JSPM and System.js as our package manager and module loader.

Setting up our config.js

With this setup there is a single config.js file that contains most of our configuration. With system.js and our config.js file, we can define maps to libraries we don't even use without worrying about a performance / load-time issue.

The reason is because system.js only uses the config.js file to find where to get the dependency when we need it, it's completely up to the developer to decide when to load it. This is important to understand because it's why our single configuration file can handle so many applications.

Setting up a shared main.js

When we load up the various pages we want to make sure they all share the same main.js file. This prevents us from having to create a bunch of different files that all do the exact same thing. To do this and support a multipage aplication architecture all we need to do is take our existing main.js and modify the aurelia.start() by passing in a string which is a reference to our starting view/view-model pair.

Before modification -

  aurelia.start().then(a => a.setRoot(););

After modification -

  aurelia.start().then(a => {
    let start = a.host.attributes.start.value;
    a.setRoot(start);
  });

We create a local variable start and set it to a.host.attributes.start.value. That is a bit verbose so let's break it down for understanding -

  1. a - the aurelia object passed in to our promise for performing configuration on our app during start-up.
  2. host - the 'host' is the node in the DOM which hosts our app (this is defined by our aurelia-app attribute)
  3. attributes - the standard attributes array available on all DOM nodes.
  4. start.value - 'start' is the name of the attribute will we add to our DOM element below, and 'value' is the value of that attribute.

We use this so that the host file can define which view/view-model pair to serve up and 'start' on. This means that for the apps that do use this main.js file that we will need to create a new attribute named start.

Setting up our parent HTML page

It doesn't matter which server-side technology (if any) that you are using to serve your Aurelia app, or even which templating language you are using. In the skeleton-navigation app we use an index.html but the same concept can be used in any other server templating file. Let's show that in the index.html -

  <body aurelia-app="main" start="app">
    <div class="splash">
      <div class="message">Aurelia Documentation</div>
      <i class="fa fa-spinner fa-spin"></i>
    </div>
    <script src="jspm_packages/system.js"></script>
    <script src="config.js"></script>
    <script>
      System.import('aurelia-bootstrapper');
    </script>
  </body>

All we've done is added the start attribute and specified that the app view/view-model pair is our starting point. Our app should still be working just fine as is.

We could just as easily start serving other pages here. To see this in action you could copy the index.html to a newly named example-index.html and change the attribute to any other view/view-model pair.

Recapping

We've enabled multipage application loading in our app's main.js. We've also shared our configuration in our config.js so we don't need to worry about maintaining multiple dependency files or updating versions. Last, we fixed up our parent HTML files that are actually serving the applications so that they implement the start attribute to be in charge of what loads.

This is really powerful because it gives us the ability to do a few really cool things -

Promote SEO

We can have our server render some base content to use for SEO purposes and have Aurelia take over that content area, per page, and make it a rich user experience

Share components between pages

Now that our applications' components are re-usable through the shared configuration we can simply re-use each component in different parts of our app and trust that it can load up it's own dependencies.

Convert applications / pages one page at a time

We can start to take our existing pages that already work and have value and convert them over to using a modern framework like Aurelia. This means it isn't an all-or-nothing type conversion or decision for our team.

I hope you've enjoyed this, please feel free to sound off in the comments!