Skip to content

Instantly share code, notes, and snippets.

@jxm262
Created January 9, 2017 01:31
Show Gist options
  • Save jxm262/657023382f5d95f4202d858e6724ba53 to your computer and use it in GitHub Desktop.
Save jxm262/657023382f5d95f4202d858e6724ba53 to your computer and use it in GitHub Desktop.
/**
* Shim up the window.Element prototype
* Partially inspired here - http://stackoverflow.com/questions/6209161/extract-the-current-dom-and-print-it-as-a-string-with-styles-intact
*/
export default (function () {
const canUseDOM = !!(
(typeof window !== 'undefined' &&
window.document && window.document.createElement)
)
if (canUseDOM) {
Element.prototype.serializeWithStyles = function () {
// Mapping between tag names and css default values lookup tables. This allows to exclude default values in the result.
//let defaultStylesByTagName = {};
// Styles inherited from style sheets will not be rendered for elements with these tag names
const noStyleTags = {
"BASE": true,
"HEAD": true,
"HTML": true,
"META": true,
"NOFRAME": true,
"NOSCRIPT": true,
"PARAM": true,
"SCRIPT": true,
"STYLE": true,
"TITLE": true
};
// This list determines which css default values lookup tables are precomputed at load time
// Lookup tables for other tag names will be automatically built at runtime if needed
const tagNames = ["A", "ABBR", "ADDRESS", "AREA", "ARTICLE", "ASIDE", "AUDIO", "B", "BASE", "BDI", "BDO", "BLOCKQUOTE", "BODY", "BR", "BUTTON", "CANVAS", "CAPTION", "CENTER", "CITE", "CODE", "COL", "COLGROUP", "COMMAND", "DATALIST", "DD", "DEL", "DETAILS", "DFN", "DIV", "DL", "DT", "EM", "EMBED", "FIELDSET", "FIGCAPTION", "FIGURE", "FONT", "FOOTER", "FORM", "H1", "H2", "H3", "H4", "H5", "H6", "HEAD", "HEADER", "HGROUP", "HR", "HTML", "I", "IFRAME", "IMG", "INPUT", "INS", "KBD", "KEYGEN", "LABEL", "LEGEND", "LI", "LINK", "MAP", "MARK", "MATH", "MENU", "META", "METER", "NAV", "NOBR", "NOSCRIPT", "OBJECT", "OL", "OPTION", "OPTGROUP", "OUTPUT", "P", "PARAM", "PRE", "PROGRESS", "Q", "RP", "RT", "RUBY", "S", "SAMP", "SCRIPT", "SECTION", "SELECT", "SMALL", "SOURCE", "SPAN", "STRONG", "STYLE", "SUB", "SUMMARY", "SUP", "SVG", "TABLE", "TBODY", "TD", "TEXTAREA", "TFOOT", "TH", "THEAD", "TIME", "TITLE", "TR", "TRACK", "U", "UL", "VAR", "VIDEO", "WBR"];
let defaultStylesByTagName = tagNames.reduce((accum, tagName, idx) => {
if (!noStyleTags[tagName]) {
accum[tagName] = computeDefaultStyleByTagName(tagName);
}
return accum;
}, {});
tagNames.forEach((tagName) => {
if (!noStyleTags[tagName]) {
defaultStylesByTagName[tagName] = computeDefaultStyleByTagName(tagName);
}
});
//TODO memoize below - this only really needs to load once and then get cached
function computeDefaultStyleByTagName(tagName) {
let defaultStyle = {};
let element = document.body.appendChild(document.createElement(tagName));
let computedStyle = getComputedStyle(element);
for (let i = 0; i < computedStyle.length; i++) {
defaultStyle[computedStyle[i]] = computedStyle[computedStyle[i]];
}
document.body.removeChild(element);
return defaultStyle;
}
function getDefaultStyleByTagName(tagName) {
tagName = tagName.toUpperCase();
//TODO: this block should never be reached, need to investigate which tags I'm missing
if (!defaultStylesByTagName[tagName]) {
defaultStylesByTagName[tagName] = computeDefaultStyleByTagName(tagName);
}
return defaultStylesByTagName[tagName];
}
function removeReactAttributes(elem) {
let attrs = elem.attributes;
let name;
let index;
for (index = attrs.length - 1; index >= 0; --index) {
name = attrs[index].nodeName;
if (name.substring(0, 5) === "data-") {
elem.removeAttribute(name);
}
}
}
return function serializeWithStyles() {
if (this.nodeType !== Node.ELEMENT_NODE) {
throw new TypeError();
}
let cssTexts = [];
let elements = this.querySelectorAll("*");
[...elements].forEach((elem, idx) => {
removeReactAttributes(elem);
console.log('elem ', elem);
if (!noStyleTags[elem.tagName]) {
let computedStyle = getComputedStyle(elem);
let defaultStyle = getDefaultStyleByTagName(elem.tagName);
cssTexts[idx] = elem.style.cssText;
for (let ii = 0; ii < computedStyle.length; ii++) {
const cssPropName = computedStyle[ii];
if (computedStyle[cssPropName] !== defaultStyle[cssPropName]) {
elem.style[cssPropName] = computedStyle[cssPropName];
}
}
}
})
var result = this.outerHTML;
for (let i = 0; i < elements.length; i++) {
elements[i].style.cssText = cssTexts[i];
}
return result;
};
}();
}
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment