Skip to content

Instantly share code, notes, and snippets.

@followdarko
Created March 13, 2017 19:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save followdarko/f6a097f2e2adfefa0d547be6bcf2094b to your computer and use it in GitHub Desktop.
Save followdarko/f6a097f2e2adfefa0d547be6bcf2094b to your computer and use it in GitHub Desktop.
Paste it into your Chrome DevTools console
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