Skip to content
An approach to serving differential bundles in Ember
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.gitignore
README.md
index.js
merge-html.js
package-lock.json
package.json

README.md

Serving differential builds with EmberJS

Introduction

There is currently no way to do differential serving of assets based on the user's browser in Ember land. The closest thing currently is a pre-RFC.

This is one feasible solution to this problem. Importantly,

  • This solution tries to address engines as well. With the usual type="module" and nomodule approach, you cannot handle engines as they rely on the asset manifest provided by a meta tag with name as app/config/asset-manifest. There is no way to toggle meta tags in the frontend as of now and until Ember Engines support the storeConfigInMeta option, you will either have to do something similar to this solution or not do differential serving of engine assets alone. PS: There is a long-pending PR to implement the storeConfigInMeta option in Ember Engines.
  • This solution requires you to serve different bundles by manipulating the HTML at runtime in the server based on a cookie, or if possible, parsing the UA.

Steps

  • In your index.html, add a data-for="ember" attribute to script tags that are generated by Ember. For example,
      <script data-for="ember" src="{{rootURL}}assets/vendor.js"></script>
      <script data-for="ember" src="{{rootURL}}assets/app.js"></script>
  • In your application code, run a small script to set a cookie to determine if the user is currently using a modern or legacy browser.
      // Serve modern build if Promise and async/await are present
      let testCode = 'async() => { let p = new Promise(); await p(); }';
    
      try {
        // this needs to be a new Function/eval because otherwise,
        // you will get Syntax Errors in the code
        (new Function(testCode))();
        // Set a never-expiring cookie
      } catch(err) {
        // Set a cookie with a short validity(say a month)
        // If the user updates their browser, it will be reflected
        // once the cookie expires
      }
  • In your targets.js file, use the process.env.LEGACY flag to toggle support for legacy browsers.
      'use strict';
    
      let browsers = [
        'last 1 Chrome versions',
        'last 1 Firefox versions',
        'last 1 Safari versions'
      ];
    
      const isCI = !!process.env.CI;
      const isProduction = process.env.EMBER_ENV === 'production';
      const isLegacyBuild = !!process.env.LEGACY;
    
      if (isCI || isProduction || isLegacyBuild) {
        browsers = [
          'Chrome >= 42',
          'Firefox >= 39',
          'Edge >= 14',
          'Safari >= 10'
          'ie 11'
        ];
    
      }
    
      module.exports = {
        browsers
      };
  • Run parallel builds with
      ember build --environment=production --output-path=modern &
      LEGACY=true ember build --environment=production --output-path=legacy
  • Run the index.js in this repo. It will generate a dist folder with an index.html containing combined script tags and meta tags wrapped in <MODERN> and <LEGACY> tags.
  • In your server, detect the presence of the cookie that was previously set (or use the UA) and replace the tags accordingly.
      // if modern browser
      replaceAll('<LEGACY>.*</LEGACY>', '');
      replaceAll('<MODERN>(.*)</MODERN>', '$1');
    
      // if legacy browser
      replaceAll('<MODERN>.*</MODERN>', '');
      replaceAll('<LEGACY>(.*)</LEGACY>', '$1');
    
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.