next
Version:
The React Framework
24 lines • 5.37 kB
JavaScript
;exports.__esModule=true;exports.default=void 0;var _nodeHtmlParser=require("node-html-parser");var _constants=require("./constants");// const MIDDLEWARE_TIME_BUDGET = parseInt(process.env.__POST_PROCESS_MIDDLEWARE_TIME_BUDGET || '', 10) || 10
const MAXIMUM_IMAGE_PRELOADS=2;const IMAGE_PRELOAD_SIZE_THRESHOLD=2500;const middlewareRegistry=[];function registerPostProcessor(name,middleware,condition){middlewareRegistry.push({name,middleware,condition:condition||null});}async function processHTML(html,data,options){// Don't parse unless there's at least one processor middleware
if(!middlewareRegistry[0]){return html;}const postProcessData={preloads:{images:[]}};const root=(0,_nodeHtmlParser.parse)(html);let document=html;// Calls the middleware, with some instrumentation and logging
async function callMiddleWare(middleware){// let timer = Date.now()
middleware.inspect(root,postProcessData,data);document=await middleware.mutate(document,postProcessData,data);// timer = Date.now() - timer
// if (timer > MIDDLEWARE_TIME_BUDGET) {
// TODO: Identify a correct upper limit for the postprocess step
// and add a warning to disable the optimization
// }
return;}for(let i=0;i<middlewareRegistry.length;i++){let middleware=middlewareRegistry[i];if(!middleware.condition||middleware.condition(options)){await callMiddleWare(middlewareRegistry[i].middleware);}}return document;}class FontOptimizerMiddleware{constructor(){this.fontDefinitions=[];this.mutate=async(markup,_data,options)=>{let result=markup;if(!options.getFontDefinition){return markup;}for(const key in this.fontDefinitions){const[url,nonce]=this.fontDefinitions[key];const fallBackLinkTag=`<link rel="stylesheet" href="${url}"/>`;if(result.indexOf(`<style data-href="${url}">`)>-1||result.indexOf(fallBackLinkTag)>-1){// The font is already optimized and probably the response is cached
continue;}const fontContent=options.getFontDefinition(url);if(!fontContent){/**
* In case of unreachable font definitions, fallback to default link tag.
*/result=result.replace('</head>',`${fallBackLinkTag}</head>`);}else{const nonceStr=nonce?` nonce="${nonce}"`:'';result=result.replace('</head>',`<style data-href="${url}"${nonceStr}>${fontContent}</style></head>`);}}return result;};}inspect(originalDom,_data,options){if(!options.getFontDefinition){return;}// collecting all the requested font definitions
originalDom.querySelectorAll('link').filter(tag=>tag.getAttribute('rel')==='stylesheet'&&tag.hasAttribute('data-href')&&_constants.OPTIMIZED_FONT_PROVIDERS.some(url=>{const dataHref=tag.getAttribute('data-href');return dataHref?dataHref.startsWith(url):false;})).forEach(element=>{const url=element.getAttribute('data-href');const nonce=element.getAttribute('nonce');if(url){this.fontDefinitions.push([url,nonce]);}});}}class ImageOptimizerMiddleware{constructor(){this.mutate=async(markup,_data)=>{let result=markup;let imagePreloadTags=_data.preloads.images.filter(imgHref=>!preloadTagAlreadyExists(markup,imgHref)).reduce((acc,imgHref)=>acc+`<link rel="preload" href="${imgHref}" as="image"/>`,'');return result.replace(/<link rel="preload"/,`${imagePreloadTags}<link rel="preload"`);};}inspect(originalDom,_data){const imgElements=originalDom.querySelectorAll('img');let eligibleImages=[];for(let i=0;i<imgElements.length;i++){if(isImgEligible(imgElements[i])){eligibleImages.push(imgElements[i]);}if(eligibleImages.length>=MAXIMUM_IMAGE_PRELOADS){break;}}_data.preloads.images=[];for(const imgEl of eligibleImages){const src=imgEl.getAttribute('src');if(src){_data.preloads.images.push(src);}}}}function isImgEligible(imgElement){let imgSrc=imgElement.getAttribute('src');return!!imgSrc&&sourceIsSupportedType(imgSrc)&&imageIsNotTooSmall(imgElement)&&imageIsNotHidden(imgElement);}function preloadTagAlreadyExists(html,href){const regex=new RegExp(`<link[^>]*href[^>]*${href}`);return html.match(regex);}function imageIsNotTooSmall(imgElement){// Skip images without both height and width--we don't know enough to say if
// they are too small
if(!(imgElement.hasAttribute('height')&&imgElement.hasAttribute('width'))){return true;}try{const heightAttr=imgElement.getAttribute('height');const widthAttr=imgElement.getAttribute('width');if(!heightAttr||!widthAttr){return true;}if(parseInt(heightAttr)*parseInt(widthAttr)<=IMAGE_PRELOAD_SIZE_THRESHOLD){return false;}}catch(err){return true;}return true;}// Traverse up the dom from each image to see if it or any of it's
// ancestors have the hidden attribute.
function imageIsNotHidden(imgElement){let activeElement=imgElement;while(activeElement.parentNode){if(activeElement.hasAttribute('hidden')){return false;}activeElement=activeElement.parentNode;}return true;}// Currently only filters out svg images--could be made more specific in the future.
function sourceIsSupportedType(imgSrc){return!imgSrc.includes('.svg');}// Initialization
registerPostProcessor('Inline-Fonts',new FontOptimizerMiddleware(),// Using process.env because passing Experimental flag through loader is not possible.
// @ts-ignore
options=>options.optimizeFonts||process.env.__NEXT_OPTIMIZE_FONTS);registerPostProcessor('Preload Images',new ImageOptimizerMiddleware(),// @ts-ignore
options=>options.optimizeImages||process.env.__NEXT_OPTIMIZE_IMAGES);var _default=processHTML;exports.default=_default;
//# sourceMappingURL=post-process.js.map