Created
January 18, 2020 18:54
-
-
Save scriptify/d0e275a45a2d460022887b7e18e94c87 to your computer and use it in GitHub Desktop.
You know what it is
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
/** Play around and try to convert a website into re-usable React components */ | |
// Code from https://github.com/scriptify/statikk-converter/ | |
(() => { | |
const $ = document.querySelectorAll.bind(document); | |
function extractElements() { | |
const allElements = $('body *'); | |
const allStatikkElements = []; | |
for (const elem of allElements) { | |
allStatikkElements.push(domToStatikk(elem)); | |
} | |
return allStatikkElements; | |
} | |
/** | |
* Convert a DOM element to | |
* a data structure with which | |
* it's easy to work with later on. | |
* @example | |
* { | |
* tag: 'div', | |
* attributes: [['class', 'header'], ['id', 'title1']], | |
* children: StatikkElement | |
* } | |
*/ | |
function domToStatikk(elem = document.createElement('div')) { | |
return { | |
id: Math.round(Math.random() * 10000), | |
tag: elem.tagName, | |
attributes: [...elem.attributes].reduce( | |
(obj, attr) => ({ [attr.name]: attr.value, ...obj }), | |
{}, | |
), | |
children: [...elem.children].map(domToStatikk), | |
domElem: elem, | |
}; | |
} | |
function haveSameHierarchalStructure(elem1, elem2) { | |
const stringify = element => | |
`${element.tag}//${element.children.map(c => c.tag).join('//')}`; | |
const str1 = stringify(elem1); | |
const str2 = stringify(elem2); | |
return str1 === str2; | |
} | |
/** | |
* If elements have same hierarchal structure | |
* and at least one common class name *or* | |
* attribute value, they are considered | |
* *similar*. | |
*/ | |
function areElementsSimilar(elem1, elem2) { | |
const elem1ClassNames = (elem1.attributes['class'] || '').split(' '); | |
const elem2ClassNames = (elem2.attributes['class'] || '').split(' '); | |
const sameStructure = haveSameHierarchalStructure(elem1, elem2); | |
if (!sameStructure) return false; | |
const sameAttribute = Object.keys(elem1.attributes).find(attrName => { | |
const isSame = elem1.attributes[attrName] === elem2.attributes[attrName]; | |
return isSame; | |
}); | |
if (sameAttribute) return true; | |
const sameClassName = elem1ClassNames.find(className => | |
elem2ClassNames.includes(className), | |
); | |
return sameClassName; | |
} | |
/** | |
* Only use this function with elements | |
* where areElementsSimilar was true. | |
* Determines which parts of the specified elements | |
* stay the same and which parts are different. | |
* The different parts (attributes, classnames) | |
* need to be controlled by props later on. | |
*/ | |
function elementDifferences(elements = []) { | |
if (elements.length < 2) throw new Error('At least 2 elements required.'); | |
const count = (arr, val) => { | |
return arr.filter(elem => elem === val).length; | |
}; | |
const deduplicate = (arr = []) => [...new Set(arr)]; | |
const allClassNames = elements | |
.map(elem => | |
elem.attributes['class'] ? elem.attributes['class'].split(' ') : [], | |
) | |
.flat(); | |
// If a classname is exactly elements.length times in the array, | |
// it is a static value | |
const staticClassNames = allClassNames.filter( | |
className => count(allClassNames, className) >= elements.length, | |
); | |
// If the attribute name,value pair occurs element.length times in the array, | |
// it is a static value | |
const allAttributesNames = elements | |
.map(elem => | |
Object.keys(elem.attributes).filter(attrName => attrName !== 'class'), | |
) | |
.flat(); | |
const staticAttributeNames = allAttributesNames.filter(attrName => { | |
const isDifferent = elements.find(elem => { | |
return elem.attributes[attrName] !== elements[0].attributes[attrName]; | |
}); | |
return !isDifferent; | |
}); | |
return { | |
staticClassNames: deduplicate(staticClassNames), | |
staticAttributeNames: deduplicate(staticAttributeNames), | |
}; | |
} | |
function determineSimilarElements(element, allStatikkElements = []) { | |
const qualifiesAsPossibleComponent = | |
element.children.length > 0 || element.attributes.length > 0; | |
if (!qualifiesAsPossibleComponent) return []; | |
// Find elements with same hierachal structure and at least | |
// *one* same attribute or class name | |
const similarElements = allStatikkElements.filter( | |
elemToCompare => | |
elemToCompare.id !== element.id && | |
areElementsSimilar(element, elemToCompare), | |
); | |
return similarElements; | |
} | |
function toCamelCase(sentenceCase) { | |
let out = ''; | |
const splitted = sentenceCase.split(/[^a-zA-Z0-9]/).filter(Boolean); | |
splitted.forEach(function(el, idx) { | |
var add = el.toLowerCase(); | |
out += idx === 0 ? add : add[0].toUpperCase() + add.slice(1); | |
}); | |
return out; | |
} | |
function cmpName(statikkElem) { | |
const joinedClassNames = statikkElem.static.staticClassNames.join(' '); | |
let retName = joinedClassNames ? toCamelCase(joinedClassNames) : ''; | |
const [attrName] = statikkElem.static.staticAttributeNames; | |
const attrVal = statikkElem.attributes[attrName]; | |
if (!retName) { | |
try { | |
const url = new URL(attrVal); | |
retName = `${url.hostname | |
.replace('www.', '') | |
.replace(/\..*$/, '')}Link`; | |
} catch {} | |
} | |
if (!retName) { | |
retName = attrName ? toCamelCase(attrVal) : ''; | |
} | |
if (!retName) { | |
retName = statikkElem.tag; | |
} | |
return `${retName.charAt(0).toUpperCase()}${retName.slice(1)}`; | |
} | |
function componentify(statikkElem) { | |
const lowercaseTag = statikkElem.tag.toLowerCase(); | |
const joinedClassNames = statikkElem.static.staticClassNames.join(' '); | |
const classNameString = | |
statikkElem.static.staticClassNames.length > 0 | |
? ` className="${joinedClassNames}"` | |
: ''; | |
const attributesStr = ` ${statikkElem.static.staticAttributeNames | |
.map(name => `${name}="${statikkElem.attributes[name]}"`) | |
.join(' ')}`; | |
const sanitizedAttrStr = attributesStr.slice(0, attributesStr.length - 1); | |
const render = `<${lowercaseTag}${classNameString}${sanitizedAttrStr}>{children}</${lowercaseTag}>`; | |
return { | |
...statikkElem, | |
component: { | |
name: cmpName(statikkElem), | |
render, | |
}, | |
}; | |
} | |
function main() { | |
const allStatikkElements = extractElements(); | |
const similarized = allStatikkElements | |
.map(elem => ({ | |
...elem, | |
similarElements: determineSimilarElements(elem, allStatikkElements), | |
})) | |
.filter(elem => elem.similarElements && elem.similarElements.length > 0); | |
const similiarWithStaticProps = similarized.map(elem => ({ | |
...elem, | |
static: elementDifferences(elem.similarElements.concat([elem])), | |
})); | |
const allSameIds = similiarWithStaticProps | |
.map(el => el.id) | |
.map(id => { | |
const elementsWithSimilarElement = similiarWithStaticProps | |
.filter(el => el.similarElements.map(el => el.id).includes(id)) | |
.map(el => el.id); | |
return [...elementsWithSimilarElement, id].sort(); | |
}); | |
const uniqueIdsArr = []; | |
for (const idsArr of allSameIds) { | |
if (!uniqueIdsArr.find(arr => arr.toString() === idsArr.toString())) { | |
uniqueIdsArr.push(idsArr); | |
} | |
} | |
const statikkElements = uniqueIdsArr | |
.map(arr => { | |
const allFoundElems = similiarWithStaticProps.filter(el => | |
arr.includes(el.id), | |
); | |
return allFoundElems; | |
}) | |
.flat(); | |
const componentRepresentationsUnprepared = statikkElements.map( | |
componentify, | |
); | |
const components = componentRepresentationsUnprepared.map( | |
compRep => compRep.component, | |
); | |
const componentRepresentations = componentRepresentationsUnprepared.map( | |
comp => ({ ...comp, component: comp.component.name }), | |
); | |
const uniqueComponents = [ | |
...new Set(components.map(cmp => cmp.name)), | |
].map(name => components.find(cmp => cmp.name === name)); | |
console.log(componentRepresentations, uniqueComponents); | |
} | |
main(); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment