inlineresources
Version:
Inlines style sheets, images, fonts and scripts in HTML documents. Works in the browser.
446 lines (383 loc) • 13.4 kB
JavaScript
;
var util = require("./util"),
cssSupport = require("./cssSupport"),
backgroundValueParser = require("./backgroundValueParser"),
fontFaceSrcValueParser = require("css-font-face-src");
var updateCssPropertyValue = function (rule, property, value) {
rule.style.setProperty(
property,
value,
rule.style.getPropertyPriority(property)
);
};
var findBackgroundImageRules = function (cssRules) {
return cssRules.filter(function (rule) {
return (
rule.type === window.CSSRule.STYLE_RULE &&
(rule.style.getPropertyValue("background-image") ||
rule.style.getPropertyValue("background"))
);
});
};
var findBackgroundDeclarations = function (rules) {
var backgroundDeclarations = [];
rules.forEach(function (rule) {
if (rule.style.getPropertyValue("background-image")) {
backgroundDeclarations.push({
property: "background-image",
value: rule.style.getPropertyValue("background-image"),
rule: rule,
});
} else if (rule.style.getPropertyValue("background")) {
backgroundDeclarations.push({
property: "background",
value: rule.style.getPropertyValue("background"),
rule: rule,
});
}
});
return backgroundDeclarations;
};
var findFontFaceRules = function (cssRules) {
return cssRules.filter(function (rule) {
return (
rule.type === window.CSSRule.FONT_FACE_RULE &&
rule.style.getPropertyValue("src")
);
});
};
var findCSSImportRules = function (cssRules) {
return cssRules.filter(function (rule) {
return rule.type === window.CSSRule.IMPORT_RULE && rule.href;
});
};
var findExternalBackgroundUrls = function (parsedBackground) {
var matchIndices = [];
parsedBackground.forEach(function (backgroundLayer, i) {
if (backgroundLayer.url && !util.isDataUri(backgroundLayer.url)) {
matchIndices.push(i);
}
});
return matchIndices;
};
var findExternalFontFaceUrls = function (parsedFontFaceSources) {
var sourceIndices = [];
parsedFontFaceSources.forEach(function (sourceItem, i) {
if (sourceItem.url && !util.isDataUri(sourceItem.url)) {
sourceIndices.push(i);
}
});
return sourceIndices;
};
exports.adjustPathsOfCssResources = function (baseUrl, cssRules, options) {
var backgroundRules = findBackgroundImageRules(cssRules),
backgroundDeclarations = findBackgroundDeclarations(backgroundRules),
change = false;
backgroundDeclarations.forEach(function (declaration) {
var parsedBackground = backgroundValueParser.parse(declaration.value),
externalBackgroundIndices =
findExternalBackgroundUrls(parsedBackground),
backgroundValue;
if (externalBackgroundIndices.length > 0) {
externalBackgroundIndices.forEach(function (backgroundLayerIndex) {
var relativeUrl = parsedBackground[backgroundLayerIndex].url,
url = util.joinUrl(baseUrl, relativeUrl);
parsedBackground[backgroundLayerIndex].url = url;
});
backgroundValue = backgroundValueParser.serialize(parsedBackground);
updateCssPropertyValue(
declaration.rule,
declaration.property,
backgroundValue
);
change = true;
}
});
findFontFaceRules(cssRules).forEach(function (rule) {
var fontFaceSrcDeclaration = rule.style.getPropertyValue("src"),
parsedFontFaceSources,
externalFontFaceUrlIndices;
try {
parsedFontFaceSources = fontFaceSrcValueParser.parse(
fontFaceSrcDeclaration
);
} catch (e) {
return;
}
externalFontFaceUrlIndices = findExternalFontFaceUrls(
parsedFontFaceSources
);
if (externalFontFaceUrlIndices.length > 0) {
externalFontFaceUrlIndices.forEach(function (fontFaceUrlIndex) {
var relativeUrl = parsedFontFaceSources[fontFaceUrlIndex].url,
url = util.joinUrl(baseUrl, relativeUrl);
parsedFontFaceSources[fontFaceUrlIndex].url = url;
});
cssSupport.changeFontFaceRuleSrc(
cssRules,
rule,
fontFaceSrcValueParser.serialize(
parsedFontFaceSources,
options
),
options
);
change = true;
}
});
findCSSImportRules(cssRules).forEach(function (rule) {
var cssUrl = rule.href,
url = util.joinUrl(baseUrl, cssUrl);
cssSupport.exchangeRule(
cssRules,
rule,
"@import url(" + url + ");",
options
);
change = true;
});
return change;
};
/* CSS import inlining */
var substituteRule = function (cssRules, rule, newCssRules) {
var position = cssRules.indexOf(rule);
cssRules.splice(position, 1);
newCssRules.forEach(function (newRule, i) {
cssRules.splice(position + i, 0, newRule);
});
};
var loadAndInlineCSSImport = function (
cssRules,
rule,
alreadyLoadedCssUrls,
options
) {
var url = rule.href,
cssHrefRelativeToDoc;
url = cssSupport.unquoteString(url);
cssHrefRelativeToDoc = util.joinUrl(options.baseUrl, url);
if (alreadyLoadedCssUrls.indexOf(cssHrefRelativeToDoc) >= 0) {
// Remove URL by adding empty string
substituteRule(cssRules, rule, []);
return Promise.resolve([]);
} else {
alreadyLoadedCssUrls.push(cssHrefRelativeToDoc);
}
return util.ajax(url, options).then(
function (cssText) {
var externalCssRules = cssSupport.rulesForCssText(cssText, options);
// Recursively follow @import statements
return exports
.loadCSSImportsForRules(
externalCssRules,
alreadyLoadedCssUrls,
options
)
.then(function (result) {
exports.adjustPathsOfCssResources(
url,
externalCssRules,
options
);
substituteRule(cssRules, rule, externalCssRules);
return result.errors;
});
},
function (e) {
throw {
resourceType: "stylesheet",
url: e.url,
msg: "Unable to load stylesheet " + e.url,
};
}
);
};
exports.loadCSSImportsForRules = function (
cssRules,
alreadyLoadedCssUrls,
options
) {
var rulesToInline = findCSSImportRules(cssRules),
errors = [],
hasChanges = false;
return Promise.all(
rulesToInline.map(function (rule) {
return loadAndInlineCSSImport(
cssRules,
rule,
alreadyLoadedCssUrls,
options
).then(
function (moreErrors) {
errors = errors.concat(moreErrors);
hasChanges = true;
},
function (e) {
errors.push(e);
}
);
})
).then(function () {
return {
hasChanges: hasChanges,
errors: errors,
};
});
};
/* CSS linked resource inlining */
var loadAndInlineBackgroundImages = function (backgroundValue, options) {
var parsedBackground = backgroundValueParser.parse(backgroundValue),
externalBackgroundLayerIndices =
findExternalBackgroundUrls(parsedBackground),
hasChanges = false;
return util
.collectAndReportErrors(
externalBackgroundLayerIndices.map(function (backgroundLayerIndex) {
var url = parsedBackground[backgroundLayerIndex].url;
return util.getDataURIForImageURL(url, options).then(
function (dataURI) {
parsedBackground[backgroundLayerIndex].url = dataURI;
hasChanges = true;
},
function (e) {
throw {
resourceType: "backgroundImage",
url: e.url,
msg: "Unable to load background-image " + e.url,
};
}
);
})
)
.then(function (errors) {
return {
backgroundValue:
backgroundValueParser.serialize(parsedBackground),
hasChanges: hasChanges,
errors: errors,
};
});
};
var iterateOverRulesAndInlineBackgroundImages = function (cssRules, options) {
var rulesToInline = findBackgroundImageRules(cssRules),
backgroundDeclarations = findBackgroundDeclarations(rulesToInline),
errors = [],
cssHasChanges = false;
return Promise.all(
backgroundDeclarations.map(function (declaration) {
return loadAndInlineBackgroundImages(
declaration.value,
options
).then(function (result) {
if (result.hasChanges) {
updateCssPropertyValue(
declaration.rule,
declaration.property,
result.backgroundValue
);
cssHasChanges = true;
}
errors = errors.concat(result.errors);
});
})
).then(function () {
return {
hasChanges: cssHasChanges,
errors: errors,
};
});
};
var loadAndInlineFontFace = function (srcDeclarationValue, options) {
var hasChanges = false,
parsedFontFaceSources,
externalFontFaceUrlIndices;
try {
parsedFontFaceSources =
fontFaceSrcValueParser.parse(srcDeclarationValue);
} catch (e) {
parsedFontFaceSources = [];
}
externalFontFaceUrlIndices = findExternalFontFaceUrls(
parsedFontFaceSources
);
return util
.collectAndReportErrors(
externalFontFaceUrlIndices.map(function (urlIndex) {
var fontSrc = parsedFontFaceSources[urlIndex],
format = fontSrc.format || "woff";
return util.binaryAjax(fontSrc.url, options).then(
function (content) {
var base64Content = btoa(content);
fontSrc.url =
"data:font/" + format + ";base64," + base64Content;
hasChanges = true;
},
function (e) {
throw {
resourceType: "fontFace",
url: e.url,
msg: "Unable to load font-face " + e.url,
};
}
);
})
)
.then(function (errors) {
return {
srcDeclarationValue: fontFaceSrcValueParser.serialize(
parsedFontFaceSources
),
hasChanges: hasChanges,
errors: errors,
};
});
};
var iterateOverRulesAndInlineFontFace = function (cssRules, options) {
var rulesToInline = findFontFaceRules(cssRules),
errors = [],
hasChanges = false;
return Promise.all(
rulesToInline.map(function (rule) {
var srcDeclarationValue = rule.style.getPropertyValue("src");
return loadAndInlineFontFace(srcDeclarationValue, options).then(
function (result) {
if (result.hasChanges) {
cssSupport.changeFontFaceRuleSrc(
cssRules,
rule,
result.srcDeclarationValue,
options
);
hasChanges = true;
}
errors = errors.concat(result.errors);
}
);
})
).then(function () {
return {
hasChanges: hasChanges,
errors: errors,
};
});
};
exports.loadAndInlineCSSResourcesForRules = function (cssRules, options) {
var hasChanges = false,
errors = [];
return Promise.all(
[
iterateOverRulesAndInlineBackgroundImages,
iterateOverRulesAndInlineFontFace,
].map(function (func) {
return func(cssRules, options).then(function (result) {
hasChanges = hasChanges || result.hasChanges;
errors = errors.concat(result.errors);
});
})
).then(function () {
return {
hasChanges: hasChanges,
errors: errors,
};
});
};