{"version":3,"file":"index.mjs","sources":["../src/index.js"],"sourcesContent":["/**\r\n * Copyright (c) 2021, Anton Babakhin\r\n * All rights reserved. (MIT Licensed)\r\n *\r\n * critical-css-parser\r\n */\r\n\r\nconst httpServer = require('http-server');\r\nconst puppeteer = require('puppeteer');\r\nconst dropcss = require('dropcss');\r\nconst { get } = require('axios');\r\n\r\n/**\r\n * Receive critical and other CSS\r\n * @param  {object} options Options\r\n * @return {Promise<{ critical: string, rest: string }>} Result object with critical css and rest css\r\n */\r\nasync function criticalCSSParser(options) {\r\n  if (options.type === 'HTML') {\r\n    const {\r\n      html = '',\r\n      css = '',\r\n      whitelist = /#fooBazBarAboveTheFold8917/,\r\n      minify = false\r\n    } = options;\r\n\r\n    const { aboveTheFold, aboveTheFoldMob } = await puppeteerResources({\r\n      html,\r\n      css,\r\n      type: options.type\r\n    });\r\n\r\n    return extract(aboveTheFold, aboveTheFoldMob, css, whitelist, minify);\r\n  } else if (options.type === 'URL') {\r\n    const {\r\n      URL = '',\r\n      enableGoogleFonts = 0,\r\n      whitelist = /#fooBazBarAboveTheFold8917/,\r\n      minify = false\r\n    } = options;\r\n\r\n    const { aboveTheFold, aboveTheFoldMob, css } = await puppeteerResources({\r\n      enableGoogleFonts,\r\n      url: URL,\r\n      type: options.type\r\n    });\r\n\r\n    return extract(aboveTheFold, aboveTheFoldMob, css, whitelist, minify);\r\n  } else if (options.type === 'localServer') {\r\n    const {\r\n      entrypoint = '',\r\n      filename = 'index.html',\r\n      enableGoogleFonts = 0,\r\n      whitelist = /#fooBazBarAboveTheFold8917/,\r\n      minify = false\r\n    } = options;\r\n\r\n    // Create local server to open the page\r\n    const server = httpServer.createServer({ root: entrypoint });\r\n    server.listen(6543);\r\n\r\n    const { aboveTheFold, aboveTheFoldMob, css } = await puppeteerResources({\r\n      enableGoogleFonts,\r\n      url: `http://127.0.0.1:6543/${filename}`,\r\n      type: options.type\r\n    });\r\n\r\n    server.close();\r\n\r\n    return extract(aboveTheFold, aboveTheFoldMob, css, whitelist, minify);\r\n  }\r\n}\r\n\r\n/**\r\n * Receive above-the-fold HTML\r\n * @param  {object} page Page from Puppeteer\r\n * @param  {number} height Height of viewport\r\n * @return {Promise<string>} Result html\r\n */\r\nasync function getAboveTheFoldHTML(page, height) {\r\n  await page.$$eval('body *:not(script):not(style)', (elems, height) => {\r\n    Array.from(elems).forEach(elem => {\r\n      try {\r\n        const computedStyle = window.getComputedStyle(elem);\r\n        let top = 0;\r\n        let marginTop = 0;\r\n\r\n        const computedTop = computedStyle.top;\r\n        if (computedTop.indexOf('px') !== -1) {\r\n          top = Math.abs(Number.parseInt(computedTop, 10));\r\n        } else if (computedTop.indexOf('%') !== -1) {\r\n          const parentHeight = elem.parentElement.offsetHeight;\r\n          const percent = Math.abs(Number.parseInt(computedTop, 10));\r\n          top = (parentHeight * percent) / 100;\r\n        }\r\n\r\n        const computedMarginTop = computedStyle.marginTop;\r\n        marginTop = Math.abs(Number.parseInt(computedMarginTop, 10));\r\n\r\n        if (elem.getBoundingClientRect().top - marginTop - top + pageYOffset > height) {\r\n          // Remove element below the fold\r\n          elem.remove();\r\n        } else {\r\n          const parentComputedStyle = window.getComputedStyle(elem.parentElement);\r\n          if (\r\n            parentComputedStyle.display === 'none'\r\n            || (\r\n              parentComputedStyle.overflow === 'hidden'\r\n              && (parentComputedStyle.height === '0px' || parentComputedStyle.width === '0px')\r\n            )\r\n          ) {\r\n            // Remove hidden element\r\n            elem.remove();\r\n          }\r\n        }\r\n      } catch (e) {\r\n        console.error(e);\r\n      }\r\n    });\r\n  }, height);\r\n\r\n  const html = await page.content();\r\n  return html;\r\n}\r\n\r\n/**\r\n * Receive resources from Puppeteer\r\n * @param  {object} options Launch options\r\n * @return {Promise<{ aboveTheFold: string, aboveTheFoldMob: string, css: string }>} Result resources\r\n */\r\nasync function puppeteerResources(options) {\r\n  const cache = { };\r\n  const browser = await puppeteer.launch();\r\n  let cssString = '';\r\n  // Puppeteer page with desktop version\r\n  const context = await browser.createIncognitoBrowserContext();\r\n  const page = await context.newPage();\r\n  await page.setRequestInterception(true);\r\n  page.on('request', interceptedRequest => {\r\n    if (interceptedRequest.url().indexOf('.css') !== -1) {\r\n      cache[interceptedRequest.url()] = interceptedRequest;\r\n    }\r\n    interceptedRequest.continue();\r\n  });\r\n  // Puppeteer page with mobile version\r\n  const context2 = await browser.createIncognitoBrowserContext();\r\n  const page2 = await context2.newPage();\r\n\r\n  await Promise.all([new Promise(async (res, rej) => {\r\n    await page.setDefaultNavigationTimeout(120000);\r\n    await page.setViewport({ width: 1920, height: 1200 });\r\n    if (options.type === 'HTML') {\r\n      await page.setContent(options.html, { waitUntil: 'networkidle2'\t});\r\n      await page.addStyleTag({ content: options.css });\r\n    } else {\r\n      await page.goto(options.url, { waitUntil: 'networkidle2' });\r\n      let styleHrefs = await page.$$eval(':not(noscript) > link[rel=stylesheet]', elems => Array.from(elems).map(s => s.href));\r\n      if (!options.enableGoogleFonts) {\r\n        styleHrefs = styleHrefs.filter(href => href.indexOf('fonts.googleapis.com') === -1);\r\n      }\r\n      // Concatenate all styles\r\n      await Promise.all(styleHrefs.map(async href => {\r\n        if (cache[href]) {\r\n          let data = await cache[href].response().text();\r\n          cssString += data;\r\n        } else {\r\n          let { data } = await get(href);\r\n          cssString += data;\r\n        }\r\n      }));\r\n    }\r\n    res();\r\n  }), new Promise(async (res, rej) => {\r\n    await page2.setDefaultNavigationTimeout(60000);\r\n    await page2.setViewport({ width: 480, height: 650, isMobile: true, hasTouch: true });\r\n    if (options.type === 'HTML') {\r\n      await page2.setContent(options.html, { waitUntil: 'networkidle2' });\r\n      await page2.addStyleTag({ content: options.css });\r\n    } else {\r\n      await page2.goto(options.url, { waitUntil: 'networkidle2' });\r\n    }\r\n    res();\r\n  })]);\r\n\r\n  const aboveTheFold = await getAboveTheFoldHTML(page, 1200);\r\n  const aboveTheFoldMob = await getAboveTheFoldHTML(page2, 650);\r\n\r\n  await browser.close();\r\n\r\n  return { aboveTheFold, aboveTheFoldMob, css: cssString };\r\n}\r\n\r\n/**\r\n * Extract critical and rest CSS from HTML\r\n * @param  {string} deskHTML HTML of desktop version\r\n * @param  {string} mobHTML HTML of mobile version\r\n * @param  {string} css CSS\r\n * @param  {RegExp} whitelist Regular Expression of needed tags\r\n * @return {Promise<{ critical: string, rest: string }>} Result object with critical css and rest css\r\n */\r\nfunction extract(deskHTML, mobHTML, css, whitelist, minify) {\r\n  // Receive above-the-fold css-selectors of desktop version\r\n  let resDesk = dropcss({\r\n    css,\r\n    html: deskHTML,\r\n    shouldDrop: sel => !whitelist.test(sel)\r\n  });\r\n\r\n  // Receive above-the-fold css-selectors of mobile version\r\n  let resMob = dropcss({\r\n    css,\r\n    html: mobHTML,\r\n    shouldDrop: sel => !whitelist.test(sel)\r\n  });\r\n\r\n  let selectors = new Set();\r\n\tresDesk.sels.forEach(sel => selectors.add(sel));\r\n\tresMob.sels.forEach(sel => selectors.add(sel));\r\n\r\n  let above = dropcss({\r\n    css,\r\n    html: '',\r\n    shouldDrop: sel => !selectors.has(sel)\r\n  });\r\n\r\n  let rest = dropcss({\r\n    css,\r\n    html: '',\r\n    shouldDrop: sel => selectors.has(sel)\r\n  });\r\n\r\n  if (minify) {\r\n    const csso = require('csso');\r\n    above.css = csso.minify(above.css).css;\r\n    rest.css = csso.minify(rest.css).css;\r\n  }\r\n\r\n  return { critical: above.css, rest: rest.css };\r\n}\r\n\r\nmodule.exports = criticalCSSParser;\r\n"],"names":["puppeteerResources","options","cache","puppeteer","launch","browser","cssString","createIncognitoBrowserContext","context","newPage","page","setRequestInterception","on","interceptedRequest","url","indexOf","context2","page2","Promise","all","res","rej","setDefaultNavigationTimeout","setViewport","width","height","type","setContent","html","waitUntil","addStyleTag","content","css","$$eval","elems","Array","from","map","s","href","styleHrefs","enableGoogleFonts","filter","response","text","data","get","isMobile","hasTouch","getAboveTheFoldHTML","aboveTheFold","aboveTheFoldMob","close","forEach","elem","marginTop","computedStyle","window","getComputedStyle","top","computedTop","Math","abs","Number","parseInt","parentElement","offsetHeight","getBoundingClientRect","pageYOffset","remove","parentComputedStyle","display","overflow","e","console","error","httpServer","require","dropcss","extract","deskHTML","mobHTML","whitelist","minify","resDesk","shouldDrop","sel","test","resMob","selectors","Set","sels","add","above","has","rest","csso","critical","module","exports","URL","entrypoint","filename","server","createServer","root","listen"],"mappings":"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"}