Created
March 13, 2017 19:30
-
-
Save followdarko/f6a097f2e2adfefa0d547be6bcf2094b to your computer and use it in GitHub Desktop.
Paste it into your Chrome DevTools console
This file contains hidden or 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
function fillStyles(el, sel, styles, type){ | |
el[sel] = el[sel] || {} | |
for (var key in styles) { | |
if (!isNaN(key)) { | |
if (el[sel][styles[key]]) { | |
let selector | |
if (type === 'id') selector = '#'+sel | |
else if (type === 'class') selector = '.'+sel | |
else selector = '<'+sel+'>' | |
return { | |
elems: { | |
[0]: selector, | |
}, | |
prop: styles[key], | |
} | |
} | |
el[sel][styles[key]] = styles[styles[key]] | |
} | |
} | |
} | |
function getAll() { | |
let currentNode | |
const ni = document.createNodeIterator(document.documentElement, NodeFilter.SHOW_ELEMENT); | |
const arr = [] | |
while(currentNode = ni.nextNode()) { | |
// console.log(currentNode.nodeName); | |
arr.push(currentNode) | |
} | |
return arr | |
} | |
function toArray(nodeList) { | |
return [].slice.call(nodeList) | |
} | |
async function getLinkStyles(links) { | |
const href = toArray(links).filter((l) => { | |
return l.rel === 'stylesheet' | |
}).map(async (l) => { | |
return await fetch(l.href).then(res => res.text()) | |
}) | |
return Promise.all(href) | |
} | |
function calculateCss(arr) { | |
const innerHTML = arr.reduce((a,b) => a.concat(b)) | |
const sheet = document.createElement('style') | |
sheet.id = 'myStyle' | |
sheet.innerHTML = innerHTML | |
document.head.appendChild(sheet) | |
} | |
function compareStyles(styles, item, params) { | |
const conflicts = [] | |
const { ids, tags, classes } = params | |
styles.forEach((st) => { | |
let compare = [] | |
Object.keys(ids[item.id] || []).forEach((i) => { | |
if (i === st) { | |
compare.push('#'+item.id) | |
} | |
}) | |
Object.keys(tags[item.tag] || []).forEach((i) => { | |
if (i === st) { | |
compare.push('<'+item.tag+'>') | |
} | |
}) | |
item.class.forEach((c) => { | |
if (!classes[c]) return | |
// Если класс нигде не определен, но присутствует у html элемента. | |
// Use-case может использоваться в JS | |
Object.keys(classes[c]).forEach((i) => { | |
if (i === st) { | |
compare.push('.'+c) | |
} | |
}) | |
}) | |
if (Object.keys(compare).length > 1) { | |
conflicts.push({ | |
prop: st, | |
elems: compare, | |
}) | |
} | |
compare = [] | |
}) | |
return conflicts | |
} | |
(async function(){ | |
const tags = {} | |
const classes = {} | |
const ids = {} | |
const selfConflicts = [] | |
// TODO: пересчитывать наследственные свойства. | |
const allDOMNodes = getAll() | |
// console.warn(allDOMNodes) | |
const links = await getLinkStyles(document.querySelectorAll('link')) | |
calculateCss(links) | |
const s = toArray(document.querySelectorAll('style')).map(st => { | |
return st.sheet.rules | |
}).reduce((a,b) => { | |
return toArray(a).concat(toArray(b)) | |
}, []) | |
document.head.removeChild(document.querySelector('#myStyle')) | |
s.filter((item) => { // Protections from CSSFontRules | |
return item.selectorText !== undefined | |
}).forEach((item) => { // Parse CSS file Rules | |
let val = null | |
if (item.selectorText.indexOf(',') !== -1) { // Если селекторы перечисленны через запятую | |
const rules = item.selectorText.split(',') | |
rules.map(i => i.replace(/\s+/g, '')).forEach((i) => { | |
if (i[0] === '#') { // #id | |
val = fillStyles(ids, i.slice(1), item.style, 'id') | |
if (val) selfConflicts.push(val) | |
} else if(i[0] === '.') { // class | |
val = fillStyles(classes, i.slice(1), item.style, 'class') | |
if (val) selfConflicts.push(val) | |
} else { // tag | |
val = fillStyles(tags, i, item.style, 'tag') | |
if (val) selfConflicts.push(val) | |
} | |
}) | |
return | |
} | |
if (item.selectorText[0] === '#') { // #id | |
val = fillStyles(ids, item.selectorText.slice(1), item.style, 'id') | |
if (val) selfConflicts.push(val) | |
} else if(item.selectorText[0] === '.') { // .class | |
val = fillStyles(classes, item.selectorText.slice(1), item.style, 'class') | |
if (val) selfConflicts.push(val) | |
} else { // <tag> | |
val = fillStyles(tags, item.selectorText, item.style, 'tag') | |
if (val) selfConflicts.push(val) | |
} | |
}) | |
// console.warn(tags) | |
// console.warn(classes) | |
// console.warn(ids) | |
const idKeys = Object.keys(ids) | |
const tagKeys = Object.keys(tags) | |
const classKeys = Object.keys(classes) | |
const filterNodes = allDOMNodes.map((item) => { | |
return {tag: item.localName, id: item.id, class: item.classList, inline: item.style } | |
}) | |
filterNodes.forEach((item) => { | |
const compare = {} | |
idKeys.forEach((id) => { | |
if (item.id === id) { | |
// console.info('id compare') | |
compare.id = id | |
} | |
}) | |
classKeys.forEach((classItem) => { | |
item.class.forEach((classNode) => { | |
if (classItem === classNode) { | |
// console.info('class compare') | |
if (compare.class === undefined) { | |
compare.class = [] | |
compare.class.push(classNode) | |
return | |
} | |
compare.class.push(classNode) | |
} | |
}) | |
}) | |
tagKeys.forEach((tag) => { | |
if (item.tag === tag) { | |
// console.info('tag compare') | |
compare.tag = tag | |
} | |
}) | |
if (compare.id || compare.tag ) { | |
item.compare = compare | |
} | |
}) | |
const compareNode = filterNodes.filter((item) => { | |
return item.compare !== undefined | |
}) | |
const selectorsConflicts = compareNode.map((item) => { | |
const styles = [] | |
if (item.compare.id) { | |
Object.keys(ids[item.compare.id]).forEach((el) => { if(!styles.includes(el)) styles.push(el) }) | |
} | |
if (item.compare.tag) { | |
Object.keys(tags[item.compare.tag]).forEach((el) => { if(!styles.includes(el)) styles.push(el) }) | |
} | |
if (item.compare.class) { | |
item.compare.class.forEach((cl) => { | |
Object.keys(classes[cl]).forEach((el) => { if(!styles.includes(el)) styles.push(el) }) | |
}) | |
} | |
return compareStyles(styles, item, { ids, tags, classes }) | |
}).filter(con => con.length > 0) | |
.reduce((a, b) => a.concat(b), []) | |
/*============INLINE=================*/ | |
const inlineNodes = filterNodes.filter((item) => { | |
return toArray(item.inline).some((prop) => { | |
return prop !== '' | |
}) | |
}).map((st) => { | |
let inline = {} | |
for (var key in st.inline) { | |
if (!isNaN(key)) { | |
inline[key] = st.inline[key] | |
} | |
} | |
st.inline = inline | |
return st | |
}) | |
// console.warn('inlineNodes = ', inlineNodes) | |
const inlineConflicts = inlineNodes.map((item) => { | |
return Object.keys(item.inline).map((prop) =>{ | |
const compare = [] | |
if (item.id && ids[item.id]) { // && проверка на существование в таблице | |
Object.keys(ids[item.id]).forEach((st) => { | |
if (st === item.inline[prop]) { | |
compare.push(st) | |
} | |
}) | |
} | |
if (item.class.length) { | |
Object.keys(item.class).forEach((cl) => { | |
if (classes[item.class[cl]]) { // проверка на существование в таблице | |
Object.keys(classes[item.class[cl]]).forEach((st) => { | |
if (st === item.inline[prop]) { | |
compare.push(st) | |
} | |
}) | |
} | |
}) | |
} | |
if (tags[item.tag]) { | |
Object.keys(tags[item.tag]).forEach((st) => { | |
if (st === item.inline[prop]) { | |
compare.push(st) | |
} | |
}) | |
} | |
return compare | |
}).filter(con => con.length > 0) | |
.reduce((a, b) => a.concat(b), []) | |
.map((res) => { | |
let name = item.tag | |
if (item.id) name+='#'+item.id | |
if (item.class.length) name+=Object.keys(item.class).map(cl=>'.'+item.class[cl]) | |
return { | |
prop: res, | |
elem: name+'--INLINE', | |
} | |
}) | |
}).reduce((a, b) => a.concat(b), []) | |
// console.warn('inlineConflicts = ', inlineConflicts) | |
/*==============RESULT===========*/ | |
const conflicts = selfConflicts.concat(selectorsConflicts).concat(inlineConflicts) | |
// console.warn('conflicts = ', conflicts) | |
console.group('Confilcts') | |
if (conflicts.length) { | |
console.log('%c Conflicts count: ' + conflicts.length, 'color: red;') | |
console.log('%c Styles recalculation in next props: ❌', 'color: red;') | |
conflicts.forEach((con) => { | |
console.group(con.prop) | |
if (con.elems && con.elems[1]) { | |
console.log(con.elems[0] + ' <---> ' + con.elems[1]) | |
} else if (con.elem) { | |
console.log(con.elem + ' has inline style that recalculate 🙈') | |
} else { | |
console.log(con.elems[0] + ' is recalculate himself 🙉') | |
} | |
console.groupEnd() | |
}) | |
} else { | |
console.log('%c There are no conflicts ✅', 'color: green;') | |
} | |
})() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment