For production, I have Broccoli generate an assets map file assets/assetMap.json by including the following in my ember-cli-build.js file:
var app = new EmberApp(defaults, {
fingerprint: {
...
generateAssetMap: true
}
});
Now I would like to define an HTMLBars helper which will take a given asset file name and convert it to its fingerprinted file name, something like this:
I guess that the best thing to do is create some kind of initializer which will load the assets/assetMap.json file and save it in some hash object which is then accessible via the helper.
Iāve looked through several examples but am having problems getting my head around the nitty-gritty aspects, is there someone out there who can help me?
Well, Iāve basically read all the issues on Github and they just say āyeah we need to do it betterā, but it hasnāt happened so far.
Iāve only run into two problems:
The assetMap is on the CDN as well in my case, so it has to be retrieved from there
The initializer was timing out all my tests, so I substituted a ādummyā service that just retuns the passed asset path in dev/testing
This is my version:
import Ember from 'ember';
import ENV from 'volders/config/environment';
export function initialize(container, application) {
var AssetMap;
application.deferReadiness();
if (ENV.APP.CDN_PATH === '') {
// use an asset map stub in development / testing
AssetMap = Ember.Object.extend({
resolve(name) {
return name;
}
});
container.register('assetMap:main', AssetMap, { singleton: true });
application.inject('service:assets', 'assetMap', 'assetMap:main');
application.advanceReadiness();
} else {
AssetMap = Ember.Object.extend();
var promise = new Ember.RSVP.Promise(function(resolve, reject) {
var assetMapURL = `${ENV.APP.CDN_PATH}assets/assetMap.json`;
Ember.$.getJSON(assetMapURL, resolve).fail(reject);
});
promise.then(function(assetMap) {
AssetMap.reopen({
assetMap: assetMap,
resolve: function(name) {
// lookup asset in asset map; if not found, try the asset name itself
return `${assetMap.prepend}${assetMap.assets[name]}` || name;
}
});
}, function() {
AssetMap.reopen({
resolve: function(name) {
return name;
}
});
}).then(function() {
container.register('assetMap:main', AssetMap, { singleton: true });
application.inject('service:assets', 'assetMap', 'assetMap:main');
application.advanceReadiness();
});
}
}
export default {
name: 'asset-map',
initialize: initialize
};
I set the ENV.APP.CDN_PATH in my config/environment.js by reading a JSON file that I also reuse in the ember-cli-build.js, so I donāt have to change paths in more than location.
edit: actually there was another problem in that the assetMap has a āprependā entry that has to be prefixed to the actual asset lookup. Maybe this is something that changed recently. I updated my example.
<script type="text/javascript">
window.assetMapping = {
app: 'assets/app.js',
vendor: 'assets/vendor.js'
// Rest of your assets
};
</script>
The assetMapping structure will be āuntouchedā in development - meaning the asset paths will remain unchanged, but in production those asset paths will change to reflect the added fingerprint. Weāre exploiting fact here that all instances of āassets/some-asset.jsā in our application, will be replaced with āassets/some-asset-FINGERPRINT.jsā. So, when built:
<script type="text/javascript">
window.assetMapping = {
app: 'assets/app-FINGERPRINT.js',
vendor: 'assets/vendor-FINGERPRINT.js'
// Rest of your fingerprinted assets
};
</script>
Then, use your asset-resolve helper:
// helpers/asset-resolve.js
import Ember from 'ember';
export function assetResolve(params/*, hash*/) {
if (!window.assetMapping || !window.assetMapping[params[0]]) {
Ember.Logger.error('No assetMapping found for ' + params[0]);
}
// Return here either raw resolved assetMapping (for development) or prefixed resolved mapping with CDN url (for production).
return window.assetMapping[params[0]];
}
export default Ember.Helper.helper(assetResolve);
You can also drop adding script tag to index.js and just add assetMapping var to asset-resolve helper.
That certainly has the benefit of sparing the client one more request to the asset map, but I donāt think itās feasible to manually manage your asset map. If there was a simple method (maybe an in-repo-addon?) to generate it, it would make much more sense.
But in fact, I think the cleanest solution would be for Ember to support importing the assetMap even if it doesnāt exist at compile time - similar to what is done with the config/environment.js. AFAIK there was some talk about implementing thisā¦
So, is there a āhumanā solution exist? Iām a newcomer in EmberJS (and JS too) and these workarounds confused me a littleā¦ Iām faced with the same problem and stuck on it.
Yes, unfortunately it looks like the combination of:
CDN + asset digests
dynamically generated asset paths
donāt work very well atm. So you either have to invest time in finding a workaround (as e.g. suggested in this thread), or you forego one of those two things.
simplest thing that worked for me was hard coding the full asset path to every image name, broccoli-asset-rev then recognizes it and rewrites my js source
Anyone arriving here in Nov 2022 and not using a CDN should be aware that this behavior is handled automagically by ember-cli-deploy and broccoli-asset-rev. If you simply write a URL path to a fingerprinted asset file in a JavaScript string, the āblack boxā that is Emberās fingerprinting scheme will alter your URL path without telling you. This behavior is super helpful, but since it is not clearly explained in the docs for either ember-cli-deploy or broccoli-asset-rev, it can be confusing, bordering on infuriating.
With fingerprinting enabled, if you write a URL path to a css file like this: let URLpath = 'assets/app.css'; in any JavaScript file in your Ember app, it will automagically be updated to let URLpath = 'assets/app-38e6dce5c3e69f23d0a6948945245231.css'; without your knowledge. You can turn fingerprinting on in ember-cli-build to test in development, it is only enabled by default in production: fingerprint: { enabled: true },
HOWEVER, there appears to be at least one shortcoming to this āblack boxā scheme, because it can sometimes ignore a URL path if it is written as part of a template literal string (surrounded by grave accent marks) instead of a plain string (surrounded by double or single quotes): let URLpath = `${window.location.protocol}//${window.location.host}/assets/app.css`; This template literal string will NOT have the css file āfingerprintedā by Ember and the request to it will result in a 404. If you write this instead: let URLpath = `${window.location.protocol}//${window.location.host}` + ā/assets/app.cssā; the fingerprinting scheme will work.
I ran into this error, and without understanding the mysterious āblack boxā of Ember fingerprinting, I beat my head against a wall for several hours. Thanks Ember!
Iāll add in here as well, because we had another use case that was not covered by Ember out of the box.
We dynamically set a custom style sheet using ember-cli-head, with the āstyleā received from our user details request. We have to retrieve the Assetmap to get the fingerprinted stylesheet name.