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"
andnomodule
approach, you cannot handle engines as they rely on the asset manifest provided by ameta
tag with name asapp/config/asset-manifest
. There is no way to togglemeta
tags in the frontend as of now and until Ember Engines support thestoreConfigInMeta
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 thestoreConfigInMeta
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.
- In your
index.html
, add adata-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 theprocess.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 };
broccoli-persistent-filter
does not support concurrent builds. This will result in build errors with the messageUnexpected end of file ...
. To avoid this, inember-cli-build.js
, set theBROCCOLI_PERSISTENT_FILTER_CACHE_ROOT
environment variable to a random path. For example,if (EmberApp.env() === 'production') { process.env.BROCCOLI_PERSISTENT_FILTER_CACHE_ROOT = path.join( process.cwd(), `cache_${Date.now()}_${Math.floor(Math.random() * 10)}` ); }
- 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 adist
folder with anindex.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');