UNPKG

betterpenthouse

Version:

Generate critical path CSS for web pages

133 lines (103 loc) 4.68 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = pruneNonCriticalSelectors; // executed inside sandboxed browser environment, // no access to scrope outside of function function pruneNonCriticalSelectors({ type, selectors, maxElementsToCheckPerSelector }) { console.log('debug: pruneNonCriticalSelectors init'); var h = window.innerHeight; // cache whether elements are above fold, // primarily because getBoundingClientRect() can be slow to query, // and some stylesheets have lots of generic selectors (like '.button', '.fa' etc) var isElementAboveFoldCache = new Map(); var isElementBelowFoldCache = new Map(); function isElementAboveFold(element) { if (isElementAboveFoldCache.has(element)) { return isElementAboveFoldCache.get(element); } // temporarily force clear none in order to catch elements that clear previous // content themselves and who w/o their styles could show up unstyled in above // the fold content (if they rely on f.e. 'clear:both;' to clear some main content) // but do that only if elements have their style attribute defined // (ref: https://github.com/pocketjoso/penthouse/issues/342) const isElementStyleDefined = typeof element.style !== 'undefined'; if (isElementStyleDefined) { var originalClearStyle = element.style.clear || ''; element.style.clear = 'none'; } var aboveFold = element.getBoundingClientRect().top < h; // cache so we dont have to re-query DOM for this value isElementAboveFoldCache.set(element, aboveFold); if (isElementStyleDefined) { // set clear style back to what it was element.style.clear = originalClearStyle; } // Should not be needed anymore with Chrome Headless: // do some monitoring before complete removing the code (below) // if (!aboveFold) { // // phantomJS/QT browser has some bugs regarding fixed position; // // sometimes positioning elements outside of screen incorrectly. // // just keep all fixed position elements - normally very few in a stylesheet anyway // var styles = window.getComputedStyle(element, null) // if (styles.position === 'fixed') { // console.log('debug: force keeping fixed position styles') // return true // } // } return aboveFold; } function isElementBelowFold(element) { if (isElementBelowFoldCache.has(element)) { return isElementBelowFoldCache.get(element); } const isElementStyleDefined = typeof element.style !== 'undefined'; if (isElementStyleDefined) { var originalClearStyle = element.style.clear || ''; element.style.clear = 'none'; } var belowFold = element.getBoundingClientRect().top > h; // cache so we dont have to re-query DOM for this value isElementBelowFoldCache.set(element, belowFold); if (isElementStyleDefined) { // set clear style back to what it was element.style.clear = originalClearStyle; } return belowFold; } function isSelectorCritical(selector, type) { // we have a selector to test, first grab any matching elements let elements; try { elements = document.querySelectorAll(selector); } catch (e) { // not a valid selector, remove it. return false; } let nrElementsToCheck = elements.length; if (maxElementsToCheckPerSelector && nrElementsToCheck > maxElementsToCheckPerSelector) { console.log(`debug: isSelectorCritical, selector: ${selector} appearing ${nrElementsToCheck} time on page, ONLY checking first ${maxElementsToCheckPerSelector}...`); nrElementsToCheck = maxElementsToCheckPerSelector; } // only keep selectors that match at least one elements on the page above the fold for (let idx = 0; idx < nrElementsToCheck; idx++) { const element = elements[idx]; const path = `${element.tagName}#${element.id}:${element.getAttribute('class')}`; if (type === 'atf' && isElementAboveFold(element)) { console.log(`debug: isSelectorCritical, selector: ${selector} is atf and find on ${path}`); return true; } if (type === 'btf' && isElementBelowFold(element)) { console.log(`debug: isSelectorCritical, selector: ${selector} is btf and find on ${path}`); return true; } } return false; } function filterSelectors(selectors, type) { console.log('debug: filterSelectors START'); selectors = selectors.filter(element => isSelectorCritical(element, type)); console.log('debug: filterSelectors DONE'); return selectors; } return filterSelectors(selectors, type); }