[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\nend_of_line = lf\nmax_line_length = null\n"
  },
  {
    "path": ".eleventy.js",
    "content": "const url = require('url');\nconst querystring = require('querystring');\nconst path = require('path');\nconst { JSDOM } = require('jsdom');\nconst sharp = require('sharp');\nconst fetch = require('cross-fetch');\nconst cache = require('./cache');\nconst {\n  transformImgPath,\n  logMessage,\n  initScript,\n  checkConfig,\n} = require('./helpers');\n\n// The default values for the plugin\nconst defaultLazyImagesConfig = {\n  maxPlaceholderWidth: 25,\n  maxPlaceholderHeight: 25,\n  imgSelector: 'img',\n  transformImgPath,\n  className: ['lazyload'],\n  cacheFile: '.lazyimages.json',\n  appendInitScript: true,\n  scriptSrc: 'https://cdn.jsdelivr.net/npm/lazysizes@5/lazysizes.min.js',\n  preferNativeLazyLoad: false,\n  setWidthAndHeightAttrs: true,\n  addNoScript: false,\n};\n\n// A global to store the current config (saves us passing it around functions)\nlet lazyImagesConfig = defaultLazyImagesConfig;\n\n// Reads the image object from the source file\nconst readImage = async (imageSrc) => {\n  let image;\n\n  if (imageSrc.startsWith('http') || imageSrc.startsWith('//')) {\n    const res = await fetch(imageSrc);\n    const buffer = await res.buffer();\n    image = await sharp(buffer);\n    return image;\n  }\n\n  try {\n    image = await sharp(imageSrc);\n    await image.metadata(); // just to confirm it can be read\n  } catch (firstError) {\n    try {\n      // We couldn't read the file at the input path, but maybe it's\n      // in './src', developers love to put things in './src'\n      image = await sharp(`./src/${imageSrc}`);\n      await image.metadata();\n    } catch (secondError) {\n      throw firstError;\n    }\n  }\n\n  return image;\n};\n\n// Gets the image width+height+LQIP from the cache, or generates them if not found\nconst getImageData = async (imageSrc) => {\n  const {\n    maxPlaceholderWidth,\n    maxPlaceholderHeight,\n    cacheFile,\n  } = lazyImagesConfig;\n\n  let imageData = cache.read(imageSrc);\n\n  if (imageData) {\n    return imageData;\n  }\n\n  logMessage(`started processing ${imageSrc}`);\n\n  const image = await readImage(imageSrc);\n  const metadata = await image.metadata();\n  const width = metadata.width;\n  const height = metadata.height;\n\n  const lqip = await image\n    .resize({\n      width: maxPlaceholderWidth,\n      height: maxPlaceholderHeight,\n      fit: sharp.fit.inside,\n    })\n    .blur()\n    .toBuffer();\n\n  const encodedLqip = lqip.toString('base64');\n\n  imageData = {\n    width,\n    height,\n    src: `data:image/png;base64,${encodedLqip}`,\n  };\n\n  logMessage(`finished processing ${imageSrc}`);\n  cache.update(cacheFile, imageSrc, imageData);\n  return imageData;\n};\n\n// Adds the attributes to the image element\nconst processImage = async (imgElem, options) => {\n  const {\n    transformImgPath,\n    className,\n    preferNativeLazyLoad,\n    setWidthAndHeightAttrs,\n  } = lazyImagesConfig;\n\n  if (preferNativeLazyLoad) {\n    imgElem.setAttribute('loading', 'lazy');\n  }\n\n  if (imgElem.src.startsWith('data:')) {\n    logMessage('skipping image with data URI');\n    return;\n  }\n\n  const imgPath = transformImgPath(imgElem.src, options);\n  const parsedUrl = url.parse(imgPath);\n  let fileExt = path.extname(parsedUrl.pathname).substr(1);\n\n  if (!fileExt) {\n    // Twitter and similar pass the file format in the querystring, e.g. \"?format=jpg\"\n    fileExt =\n      querystring.parse(parsedUrl.query).format ||\n      querystring.parse(parsedUrl.query).fm;\n  }\n\n  imgElem.setAttribute('data-src', imgElem.src);\n\n  const classNameArr = Array.isArray(className) ? className : [className];\n  imgElem.classList.add(...classNameArr);\n\n  if (imgElem.hasAttribute('srcset')) {\n    const srcSet = imgElem.getAttribute('srcset');\n    imgElem.setAttribute('data-srcset', srcSet);\n    imgElem.removeAttribute('srcset');\n  }\n\n  try {\n    const image = await getImageData(imgPath);\n    imgElem.setAttribute('src', image.src);\n\n    if (!setWidthAndHeightAttrs || fileExt === 'svg') {\n      return;\n    }\n\n    const widthAttr = imgElem.getAttribute('width');\n    const heightAttr = imgElem.getAttribute('height');\n\n    if (!widthAttr && !heightAttr) {\n      imgElem.setAttribute('width', image.width);\n      imgElem.setAttribute('height', image.height);\n    } else if (widthAttr && !heightAttr) {\n      const ratioHeight = (image.height * widthAttr) / image.width;\n      imgElem.setAttribute('height', Math.round(ratioHeight));\n    } else if (heightAttr && !widthAttr) {\n      const ratioWidth = (image.width * heightAttr) / image.height;\n      imgElem.setAttribute('width', Math.round(ratioWidth));\n    }\n  } catch (e) {\n    logMessage(`${e.message}: ${imgPath}`);\n  }\n};\n\n// Scans the output HTML for images, processes them, & appends the init script\nasync function transformMarkup(rawContent, outputPath) {\n  const {\n    imgSelector,\n    appendInitScript,\n    scriptSrc,\n    preferNativeLazyLoad,\n    addNoScript,\n  } = lazyImagesConfig;\n  let content = rawContent;\n\n  if (outputPath && outputPath.endsWith('.html')) {\n    const dom = new JSDOM(content);\n    const images = [...dom.window.document.querySelectorAll(imgSelector)];\n\n    const params = {\n      outputPath,\n      outputDir: this.outputDir,\n      inputPath: this.inputPath,\n      inputDir: this.inputDir,\n      extraOutputSubdirectory: this.extraOutputSubdirectory,\n    };\n\n    if (addNoScript) {\n      Array.from(images).forEach((image) => {\n        const wrapper = dom.window.document.createElement('noscript');\n        wrapper.classList.add('nojs-image');\n        wrapper.innerHTML = image.outerHTML;\n        image.parentNode.insertBefore(wrapper, image);\n        wrapper.nextSibling.classList.add('js-image');\n      });\n    }\n\n    if (images.length > 0) {\n      logMessage(`found ${images.length} images in ${outputPath}`);\n      await Promise.all(images.map((image) => processImage(image, params)));\n      logMessage(`processed ${images.length} images in ${outputPath}`);\n\n      if (appendInitScript) {\n        dom.window.document.body.insertAdjacentHTML(\n          'beforeend',\n          `<script>\n            (${initScript.toString()})(\n              '${imgSelector}',\n              '${scriptSrc}',\n              ${!!preferNativeLazyLoad}\n            );\n          </script>`\n        );\n      }\n\n      content = dom.serialize();\n    }\n  }\n\n  return content;\n}\n\n// Export as 11ty plugin\nmodule.exports = {\n  initArguments: {},\n  configFunction: (eleventyConfig, pluginOptions = {}) => {\n    lazyImagesConfig = {\n      ...defaultLazyImagesConfig,\n      ...pluginOptions,\n    };\n\n    checkConfig(lazyImagesConfig, defaultLazyImagesConfig);\n    cache.load(lazyImagesConfig.cacheFile);\n    eleventyConfig.addTransform('lazyimages', transformMarkup);\n  },\n};\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# TypeScript v1 declaration files\ntypings/\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n\n# build output\n.next\n_site\n\n# other junk\n.DS_Store\nexample/**/package-lock.json\nexample/**/.lazyimages.json\n/.vscode\n"
  },
  {
    "path": ".npmignore",
    "content": "example/**/*\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"singleQuote\": true,\n  \"trailingComma\": \"es5\"\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019 Liam Fiddler\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "cache.js",
    "content": "const fs = require('fs');\n\n// A global to store the cache data in memory\nlet lazyImagesCache = {};\n\n// Loads the cache data into memory\nexports.load = (cacheFile) => {\n  if (!cacheFile) {\n    return;\n  }\n\n  try {\n    if (fs.existsSync(cacheFile)) {\n      const cachedData = fs.readFileSync(cacheFile, 'utf8');\n      lazyImagesCache = JSON.parse(cachedData);\n    }\n  } catch (e) {\n    console.error('LazyImages - cacheFile', e);\n  }\n};\n\n// Reads the cached data for an image\nexports.read = (imageSrc) => {\n  if (imageSrc in lazyImagesCache) {\n    return lazyImagesCache[imageSrc];\n  }\n\n  return undefined;\n};\n\n// Updates image data in the cache\nexports.update = (cacheFile, imageSrc, imageData) => {\n  lazyImagesCache[imageSrc] = imageData;\n\n  if (cacheFile) {\n    const cacheData = JSON.stringify(lazyImagesCache);\n\n    fs.writeFile(cacheFile, cacheData, (err) => {\n      if (err) {\n        console.error('LazyImages - cacheFile', err);\n      }\n    });\n  }\n};\n"
  },
  {
    "path": "example/basic/.eleventy.js",
    "content": "const lazyImagesPlugin = require('eleventy-plugin-lazyimages');\n\nmodule.exports = function(eleventyConfig) {\n  eleventyConfig.addPassthroughCopy('img');\n  eleventyConfig.addPassthroughCopy('nested');\n  eleventyConfig.addPlugin(lazyImagesPlugin);\n};\n"
  },
  {
    "path": "example/basic/_includes/template.njk",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>{{title}}</title>\n    <style>\n        body {\n          width: 100%;\n          max-width: 80%;\n          margin: 0 auto;\n          padding: 1rem;\n        }\n        img:not(.unstyled) {\n          display: block;\n          width: 100%;\n          max-width: 100%;\n          height: auto;\n        }\n    </style>\n  </head>\n  <body>\n    {{ content | safe }}\n  </body>\n</html>\n"
  },
  {
    "path": "example/basic/index.md",
    "content": "---\nlayout: template.njk\ntitle: Basic demo - eleventy-plugin-lazyimages\n---\n# Basic demo\n\nThis is a basic demo of\n[eleventy-plugin-lazyimages](https://github.com/liamfiddler/eleventy-plugin-lazyimages)\n\n[Nested directory](./nested)\n\n## These images are from the local filesystem:\n\n![Local Test](/img/test-01.png \"Local Test\")\n\n<img src=\"/img/test-02.jpg\" width=\"100\" alt=\"Local Test with width attr\" class=\"unstyled\">\n\n<img src=\"/img/test-02.jpg\" height=\"50\" alt=\"Local Test with height attr\" class=\"unstyled\">\n\n![Local Test](/img/test-03.jpg \"Local Test\")\n\n![Transparent Local Test](/img/transparent.png \"Transparent Local Test\")\n\n## These images are from a third-party website:\n\n![No file extension](https://placekitten.com/200/300 \"No file extension\")\n\n<img srcset=\"https://live.staticflickr.com/3915/14746807980_875aa68823_c.jpg 800w, https://live.staticflickr.com/3915/14746807980_875aa68823_b.jpg 1024w, https://live.staticflickr.com/3915/14746807980_875aa68823_h.jpg 1600w\" src=\"https://live.staticflickr.com/3915/14746807980_875aa68823_c.jpg\">\n\n![Test](https://live.staticflickr.com/7807/47291519341_1ceba19252_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/65535/48071415937_e2ac4b7e35_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/686/32013411203_85cb9cc1b1_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/8125/8619142600_e5bddd2892_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/8591/15975792640_109ea4b06f_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/65535/48071419701_9903d29f0b_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/3715/9595703973_4232326b62_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/8826/17099558470_d9711f028f_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/8654/16160691337_6d7269d5f2_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/65535/48072104611_6ff9ff703b_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/2915/14409603372_462fa7275c_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/7905/47521441372_9ec3f22a08_o.jpg \"Test\")\n\nCC0 images from [Flickr](https://www.flickr.com/search/?license=9)\n"
  },
  {
    "path": "example/basic/nested/index.md",
    "content": "---\nlayout: template.njk\ntitle: Basic demo - eleventy-plugin-lazyimages\n---\n# Nested relative path demo\n\n![Nested Relative Test](test-02.jpg \"Nested Relative Test\")\n\n![Nested Relative Test](../img/test-01.png \"Nested Relative Test\")\n"
  },
  {
    "path": "example/basic/package.json",
    "content": "{\n  \"name\": \"eleventy-plugin-lazyimages-example-basic\",\n  \"version\": \"1.0.0\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\",\n    \"start\": \"eleventy --serve\",\n    \"build\": \"eleventy\"\n  },\n  \"author\": \"@liamfiddler\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@11ty/eleventy\": \"^0.11.0\",\n    \"eleventy-plugin-lazyimages\": \"../../\"\n  }\n}\n"
  },
  {
    "path": "example/custom-selector/.eleventy.js",
    "content": "const lazyImagesPlugin = require('eleventy-plugin-lazyimages');\n\nmodule.exports = function(eleventyConfig) {\n  eleventyConfig.addPlugin(lazyImagesPlugin, {\n    imgSelector: '.lazyimages img',\n  });\n};\n"
  },
  {
    "path": "example/custom-selector/_includes/template.njk",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>{{title}}</title>\n    <style>\n        body {\n          width: 100%;\n          max-width: 26rem;\n          margin: 0 auto;\n          padding: 1rem;\n        }\n        img {\n          display: block;\n          width: 100%;\n          max-width: 100%;\n          height: auto;\n        }\n    </style>\n  </head>\n  <body>\n    {{ content | safe }}\n  </body>\n</html>\n"
  },
  {
    "path": "example/custom-selector/index.md",
    "content": "---\nlayout: template.njk\ntitle: Custom selector demo - eleventy-plugin-lazyimages\n---\n# Custom selector demo\n\nThis is a demo of \n[eleventy-plugin-lazyimages](https://github.com/liamfiddler/eleventy-plugin-lazyimages) \nthat uses a custom selector for deciding which images should be\ntransformed using the plugin.\n\n## These four images should be upgraded to lazy images:\n\n<div class=\"lazyimages\">\n\n![Test](https://live.staticflickr.com/3915/14746807980_875aa68823_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/7807/47291519341_1ceba19252_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/65535/48071415937_e2ac4b7e35_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/686/32013411203_85cb9cc1b1_o.jpg \"Test\")\n\n</div>\n\n## These four images should be treated normally:\n\n![Test](https://live.staticflickr.com/8125/8619142600_e5bddd2892_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/8591/15975792640_109ea4b06f_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/65535/48071419701_9903d29f0b_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/3715/9595703973_4232326b62_o.jpg \"Test\")\n\n## These four images should be upgraded to lazy images:\n\n<div class=\"lazyimages\">\n\n![Test](https://live.staticflickr.com/8826/17099558470_d9711f028f_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/8654/16160691337_6d7269d5f2_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/65535/48072104611_6ff9ff703b_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/2915/14409603372_462fa7275c_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/7905/47521441372_9ec3f22a08_o.jpg \"Test\")\n\n</div>\n\nCC0 images from [Flickr](https://www.flickr.com/search/?license=9)\n"
  },
  {
    "path": "example/custom-selector/package.json",
    "content": "{\n  \"name\": \"eleventy-plugin-lazyimages-example-custom-selector\",\n  \"version\": \"1.0.0\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\",\n    \"start\": \"eleventy --serve\",\n    \"build\": \"eleventy\"\n  },\n  \"author\": \"@liamfiddler\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@11ty/eleventy\": \"^0.10.0\",\n    \"eleventy-plugin-lazyimages\": \"../../\"\n  }\n}\n"
  },
  {
    "path": "example/eleventy-plugin-local-images/.eleventy.js",
    "content": "const localImages = require('eleventy-plugin-local-images');\nconst lazyImagesPlugin = require('eleventy-plugin-lazyimages');\n\nmodule.exports = function(eleventyConfig) {\n  eleventyConfig.addPlugin(lazyImagesPlugin); // eleventy-plugin-lazyimages must run before eleventy-plugin-local-images\n\n  eleventyConfig.addPlugin(localImages, {\n    distPath: '_site',\n    assetPath: '/assets/img',\n    selector: 'img',\n    attribute: 'data-src', // eleventy-plugin-lazyimages moves the path from `src` to `data-src`\n    verbose: false,\n  });\n};\n"
  },
  {
    "path": "example/eleventy-plugin-local-images/_includes/template.njk",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>{{title}}</title>\n    <style>\n        body {\n          width: 100%;\n          max-width: 26rem;\n          margin: 0 auto;\n          padding: 1rem;\n        }\n        img {\n          display: block;\n          width: 100%;\n          max-width: 100%;\n          height: auto;\n        }\n    </style>\n  </head>\n  <body>\n    {{ content | safe }}\n  </body>\n</html>\n"
  },
  {
    "path": "example/eleventy-plugin-local-images/index.md",
    "content": "---\nlayout: template.njk\ntitle: Usage with eleventy-plugin-local-images demo - eleventy-plugin-lazyimages\n---\n# Usage with eleventy-plugin-local-images\n\nThis is a demo of\n[eleventy-plugin-lazyimages](https://github.com/liamfiddler/eleventy-plugin-lazyimages)\nwith [eleventy-plugin-local-images](https://github.com/robb0wen/eleventy-plugin-local-images)\n\nThe key changes to **.eleventy.js** that make the plugins work together are:\n\n1. The eleventy-plugin-lazyimages plugin must be added to 11ty _before_ eleventy-plugin-local-images\n2. The `attribute` config value in eleventy-plugin-local-images must be set to `data-src`\n\n![Test](https://live.staticflickr.com/3915/14746807980_875aa68823_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/7807/47291519341_1ceba19252_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/65535/48071415937_e2ac4b7e35_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/686/32013411203_85cb9cc1b1_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/8125/8619142600_e5bddd2892_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/8591/15975792640_109ea4b06f_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/65535/48071419701_9903d29f0b_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/3715/9595703973_4232326b62_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/8826/17099558470_d9711f028f_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/8654/16160691337_6d7269d5f2_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/65535/48072104611_6ff9ff703b_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/2915/14409603372_462fa7275c_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/7905/47521441372_9ec3f22a08_o.jpg \"Test\")\n\nCC0 images from [Flickr](https://www.flickr.com/search/?license=9)\n"
  },
  {
    "path": "example/eleventy-plugin-local-images/package.json",
    "content": "{\n  \"name\": \"eleventy-plugin-lazyimages-example-local-images\",\n  \"version\": \"1.0.0\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\",\n    \"start\": \"eleventy --serve\",\n    \"build\": \"eleventy\"\n  },\n  \"author\": \"@liamfiddler\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@11ty/eleventy\": \"^0.10.0\",\n    \"eleventy-plugin-local-images\": \"^0.3.0\",\n    \"eleventy-plugin-lazyimages\": \"../../\"\n  }\n}\n"
  },
  {
    "path": "example/verlok-vanilla-lazyload/.eleventy.js",
    "content": "const lazyImagesPlugin = require('eleventy-plugin-lazyimages');\n\nmodule.exports = function(eleventyConfig) {\n  eleventyConfig.addPlugin(lazyImagesPlugin, {\n    scriptSrc: 'https://cdn.jsdelivr.net/npm/vanilla-lazyload@16.1.0/dist/lazyload.min.js',\n  });\n};\n"
  },
  {
    "path": "example/verlok-vanilla-lazyload/_includes/template.njk",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>{{title}}</title>\n    <style>\n        body {\n          width: 100%;\n          max-width: 80%;\n          margin: 0 auto;\n          padding: 1rem;\n        }\n        img {\n          display: block;\n          width: 100%;\n          max-width: 100%;\n          height: auto;\n        }\n    </style>\n  </head>\n  <body>\n    {{ content | safe }}\n  </body>\n</html>\n"
  },
  {
    "path": "example/verlok-vanilla-lazyload/index.md",
    "content": "---\nlayout: template.njk\ntitle: Verlok vanilla-lazyload demo - eleventy-plugin-lazyimages\n---\n# Basic demo\n\nThis is a demo of\n[eleventy-plugin-lazyimages](https://github.com/liamfiddler/eleventy-plugin-lazyimages) \nwith [Verlok's vanilla-lazyload script](https://github.com/verlok/lazyload)\n\n<img srcset=\"https://live.staticflickr.com/3915/14746807980_875aa68823_c.jpg 800w, https://live.staticflickr.com/3915/14746807980_875aa68823_b.jpg 1024w, https://live.staticflickr.com/3915/14746807980_875aa68823_h.jpg 1600w\" src=\"https://live.staticflickr.com/3915/14746807980_875aa68823_c.jpg\">\n\n![Test](https://live.staticflickr.com/7807/47291519341_1ceba19252_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/65535/48071415937_e2ac4b7e35_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/686/32013411203_85cb9cc1b1_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/8125/8619142600_e5bddd2892_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/8591/15975792640_109ea4b06f_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/65535/48071419701_9903d29f0b_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/3715/9595703973_4232326b62_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/8826/17099558470_d9711f028f_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/8654/16160691337_6d7269d5f2_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/65535/48072104611_6ff9ff703b_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/2915/14409603372_462fa7275c_o.jpg \"Test\")\n\n![Test](https://live.staticflickr.com/7905/47521441372_9ec3f22a08_o.jpg \"Test\")\n\nCC0 images from [Flickr](https://www.flickr.com/search/?license=9)\n"
  },
  {
    "path": "example/verlok-vanilla-lazyload/package.json",
    "content": "{\n  \"name\": \"eleventy-plugin-lazyimages-example-verlok\",\n  \"version\": \"1.0.0\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\",\n    \"start\": \"eleventy --serve\",\n    \"build\": \"eleventy\"\n  },\n  \"author\": \"@liamfiddler\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@11ty/eleventy\": \"^0.11.0\",\n    \"eleventy-plugin-lazyimages\": \"../../\"\n  }\n}\n"
  },
  {
    "path": "helpers.js",
    "content": "const path = require('path');\n\n// Ensures relative paths start in the project root\nexports.transformImgPath = (src, options = {}) => {\n  if (\n    src.startsWith('./') ||\n    src.startsWith('../') ||\n    (!src.startsWith('/') &&\n      !src.startsWith('http://') &&\n      !src.startsWith('https://') &&\n      !src.startsWith('data:'))\n  ) {\n    // The file path is relative to the output document\n    const outputDir = path.posix.parse(options.inputPath).dir;\n    return path.posix.normalize(outputDir + '/' + src);\n  }\n\n  // Reference files from the root project directory\n  if (src.startsWith('/') && !src.startsWith('//')) {\n    return `.${src}`;\n  }\n\n  return src;\n};\n\n// Logs a message prepended with \"LazyImages - \"\nexports.logMessage = (message) => {\n  console.log(`LazyImages - ${message}`);\n};\n\n// Init script for the plugin that gets injected into the final markup\n// (we have to use lowest common denominator JS language features\n// because we don't know what the target browser support is)\nexports.initScript = function (selector, src, preferNativeLazyLoad) {\n  var images = document.querySelectorAll(selector);\n  var numImages = images.length;\n\n  if (numImages > 0) {\n    if (preferNativeLazyLoad && 'loading' in HTMLImageElement.prototype) {\n      for (var i = 0; i < numImages; i++) {\n        var keys = ['src', 'srcset'];\n\n        for (var j = 0; j < keys.length; j++) {\n          if (images[i].hasAttribute('data-' + keys[j])) {\n            var value = images[i].getAttribute('data-' + keys[j]);\n            images[i].setAttribute(keys[j], value);\n          }\n        }\n      }\n\n      return;\n    }\n\n    var script = document.createElement('script');\n    script.async = true;\n    script.src = src;\n    document.body.appendChild(script);\n  }\n};\n\n// Warns about common issues with custom configs\nexports.checkConfig = (config, defaultConfig) => {\n  const { appendInitScript, className } = config;\n  const isDefaultScriptSrc = config.scriptSrc === defaultConfig.scriptSrc;\n\n  if (!isDefaultScriptSrc && !appendInitScript) {\n    console.warn(\n      'LazyImages - scriptSrc will be ignored because appendInitScript=false'\n    );\n  }\n\n  if (\n    isDefaultScriptSrc &&\n    appendInitScript &&\n    !className.includes('lazyload')\n  ) {\n    console.warn(\n      'LazyImages - LazySizes with the default config requires \"lazyload\" be included in className'\n    );\n  }\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"eleventy-plugin-lazyimages\",\n  \"version\": \"2.1.2\",\n  \"description\": \"An 11ty plugin that adds lazy loading to your images\",\n  \"main\": \".eleventy.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\"\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/liamfiddler/eleventy-plugin-lazyimages.git\"\n  },\n  \"keywords\": [\n    \"11ty\",\n    \"eleventy\",\n    \"eleventy-plugin\",\n    \"plugin\",\n    \"lazy\",\n    \"lazyload\",\n    \"image\"\n  ],\n  \"author\": \"@liamfiddler\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/liamfiddler/eleventy-plugin-lazyimages/issues\"\n  },\n  \"homepage\": \"https://github.com/liamfiddler/eleventy-plugin-lazyimages#readme\",\n  \"dependencies\": {\n    \"cross-fetch\": \"^3.1.4\",\n    \"jsdom\": \">=18.1.1\",\n    \"sharp\": \">=0.29.3\"\n  }\n}\n"
  },
  {
    "path": "readme.md",
    "content": "# LazyImages plugin for [11ty](https://www.11ty.io/)\n\n![Banner image](https://repository-images.githubusercontent.com/190408612/4305b000-94d2-11e9-922c-72a93cafadcf)\n\nWhat this plugin does:\n\n- 🔍 Finds IMG elements in your markup\n- ➕ Adds width and height attributes to the element\n- ✋ Defers loading the image until it is in/near the viewport\n  (lazy loading)\n- 🖼️ Displays a blurry low-res placeholder until the image has loaded\n  (<abbr title=\"Low Quality Image Placeholder\">LQIP</abbr>)\n\nThis plugin supports:\n\n- Any 11ty template format that outputs to a .html file\n- Absolute image paths\n- Relative image paths (improved in v2.1!)\n- Custom image selectors; target all images or only images in a certain part\n  of the page\n- Placeholder generation for all image formats supported by\n  [Sharp](https://sharp.pixelplumbing.com/); including JPEG, PNG, WebP, TIFF, GIF, & SVG\n- Responsive images using `srcset`; the image in the `src` attribute will be\n  used for determining the placeholder image and width/height attributes\n\n---\n\n**v2.1 just released! [View the release/upgrade notes](#upgrade-notes)**\n\n---\n\n**Like this project? Buy me a coffee via [PayPal](https://paypal.me/liamfiddler) or [ko-fi](https://ko-fi.com/liamfiddler)**\n\n---\n\n## Getting started\n\n### Install the plugin\n\nIn your project directory run:\n\n```sh\n# Using npm\nnpm install eleventy-plugin-lazyimages --save-dev\n\n# Or using yarn\nyarn add eleventy-plugin-lazyimages --dev\n```\n\nThen update your project's `.eleventy.js` to include the plugin:\n\n```js\nconst lazyImagesPlugin = require('eleventy-plugin-lazyimages');\n\nmodule.exports = function (eleventyConfig) {\n  eleventyConfig.addPlugin(lazyImagesPlugin);\n};\n```\n\n### Tweak your CSS (optional)\n\nThis plugin will automatically set the width and height attributes\nfor each image based on the source image dimensions. You might want\nto overwrite this with the following CSS:\n\n```css\nimg {\n  display: block;\n  width: 100%;\n  max-width: 100%;\n  height: auto;\n}\n```\n\nThe above CSS will ensure the image is never wider than its\ncontainer and the aspect ratio is maintained.\n\n### Configure the plugin (optional)\n\nYou can pass an object with configuration options as the second\nparameter:\n\n```js\neleventyConfig.addPlugin(lazyImagesPlugin, {\n  imgSelector: '.post-content img', // custom image selector\n  cacheFile: '', // don't cache results to a file\n});\n```\n\nA full list of available configuration options are listed below,\nand some common questions are covered at the end of this file.\n\n## Configuration options\n\n| Key                      | Type     | Description                                                                                                                                                                   |\n| ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `maxPlaceholderWidth`    | Integer  | The maximum width in pixels of the generated placeholder image. Recommended values are between 15 and 40.<br>Default: `25`                                                    |\n| `maxPlaceholderHeight`   | Integer  | The maximum height in pixels of the generated placeholder image. Recommended values are between 15 and 40.<br>Default: `25`                                                   |\n| `imgSelector`            | String   | The DOM selector used to find IMG elements in the markup.<br>Default: `img`                                                                                                   |\n| `transformImgPath`       | Function | A function that takes the IMG `src` attribute and returns a string representing the actual file path to your image.                                                           |\n| `cacheFile`              | String   | Cache image metadata and placeholder images to this filename. Greatly speeds up subsequent builds. Pass an empty string to turn off the cache.<br>Default: `.lazyimages.json` |\n| `appendInitScript`       | Boolean  | Appends code to initialise lazy loading of images to the generated markup. Set this to `false` if you include your own lazy load script.<br>Default: `true`                   |\n| `scriptSrc`              | String   | The URI for the lazy load script that is injected into the markup via `appendInitScript`.<br>Default: `https://cdn.jsdelivr.net/npm/lazysizes@5/lazysizes.min.js`             |\n| `preferNativeLazyLoad`   | Boolean  | Use the native browser `loading=\"lazy\"` instead of the lazy load script (if available).<br>Default: `false`                                                                   |\n| `setWidthAndHeightAttrs` | Boolean  | Set the `width` and `height` attributes on `img` elements to the actual size of the image file.<br>Default: `true`                                                            |\n| `className`              | String[] | The class names added to found IMG elements. You usually don't need to change this unless you're using a different `scriptSrc`.<br>Default: `['lazyload']`                    |\n\n## Example projects\n\nExample projects using the plugin can be found in the\n[`/example`](./example) directory.\n\n- [Basic](./example/basic) - using default configuration\n- [Custom selector](./example/custom-selector) - using a custom image selector to only target image in certain DIVs\n- [Usage with eleventy-plugin-local-images](./example/eleventy-plugin-local-images) - using this plugin with [eleventy-plugin-local-images](https://github.com/robb0wen/eleventy-plugin-local-images)\n- [Usage with vanilla-lazyload](./example/verlok-vanilla-lazyload) - using this plugin with [vanilla-lazyload](https://www.npmjs.com/package/vanilla-lazyload)\n\n## Built with\n\n- [JSDOM](https://github.com/jsdom/jsdom) - To find and modify image\n  elements in 11ty's generated markup\n- [Sharp](https://sharp.pixelplumbing.com/) - To read image\n  metadata and generate low-res placeholders\n- [LazySizes](https://github.com/aFarkas/lazysizes) - Handles lazy loading\n\n## Contributing\n\nThis project welcomes suggestions and Pull Requests!\n\n## Authors\n\n- **Liam Fiddler** - _Initial work / maintainer_ - [@liamfiddler](https://github.com/liamfiddler)\n\nSee also the list of\n[contributors](https://github.com/liamfiddler/eleventy-plugin-lazyimages/contributors)\nwho participated in this project.\n\n## License\n\nThis project is licensed under the MIT License -\nsee the [LICENSE](LICENSE) file for details\n\n## Acknowledgments\n\n- The wonderfully supportive team at\n  [Mentally Friendly](https://mentallyfriendly.com)\n- Everyone who has contributed to the\n  [11ty](https://www.11ty.io/) project, without whom\n  this plugin wouldn't run\n- [José M. Pérez's blog post about progressive image loading](https://jmperezperez.com/medium-image-progressive-loading-placeholder/)\n  which served as the inspiration for this plugin\n- [Addy Osmani's blog post about lazy loading](https://addyosmani.com/blog/lazy-loading/)\n  which served as the inspiration for the init script\n\n## Common questions\n\n### Can I host the lazy load script locally?\n\nYes! This plugin defaults to\n[LazySizes from JSDelivr](https://cdn.jsdelivr.net/npm/lazysizes@5/lazysizes.min.js)\nbut you can specify a relative path via the `scriptSrc` configuration option.\n\n### Does my local image path have to match the output path?\n\n**(a.k.a Why do I have \"Input file is missing\" messages in my terminal?)**\n\nBy default this plugin will look for the file referenced in a `src` attribute like\n`<img src=\"/images/dog.jpg\" />` at `<project root>/images/dog.jpg` or\n`<project root>/src/images/dog.jpg`.\n\nWhereas a file referenced like\n`<img src=\"./images/dog.jpg\" />` or `<img src=\"images/dog.jpg\" />` is expected to\nbe found at `<input file directory>/images/dog.jpg`.\n\nIf you prefer to store your images elsewhere the `transformImgPath` config\noption allows you to specify a function that points the plugin to your\ninternal image path.\n\nFor example, if your file structure stores `<img src=\"/images/dog.jpg\" />`\nat `<project root>/assets/dog.jpg` you could set `transformImgPath` like:\n\n```js\n// .eleventy.js\neleventyConfig.addPlugin(lazyImagesPlugin, {\n  transformImgPath: (imgPath) => imgPath.replace('/images/', './assets/'),\n});\n```\n\nThe `transformImgPath` configuration option takes a function that receives two\nparameters; `src`, and `options`.\n\n`src` is a string containing the value of the `img` elements `src` attribute.\n\n`options` is an object containing the `outputPath` of the file being processed,\nas well as the `outputDir`, `inputPath`, `inputDir`, and\n`extraOutputSubdirectory` values from eleventy config.\n\n### Can I use a different lazy load script?\n\nYes! By default this plugin uses [LazySizes](https://github.com/aFarkas/lazysizes)\nto handle lazy loading, but any lazy load script that reads from the `data-src`\nattribute is supported via the `scriptSrc` configuration option.\n\nWe've included an [example project in this repo](./example/verlok-vanilla-lazyload)\ndemonstrating this plugin using\n[vanilla-lazyload](https://www.npmjs.com/package/vanilla-lazyload).\n\nNote: if you need to modify the custom script's parameters the recommended approach\nis to set `appendInitScript: false` in this plugin's config. This tells the plugin\nto skip adding the script loader code to the page. It ignores any value set for\nscriptSrc and allows you to use your own method for including the custom script.\nThe plugin will still set the `data-src` + `width` + `height` attributes on IMG\ntags and generate the low quality image placeholders, it just doesn't manage the\nactual lazy loading.\n\n### Can I use this plugin with a plugin that moves/renames image files?\n\nYes! The key to solving this problem is the order in which the plugins are\ndefined in `.eleventy.js`. It is important this plugin runs after the plugin\nthat moves/renames files otherwise this plugin may still be referencing the\noriginal filepath in the markup, not the one generated by the other plugin.\n\nWe've included an\n[example project in this repo](./example/eleventy-plugin-local-images)\ndemonstrating this plugin with\n[eleventy-plugin-local-images](https://github.com/robb0wen/eleventy-plugin-local-images).\n\n## Upgrade notes\n\n### v2.1.0\n\nThis release improves support for relative file paths in `src` attributes.\n\n`transformImgPath` now receives an optional second parameter containing the `outputPath`\nof the file being processed, as well as the `outputDir`, `inputPath`, `inputDir`, and\n`extraOutputSubdirectory` values from eleventy config.\n\nThis release also adds the `setWidthAndHeightAttrs` config option which allows you to turn\noff the setting of `width` and `height` attributes being added to `img` elements.\n\n### v2.0.0\n\nThe underlying tool used to generate placeholders has switched from JIMP to Sharp.\nThis allows the plugin to handle a greater variety of image formats, while also increasing in speed.\n\nThe API remains largely the same so most sites should not need to adjust their config.\n\n- The default values for `maxPlaceholderWidth` and `maxPlaceholderHeight` have been increased from 12 to 25 - this increases the quality of the LQIP without a significant change in filesize\n- `placeholderQuality` has been removed - at the size of the LQIP it didn't make much of a difference to filesize or image quality\n- The default value for `preferNativeLazyLoad` is now `false` - most users install this plugin to generate LQIP and the previous default meant the LQIP weren't visible in modern browsers\n\n---\n\n**Like this project? Buy me a coffee via [PayPal](https://paypal.me/liamfiddler) or [ko-fi](https://ko-fi.com/liamfiddler)**\n\n---\n"
  }
]