Skip to content

Recipe: app shell

Jose edited this page Mar 12, 2019 · 1 revision

Creating an app-shell

What is an app-shell?

The main purpose of this guide is to provide an easy way to implement this pattern in your Ember application by using this addon.

Creating an in-repo addon

We'll create an in-repo addon that will do all the necessary magic to enable this pattern. Why an addon? Because the code is separated from the rest of the application and can be asily toggled on/off.

Let's start creating the an addon inside the root of our Ember application. We will use the Ember cli tool by using the next command:

$ ember generate in-repo-addon app-shell

The previous command will generate something like this structure:

lib/app-shell
├── addon/
├── app/
├── index.js
└── package.json

Addon configuration

We must have fully control about how the Service Worker going to be created, so we have to disable the automatic creation using the addon configuration, also we have to cache the index.html and the rest of the assets:

// config/environment.js

{
  workbox: {
    globPatterns: includedAppCache.concat([
      'index.html',
      'assets/vendor.js',
      'assets/vendor.css',
      'assets/app.js',
      'assets/app.css'
    ])
  },
  'ember-cli-workbox': {
    autoRegister: false
  }
}

Async loading Ember

Once the addon is configured, the first thing we are going to do is to remove all the assets that are loaded in the index.html file. We will want to keep our app-shell as light as possible so it's time to remove the vendor.js, app.js and the css files (all the files that we have previously cached).

Now we are ready to start writing some Ember code:

Create the Service Worker

Create a file in lib/app-shell/inline-content/boot.js. This file will create the Service Worker also load the application once it's ready.

Here it's an example of how to create it:

// lib/app-shell/inline-content/boot.js

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('sw.js').then((registration) => {
    // Registration was successful
  }).catch((err) => {
    // registration failed :(
  });
}

Booting the application

Your app should always load even if the service worker fails (maybe depends on how it works) so now it's time to inject the necessary <script> and <link> tags in your DOM.

// lib/app-shell/inline-content/boot.js

function loadScript(src) {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script');

    script.src = src;
    script.addEventListener('load', resolve);
    script.addEventListener('error', reject);
    document.body.appendChild(script);
  });
}

// Registration was successful/failed
loadScript('assets/vendor.js');
loadScript('assets/app.js');
loadLink('assets/vendor.css');
loadLink('assets/app.css');

Creating an app-shell

The app-shell must contain the minimum content to show something similar like your app, so it's time to add some HTML and CSS to our DOM.

// lib/app-shell/inline-content/app-shell.html

<div style="color: red">
  <p>My awesome app!</p>
</div>

Injecting our code into the index.html

We've created a couple of files in lib/app-shell/inline-content, but this files are outside the ember-cli building process. We should inject this content into the index.html using some ember-cli hooks:

// lib/app-shell/index.js

{
  contentFor(type, config) {
    if (type === 'body-footer' && config && config.environment !== 'test') {
      const bootJs = fs.readFileSync(path.resolve(__dirname, 'inline-content', 'boot.js'));
      const appShellHtml = fs.readFileSync(path.resolve(__dirname, 'inline-content', 'app-shell.html'));

      // Remember!!!!!
      // You should use babel after injecting your code

      return [
        appShellHtml,
        `<script type="text/javascript">${bootJs}</script>`
      ].concat('');
    }
  }
}

Enabling the addon

// package.json
{
"ember-addon": {
  "paths": [
    "lib/app-shell"
  }
}

That's all folks

It's time to check if everything works :)