const HtmlRelation = require('../HtmlRelation');
function getExtension(url) {
const matches = url.match(/\.([^$#?]+)/);
if (matches && matches[1]) {
return matches[1];
}
}
/**
* @constructor
* @abstract
* @augments HtmlRelation
*
* Abstract class for resource hint relations
*/
class HtmlResourceHint extends HtmlRelation {
get href() {
return this.node.getAttribute('href');
}
set href(href) {
this.node.setAttribute('href', href);
}
get contentType() {
if ('_contentType' in this) {
return this._contentType;
} else {
if (
this.to.contentType &&
this.to.contentType !== 'application/octet-stream'
) {
this._contentType = this.to.contentType;
}
// Let's see if we can do slightly better by extension
if (
!this._contentType ||
this._contentType === 'application/octet-stream'
) {
// Snapped from https://github.com/jshttp/mime-db/blob/master/db.json
const extensionToTypeMap = {
woff: 'font/woff',
woff2: 'font/woff2',
ttf: 'font/ttf',
eot: 'application/vnd.ms-fontobject',
otf: 'font/otf',
js: 'application/javascript',
};
const betterContentType = extensionToTypeMap[getExtension(this.to.url)];
if (betterContentType) {
this._contentType = betterContentType;
}
}
return this._contentType;
}
}
// Map Assetgraph relations to request destination (<link as="${destination}">).
// See https://fetch.spec.whatwg.org/#concept-request-destination
get requestDestination() {
if (this.to.isImage) {
return 'image';
}
if (this.to.type === 'JavaScript') {
return 'script';
}
if (this.to.type === 'Css') {
return 'style';
}
const incomingRelations = this.to.incomingRelations;
const firstOtherIncomingRelation =
incomingRelations &&
incomingRelations.find((relation) => relation !== this);
if (firstOtherIncomingRelation) {
const relType = firstOtherIncomingRelation.type;
if (['HtmlStyle', 'CssImport'].includes(relType)) {
return 'style';
}
if (
this.to.type === 'Html' &&
(relType === 'HtmlFrame' || relType === 'HtmlIFrame')
) {
return 'document';
}
if (relType === 'HtmlAudio') {
return 'media';
}
if (relType === 'HtmlVideo') {
return 'media';
}
if (relType === 'CssFontFaceSrc') {
return 'font';
}
if (relType === 'HtmlEmbed') {
return 'embed';
}
if (relType === 'HtmlObject') {
return 'object';
}
}
// If the asset hasn't been populated, fall back to best guess based on file extension
if (this.to.url) {
switch (getExtension(this.to.url)) {
case 'css':
return 'style';
case 'js':
return 'script';
case 'svg':
case 'jpg':
case 'jpeg':
case 'png':
case 'gif':
case 'webp':
case 'ico':
case 'tiff':
case 'bmp':
return 'image';
case 'html':
return 'document';
case 'woff':
case 'woff2':
case 'ttf':
case 'eot':
case 'otf':
return 'font';
}
}
return '';
}
get as() {
if (typeof this._as === 'undefined') {
this._as = this.requestDestination;
}
return this._as;
}
get targetType() {
const as = this.node && this.node.getAttribute('as');
if (as) {
switch (as) {
case 'script':
case 'worker':
return 'JavaScript';
case 'style':
return 'Css';
case 'image':
return 'Image';
case 'font':
return 'Font';
}
}
}
set as(type) {
// See https://www.w3.org/TR/preload/#as-attribute
// ... but we also have to tolerate invalid values here: https://github.com/assetgraph/assetgraph/issues/1138
this._as = type;
if (this.node) {
this.node.setAttribute('as', type);
}
}
attach(position, adjacentRelation) {
super.attach(position, adjacentRelation);
if (this.crossorigin) {
// fonts should always be treated as crossorigin: https://w3c.github.io/preload/#h-note6
this.node.setAttribute('crossorigin', 'anonymous');
}
}
inlineHtmlRelation() {
throw new Error(
'HtmlResourceHint: Inlining of resource hints is not allowed'
);
}
}
HtmlResourceHint.prototype.preferredPosition = 'lastInHead';
module.exports = HtmlResourceHint;