inlineresources
Version:
Inlines style sheets, images, fonts and scripts in HTML documents. Works in the browser.
299 lines (259 loc) • 9.14 kB
JavaScript
;
var util = require("./util"),
inlineImage = require("./inlineImage"),
inlineScript = require("./inlineScript"),
inlineCss = require("./inlineCss"),
cssSupport = require("./cssSupport");
var getUrlBasePath = function (url) {
return util.joinUrl(url, ".");
};
var parameterHashFunction = function (params) {
// HACK JSON.stringify is poor man's hashing;
// same objects might not receive same result as key order is not guaranteed
var a = params.map(function (param, idx) {
// Only include options relevant for method
if (idx === params.length - 1) {
param = {
// Two different HTML pages on the same path level have the same base path, but a different URL
baseUrl: getUrlBasePath(param.baseUrl),
};
}
return JSON.stringify(param);
});
return a;
};
var memoizeFunctionOnCaching = function (func, options) {
if (
options.cache !== false &&
options.cache !== "none" &&
options.cacheBucket
) {
return util.memoize(func, parameterHashFunction, options.cacheBucket);
} else {
return func;
}
};
/* Style inlining */
var requestExternalsForStylesheet = function (
styleContent,
alreadyLoadedCssUrls,
options
) {
var cssRules = cssSupport.rulesForCssText(styleContent, options);
return inlineCss
.loadCSSImportsForRules(cssRules, alreadyLoadedCssUrls, options)
.then(function (cssImportResult) {
return inlineCss
.loadAndInlineCSSResourcesForRules(cssRules, options)
.then(function (cssResourcesResult) {
var errors = cssImportResult.errors.concat(
cssResourcesResult.errors
),
hasChanges =
cssImportResult.hasChanges ||
cssResourcesResult.hasChanges;
if (hasChanges) {
styleContent = cssSupport.cssRulesToText(cssRules);
}
return {
hasChanges: hasChanges,
content: styleContent,
errors: errors,
};
});
});
};
var loadAndInlineCssForStyle = function (style, options, alreadyLoadedCssUrls) {
var styleContent = style.textContent,
processExternals = memoizeFunctionOnCaching(
requestExternalsForStylesheet,
options
);
return processExternals(styleContent, alreadyLoadedCssUrls, options).then(
function (result) {
if (result.hasChanges) {
style.childNodes[0].nodeValue = result.content;
}
return util.cloneArray(result.errors);
}
);
};
var getCssStyleElements = function (doc) {
var styles = doc.getElementsByTagName("style");
return Array.prototype.filter.call(styles, function (style) {
return (
!style.attributes.type || style.attributes.type.value === "text/css"
);
});
};
exports.loadAndInlineStyles = function (doc, options) {
var styles = getCssStyleElements(doc),
allErrors = [],
alreadyLoadedCssUrls = [],
inlineOptions;
inlineOptions = util.clone(options);
inlineOptions.baseUrl =
inlineOptions.baseUrl || util.getDocumentBaseUrl(doc);
return Promise.all(
styles.map(function (style) {
return loadAndInlineCssForStyle(
style,
inlineOptions,
alreadyLoadedCssUrls
).then(function (errors) {
allErrors = allErrors.concat(errors);
});
})
).then(function () {
return allErrors;
});
};
/* CSS link inlining */
var substituteLinkWithInlineStyle = function (oldLinkNode, styleContent) {
var parent = oldLinkNode.parentNode,
styleNode;
styleContent = styleContent.trim();
if (styleContent) {
styleNode = oldLinkNode.ownerDocument.createElement("style");
styleNode.type = "text/css";
styleNode.appendChild(
oldLinkNode.ownerDocument.createTextNode(styleContent)
);
parent.insertBefore(styleNode, oldLinkNode);
}
parent.removeChild(oldLinkNode);
};
var requestStylesheetAndInlineResources = function (url, options) {
return util
.ajax(url, options)
.then(function (content) {
var cssRules = cssSupport.rulesForCssText(content, options);
return {
content: content,
cssRules: cssRules,
};
})
.then(function (result) {
var hasChangesFromPathAdjustment =
inlineCss.adjustPathsOfCssResources(
url,
result.cssRules,
options
);
return {
content: result.content,
cssRules: result.cssRules,
hasChanges: hasChangesFromPathAdjustment,
};
})
.then(function (result) {
return inlineCss
.loadCSSImportsForRules(result.cssRules, [], options)
.then(function (cssImportResult) {
return {
content: result.content,
cssRules: result.cssRules,
hasChanges:
result.hasChanges || cssImportResult.hasChanges,
errors: cssImportResult.errors,
};
});
})
.then(function (result) {
return inlineCss
.loadAndInlineCSSResourcesForRules(result.cssRules, options)
.then(function (cssResourcesResult) {
return {
content: result.content,
cssRules: result.cssRules,
hasChanges:
result.hasChanges || cssResourcesResult.hasChanges,
errors: result.errors.concat(cssResourcesResult.errors),
};
});
})
.then(function (result) {
var content = result.content;
if (result.hasChanges) {
content = cssSupport.cssRulesToText(result.cssRules);
}
return {
content: content,
errors: result.errors,
};
});
};
var loadLinkedCSS = function (link, options) {
var cssHref = link.attributes.href.value,
documentBaseUrl = util.getDocumentBaseUrl(link.ownerDocument),
ajaxOptions = util.clone(options);
if (!ajaxOptions.baseUrl && documentBaseUrl) {
ajaxOptions.baseUrl = documentBaseUrl;
}
var processStylesheet = memoizeFunctionOnCaching(
requestStylesheetAndInlineResources,
options
);
return processStylesheet(cssHref, ajaxOptions).then(function (result) {
return {
content: result.content,
errors: util.cloneArray(result.errors),
};
});
};
var getCssStylesheetLinks = function (doc) {
var links = doc.getElementsByTagName("link");
return Array.prototype.filter.call(links, function (link) {
return (
link.attributes.rel &&
link.attributes.rel.value === "stylesheet" &&
(!link.attributes.type || link.attributes.type.value === "text/css")
);
});
};
exports.loadAndInlineCssLinks = function (doc, options) {
var links = getCssStylesheetLinks(doc),
errors = [];
return Promise.all(
links.map(function (link) {
return loadLinkedCSS(link, options).then(
function (result) {
substituteLinkWithInlineStyle(link, result.content + "\n");
errors = errors.concat(result.errors);
},
function (e) {
errors.push({
resourceType: "stylesheet",
url: e.url,
msg: "Unable to load stylesheet " + e.url,
});
}
);
})
).then(function () {
return errors;
});
};
/* Main */
exports.loadAndInlineImages = inlineImage.inline;
exports.loadAndInlineScript = inlineScript.inline;
exports.inlineReferences = function (doc, options) {
var allErrors = [],
inlineFuncs = [
exports.loadAndInlineImages,
exports.loadAndInlineStyles,
exports.loadAndInlineCssLinks,
];
if (options.inlineScripts !== false) {
inlineFuncs.push(exports.loadAndInlineScript);
}
return Promise.all(
inlineFuncs.map(function (func) {
return func(doc, options).then(function (errors) {
allErrors = allErrors.concat(errors);
});
})
).then(function () {
return allErrors;
});
};