Created
January 9, 2017 01:31
-
-
Save jxm262/657023382f5d95f4202d858e6724ba53 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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