{"_id":"@ampproject/toolbox-optimizer","_rev":"109-4d0146ffc9ce2b20ec88cf53a8a51d3b","name":"@ampproject/toolbox-optimizer","description":"Server-side rendering for AMPs.","dist-tags":{"beta":"1.1.0-beta.0","alpha":"2.7.0-alpha.1","canary":"3.0.0-canary.2","latest":"2.10.1"},"versions":{"1.0.0-beta.0":{"name":"@ampproject/toolbox-optimizer","version":"1.0.0-beta.0","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@1.0.0-beta.0","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"prateekbh","email":"prateek89born@gmail.com"},{"name":"choumx","email":"willchou@google.com"},{"name":"sepandparhami","email":"sparhami@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"84e1e4f7b57458fce63a739a30aaad59abb04429","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-1.0.0-beta.0.tgz","fileCount":24,"integrity":"sha512-VN//8GPFPLy0R/xnBToQQ2Xn30ivffGhqpLmsP05umyhwCOFSTjj/VSZXz7kqBJT2Y6d7U2hIRoSQQaFMiqJHQ==","signatures":[{"sig":"MEUCIQDwurDMQ9Fq29ki7gTuw3rGKxO9VkjPk9DuY6fBIrMcqQIgCrRjw7oiR8afC0bsh4m21pL0ee+lOC6G3Rbk7PG1loQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":82995,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJc/p+LCRA9TVsSAnZWagAAf5sQAIhInNdrY0NPHWWVoB9X\nZn+bYqWhE5zSs0//ORGceESnBrqbjsqiRlj2b1nn3A/AgapGmtR3aKv1ba1o\nN0D2AAW5CgeuGGQ5TmpINwllolnizNMCxwe3TXBA7blcGgwcmPZudWQz4VZ5\nimTZX8GlV0qcLyrOC0QpymCk1ne6rvkyaKKUI860EU7xrbC3Lv+yzPr2EwJS\nkPuPPlJQ2OmY3cSjlw+lOnS/FC8oPJQmcHvYw30lJeb8fd238sbaKc+Y9/ST\nhfEmOmHZt4PN+GqW9PZx/8bwg7gBB8Pgo0dKf79NlS+zkEXanVEAgtHFs31I\ndFp+Rhc5h7XIN/K5zuCYLFCRfIuhFyTTKS0MAZBmOrEAnemvvoKCGNVGAYHy\n7jGxJtbQQ/9aLJtXdVYIb/cD36GU9N8sGtfeX8FjITA9wcbLYmw//P0p1gYE\nE94ACXW+eVpxdPdiGSTN8CQP3iVashyvy3m8Gw94zxs/UjJ7tyc1kZMpIr7x\nLNv/ms7nO+vTvePzTOHJVWAyI44Y+CJc+HMnT9E/bRIGjJuBCJ+SQhkwz82s\nsZ7fZAUWFgw39B+f9RFXWmzCNuhUpxGxE22r98UiLdaUUOY6E0WTBzBknesG\nhnyPKwQPLibI+o4j44C/0NtiWz7QvkAjIGk08kJCIpjpPOU49myb9jJHhWJA\nTzjY\r\n=Rx19\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"68bce56d4878ace45b6573824a56656667db5c85","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git"},"_npmVersion":"6.9.0","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"10.16.0","dependencies":{"css":"2.2.4","parse5":"5.1.0","@ampproject/toolbox-core":"^1.0.0-beta.0","parse5-htmlparser2-tree-adapter":"5.1.0","@ampproject/toolbox-runtime-version":"^1.0.0-beta.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_1.0.0-beta.0_1560190859376_0.1268771768760193","host":"s3://npm-registry-packages"}},"1.0.0-beta.2":{"name":"@ampproject/toolbox-optimizer","version":"1.0.0-beta.2","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@1.0.0-beta.2","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"prateekbh","email":"prateek89born@gmail.com"},{"name":"sepandparhami","email":"sparhami@google.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"7fe92e10f6516170f6828b565d931c390d6aee1d","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-1.0.0-beta.2.tgz","fileCount":25,"integrity":"sha512-IMK4aw1b9WpxN6AcWATdKpiUdGUIbcbo3O8s7DI+pBusXXi+KwEDjVwykianRrdkiX9EKK5MrfN2QOp0W2piOg==","signatures":[{"sig":"MEUCIQCiv1YOmZBhdO8NqwlyEqrN7/u0HYNrh9sOvSTbcPRVxwIgeBQ+4OIv3EncwcnbGP3ZKn0pMIlO/S8VqTCcaVaFf5o=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":94343,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdC1SmCRA9TVsSAnZWagAAeBsQAIxhrqt/DOz0pdALiitt\nDKca4DKLB+GACF3M9UmaLnvqSgs7U+/WrNQCqfe7rCyAxDyKd1WjpReUx5JT\nuNHpuMUSrfUE2n1/8+OXx3bMuqSRkRKATGuzzVEWenQnpFyrKRsWOcL6W5r4\nvfoex+cMl3gojmOplLomrV2lzAuizvvrhaA4QMHDIMDJaeML96z8Ld5K4u9/\nvGH/S/ZLKtwjRg41Ld8XY9fgEiIflud+cIoh3s2kqWKiNu9AfafnfSdGSzQ8\nRJjgsryLkDgHpGumfIzm11585r95X6yfkAZVsHDGW7OxYWL7MGXVuWBVUXHk\nuq4Ei8GHyu9EhiNx2CMj2Zm051ZNqmwg/swPAj4TSlx22pprICg8TMlugNsG\nB+vN+W0rBRjc7C3geivFwJPae+Z1jUWoULGtK1JlNSxZO4ZekPK4cMzOw5Sz\nCLVQQXq4esd2IRZWknJMlOZJRwc5VvrBCjNFcLncnKmCpk+sHaZuujNel4tT\n/YtPhyZ0Y0kW3GlQURb7irzakcOdZceuWjSrs/eUCsFpr9janKBUegFRd53Q\npQDkP040O8pKisHB2T1ljBmzEL2Lt7SG/Vh/uxAfEolJ5bWFuL9AXDkbaeK+\nag//XWjBUbnPNRcuYGDAqwAgh+j9W+lxYA0LppKRfMvyKuIJdcYs89LC36zs\nkNVe\r\n=FtRi\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer \n\n[![npm version](https://badge.fury.io/js/@ampproject/toolbox-optimizer.svg)](https://badge.fury.io/js/@ampproject/toolbox-optimizer)\n\nAMP Optimizer is a tool to server-side enhance the rendering performance of AMP pages. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n* Server-side render AMP layouts.\n* Remove the AMP boilerplate (when possible).\n* Inline critical CSS.\n* Speed-up AMP framework and custom font loading.\n* Move CSS keyframe animations to the bottom of the page.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/).\n\n**Important: AMP Optimizer will produce valid AMP, if the input is valid AMP.**\n\n* [Usage](#usage)\n* [Best Practices](#best-practices)\n  + [Regenerate pages at least once a week](#regenerate-pages-at-least-once-a-week)\n  + [Debugging](#debugging)\n  + [Transform AMP pages at build time if possible](#transform-amp-pages-at-build-time-if-possible)\n  + [Cache transformed AMPs at runtime](#cache-transformed-amps-at-runtime)\n* [Experimental Features](#experimental-features)\n  + [Paired AMP](#paired-amp)\n  + [Versioned AMP Runtime](#versioned-amp-runtime)\n  + [Blurry image placeholders](#blurry-image-placeholders)\n  + [Self-hosted AMP components](#self-hosted-amp-components)\n\n## Usage\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer@beta\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>\n`\n\nampOptimizer.transformHtml(originalHtml).then(optimizedHtml => {\n  console.log(optimizedHtml);\n});\n\n```\n\nYou can find a sample implementation [here](demo/simple/). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](../optimizer-express).\n\nThere's also a [command line version](../cli/README.md) available:\n\n```shell\n$ npx @ampproject/toolbox-cli myFile.html\n```\n\n## Best Practices\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a week. The good news is, out-of-sync CSS will not break your page, as AMP will check the version of the inlined CSS at runtime and will automatically update it to the latest version.\n\n### Debugging\n\nEnable `verbose` mode to find out why the AMP boilerplate is not being removed. You can\nenable verbose mode either when creating a new optimizer instance:\n\n```\n// globally\nconst optimizer = AmpOptimzer.create({\n  verbose: true\n});\n```\n\n... or for individual pages:\n\n```\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true\n})\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP \n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length)\n    .replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example: \n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.  \n\nYou can use [@ampproject/toolbox-runtime-version](../@ampproject/toolbox-runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n* `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n* `imageBasePath`: specifies a base path used to resolve an image during build.\n* `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n* `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage: \n\n```\nconst optimizer = AmpOptimizer.create({\n  // blurry image placeholders are currently not considered valid AMP\n  // hence it's recommended to setup paired AMP mode when enabling this feature.\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp'\n});\n\nconsole.log(optimizedHtml);\n```\n","gitHead":"fa3197ef68f96c516511d8cd97f30dd06a342553","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git"},"_npmVersion":"lerna/3.15.0/node@v10.16.0+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"10.16.0","dependencies":{"css":"2.2.4","parse5":"5.1.0","@ampproject/toolbox-core":"^1.0.0-beta.2","parse5-htmlparser2-tree-adapter":"5.1.0","@ampproject/toolbox-runtime-version":"^1.0.0-beta.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_1.0.0-beta.2_1561023654004_0.7149146291876667","host":"s3://npm-registry-packages"}},"1.0.0-beta.3":{"name":"@ampproject/toolbox-optimizer","version":"1.0.0-beta.3","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@1.0.0-beta.3","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"prateekbh","email":"prateek89born@gmail.com"},{"name":"sepandparhami","email":"sparhami@google.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"ddc23cb4383a988e886f299fa35d1138aab98903","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-1.0.0-beta.3.tgz","fileCount":25,"integrity":"sha512-tn4JZA0iWH45woFUxia0bYbj1g0f6s/yavhuyREk2W14OSKINxjkGBUi66dZrAPTn9E3SHVDuWzPd5htTTTzNA==","signatures":[{"sig":"MEUCIQCpQCGb74j165kUq4idrOtR3gsAe1dv02I0uhyuDb77xgIgaeuSdvvy12++Adx7jOPrLXT54NcIe2l/UFG/TKt01VA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":94343,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdC1bfCRA9TVsSAnZWagAAMHcP/17yHUeYvwCleLsoV7zr\nXE+pHDMYufnRsjzQy9LIS/YVIdTyjZ7f4XVaQc+hI1Q05j6ghp9rdTHS5DFr\nvqhgpSZGXCZROSoM4oZA10SFGnFxn+Ux/sd8gbQD3+mf2wBK5lORY4sRlWez\nQkFH371cq4rVjNoMiBFfE1hMsK8ABgSiZOWZjjNNLGdQ2vx8/j3KnCxlDLIc\nKXS3piOr9ZYJ2Tk4I2T2pgdv5SfCZOSSb4AegpYkbwomBlo3vijs3d78/Im1\n4BEH5rf7VawQrDM2Zu+bjp0qdVz8dcWg8Yl+LuIK4zgBf+OLmdonuvsiB6Mc\nVunnk6BidcQfLT8EDq6Dsldbra7ZVsJKNtXfZ6moTsRQaK9zQMKLa/nMKDq2\nPpPZPzhPeL2+0O8svhAG43vvWcyk1kHVGymx8KGHYm+gPjqneFverkRfL6jq\nVRu4JBsFMzQL87xc/GQSyu151hIj/D5qL4n8jCfKKSW/f9Q5fbSEjFO+7MVh\nKrEdmAKbhjudkp0ZDuRfZaDRpteZYpY7rteN2yXNu7tuOzWZgzk8JQOCl4vZ\nz0H2HEPNHA7/+K7BK6BKZjoddeRwmnBmWuhHKzf712KAcKbPsQedT4XelmtX\nfvgDyyroaHl6HEfmT94snbTrqsovpzh7GP72GFxsex5UONfaYX05HxnFuUkp\noWyV\r\n=nnKe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer \n\n[![npm version](https://badge.fury.io/js/@ampproject/toolbox-optimizer.svg)](https://badge.fury.io/js/@ampproject/toolbox-optimizer)\n\nAMP Optimizer is a tool to server-side enhance the rendering performance of AMP pages. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n* Server-side render AMP layouts.\n* Remove the AMP boilerplate (when possible).\n* Inline critical CSS.\n* Speed-up AMP framework and custom font loading.\n* Move CSS keyframe animations to the bottom of the page.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/).\n\n**Important: AMP Optimizer will produce valid AMP, if the input is valid AMP.**\n\n* [Usage](#usage)\n* [Best Practices](#best-practices)\n  + [Regenerate pages at least once a week](#regenerate-pages-at-least-once-a-week)\n  + [Debugging](#debugging)\n  + [Transform AMP pages at build time if possible](#transform-amp-pages-at-build-time-if-possible)\n  + [Cache transformed AMPs at runtime](#cache-transformed-amps-at-runtime)\n* [Experimental Features](#experimental-features)\n  + [Paired AMP](#paired-amp)\n  + [Versioned AMP Runtime](#versioned-amp-runtime)\n  + [Blurry image placeholders](#blurry-image-placeholders)\n  + [Self-hosted AMP components](#self-hosted-amp-components)\n\n## Usage\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer@beta\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>\n`\n\nampOptimizer.transformHtml(originalHtml).then(optimizedHtml => {\n  console.log(optimizedHtml);\n});\n\n```\n\nYou can find a sample implementation [here](demo/simple/). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](../optimizer-express).\n\nThere's also a [command line version](../cli/README.md) available:\n\n```shell\n$ npx @ampproject/toolbox-cli myFile.html\n```\n\n## Best Practices\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a week. The good news is, out-of-sync CSS will not break your page, as AMP will check the version of the inlined CSS at runtime and will automatically update it to the latest version.\n\n### Debugging\n\nEnable `verbose` mode to find out why the AMP boilerplate is not being removed. You can\nenable verbose mode either when creating a new optimizer instance:\n\n```\n// globally\nconst optimizer = AmpOptimzer.create({\n  verbose: true\n});\n```\n\n... or for individual pages:\n\n```\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true\n})\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP \n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length)\n    .replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example: \n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.  \n\nYou can use [@ampproject/toolbox-runtime-version](../@ampproject/toolbox-runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n* `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n* `imageBasePath`: specifies a base path used to resolve an image during build.\n* `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n* `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage: \n\n```\nconst optimizer = AmpOptimizer.create({\n  // blurry image placeholders are currently not considered valid AMP\n  // hence it's recommended to setup paired AMP mode when enabling this feature.\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp'\n});\n\nconsole.log(optimizedHtml);\n```\n","gitHead":"182e821deefc39940858ab46912e55f1f3da59f5","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git"},"_npmVersion":"lerna/3.15.0/node@v10.16.0+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"10.16.0","dependencies":{"css":"2.2.4","parse5":"5.1.0","@ampproject/toolbox-core":"^1.0.0-beta.3","parse5-htmlparser2-tree-adapter":"5.1.0","@ampproject/toolbox-runtime-version":"^1.0.0-beta.3"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_1.0.0-beta.3_1561024222520_0.8067050354583343","host":"s3://npm-registry-packages"}},"1.0.0-beta.4":{"name":"@ampproject/toolbox-optimizer","version":"1.0.0-beta.4","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@1.0.0-beta.4","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"prateekbh","email":"prateek89born@gmail.com"},{"name":"sepandparhami","email":"sparhami@google.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"11b7e066a9c867c1b705b31723478451e4860d41","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-1.0.0-beta.4.tgz","fileCount":24,"integrity":"sha512-piwWUuhfq+oX0/7XmebUgwatoqf+SoYmRcvWYaRNXnaTBO75CofHOsKGIA90Msg9Nl4HQ5+e/9o9lb0ZqCFHxA==","signatures":[{"sig":"MEYCIQCjDLW+c2dgq5v7iqEyk0rA4wlEA5+G3T7aAtZxY2ngoQIhAJ8WTPiOyZOqRQ+ZC2XoMASiU0DKx9UyRnBWP0Uhn6Ne","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":83002,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdJ0nTCRA9TVsSAnZWagAA+SIQAJFrVSHxMds6huWjgcbZ\nfAtsakXiQDWOu5bZjhgLrA8IoJdqEIUbRzqzQvPD5lyOkIiy50Iedft6X6E2\n5kvUes1EBllROnEMsn+We66H1eCu8MY614BE5kkWemQh2GTiOIznBFW/2zIV\nzpmOxI5+GhC7mfFq7uAy1ajyGCJFJtwmBv742cG5kVFIaYpY7xaqOV8NHqj1\nXXnuOWfEat0O04u//ksDWoHcbsyz5g2jAu9mM70oH9J3LqQp8b0P2BAv8TTG\nQKO3+Owus0TurARj5Nf68JAe8kjL4orVHn2dPJHRScfXve3nlIE9+oBwHJnW\n1OAGeDKJvISpjGXVsv63HsG+XNadHtuM7NBlw9Jp8C6bRbltfSxxZLVp+6CC\nx7yBjo9WC1wKi8jdckC12d8uVS+8u8QD5jh0hI1OjcQVt1KPCfDtcolGmCZX\n6g/jsWb3O9uf9+e4/LPaRSNEzMSsXx93QwykCAbVZlIgh7qQoXOzav48s5wG\nbU6B7AhhAU2FHBMpsDOfCMEoqCHBv5xneClz1VwOWFfwZTuRFpBD/b5Nw691\n1pRIoKXlWN6lIRakxNF6O6YT6JPA2LS+PmtPKdy+5xW8zvjH9N/Ap8+7L98/\n7+YZWxbhXbcApvWVk2WI1BsAG8e/OR3c1ZaiR2nY2qFRwxwxLQijs4wqEGm4\n+oPa\r\n=/F1h\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer \n\n[![npm version](https://badge.fury.io/js/@ampproject/toolbox-optimizer.svg)](https://badge.fury.io/js/@ampproject/toolbox-optimizer)\n\nAMP Optimizer is a tool to server-side enhance the rendering performance of AMP pages. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n* Server-side render AMP layouts.\n* Remove the AMP boilerplate (when possible).\n* Inline critical CSS.\n* Speed-up AMP framework and custom font loading.\n* Move CSS keyframe animations to the bottom of the page.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/).\n\n**Important: AMP Optimizer will produce valid AMP, if the input is valid AMP.**\n\n* [Usage](#usage)\n* [Best Practices](#best-practices)\n  + [Regenerate pages at least once a week](#regenerate-pages-at-least-once-a-week)\n  + [Debugging](#debugging)\n  + [Transform AMP pages at build time if possible](#transform-amp-pages-at-build-time-if-possible)\n  + [Cache transformed AMPs at runtime](#cache-transformed-amps-at-runtime)\n* [Experimental Features](#experimental-features)\n  + [Paired AMP](#paired-amp)\n  + [Versioned AMP Runtime](#versioned-amp-runtime)\n  + [Blurry image placeholders](#blurry-image-placeholders)\n  + [Self-hosted AMP components](#self-hosted-amp-components)\n\n## Usage\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer@canary\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>\n`\n\nampOptimizer.transformHtml(originalHtml).then(optimizedHtml => {\n  console.log(optimizedHtml);\n});\n\n```\n\nYou can find a sample implementation [here](demo/simple/). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](../optimizer-express).\n\nThere's also a [command line version](../cli/README.md) available:\n\n```shell\n$ npx @ampproject/toolbox-cli myFile.html\n```\n\n## Best Practices\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a week. The good news is, out-of-sync CSS will not break your page, as AMP will check the version of the inlined CSS at runtime and will automatically update it to the latest version.\n\n### Debugging\n\nEnable `verbose` mode to find out why the AMP boilerplate is not being removed. You can\nenable verbose mode either when creating a new optimizer instance:\n\n```\n// globally\nconst optimizer = AmpOptimzer.create({\n  verbose: true\n});\n```\n\n... or for individual pages:\n\n```\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true\n})\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP \n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length)\n    .replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example: \n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.  \n\nYou can use [@ampproject/toolbox-runtime-version](../@ampproject/toolbox-runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n* `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n* `imageBasePath`: specifies a base path used to resolve an image during build.\n* `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n* `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage: \n\n```\nconst optimizer = AmpOptimizer.create({\n  // blurry image placeholders are currently not considered valid AMP\n  // hence it's recommended to setup paired AMP mode when enabling this feature.\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp'\n});\n\nconsole.log(optimizedHtml);\n```\n","gitHead":"cb496f081c871a25dc73113ae1e1fccfb59b2732","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git"},"_npmVersion":"6.9.0","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"10.16.0","dependencies":{"css":"2.2.4","parse5":"5.1.0","@ampproject/toolbox-core":"^1.0.0-beta.4","parse5-htmlparser2-tree-adapter":"5.1.0","@ampproject/toolbox-runtime-version":"^1.0.0-beta.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_1.0.0-beta.4_1562855890279_0.2660301755723795","host":"s3://npm-registry-packages"}},"1.0.0-beta.5":{"name":"@ampproject/toolbox-optimizer","version":"1.0.0-beta.5","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@1.0.0-beta.5","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"prateekbh","email":"prateek89born@gmail.com"},{"name":"sepandparhami","email":"sparhami@google.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"083d38ab1854b0b1ae7ca1df53de32c297801b1a","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-1.0.0-beta.5.tgz","fileCount":25,"integrity":"sha512-m63EXexoCFT0Bcd8Cb9Bn4kWZ1jOjXEfl0FdEG48Ru+yk/NFG1B7QcqlzDfg6RGuSUAmo3JpQiKO6rtlG16aeQ==","signatures":[{"sig":"MEQCIFJAB/PTNkGiv1J2oIVzF+8KUiUlONZcfQP9FfedcHBNAiBfqjHjSyVt29Q+lxFkrbqgOkktvjTy9l5+RLuaE+VpAw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":94345,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdLJ1mCRA9TVsSAnZWagAA+P0P/jRCWE9/3EOMA8bIYJiw\nb8zSWqo+E0s4NqaB3/7f70mavIE5mXAMfbW7sGLouC0Dt5VN1Dv8XI+PD74T\nqvDFnX3qa79aPVzpvWUn0UsXG053rd8Gj3yz201RHkFz9EnHLuJqsS1naOde\ngxcKYB7fb2m/yf3ZCTVgpfJbdU7qTlYvhS4mLtZC2f/Uh6K+M/G/5NjIT3kL\nyRBGik/VYsd5ihHDsyLPjFbPmd4GVQonmid6PSewGYtIqXaOGJWWABNiJpSm\n62rQugzUQ3xNFma6Vb6QaSuTh4HwlQMpI5/FInrhnUNjc/yJe97QI4kaTayA\nMfBD3+Q40zQ7ZCXZOJ9ULr10FIY4iNA9o2Vk8Kz3B+pCeO9ZbHzCHoO2MSwF\nxXVBbiRZhcesZnyWJH+0t0S7e3XZrA9E/u0VhmUO98n4UvIgdTpeXSgKTASu\nBfxgWcuarZ/ch9pnBLLkUgPuL4/UN5K0PMVa0AVOQSaQ+FIK0ESxvdmf3c/d\nixQbHPBtqCPHzs9C9h7q81NnL21SrJAq2juDPWSnn38kYnfq+hcizrMJkycf\nQbPbvQY8ouaOWCHYPXTTlGfezQlssz+Vg63O6MYLR0IvJEHjKSBm4/arzfsB\nuTu11XJTdJ/+Gc6rikgckX1kmFAjANWISwMJ9JXmutX2lg3GlAYKZA+TePIu\nFEbr\r\n=y2JC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer \n\n[![npm version](https://badge.fury.io/js/@ampproject/toolbox-optimizer.svg)](https://badge.fury.io/js/@ampproject/toolbox-optimizer)\n\nAMP Optimizer is a tool to server-side enhance the rendering performance of AMP pages. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n* Server-side render AMP layouts.\n* Remove the AMP boilerplate (when possible).\n* Inline critical CSS.\n* Speed-up AMP framework and custom font loading.\n* Move CSS keyframe animations to the bottom of the page.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/).\n\n**Important: AMP Optimizer will produce valid AMP, if the input is valid AMP.**\n\n* [Usage](#usage)\n* [Best Practices](#best-practices)\n  + [Regenerate pages at least once a week](#regenerate-pages-at-least-once-a-week)\n  + [Debugging](#debugging)\n  + [Transform AMP pages at build time if possible](#transform-amp-pages-at-build-time-if-possible)\n  + [Cache transformed AMPs at runtime](#cache-transformed-amps-at-runtime)\n* [Experimental Features](#experimental-features)\n  + [Paired AMP](#paired-amp)\n  + [Versioned AMP Runtime](#versioned-amp-runtime)\n  + [Blurry image placeholders](#blurry-image-placeholders)\n  + [Self-hosted AMP components](#self-hosted-amp-components)\n\n## Usage\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer@canary\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>\n`\n\nampOptimizer.transformHtml(originalHtml).then(optimizedHtml => {\n  console.log(optimizedHtml);\n});\n\n```\n\nYou can find a sample implementation [here](demo/simple/). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](../optimizer-express).\n\nThere's also a [command line version](../cli/README.md) available:\n\n```shell\n$ npx @ampproject/toolbox-cli myFile.html\n```\n\n## Best Practices\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a week. The good news is, out-of-sync CSS will not break your page, as AMP will check the version of the inlined CSS at runtime and will automatically update it to the latest version.\n\n### Debugging\n\nEnable `verbose` mode to find out why the AMP boilerplate is not being removed. You can\nenable verbose mode either when creating a new optimizer instance:\n\n```\n// globally\nconst optimizer = AmpOptimzer.create({\n  verbose: true\n});\n```\n\n... or for individual pages:\n\n```\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true\n})\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP \n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length)\n    .replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example: \n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.  \n\nYou can use [@ampproject/toolbox-runtime-version](../@ampproject/toolbox-runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n* `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n* `imageBasePath`: specifies a base path used to resolve an image during build.\n* `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n* `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage: \n\n```\nconst optimizer = AmpOptimizer.create({\n  // blurry image placeholders are currently not considered valid AMP\n  // hence it's recommended to setup paired AMP mode when enabling this feature.\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp'\n});\n\nconsole.log(optimizedHtml);\n```\n","gitHead":"630677e390bf5f63f82f4d2c6a0c53a20c33bc2e","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git"},"_npmVersion":"lerna/3.15.0/node@v10.16.0+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"10.16.0","dependencies":{"css":"2.2.4","parse5":"5.1.0","@ampproject/toolbox-core":"^1.0.0-beta.5","parse5-htmlparser2-tree-adapter":"5.1.0","@ampproject/toolbox-runtime-version":"^1.0.0-beta.5"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_1.0.0-beta.5_1563204965769_0.5857800680021485","host":"s3://npm-registry-packages"}},"1.0.0-beta.6":{"name":"@ampproject/toolbox-optimizer","version":"1.0.0-beta.6","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@1.0.0-beta.6","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"prateekbh","email":"prateek89born@gmail.com"},{"name":"sepandparhami","email":"sparhami@google.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"ae52307ed11ed67f735c539ed46c54109ceab86e","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-1.0.0-beta.6.tgz","fileCount":24,"integrity":"sha512-FrnRawTuWWbH1Q/HApCbnz9eUMC87+3wgWeriBDfZ1Dl9PbHBahpVPHu4kcmCW9AyNRlzG2637LVBFN3ng7ThQ==","signatures":[{"sig":"MEUCIQD5CkSLKoJDscI5YGQ104xxDPdMVyvmgwYGYuJ61qvBuQIgQIZeGbxlOZnzTBdwGRlUq22tTMz6/fOGIrxf34rQ1sg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":82995,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdNwVdCRA9TVsSAnZWagAAi0UP/0l7hrOSFUw5Ro0D2Ym7\nw+6OMrAR7tlQl0Tpn9Ax7ga9FvoZ7oWP9nN1l4tom3VWntr/bGlCQ6bsO15q\n4kXbsubo5snB7AgwgaONu8eX3Mv7Xluwn5ehkSRbCmvT+iw+CAGwPkdutlal\njmmOs9neR8x8X+M1DY4Y96H7d719QDjUqiV6MhIfcig2V8cWwRWQdSugKxK8\nwvJpWj0L9Yv9uN2GThqPfvIjfC0D3QQ90fUPcu4wTiELFeorMVPvhikwqiuj\nlXZLAXLWAfCrqelrnAr6EtJF3OtiLXYeQJ1nFKDu0mwA9zOS7H9VWKCJTfcG\nX9lSUf2tWi4OL2elJqQjD5jUTrEUQl165mhJIfSJswtUQvwTbUQWAsCMe+fO\nARATP78Ivgf1Q9QZFgAvb+PQBiKpqpHmw+K/Vjdy5dVFvvjuLz2Sm3doxS5x\nk2f98B7PfJJpm+wdGgAWvcAJrjtWRf/8EKF89hBszbqW2CcGDKMMkRu6/C5L\n1tZTuQ6mBLGILZSVwOQb3Cn0wT2okm50Hv63/SlK+JtnH4K36wYmERK/Vsyf\n3pA0RE+cxYIJDpwKfoZlpt9mr9X9McAn3dkVt51x0IjTw79G+DqUp0USu8R7\nMAM+V2/n9PfDdLlWbO+WzRlWH1U86dS/AQkcB+mhbonfxWQ4RhQ5qJMNLHzt\nQZZa\r\n=xs9D\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer \n\n[![npm version](https://badge.fury.io/js/@ampproject/toolbox-optimizer.svg)](https://badge.fury.io/js/@ampproject/toolbox-optimizer)\n\nAMP Optimizer is a tool to server-side enhance the rendering performance of AMP pages. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n* Server-side render AMP layouts.\n* Remove the AMP boilerplate (when possible).\n* Inline critical CSS.\n* Speed-up AMP framework and custom font loading.\n* Move CSS keyframe animations to the bottom of the page.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/).\n\n**Important: AMP Optimizer will produce valid AMP, if the input is valid AMP.**\n\n* [Usage](#usage)\n* [Best Practices](#best-practices)\n  + [Regenerate pages at least once a week](#regenerate-pages-at-least-once-a-week)\n  + [Debugging](#debugging)\n  + [Transform AMP pages at build time if possible](#transform-amp-pages-at-build-time-if-possible)\n  + [Cache transformed AMPs at runtime](#cache-transformed-amps-at-runtime)\n* [Experimental Features](#experimental-features)\n  + [Paired AMP](#paired-amp)\n  + [Versioned AMP Runtime](#versioned-amp-runtime)\n  + [Blurry image placeholders](#blurry-image-placeholders)\n  + [Self-hosted AMP components](#self-hosted-amp-components)\n\n## Usage\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer@canary\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>\n`\n\nampOptimizer.transformHtml(originalHtml).then(optimizedHtml => {\n  console.log(optimizedHtml);\n});\n\n```\n\nYou can find a sample implementation [here](demo/simple/). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](../optimizer-express).\n\nThere's also a [command line version](../cli/README.md) available:\n\n```shell\n$ npx @ampproject/toolbox-cli myFile.html\n```\n\n## Best Practices\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a week. The good news is, out-of-sync CSS will not break your page, as AMP will check the version of the inlined CSS at runtime and will automatically update it to the latest version.\n\n### Debugging\n\nEnable `verbose` mode to find out why the AMP boilerplate is not being removed. You can\nenable verbose mode either when creating a new optimizer instance:\n\n```\n// globally\nconst optimizer = AmpOptimzer.create({\n  verbose: true\n});\n```\n\n... or for individual pages:\n\n```\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true\n})\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP \n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length)\n    .replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example: \n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.  \n\nYou can use [@ampproject/toolbox-runtime-version](../@ampproject/toolbox-runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n* `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n* `imageBasePath`: specifies a base path used to resolve an image during build.\n* `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n* `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage: \n\n```\nconst optimizer = AmpOptimizer.create({\n  // blurry image placeholders are currently not considered valid AMP\n  // hence it's recommended to setup paired AMP mode when enabling this feature.\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp'\n});\n\nconsole.log(optimizedHtml);\n```\n","gitHead":"4c1963ce6f32ce607ce3925653ab38db250e79c3","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git"},"_npmVersion":"6.9.0","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"10.16.0","dependencies":{"css":"2.2.4","parse5":"5.1.0","@ampproject/toolbox-core":"^1.0.0-beta.6","parse5-htmlparser2-tree-adapter":"5.1.0","@ampproject/toolbox-runtime-version":"^1.0.0-beta.6"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_1.0.0-beta.6_1563886940371_0.5424736124601579","host":"s3://npm-registry-packages"}},"1.0.0-beta.7":{"name":"@ampproject/toolbox-optimizer","version":"1.0.0-beta.7","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@1.0.0-beta.7","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"prateekbh","email":"prateek89born@gmail.com"},{"name":"sepandparhami","email":"sparhami@google.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"6249a6f3623378e930d88af94e1ed9a44a15fc39","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-1.0.0-beta.7.tgz","fileCount":25,"integrity":"sha512-VFqxiyshR8yabB9OMh4dATxyRVmIXMrXw4tizW5k8Wq4Ie7aqDSpPoJh38fdjjj4xBm+yHS7L1nW1Nu44D2U2Q==","signatures":[{"sig":"MEYCIQCJ95OctqcxIhTYWpp8Y9vnOMpV5H+OIguNhoDflq1eYgIhAKevDGQy1FcyvbnvuE1Ukeukl9NKNDAv7b0RhECh31Ex","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":94339,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdOGJqCRA9TVsSAnZWagAA+f0P/if0FsTUihOigZNzMtUz\n9P5qV2W9dXvhpiNAxYfTJHxcBe97NtCcJMaYYJOpfKC8MMyNtRTR3MibjvSy\niJpSZ29KnPSRurXN4DQAqSCG/E5epAVqWvL59qwqZFv0zaVdw7dC84WTU60H\njY0A0U93iOHP1q3hnn1x7ga3u5FFeR1bUkodU+ODaF+78Hgzu4iXUwTerZt7\nnQ9IjmSTygOuxV9AwiicEbFSqqL3d0OOC3OH2ftMggmvzKG0yNdfQR+EfJic\nZAleY2U6UHBWo2dwr2xKyI4iWw7g27d0LAPEL2gHTs5gbMJc3/Kh4Sfq6yrY\nSQR8sn8jQEfqFGUbMGj0RMToOjMDGpMfFuOPWE/2K3JGTPDooZNUSuUevxqb\nKZEreRSUqkLIKvr/t+80YOfHuMOv3LKpdmLEqsUIs36jutNe6hL1QGPB4q9d\n8WMdnFnvDFNPjQgMsbMHNzZmkAwgmAyzjcRYGRUAPQgFCsn7kM53AxAxjoWg\n1BTQAkV4ZHHmUBTOS3bnBDnbZaRp+AqaLQriIs9dm3ZGS7M4mHYtEb79QBga\n5GDOJmHqmtUFKZIKRVbpL+lQYvb+vPP18dWwVC3y+E+mPH8SS7vkfgIUr/9A\nh8i7Tsb0DvCHUroW0ISvF9OrB4MlkpsRTMCkRdHLFw+vngBbxGHl72MxP9o6\nCqW9\r\n=1JRC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer \n\n[![npm version](https://badge.fury.io/js/@ampproject/toolbox-optimizer.svg)](https://badge.fury.io/js/@ampproject/toolbox-optimizer)\n\nAMP Optimizer is a tool to server-side enhance the rendering performance of AMP pages. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n* Server-side render AMP layouts.\n* Remove the AMP boilerplate (when possible).\n* Inline critical CSS.\n* Speed-up AMP framework and custom font loading.\n* Move CSS keyframe animations to the bottom of the page.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/).\n\n**Important: AMP Optimizer will produce valid AMP, if the input is valid AMP.**\n\n* [Usage](#usage)\n* [Best Practices](#best-practices)\n  + [Regenerate pages at least once a week](#regenerate-pages-at-least-once-a-week)\n  + [Debugging](#debugging)\n  + [Transform AMP pages at build time if possible](#transform-amp-pages-at-build-time-if-possible)\n  + [Cache transformed AMPs at runtime](#cache-transformed-amps-at-runtime)\n* [Experimental Features](#experimental-features)\n  + [Paired AMP](#paired-amp)\n  + [Versioned AMP Runtime](#versioned-amp-runtime)\n  + [Blurry image placeholders](#blurry-image-placeholders)\n  + [Self-hosted AMP components](#self-hosted-amp-components)\n\n## Usage\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer@canary\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>\n`\n\nampOptimizer.transformHtml(originalHtml).then(optimizedHtml => {\n  console.log(optimizedHtml);\n});\n\n```\n\nYou can find a sample implementation [here](demo/simple/). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](../optimizer-express).\n\nThere's also a [command line version](../cli/README.md) available:\n\n```shell\n$ npx @ampproject/toolbox-cli myFile.html\n```\n\n## Best Practices\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a week. The good news is, out-of-sync CSS will not break your page, as AMP will check the version of the inlined CSS at runtime and will automatically update it to the latest version.\n\n### Debugging\n\nEnable `verbose` mode to find out why the AMP boilerplate is not being removed. You can\nenable verbose mode either when creating a new optimizer instance:\n\n```\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true\n});\n```\n\n... or for individual pages:\n\n```\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true\n})\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP \n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length)\n    .replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example: \n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.  \n\nYou can use [@ampproject/toolbox-runtime-version](../@ampproject/toolbox-runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n* `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n* `imageBasePath`: specifies a base path used to resolve an image during build.\n* `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n* `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage: \n\n```\nconst optimizer = AmpOptimizer.create({\n  // blurry image placeholders are currently not considered valid AMP\n  // hence it's recommended to setup paired AMP mode when enabling this feature.\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp'\n});\n\nconsole.log(optimizedHtml);\n```\n","gitHead":"386bd4636da2dfd92569a6f0c0ede7bd2b49580c","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git"},"_npmVersion":"lerna/3.16.0/node@v10.16.0+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"10.16.0","dependencies":{"css":"2.2.4","parse5":"5.1.0","@ampproject/toolbox-core":"^1.0.0-beta.7","parse5-htmlparser2-tree-adapter":"5.1.0","@ampproject/toolbox-runtime-version":"^1.0.0-beta.7"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_1.0.0-beta.7_1563976298393_0.33840699362621374","host":"s3://npm-registry-packages"}},"1.0.0-beta.8":{"name":"@ampproject/toolbox-optimizer","version":"1.0.0-beta.8","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@1.0.0-beta.8","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"prateekbh","email":"prateek89born@gmail.com"},{"name":"sepandparhami","email":"sparhami@google.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"d6d0cacbd7dc772833b42d15b826186c1354c6cc","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-1.0.0-beta.8.tgz","fileCount":25,"integrity":"sha512-E6yfXitVbr8W4OX6hFChSrHY7EUTWzC4R06JMYEL8VFXGdubYQRI/u+oys7mpYaIdoC02jShBNLs11/y1Tyw7Q==","signatures":[{"sig":"MEQCIEmJ80aSVgAqjGVdfRHLY+YQZSXAj7f2JWcWRcgahWLiAiBaBBHfAqOrjKJKeMWBMygGdj4a9tkqMrdAN9QAvOdkkw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":94339,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdQqw0CRA9TVsSAnZWagAAOooQAKDpkvsqVdwHdr6NhvTE\nBZc6o60kYzY56AundTnKYJologZZc0h2Cq1eDcX4LJRmA5PzYGDWD1uaRmAW\nff+YqMEUGOzZ0h4W8TzZW9WEuc5QynI2RPIu3xmv/vBkGJhSRT/qbYq+UhC0\n9jD7cOLSFiCF0tcrUu+eqCd91OoS+LzEi0GdiVJRCfWKLZRac3C8jvvTkXcl\nDSIJ/N4ddrMM2dYOI4PdJpJjsqnHsRTCGz/iY3xyIcONHofMbvN6RL279Q7d\nKsi1yyZhZFPUuLZsBKV9g3xPYwSQXoEjTvK44JOZ4PwcVqZmprDuUXI38MKL\nebzlGN3oHCYwx9saWZr3kwGnz0gkNbUjtQ/HQ4f6wGH3hd1ijfpQz79hGvtw\nZqGtoVIAHpxwqZP7XmXMxbkoMDw4+Sx334sbKXjnqU03i4Q9PrnbuS5OtHH8\nDbXFeEm8Hr/1AlfofscteicUszdpKi+zBn9feaqziWDsm/sXVn7mQXvV9TBr\n+SCq25uwuuat1vTkgMiu2YQPo+1lcy9DQjqK+E3wv0epMjsxyopAt8J51cfi\nenQlCPMfmukDOBgg/Df0ESkCwYEClDrm6psFSr1DHZ+sk34vMpBXGJHhpMf2\nUdlkjUwDeIAoRsZqKMb1039HtvTYLcYi1Zl5lcXgG4sRYS+Aqo5IeUgTGfn2\ndUti\r\n=NcAP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer \n\n[![npm version](https://badge.fury.io/js/@ampproject/toolbox-optimizer.svg)](https://badge.fury.io/js/@ampproject/toolbox-optimizer)\n\nAMP Optimizer is a tool to server-side enhance the rendering performance of AMP pages. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n* Server-side render AMP layouts.\n* Remove the AMP boilerplate (when possible).\n* Inline critical CSS.\n* Speed-up AMP framework and custom font loading.\n* Move CSS keyframe animations to the bottom of the page.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/).\n\n**Important: AMP Optimizer will produce valid AMP, if the input is valid AMP.**\n\n* [Usage](#usage)\n* [Best Practices](#best-practices)\n  + [Regenerate pages at least once a week](#regenerate-pages-at-least-once-a-week)\n  + [Debugging](#debugging)\n  + [Transform AMP pages at build time if possible](#transform-amp-pages-at-build-time-if-possible)\n  + [Cache transformed AMPs at runtime](#cache-transformed-amps-at-runtime)\n* [Experimental Features](#experimental-features)\n  + [Paired AMP](#paired-amp)\n  + [Versioned AMP Runtime](#versioned-amp-runtime)\n  + [Blurry image placeholders](#blurry-image-placeholders)\n  + [Self-hosted AMP components](#self-hosted-amp-components)\n\n## Usage\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer@canary\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>\n`\n\nampOptimizer.transformHtml(originalHtml).then(optimizedHtml => {\n  console.log(optimizedHtml);\n});\n\n```\n\nYou can find a sample implementation [here](demo/simple/). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](../optimizer-express).\n\nThere's also a [command line version](../cli/README.md) available:\n\n```shell\n$ npx @ampproject/toolbox-cli myFile.html\n```\n\n## Best Practices\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a week. The good news is, out-of-sync CSS will not break your page, as AMP will check the version of the inlined CSS at runtime and will automatically update it to the latest version.\n\n### Debugging\n\nEnable `verbose` mode to find out why the AMP boilerplate is not being removed. You can\nenable verbose mode either when creating a new optimizer instance:\n\n```\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true\n});\n```\n\n... or for individual pages:\n\n```\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true\n})\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP \n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length)\n    .replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example: \n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.  \n\nYou can use [@ampproject/toolbox-runtime-version](../@ampproject/toolbox-runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n* `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n* `imageBasePath`: specifies a base path used to resolve an image during build.\n* `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n* `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage: \n\n```\nconst optimizer = AmpOptimizer.create({\n  // blurry image placeholders are currently not considered valid AMP\n  // hence it's recommended to setup paired AMP mode when enabling this feature.\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp'\n});\n\nconsole.log(optimizedHtml);\n```\n","gitHead":"6f291b4f9b0a00a24e3af763c918a2b70d47bb5e","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git"},"_npmVersion":"lerna/3.16.4/node@v10.16.0+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"10.16.0","dependencies":{"css":"2.2.4","parse5":"5.1.0","@ampproject/toolbox-core":"^1.0.0-beta.8","parse5-htmlparser2-tree-adapter":"5.1.0","@ampproject/toolbox-runtime-version":"^1.0.0-beta.8"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_1.0.0-beta.8_1564650547169_0.9783361571338101","host":"s3://npm-registry-packages"}},"1.0.0-beta.9":{"name":"@ampproject/toolbox-optimizer","version":"1.0.0-beta.9","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@1.0.0-beta.9","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"prateekbh","email":"prateek89born@gmail.com"},{"name":"sepandparhami","email":"sparhami@google.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"c974324ddcb686cdde71be823cc14d5b6763ba2d","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-1.0.0-beta.9.tgz","fileCount":25,"integrity":"sha512-iO269T5ffo96+VXV6IXpyp2SaWXunSMDM79QUWxBbQ4Jar5WAesiyJXGQaw5CEAlR8JlfRGd4LVNtMcNAiItrw==","signatures":[{"sig":"MEUCIC+MNhQseEV/QSV9UEzq2CXErVE1PbqK8bSr22MceEZ5AiEAl7w0y/38yUqV7RXGLdxZl6Wd4VaIf1XkmgcWEQ8R2X0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":94339,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdQq3PCRA9TVsSAnZWagAAZ0QQAKI9VX0vqHkip8HixZBc\nH0AqRqVu+v9CoKb6pnETpqhbtChThqFtzH5ZUP8IV5W5rUVgvA5r5sCl1P/3\na3U1wXMvLHwKySDtK7TnpgZZctrJ17y5LBD1uyVplbYGjarNNp5QP+7nbM4Y\nkZ80Yx1g+AwNvhjibg7mfTzhngNfMbXxChE7E50thAQjFPSOeCyvakTB8WrH\nA+82vDOPAuUoj7tvhjLQe9c4bgSnv53a/3pmnPg7V81wfuVLd08naYYzkc5c\nbJiA7yIwaIfSlSP5q+clzAMVLchpR3q+0LVYQp0OBeHuGeO48quWXV1H6i3u\nPyDLNv3XT9cs0rC0mS/o08HtUDL29Dfndg08K4M5el5wNEuba89ZXzIfzlGz\nBkHggNb8kXLBR+ug3UgsElHqJpZ1ob7bEGU1t/2IMaDfiTQ4MrZkCexBhnpb\n6T0eEKmqiXWNTyk0a2nE+J9bgo10VOm7ATvYTPgG6hJ5iaBwjIugaq4JzebC\n0DamqIQCRWUrUTXD4hLYUdkwTOZzY0Yh7LcpsgComt89pfJacaR3YOYUKvxz\nhv+9jVIy1vn0P6icaVy6fYBOitFyye/zb3h1rWggz13bc7moUAX3iz79tF11\n+hYpqYVhi6FnP7rl+UdSSnglDPcFoLugKF6NwbYDvlqdyW2MVMkijftQ1a5S\nlpOW\r\n=dneU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer \n\n[![npm version](https://badge.fury.io/js/@ampproject/toolbox-optimizer.svg)](https://badge.fury.io/js/@ampproject/toolbox-optimizer)\n\nAMP Optimizer is a tool to server-side enhance the rendering performance of AMP pages. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n* Server-side render AMP layouts.\n* Remove the AMP boilerplate (when possible).\n* Inline critical CSS.\n* Speed-up AMP framework and custom font loading.\n* Move CSS keyframe animations to the bottom of the page.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/).\n\n**Important: AMP Optimizer will produce valid AMP, if the input is valid AMP.**\n\n* [Usage](#usage)\n* [Best Practices](#best-practices)\n  + [Regenerate pages at least once a week](#regenerate-pages-at-least-once-a-week)\n  + [Debugging](#debugging)\n  + [Transform AMP pages at build time if possible](#transform-amp-pages-at-build-time-if-possible)\n  + [Cache transformed AMPs at runtime](#cache-transformed-amps-at-runtime)\n* [Experimental Features](#experimental-features)\n  + [Paired AMP](#paired-amp)\n  + [Versioned AMP Runtime](#versioned-amp-runtime)\n  + [Blurry image placeholders](#blurry-image-placeholders)\n  + [Self-hosted AMP components](#self-hosted-amp-components)\n\n## Usage\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer@canary\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>\n`\n\nampOptimizer.transformHtml(originalHtml).then(optimizedHtml => {\n  console.log(optimizedHtml);\n});\n\n```\n\nYou can find a sample implementation [here](demo/simple/). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](../optimizer-express).\n\nThere's also a [command line version](../cli/README.md) available:\n\n```shell\n$ npx @ampproject/toolbox-cli myFile.html\n```\n\n## Best Practices\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a week. The good news is, out-of-sync CSS will not break your page, as AMP will check the version of the inlined CSS at runtime and will automatically update it to the latest version.\n\n### Debugging\n\nEnable `verbose` mode to find out why the AMP boilerplate is not being removed. You can\nenable verbose mode either when creating a new optimizer instance:\n\n```\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true\n});\n```\n\n... or for individual pages:\n\n```\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true\n})\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP \n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length)\n    .replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example: \n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.  \n\nYou can use [@ampproject/toolbox-runtime-version](../@ampproject/toolbox-runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n* `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n* `imageBasePath`: specifies a base path used to resolve an image during build.\n* `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n* `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage: \n\n```\nconst optimizer = AmpOptimizer.create({\n  // blurry image placeholders are currently not considered valid AMP\n  // hence it's recommended to setup paired AMP mode when enabling this feature.\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp'\n});\n\nconsole.log(optimizedHtml);\n```\n","gitHead":"2b6eaa91c3654fc1523f7fc2ff93b37579b52869","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git"},"_npmVersion":"lerna/3.16.4/node@v10.16.0+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"10.16.0","dependencies":{"css":"2.2.4","parse5":"5.1.0","@ampproject/toolbox-core":"^1.0.0-beta.9","parse5-htmlparser2-tree-adapter":"5.1.0","@ampproject/toolbox-runtime-version":"^1.0.0-beta.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_1.0.0-beta.9_1564650958346_0.4824324554531134","host":"s3://npm-registry-packages"}},"1.0.0":{"name":"@ampproject/toolbox-optimizer","version":"1.0.0","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@1.0.0","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"prateekbh","email":"prateek89born@gmail.com"},{"name":"sepandparhami","email":"sparhami@google.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"382ec15baf47229e3a33028253fdcae436e09b4e","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-1.0.0.tgz","fileCount":25,"integrity":"sha512-doAi5nR8k9sElUi3h14iQJAfRXf+4D6i6ONF2qHkS9UbhYUljDB7bCN9C1RlhpwF3r+nCBdBdx8JUHl77M8n2Q==","signatures":[{"sig":"MEYCIQCLdTd60+KernrS3FhzFXHznQbal7E/OmQvc2sYXjpvxQIhAIUz2UKH/fTyubmsLHwHEvCaQLkdWsb+Yx9PGivThDpw","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":94311,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdSdw8CRA9TVsSAnZWagAAwIQP/0S4TxmEGdTET1gXlnyR\nLLsPWA8SIhJk7V+nXRtH8hr5hVOjHJE+Fmy0DQBJZ+9a7DAhTaiybtVPg00Y\nq3/3KstoXsuy2TMFC66bfsubVydEJudvEYXI375IvP8f85XbtkkPZ6XVIgSX\nLoglyB92HKFH3EUgc13f8uG98c7mzdXGN8+mxOU8OzOpyXESx2Ys7naBUBIF\nY/wVMVENjw7zxY3AInft93nWW0BUsnytwX1jNeNZ0jLnybo5AoTqvIGwsq44\nCW8VUKCEbxa3yBPQIdNWczbobaLKVQ8kAJfY5dNEYCjwZbh2YFQarvr0o9VS\nD7FFEB+CLRMe+UaXQG3W1ldN2pILaGfnapUvSJbQBU/6P+qy+oA1hV2AbD1k\nJlqs2kk2IFb8rImtEUqa780yz3zoBVxo8B/4MaOJmyAEho1BZo9lJCU1R2u+\no4gEXYg+z0fzyn+JTEHHGBu8GrD+avxns92TCtDTy4KSK5tum9xpjvh5DhKs\nphk5d4+kyq6inDVRs4fpNwaALLqs9nhgnm2sOM8rOIE8NRD5+vFpqL5fwYkn\ntsZMEuh0JYvlMdKfto3Lio5owUAjKPozpfAMazafgKkgKvYjHnGABlQRuLP/\nXaGh8YmDoIt9mmsB46gMtg3VZ3SFn2vqY9tQ27+YuwhLrfUFR+WgvJzixshL\ncKzS\r\n=GNbs\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"cc04a8267a51e57290ca81610ac6854de1aea986","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git"},"_npmVersion":"lerna/3.16.4/node@v10.16.0+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"10.16.0","dependencies":{"css":"2.2.4","parse5":"5.1.0","@ampproject/toolbox-core":"^1.0.0","parse5-htmlparser2-tree-adapter":"5.1.0","@ampproject/toolbox-runtime-version":"^1.0.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_1.0.0_1565121595988_0.529091121408235","host":"s3://npm-registry-packages"}},"1.0.1":{"name":"@ampproject/toolbox-optimizer","version":"1.0.1","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@1.0.1","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"prateekbh","email":"prateek89born@gmail.com"},{"name":"sepandparhami","email":"sparhami@google.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"5eeda7bc84c23237479c35442d4696c4bdbeb1d3","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-1.0.1.tgz","fileCount":25,"integrity":"sha512-zz1cJsQWBvfg2h1ce2/bbgNdSkTjIY7PaF7QhWMzYVcfvdxGSAykA+Ajt+F13H6adNAtIn09s96z/+6pn7XiXQ==","signatures":[{"sig":"MEUCIQCBlc6DsDHUdu+B1/pIs+bp6pYgQFa6bAuhkHeoWa3KvwIgCwC0dHkXxkZanLTrSs9/w5GrHX5y57Ts2poJU0z6t1o=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":94311,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdSd7iCRA9TVsSAnZWagAAMZAP/R6hsYp8bx4hFj7Y0Tnl\nRJc3XIHQyAi2axK69G/FN8HgnSNYReATOYEwpS+zkFXD02zbItCNAWN+91nZ\nZ/RBzIDxZUS0K69nWzuCoP8kcz2DObFLl60q9jYWtmS418yT7eQRLdQIjNE9\nkYKLp1G0MSdCBYdLd0rmSTED5z9lJr7njVxx2/+wGz3nQoq0h88rwsvjy/Cx\nRrNxEmsfH+1J154TUNureJwVmKIMf7gIamqat54Lfnfdh8rUKRFdv2Qt1Iab\nn0NOAUPC7en3k9oc9VfcWsGbovUCQD4Z5wyiQ3eT7P6oUd4YwtXqAZggDag5\nwOIEtyXRRE/NI4CJJjycRx5XoLRsnTBfmjUdsWKdzZaMSuYEyjJYhS0h6ib5\naLJQnydcMzhHVwCE51v3pUq+zzs5c9HxLf1duHAhWX+RziGtWj5WHprrOnSH\nEbzpTqHO7UFNa1eOtnfqvdReQZzi2Jtvctal6Qte/yU5pFKa1ffnLIlZbrSV\nW9o2CaDo78b4MaGHr5M0FUXi3+6RhHFnvEtSadP2KgoRAQ/nkaLJ+UZ3Kl5o\npQdrvYSGAs6++yDaQNtxZ2kwDGJBf7fqEPGL3q+YVBiWrYRXavUQi65VRVDH\nsb6HWV/UHliMpx5hDaNRf3xBTqtFoVg5uXxQBXExmf8bPpIsyq73TRCs8Yff\ntCSq\r\n=AfOM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"4da05d4a419241bbcc8157553022af3a84676e9a","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git"},"_npmVersion":"lerna/3.16.4/node@v10.16.0+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"10.16.0","dependencies":{"css":"2.2.4","parse5":"5.1.0","@ampproject/toolbox-core":"^1.0.1","parse5-htmlparser2-tree-adapter":"5.1.0","@ampproject/toolbox-runtime-version":"^1.0.1"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_1.0.1_1565122273813_0.562205869776816","host":"s3://npm-registry-packages"}},"1.1.0-beta.0":{"name":"@ampproject/toolbox-optimizer","version":"1.1.0-beta.0","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@1.1.0-beta.0","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"prateekbh","email":"prateek89born@gmail.com"},{"name":"sepandparhami","email":"sparhami@google.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"bb0a7289aaaaa791868eb1dbdb021c4fce2f273e","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-1.1.0-beta.0.tgz","fileCount":25,"integrity":"sha512-Aw3LHgBktWZse/R4O1niZAohzmrf3PxJS7OXRf00YhBQLVfa8krtSF8upQbooPsYcbZ502sAkrViEfOqX7miig==","signatures":[{"sig":"MEUCIG3QppkVCajr4wNshDaPirL29c8lsxUg+/FdGW2+cOiBAiEAnHC+9M4aT4b0jO9Ix2+GkghfkmSgifFt2fBt7KHkBqo=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":86880,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdaDVACRA9TVsSAnZWagAAwQYP/AlE3bK0VOb/LIA4eeEZ\nzWCYqK1Pj6n3BRW+S9Xq5xhvnAnTeozJk6D6mfN22VxoFFolG28l/jIsN/j3\n89wRnOyMDhQ2mcL0law7AG44iqeHo1Cbz6nD4ex9IWkGLCy2rLquf3OdCBm2\nzPb53Wqz70GDKuAFr5ZEnxX0MBW7iOXiSnYAksz0piPic+GDfZGFEJiR/iQI\nWvCmyxIkJRKQfYX70luXZYPL5G9eoZdQvWeHDV94bl+gugmuXIyQET+rSnez\nptfcxp40O06xuSwPutdfr6pNyENibVb8IeoJo+G2tlPhWWca9dXdmhki/3qI\n8Poos38V8PypunMKkqgm3OWm3VEKBKkSGCAw6bEXS9WDxR/A7zsDDlVQVuMN\nRsJ+qgWI6ReH305OlTrwt99iahejdFhM0/ie9GumpbDC9CgvLRu7RZorjWpY\nVJrjm2M1y/x+WdUiK/ObgJYIfserhI81ILdDACXof0aG8GmR/K3HOsNQ0VMw\np9tyWgI+yH5eGXKekg1F4cHAUw5IUCYOqXIe5bLAw5bIJtlQh1+uZ35gvr4k\n9XlhzZhKP7EuLUltxWVZH2Dhdg9IIveBO7auzrEQ1i1IvjDMKBfSpDdMv5BT\nf9x09q26QfUqO7RhoeQowbE9m4By/AnsTptafAYwcfpiRLTaoJEVEMwbt+9a\nSc4f\r\n=VHf6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to server-side enhance the rendering performance of AMP pages. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n* Server-side render AMP layouts.\n* Remove the AMP boilerplate (when possible).\n* Inline critical CSS.\n* Speed-up AMP framework and custom font loading.\n* Move CSS keyframe animations to the bottom of the page.\n* Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n* AMP Optimizer will produce valid AMP, if the input is valid AMP.\n* AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>\n`\n\nampOptimizer.transformHtml(originalHtml).then(optimizedHtml => {\n  console.log(optimizedHtml);\n});\n\n```\n\nYou can find a sample implementation [here](demo/simple/). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](../optimizer-express).\n\nThere's also a [command line version](../cli/README.md) available:\n\n```shell\n$ npx @ampproject/toolbox-cli myFile.html\n```\n\n## Why doesn't my AMP page render faster?\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n* if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n* if an AMP component uses the `media`, `sizes` or `heights` attribut ([documentation](https://amp.dev/documentation/guides-and-tutorials/learn/common_attributes/?format=websites#heights)). A simple workaround is to replace the `media`, `sizes` or `heights` attributes with normal CSS media queries.\n\n* if an AMP component uses the `intrinsic` layout. The good news is: support for `intrinsic` layout is currently [work in progress](https://github.com/ampproject/amp-toolbox/issues/264).\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true\n} );\n```\n\n... or for individual pages:\n\n```\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true\n})\n```\n\n## Best Practices\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length)\n    .replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](../@ampproject/toolbox-runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n* `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n* `imageBasePath`: specifies a base path used to resolve an image during build.\n* `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n* `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```\nconst optimizer = AmpOptimizer.create({\n  // blurry image placeholders are currently not considered valid AMP\n  // hence it's recommended to setup paired AMP mode when enabling this feature.\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp'\n});\n\nconsole.log(optimizedHtml);\n```\n","gitHead":"cc04a8267a51e57290ca81610ac6854de1aea986","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git"},"_npmVersion":"6.11.2","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"10.16.3","dependencies":{"css":"2.2.4","parse5":"5.1.0","@ampproject/toolbox-core":"^1.1.0-beta.0","@ampproject/toolbox-script-csp":"^1.1.0-beta.0","parse5-htmlparser2-tree-adapter":"5.1.0","@ampproject/toolbox-runtime-version":"^1.1.0-beta.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_1.1.0-beta.0_1567110464064_0.3803204305673378","host":"s3://npm-registry-packages"}},"1.1.0-beta.1":{"name":"@ampproject/toolbox-optimizer","version":"1.1.0-beta.1","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@1.1.0-beta.1","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"prateekbh","email":"prateek89born@gmail.com"},{"name":"sepandparhami","email":"sparhami@google.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"fb2e1cc7ce2f091256f7a9cca88c6235e516f2c8","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-1.1.0-beta.1.tgz","fileCount":26,"integrity":"sha512-XTL0i0UvUiVpb186ueAXk+d7itEfi9fFzwWIH/DP7OeXe69gSDe2AIOdFUYdUzcj4Oi226gY7ZRIP6umInALJg==","signatures":[{"sig":"MEQCICGLlIiz9C48q/rGoMZgBPZRxVPrqNfOkBFBa5qPvOm+AiAs/sV3nNqTZIMOtT26AXq5NKxluNjuvyZi/UsZLE5YGw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":98278,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdg9UJCRA9TVsSAnZWagAAz2sP/0mNJg0Cfxe1k42yKW2s\nZ9OrRhTyDmGwYYb0pjwgNmaEnLH0TisptTgvfs1dEf6VDY4zauZuhjKsCobR\nClXUUYUr4lDIiwcpdnJqAVsVA3YSp+QmmZEgPZGk+FZ6a1xSD9Nzy3+p3Yff\n9l6qaaQaJpKpjF8haWnWdHinhI5QxKYpnq81hjhrwWWYmv4MnY5RHLu6k55g\nXS58gxU2rmOi7eSPoObTiXcPCKaT5K4HXMZJELOnZUPZwJXXvUjP/3Xrju/F\nYSnUozB4vgIZCpNiWlY+vew7b6IVvHybd22PYdpJqruB3O0Q0z3YwPYg2iSd\n7XQYRiXMCy7b7Xm2zeGZQm9t1+2yJ8o/eaj+iuTTiOJmp5U6oA872MXN1egM\nRQXXTUojmhdNEVYB5iI12phX8i37RdxkApty7gTcz3VNIYpw+hmQ3xM3dEQy\noKNmxazThsvyp8+hbDhqX+rrqKfTn8i73+oUaoE8z7ooXEZl1xVLv18J0Pxp\nlou6jFGNwzJJHiMou9dEVyxrGAG2I3vLLoMFuaWNliYtcW4rcJn3Hq0AsSB0\nq/TrYA5Z75g7pDSUTFhpEsfqWt9kMUSqO5Z8iZIhSHihGfYQhLveoY3bitgb\n+DH63SDswRfCqtgpJi27dY0Tq7FUaLHgUdG7WNjaqNaQ3KMSjodMF/R9y/mL\naldz\r\n=PLNX\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to server-side enhance the rendering performance of AMP pages. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n* Server-side render AMP layouts.\n* Remove the AMP boilerplate (when possible).\n* Inline critical CSS.\n* Speed-up AMP framework and custom font loading.\n* Move CSS keyframe animations to the bottom of the page.\n* Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n* AMP Optimizer will produce valid AMP, if the input is valid AMP.\n* AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>\n`\n\nampOptimizer.transformHtml(originalHtml).then(optimizedHtml => {\n  console.log(optimizedHtml);\n});\n\n```\n\nYou can find a sample implementation [here](demo/simple/). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](../optimizer-express).\n\nThere's also a [command line version](../cli/README.md) available:\n\n```shell\n$ npx @ampproject/toolbox-cli myFile.html\n```\n\nCheckout [the samples](demo/simple/index.js) to learn how to customize AMP Optimizer.\n\n## Why doesn't my AMP page render faster?\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n* if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n* if an AMP component uses the `media`, `sizes` or `heights` attribut ([documentation](https://amp.dev/documentation/guides-and-tutorials/learn/common_attributes/?format=websites#heights)). A simple workaround is to replace the `media`, `sizes` or `heights` attributes with normal CSS media queries.\n\n* if an AMP component uses the `intrinsic` layout. The good news is: support for `intrinsic` layout is currently [work in progress](https://github.com/ampproject/amp-toolbox/issues/264).\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true\n} );\n```\n\n... or for individual pages:\n\n```\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true\n})\n```\n\n## Best Practices\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length)\n    .replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](../@ampproject/toolbox-runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n* `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n* `imageBasePath`: specifies a base path used to resolve an image during build.\n* `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n* `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```\nconst optimizer = AmpOptimizer.create({\n  // blurry image placeholders are currently not considered valid AMP\n  // hence it's recommended to setup paired AMP mode when enabling this feature.\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp'\n});\n\nconsole.log(optimizedHtml);\n```\n","gitHead":"b5c4db9bdc2962da78862a181d409f6082b9b3ce","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git"},"_npmVersion":"lerna/3.16.4/node@v10.16.3+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"10.16.3","dependencies":{"css":"2.2.4","parse5":"5.1.0","@ampproject/toolbox-core":"^1.1.0-beta.1","@ampproject/toolbox-script-csp":"^1.1.0-beta.1","parse5-htmlparser2-tree-adapter":"5.1.0","@ampproject/toolbox-runtime-version":"^1.1.0-beta.1"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_1.1.0-beta.1_1568920841105_0.1983511687905637","host":"s3://npm-registry-packages"}},"1.1.0":{"name":"@ampproject/toolbox-optimizer","version":"1.1.0","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@1.1.0","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"prateekbh","email":"prateek89born@gmail.com"},{"name":"sepandparhami","email":"sparhami@google.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"4ab9949cd2ce1e1f6c50b8e97b6cfcf75302a8e6","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-1.1.0.tgz","fileCount":26,"integrity":"sha512-7LKvsF19eCaTxByOdnFhlEZzJcd3yNpgRztpGylMnL+Ez2U3Q/VR0yQePvgpsnvm5PBvN7/09Bql1TFEz8lmAQ==","signatures":[{"sig":"MEYCIQD3hDCL8U9z/3otVp8cTzo7+fLlXl3zV2gegf02cx2SNAIhAL3u1tCt19TT+hB0oqVPCtjj+3oMZZi2OjXQFhM+1h4t","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":98253,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdnJm7CRA9TVsSAnZWagAAzIwP/iAExp333jl4YYy19bWt\npuR1Il9EOfvn1Ma7IfdD8Z2jqsEDRPI8usiDGzviOf/UW5H9GMJUWdd42aFG\nbyy8DE3ldH0/3+ge2cePHKAto5YbfnHCKUNi/kb1aKIp8VCBdY+LvaSp82sZ\naMCrXoUS3WqwjQ7IdRBEWBLAQN7LIDWYHG1ssIJ5yQ+PsdY2yVwNem7GAfQM\nkrpVn5XOvYVLBv0w97Ng84+s7ycVy5W8BrKFysb7mHD+FYYLs0+30UYVjJwj\nCBG0RzXUOZAH8yAL8RzQ/kuAPfwi5jDd2qoGJqPhv4YBjCH4tb7GQRK84KQI\nMlopXXwbA59XU1qONB5p8Onag0Ak8pwgocdpEYs9uCH1yJ1yW/W8okB5NJjN\nwCNtcXJRLvV9icjzdukhPqtuIFsvUs6ASdpLrLXrAbjuZVMAdymz1xYSUXHh\nxe5n9tuHW6z3p83WQiOPfhy90OuR3QphGdSYPDP3YUcflXuYo9pks4pV8imW\nyyqsk/VjyuMKYbytNj4ISsgsYB7TRK/sZB6cQXomN7PqwUIzDQp7q2OF83ow\nNnOTxB/aGn37BKqRhEup+lLNpk5FTTGAcYKzHhmHkdNigGHHrvSShp2uXxVE\nOsxZ/sn50lNMjbdbC9UykEmzYnbgMvVQY+leUGJsIg9/ml/Wxk2wlYO5aCmQ\n3HLa\r\n=mvrp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"783938063dde0c32f5a79af4148eec53f26e903f","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git"},"_npmVersion":"lerna/3.16.5/node@v10.16.3+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"10.16.3","dependencies":{"css":"2.2.4","parse5":"5.1.0","@ampproject/toolbox-core":"^1.1.0","@ampproject/toolbox-script-csp":"^1.1.0","parse5-htmlparser2-tree-adapter":"5.1.0","@ampproject/toolbox-runtime-version":"^1.1.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_1.1.0_1570544059135_0.18769180654372009","host":"s3://npm-registry-packages"}},"1.1.1":{"name":"@ampproject/toolbox-optimizer","version":"1.1.1","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@1.1.1","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"prateekbh","email":"prateek89born@gmail.com"},{"name":"sepandparhami","email":"sparhami@google.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"be66245c966ba9b0f5e3020109f87fea90ea377d","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-1.1.1.tgz","fileCount":26,"integrity":"sha512-LTtTM5FSOrWuTJ6mOwPfZmpUDI6polrNz3tX2EmDmDkjDK+43vSpq1OHtukivIFHafdixJuoeki5dF3PC/ZoWw==","signatures":[{"sig":"MEYCIQCz4h/tgg6b14qQxt++RY8Q84whhrsjuqcpjqD2L3AfYQIhAPGbEpcEMVkRCQY+CUS1iR3YkIwfG/mWItJBFYNnpdLV","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":98253,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdnKCOCRA9TVsSAnZWagAA0mwP/3P9O7rZaCMfWHGXEFuv\nFtivXJxsEiE0TwWlB9kUi//bUWmDO7DBxhEhSanSaa7McfnImdf3SgqepxLF\nnlebp8vgLb3p8wGAktekmNuk4m8sprN8dZQz+V4EG2F+GP0qWb+4YSCws9+m\nH/74+ngy2k7BqCHosDU231r9Kxlp61Edkg9NJ4vHFbSthSeBHUYguyGDToXV\nb8y1vf4091OyM4vWgaI9HPtwnlWMS8OQ16La2kw9J88C1R5hghNjV49QP76B\n4iC5N3Im/0Ha29X5OfJ+M1IguwLoK3xz7qFVjFFZjY1aqnnYhSkAYLj0bIm6\n3fKBouGwDPtSOP9nLQHsBZw1mvAZ2MIABhftj8M2vUUc3DST/tB/kAarppfA\n2MWeNeHr25ZvqKTUtOiAZ36WvHoCemfX/9EAOB5euNbEgJWRxfcRJBRw2WyR\n71KH4CmOiyAFX1L8EjR9sx68PIfpF5ECRDHRrSuLnK/MR742lyvdmmerBI1m\nrWbjbShqDaa3XRV3U6dASdpQG9RiOw9JBsJUVHH+Y+QpHNNjdBSeIomVLNZO\nM8hShGLquZMVfymvA4CofsqK4hVA3kjL9BK5N7PSPaOaa63/YRBjnTxlL0N2\nQ1n9tVL/0Ij7PQtfGv9e6+dDAW77AEWh/i8/ft4L/XYpakN1uWoBJ+Hzt6jE\nSh+u\r\n=jj+X\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"6ce20613dba3e66b7722df599f62f35dc6391962","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git"},"_npmVersion":"lerna/3.16.5/node@v10.16.3+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"10.16.3","dependencies":{"css":"2.2.4","parse5":"5.1.0","@ampproject/toolbox-core":"^1.1.1","@ampproject/toolbox-script-csp":"^1.1.1","parse5-htmlparser2-tree-adapter":"5.1.0","@ampproject/toolbox-runtime-version":"^1.1.1"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_1.1.1_1570545805886_0.9633472961793956","host":"s3://npm-registry-packages"}},"1.1.2-alpha.0":{"name":"@ampproject/toolbox-optimizer","version":"1.1.2-alpha.0","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@1.1.2-alpha.0","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"prateekbh","email":"prateek89born@gmail.com"},{"name":"sepandparhami","email":"sparhami@google.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"5314b8da5d6a3f12003af730a1094e677900f551","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-1.1.2-alpha.0.tgz","fileCount":26,"integrity":"sha512-FGpt+oKZ/E+K0Fiy3nlr6eJ1utvDDT//dFC46tTbzcS/D9yaTnWQGxP8OO3IKIfWa63+tH2EiyurbInAGvwrzg==","signatures":[{"sig":"MEUCIQDOH613nmGJtWSpNozpxRiMIP+WbDEvtIBcjf/I7g+V2QIgLvgNzg/YpsoNjaej5pUado6xBJ5+6LQR52BfxeDM4vg=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":101294,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdnjs6CRA9TVsSAnZWagAAWjsP/R3Zbig2dwCkOVX0jAuH\nY8fDzmNfIRrOJ/s9G3sb1QXxr+21UPSysFrkL+oIXlE6NCqIuWbe7Tkec20k\nu/orcquOhakzdvMlJGParP7wmBk0ReFo3ikXUMi4aE/8Y/J/mRDZ6bBmNOo1\nf8OdxhWyE2G2u8UuLgQWggsOaLIYJLR+/QHwjR90cj7uyueYLA1YLYv8RPRe\nckfOcyPzsJFZAMujMR7j/Q0sLsnDbrv+zMRlRMsj2L19Sa2N75VGvjvxqxUb\n5+5hv/UFvrYf2ciDfc0PrMeR6LakHQ8/gOMm6TFqQxsp74tjsERFfkcN5f6r\n6ST8BAVJPjsCPrWgfgi6d/GVNqyVnDqk6Sg6TZe75lcEJeMPuORgaDUuv7Lf\no6bHTVRSnVTfvk4tvgOBSO0m34t4mxaorfA054YfW2g9SKDP/GU3F7Z/U3E0\nIUeT/x0uDI8XLWri0LfpVXVuWh7lMPLYwymB8OoQXuZ9TynK67HecfEK106o\n4NtN/8yQRi5PrBqIMTQmtCzde1g6kIOdClyiRcv7DJifAHweqzyuyLQfIpYi\n5PD0YFrep++woL9DPVTu48aHYcuVxSRu2kboYgtzlgSCJ+22rUsLQ5TBIB4I\nvqPClMpOOgwpB0lqkeGFRoHtBX63b/1BWxnyESfekdzQy1VMNYXwHEplR9eM\nwE1r\r\n=+DhL\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"9fe174b241a47ace8a888dedfa748d59e7648de2","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git"},"_npmVersion":"lerna/3.16.5/node@v10.16.3+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"10.16.3","dependencies":{"css":"2.2.4","parse5":"5.1.0","@ampproject/toolbox-core":"^1.1.1","@ampproject/toolbox-script-csp":"^1.1.1","parse5-htmlparser2-tree-adapter":"5.1.0","@ampproject/toolbox-runtime-version":"^1.1.1"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_1.1.2-alpha.0_1570650937315_0.8634316816351291","host":"s3://npm-registry-packages"}},"1.1.2":{"name":"@ampproject/toolbox-optimizer","version":"1.1.2","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@1.1.2","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"prateekbh","email":"prateek89born@gmail.com"},{"name":"sepandparhami","email":"sparhami@google.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"70bb3ed433fbe999cc623ea28ea7ed9480a1d4f1","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-1.1.2.tgz","fileCount":26,"integrity":"sha512-hb7WbiS7PrPNcv91uNLJkZhEF5M+gmckAEFfpZCc1nA6bJUo6OofQQL6IVkblCeZNvFDcPyDXBFfdHPoC3Cy2w==","signatures":[{"sig":"MEQCIEXu+f8D6Zx2cuEbr41o+KdKCozdUcIILqD69RFCEyt7AiA+0olRwsWEUrRs9XlVjkOab69hsVdepTK8I9KyHUhCHA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":101312,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJdpclvCRA9TVsSAnZWagAAYsoP/3x+2o2v1F3PFsaMcKAr\nTkiRDCI1YyAvThwIBfWc2JBDXYoD9eu9pq/9bV5C/DIvcEEVIgOBuPg/dhZd\nsEQW1e4tVhAfXGZdC7pBTPiDYzpyesroM/ZFS0e3+1ihaiAL6CglwC0C++y/\nH0/YZwKHSoff1n/rhCa6+8V94mqD7W6Q+zLDjWk8opcK/1LgfdBfr+pC8Brl\n8kUR8VRL+o1JDRueE25X0ZLEglKS+zcZpB78V5T4IntB/QBLj7xD8eMUKhnW\nnlqxv4fuQcS+OdB5fH8jPY5idCBvhyzJKTsvjkiV8AFavmww9eXUNVpIT3MO\nqFQHXKgodBu2GoByp/LPOPaGjbsguWOJ3pzvqD6eflcna5kEpTKmzDjIE0JA\ndxW8LnLSo5Ab4SkZHSM56aX8YA8WHgQZ4Yxy4ebNsjFHZBDUpByGyOJTw26q\nbebOo2iAb23FreMneLF84KbiwBPWaL48CdMDxhPrShv2LZvYwHfylT5XFC0a\njtaqn2zsoH2bJCrfiyyf1Ej4GmEEzIBT4ks0+/I5hv0mgF0GIFtWtwoRjEBJ\nh+q3n3H/ivVGOP4qEiyqCxeQt4tkNQNgnp3jO+Edqd/BKVa0NoR+/iZRD9a2\nK6JyHkRtJLMZdqa6Nb2IrNPBQkRIOs8nXOuIW+71hQfGdF5CIIXYOl4fthxH\nzMQA\r\n=YFGE\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"e914411b6dca30b203875a6be32f2b07fcf385ed","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git"},"_npmVersion":"lerna/3.17.0/node@v10.16.3+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"10.16.3","dependencies":{"css":"2.2.4","parse5":"5.1.0","@ampproject/toolbox-core":"^1.1.1","@ampproject/toolbox-script-csp":"^1.1.1","parse5-htmlparser2-tree-adapter":"5.1.0","@ampproject/toolbox-runtime-version":"^1.1.1"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_1.1.2_1571146094776_0.688702246241002","host":"s3://npm-registry-packages"}},"2.0.0-alpha.0":{"name":"@ampproject/toolbox-optimizer","version":"2.0.0-alpha.0","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.0.0-alpha.0","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"sepandparhami","email":"sparhami@google.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"df915588a9487c7a518198d9274ec97cd31248de","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.0.0-alpha.0.tgz","fileCount":31,"integrity":"sha512-egOJE5+8+G+CfhTiU2zMDLTm0LEiNJEg7+qZJAFCivecyfTVFXUP8wCXJErmDtS0pB4TancLs6YDdtz3XKI5CA==","signatures":[{"sig":"MEYCIQCEuMoFyP9p3n2eC113nDja1fVHy1OsyhX8r92Jl1udJQIhANawAdX7ASkacff0onb5UgkdUrqQI6wZeNl4KGX/Yd89","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":128683,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeKFyGCRA9TVsSAnZWagAAifIP/jX7zd8cAJmqRjGsRczm\nMLN1yT+R+d31Kk3B+hdjHmfUV5IgYgKXByMsQlHYqpuhvOk3GfvFikR5JrUB\nP7dHM1m/kTat9/CN+gfhh0fWpsS1FrZafWiuWBtNxLa62cmwW/IFtSW8eaGa\nxuurKcNIhamwJ2+lBP52Fz7b/cAk7YSzEiez4NqJMAGclIQGmVFBc4Fdayzp\nSoOgCrSwAfUk7Jwyd6Pms1R3sjfYQRsue9pm013HAXpD6TN86zERzEPWvqyU\nMYoPKRHklqkxVzQHss3iinDzKmOVDuDDRyPMKxeFd92OXPlla76k6hoCapT9\nk9HHhmzOgIwaahjArnntYCgOEOFBm5XPwVtbCe3J5embzBExPICZaCli9q+b\nHCdKwfUKT8FVYCeIzKH45594NaIHYuq1sde8r/hqvZ+nXBfoXPKG6eWqwf9Y\nj6YMB/kR7Bz+fgKoRpEBCOApSVZb3I0sOIKY2axQuMHy2jSMY8cvOBy5M5bt\nqSsApkynUa5/Cev3BKmENJbLHV53WA8/1nEmzeYxMDmcWUfjOICpdj6MwkYs\nMT0iWBX2+HKsECevark9ai1EzrxmfEAVsA40gAToGfFqY18LTUocRSU7Y7Bi\nx8gL90y2KTqR0A1lNgsN8myb8qktULTtpS8M+rP4u6Eown7ug2+mQDow07Hi\nBAJa\r\n=QO+E\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"6140009b75eaa18afeaccf0a5f1996f490dcebfb","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.20.2/node@v12.14.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.14.1","dependencies":{"css":"2.2.4","domutils":"2.0.0","domhandler":"3.0.0","htmlparser2":"4.0.0","@ampproject/toolbox-core":"^2.0.0-alpha.0","@ampproject/toolbox-script-csp":"^2.0.0-alpha.0","@ampproject/toolbox-runtime-version":"^2.0.0-alpha.0","@ampproject/toolbox-validator-rules":"^2.0.0-alpha.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.0.0-alpha.0_1579703430027_0.7168987604871879","host":"s3://npm-registry-packages"}},"2.0.0-alpha.1":{"name":"@ampproject/toolbox-optimizer","version":"2.0.0-alpha.1","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.0.0-alpha.1","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"sepandparhami","email":"sparhami@google.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"5136aa8b4770716ced407392515ee9bc23a2722f","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.0.0-alpha.1.tgz","fileCount":31,"integrity":"sha512-SEmqPi+zcgxPFQUjA4JAAph5Ofpi0e2euuJ/x8By3fYPkAgVrOiDZkw07lGau5u0x8ePhn/0THlLg2T6EDAsMg==","signatures":[{"sig":"MEYCIQDtZZnK5BJ1zYCag+yi/6M5YAlp2w9SiuV+RPY8ies0hAIhAKhtDriBiLAdoxgBMEm1E0awbR1aih6LJijpZvIsrdyh","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":128745,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeKF/ICRA9TVsSAnZWagAA2isP/jvmx0g3XAXVYecn3kS5\noNkL4Wg0HJEsQ+t9bz2YWm1PaZ0FgmMHZe0o/ywufrzQBhxIGtTYeYC5Aojd\n2KUmN0L9Z7LIyTlr4FKYVFRpd2qlbwNoaRmMowz8GgeTr827ZMqvEJ2o0ITv\n9tc9V/3nMtp0j08Hzz22mOor1qfs1qfJa84bVteycmzrJQ0TO75kKuBcSkwe\nse7pOat/AEuHhp3VAl8QEWG8iVcK5EmlIKirhQovXmOR9jjTasMgsY91Fj5b\nKjIeSqXW85fLwkVM519zaP9zswUZwvLuLrkJ84fTK/dC9r9lzWQZ2871LnSa\n5Ss79fDtSCLY2T5RRDbVpI5R+i15ZOBnADrMXHbUFURFqw5YExhrjk6oAFoQ\n0YOqAKRYN2VsLx5PVJTkyG0K5Q4r3bmg7iooSxfw4kGQo2PMC3sHmD2oDjr5\niqn/1lWTp7oa/ZthZZNViH4HZ49Xoz2/gRtTtS5nVejDWP+jbZmvRN0KFnfH\n/Mork/sIL3sHU3c5yIbsw/jmlfuDLnmPAjxM1RQa2imXFznJtAheXztBuqlg\noeeuiARLFTPf1j+/0kM7hRZXAITjFLaMQ7/9xaArz8gB3JrNOfqwifCoyQcr\nZZS5/M8LbeI2OrLz4RAvSZZo6T0CVTIo9Ec0Fhfo6OeX8MvsA8EffQZRbZyQ\nhWqs\r\n=8m3+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"fa5c2c2020f3eab1fe6a236533759124209d34e0","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.20.2/node@v12.14.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.14.1","dependencies":{"css":"2.2.4","domutils":"2.0.0","domhandler":"3.0.0","htmlparser2":"4.0.0","@ampproject/toolbox-core":"^2.0.0-alpha.0","@ampproject/toolbox-script-csp":"^2.0.0-alpha.0","@ampproject/toolbox-runtime-version":"^2.0.0-alpha.0","@ampproject/toolbox-validator-rules":"^2.0.0-alpha.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.0.0-alpha.1_1579704264303_0.8725559194698791","host":"s3://npm-registry-packages"}},"2.0.0-alpha.2":{"name":"@ampproject/toolbox-optimizer","version":"2.0.0-alpha.2","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.0.0-alpha.2","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"sepandparhami","email":"sparhami@google.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"8162e76b26d4b47aaf3d07913eb82789fe335bac","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.0.0-alpha.2.tgz","fileCount":31,"integrity":"sha512-/XeiuYz4s0nHRGzrU4c8KOp0g64GobYZgPFcKVrSsfwqSL3gVv0iATmygxXwf+Ebw8jb9gc96xSVFj4lazvl+Q==","signatures":[{"sig":"MEQCIF9b5oqdEcCeJDEJIrUMc0UZHlhNU6ohB36/SXUpQXJgAiAnZNydSw5+bryQPL3pKRULeljp4eRBMeeYPR+GLLhnWw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":128749,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeKGKLCRA9TVsSAnZWagAAJGMQAIXMkJudZWRcFXp/jrze\nk0sosf+lLi3p6B9RLPOm9O60ZYbbHnVCGQKGUpZCthmEktYwQ7xPQg6zpGQP\nM6IC+vx1j9kFiCnqhbxZklmCmQ1ruooLriF/DH62kvK+7UvIWRfaVSYjTnYa\nL4PCU47ZBHKXoXN+hxG/GEhqAHP5fz4tF+V/6PUNbmuNVLa0DgRqvYds+vSm\nfO06/wqjHC3cxvb8Ps/QYIOJr+bHJ+dDOw9msdHxOBI45kN171tGKGhZUS4R\nCrnzv1rP/f0BaJ2HXYQbJraaq7uYFZTiZmSlWoQTHp2YqlCnk8bYPDts7bbX\nnR4LL9LtAk6LCy2TqJiTsws0NfDqyM9ZkYEAkbDlbpnI1yxfjxHW7q1NQfXC\nJpL5ZxBTUENfM/2JWgD9t4hbzBpS+NaB+W7+snlhCgMZrELJWOckHJF3i6aZ\nJwhDLEtrHeeKzgkBRbmIz43EWc3O3+3SYe3SrTS9VefTRY2nksdcXRyOLglu\nDk0go9AyPjE9HxIvVfbzNz3oa9BHcQBE3dCj+4SMmcT+DaB8JYUtZpL3VmKc\nL6zpRThclduNaVABjQi6Zf+oLLewo2OH/BgfZnhLrq/tUCF7r0t5A+Vmfohn\nI43BCepxE3y5me557MWo23FFFCBhFlxz/gB4EJjSGNHv4HmzvyfeTIGI5eBS\nFfBJ\r\n=fUbk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"fcd31172ef02fd2d4162a8fb7decd2eefa468873","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.20.2/node@v12.14.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.14.1","dependencies":{"css":"2.2.4","domutils":"2.0.0","domhandler":"3.0.0","htmlparser2":"4.0.0","@ampproject/toolbox-core":"^2.0.0-alpha.0","@ampproject/toolbox-script-csp":"^2.0.0-alpha.0","@ampproject/toolbox-runtime-version":"^2.0.0-alpha.0","@ampproject/toolbox-validator-rules":"^2.0.0-alpha.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.0.0-alpha.2_1579704971152_0.3015728938946114","host":"s3://npm-registry-packages"}},"2.0.0-alpha.3":{"name":"@ampproject/toolbox-optimizer","version":"2.0.0-alpha.3","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.0.0-alpha.3","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"sepandparhami","email":"sparhami@google.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"5536546a8eb7a5e1f522efd44897a426a15eab9d","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.0.0-alpha.3.tgz","fileCount":31,"integrity":"sha512-FZKpdxd234L3msWLzOk6p9B/gOig5XnWD3HfYiF7FhtjVTHIpaseAR2v7vRMOSP14JjF95mkSejrr1tgIe/oVw==","signatures":[{"sig":"MEQCIF8AowR94Zs4Yl244zizfaz2DRL4mDOHXUHZm5/Hwu63AiAKVuMRcka7SsOkXz95gP3XWVOCH1XrHLnsF2xvmVjbzA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":128857,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeKW7uCRA9TVsSAnZWagAAiNYP/2W6b3O19N2FAmruVhyT\nZ59toMn4BK9rTuQuYyDg8YUhS8gKb8V8mYB3kwi+iInka/CtneRuTU+0FNI5\nkgAf1so3HawmMfrjvDNaHSxB8HzrOrDE8B2e18P48cbDEbwBT0o7zb3NWfKC\nTbnFDI5ru8Ad835399ZYhmOT5O3TyDoKWlwIIBQRLuOgMnGy7uG7KABOHm6D\nmT5CbFjNDI70B4ZnlM81Uk/ZTVy4D22HyR1sICt2VfQJyVQiGDgDi3lD5BzJ\nKwEE8lOrYijU5ln7HYTWltBJDbWtI65eu5x6r3r4pcw3qG8wGDua/dFg+wSo\nIXZ1o1v7/nDrcqvLnNXd8GV9/7y4tvDVPiETmSiahKDXXlbnKw2h8O44SQVj\nodtyxCB7cUUstiaIjkELN6zgX3LmrWchPNiQE8qO4do/y7uhcIP0fAQBRGkW\nxL7a1gc6fv5ChYIeTUmfZ/sYKqyv+naqkJD+X6e7n6JHNd+x7GUjceY4sMI7\nvrErf9gY3xTP3Ogyb5uDu3NuLqWX8A71e7bIi1Ka/ljWNb9isZuORg3x5bvi\n0uW9UOUcSF3j9vojALrofmO2TXrpeN3OVXcejxyaO4qMtLgcARzTlVmkD3I/\nTtJl9Li+f+xAnG3TWTNXM8tDth4VufOMfNQGr80X6IRQtcHS2ARC07r7JTds\nS5GP\r\n=hzPd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"2a81fc3795a0a5354920f7c2855af9900eecacb6","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.20.2/node@v12.14.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.14.1","dependencies":{"css":"2.2.4","domutils":"2.0.0","domhandler":"3.0.0","htmlparser2":"4.0.0","@ampproject/toolbox-core":"^2.0.0-alpha.0","@ampproject/toolbox-script-csp":"^2.0.0-alpha.0","@ampproject/toolbox-runtime-version":"^2.0.0-alpha.0","@ampproject/toolbox-validator-rules":"^2.0.0-alpha.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.0.0-alpha.3_1579773677987_0.6791205113840242","host":"s3://npm-registry-packages"}},"2.0.0-alpha.4":{"name":"@ampproject/toolbox-optimizer","version":"2.0.0-alpha.4","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.0.0-alpha.4","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"c81b4706b6dcd2a6014332f24f3fafdb2fce430d","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.0.0-alpha.4.tgz","fileCount":32,"integrity":"sha512-CWiLwbBizZQtp51fHClGA1BQ3+b0XsF3o0hsboHdRsHwsbL8aemELmYvUx78trzU4p2C0y9puZNuZJBIAqcAsQ==","signatures":[{"sig":"MEUCIQD5r6bh3+/+g4G30RAcHSF1+sqFoaxRdtk95bVYVq8CsAIgALCBmiT/MI4TRdSohBvTFWUcYYIdqBQ08Hu3neccenE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":135451,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeMFKOCRA9TVsSAnZWagAAlkcP/2v0oyG0AlKIb2kP98UL\nxgWGRipEYArJ145zdvRruPyp8RQRwrS9VuB0kANk07rlD4IBjkGWFsEyl4Nq\n6rrGXp0v7qx/YOW7WTWI09yDqRKDsGIVc1lq271Nsewy1XF7X5FZlZXRlGPn\nDxj7y6V0x04/H75L0BCs01fA5IlW4EUZe3z4w3V0sfCMSOo47lhUl3yZhLPd\nyyZOQx7AywO+BkWYk06gqsaVVu3pPAcAbZxY0LixkGL0WmzqkKYbbxky9yKv\nvgDgOn+lARLIXO2jJL89rfPSVHLZmBiiP6VDfnXMMAEQy5c2CqmAVpHqlSph\n7LKG8sfcv2Fryu0AMVJNVED+ZLfQsvpMzFMadFtEhJJDOuOGYM5OF4rrNfEM\n/L2x3cRevdjcQVTubMah7Ur0Hf1d20jGfvNNcTwPEODzsScfOqsESpMjm0XV\nCpREAPoYqE7VpudviKWozDiuKqGt4NUjDjDeeXpiAyDVJgEPb7UuZVsUMCK8\nfjWiOtjJClCHL28C/8sOlvBEUT5LciJEls4zCXGUWGO60uwsFC90J2kZ+FC3\n2BF0Id1kxihItAlxEqcbx0JgM/qvDFjYBSJl7q78gqImyWbqIbWGDzn3bN+m\nMZjrUY44jzzFYkeo5RB6i3+KnE+bSt3rzsxaRIxuEywjR1xFlb4hmyy66Awb\nQg5i\r\n=RE1M\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"fe0d72e23b0d537c91fa78c465ca1ea99d3487ab","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.20.2/node@v12.14.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.14.1","dependencies":{"css":"2.2.4","terser":"4.6.3","domutils":"2.0.0","domhandler":"3.0.0","htmlparser2":"4.0.0","@ampproject/toolbox-core":"^2.0.0-alpha.4","@ampproject/toolbox-script-csp":"^2.0.0-alpha.0","@ampproject/toolbox-runtime-version":"^2.0.0-alpha.4","@ampproject/toolbox-validator-rules":"^2.0.0-alpha.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.0.0-alpha.4_1580225165825_0.4905262885622459","host":"s3://npm-registry-packages"}},"2.0.0-alpha.5":{"name":"@ampproject/toolbox-optimizer","version":"2.0.0-alpha.5","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.0.0-alpha.5","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"026540bfde20d3c90eb2fc1125c97caa9a8263c1","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.0.0-alpha.5.tgz","fileCount":32,"integrity":"sha512-yDBdKeGnb4/hKunFmvxZaBQJcJs8BghCT3GQJBG1iwACg5oYRd1wUJ1ga7zDVQnEDMXesinmfX3uYhyR3PxbBg==","signatures":[{"sig":"MEQCIHgJinOnlWOTR4OZ1Kzo5lL6JtDJlA35k8E1iXdWPjisAiAOiIzU1TqjhS6NJ8U7KeNXh4VrOXQ7N+R2CwAy7qBzcg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":135375,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeMK2uCRA9TVsSAnZWagAAHzYP/AwWFhN6qd4QljAaClEu\nq2Azv5hMmjYcyV7SIXqMPFBIzPPpQx+w8hp72UFVUUNmHnQsb/ISqxHpvFIX\n/Drysndraiu2AWkbTB9aDp9vgGrD/raC8KIKR+ab7jrCYolO4SKODGBek6PC\nFFglNb6mMqxPWr4qXnngZjLy4rso8YhQyrfwiYZWTKAy3QP9hPFw1Sjtq55s\nEV5pmhOApLiIXpMWQh9kOfgH4/eX7xa9/5RZnFrhBOaWsOHESL47TsrnLKN2\n9/rXP+aIQrw5A2T4a2+NlURDNvfCuGo+MaeeitUMeWQlA2hS/SXFK8EIv8RJ\nTZW28UqJ/OO4x8RwYApSutpkTF8Fd4yVwL+dta9r+RYtteNxtBzxdyuZZ51K\nL0QmUAYBbcFh0EPk+AVgwx416KUneeqihwrsKsOqzpXxA4Rc+/dcloNyMR9/\na6iPgT1qSZsTZ8dNW7uVcbqfWTUeEBuTp0PvdBOQYoLWKsWcTO991Hnwqtwv\nZ4Q6kDgiXtGOkg9Jc+xjLvp6hYy3/wnNBFmweh7HV96YUtP9ZDFzlTku48sH\nnuOsCpwCSZA7KgkDfX7szPiFGLI7x1jGGTeTX8bPzJQakQ2R1A2MD+bc5433\nba98R4fHlZCwN1q91D7ofX/HSiGT/jPzFWfbY8iBQtJIILdTNwFlZP8mjphG\nBch/\r\n=2yRs\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"91100dd67c4af2c28d83945c2fa4872cb3c103cd","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.20.2/node@v12.14.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.14.1","dependencies":{"css":"2.2.4","terser":"4.6.3","domutils":"2.0.0","domhandler":"3.0.0","htmlparser2":"4.0.0","@ampproject/toolbox-core":"^2.0.0-alpha.4","@ampproject/toolbox-script-csp":"^2.0.0-alpha.0","@ampproject/toolbox-runtime-version":"^2.0.0-alpha.4","@ampproject/toolbox-validator-rules":"^2.0.0-alpha.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.0.0-alpha.5_1580248493616_0.8924964986647845","host":"s3://npm-registry-packages"}},"2.0.0-alpha.6":{"name":"@ampproject/toolbox-optimizer","version":"2.0.0-alpha.6","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.0.0-alpha.6","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"ed1f7101652861364fa96d3e39776fc1447aec2e","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.0.0-alpha.6.tgz","fileCount":35,"integrity":"sha512-QW+aWvklNol8PqNzr9AlwsgJgQBHcQRQpfYCcxAlYmpjFYM0aJW4Dt5Vja2194oDKEImmP+gmLFlv4vUKXW9Rg==","signatures":[{"sig":"MEQCIEQn6uH7zjhHFB2PBaipMx/UBR1jBriyg6wSelU2MGR0AiA5atqAoZKY4V8ntQogsflwvnkeFKndlfGQHW9YWmj47g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":142983,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeNBxBCRA9TVsSAnZWagAA9VQP/2kAPAWvngIUrwV7U2nM\njtjouTIHNMaFiYau/sIjBkuC0tLXzKLfCjy/9nA/zoHY/BddiRCj9tP0lhwE\nm/XQ73/cPhtQrPt9VqGhRo/TT91boIw3cm2fnp/VKm+0lIiyk4z4/3qGo3pS\n1kpKdp5BupHW+AExMSdzmeNlX0WY8TlFcAkSbb4gNbDPHUAoNBd6FAA7Qy+q\n3w87Z+arQ5rDM9vPauhjUsWfEBs527si5hpCWuqsQqHKsF7f0qAe6cVTrUO6\ndaNbgejFOUmHiekcUuRA02j+azqZ8ii1y/KkTZLL9PKxe3s5j6fqEl6CHviM\nvMjUyaNNAN6BJaPAayvi7dyuBX1a3Mg+amUWeL4/cy59qVzZorMp72G/yJUV\nrd8sM3WcWDOAWazECtr5bEDWSE8V6ruX+jdfOsblX16yA6ilSRipetDgh3Kz\nEwLuaHY6Aq4hDl/IXbzfAdKCNikEm4tvbhzc2P0fKtXLz/rOy5T3j3YlrSY8\noijxwPhxPujhxa6vVNzvd2/s6yA4iH+Y7b+cW8jBMlu3TbGSGtA4EKJPfwl6\ntcU28dbgrojXv9gvc5RSxAivwKqFm5FkudlAoViH7rQlxnCIFtbg5ZlUc1p0\niU5V1lU3rqaQTAOquL/ONDAZ4OkEnL4dEtD/i6WMqsDpHwa+MJLNqUpJ7QBt\nRkD1\r\n=epby\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"9d8d41a7bd0fd93c11fe44fa2f84a4a0778fc830","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.20.2/node@v12.14.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.14.1","dependencies":{"css":"2.2.4","terser":"4.6.3","domutils":"2.0.0","domhandler":"3.0.0","htmlparser2":"4.0.0","@ampproject/toolbox-core":"^2.0.0-alpha.4","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.0.0-alpha.0","@ampproject/toolbox-runtime-version":"^2.0.0-alpha.4","@ampproject/toolbox-validator-rules":"^2.0.0-alpha.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.0.0-alpha.6_1580473408791_0.9914517947884338","host":"s3://npm-registry-packages"}},"2.0.0-alpha.7":{"name":"@ampproject/toolbox-optimizer","version":"2.0.0-alpha.7","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.0.0-alpha.7","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"7822bf56eefbcda1ee84ca2c1864c7a6032f2002","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.0.0-alpha.7.tgz","fileCount":35,"integrity":"sha512-FOZ3i+3ln0OOV3Ts4LQbjM7Vf9VBr4gPXQqMbjHqHLf0fLw9aMBTK0yeiGyJl0O+AEwW4OIPRKuWofMNO4AJow==","signatures":[{"sig":"MEUCIQDchiiBoJAGXmR3L0r7zoYy7jekabm3brDtziy6oaoPJAIgTUbH47TswstXXxXzyaOX5KO7KirlRnMq4i98wJQKiRc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":144901,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeRbwGCRA9TVsSAnZWagAAkzsP/jvQ43o5tFYPxwJVul9b\nxiXELb4o8lviIvaNZ2UoTAFEf1SGQ7bIZJM3Z0vFMmlBU0Um0b+8tr0bsHvU\n50xuP6rSrmydRp8RMa9PWQN1djEQzf5RNo4CA1I13hWapyhOdYetzRHZefGH\naIpZf8GOdrITkDQdAwreb1r6GZ/XTq6+uwldi0YePEDdGrYYyPANy50TdDou\nMoXF36RjUXt5orUH5cshPNBiZ8P5A8ZVqkYBgiVIK7BmqkBOTOuOj3LfQ7jN\nGVsRvLEYu4hWGDNrCjsEDfIGt6Jo4Ye3Pdo1kuYz72ntiI4Hj+l6eG+LdNGm\nD8KX5E8o0XOK2zkzc+Ak008lN+lTEMRMhb6N+DnTKx/Fkpo+jTAq4/ICaw5L\nLHaYTM9Pf+0CJiZWbA3fd7IwlwnmBvRwSVlb7ySnuF3pzj5r4Kz5OHYi75Df\nP/M+pTcogs29NPS84sHjAVHUC0AcW7D6Q5ontkaJz1xc5uC93OOwqaAzWNfj\n3HQiam3KGZJXmcG3GAG8Uz1OdsZMHXz27bctJSLiNNs5//6SVI7EJGileAZq\ndrPktQE94OnzTM2nTCwWdVRWkYVvFpDzwb0R72G3Qgmije79i/F/oBwUhLMX\nDx6wABvYRkGgYikRmjF8adXV3cDVlxq1xIuyW40f0H1s8QeR1iWK3oJvrHUS\nJvQH\r\n=XofS\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"2e6b60d382d205f884cd78ce4d41fd702121145d","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.20.2/node@v12.14.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.14.1","dependencies":{"css":"2.2.4","terser":"4.6.3","domutils":"2.0.0","domhandler":"3.0.0","htmlparser2":"4.0.0","@ampproject/toolbox-core":"^2.0.0-alpha.4","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.0.0-alpha.0","@ampproject/toolbox-runtime-version":"^2.0.0-alpha.7","@ampproject/toolbox-validator-rules":"^2.0.0-alpha.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.0.0-alpha.7_1581628422186_0.0367040704209427","host":"s3://npm-registry-packages"}},"2.0.0":{"name":"@ampproject/toolbox-optimizer","version":"2.0.0","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.0.0","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"96ea3d26bc7a9f19718a27c721e021a121003601","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.0.0.tgz","fileCount":35,"integrity":"sha512-ZYRi7vB4ALC8DnTHQLchAeHMGsFml/Zr4fNWBlTiMGfvWGL0XTV9YyP4s24IDwAlunEgynmz0FTrGMJdRpNf2Q==","signatures":[{"sig":"MEYCIQD4C9W/H8FUWOri08dpbxlj5NSAJUhfMgnWDEWBjjxKvgIhAIUpOfKoYpIEKddgpkY7U1TUhG3RBtoR+2bBacBJ9DjR","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":144861,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeRb6fCRA9TVsSAnZWagAAuA8QAJUFH2GXId4KpzKU9jw8\nxx5GBov6tacTd2rDTtmE1omfbYOdpRBLJX2ObppyaAZsCM9FGdzmc9F5jjUX\n45CfG2HM2qr61VdM5sZzLrvnbyJT/N3hjvAFwXE4uGpsq1Plsi/DytXmttvW\nWUI5NJE0eKBbsV3T+8WrdlBJLoZeJNpg/ImfY9Yfk77F5e6Z1DpM7lfrdHSr\nqDowkBeGeRvCrAqn9b99BGEsAbfx4NAMgs2AxWeWN8SYzo1/pBiuJ34qcDez\n+oPIwVxJceY4X2ZjluUwTSKDOQEElL+RbpJro2hW3R3HEO4m7YTLnOCsYqfH\n2Z8hI15R+N/43CaphUHnrocdMcmAa2z1z17ETkfQgGy2VhiqPdvhMNxH0lR/\nmwtUBcQJ0ynyGO+Ev2tDYppvTLJaedBogslDJQwAlHcnB9ymD3nYzC/6RL71\nimJRtuZnRNoNFs6K0Uht+Ks8fZeCGyF6KjTDMKYGW/Qq4mgE8EKUKDOgs6MY\n4Mq1GXvxszkx1jChNC+lzk5XiLIZLM+ZvdgY6lfNZp7F/CHV26N/mxnb4evY\nAwVwQRATR4OGCdyweQFceOnT7p/hMfNs0OAuzUixVXOH5asILEzuu7aKQXVE\ng5gyxORrV274ZMkyGX0VaXdWhbjqKaN+PzRxKdCPaApZeHGEKX/EEvnXrE4/\nsWMn\r\n=iXle\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"908b78b9d007d18d97c50b5a79421d5936150233","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.20.2/node@v12.14.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.14.1","dependencies":{"css":"2.2.4","terser":"4.6.3","domutils":"2.0.0","domhandler":"3.0.0","htmlparser2":"4.0.0","@ampproject/toolbox-core":"^2.0.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.0.0","@ampproject/toolbox-runtime-version":"^2.0.0","@ampproject/toolbox-validator-rules":"^2.0.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.0.0_1581629087325_0.9395198695522087","host":"s3://npm-registry-packages"}},"2.0.1":{"name":"@ampproject/toolbox-optimizer","version":"2.0.1","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.0.1","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"943681faf24443044aa66f0b55eefb13cdcc068c","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.0.1.tgz","fileCount":36,"integrity":"sha512-zroXqrV7mY77+/6hV7kaaWxp4LA85V0B/2vg7WdF+FrwiO9Wior/lIW8UbpRek6INjw0VOp1ED73MmGJkwaDhA==","signatures":[{"sig":"MEUCIDumoRp1GAgFf5qiNZ9ADfFogX9PeM/wuiMY05HTsRjnAiEAsaxSHYxBWLnVrv9uZ/7iGiLzF7DI4UtAa4VvWZYCV74=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":147096,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJecgrDCRA9TVsSAnZWagAAGAoP/1utrkqB22xcSBHN+FPn\nMEnksdwvEX9953cHDC5gkvfI6QFiRTvAGQVlXjpYJQ+/5DlnCS3c6+bwhc8G\nqoWhVh3jhVTE284DOMYo7GNFq6cOjaEX7WrKH2tERUQs/s5LYpjyR6xxLQjX\ncq65JVQ2nVezLhdu7dBtd0zcp9d2uJZAf1s34sNzB0VtjEDTTmqWm5APtFEl\nadlFyGCWF0cc7is/6LcqwyBsK4pWbvW/4JrMxRgLdSvCpLp21YE/JoWmwjIq\ntTsZtHYFv88WgwUOu7T2H93+jFkxAt8f9NK5mnCFQPZ8tJrp1cPGxIKeO6oA\neZ0tkdOeK4aHBHW+dG8oAkorxI+Aol8VtRcPL/iOMt5mt5KOG3ifkzy0YxSj\nXTjGjBR8Cf8YVFtPgw3fwWuocbbJdirLsReJI18yze/a2f61tP4WxltReinj\n3U3hgkzrN0tZHd2A84Y+gvdJHgCczTdd2MS65SH8ICTzDH3LcOc1TY5zzUUh\n/auRxtBZZG2HF4KZnADQH8/8aaNDDGPBZML1QiCO4WCc/2iI4z23lzUXAGa5\nIZXvNAtpxd0Q3Vxr81pocIEP6ZbVZ+SCOyG4RWieJGzWBitDXJUz3HiUoM/G\nqhnyMfJsNitfMiA3xd8ZFoDpT451VTMRNkK+uexojN52YzbmDoojxOC6tyoY\nw59v\r\n=fQia\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"2785e09433bb22977b61f153ed7694b68ca5151d","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.20.2/node@v12.14.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.14.1","dependencies":{"css":"2.2.4","terser":"4.6.7","domutils":"2.0.0","domhandler":"3.0.0","htmlparser2":"4.1.0","@ampproject/toolbox-core":"^2.0.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.0.0","@ampproject/toolbox-runtime-version":"^2.0.0","@ampproject/toolbox-validator-rules":"^2.0.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.0.1_1584532162820_0.6631049756188139","host":"s3://npm-registry-packages"}},"2.1.0":{"name":"@ampproject/toolbox-optimizer","version":"2.1.0","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.1.0","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"dab310c9a541cf740efa015a2a62138b169f931c","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.1.0.tgz","fileCount":36,"integrity":"sha512-2bNgyhlYm3AX+717w+qfC6b9sKecaYrzFBBf8LZJ235v95C9bYLFBf0kJHayDWYOxYi2gIQEmUgESThirI60Hg==","signatures":[{"sig":"MEUCIQCb8NJN4d9S7ZBjVPgblX22P1LeUR0VuxrMNkPSj/0dJgIgbS0t1WhAxCHqpZXTkRuW68FI1nDMllRgElL5N6R/Zz0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":148912,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJehFgLCRA9TVsSAnZWagAAK74P+wUOSi/dZX0m7vES+r+k\nAdZno4vi3AG43kOFRtEmtZLHN8kWsQ8gbYj11aeW7wyygKPYbwN0bfrEUIXi\nGcbbjmLrGy6Zu7gYG4BTqoqeVbSkR0q0M/3kzLCj09y7veINMJLbXiIsgEwk\nZGlJAreCqiyS0ykvSvXnuPCwcxRr9dzQHn8LkcGVl/nLu6qJSlNCvN1HcJOa\nHM8jXC4S3Yuv9wi2drvGfTqXmwljXpEoDaX29KCse82cd1zqLvycZzxznc/P\n6fKT8zIqjh9Lp6F/4YPvEELOLrWq4QMCgTUxe5vrin6RHczdc5+OVYbklT6J\nt+9L/uDlMLTEAJsZ28589WY0Ou0WQlQS+nPWsFsBwmmAgAOnROmFCHO7SyW9\nIXPLKr0JNUuAq4NWy8ePo+16wN+fGzg0lcjMMc6tSvkWK1g0iJMsXCnhrgdU\ne1A8/k9Ztar07sC/YIdQ5ilQa17VUoevhsZfNswn6DM1eIPrn2q0IhBUOSR7\nWhsroah9/9M+tFYnUmrWsnrWORuWLbRxV/IyY2Zgo4+zTBryQUOOw/yKFxn4\nK0aSJCpami6AK5dM4tJnGNdhpI4CsGfYm0PmY2NhidTl5JKqJSGW3It80RqM\nxp9Ur5wnAgtTy06zJ6RzwaTTQKi82Lt5hytyJ8WTr1JlA2iizgjEGgBQHgLE\n0930\r\n=BQNn\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"a6bc962a0af397c2fc21de1e8a730257947d8fb6","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.20.2/node@v12.14.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.14.1","dependencies":{"css":"2.2.4","terser":"4.6.10","domutils":"2.0.0","domhandler":"3.0.0","htmlparser2":"4.1.0","@ampproject/toolbox-core":"^2.1.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.1.0","@ampproject/toolbox-runtime-version":"^2.1.0","@ampproject/toolbox-validator-rules":"^2.1.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.1.0_1585731594753_0.23213598095773125","host":"s3://npm-registry-packages"}},"2.2.0":{"name":"@ampproject/toolbox-optimizer","version":"2.2.0","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.2.0","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"2438d7102beb1a74bae8d20926e038c5f724a8ae","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.2.0.tgz","fileCount":36,"integrity":"sha512-lEujArv6jyl/mEab0uBZ25oMkf+kf8cpTuHPcy8k3+jtomNyVtd94lbSWbQtomsEnYQ0MA9MvLvCJXsJz1fQcg==","signatures":[{"sig":"MEQCIAfbol/5QQPzjooKDxS5w97O1hlh2wZ2JvYwtgl5tlq5AiBMLUf9DPLu8VmLnjf3NwrX1OrVJSGSgFdhpQC2kLvxRw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":149264,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJehvKLCRA9TVsSAnZWagAA0BMQAItimLX5EhN/1FnwvT8h\nJ3PvDtlE9CJ253FhjwHyTOdQB/7YS72Xtca8s4njXTkiSoyc7b9Bovg0LUaX\ntPBmW+v0ygF0bWeDKotgBPe/qKAvHGDwOMl12h8eszFGe5hd2NLS0zD+QuNs\n6OxD8ja/46xspCKe5sAquz/SdBy4wZTdXBXc24zns31SP9ijF7DmrZBqSYJl\nfH9wXRoI+5y+0ax+wuEs6HRePOZJMnNfjcLbD9UFeeTUuyOSeoaKbgg4+VbB\nrZPTgkDybIvI2lvcbJe/2cnJvdouH7Q6yVeOVvFKHudq1yHB4AW0o+ytFzhD\nMWvXNmwYzvzWoEwM7JTbjJQFG4+tsBL57c08sI6db3A+GpGOlR59jow+wP95\nsqIDTd3S5ymmXfWpFa00kRxxrNZZ2iOlrF3jT2DoojAULdI3+77eElca7zhf\nY9qW/Hh4SNK9U/eV0bX3l5C7ieMaDVX/bNrCNDgBGZw9zyRl5vnHeMil82mh\nlFoCHYpjtuDyrH35LU4y83yn3F2xBcfJKIqbG25Uz33nEp2ZUekf2Y7tTZ/I\n4Xam3U25DZWxz6gbooOOY3NORzAPL2uAOP1O6UwPwgUt9EAiSn3y7jdG5SGs\nS/ueI38VAbA5Y11mc7X5qK1LVZUxL5XONplBXBgJGzu0d0Xd5MYcZF6VlRkO\nlviY\r\n=k8t7\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"e2bea7ac7d4cb6a57d196e124ee8a5f818123a02","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.20.2/node@v12.14.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.14.1","dependencies":{"terser":"4.6.8","cssnano":"4.1.10","domutils":"2.0.0","domhandler":"3.0.0","htmlparser2":"4.1.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.2.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.2.0","@ampproject/toolbox-runtime-version":"^2.2.0","@ampproject/toolbox-validator-rules":"^2.2.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.2.0_1585902219257_0.3439927126075657","host":"s3://npm-registry-packages"}},"2.3.0":{"name":"@ampproject/toolbox-optimizer","version":"2.3.0","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.3.0","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"504341aa75f81b42d17406d323a71b961e5fa062","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.3.0.tgz","fileCount":36,"integrity":"sha512-g2I7qNK6G1UWGFgw7BaHy9fUW8866vyieZeZbiKyuVNvxandxgrB0P8ujhSDC0XbKpBSi5F5Ei+GNRdO3PzU9g==","signatures":[{"sig":"MEUCIQDBYgQYB7E9Ai/FVEGsYnumyZ42EsxSDo91j7bUs2N26wIge1kQweQ1M7VysLuLRiv6QjSXNu+3TLkRWzf8a6RNNQM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":149295,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeltSBCRA9TVsSAnZWagAAuAIP/1pX5qPu4U6dgzL9iFgt\nZ+Euje54cPjzdKKKeGnn+Xnn2cI2GMOXZdXffE0jX1oQfJMVrOpurjBsSaSu\nCkTko8ZT1ygKbp4E3/eatc2/Vu+7HICzkSfoxHhvTGXb8Ws+xEECU1Hm+DAK\na0rImIGwABwV3kbSL5nusoJI2Njlf/w0TkCwcFm9FAINC5xArSjL3UNvsiJG\nC6lvEoZ+sQmjq1JTL09pfzhx42nqjvjcBiWMIGn1uZCGD93X6sTSEBKPAwn7\n8Fdxl0jAmzeQL5IQrN2akWqi2ZM1iTpHCJuJfqeugSk/ApUxVnue3nRLvYS8\nL5uyv0FdSRxD6yzGXYxzIX3yyA7fTcOkNj9X82k+bi5J68m7u1d66y9deLVs\nfQny1ViGstmuWabsZd4c/8q2LBGcDckt94huW7fZi1jiaSPYv1zZofteuZb3\n+Sr7V4felhpnLA9I17tK51gOWvfenjxSCkazM4V6JcLSck6i8I/QEjs/U/WK\nm+BF1UYpGPXN7k27g7x/Kio535L8v+YY8ips/UOFp+ag08cix897NRGfHqiB\ndnsuESZHzfuTsGJ8Kne/o9t8t79tmbaHPuaG1EyGsT65xdYMeKJyASF44/kz\ny47g92XUQzT9uOOFvXHtRleXfLo+HG4CU86eVcFMECVn/Mi8j9tjqTagecuL\nl/uN\r\n=NGHq\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"bb7eaa6c720044e84f01c6406f5f0805dc637923","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.20.2/node@v13.12.0+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"13.12.0","dependencies":{"terser":"4.6.11","cssnano":"4.1.10","domutils":"2.0.0","domhandler":"3.0.0","htmlparser2":"4.1.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.3.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.3.0","@ampproject/toolbox-runtime-version":"^2.3.0","@ampproject/toolbox-validator-rules":"^2.3.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.3.0_1586943105316_0.2647953171244588","host":"s3://npm-registry-packages"}},"2.3.1":{"name":"@ampproject/toolbox-optimizer","version":"2.3.1","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.3.1","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"ad31d2bbeef8cf863b055bd9a520337f796a957d","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.3.1.tgz","fileCount":40,"integrity":"sha512-614CKgpHJfVVnns1kHMuXd8uTvE0bhgIEyhnBqQ06Af1s91bKDUKDksGFVd/drGhEYmyX3Y8pizoZgw2tiQtSQ==","signatures":[{"sig":"MEUCIQDgMmnQKyuJifOezCCWA7scQ1qHb89I7LsjrkGgIZPJTQIgNkGavnOJofzip2pI7FmmiHiVt8z3EJgMBuQJyQIaghU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":175698,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJescTVCRA9TVsSAnZWagAAr90P/j6c4qdJcXyL5CHUJx4/\n65n8uHcU6PNOnyptuMdmZ1gxIWz1pzixw0Dc/RCSxRVgIjep2LewEBM/xCDN\nzmIGzi9I5/yi7XGRGieQBaQ699h9eSvBIxYxK3zqutlw5jRg+l76AfpFN9FN\nmMIkoFAeNBuzQf3pgAadvh7nt5BntCYhf5w4Q7hAfxDahlsMpKqCKTY0K3tP\nW9DyjMCFlUrzW8c2bEDNUMVHEz4oOR5CdEMaeImKUe4DzRIgNJZrpV15uMH2\nPolqSwjZ3rUcf1qn1oIJ/EzhavKnzh4cw1dSWrhTzZAhfD3qvRSHJxMrDuwW\nzBZJiutWg/dbRFjrdYIg2e6KjyI5b9e3zBh1R/zlwp1vkgvhOnkQwUA7bB+5\n3rRlI82aTck+/pTJl+s7NfZPa4DCbrOP7jAadb3JJ2ohx+mOSIHXfcdEtRGF\nWGXG2HNbY4xL1oj+S0kK9+65RNx8cDakiaAltD286kwOulJJhym5uLGL0wVh\nZX/k4tmIi/BB//o2yIjFtL1CeB7DYftGENcFjUP4l2uBgNKpnzfQ+R5ZgS51\nsLzIo6EKfuVdQFNcyOtO+YpXj1u7OM9+eKrXPuMubGc2q8JRx3FA1sBKk0t1\nhWS+L5KUQF5510ZdYLuS6cCQHWB7NDx4uc10e4XOIEd/AR3rKEmGUQ/71+OJ\nmX0r\r\n=DhMo\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"2396773adfc982c044cf932dfef73b6955cb005b","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.20.2/node@v12.16.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.16.1","dependencies":{"terser":"4.6.13","cssnano":"4.1.10","domutils":"2.0.0","domhandler":"3.0.0","htmlparser2":"4.1.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.3.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.3.0","@ampproject/toolbox-runtime-version":"^2.3.1","@ampproject/toolbox-validator-rules":"^2.3.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"lru-cache":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.3.1_1588708565412_0.5594433476588949","host":"s3://npm-registry-packages"}},"2.4.0-alpha.0":{"name":"@ampproject/toolbox-optimizer","version":"2.4.0-alpha.0","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.4.0-alpha.0","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"d5658da9c6164ba126dd03babdcc18c953ef174e","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.4.0-alpha.0.tgz","fileCount":42,"integrity":"sha512-BEKEaW6OML+M8O+VMAWHYeQwny/kuuNEnSr3K9oARsI6/Ak2y0tPK/arEOdTOkKwXX29AdtDNtePNm1PaJ3kig==","signatures":[{"sig":"MEUCIGX6LxS5hf3gusJAA2AJVTROpRu3RhDhTzT0HtHnh4w2AiEAolmYTVR47Bbe4Ve6HX28K/NfoUR3M4FSOqZNsICZJN0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":180649,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJesxThCRA9TVsSAnZWagAAbT4P/1rnlESkfyfezgY6eDun\nSVCesaSffx+l3B9Hlh6uCUPLGPSzSrdBd5zu2UJ7rBbreUbstNIyVhm7m6Jw\n41x+TwDa3zmtBsTWIhdN+Pl4rSoh8Z26sNwYobLd+DxHKnlrVJINcbcBjuhl\nm7puHZIku7kE9FBdsGddT0CMseFa7NWjW+bBykXsuoTsdx/iO/URawVxUxgc\n8wt2K5Wnqkz0GFQ+DihYIgj/7SjcVcKYgc7SVcxvzbH07KNncKdrGxtPALP8\nmKPnNNQAPpdEx2j41p6Wdz58IFvCSzV8bUoOkES/pehmS4flp5leMwE6I5Z0\nqNygciUeA8E7eTDU6Qf/CpDIeKlCKGo28QXEiwAwyyrMpYx5QHUw6Q6hm9fB\n9gV67Efdwrp2ikig5Z/FTDryG1PIWnkZBF1V5TSzMFxU4+FutY2P+9v31ofG\nrjTBOab8CP/VZYShahA6Ld3BMvqoW0XwFKL5GnQ0CTbbqSu5N1Mw8rB5TJsT\nQgropZQ8DT7wQXbQJDAC+zGFq2jFw6qNq57i5tPbOiMf/s6uc4j7uxtHAe2/\ndmRRdSr3cl+ArRVlx4l3CzmmZFi5ExuvQdIbk7f0udRo4bgFMumqadnbGvCt\nioFPsf67hA4CIz1EM+E5N2IJ9JD+rKxUYNc3pY99srTixP7xV0YajeQN7n3p\nzJM+\r\n=OOrm\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to simplify creating AMP pages and improve AMP rendering performance. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n* Server-side render AMP layouts.\n* **Automatically import all missing AMP component scripts**.\n* **Automaticallly add any missing mandatory AMP tags**.\n* Remove the AMP boilerplate (when possible).\n* Remove not needed whitespace.\n* Extract and move CSS keyframe animations to the bottom of the page.\n* Optimize AMP framework and custom font loading\n* Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n* AMP Optimizer will produce valid AMP.\n* AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>`;\n\nampOptimizer.transformHtml(originalHtml).then((optimizedHtml) => {\n  console.log(optimizedHtml);\n});\n```\n\nYou can find a sample implementation [here](/packages/optimizer/demo/simple). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](/packages/optimizer-express).\n\n### Incomplete markup\n\nIt's possible to pass incomplete documents and AMP Optimizer will add any \nmissing tags and extension imports required by a valid AMP document. \n\n```\nconst originalHtml = `\n  <h1>Hello World!</h1>\n  <amp-twitter width=\"375\" \n               height=\"472\" \n               layout=\"responsive\" \n               data-tweetid=\"1182321926473162752\">\n  </amp-twitter>\n`;\n\n// you can pass the canonical URL, default is `.`\nconst opts = {\n  canonical: '/example.html'\n}\nampOptimizer.transformHtml(originalHtml, params).then((optimizedHtml) => {\n  // optimizedHtml will be a valid AMP document\n  console.log(optimizedHtml);\n});\n```\n\n### Markup support\n\nAMP Optimizer supports converting Markdown to AMPHTML. A typical conversion flow would be:\n\n```\nREADME.md => HTML => AMP Optimizer => valid AMP\n```\n\nThe AMP Optimizer converts `<img>` tags into `<amp-img>` or `<amp-anim>` tags when in Markdown mode. Enable Markdown mode via `markdown : true`. AMP Optimizer will try to resolve image dimensions from the actual files. Images wider than 320px will automatically get an `intrinsic` layout.\n\nAll other Markdown features are already supported by AMP.\n\nYou can pass an additional option `imageBasePath` to specify a base path used to resolve an image during build, this can be a file system path or URL prefix.\n\n**Important:** for image size detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n```\nnpm install probe-image-size --save-dev\n```\n\nExample:\n\n```\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst md = require('markdown-it')({\n  // don't sanitize html if you want to support AMP components in Markdown\n  html: true,\n});\n\n// enable markdown mode\nconst ampOptimizer = AmpOptimizer.create({\n  markdown: true,\n});\n\nconst markdown = `\n# Markdown 🤯\n\nHere is an image declared in Markdown syntax: \n\n![A random image](https://unsplash.it/800/600).\n\nYou can directly declare AMP components:\n\n<amp-twitter width=\"375\" \n             height=\"472\" \n             layout=\"responsive\" \n             data-tweetid=\"1182321926473162752\">\n</amp-twitter>\n\nAny missing extensions will be automatically imported.\n`;\n\nconst html = md.render(markdown);\n\nconst amphtml = await ampOptimizer.transformHtml(html, {\n  canonical: filePath,\n});\n```\n\nYou can find a working sample [here](/packages/optimizer/demo/markdown).\n\n### Custom transformations\n\nAMP Optimizer supports custom HTML transformations:\n\n```\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst {createElement, firstChildByTag, appendChild} = AmpOptimizer.NodeUtils;\n\nclass CustomTransformer {\n  constructor(config) {\n    this.log_ = config.log.tag('CUSTOM');\n  }\n  transform(tree, params) {\n    this.log_.info('Running custom transformation for ', params.filePath);\n    const html = firstChildByTag(tree, 'html');\n    if (!html) return;\n    const head = firstChildByTag(html, 'head');\n    if (!head) return;\n    const desc = createElement('meta', {\n      name: 'description',\n      content: 'this is just a demo',\n    });\n    appendChild(head, desc);\n  }\n}\n\n// it's best to run custom transformers first\nconst customTransformations = [CustomTransformer, ...AmpOptimizer.TRANSFORMATIONS_AMP_FIRST];\n\n// pass custom transformers when creating the optimizer\nconst optimizer = AmpOptimizer.create({\n  transformations: customTransformations,\n});\n// you can add custom parameters on a per document basis\nconst transformedHtml = await optimizer.transformHtml(html, {\n  filePath,\n});\n```\n\nCheckout [the samples](/packages/optimizer/demo/simple/index.js) to learn how to customize AMP Optimizer.\n\n### CLI\n\nThere's also a [command line version](/packages/cli/README.md) available:\n\n```shell\n$ npx @ampproject/toolbox-cli optimize myFile.html\n```\n\n## Why doesn't my AMP page render faster?\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n* if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n* if an AMP component uses the `media`, `sizes` or `heights` attribute ([documentation](https://amp.dev/documentation/guides-and-tutorials/learn/common_attributes/?format=websites#heights)). A simple workaround is to replace the `media`, `sizes` or `heights` attributes with normal CSS media queries.\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true\n} );\n```\n\n... or for individual pages:\n\n```\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true\n})\n```\n\n## Best Practices\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length)\n    .replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](/packages/runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n* `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n* `imageBasePath`: specifies a base path used to resolve an image during build.\n* `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n* `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```\nconst optimizer = AmpOptimizer.create({\n  // blurry image placeholders are currently not considered valid AMP\n  // hence it's recommended to setup paired AMP mode when enabling this feature.\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp'\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Fallback API for amp-geo\n\nIdeally, when self-hosting the AMP framework, `amp-geo-0.1.js` should be patched at delivery time to replace `{{AMP_ISO_COUNTRY_HOTPATCH}}` with the ISO 3166-1 alpha-2 country code where the request originated ([reference](https://github.com/ampproject/amphtml/blob/master/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem)). If your host does not have this capability, you can instead rely on a web API to return the country at runtime. The web API must be secure (HTTPS), adhere to [AMP CORS guidelines](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests/), and return JSON in the following format:\n\n```\n{\"country\": \"de\"}\n```\n\nwhere in this example, `de` is the ISO 3166-1 alpha-2 country code for Germany.\n\nExample:\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  // this will instruct amp-geo to fetch the user's country from an API\n  // which returns JSON in format: {\"country\": \"de\"}\n  geoApiUrl: 'https://example.com/geo'\n});\n\nconsole.log(optimizedHtml);\n```\n\n## Development & Testing\n\nAMP Optimizer uses a snapshot based testing approach. To execute the tests, run in the project root:\n\n```\n$ npm run test:node\n```\n\nTransformer tests are located in:\n\n```\n- spec/transformers/valid/TransformerName/test-name/\n    expected_output.html\n    input.html\n```\n\nThe transformation input is defined in `input.html`, whereas `expected_output.html` contains the expected\noutcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing \na transformer implementation, run: \n\n```\n$ npm run test:optimizer:snapshot\n```\n\nto store a new snapshot version in `expected_output.html`. \n\n\n","gitHead":"aeceb5cb83cc111c08297415ab469696f2f7994e","scripts":{"postinstall":"scripts/init.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.20.2/node@v12.16.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.16.1","dependencies":{"terser":"4.6.13","cssnano":"4.1.10","domutils":"2.0.0","lru-cache":"5.1.1","domhandler":"3.0.0","htmlparser2":"4.1.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.4.0-alpha.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.3.0","@ampproject/toolbox-runtime-version":"^2.4.0-alpha.0","@ampproject/toolbox-validator-rules":"^2.3.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.4.0-alpha.0_1588794592977_0.7578094815497312","host":"s3://npm-registry-packages"}},"2.4.0-alpha.1":{"name":"@ampproject/toolbox-optimizer","version":"2.4.0-alpha.1","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.4.0-alpha.1","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"5ddcb1ebd9034a67f82d75b35ff409cc9fd6107a","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.4.0-alpha.1.tgz","fileCount":42,"integrity":"sha512-yKdoigOo0jFFRxxu7Ju5vh+eHjowE0rOpc7FhTpb/q3xXxjH6vEPFIKimGmEqgjeAuomcoOzp60YwJRe01jZyw==","signatures":[{"sig":"MEUCIQCn023sGOnQikJEEzXaE/z8J6JbdMoU7AxKL8+GLWZ9mgIgSK+pNNP3evxDWNVjtCU++AQxmVQHrYwfJ/nk37nYjt4=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":180673,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJes+wTCRA9TVsSAnZWagAA0WIP/RM/4NhgY58K9TRthsv9\ny4NWELIwwjdUtyVybraxOOXu79o151CW3vfQ/+Pcmao1qmt39qNdR0xpM0v4\nKBdvrbIkm8Cl2xqqQSV304li06xEzAEnMTjFmCAr4cv6I2sgdtW1wK6ULtJI\nz3f3ugD2/JwvDNmqv4htJmty1FNyU0U0MJIxWePs0n2BNosiyNCTzP1t3TZh\nqa6usMSE4CNzTqAhfP2qLOgInEvf6PnLT2sJYruOhbWd5mCDpMu3624xh5FG\nRQw4uDqbPErRz76kYTLIfvB9DuAJwkEdBkFsL5M6nSwqLIpRtkGWggU4ZS8t\ntNDi+SciqKfPYRUhiOMvsSN3qudpdHcxUCXsqdOkOEuiaZyKdW/PH6KSu6cu\nKNzE+SsdfzZoylUZ+MeRCzowdxbzVWYHvB4n1DXgyTVMFKCERJolOBcacV9l\nImNMZXRCYLaijsrR41MOU5MNPMKnFqqysCGnw6utcZOp4bgFMPQ3fduPrb3s\nOdkSGDRQkbxGzGb+QbxNahfM8xXm1eY8sGki1nOdoqjt0VvgsEOe7rUOmkh4\nJo/NW+crbZWyZFa5jTi7cKQxRl3itFjyJImJ2fOgsnpA0mBOd/lJRAHLorDY\ndhez44+MUIrR9DV2qGlMTbrS9obKcaDZKTqfYzqpnEj6wBoG+w7YBsEExKGZ\nBpSD\r\n=UQ8H\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to simplify creating AMP pages and improve AMP rendering performance. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n* Server-side render AMP layouts.\n* **Automatically import all missing AMP component scripts**.\n* **Automaticallly add any missing mandatory AMP tags**.\n* Remove the AMP boilerplate (when possible).\n* Remove not needed whitespace.\n* Extract and move CSS keyframe animations to the bottom of the page.\n* Optimize AMP framework and custom font loading\n* Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n* AMP Optimizer will produce valid AMP.\n* AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>`;\n\nampOptimizer.transformHtml(originalHtml).then((optimizedHtml) => {\n  console.log(optimizedHtml);\n});\n```\n\nYou can find a sample implementation [here](/packages/optimizer/demo/simple). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](/packages/optimizer-express).\n\n### Incomplete markup\n\nIt's possible to pass incomplete documents and AMP Optimizer will add any \nmissing tags and extension imports required by a valid AMP document. \n\n```\nconst originalHtml = `\n  <h1>Hello World!</h1>\n  <amp-twitter width=\"375\" \n               height=\"472\" \n               layout=\"responsive\" \n               data-tweetid=\"1182321926473162752\">\n  </amp-twitter>\n`;\n\n// you can pass the canonical URL, default is `.`\nconst opts = {\n  canonical: '/example.html'\n}\nampOptimizer.transformHtml(originalHtml, params).then((optimizedHtml) => {\n  // optimizedHtml will be a valid AMP document\n  console.log(optimizedHtml);\n});\n```\n\n### Markup support\n\nAMP Optimizer supports converting Markdown to AMPHTML. A typical conversion flow would be:\n\n```\nREADME.md => HTML => AMP Optimizer => valid AMP\n```\n\nThe AMP Optimizer converts `<img>` tags into `<amp-img>` or `<amp-anim>` tags when in Markdown mode. Enable Markdown mode via `markdown : true`. AMP Optimizer will try to resolve image dimensions from the actual files. Images wider than 320px will automatically get an `intrinsic` layout.\n\nAll other Markdown features are already supported by AMP.\n\nYou can pass an additional option `imageBasePath` to specify a base path used to resolve an image during build, this can be a file system path or URL prefix.\n\n**Important:** for image size detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n```\nnpm install probe-image-size --save-dev\n```\n\nExample:\n\n```\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst md = require('markdown-it')({\n  // don't sanitize html if you want to support AMP components in Markdown\n  html: true,\n});\n\n// enable markdown mode\nconst ampOptimizer = AmpOptimizer.create({\n  markdown: true,\n});\n\nconst markdown = `\n# Markdown 🤯\n\nHere is an image declared in Markdown syntax: \n\n![A random image](https://unsplash.it/800/600).\n\nYou can directly declare AMP components:\n\n<amp-twitter width=\"375\" \n             height=\"472\" \n             layout=\"responsive\" \n             data-tweetid=\"1182321926473162752\">\n</amp-twitter>\n\nAny missing extensions will be automatically imported.\n`;\n\nconst html = md.render(markdown);\n\nconst amphtml = await ampOptimizer.transformHtml(html, {\n  canonical: filePath,\n});\n```\n\nYou can find a working sample [here](/packages/optimizer/demo/markdown).\n\n### Custom transformations\n\nAMP Optimizer supports custom HTML transformations:\n\n```\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst {createElement, firstChildByTag, appendChild} = AmpOptimizer.NodeUtils;\n\nclass CustomTransformer {\n  constructor(config) {\n    this.log_ = config.log.tag('CUSTOM');\n  }\n  transform(tree, params) {\n    this.log_.info('Running custom transformation for ', params.filePath);\n    const html = firstChildByTag(tree, 'html');\n    if (!html) return;\n    const head = firstChildByTag(html, 'head');\n    if (!head) return;\n    const desc = createElement('meta', {\n      name: 'description',\n      content: 'this is just a demo',\n    });\n    appendChild(head, desc);\n  }\n}\n\n// it's best to run custom transformers first\nconst customTransformations = [CustomTransformer, ...AmpOptimizer.TRANSFORMATIONS_AMP_FIRST];\n\n// pass custom transformers when creating the optimizer\nconst optimizer = AmpOptimizer.create({\n  transformations: customTransformations,\n});\n// you can add custom parameters on a per document basis\nconst transformedHtml = await optimizer.transformHtml(html, {\n  filePath,\n});\n```\n\nCheckout [the samples](/packages/optimizer/demo/simple/index.js) to learn how to customize AMP Optimizer.\n\n### CLI\n\nThere's also a [command line version](/packages/cli/README.md) available:\n\n```shell\n$ npx @ampproject/toolbox-cli optimize myFile.html\n```\n\n## Why doesn't my AMP page render faster?\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n* if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n* if an AMP component uses the `media`, `sizes` or `heights` attribute ([documentation](https://amp.dev/documentation/guides-and-tutorials/learn/common_attributes/?format=websites#heights)). A simple workaround is to replace the `media`, `sizes` or `heights` attributes with normal CSS media queries.\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true\n} );\n```\n\n... or for individual pages:\n\n```\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true\n})\n```\n\n## Best Practices\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length)\n    .replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](/packages/runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n* `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n* `imageBasePath`: specifies a base path used to resolve an image during build.\n* `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n* `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```\nconst optimizer = AmpOptimizer.create({\n  // blurry image placeholders are currently not considered valid AMP\n  // hence it's recommended to setup paired AMP mode when enabling this feature.\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp'\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Fallback API for amp-geo\n\nIdeally, when self-hosting the AMP framework, `amp-geo-0.1.js` should be patched at delivery time to replace `{{AMP_ISO_COUNTRY_HOTPATCH}}` with the ISO 3166-1 alpha-2 country code where the request originated ([reference](https://github.com/ampproject/amphtml/blob/master/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem)). If your host does not have this capability, you can instead rely on a web API to return the country at runtime. The web API must be secure (HTTPS), adhere to [AMP CORS guidelines](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests/), and return JSON in the following format:\n\n```\n{\"country\": \"de\"}\n```\n\nwhere in this example, `de` is the ISO 3166-1 alpha-2 country code for Germany.\n\nExample:\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  // this will instruct amp-geo to fetch the user's country from an API\n  // which returns JSON in format: {\"country\": \"de\"}\n  geoApiUrl: 'https://example.com/geo'\n});\n\nconsole.log(optimizedHtml);\n```\n\n## Development & Testing\n\nAMP Optimizer uses a snapshot based testing approach. To execute the tests, run in the project root:\n\n```\n$ npm run test:node\n```\n\nTransformer tests are located in:\n\n```\n- spec/transformers/valid/TransformerName/test-name/\n    expected_output.html\n    input.html\n```\n\nThe transformation input is defined in `input.html`, whereas `expected_output.html` contains the expected\noutcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing \na transformer implementation, run: \n\n```\n$ npm run test:optimizer:snapshot\n```\n\nto store a new snapshot version in `expected_output.html`. \n\n\n","gitHead":"64a25a757ff6d2acc9a7d59ec718a025c713b63e","scripts":{"postinstall":"scripts/init.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.20.2/node@v12.16.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.16.1","dependencies":{"terser":"4.6.13","cssnano":"4.1.10","domutils":"2.0.0","lru-cache":"5.1.1","domhandler":"3.0.0","htmlparser2":"4.1.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.4.0-alpha.1","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.3.0","@ampproject/toolbox-runtime-version":"^2.4.0-alpha.1","@ampproject/toolbox-validator-rules":"^2.3.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.4.0-alpha.1_1588849683213_0.37364403172279537","host":"s3://npm-registry-packages"}},"2.4.0-alpha.2":{"name":"@ampproject/toolbox-optimizer","version":"2.4.0-alpha.2","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.4.0-alpha.2","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"ca2fc9bb88ca0d37a6e2db498b4a53ff98c0f227","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.4.0-alpha.2.tgz","fileCount":42,"integrity":"sha512-r80SdhPtTocKLWG7q/F0wGXWTirAiZ4tkETozmbDU/ZyBZ9Sa77xGbjT3c1QJPu5LpuLuvAGl63uQ6NiE5QEhw==","signatures":[{"sig":"MEQCIEdPE/wHk4+hKpl7kHCJVVYdf8RgT02azEGhD8PhCGVgAiB0nqKrnTtZATxg0ZASy3Rv4/0vxBetpwMUI/U1oPr8lg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":181185,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJetWAXCRA9TVsSAnZWagAAOTkP/0kAgtK75LFpQ8Uke95B\nfrYIka3dGxt4WCUgBhRJOvJeQt6qh7mM+bqFKg0HJy0WZA+uqBPH7xL+HcID\nTX5JOuuWUpuqOa7mSiEKnHGKTTTCb4n52wr6uW0bDwNEtVg0dno5Z9pOeXsZ\nhdzVUatM794Dj5oPURuqDpAzU4jXUDD9uOdB5WYS4hicxexnTvCxrbQHetau\nvGE9CbPOtrg4Az/9Y3nXWmh/LCRJwsryfXHVH8zBM6NIrc6NXaVCeIiYhfLm\n8Gyf1hbZ9jXveXYAl3a4QCPD7ysCfLF/H1KFi8cjvONkiqcuubr7EYrGOF/G\nefrS45XT4tsJqGWaSUnbfdisKFumpGUWGJ/6VpamgEG43nVPC1S5cjYHHmRY\ntK6p749RJ6A58W2FqS/87bUVV5IKWYwpmw0iU29JhKDQ+NtO+EJLSNbazY33\nPqv/QV+aR6TPqM8MPMeRuEUVsMl4IZ1TYoFuO1/AzbZ4j9RK9nJnDKVKI46I\nSSQuCh69BgMhV3sTt4ZuBsse1PODH2amf+o/GxLlrskWUEM+sHPBMp77IgJa\nxySjwieKvnfeY7B/Fe70/KFdE8sJjiOiLX8gLAPhtfgCdUtQFXgsqed8xplL\np+OUhEOfI6cLfllx1x1P1shgalIheaQu3dIYfnG57DZ1Sla/Kr2btvf/Okzy\nKtkK\r\n=s4pT\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to simplify creating AMP pages and improve AMP rendering performance. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n* Server-side render AMP layouts.\n* **Automatically import all missing AMP component scripts**.\n* **Automaticallly add any missing mandatory AMP tags**.\n* Remove the AMP boilerplate (when possible).\n* Remove not needed whitespace.\n* Extract and move CSS keyframe animations to the bottom of the page.\n* Optimize AMP framework and custom font loading\n* Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n* AMP Optimizer will produce valid AMP.\n* AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>`;\n\nampOptimizer.transformHtml(originalHtml).then((optimizedHtml) => {\n  console.log(optimizedHtml);\n});\n```\n\nYou can find a sample implementation [here](/packages/optimizer/demo/simple). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](/packages/optimizer-express).\n\n### Incomplete markup\n\nIt's possible to pass incomplete documents and AMP Optimizer will add any \nmissing tags and extension imports required by a valid AMP document. \n\n```\nconst originalHtml = `\n  <h1>Hello World!</h1>\n  <amp-twitter width=\"375\" \n               height=\"472\" \n               layout=\"responsive\" \n               data-tweetid=\"1182321926473162752\">\n  </amp-twitter>\n`;\n\n// you can pass the canonical URL, default is `.`\nconst opts = {\n  canonical: '/example.html'\n}\nampOptimizer.transformHtml(originalHtml, params).then((optimizedHtml) => {\n  // optimizedHtml will be a valid AMP document\n  console.log(optimizedHtml);\n});\n```\n\n### Markup support\n\nAMP Optimizer supports converting Markdown to AMPHTML. A typical conversion flow would be:\n\n```\nREADME.md => HTML => AMP Optimizer => valid AMP\n```\n\nThe AMP Optimizer converts `<img>` tags into `<amp-img>` or `<amp-anim>` tags when in Markdown mode. Enable Markdown mode via `markdown : true`. AMP Optimizer will try to resolve image dimensions from the actual files. Images wider than 320px will automatically get an `intrinsic` layout.\n\nAll other Markdown features are already supported by AMP.\n\nYou can pass an additional option `imageBasePath` to specify a base path used to resolve an image during build, this can be a file system path or URL prefix.\n\n**Important:** for image size detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n```\nnpm install probe-image-size --save-dev\n```\n\nExample:\n\n```\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst md = require('markdown-it')({\n  // don't sanitize html if you want to support AMP components in Markdown\n  html: true,\n});\n\n// enable markdown mode\nconst ampOptimizer = AmpOptimizer.create({\n  markdown: true,\n});\n\nconst markdown = `\n# Markdown 🤯\n\nHere is an image declared in Markdown syntax: \n\n![A random image](https://unsplash.it/800/600).\n\nYou can directly declare AMP components:\n\n<amp-twitter width=\"375\" \n             height=\"472\" \n             layout=\"responsive\" \n             data-tweetid=\"1182321926473162752\">\n</amp-twitter>\n\nAny missing extensions will be automatically imported.\n`;\n\nconst html = md.render(markdown);\n\nconst amphtml = await ampOptimizer.transformHtml(html, {\n  canonical: filePath,\n});\n```\n\nYou can find a working sample [here](/packages/optimizer/demo/markdown).\n\n### Custom transformations\n\nAMP Optimizer supports custom HTML transformations:\n\n```\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst {createElement, firstChildByTag, appendChild} = AmpOptimizer.NodeUtils;\n\nclass CustomTransformer {\n  constructor(config) {\n    this.log_ = config.log.tag('CUSTOM');\n  }\n  transform(tree, params) {\n    this.log_.info('Running custom transformation for ', params.filePath);\n    const html = firstChildByTag(tree, 'html');\n    if (!html) return;\n    const head = firstChildByTag(html, 'head');\n    if (!head) return;\n    const desc = createElement('meta', {\n      name: 'description',\n      content: 'this is just a demo',\n    });\n    appendChild(head, desc);\n  }\n}\n\n// it's best to run custom transformers first\nconst customTransformations = [CustomTransformer, ...AmpOptimizer.TRANSFORMATIONS_AMP_FIRST];\n\n// pass custom transformers when creating the optimizer\nconst optimizer = AmpOptimizer.create({\n  transformations: customTransformations,\n});\n// you can add custom parameters on a per document basis\nconst transformedHtml = await optimizer.transformHtml(html, {\n  filePath,\n});\n```\n\nCheckout [the samples](/packages/optimizer/demo/simple/index.js) to learn how to customize AMP Optimizer.\n\n### CLI\n\nThere's also a [command line version](/packages/cli/README.md) available:\n\n```shell\n$ npx @ampproject/toolbox-cli optimize myFile.html\n```\n\n## Why doesn't my AMP page render faster?\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n* if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n* if an AMP component uses the `media`, `sizes` or `heights` attribute ([documentation](https://amp.dev/documentation/guides-and-tutorials/learn/common_attributes/?format=websites#heights)). A simple workaround is to replace the `media`, `sizes` or `heights` attributes with normal CSS media queries.\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true\n} );\n```\n\n... or for individual pages:\n\n```\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true\n})\n```\n\n## Best Practices\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length)\n    .replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](/packages/runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n* `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n* `imageBasePath`: specifies a base path used to resolve an image during build.\n* `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n* `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```\nconst optimizer = AmpOptimizer.create({\n  // blurry image placeholders are currently not considered valid AMP\n  // hence it's recommended to setup paired AMP mode when enabling this feature.\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp'\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Fallback API for amp-geo\n\nIdeally, when self-hosting the AMP framework, `amp-geo-0.1.js` should be patched at delivery time to replace `{{AMP_ISO_COUNTRY_HOTPATCH}}` with the ISO 3166-1 alpha-2 country code where the request originated ([reference](https://github.com/ampproject/amphtml/blob/master/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem)). If your host does not have this capability, you can instead rely on a web API to return the country at runtime. The web API must be secure (HTTPS), adhere to [AMP CORS guidelines](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests/), and return JSON in the following format:\n\n```\n{\"country\": \"de\"}\n```\n\nwhere in this example, `de` is the ISO 3166-1 alpha-2 country code for Germany.\n\nExample:\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  // this will instruct amp-geo to fetch the user's country from an API\n  // which returns JSON in format: {\"country\": \"de\"}\n  geoApiUrl: 'https://example.com/geo'\n});\n\nconsole.log(optimizedHtml);\n```\n\n## Development & Testing\n\nAMP Optimizer uses a snapshot based testing approach. To execute the tests, run in the project root:\n\n```\n$ npm run test:node\n```\n\nTransformer tests are located in:\n\n```\n- spec/transformers/valid/TransformerName/test-name/\n    expected_output.html\n    input.html\n```\n\nThe transformation input is defined in `input.html`, whereas `expected_output.html` contains the expected\noutcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing \na transformer implementation, run: \n\n```\n$ npm run test:optimizer:snapshot\n```\n\nto store a new snapshot version in `expected_output.html`. \n\n\n","gitHead":"c2105027e95afd110e9f3b8c3e252492e3db5149","scripts":{"postinstall":"scripts/init.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.20.2/node@v12.16.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.16.1","dependencies":{"terser":"4.6.13","cssnano":"4.1.10","domutils":"2.0.0","lru-cache":"5.1.1","domhandler":"3.0.0","htmlparser2":"4.1.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.4.0-alpha.1","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.3.0","@ampproject/toolbox-runtime-version":"^2.4.0-alpha.1","@ampproject/toolbox-validator-rules":"^2.3.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.4.0-alpha.2_1588944918950_0.8887560898710563","host":"s3://npm-registry-packages"}},"2.4.0":{"name":"@ampproject/toolbox-optimizer","version":"2.4.0","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.4.0","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"16bde73913f8b58a9bf617d37cdc1f21a1222f38","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.4.0.tgz","fileCount":42,"integrity":"sha512-Bmb+eMF9/VB3H0qPdZy0V5yPSkWe5RwuGbXiMxzqYdJgmMat+NL75EtozQnlpa0uBlESnOGe7bMojm/SA1ImrA==","signatures":[{"sig":"MEYCIQDATrE/eBS2jqR3XQ5htKYYYMW5wizCkINcDcY/k7GxfAIhAI2qX3sbqwJLrPoGDeZ37VPAiKuN5zHS7QwMRdOFkVaP","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":181804,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJevFjwCRA9TVsSAnZWagAAdgcP/3romjihBkMZ/Y8dqOnf\nKhbstPpS0y2EPEFKgSD3LKXz8qkX1LF24Gio6iI2XXIa6DPxdrzNIVQBVFck\n+L7gQ/52GcqWj3Nzmktlhfi5sxCHbgjyUvXQXhJ/ys0EYbuGRRD7E92DpfDX\nhrE7xNJpCmG7Fvs7zoRTN9fW1sjmCUsbhqpfW5SeCyzBTkAUqIjWuEiwOkDd\nQPulKGCY15LNRnEMJGDaz5OU9ocACcyOng0ZhBTmYy40PyZNa+WzwqKPaV2T\nwHuX1QUNJM8CM732C5Y9kUb4FpYd9CD/VthrJ9fmPg/8DNIdLtRXzOiqap7w\noBTEs0XoVC6XC1WkBCsNuJOqr1nA/BaZGgkgX3sMTwnwc+Z4KpL2p8KACUqM\nYOlozdGi4zavYndA5JnkZ7TuCf0saIgAKpun3O3HjBnleQ9YiFD72ADw+jJX\nPDcmDfed7HrAMwx6Lc+31C/e5QMo8WCO42KJZ0MDm8jLxgWhzGHdcNmzq/5d\nlAsqFHz6q+uQ3VePJy6MwNT9+MALkPBVfBezkKT4Xj8YmxW/nol7Jz+k/EOQ\napIaeDBqZpbUAm55hzgN9Y+6VHlJKyJVZC7KYnZwlQv8WL1MN/QhqxrmbKqR\nymGmQmLY+iJuBm+labCGxuaOYRnLjC0GgRhJd9Gw2lNVNCcDstiAt3+InRii\nk25+\r\n=553Z\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"80f09fcf0cf43cbc6d3f1ae9cbcbfb0552eea220","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.21.0/node@v12.16.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.16.1","dependencies":{"terser":"4.6.13","cssnano":"4.1.10","domutils":"2.1.0","lru-cache":"5.1.1","domhandler":"3.0.0","htmlparser2":"4.1.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.4.0-alpha.1","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.3.0","@ampproject/toolbox-runtime-version":"^2.4.0-alpha.1","@ampproject/toolbox-validator-rules":"^2.3.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.4.0_1589401840199_0.6363872975952491","host":"s3://npm-registry-packages"}},"2.5.0-alpha.0":{"name":"@ampproject/toolbox-optimizer","version":"2.5.0-alpha.0","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.5.0-alpha.0","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"963dcc2bf72c4db0f3f3173dda755fb314aba203","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.5.0-alpha.0.tgz","fileCount":45,"integrity":"sha512-pl6Np/uqzOazfxHUN3YmHQxMAMvrnefOqZyZf4dJ4Nvxejvzha9ZJfp2HtQ8wXXLEFymN0BZ/Gy6VzVD2LH8YQ==","signatures":[{"sig":"MEQCIEYBN5TriyXmBsrWxruYqfE0vOJ/IcRVz3udrso19OqxAiBZdmjAQr+nPhloe5c9ee6DuvVf87deb0WNq0uP8vxHiA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":190545,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJew352CRA9TVsSAnZWagAAx64QAIhe2gENimkGeW9joEUV\ncMs6kDIOlAVKGYCgPVi8WqDDveKa/0DQfs0VsTbSI3XMPKhaYA7p5l3o7E81\nObqeZJpQ5DsHSUB/dhAS7sgHlL5FQqhsw47M59PxbSKRudDLhO7wQJNk9S6t\ntH2kRYS7qVYYqMzQZ+rTv0VGNgy7UfSG1apvha4nawro3Q1ltiVYVeHqsEKS\n3m53LFneNcHtD0Tin4Jvej4UH7Ex79Y7evwgpSRa04gG5Cy2o7pD5v1LEDic\nVVhT1kIFT5IqSwC9vLGcQtmHDg5g0HkhSAoWGV8DTWhwwSjetZVx0GPgiMCO\n9MbsVDxu46GYpwFYRkMZB8PzJ+OiyXbg/krVhCV0e/HlSJOdmVg8qWOOaV5B\n/gd/Yx+xwkfCjY8uld/2W/9bOv2HknOjAvIIjl77c0Q2ZqcpLiAiTQlVJCcw\nzGc8R7QFaqMR3qaI0ZizJ+2dSInDL1Mlj5ulZr0QtRkTIVSWi3V5Dhc8Kb20\nx0ycSkrcvM1oEb8e9hyBx+9rIVHcxLJAzL9/kzjFz2B0a5JS9Pb7/qeoDyxW\nqOFQ4U0Bv/yJFmHVSdDcg4c/OpA/JomQRLMCwCnXi185P7IUUmFo4/AsUdvs\n+ht0IGvVryupDg/o+Pn1YYIGAil2v1xyyk+OvfQUspmxIL1+WjvFnGtAlTQ0\niiGy\r\n=ZRb5\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to simplify creating AMP pages and improve AMP rendering performance. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n* Server-side render AMP layouts.\n* **Automatically import all missing AMP component scripts**.\n* **Automaticallly add any missing mandatory AMP tags**.\n* Remove the AMP boilerplate (when possible).\n* Remove not needed whitespace.\n* Extract and move CSS keyframe animations to the bottom of the page.\n* Optimize AMP framework and custom font loading\n* Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n* AMP Optimizer will produce valid AMP.\n* AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>`;\n\nampOptimizer.transformHtml(originalHtml).then((optimizedHtml) => {\n  console.log(optimizedHtml);\n});\n```\n\nYou can find a sample implementation [here](/packages/optimizer/demo/simple). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](/packages/optimizer-express).\n\n### Incomplete markup\n\nIt's possible to pass incomplete documents and AMP Optimizer will add any \nmissing tags and extension imports required by a valid AMP document. \n\n```\nconst originalHtml = `\n  <h1>Hello World!</h1>\n  <amp-twitter width=\"375\" \n               height=\"472\" \n               layout=\"responsive\" \n               data-tweetid=\"1182321926473162752\">\n  </amp-twitter>\n`;\n\n// you can pass the canonical URL, default is `.`\nconst opts = {\n  canonical: '/example.html'\n}\nampOptimizer.transformHtml(originalHtml, params).then((optimizedHtml) => {\n  // optimizedHtml will be a valid AMP document\n  console.log(optimizedHtml);\n});\n```\n\n### Markup support\n\nAMP Optimizer supports converting Markdown to AMPHTML. A typical conversion flow would be:\n\n```\nREADME.md => HTML => AMP Optimizer => valid AMP\n```\n\nThe AMP Optimizer converts `<img>` tags into `<amp-img>` or `<amp-anim>` tags when in Markdown mode. Enable Markdown mode via `markdown : true`. AMP Optimizer will try to resolve image dimensions from the actual files. Images wider than 320px will automatically get an `intrinsic` layout.\n\nAll other Markdown features are already supported by AMP.\n\nYou can pass an additional option `imageBasePath` to specify a base path used to resolve an image during build, this can be a file system path or URL prefix.\n\n**Important:** for image size detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n```\nnpm install probe-image-size --save-dev\n```\n\nExample:\n\n```\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst md = require('markdown-it')({\n  // don't sanitize html if you want to support AMP components in Markdown\n  html: true,\n});\n\n// enable markdown mode\nconst ampOptimizer = AmpOptimizer.create({\n  markdown: true,\n});\n\nconst markdown = `\n# Markdown 🤯\n\nHere is an image declared in Markdown syntax: \n\n![A random image](https://unsplash.it/800/600).\n\nYou can directly declare AMP components:\n\n<amp-twitter width=\"375\" \n             height=\"472\" \n             layout=\"responsive\" \n             data-tweetid=\"1182321926473162752\">\n</amp-twitter>\n\nAny missing extensions will be automatically imported.\n`;\n\nconst html = md.render(markdown);\n\nconst amphtml = await ampOptimizer.transformHtml(html, {\n  canonical: filePath,\n});\n```\n\nYou can find a working sample [here](/packages/optimizer/demo/markdown).\n\n### Custom transformations\n\nAMP Optimizer supports custom HTML transformations:\n\n```\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst {createElement, firstChildByTag, appendChild} = AmpOptimizer.NodeUtils;\n\nclass CustomTransformer {\n  constructor(config) {\n    this.log_ = config.log.tag('CUSTOM');\n  }\n  transform(tree, params) {\n    this.log_.info('Running custom transformation for ', params.filePath);\n    const html = firstChildByTag(tree, 'html');\n    if (!html) return;\n    const head = firstChildByTag(html, 'head');\n    if (!head) return;\n    const desc = createElement('meta', {\n      name: 'description',\n      content: 'this is just a demo',\n    });\n    appendChild(head, desc);\n  }\n}\n\n// it's best to run custom transformers first\nconst customTransformations = [CustomTransformer, ...AmpOptimizer.TRANSFORMATIONS_AMP_FIRST];\n\n// pass custom transformers when creating the optimizer\nconst optimizer = AmpOptimizer.create({\n  transformations: customTransformations,\n});\n// you can add custom parameters on a per document basis\nconst transformedHtml = await optimizer.transformHtml(html, {\n  filePath,\n});\n```\n\nCheckout [the samples](/packages/optimizer/demo/simple/index.js) to learn how to customize AMP Optimizer.\n\n### CLI\n\nThere's also a [command line version](/packages/cli/README.md) available:\n\n```shell\n$ npx @ampproject/toolbox-cli optimize myFile.html\n```\n\n## Why doesn't my AMP page render faster?\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n* if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n* if an AMP component uses the `media`, `sizes` or `heights` attribute ([documentation](https://amp.dev/documentation/guides-and-tutorials/learn/common_attributes/?format=websites#heights)). A simple workaround is to replace the `media`, `sizes` or `heights` attributes with normal CSS media queries.\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true\n} );\n```\n\n... or for individual pages:\n\n```\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true\n})\n```\n\n## Best Practices\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length)\n    .replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](/packages/runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n* `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n* `imageBasePath`: specifies a base path used to resolve an image during build.\n* `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n* `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```\nconst optimizer = AmpOptimizer.create({\n  // blurry image placeholders are currently not considered valid AMP\n  // hence it's recommended to setup paired AMP mode when enabling this feature.\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp'\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Fallback API for amp-geo\n\nIdeally, when self-hosting the AMP framework, `amp-geo-0.1.js` should be patched at delivery time to replace `{{AMP_ISO_COUNTRY_HOTPATCH}}` with the ISO 3166-1 alpha-2 country code where the request originated ([reference](https://github.com/ampproject/amphtml/blob/master/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem)). If your host does not have this capability, you can instead rely on a web API to return the country at runtime. The web API must be secure (HTTPS), adhere to [AMP CORS guidelines](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests/), and return JSON in the following format:\n\n```\n{\"country\": \"de\"}\n```\n\nwhere in this example, `de` is the ISO 3166-1 alpha-2 country code for Germany.\n\nExample:\n```\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  // this will instruct amp-geo to fetch the user's country from an API\n  // which returns JSON in format: {\"country\": \"de\"}\n  geoApiUrl: 'https://example.com/geo'\n});\n\nconsole.log(optimizedHtml);\n```\n\n## Development & Testing\n\nAMP Optimizer uses a snapshot based testing approach. To execute the tests, run in the project root:\n\n```\n$ npm run test:node\n```\n\nTransformer tests are located in:\n\n```\n- spec/transformers/valid/TransformerName/test-name/\n    expected_output.html\n    input.html\n```\n\nThe transformation input is defined in `input.html`, whereas `expected_output.html` contains the expected\noutcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing \na transformer implementation, run: \n\n```\n$ npm run test:optimizer:snapshot\n```\n\nto store a new snapshot version in `expected_output.html`. \n\n\n","gitHead":"48d9b99ee4744e54eac1f73a7cbf05f3da2c0018","scripts":{"postinstall":"scripts/init.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.21.0/node@v12.16.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.16.1","dependencies":{"terser":"4.6.13","cssnano":"4.1.10","domutils":"2.1.0","lru-cache":"5.1.1","domhandler":"3.0.0","htmlparser2":"4.1.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.4.0-alpha.1","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.3.0","@ampproject/toolbox-runtime-version":"^2.4.0-alpha.1","@ampproject/toolbox-validator-rules":"^2.3.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.5.0-alpha.0_1589870198305_0.7268410137663912","host":"s3://npm-registry-packages"}},"2.5.0":{"name":"@ampproject/toolbox-optimizer","version":"2.5.0","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.5.0","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"cc5e5dd43919ec8b9731148f44b2a9feb43d6aa7","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.5.0.tgz","fileCount":47,"integrity":"sha512-aMUx4pyxx2j+HA1qT3RuzGj9unjMO7HEt5FBsV5hqc4g+6MkKlLi/GOktvhHArVWKWOy0qNOzCIHhPzIP5xQTA==","signatures":[{"sig":"MEUCIB/JVMfqUJIoHcaLJjy/jXHDqE8UipeYRT7Sv0F64r8zAiEA3bi3581tMw+qMeF1h/Q4AiifBrcFRYwT3eBqFmD9FwM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":209188,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJe0PgVCRA9TVsSAnZWagAA7TsQAISB7dc0FrG90aJfwjkT\nkuUEgOZX4kCmgboJwo4VaqwTf0Yt4QHImLBp0v/WMsD8K98A2Dd+TvtZM4oD\nzlv9fyWPGbOOlj0/VyE2FLPLQwODrk2ysq32id+4VQ3ukgxdeY3/dt57hKU7\nTBp0VXJt2liRORuItfcEzS/gRJViy98BGe1wKlN85wY7p4X03wB7HeZ0+e5X\n8sfWg/q39Kk4eYmFTD0Haw+2rIR3POYpfFYtY2p7zb6e9uVwI2W1l6nngfgd\n6FLcY2LAn5oN+oQKfIuhShpl34XAcF82wL1D+ZzHHYTg33ESO/0VLCdP7wh/\nAWVh6NRqJKtP+nLpX9yzsF234L31WV0cCp4yZrOl1BlQGHygr+k1lc8U+vpe\nnhUcwYypYEubIiNgj2K7nIn20RqX4K1+nBGVFeYFoiOxztiyJxOkGDcUC+F6\n/KFtVuOs2TLmSSXUKaVBvNbsU9hHKAQ33J8yZU6VyD9+q7OBFyaXvAkX0rPE\ngJP0wVtnhz5mnFTK8rxV9IM5kbppzondHq5vZydtaQ2yUPuxYWZ9vnw/Baus\nEOytaWOpdn4xhQZ9ZCRp2ij1rtqn/Vy+8I+gKeQuKYpQqs5plttGYSBFpOoa\nC0uk6+B7NnMfUMihF0QkI2ZwPTRHKtUc8STMHxViattSO2JjVC+T8KG/Y64A\nYQyj\r\n=N+b+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"c0ba2297de1e3cd583018880e2ea6c7fe5fd7905","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.0/node@v12.16.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.16.1","dependencies":{"terser":"4.7.0","cssnano":"4.1.10","postcss":"7.0.31","domutils":"2.1.0","lru-cache":"5.1.1","domhandler":"3.0.0","cross-fetch":"3.0.4","htmlparser2":"4.1.0","dom-serializer":"1.0.1","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.5.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.3.0","@ampproject/toolbox-runtime-version":"^2.5.0","@ampproject/toolbox-validator-rules":"^2.3.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.5.0_1590753301250_0.27868375170722537","host":"s3://npm-registry-packages"}},"2.5.1":{"name":"@ampproject/toolbox-optimizer","version":"2.5.1","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.5.1","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"0f5897d77d0e73957541c85f0f3f49a73e3449fe","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.5.1.tgz","fileCount":47,"integrity":"sha512-C4pYFt9K9+sXngBeYB/+zrW+38mMhjG8Xu3iTO13WsPL6Ym/UNopLjmhK99mxsnQgn/lkzcACY0n7QH7WMp4JA==","signatures":[{"sig":"MEUCIQCJ4KadKAc3dta7zUTforpw6Mh0A1VOaz9edYmrRyyknAIgdio9LC7pUIUlslkPwIjN5lWA0MYRBKdh8+tLuheaYuM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":209629,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJe3zquCRA9TVsSAnZWagAAccEP+wdjYiFFaOiokTZGVNPw\nIxNJOLLcgR57HUXGf4J2hjvpU7v/4SCUxmovz2iTMjkmknA0GeEEy/d2qDFD\nleyXXU4ICAFczlQ4ePRu1Q9QJ6gd7VT8mayVlFTCm+8FG4QZXG3W5HfCFc3x\n73t5TdKoaoah5J1asQ1aq+9JiQq5EMDka7xI1iXB6FxSRa0y8wrz2WpYxLsT\nmpmztMwkNwFhmw5WE1R/zSCOQYMMFEM1NQTETWtrS31ZEIZk+eDYCoAZb8bL\ngzWkVtB1hiySfxHJWr+W3gEhhrpYlnpxCklEdl8KxvvBaYyYaZi6td1JwZSg\nw4zVpcYADMlix1yGNHFpGCx9FimsMzATOjlYj/sYETjHweZyDR4FMOuC2Yoc\n87oqxgdXt1bqR388+blPQaDMZqBbU+lW3htQfjsaCvC9jyhz6Rpg+ngfvezM\nYQk9wIeOPPSGDzZVsRUEE+AegQECSXTW4WAgr9rVKAwAmAbogNJrIEw7EeSG\nW4k+zd2cduZ09Gc58AYTFTlcbWvAQ9aLYeXXx5cCM8AZ/HnGV0PGU54lidin\n1r9/miVA9rrcrXwUEq+syjR648QGBkMwaH2tcLYDbO9nsec+KX2HrSE9UZiy\n4g+5++9mOkTfgAgCK8F4N8bjSFNYKHVPCMaPaWqBjru+TGLwA41bOA2F86uk\n31my\r\n=gbrK\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"f92ba13281ecf3e624ee8285f2deebee18d580bd","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.0/node@v12.16.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.16.1","dependencies":{"terser":"4.7.0","cssnano":"4.1.10","postcss":"7.0.32","domutils":"2.1.0","lru-cache":"5.1.1","domhandler":"3.0.0","cross-fetch":"3.0.4","htmlparser2":"4.1.0","dom-serializer":"1.0.1","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.5.1","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.3.0","@ampproject/toolbox-runtime-version":"^2.5.1","@ampproject/toolbox-validator-rules":"^2.3.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.5.1_1591687854333_0.5701842732296372","host":"s3://npm-registry-packages"}},"2.5.2":{"name":"@ampproject/toolbox-optimizer","version":"2.5.2","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.5.2","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"17085463255d96a81606803c4bf5d1f1414cfc17","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.5.2.tgz","fileCount":47,"integrity":"sha512-hDvDgAMTHUnye2Y7WhoF+6MtgOlkru+4EJtLUdrfOKdZyNGJ7s9E+lGsncFHwyyIaEWcs5RkthcZMVHgkg7ZoQ==","signatures":[{"sig":"MEUCIQCYF33BG+CfItOc9yNlwGL5ZgBhT84mPpmIYBRQB8tv1wIgX8Ns6UA/7FHfbHsal0Lfo+t0SEZqeF1O0ca4wZjsqM0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":211716,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJe4nusCRA9TVsSAnZWagAA+/YP/i24PPtu9Zqb8IViYh+W\n9JDzlzu1QcSLJiuPHfLgBhp/4bKpfElUC3BCxdBytLow07JA8FdrdOZdZTtM\nT4jjPJjXfgMx8zzNNQgUJ5D9AA3LKQ/jrLuI6m2TSorRhlV4lQ4BuE/xNSkZ\nFZOUwP6g4NqE5iLLtLYq2tcjnRsYulJq89HcdAZ+X+UuZOBAl+qrjzFzfibB\nBT7p8LGKQYRamjXhy0HyQOSe6D4mu9VJ28ddDJ3dKv2Ygepv2bg4t2mcSWgQ\nwnShfEJAXsWVb8bDahlWAIthXVbU2/qQZGGewWZ3/9IK0mJrZn89zyJ725rx\nxMIvQWNTGM+NtUYg3IieZ0E0+PBXJopugnb4bFxMaXk7nuTOk3xUL2UpLFr8\nz7IlwHhWKycBuGu1iKAGIZcrJsI3+C5No2JbHjUy85k6J1dZMT8lnVKX8I+x\nZLjRme3ENAiZOun0L+rzurNAYGfbD7HZiKUaJzYFKQ2lXUNVd41ISwyNp+uH\nGWm1DwcHeLNNQEXWORVFwlU9otiUwS+Td8PSklUKgmrqF9VrBmnVRh6J7A2S\nGYOO/MywyC5byoYTju87HKe2Z7UinXRTPMr4eDAAefYH4KySg67X04gHje7G\nPNch4VF8bJsQJevBiE4BSMalUw5AHT6QvT5yKCJwrmo4qdCioqMoN//UtMxH\nxN9c\r\n=5HJd\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"0e67c978f403a0f68d5b876010d5eb1a5ae205d9","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v12.16.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.16.1","dependencies":{"terser":"4.7.0","cssnano":"4.1.10","postcss":"7.0.32","domutils":"2.1.0","lru-cache":"5.1.1","domhandler":"3.0.0","cross-fetch":"3.0.4","htmlparser2":"4.1.0","dom-serializer":"1.0.1","abort-controller":"3.0.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.5.1","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.3.0","@ampproject/toolbox-runtime-version":"^2.5.1","@ampproject/toolbox-validator-rules":"^2.3.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.5.2_1591901099463_0.6062601111650059","host":"s3://npm-registry-packages"}},"2.5.3":{"name":"@ampproject/toolbox-optimizer","version":"2.5.3","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.5.3","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"da24e0bad9350fded8a923c9e1c2402f0fe01e5b","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.5.3.tgz","fileCount":47,"integrity":"sha512-D6j7nU02sLRaW+ZKd1UNyUESu9GVLhVrtGHQ/rORj87ZJS5s0DCpoIDbq1slKQDCDHl7RknQ3A6CVm14ihXV2A==","signatures":[{"sig":"MEUCIQCY+ApipHoZy70IGzW3olK7NeZ7BtcYW4RsahSQWG63uAIgDERCyDEgQPzoZlyf6lvvV+9DI00g8hUl98en1ZU1e8w=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":211743,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJe5yeYCRA9TVsSAnZWagAAlyIP/iavKvB7wfiun9IiLsNA\n7ZIk3M1ABYVVBYtnkDRMX/hVIgGQQjXfGZTW7kIsQSyMYc5OrlwFLjP9mwvT\nR02DjSmpkqHBePvtio7uAlrYoksmUjrroV094qmfbEcikPDhZvRC4SFU9/uX\nSCDF/ETM1cGAgiLXqyuvrHttO7j4Hq0kWRLVwpmjszq8GU+zHYxp7EUaaQE7\nuaVGncpWF8g1n2FiWqtISCyYI3i4ySddjl1kb+Blo5BiiGAG5N2AkIyiL2O0\nOHrO7Ip6Fm/h3HiFi620qcZgciskf7GdkAbotUiBga3Dxm+8kztmgfmoBOIG\n9VOXuyHoA5NpNQ7fLMHWECoSbGqPYrZwIh+G1uG+LrgywJK3fKOcz1fyfIQf\nhA7Nbyv/EmsgF5DqnOiokdH13VTuBxmewnrd8f9U0n3PYv6veLwHX1aE0jpA\nkEGOEYRvEH4ZKiDG13Mz3mT6/jfyaz9i8CvujLXI29J6pspPnJOv5vCLrw4D\nCjy/hjvGMGJLxVmujbXFDZIlvuAhlgTjoBCsJeeszpxm9BUae4lBySJ5cZdK\nmwSpGxypySLeFrpSv62yVQ98G2IO6yOaEvZDlqCZEfV7UCUL0lji4hebElMa\ncxPCpp4cVFIC7peOeRRwZ40T5eTL9Fu6LFBkFXIvZcgU1TZMQsbmvayYUK3d\n9SGx\r\n=d4nU\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"bf9cdb032c1c35e1c3a11ed288c081ef7631f239","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v12.16.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.16.1","dependencies":{"terser":"4.7.0","cssnano":"4.1.10","postcss":"7.0.32","domutils":"2.1.0","lru-cache":"5.1.1","domhandler":"3.0.0","node-fetch":"2.6.0","cross-fetch":"3.0.4","htmlparser2":"4.1.0","dom-serializer":"1.0.1","abort-controller":"3.0.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.5.1","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.3.0","@ampproject/toolbox-runtime-version":"^2.5.1","@ampproject/toolbox-validator-rules":"^2.3.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.5.3_1592207255798_0.6873943604009964","host":"s3://npm-registry-packages"}},"2.5.4":{"name":"@ampproject/toolbox-optimizer","version":"2.5.4","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.5.4","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"56e986971c617cf4abcd25eb87e5758d28f40e3f","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.5.4.tgz","fileCount":47,"integrity":"sha512-NyDVMYcyNun6/i6zucJnorfGAGqIbQAfD54v8znmj8ycRdcfKtlywghHlFyHwfQDgZj4bkX85ZTlaW9EGMa+mA==","signatures":[{"sig":"MEUCIQDrnLSb4Td06Pg8BGTFJHgZ8xF25zEeCBhZWDS7RyS8ygIgNeaGus1EaoEN+6KyAJxglNkaaOdUtyeFy+39pr/zDU8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":211732,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJe8QvvCRA9TVsSAnZWagAA1VMP/1OeSszI3vUAcvWw1D2R\nZlji0XDXgOR+ARPS0CWsF84mHmC76Gzje8Nf7fUEp7CRdv3QCaszByP0grOY\n0yoetTAUJ/RBEJBZPdPZgiHte4r7mxwEsQD1IPH6rzY/c8OHKLuYtsxUmCbO\nkDwNFDBDWgJVKRO0d1fjNVIej/dH0hKUoIJ7XXtuSysphB+j3loxh7QNf97R\nOjW3sUk7wp7Qq+k5dtWm6bwSyJpgyYpnhKz0KoqS5xOC+OCyDbVG/M29ybV0\n8t+68pzlhha2S0asQigsiphmlNjsHwcIkoJpL68SzxrecOPHe9wpre4aI7UU\nDbzYFclxiJvRzC3M6LM70pBZyqIKoBMzdygEfGPQtEasWaZ8HWUoEf6eoBCy\n6Yb7BCnXL3ESAynzTTIGyTzv7gIMxRlQugKCwHkDe10Q6TMKLZlBT+z9a1/9\n4OunA3kwot/0F3Bz6N+5+MTuTy60L/ZWY+ZqZqPhgViUDCm/9vKnOcWT3hIT\ntT56W56kLZ3jRHDA+OksKuw0/ga8FOIeNtNkJW2Fs9Sh4NzjwEk2eumjbYlY\nyIBHhDRngkNIWI3Gq69Qi9qPi8IxEYcOsgNTwR+4YZ7bIWYsRpIoj95Ibak0\nmrLR3dQFiFn2T6MNho9e1mgXEm4PrlQag0F3bCB6V+XPNlBj8SyBVuqE9JPi\nDu8b\r\n=bRXZ\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"c194eb829c2d0d4cda21c961107d73bd7528f53f","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v12.16.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.16.1","dependencies":{"terser":"4.8.0","cssnano":"4.1.10","postcss":"7.0.32","domutils":"2.1.0","lru-cache":"5.1.1","domhandler":"3.0.0","node-fetch":"2.6.0","cross-fetch":"3.0.5","htmlparser2":"4.1.0","dom-serializer":"1.0.1","abort-controller":"3.0.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.5.4","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.5.4","@ampproject/toolbox-runtime-version":"^2.5.4","@ampproject/toolbox-validator-rules":"^2.5.4"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.5.4_1592855534676_0.8044174549808063","host":"s3://npm-registry-packages"}},"2.5.5":{"name":"@ampproject/toolbox-optimizer","version":"2.5.5","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.5.5","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"8f76ad5bb223f042bbd8e695e6296d10e8e42bed","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.5.5.tgz","fileCount":47,"integrity":"sha512-OanuEl5ChEg7y0zLBj7HwN0Nr9+7M9KXmqD8dpDd29S2oRUng5WVZ6nuu6ZOV1k4iyP1j8pPqFum8GFGQ30H3A==","signatures":[{"sig":"MEUCIGmfzVK0o+HE8Ex8qpnFsI8ur23MjTYoxv9aaLKDFDkVAiEA03klu5aBmB4mmhW0bLVSsBv0NGabR830hlqeNQCG6ng=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":211847,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJe9jaGCRA9TVsSAnZWagAADWoP/AgV+K+dE69u1qFjSHAO\niITnuqnrVz4BswTkt3OoGOeaCQ96YkTUia2G5dd2qN9q7C92FIJ0bT5ZRDWT\neQJKRjZ1tWtXj1w6cwrKBdKTjdOy2nejvJiPewFntNNryAx6oUorWkxr4dZG\nCFKdN9j0agQvjqe//lEJKGxQf3SzkhDrIArw3ycBshFvO577qe6161bx9dy7\ndQdCypT+zKGO8JXxbJhPRvcxGKyQjbILSSrADLUyz1xRNf+5H9LBys3d5wgU\nKwZ+OWtxM0kdu5qADBu3RNWrSGyx8GFLu8hS/lqzoTH7rVwvEob6hHBtVoyv\nEqNvdJv8hB9ShLpaSFFkisTwtB1YVdGMP9vzsja4ccZoTZCDjDm2vEnUv7s/\nL1zkBdzmw3VPig9uf5q5/BQN3JlqtSHW2dM15IyFmUQ7rAX2kiuODMXEJX0e\nz4wB7mMd/fIkcqEEitpdmSCluOlQb+PD4UNNIDaJKw5mDiQvt9jj3q4CQ9p7\nqboFcTDg9lsT0b5+/gvito3rGZm9bCNJn1bLLUZwe7gTgAkjNnziI0oxfH6i\nhJLsmL7m6uC2VkqKsZlxtG5CnKOQqli9iY0Id33Jq3+y6x0h59pGkFSyqXmu\n4i5VfSkUFrtkkhHeZWr2VzDcODYP2O5JoOvbcoKOaGqQ/FsVYCgZ8oRp5pvi\n7Jmv\r\n=5VNv\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"2c10143d29eaea2b98307ff0595e7b9beedbe96f","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v12.16.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.16.1","dependencies":{"terser":"4.8.0","cssnano":"4.1.10","postcss":"7.0.32","domutils":"2.1.0","lru-cache":"5.1.1","domhandler":"3.0.0","node-fetch":"2.6.0","cross-fetch":"3.0.5","htmlparser2":"4.1.0","dom-serializer":"1.0.1","abort-controller":"3.0.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.5.4","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.5.4","@ampproject/toolbox-runtime-version":"^2.5.4","@ampproject/toolbox-validator-rules":"^2.5.4"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.5.5_1593194116753_0.005102402873393919","host":"s3://npm-registry-packages"}},"2.5.6":{"name":"@ampproject/toolbox-optimizer","version":"2.5.6","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.5.6","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"cedf63e4b20ae03060f5fc317f726daf104cb38b","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.5.6.tgz","fileCount":47,"integrity":"sha512-HIMN7e8fGKsZ84r3dAC4L6ivIWVdTjUZfwS6ccEfm17ilt0go0zlUUAEHSV6A0BrQ237egFqGTO2VuefNNrldg==","signatures":[{"sig":"MEQCIADiHzUFGvDDJ9CyBwBACAOTB1yf3bPO5TG/GGA3+xv1AiAiYv+6Us08UrNCG9HKSFjtnTC+vI8YJvo4eqzQ7ilOgA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":212471,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfBKl0CRA9TVsSAnZWagAAilcP/iv4NjJ91jw2WzofYg36\ncSfCbwhc+qMGgOSuOj7qkbWlPDE8cNGDG8QsQz+qNodeshWblVNRpiQ2va0w\nNBVi2VuNLiHf8E7jz+5Ey+7RrumS0MR4u9dU7rA8f1WNxFF/BBS4bNjDqVNv\np+RvKedwdxTZTjmLA7/6SDjawPQMrgRrShG7gBpfuoyxVXo/lJRpP5r1+S8D\n/YvN+DGNhbE5aTjM7zso4i4CXE9p5h2jgfSA18NO2yaTuV2P0rDHoC9VwGlV\nmjB7rcZTQZvEwsf6MCsQery9SNVeSH1If8Ik8yaGvp46js/Xh/dXKInj9eOp\nLccyVqludMMW/aT4GokVlXim4Khbwm1IueqFWmF4ctFyf4t6ylYwSy4G1zwe\n52/522CmbGz+c0wh1xmBODfD25eZC1qqiDbYU4DRsjUq6nc6jBMwhijGjWin\nPICWMaWwS1vn1lEL+SYPBODs7TBC0MbbLj7eyfJxi4tAcneUPs7O6N/bf4UN\ngwe85By4LTw/lx0MWng4lxzvzVLmbrFATNYKvHzrrc1llE1dSnyb+iAb9hOJ\nhn8vd0JaYyINBjwxNL7wjoLTd+CNHPYOnvhqDC5V7bVikSJSoPQ1wCPED835\nA1TLv0P5zoXPcFCjtzbm3uoSu8Le6aHSxkJi7OEQP4dJNvPKqceHtiFF+1O8\nxSQI\r\n=riuV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"f756b54ffe76e288ae7f62755a4338183bfd91bf","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v12.16.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.16.1","dependencies":{"terser":"4.8.0","cssnano":"4.1.10","postcss":"7.0.32","domutils":"2.1.0","lru-cache":"5.1.1","domhandler":"3.0.0","node-fetch":"2.6.0","cross-fetch":"3.0.5","htmlparser2":"4.1.0","dom-serializer":"1.0.1","abort-controller":"3.0.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.5.4","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.5.4","@ampproject/toolbox-runtime-version":"^2.5.4","@ampproject/toolbox-validator-rules":"^2.5.4"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.5.6_1594141043848_0.8008141350803095","host":"s3://npm-registry-packages"}},"2.5.7":{"name":"@ampproject/toolbox-optimizer","version":"2.5.7","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.5.7","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"99082d6509f548fee9b886742d6ff118eab5bc24","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.5.7.tgz","fileCount":47,"integrity":"sha512-B8UR+LgegMtTAzsmXeCwYFCZvM5EZYI5S7vge4XTRVRWzac/x8rxw6ow8dQKYnrI5pR2y7dWH44jMYPsDTfn4Q==","signatures":[{"sig":"MEUCICCcjlugo2RZYnRHLquHiWGPcxBUfgxtM7edjUM4JYflAiEA/bV7+8HYhnuW4MXPj8mEEbnDeA5GbAGmwp58YsKCrLI=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":212719,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfBYKICRA9TVsSAnZWagAAlboQAJq+NBC3W+hkGGTGf3HA\nCTxCdeHezie2BSmNdID59BkDLJGyYkr4SJckUNLbWuBE5eWOoynM4QkqxpnC\nVykVbi5fgpaRRoI9xvEj2U2nUeCumQxd69lsM5wVXh94bofRnwDVb8xoodM6\nSb7NYVHsIP368jCnFgLIpOHKJavOz+Qah8FpYgnlH2jY4NOlFliyyFYbaZh+\nxxOzQDfQDkCNesmYRjm0t1dx3OMKa0+UTKTVQ7TCDEqYZdIwiZKeXXwKpXf4\nQQXxJbWqoi1slSQryEdjwPM0URdoVyYtzchX2PGFgm/89Nf5SviE6HTlfD6q\nk2IeaG/zkscAtAJS0NEHBBxDJej6GRecas6OpO/Hh6CVu6iQlh4yyNvhbOKp\n3lVkJpHvGG2SGt9kWfRPdahGp5A3Z3uPRNPGVE4EHL+azCWDMbE8ryx4mbAE\nOkbpnGm7TXZrRG27atS1lwQenJI43pq4wtfhv8RDSrxitGDb/pEo5Nz5TU8G\nCwdBcr9KsO/66pn0x4hes9Ulr3Ht+ja15LRLb7yVYTqDqJDMJBcMrI+N2LL5\nAKYtkK/5kyq935VvoDEPDTAcXjzUYGwdLd50aGipyfWaCBj0HSxWuq21xR6o\nOOKjHed+UgNUFykEUM1lHO+wQPGHtQ+Kf8ceY7+DmzPZ9yiOoyujqMaMq4hW\nFdMk\r\n=UpMV\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"73647dd60013f39de32d6116eaa1403a82fd29d8","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v12.16.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.16.1","dependencies":{"terser":"4.8.0","cssnano":"4.1.10","postcss":"7.0.32","domutils":"2.1.0","lru-cache":"5.1.1","domhandler":"3.0.0","node-fetch":"2.6.0","cross-fetch":"3.0.5","htmlparser2":"4.1.0","dom-serializer":"1.0.1","abort-controller":"3.0.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.5.4","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.5.4","@ampproject/toolbox-runtime-version":"^2.5.4","@ampproject/toolbox-validator-rules":"^2.5.4"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.5.7_1594196615902_0.5250161803205202","host":"s3://npm-registry-packages"}},"2.5.8":{"name":"@ampproject/toolbox-optimizer","version":"2.5.8","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.5.8","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"917a43d3f136e237e393e5c198e9bd32469d9915","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.5.8.tgz","fileCount":47,"integrity":"sha512-0mkb+vnZ4M2aJEGhmYDLKUay5Z8jI9W92ZVv4WNtWOTXvRzUsT04QTazR4pfOaTu6o+aV75qKVFcINuTNwncpw==","signatures":[{"sig":"MEUCIGdtf1+3yWyfWNpzzp5l8qD8jz1dcCVGG+XxD3wtjC0EAiEAzeDgqE2FuRDdhAxQwPrGbCnID/c2aDmYwZqgJgrC7r0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":212668,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfBYy6CRA9TVsSAnZWagAALoUP/RhfuJBe3QQma2MyvHk6\njDbNc+ca9dchrlhVfrU3d//TXuxE6ebrK/nIa1USyjuXhPCttAjlVtd6ClF6\nEVDDBe7yioUx1NusnFpASxFjXmFsxO3eyKYpBGlxx3NBeiYgPYckoqnOJt/2\no6RLMZNaBQPjT2URH3dJznpmQ10J/3zHRwc2PKtGDXjySqEGyWyy/hI9st8c\n1pDJJsi8JOalcX7mJUVWnk2oGZ9svWvoudLI2BI39/lNkPerxsaPjy1C8IVO\nsUuophEjrECjsXWXlfNfDiMG4GLXJ3ioam4GD16cI9ZKTY4sGQio1SI4yirZ\nlRoVi47z7GxIODiEqeDXHK6Ut0HMpx48/uD9SOGKJ4ONaAukHysFBt4nbCqj\nmzhfvUApKvDuewvFtMVAvRbgphbM08R845OE1uBC6ws+nR4g1+upuw2ThCq3\nSo/AfZTwCiP0jy4q5G96rrqdLqAdC4ITCnSubxjKTZ0BdMNw7YR6Rd+6qT2C\nD4R8LRMN4/0wqMflPSA8QiMxakUvyPHk5sIlvhEtc0NC3dMfDUNWVXx8UzQg\nQ6AzaZHNpAUKRujhb07z6AOl1FyDr1EG14qC06t5CwnSjb6KAenagQsAZzzV\nd0EuJ6d7IERa/MdiB5vLfimJedesRrNwnDMK3/NkEQEEMYP6SIAto+gOvDCP\nVOOH\r\n=NHFu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"1165683f96b5de148f5756482daf95c1eec615f4","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v12.16.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.16.1","dependencies":{"terser":"4.8.0","cssnano":"4.1.10","postcss":"7.0.32","domutils":"2.1.0","lru-cache":"5.1.1","domhandler":"3.0.0","node-fetch":"2.6.0","cross-fetch":"3.0.5","htmlparser2":"4.1.0","dom-serializer":"1.0.1","abort-controller":"3.0.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.5.4","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.5.4","@ampproject/toolbox-runtime-version":"^2.5.4","@ampproject/toolbox-validator-rules":"^2.5.4"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.5.8_1594199225718_0.4468916825419784","host":"s3://npm-registry-packages"}},"2.5.9":{"name":"@ampproject/toolbox-optimizer","version":"2.5.9","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.5.9","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"6b7dc77bf3e61f5f02009d3fe477eff24719b236","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.5.9.tgz","fileCount":47,"integrity":"sha512-saNCHw7plgiP6CStl3HmXkK2SuPlMXCI6PG7dty2sDm0yqh74A82Cj2JMY9ucW9bq7HSsZa9Sq+cdVZfhvp9IQ==","signatures":[{"sig":"MEYCIQCSKUh+C+GCES91tox05YX8vquW+MuSjhHw885hZh9wfwIhAMRybv0oOGUvwqtYaLPD5bNCmrm3FBLW4NZbENxa5MvV","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":212937,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfBbpGCRA9TVsSAnZWagAAhOUP/3LoC3FyiyqQDzJe7YEG\nsnnvOmATobcJfvaSS31khO531KLpO4LXNzvKQNDIuX/wYGxF0zocaEDaNVFJ\niEPbHuZF2Xq+UCFyqBhzNlNrVIrGWcO2VTalGm563qGtKZlCC1oCBGPtybsU\nwFajCa7YltchhM90OlGvX+JrEyNZXgZPztfxEC2GujkJxE0WmynJEF1lkL5U\nfgokbI3bmkDJ1zW5RAFpUQgGDGZMiCSCG3GSY5Td9hIgcZsRNd2HGhQFgEJ2\nZGP0XllCYBZjjX6WWWqHZ+oYvn9q75OeZnBBRaatsGzSaS9I1U2il86Sx3cE\n9OGCCgk36t9Emr2sGcVF8T1Ylc02CyeMCbM8OIwUqrpaPKjKYHCHW7e5TiAv\nDVZT2QlUeH/0cxfXpeyM48i3v1LbYigEs2bQKigl9uW9veTMF254oirr1UzV\nnMOiUgdJAd6d/4PQu/unJB/F/b4J8PpSyjPYvQfWa1x3dfMxkAmNThxZmd9/\niNyNwZqvXIzWkbJJ9j0didtqaTVIx6yKwOzywbA9KJrh4jFRi8sx5JBjj49O\njbGUw1NLW8KImU5bTJR69leBPqhcI/v3iv8nnFPgp1mGvzKBZ+H1HjUmoypv\nPlHlV+pekhCiSVonBOWiX6g0FLfbgYKd2kolli3d/BBT+AmPND2LJLyZoiSW\nZ3Hx\r\n=3Y80\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"c5eed658dd3479ef8fca1e963109092f380f5633","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v12.16.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.16.1","dependencies":{"terser":"4.8.0","cssnano":"4.1.10","postcss":"7.0.32","domutils":"2.1.0","lru-cache":"5.1.1","domhandler":"3.0.0","node-fetch":"2.6.0","cross-fetch":"3.0.5","htmlparser2":"4.1.0","dom-serializer":"1.0.1","abort-controller":"3.0.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.5.4","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.5.4","@ampproject/toolbox-runtime-version":"^2.5.4","@ampproject/toolbox-validator-rules":"^2.5.4"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.5.9_1594210885493_0.8086415178286512","host":"s3://npm-registry-packages"}},"2.5.10":{"name":"@ampproject/toolbox-optimizer","version":"2.5.10","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.5.10","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"ec01f13ea85e5551e7e9520cdd2678a04869701d","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.5.10.tgz","fileCount":47,"integrity":"sha512-6e55oMOudjU4+PnBK6kpTZI+RzqooSIczYha/gdei3WBDhWeE41hSVuIrt9HZciLxXhjKbEalbeGGeM5nydvig==","signatures":[{"sig":"MEUCIHdcMCev+mhzwMof/WayF/HIcHNUSj8hKiZ8TRaDiGlJAiEAoUhTh2p3JQtSmxnJ6pjwWI58uv/imp9SRw2RB0c0sA0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":212869,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfBb9bCRA9TVsSAnZWagAAB6wP/1KVObjKlgX4HlDizO11\n5+1HxC6H0UdF7fxGHdxoBerPaTtkDTE1Xk774gewYyJbuKKggIZmkNTbhWcm\n+KPLaYdG/Ahw0vQBrXhV+z1d/Do2O92Nsit9cutrZ7+eKJbUHxV/WQLJaxXN\nPqITqoSluY7KLdIiftqbFM/esWLUnHTQrYf2HqCKIHXQ/WBSnCU8qEjuawJj\nBNic6znadLP7n2C4pbTTXVIc6EtKMSjQiRE6WRYw0ssxJMadjhoSH0MKUiqf\n5sLVMztpSMSUD+KwYbtqApd8xKyHCU0u5Yu2EMPGRaOVEqUTNvB09iSEm2XW\n2gga1NFPec+cLNlN9ioAkY47PPrhKQmx2qzu/M2jsZH4n53W94+a8UAOWzEO\nYUbYpK/XW5mfeUleYDSKMvM/Cl42z3fwFcyn/dTTQPNWh95yWudPNs2QKfBn\nJ2nchY/uozc5+w5Cc+BOJ4sktgIzeQZW10x+lCa0YS4JMfNp/LHVqZuuHeSf\nt8pw+5+Rs9fpAxmaGNGsYqTrJZ8Cj9Rkh/fzmdaLUdcPpsB3rfKox5+80FY1\n/Xs0oskw7dog0HgYVQBzETQpzUGyQFPnU5HCUhc7bZDCKyr+QDvJdHJoWrgD\nXBPTKOpj6PwMnBgnCi62LDXwk2gTUtgAOrFcJZ6QfCzzfRh/wty5y9sOW2QF\niyyO\r\n=PTnO\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"381ff5ab711f2560ef222c28a05f17edf18f3cf2","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v12.16.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.16.1","dependencies":{"terser":"4.8.0","cssnano":"4.1.10","postcss":"7.0.32","domutils":"2.1.0","lru-cache":"5.1.1","domhandler":"3.0.0","node-fetch":"2.6.0","cross-fetch":"3.0.5","htmlparser2":"4.1.0","dom-serializer":"1.0.1","abort-controller":"3.0.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.5.4","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.5.4","@ampproject/toolbox-runtime-version":"^2.5.4","@ampproject/toolbox-validator-rules":"^2.5.4"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.5.10_1594212186880_0.12407339216343538","host":"s3://npm-registry-packages"}},"2.5.11":{"name":"@ampproject/toolbox-optimizer","version":"2.5.11","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.5.11","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"a8b004caeffc66b2c4a06a975cb8d387cf058662","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.5.11.tgz","fileCount":47,"integrity":"sha512-P1sLd6whsxv2hGejMWm5AxDb6svSikjLikfcMMgbeV/s4sQvI0W56TnG9jhrOnFO7tXxPcRlyu0wAgMZmLCuPg==","signatures":[{"sig":"MEUCIQC9UCO39UDECJmo26T9RyrA8z1xgWH7XjqHCPXZAtPjewIgY011qL4yUOKmIWdxyx9rRi5E0icn0x61/2hoAv5Ou8k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":212924,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfDZ25CRA9TVsSAnZWagAA1PAP/AoXFQUZ42rbPkwSf7tn\nsIMFmhTk8gEkTcBfy0hwy2RiB07pb7rf+kgcOpCyh23Sk7+KvdiDOBAFrRwj\nbC1xd6i4P4IXwU1pujFt9s9rPdg7S0aKMj9nvgAF5VjfNxDN6YxKQznCfBYY\n81n+tcmiZPDsHBhm8R21Q9qG8pmyYrF3APsjhYLlBAWx01dBRTKpu54mDTv6\nt5SGAyLlmLfoC6fafue/RBUi0UWLFj867WPQuvSTxp9zUl5sAFGK8Q2djzl/\nKZBujNWkmoowuRQ3Y6XIFSbKp1HLKueEvecJB/jHkgHHIzymvzxE6OkSoRb/\nrPbAKpHld993KyBQPCiy0O64HmiSNvtOZiMlAnq6uaPj6lBz2sNXEeO5f8iA\nlA6j+YZ+TBXG6MQcRnracf5L7Gp6b7mUU26Acmdaq9udAgO1nBkCwRUIKeGs\ndzvwEoI5XX2BKbkstyo6oCxDxtQArFKblwO6x2/YkeJ26m3KlZ/PNFdpYDrR\nYHd6Z2TCGXqDuvfIU2A1ZxR83Oamjv3B/F+T7gVqF1tfVb/E0WlswbO06KNs\njpcnxm0TYzbdQr8pkH3y1UFroKTztK9kfLovvdKoA73tv2bv5nsL0eguxics\n2xS0TSafLZst7LB7uPl7bO5pmItgf2L/yV3IPNryaqLmPdhOWteZALwBw/hU\nEJY8\r\n=JyTM\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"f886f72aaf198efaccf2f1a0e178c05591715bc5","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v12.16.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.16.1","dependencies":{"terser":"4.8.0","cssnano":"4.1.10","postcss":"7.0.32","domutils":"2.1.0","lru-cache":"5.1.1","domhandler":"3.0.0","node-fetch":"2.6.0","cross-fetch":"3.0.5","htmlparser2":"4.1.0","dom-serializer":"1.0.1","abort-controller":"3.0.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.5.4","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.5.4","@ampproject/toolbox-runtime-version":"^2.5.4","@ampproject/toolbox-validator-rules":"^2.5.4"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.5.11_1594727865454_0.6705335026091968","host":"s3://npm-registry-packages"}},"2.5.12":{"name":"@ampproject/toolbox-optimizer","version":"2.5.12","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.5.12","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"9dcf1f9977e048bfd01269a55a400e595b413abb","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.5.12.tgz","fileCount":47,"integrity":"sha512-t42VPeXaVmtx+gEhSUQvOtIR2nuLJXQ6YGvne9hTQleUZjslU/B7wIf2gDBCoHkS9nYEzS1lx70sTCQnfIYxWQ==","signatures":[{"sig":"MEQCIFL/bXEH4HFs20V+ZUNvwaEOePyE7yCQBVvVTB+cjicCAiATZ+iHEinJFrik1/W/zyL9O9O+e9N4kOZrc/V/9K6NqQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":215923,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfD01NCRA9TVsSAnZWagAAiRwP/3KxkmEGz+XfXa/2gif9\nhUVB3MToaSFwWsgjX7aNVZKFOE7re+44pyH0jqjThIkW8D++FusxRwTN55zk\nX+eY2zmWXD8ETgw7VYLRe0ZjMy7r1kiWVrMQfb30vvd8t+rLKqIra1n9NkSz\njrAlwOcxfiokcrtfTLO1AuChHekZrfvFiQXYWJiVk1hXsS7fLdbSvQuVeJfn\n0xoQHQOze64qpTcN6LsDfY+Tu6t03XRkC9LE/OkPHaEo2GK1JTzQ9h00YG+G\nxi+Ng2Fwu3gm1ZtMnmCDpmfDj2ofRA7ZH/85kH8f/ESaGbHE+BISpDA7uhzm\nYpghO4rQoQYSorTbsyEsZCKj/JkUYcKhen4OLj4bCHLmuKZbvD21I6LyEaPX\n+ALf3A65+LL8zAv3aek/DAnj8CO9lqM+8dYIEx3BuGzXoR0QASt0si2Gnddd\nZ+bLbrtzmatOnnNGB3ITQfdzbvKrKf2KIB3lsT4wwXo46U1W/tOIKGiOIPaT\n1MlKwZ18sitE7PT7HJwjc7zRITcBNQRPbm8v52FbMMsDhD5UvSg0322AfLRK\nSGOOhnlMRqiHwP4z6yC1JvdYAF1nr3BbVQdLXWYsQlUoKbeWB7a0eSD24i+g\nZzpyyXyVdRXvtJDbk63c6D/bpjTDRTjGw4Uvkvgl17NA6sxY2uDtCIE0iwHn\nUy+u\r\n=RpQa\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"19ef3dffbecac926ab5f60bb4c041f258e350237","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v12.16.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.16.1","dependencies":{"terser":"4.8.0","cssnano":"4.1.10","postcss":"7.0.32","domutils":"2.1.0","lru-cache":"5.1.1","domhandler":"3.0.0","node-fetch":"2.6.0","cross-fetch":"3.0.5","htmlparser2":"4.1.0","dom-serializer":"1.0.1","abort-controller":"3.0.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.5.4","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.5.4","@ampproject/toolbox-runtime-version":"^2.5.4","@ampproject/toolbox-validator-rules":"^2.5.4"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.5.12_1594838348950_0.13470976756897524","host":"s3://npm-registry-packages"}},"2.5.14":{"name":"@ampproject/toolbox-optimizer","version":"2.5.14","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.5.14","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"3235919e730e017ec4e54718e76ca5db8bd003e9","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.5.14.tgz","fileCount":47,"integrity":"sha512-UTXKVwDKn/xsNOHYroFUtQRxnukdZuiYj7iM1rCAvRmDT9MqrtMTVG8boGjxt5aHLgCnrUZEMz+8d61iBYZeDQ==","signatures":[{"sig":"MEUCIQDOb9+YB5mUdEv9/a63BeEzkYwJE/O0SfnDZCmKZYvTqwIgIMUTIgZT4yB6CrW2HQlCkP6j3TPQrBX3kCn3A49J7N8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":216197,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfGoS7CRA9TVsSAnZWagAAQqcP/25/8UdmT0CKT6bIk5Dg\nBg6gCXTN41l03nX37eQWDWl950oOGZqqxVx+cKAQLfs0/de3QvSui8KIRQxC\ngz+nF6rFtnJsqt8fl2Eq/SQKrduDqddejFb44TfVOgzsdPmN/LyEfjN4UcxW\nJR4DkjVTBbZMdskdd89BszAsCL0Q423s0kbZ4/mB9wNGBzrXq+STyUwYBcy8\nX8RzuY5DjmylLMAzdta0mo/Szl9vm6Yy2wqarWY+6hO9Ql/6VcVg7qUzqZ3P\ntNMnZ4/jvegvNEFQ4R23bW+O1KzR25S3W/vqIo4bwjjy93gVRrxuJfs24nVX\niUHbQ1oTJcP8Ik1vxAbnOTUcT+1xcNh6OIy7nQXeXupx9Ed0MrcoQoOJDDZ6\nwy8+4xEKefoQVL3pZ9FfVSzTIwZ23+rk3uTuWKbRM/2/ygf2lLHaoWxf3+5x\nUQtBf38AokauwYU6jEorB3ia55Vpol/bD4EZDBuoV4ukWpDi7rZL1DdckJc7\nbqMZ3oJPK7At9vHp5GFGMRzjOxMMjGy9gptLXOne7Dww935rsCSAQOo+QMVt\ne2pNR08hREbQ84G/rrYGymOU7Ah5jdxBoT9kIlFAQtjA4szttyT5C/fzUOhz\niw8vGQjd8O4pL55gUu5zTMm2U48qTcJ6AaR+ZbKh2ikN6BbCi/K+cnFl8xwJ\nNp/r\r\n=suZ8\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"bf92d77fd1f53c54519bf8bed2be05b0bca26004","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v12.16.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.16.1","dependencies":{"terser":"4.8.0","cssnano":"4.1.10","postcss":"7.0.32","domutils":"2.1.0","lru-cache":"5.1.1","domhandler":"3.0.0","node-fetch":"2.6.0","cross-fetch":"3.0.5","htmlparser2":"4.1.0","dom-serializer":"1.0.1","abort-controller":"3.0.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.5.4","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.5.4","@ampproject/toolbox-runtime-version":"^2.5.4","@ampproject/toolbox-validator-rules":"^2.5.4"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.5.14_1595573434699_0.9157780967581117","host":"s3://npm-registry-packages"}},"2.6.0":{"name":"@ampproject/toolbox-optimizer","version":"2.6.0","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.6.0","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"e1bde0697d0fb25ab888bc0d0422998abaf6bad1","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.6.0.tgz","fileCount":47,"integrity":"sha512-saToXVopb15a6zKK6kW4B1N/sYZZddkECcqmfTotRxJ2DaLE+wFB6jgWLbaPkgHwvLPQyA2IjV9BHJ/KUFuGzg==","signatures":[{"sig":"MEUCIQDLPJjbCEUmAcsD/62yhdMIMS3hmq290J+mU8FaxO5FjgIgGAKYh80sWqagbKPD5uMJNXGaQ6UfegvhIwiQAgiumv8=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":217129,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfMUETCRA9TVsSAnZWagAAsswQAIzkxCVaEtXaqlhfbGGY\nL/s/ti0V29eXGs7Nhma59ldWthbjQe3UtRWVzy/DvLV5ne3OA0atZDNpDABk\n3SApmn+LSgJY0lwd8rMnej3LY99w6VkKVO9oM/ByTDk9Xvd3yCsWFlaV6ZVv\nHBdg2KRjHqToKyibEc2B4MrbxwA/AglbourC/MYuEYjWJXu7GAMJ5VHRZB+6\nIQoVpc3vhLwHKtpOc6PyT8Ok6gNXPnM7NLCDm+JsDLszwaiwE61SNEiSOCEw\n4bY53vhqTT70tU0FkBUFLpwHwQI/fjZP6ajSa2rybePPn8k4OMNVm2Jfpa/L\n2sF5vdIQ8eMvwMacjPj9qcqnUyAJQOTS03IQ/gv9x/6UvmwQ+NsI84fxi1ue\ni+2XdFoCrisY0vpbtx4babvJ3DylYLe77/rvLhh//qpZxakIdJeqlcKWeqvq\nMWemjrCT8J+2ZkvffaLO5X0EYWy2e03w3QfJiGErsTTP5QvmkrxtQvGM9N2r\n0+zeU+2TWnutXjK5N5/yrJ/VTmmkqhfYceZwOjgLMOFhMjWCnFo0+akw5POb\n3T2eosAJdvdnHbPX/LKy33TROYSCqRsPbgGA9IiC+CGu7qmhSSWaDSUciozb\nZmSx+ja41qusdFYZA5qeUDxjozxZoTRf0ziNu2BU2/elOz+WNKUY0RuQ2IXa\n+l3g\r\n=HPwh\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"1e47f01b6630a01581b7df014b76d9ba0f68542c","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v12.18.3+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.18.3","dependencies":{"terser":"4.8.0","postcss":"7.0.32","domutils":"2.1.0","lru-cache":"6.0.0","domhandler":"3.0.0","node-fetch":"2.6.0","cross-fetch":"3.0.5","htmlparser2":"4.1.0","cssnano-simple":"1.0.5","dom-serializer":"1.0.1","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.6.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.5.4","@ampproject/toolbox-runtime-version":"^2.6.0","@ampproject/toolbox-validator-rules":"^2.5.4"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.6.0_1597063443005_0.9964105648964652","host":"s3://npm-registry-packages"}},"2.7.0-alpha.0":{"name":"@ampproject/toolbox-optimizer","version":"2.7.0-alpha.0","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.7.0-alpha.0","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"67b00be57aa06e95e8411399134d81ef9acd7c55","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.7.0-alpha.0.tgz","fileCount":46,"integrity":"sha512-fbnob3krLAZPSyuamVaUWTukGlJPgEjuMcgzlT0sGaJmD7YTVPMaBmdkOSh4sXkwV/ydEz8itwNeku5PAkbgfw==","signatures":[{"sig":"MEYCIQDnRxBU1IPhDulZ3jz9/tqdfj1JLhrxDFRMSHWx4RY8kQIhAOwZa3hWpmYglCmQN6FdTkB3iAOMoFCeJivRcfA9BEPK","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":209002,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfNuOZCRA9TVsSAnZWagAAoXEP/Rlo+BeAl7iiJhptWMms\nL8/JCW/RHIhuvz41oZLwv+mAMcme95a8P1rgMGWKW+BniyK8d3ZfTbTuTfXK\nLje5A86qRt0/ZGGVQf5DYFtbXqCvCxXU0bSAt5OXPS9w0OJPgvTMSNI69rWn\nx+cYmMOpMD3dJwO2bxBjx3QjXSwRy+XXFNoMCjROA2yD3GVQpj7bYsPIeTGS\nfql3McxMmGt/QjnOPvtQWVE9fkYLlKQSI9upEF1VQrvOuKPHz5eF5kjRIiR/\nwzRsGInWMdrrhT1hqp9Lv6++ihRWRu4dGVwK0DBtjEjXDt0IMlhQbfBXGv0B\nZVzghADhqr481o5Z2cU4TGPnX60VqER2oydHlPy2qKpVctTAfu6efr+OWzdV\na6fLvMCiB5RZOdhLF0v5FjAxAwy+j5T/f46cimDH4g26CSz7VyU6sdGpgA9b\nmnkuta9eJMzfQGha3cW70xsSb5puufgxOiVPyRkGtns1LwAQ3Gg7xGsz0VO0\nE5XT8BAtxfXzP3ZcsIQUqW3HWPBuUHyyOZfN+dSAi8cyQ7V/fU8MW2sK15WO\n8nCaU1Ll/sw2TynBaO08wqAzOwwzfhzNKhF42TndPnoR3zDODx4GBisrZKot\nFS4Me4XondpEDDbjPl0a/Zs26NLjSLjB49t9pQxfzj9X/MzZtLgS+Iycmfnd\nZlBZ\r\n=8w89\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to simplify creating AMP pages and improve AMP rendering performance. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n- [Server-side render AMP layouts](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/).\n- **Automatically import all missing AMP component scripts**.\n- **Automatically add any missing mandatory AMP tags**.\n- Auto detects and preloads hero images from amp-img, amp-iframe, amp-video, or amp-video-iframe.\n- Remove the AMP boilerplate (when possible).\n- Remove not needed whitespace.\n- Extract and move CSS keyframe animations to the bottom of the page.\n- Optimize AMP framework and custom font loading\n- Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n- AMP Optimizer will produce valid AMP.\n- AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\n### API\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>`;\n\nampOptimizer.transformHtml(originalHtml).then(optimizedHtml => {\n  console.log(optimizedHtml);\n});\n```\n\nYou can find a sample implementation [here](/packages/optimizer/demo/simple). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](/packages/optimizer-express).\n\n### CLI\n\nAMP Optimizer can be used via the [AMP Toolbox CLI](/packages/cli/README.md):\n\n```shell\nnpm install @ampproject/toolbox-cli -g\namp optimize myFile.html\n```\n\nor run without installation via `npx`:\n\n```shell\nnpx @ampproject/toolbox-cli optimize myFile.html\n```\n\n### Options\n\nOptions are passed when creating a new AMP Optimizer instance:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  verbose: true\n});\n...\n```\n\nAvailable options are:\n\n- [autoAddMandatoryTags](#autoaddmandatorytags)\n- [autoExtensionImport](#autoextensionimport)\n- [fetch](#fetch)\n- [format](#format)\n- [imageBasePath](#imagebasePath)\n- [imageOptimizer](#imageoptimizer)\n- [lts](#lts)\n- [markdown](#markdown)\n- [minify](#minify)\n- [preloadHeroImage](#preloadheroimage)\n- [verbose](#verbose)\n\n#### `autoAddMandatoryTags`\n\nAutomatically inject any missing markup required by AMP.\n\n- name: `autoAddMandatoryTags`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `autoExtensionImport`\n\nAutomatically import any missing AMP Extensions (e.g. amp-carousel).\n\n- name: `autoExtensionImport`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AutoExtensionImport](lib/transformers/AddMandatoryTags.js)\n\n#### `format`\n\nSpecifies the AMP format of the input file. Defaults to `AMP`.\n\n- name: `format`\n- valid options: `[AMP|AMP4EMAIL|AMP4ADS]`\n- default: `AMP`\n- used by: [AutoExtensionImport](lib/transformers/AddMandatoryTags.js), [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `experimentEsm`\n\nEnable [JavaScript Module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) support for AMP runtime and components. AMP Optimizer will generate module/nonmodule script imports for AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n**Warning: this will result in invalid AMP pages.**\n\n- name: `experimentEsm`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `imageBasePath`\n\nSpecifies a base path used to resolve an image during build,\nthis can be a file system path or URL prefix. You can also pass a function\n`(imgSrc, params) => '../img/' + imgSrc` for dynamically calculating the image path.\n\n- name: `imageBasePath`\n- valid options: `STRING|FUNCTION`\n- default: undefined\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `imageOptimizer`\n\nEnable automated image `srcset` generation by providing a function for calculating `srcset` URLs for a given image `src`. The function should return a URL pointing to a version of the `src` image with the given `width`. If no image is available, it should return a falsy value.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  imageOptimizer: (src, width) => `${src}?width=${width}`\n});\n```\n\n- name: `imageOptimizer`\n- valid options: `FUNCTION`\n- default: undefined\n- used by: [OptimizeImages](lib/transformers/OptimizeImages.js)\n\n#### `lts`\n\nUse [long-term stable URLs](https://github.com/ampproject/amphtml/blob/main/contributing/lts-release.md) for downloading the AMP runtime and components.\n\n- name: `lts`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `markdown`\n\nThis transformer adds out-of-the-box markdown support. This allows\nusing AMP Optimizer to convert HTML documents created from Markdown\nfiles into valid AMP. A typical conversion flow would be:\n\nREADME.md => HTML => AMP Optimizer => valid AMP\n\nThe only thing this transformer does is converting `<img>` tags into\neither `amp-img` or `amp-anim` tags. All other Markdown features are\nalready supported by AMP. The transformer will try to resolve image\ndimensions from the actual files. Images larger than 320px will automatically\nget an intrinsic layout. For image detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n- name: `markdown`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `minify`\n\nMinifies the generated HTML output and inlined CSS.\n\n- name: `minify`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [MinifyHtml](lib/transformers/MinifyHtml.js), [SeparateKeyframes[(lib/transformers/SeparateKeyframes.js)\n\n#### `preloadHeroImage`\n\nEnables hero image optimization. Hero images will either be auto detected or you can explicitly mark these by adding the `data-hero` attribute:\n\n```\n<amp-img data-hero src=\"foo.jpg\" ...>\n```\n\nThe maximum number of hero images that can be marked up using `data-hero` is `2`.\n\nIf no `data-hero` attribute is present, AMP optimizer auto-detects hero images for `amp-img`, `amp-iframe`, `amp-video`, or `amp-video-iframe` and injects a `link rel=preload`. Image preload links will only be generated if there is none already existing. For `amp-img` elements, it will also server-side render the `img` element inside the `amp-img` element. This greatly improves image rendering performance and reduces the [largest contentful paint](https://web.dev/lcp/) (LCP) metric from [Core Web Vitals](https://web.dev/vitals/).\n\n- name: `preloadHeroImage`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [PreloadHeroImage](lib/transformers/PreloadHeroImage.js)\n\n#### `verbose`\n\nEnable verbose mode with more detailed logging output.\n\n- name: `verbose`\n- valid options: `[true|false]`\n- default: `false`\n\n## Features\n\n### Image optimization\n\nAMP Optimizer helps you serve optimized images. For this to work, you need to provide a function that maps an image `src` to a resized `srcset` source value. The image resizing needs to either happen at build time (e.g. for static sites) or via a image hosting service such as [thumbor](https://github.com/thumbor/thumbor).\n\nHere is an example implementation that appends the image width to the `src`:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  // parameters are the amp-img `src` and the `width` of the to be generated srcset source value\n  imageOptimizer: (src, width) => {\n    // we cannot rename if the image does not have a file extension\n    const index = src.lastIndexOf('.');\n    if (index === -1) {\n      // return null means we won't generate a srcset source value for this width\n      return null;\n    }\n    const prefix = src.substring(0, index);\n    const postfix = src.substring(index, src.length);\n    return `${prefix}.${width}w${postfix}`;\n  };\n})\n```\n\nUsing this implementation, AMP Optimizer will transform the following `amp-img` declarations:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img src=\"image1.png\" width=\"400\" height=\"800\" layout=\"responsive\"></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img layout=fill srcset=\"image-1x.png 1x,\n                             image-2x.png 2x\"></amp-img>\n```\n\ninto:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img src=\"image1.png\" width=\"400\" height=\"800\" layout=\"responsive\" srcset=\"image1.470w.png 470w, image1.820w.png 820w, image1.1440w.png 1440w\"></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img layout=\"fill\" srcset=\"image-1x.png 1x,\n                               image-2x.png 2x\"></amp-img>\n```\n\n**Important** when using `layout=responsive` use the `width` and `height` attribute to specify the minimum image dimensions. For example, for a full-bleed hero image on mobile, specify the width as`width=320`.\n\n### Auto add incomplete markup\n\nIt's possible to pass incomplete documents and AMP Optimizer will add any\nmissing tags and extension imports required by a valid AMP document.\n\n```js\nconst originalHtml = `\n  <h1>Hello World!</h1>\n  <amp-twitter width=\"375\"\n               height=\"472\"\n               layout=\"responsive\"\n               data-tweetid=\"1182321926473162752\">\n  </amp-twitter>\n`;\n\n// you can pass the canonical URL, default is `.`\nconst opts = {\n  canonical: '/example.html'\n}\nampOptimizer.transformHtml(originalHtml, opts).then((optimizedHtml) => {\n  // optimizedHtml will be a valid AMP document\n  console.log(optimizedHtml);\n});\n```\n\n### Automated Markdown conversion\n\nAMP Optimizer supports converting Markdown to AMPHTML. A typical conversion flow would be:\n\n```\nREADME.md => HTML => AMP Optimizer => valid AMP\n```\n\nThe AMP Optimizer converts `<img>` tags into `<amp-img>` or `<amp-anim>` tags when in Markdown mode. Enable Markdown mode via `markdown : true`. AMP Optimizer will try to resolve image dimensions from the actual files. Images wider than 320px will automatically get an `intrinsic` layout.\n\nAll other Markdown features are already supported by AMP.\n\nYou can pass an additional option `imageBasePath` to specify a base path used to resolve an image during build, this can be a file system path or URL prefix.\n\n**Important:** for image size detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n```shell\nnpm install probe-image-size --save-dev\n```\n\nExample:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst md = require('markdown-it')({\n  // don't sanitize html if you want to support AMP components in Markdown\n  html: true,\n});\n\n// enable markdown mode\nconst ampOptimizer = AmpOptimizer.create({\n  markdown: true,\n});\n\nconst markdown = `\n# Markdown 🤯\n\nHere is an image declared in Markdown syntax:\n\n![A random image](https://unsplash.it/800/600).\n\nYou can directly declare AMP components:\n\n<amp-twitter width=\"375\"\n             height=\"472\"\n             layout=\"responsive\"\n             data-tweetid=\"1182321926473162752\">\n</amp-twitter>\n\nAny missing extensions will be automatically imported.\n`;\n\nconst html = md.render(markdown);\n\nconst amphtml = await ampOptimizer.transformHtml(html, {\n  canonical: filePath,\n});\n```\n\nYou can find a working sample [here](/packages/optimizer/demo/markdown).\n\n## Extending AMP Optimizer with custom transformations\n\nAMP Optimizer supports custom HTML transformations:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst {createElement, firstChildByTag, appendChild} = AmpOptimizer.NodeUtils;\n\nclass CustomTransformer {\n  constructor(config) {\n    this.log_ = config.log.tag('CUSTOM');\n  }\n  transform(tree, params) {\n    this.log_.info('Running custom transformation for ', params.filePath);\n    const html = firstChildByTag(tree, 'html');\n    if (!html) return;\n    const head = firstChildByTag(html, 'head');\n    if (!head) return;\n    const desc = createElement('meta', {\n      name: 'description',\n      content: 'this is just a demo',\n    });\n    appendChild(head, desc);\n  }\n}\n\n// it's best to run custom transformers first\nconst customTransformations = [CustomTransformer, ...AmpOptimizer.TRANSFORMATIONS_AMP_FIRST];\n\n// pass custom transformers when creating the optimizer\nconst optimizer = AmpOptimizer.create({\n  transformations: customTransformations,\n});\n// you can add custom parameters on a per document basis\nconst transformedHtml = await optimizer.transformHtml(html, {\n  filePath,\n});\n```\n\nCheckout [the samples](/packages/optimizer/demo/simple/index.js) to learn how to customize AMP Optimizer.\n\n## Best Practices\n\n### Make sure to enable AMP Boilerplate removal\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n- if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```js\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true\n} );\n```\n\n... or for individual pages:\n\n```js\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true\n})\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length)\n    .replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](/packages/runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n- `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n- `imageBasePath`: specifies a base path used to resolve an image during build.\n- `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n- `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  // blurry image placeholders are currently not considered valid AMP\n  // hence it's recommended to setup paired AMP mode when enabling this feature.\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp'\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Fallback API for amp-geo\n\nIdeally, when self-hosting the AMP framework, `amp-geo-0.1.js` should be patched at delivery time to replace `{{AMP_ISO_COUNTRY_HOTPATCH}}` with the ISO 3166-1 alpha-2 country code where the request originated ([reference](https://github.com/ampproject/amphtml/blob/main/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem)). If your host does not have this capability, you can instead rely on a web API to return the country at runtime. The web API must be secure (HTTPS), adhere to [AMP CORS guidelines](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests/), and return JSON in the following format:\n\n```js\n{\"country\": \"de\"}\n```\n\nwhere in this example, `de` is the ISO 3166-1 alpha-2 country code for Germany.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  // this will instruct amp-geo to fetch the user's country from an API\n  // which returns JSON in format: {\"country\": \"de\"}\n  geoApiUrl: 'https://example.com/geo'\n});\n\nconsole.log(optimizedHtml);\n```\n\n## Development & Testing\n\nAMP Optimizer uses a snapshot based testing approach. To execute the tests, run in the project root:\n\n```shell\nnpm run test:node\n```\n\nTransformer tests are located in:\n\n```\n- spec/transformers/valid/TransformerName/test-name/\n    expected_output.html\n    input.html\n```\n\nThe transformation input is defined in `input.html`, whereas `expected_output.html` contains the expected\noutcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing\na transformer implementation, run:\n\n```shell\nnpm run test:optimizer:snapshot\n```\n\nto store a new snapshot version in `expected_output.html`.\n","gitHead":"e4141565bb5666ab03f26ae2b702f1a89715563a","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"caroqliu","email":"carolineliu@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v12.18.3+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.18.3","dependencies":{"terser":"4.8.0","postcss":"7.0.32","domutils":"2.1.0","lru-cache":"6.0.0","domhandler":"3.0.0","node-fetch":"2.6.0","cross-fetch":"3.0.5","htmlparser2":"4.1.0","cssnano-simple":"1.0.5","dom-serializer":"1.0.1","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.6.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.5.4","@ampproject/toolbox-runtime-version":"^2.6.0","@ampproject/toolbox-validator-rules":"^2.5.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.7.0-alpha.0_1597432728611_0.1984517617157744","host":"s3://npm-registry-packages"}},"2.7.0-alpha.1":{"name":"@ampproject/toolbox-optimizer","version":"2.7.0-alpha.1","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.7.0-alpha.1","maintainers":[{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"choumx","email":"willchou@google.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"samouri","email":"jzfisgreat@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"ab4c386645f991e5da5a9d2967ed2bb734a9f6c4","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.7.0-alpha.1.tgz","fileCount":46,"integrity":"sha512-2wTvOyM6GP6FrYQzxSQCg43STo1jMRGeDKa6YUkYXYH9fm9Wbt2wTRx+ajjb48JQ6WwUnGwga1MhQhVFzRQ+wQ==","signatures":[{"sig":"MEUCIAnl+e9+sWqnQsJvZCubiYC6UhjdVwBHl4IuXUK1pfuvAiEA7cgmz67RH17L9VSQduxHx2v3TAOOJJGNxVBZYcaXoyY=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":209844,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfPVWYCRA9TVsSAnZWagAA+3UP/R1lw0iEmM4m7ScwUire\n+/F2bp61rXrHIsVQrhZgNPiap578Jp2tXAhOT457ErBsf3dd/0bKkI6xvMmP\n+4t9101kHN1jWP8mDxCELTwR8NbbWIZhrtBPDQ6g9VXyssEp6vS58kYUtCCr\nP6hPNJmEggkRQPDnaM1CCf7EN0ySOe3P2NRztV5zi2xTchaSTDOdTb2se0WL\nOSPQuwYgu3V9/R8pBtnySWn2/UeysIB5BwRI4uoL7XE6I+eYYLwYu2VjLC+s\nFRDcolVh/totJOG0NxlPK31egrh1PMbtHeadUVShB67niq/bGgttFkunxOPq\nIs8dJN9F8MwzVNi+6SvLgvMynPJynOnH3N4CdqK5DmvhzUPcEzjfCUbGDKOf\n/zR0ZEUKGPJBx2NJqvc9TAqWxVjGrfJIyhbGks+xpvhdnU4tQxzE9ahDhO3k\nkOd30KDg2gqeaROQKyf6TjloJOGqgCsB1f3wrIeT0hWH1wydUZQuI/1UvBHF\na0PoFiPSJbN4w/upPfLbtkvY9nBxgxD0HfGPbBiq8aAwuC+ilBjkxGVZ9HTs\n7y20ZV9pH25ZYnw3AbhDqr24Zr3MjOQEG27y4FrhoKj9cpd30kHr/Z/ppHj0\nHtDh7UjuPpmUeDjBoOI4m+042Ut1HvkCvjfH0LJ1wgfeXdYIeyGREZnQ7oRu\nJJxN\r\n=gnqk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to simplify creating AMP pages and improve AMP rendering performance. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n- [Server-side render AMP layouts](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/).\n- **Automatically import all missing AMP component scripts**.\n- **Automatically add any missing mandatory AMP tags**.\n- Auto detects and preloads hero images from amp-img, amp-iframe, amp-video, or amp-video-iframe.\n- Remove the AMP boilerplate (when possible).\n- Remove not needed whitespace.\n- Extract and move CSS keyframe animations to the bottom of the page.\n- Optimize AMP framework and custom font loading\n- Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n- AMP Optimizer will produce valid AMP.\n- AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\n### API\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>`;\n\nampOptimizer.transformHtml(originalHtml).then(optimizedHtml => {\n  console.log(optimizedHtml);\n});\n```\n\nYou can find a sample implementation [here](/packages/optimizer/demo/simple). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](/packages/optimizer-express).\n\n### CLI\n\nAMP Optimizer can be used via the [AMP Toolbox CLI](/packages/cli/README.md):\n\n```shell\nnpm install @ampproject/toolbox-cli -g\namp optimize myFile.html\n```\n\nor run without installation via `npx`:\n\n```shell\nnpx @ampproject/toolbox-cli optimize myFile.html\n```\n\n### Options\n\nOptions are passed when creating a new AMP Optimizer instance:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  verbose: true\n});\n...\n```\n\nAvailable options are:\n\n- [autoAddMandatoryTags](#autoaddmandatorytags)\n- [autoExtensionImport](#autoextensionimport)\n- [fetch](#fetch)\n- [format](#format)\n- [imageBasePath](#imagebasePath)\n- [imageOptimizer](#imageoptimizer)\n- [lts](#lts)\n- [markdown](#markdown)\n- [minify](#minify)\n- [preloadHeroImage](#preloadheroimage)\n- [verbose](#verbose)\n\n#### `autoAddMandatoryTags`\n\nAutomatically inject any missing markup required by AMP.\n\n- name: `autoAddMandatoryTags`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `autoExtensionImport`\n\nAutomatically import any missing AMP Extensions (e.g. amp-carousel).\n\n- name: `autoExtensionImport`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AutoExtensionImport](lib/transformers/AddMandatoryTags.js)\n\n#### `format`\n\nSpecifies the AMP format of the input file. Defaults to `AMP`.\n\n- name: `format`\n- valid options: `[AMP|AMP4EMAIL|AMP4ADS]`\n- default: `AMP`\n- used by: [AutoExtensionImport](lib/transformers/AddMandatoryTags.js), [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `experimentEsm`\n\nEnable [JavaScript Module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) support for AMP runtime and components. AMP Optimizer will generate module/nonmodule script imports for AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n**Warning: this will result in invalid AMP pages.**\n\n- name: `experimentEsm`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `imageBasePath`\n\nSpecifies a base path used to resolve an image during build,\nthis can be a file system path or URL prefix. You can also pass a function\n`(imgSrc, params) => '../img/' + imgSrc` for dynamically calculating the image path.\n\n- name: `imageBasePath`\n- valid options: `STRING|FUNCTION`\n- default: undefined\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `imageOptimizer`\n\nEnable automated image `srcset` generation by providing a function for calculating `srcset` URLs for a given image `src`. The function should return a URL pointing to a version of the `src` image with the given `width`. If no image is available, it should return a falsy value.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  imageOptimizer: (src, width) => `${src}?width=${width}`\n});\n```\n\n- name: `imageOptimizer`\n- valid options: `FUNCTION`\n- default: undefined\n- used by: [OptimizeImages](lib/transformers/OptimizeImages.js)\n\n#### `lts`\n\nUse [long-term stable URLs](https://github.com/ampproject/amphtml/blob/main/contributing/lts-release.md) for downloading the AMP runtime and components.\n\n- name: `lts`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `markdown`\n\nThis transformer adds out-of-the-box markdown support. This allows\nusing AMP Optimizer to convert HTML documents created from Markdown\nfiles into valid AMP. A typical conversion flow would be:\n\nREADME.md => HTML => AMP Optimizer => valid AMP\n\nThe only thing this transformer does is converting `<img>` tags into\neither `amp-img` or `amp-anim` tags. All other Markdown features are\nalready supported by AMP. The transformer will try to resolve image\ndimensions from the actual files. Images larger than 320px will automatically\nget an intrinsic layout. For image detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n- name: `markdown`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `minify`\n\nMinifies the generated HTML output and inlined CSS.\n\n- name: `minify`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [MinifyHtml](lib/transformers/MinifyHtml.js), [SeparateKeyframes](lib/transformers/SeparateKeyframes.js)\n\n#### `preloadHeroImage`\n\nEnables hero image optimization. Hero images will either be auto detected or you can explicitly mark these by adding the `data-hero` attribute:\n\n```\n<amp-img data-hero src=\"foo.jpg\" ...>\n```\n\nThe maximum number of hero images that can be marked up using `data-hero` is `2`.\n\nIf no `data-hero` attribute is present, AMP optimizer auto-detects hero images for `amp-img`, `amp-iframe`, `amp-video`, or `amp-video-iframe` and injects a `link rel=preload`. Image preload links will only be generated if there is none already existing. For `amp-img` elements, it will also server-side render the `img` element inside the `amp-img` element. This greatly improves image rendering performance and reduces the [largest contentful paint](https://web.dev/lcp/) (LCP) metric from [Core Web Vitals](https://web.dev/vitals/).\n\n- name: `preloadHeroImage`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [PreloadHeroImage](lib/transformers/PreloadHeroImage.js)\n\n#### `verbose`\n\nEnable verbose mode with more detailed logging output.\n\n- name: `verbose`\n- valid options: `[true|false]`\n- default: `false`\n\n## Features\n\n### Image optimization\n\nAMP Optimizer helps you serve optimized images. For this to work, you need to provide a function that maps an image `src` to a resized `srcset` source value. The image resizing needs to either happen at build time (e.g. for static sites) or via a image hosting service such as [thumbor](https://github.com/thumbor/thumbor).\n\nHere is an example implementation that appends the image width to the `src`:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  // parameters are the amp-img `src` and the `width` of the to be generated srcset source value\n  imageOptimizer: (src, width) => {\n    // we cannot rename if the image does not have a file extension\n    const index = src.lastIndexOf('.');\n    if (index === -1) {\n      // return null means we won't generate a srcset source value for this width\n      return null;\n    }\n    const prefix = src.substring(0, index);\n    const postfix = src.substring(index, src.length);\n    return `${prefix}.${width}w${postfix}`;\n  };\n})\n```\n\nUsing this implementation, AMP Optimizer will transform the following `amp-img` declarations:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img src=\"image1.png\" width=\"400\" height=\"800\" layout=\"responsive\"></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img layout=fill srcset=\"image-1x.png 1x,\n                             image-2x.png 2x\"></amp-img>\n```\n\ninto:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img src=\"image1.png\" width=\"400\" height=\"800\" layout=\"responsive\" srcset=\"image1.470w.png 470w, image1.820w.png 820w, image1.1440w.png 1440w\"></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img layout=\"fill\" srcset=\"image-1x.png 1x,\n                               image-2x.png 2x\"></amp-img>\n```\n\n**Important** when using `layout=responsive` use the `width` and `height` attribute to specify the minimum image dimensions. For example, for a full-bleed hero image on mobile, specify the width as`width=320`.\n\n### Auto add incomplete markup\n\nIt's possible to pass incomplete documents and AMP Optimizer will add any\nmissing tags and extension imports required by a valid AMP document.\n\n```js\nconst originalHtml = `\n  <h1>Hello World!</h1>\n  <amp-twitter width=\"375\"\n               height=\"472\"\n               layout=\"responsive\"\n               data-tweetid=\"1182321926473162752\">\n  </amp-twitter>\n`;\n\n// you can pass the canonical URL, default is `.`\nconst opts = {\n  canonical: '/example.html'\n}\nampOptimizer.transformHtml(originalHtml, opts).then((optimizedHtml) => {\n  // optimizedHtml will be a valid AMP document\n  console.log(optimizedHtml);\n});\n```\n\n### Automated Markdown conversion\n\nAMP Optimizer supports converting Markdown to AMPHTML. A typical conversion flow would be:\n\n```\nREADME.md => HTML => AMP Optimizer => valid AMP\n```\n\nThe AMP Optimizer converts `<img>` tags into `<amp-img>` or `<amp-anim>` tags when in Markdown mode. Enable Markdown mode via `markdown : true`. AMP Optimizer will try to resolve image dimensions from the actual files. Images wider than 320px will automatically get an `intrinsic` layout.\n\nAll other Markdown features are already supported by AMP.\n\nYou can pass an additional option `imageBasePath` to specify a base path used to resolve an image during build, this can be a file system path or URL prefix.\n\n**Important:** for image size detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n```shell\nnpm install probe-image-size --save-dev\n```\n\nExample:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst md = require('markdown-it')({\n  // don't sanitize html if you want to support AMP components in Markdown\n  html: true,\n});\n\n// enable markdown mode\nconst ampOptimizer = AmpOptimizer.create({\n  markdown: true,\n});\n\nconst markdown = `\n# Markdown 🤯\n\nHere is an image declared in Markdown syntax:\n\n![A random image](https://unsplash.it/800/600).\n\nYou can directly declare AMP components:\n\n<amp-twitter width=\"375\"\n             height=\"472\"\n             layout=\"responsive\"\n             data-tweetid=\"1182321926473162752\">\n</amp-twitter>\n\nAny missing extensions will be automatically imported.\n`;\n\nconst html = md.render(markdown);\n\nconst amphtml = await ampOptimizer.transformHtml(html, {\n  canonical: filePath,\n});\n```\n\nYou can find a working sample [here](/packages/optimizer/demo/markdown).\n\n## Extending AMP Optimizer with custom transformations\n\nAMP Optimizer supports custom HTML transformations:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst {createElement, firstChildByTag, appendChild} = AmpOptimizer.NodeUtils;\n\nclass CustomTransformer {\n  constructor(config) {\n    this.log_ = config.log.tag('CUSTOM');\n  }\n  transform(tree, params) {\n    this.log_.info('Running custom transformation for ', params.filePath);\n    const html = firstChildByTag(tree, 'html');\n    if (!html) return;\n    const head = firstChildByTag(html, 'head');\n    if (!head) return;\n    const desc = createElement('meta', {\n      name: 'description',\n      content: 'this is just a demo',\n    });\n    appendChild(head, desc);\n  }\n}\n\n// it's best to run custom transformers first\nconst customTransformations = [CustomTransformer, ...AmpOptimizer.TRANSFORMATIONS_AMP_FIRST];\n\n// pass custom transformers when creating the optimizer\nconst optimizer = AmpOptimizer.create({\n  transformations: customTransformations,\n});\n// you can add custom parameters on a per document basis\nconst transformedHtml = await optimizer.transformHtml(html, {\n  filePath,\n});\n```\n\nCheckout [the samples](/packages/optimizer/demo/simple/index.js) to learn how to customize AMP Optimizer.\n\n## Best Practices\n\n### Make sure to enable AMP Boilerplate removal\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n- if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```js\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true\n} );\n```\n\n... or for individual pages:\n\n```js\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true\n})\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length)\n    .replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](/packages/runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n- `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n- `imageBasePath`: specifies a base path used to resolve an image during build.\n- `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n- `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  // blurry image placeholders are currently not considered valid AMP\n  // hence it's recommended to setup paired AMP mode when enabling this feature.\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp'\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Fallback API for amp-geo\n\nIdeally, when self-hosting the AMP framework, `amp-geo-0.1.js` should be patched at delivery time to replace `{{AMP_ISO_COUNTRY_HOTPATCH}}` with the ISO 3166-1 alpha-2 country code where the request originated ([reference](https://github.com/ampproject/amphtml/blob/main/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem)). If your host does not have this capability, you can instead rely on a web API to return the country at runtime. The web API must be secure (HTTPS), adhere to [AMP CORS guidelines](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests/), and return JSON in the following format:\n\n```js\n{\"country\": \"de\"}\n```\n\nwhere in this example, `de` is the ISO 3166-1 alpha-2 country code for Germany.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  // this will instruct amp-geo to fetch the user's country from an API\n  // which returns JSON in format: {\"country\": \"de\"}\n  geoApiUrl: 'https://example.com/geo'\n});\n\nconsole.log(optimizedHtml);\n```\n\n## Development & Testing\n\nAMP Optimizer uses a snapshot based testing approach. To execute the tests, run in the project root:\n\n```shell\nnpm run test:node\n```\n\nTransformer tests are located in:\n\n```\n- spec/transformers/valid/TransformerName/test-name/\n    expected_output.html\n    input.html\n```\n\nThe transformation input is defined in `input.html`, whereas `expected_output.html` contains the expected\noutcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing\na transformer implementation, run:\n\n```shell\nnpm run test:optimizer:snapshot\n```\n\nto store a new snapshot version in `expected_output.html`.\n","gitHead":"cdab35d31a9d431c66cda62174fb28aff93287eb","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"caroqliu","email":"carolineliu@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v12.18.3+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.18.3","dependencies":{"terser":"5.1.0","postcss":"7.0.32","domutils":"2.1.0","lru-cache":"6.0.0","domhandler":"3.0.0","node-fetch":"2.6.0","cross-fetch":"3.0.5","htmlparser2":"4.1.0","cssnano-simple":"1.2.0","dom-serializer":"1.0.1","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.6.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.5.4","@ampproject/toolbox-runtime-version":"^2.7.0-alpha.1","@ampproject/toolbox-validator-rules":"^2.5.4"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.7.0-alpha.1_1597855128310_0.49997249374248987","host":"s3://npm-registry-packages"}},"2.7.0-alpha.4":{"name":"@ampproject/toolbox-optimizer","version":"2.7.0-alpha.4","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.7.0-alpha.4","maintainers":[{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"choumx","email":"willchou@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"75d08a374fd5fb8662ae2d02db7fe4a9a3b9ea4c","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.7.0-alpha.4.tgz","fileCount":45,"integrity":"sha512-Ox3WAG1AaF+PoWmM5+W/lB959bpCCtz1+MPX03bD8+DMmLoy4qoS8ndG/EclKC3EI3rsDOQykP7SHu2SDN31cw==","signatures":[{"sig":"MEQCIADyEufIu+m8NCEPLdHvO5kCx5T+G9nUCg/S6D6H8w2FAiAU2WjkHeiq0Ci+sKtzvSKnjAulh1GQJVkNaivIJuldDw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":199106,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJflpzhCRA9TVsSAnZWagAA6wsP/0aJ4unkHE6j2XwIfDaU\nWNu051Aznl9EHD6vIahG6ekbqIIMs6G1wRiWRVw/A342YhPxID2jKhxyiM0e\nxis8ZoyMAV4nP/AcslbMCz+S2ByTZNAriPz2hCy03eDpmkuab7APFo1NPH1L\n+I8uRZfHktmg2GbxvI5aZj/5tPg2rrcXmSoKw/X7JNUnHsSG0SFisWoF6XDA\nOLXJwEDintl0jmLL3AsIsvMdwrrKV8DU1K5awa2y3Blk7kqMmPVG5JTu8sPi\nBinkCY4zd4floEQEwVv5lyIetAeMhcFqZH91Qdn6p5RmSXFecAqnDtEjV/xk\neba0mpuaD+VXqHfHtONBaLxrwbuKWX0mDX1h+JaTGkEsVmBXaBtKKHYBx6DW\nMJiuyeTaic3apxGeyOadjxRUMw7e82bCW9Xg6NPHnso5npPJHgKVtdZ7s3PJ\nhaS4cAKV0jm6bD7wobDIaz21ph1lhGyM0025CvoZtfdmwTuqIFb2I/J9vnZf\n+QkuBBeqs9e0KP14L+FC90dLAbdmIWEA2IFXQXG/BPksoFnLlMfSuDt37NM4\nZujP7Sf4DQmErpWJiWSjTRt22/Bi5k5iuaM4KyOYqvTI+ie2q7blsnRjFvVi\nKYG3U28E/M1edUuicu5qqaBbL+Pk9BN3M93ZDByR20hyxKxRe7CCjDa/SIHY\ngGXe\r\n=9jXN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"c0ba2297de1e3cd583018880e2ea6c7fe5fd7905","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"patrickkettner","email":"patrickkettner@gmail.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"6.14.6","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"10.22.1","dependencies":{"terser":"5.2.1","postcss":"7.0.32","domutils":"2.2.0","lru-cache":"6.0.0","domhandler":"3.0.0","node-fetch":"^2.6.1","cross-fetch":"^3.0.6","htmlparser2":"4.1.0","cssnano-simple":"1.2.0","dom-serializer":"1.0.1","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.6.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.5.4","@ampproject/toolbox-runtime-version":"^2.7.0-alpha.1","@ampproject/toolbox-validator-rules":"^2.5.4"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.7.0-alpha.4_1603706080523_0.71230957814298","host":"s3://npm-registry-packages"}},"2.7.1-alpha.0":{"name":"@ampproject/toolbox-optimizer","version":"2.7.1-alpha.0","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.7.1-alpha.0","maintainers":[{"name":"alanorozco","email":"orozcoalan@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"choumx","email":"willchou@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"1571dcd02608223ff68f6b7223102a123e381197","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.7.1-alpha.0.tgz","fileCount":45,"integrity":"sha512-WGPZKVQvHgNYJk1XVJCCmY+NVGTGJtvn0OALDyiegN4FJWOcilQUhCIcjMkZN59u1flz/u+sEKccM5qsROqVyg==","signatures":[{"sig":"MEYCIQCTiGtm/pM47VncqntloxObm3coZWfOLYPKr8FEZHi6NgIhAK48yzRjTQ/AoEb3GGab/YoksvaFla50Ft4Y6E6vjdCd","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":198920,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfxpVvCRA9TVsSAnZWagAAERIP/00nC3ecVSVfG/Vu8Rik\n4RbjYQeI5RCEa0+CIZbfc0SSftEKHbmOdj7JuuMAWNMr9xBPgDrWGLPr3cyb\nD8HDeqNL9/9h17+YI0AUblQu8viCb6UsE1wzgDEZ85pmo5RrU8yvmhIaLeev\nEnHSlAhQyTu7L/CVrznFRR5NWUpnhigraa8CDHLrLEfLrJcgdV8pesNy2zJG\nZ2cA0UrtLnQfwF+BRgUEnt7Ngri46ehm+l7avqI47nvjmenJLOsrbpaz3RpR\n9EwNtTQGyQgu0XU/rdfBxzZnaYLp56wWd5uePEFbDkaGoSarQ3H1GW5S+vcK\nCi+V5W7ysmbR9JHw8AoUHHMc+crUW3ivEF2xtJRBk2eqKr0/cDwO+PkxQA5F\ns/OWnmpCzPMpnHtEgipLECd7vQtrP/R2mEgiofI99eqjDcsD0xmiSzapsq5q\nALc5IinbDUY4xE/S0o2AwidPDnKqtz1Pw8LYFLLv0uneyw2ZBevk8KVWqRKM\nrjAQsLTPub7szh7BITTprczPHu3+g0m4g0tleRqFz4CdiwAGjRq5JKETc8C8\n2/1l/mUVCsQNdCmFDZoIVp4vBuLYYefOYq3zAdf7PirKEpemvdwzDP6nGzYx\ndvbhy0PIfg+6P7++lngfpS4EznVSKmUb0xEixwpQpy1lqD/9sQNHKcElpYaN\nSvbM\r\n=Yrol\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"c0ba2297de1e3cd583018880e2ea6c7fe5fd7905","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"patrickkettner","email":"patrickkettner@gmail.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"6.14.8","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"14.15.1","dependencies":{"terser":"5.5.1","postcss":"7.0.32","domutils":"2.4.2","lru-cache":"6.0.0","domhandler":"3.3.0","node-fetch":"2.6.1","cross-fetch":"3.0.6","htmlparser2":"5.0.1","cssnano-simple":"1.2.1","dom-serializer":"1.1.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.7.1-alpha.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.5.4","@ampproject/toolbox-runtime-version":"^2.7.1-alpha.0","@ampproject/toolbox-validator-rules":"^2.7.1-alpha.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.7.1-alpha.0_1606849902573_0.40521550292043207","host":"s3://npm-registry-packages"}},"2.7.1":{"name":"@ampproject/toolbox-optimizer","version":"2.7.1","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.7.1","maintainers":[{"name":"alanorozco","email":"orozcoalan@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"choumx","email":"willchou@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"8c1378d9f0e5a28a85634ea4f8d1738d68390729","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.7.1.tgz","fileCount":46,"integrity":"sha512-jBSbif9WpFbcIJWSXFjNemc83d/gMspRnYaSayM43HMDDIo2Je4k1dx9EN83fxXQN91Gr2WeXrcA4mIGocZkHg==","signatures":[{"sig":"MEYCIQC1TDdS+NnO1wR4Cc/wMmJ7ZReKfjmch9WoRbCwnkHVmwIhAM5LegxnQK76LXdZzFx5yHweP0/EIqVubeW+ZA0HaLP9","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":210271,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfx6rFCRA9TVsSAnZWagAAQS4QAJw01IZp8TZS+k3UEmWF\n07BH7efkMofwAoS9kspKXpv6s4QrEY0IU3G0RRlvgv4tkaMpRrr54g4GBWXX\n+d6nST8l4ZXkDbWdpdBey2+INhDG468cY7yucngAJQUNMqmY/uhzhngj7rPt\nRZgkXtJC7hKC72nJ60mrhJg3Rz//8xH6ejv5oJAILyae1wlIqMKBVxyrLPIU\nOTyF3eu2/IhFpyWjsVCikeV4tt1l3uhc0VF57fQ8kzIgO/c+MUBTp8YpZ3g6\nT5ilELcpjRIWRszMScGJ7q7szY41rQi76tOTmsiR4FEF3l1E6zoy2kLYz0AH\nyFkXaFcj3Ovp8ckenALnTLa62jjJrQIj63rXNi3J1lmTeRTaa1FJ4oB5wvUl\nAq0hJZdVfLKhZ/Gk6lSPyJy01I24mf7uORVWGxbiQJaiV8V1M0SlTjvHt4uJ\naX0bChGGy4a+OEQMKsxhzCTD3mTbJd4ZgADLQSOYQw+ZlfbEgoZnrGxtAk4U\nAPYfVCVtwL/wKJorDZQlxDai87IiIoJMPRxO/hNpRtglo0mTdgrsjIO+aFZ0\nNekezgG0OjuzPTtonRn+v9P0MHRudZeeGar45EO5MdG+xpYcLwqt7UUoiir1\nA8+11qM1dBYvH8/sX/PZ3AVDQ70GCTkRPaNCH8OAPMMisr8NRtyUfvYw2nwt\n55zT\r\n=qwUk\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"9b159391d776396512b01ec250fa76941713c763","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v12.18.3+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.18.3","dependencies":{"terser":"5.5.1","postcss":"7.0.32","domutils":"2.4.2","lru-cache":"6.0.0","domhandler":"3.3.0","node-fetch":"2.6.1","cross-fetch":"3.0.6","htmlparser2":"5.0.1","cssnano-simple":"1.2.1","dom-serializer":"1.1.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.7.1","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.5.4","@ampproject/toolbox-runtime-version":"^2.7.1","@ampproject/toolbox-validator-rules":"^2.7.1"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.7.1_1606920900899_0.07771983931203708","host":"s3://npm-registry-packages"}},"2.7.2":{"name":"@ampproject/toolbox-optimizer","version":"2.7.2","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.7.2","maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"e813b1a89356b48bea4abfcd85a611ea2cc872a0","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.7.2.tgz","fileCount":46,"integrity":"sha512-a9/vmIZ5xRY6keKOzLeEchoPHdhCAb1G98NnJXXPmGHglYYZ8AgmpcQ7Bh1DlY8N3KtZ26L5mJUqr+oSToo1FQ==","signatures":[{"sig":"MEQCIDCXVPc9iggdIcx59ETJn3TXhlbwAqGw45ETDSyRPTsLAiB4DS2RoGrUSk/VxXoPcmj+rj4nmbT4lxMWKhNtWJVgJQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":211153,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf2ROhCRA9TVsSAnZWagAAqocP/11ZMO7Yj/J5pwdA07+Q\n0NfY7yQW2lr7r26jud0xbN9vVo3Zp2ldpn/dYafc4mgTJA7Xf2gqyohAbxFR\nvUxdnlikssUTaXr5LaDKGZMQa71yH8NnRmSlYcinYNnh8lMEqK1ER0AgtZtV\nJ3q4KopyGb4vOu/+IgJIk10xiSrqddaqsCzoL+KIo3n6Zply5161wc3kEBeS\nWX+Uni+h43wrGe0TJBa/5vtSTek2YdRcH2wKw2gkPMb9HgcIuwXVzDc6mjlA\nX4F+McjD3JXw7pHIIzKsDyl18CHedutQ0rrStZ/QowyiUE1hTXVn1yb4HCrU\nbRq1/r2jEY6l+ymNduP2bD0DTtIn7pa8vY78tEjSsvCQbFZr6SOU4OsYnBHH\nTj6DBp2LOosYKJryWTc+PmjvBKBX1Rwnm+hr6ZKKI5AA0nIYQwez7fanrcmL\ntxNSCWC4uAnfQYM7WCPMWdbOeYGZjEHXcwdOUG9s4f3mFtJhrGnhSe9Fjao7\nVH9ZdIc6VfBnhwx5Nq0cNwEsK0yRMixtzNZf6HNG3EaUKgZPSFErIP5tdXqP\nuOfDwplIw3OyAd8W8WYpXHirD95z/YrxolMgKuo2rRd5ARWJfiRa3P2hAu5X\nRxb44chnj69+FuCrOPfjMA23p2iU+bj63vmI7gXXwriDd0++em9rq1DTjS3c\n1XQi\r\n=sl/L\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"83d1d4290d9da2683596506cff266c68e618fa81","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v12.18.3+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.18.3","dependencies":{"terser":"5.5.1","postcss":"7.0.32","domutils":"2.4.2","lru-cache":"6.0.0","domhandler":"3.3.0","node-fetch":"2.6.1","htmlparser2":"5.0.1","cssnano-simple":"1.2.1","dom-serializer":"1.1.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.7.2","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.5.4","@ampproject/toolbox-runtime-version":"^2.7.2","@ampproject/toolbox-validator-rules":"^2.7.2"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.7.2_1608061856655_0.04520239966816697","host":"s3://npm-registry-packages"}},"2.7.3":{"name":"@ampproject/toolbox-optimizer","version":"2.7.3","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.7.3","maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"e8be51874456587d857fb6be8e47a7fa37aae169","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.7.3.tgz","fileCount":46,"integrity":"sha512-Fq8w4xt2NxHIhPjvf4i2p1ILVKa1FCgycrxf0AfXMAcnMZs4uC1Ig3qv+TynK87BFe2dnS7qS44nAauixuEeig==","signatures":[{"sig":"MEYCIQDii/NiKxqQ1R0IdBDF0Q/OIw6bIDq5qkMc7xDyMed+9wIhANLeluWU1uvVKlBZv12jroY4DyQdMxKcHsa1F17XgGaR","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":211383,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf23VYCRA9TVsSAnZWagAAHbkP/iffUrlBwuGaO37MpE9m\n0ZQcKxoCuFOKbafoEmDPlPK9eERNzN9EVLoYMTDl1pUXwLS/UreB4BgFl1uW\n95HM7i+q59tXFAfb/PDQaPVQlDERr4rF9hyGpmJ65U/RNJa9dK4TVQHZqrAQ\nxIK1eVcjRMSsYOWd6pSUq1SgCfI3BcHfJ+qKsffVF5uNEAN0SmFD7FmEx2PS\ndxNXxIa7HZeTeUU5+Z19bDdIt/uybReLk6boqB0fXks+SqKGOSV683VICr6d\nSMpMZLGIEufCMiDlNS20KWw0E8RNE0nSl21ZEu8msBaHkLnx02zsqouda1gg\ny8Jbww/h4jipbodoZLv6YgZXkcntvwhF18+XmpZsqtwCi2B5Ucp5dSSerHVC\nB6qYt012l52EDSGLF3ON7izWyH5JJalbINsKXexJElNKNxfJTW6ziVwalsaS\nzOaZeLpP5fiWlZt7Xv3SfoImnij9t4qh57WOCusgUUyI0xxlkmWdY6uBJ8ZI\n11UuXkyJzN5FFs2gdH/Mbn+Vk+vtFyFPrerm/vBlGQwPwEWsyV9467Gb1IhM\nC0S2day7hU8djTxY5gIAwbRkCVlu5PaadlobYLN8es403MgNnLb76bIJgRE6\nJXO0frDHgziI63bhfJOy5fLb8fHxoDw/A9e/ZOXB5bwl4kOfOo8Em8I7twcZ\nLjBY\r\n=Yyh6\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"b0efede4ff9a6baa8173016f7c630d406e26d5e3","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v12.18.3+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.18.3","dependencies":{"terser":"5.5.1","postcss":"7.0.32","domutils":"2.4.2","lru-cache":"6.0.0","domhandler":"3.3.0","node-fetch":"2.6.1","htmlparser2":"5.0.1","cssnano-simple":"1.2.1","dom-serializer":"1.1.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"^2.7.2","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.5.4","@ampproject/toolbox-runtime-version":"^2.7.2","@ampproject/toolbox-validator-rules":"^2.7.2"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.7.3_1608217943834_0.7506013229656929","host":"s3://npm-registry-packages"}},"2.7.4":{"name":"@ampproject/toolbox-optimizer","version":"2.7.4","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.7.4","maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"5fa9ec8a464d9faf43a0ff1366d7e7c01c11d79a","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.7.4.tgz","fileCount":46,"integrity":"sha512-sffbmFg/gjWsv/DvaoPb7SiMiO75q2No7+HoR1l4A0s9PUTsukFo8/Fg0bDtKxlSYprbyinxKU1XNtaBf2KqzA==","signatures":[{"sig":"MEQCIDOCJH5HwXgH+lhhsVd84aA/7fBfKRTU5c3RUKkhfgq/AiBwKitErhxQAWuYK/ISVTIj8RHac/iW++Vl0vjZgFGAog==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":211382,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf4y8WCRA9TVsSAnZWagAAnREP/AkunmGrxNiZ+ySjkpW3\nxa083shtOeDrk6TEjjpbkMqSScBn7OYkfpxbfvzApsgnXo/HJZb/Eod8bZDF\nN44KGMV0tTiMFavWXdqaoWSFRbG+l9IRptVmhTRMfTA+iqU2aYrvhHAkOr9e\nzLrWoJrT3C81gI1mYEDV55Wlb7Q8eVzz+v6FLRbT4F2c2pNic9eL0X30T2aY\n44p8d4CpIrUm/l7YKoofgzBEklaP81Kaqc5gC/7xPE4dp+wuKqYahEbVDNqE\nmxjAAdjsY4eSYrjMNjFnqZ80P0OlZkuMt49ZdcURSniCGNwLeqnCdUPG+Aex\nRejH68oOR5MFCECx7lt/I9iZHkIHdF/0eeIO4dRoQ7JmX5Vc1mDJFvQ/z96q\n0+/uU0VORC6wRD394xTRGYtVA0UsgLaUI+/vC2T5BYoRcTyygvO382yyLazg\nf4owb5dQz4IXFHGf5O4D1sPiBtgUw2wozLcSsjQj79Rvw9wNbzfP4af2MAMI\nZKVLW0RXpPkWbBtyFR7rSbt7cnAeL2GWEUhhWxy0HTzceh5bYYDUuLteAf4O\n0vvSNm2H5ubZP+/XZU2ODKGL8PTg+LONwKkBw+GCTSe1LHk1WRyFI+W3xio4\nlbLFKjdTORjc4vNTurrJlqxHAWuobAsDzO3jwFs6w7MKwveEDEYD37NaJgQB\nJ3Wk\r\n=ZYy1\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"881ffbde5be5e78e039b132f0daae3a3598ae248","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v12.18.3+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.18.3","dependencies":{"terser":"5.5.1","postcss":"7.0.32","domutils":"2.4.2","lru-cache":"6.0.0","domhandler":"3.3.0","cross-fetch":"3.0.6","htmlparser2":"5.0.1","cssnano-simple":"1.2.1","dom-serializer":"1.1.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"4.0.2","@ampproject/toolbox-core":"2.7.4","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.5.4","@ampproject/toolbox-runtime-version":"2.7.4","@ampproject/toolbox-validator-rules":"2.7.4"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.7.4_1608724245542_0.28838146903162154","host":"s3://npm-registry-packages"}},"2.7.5":{"name":"@ampproject/toolbox-optimizer","version":"2.7.5","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.7.5","maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"afc4814216a52a4890c3d7e99ac8d1582efbd899","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.7.5.tgz","fileCount":46,"integrity":"sha512-JbLXA3TTiP6384IBtHrywM24A+E1db7EDnXOXWLq6INp9845y8L0sAyFtj4ZcYZh34u/8rxBfhIMa4ueLy+Lyg==","signatures":[{"sig":"MEQCIGvhegZicT6fMUl8uPbkeq7ur0/Tz2rBPHorimraOPy/AiA8cfASjCGDLTL8w1I0zHX3HcIoq8fhB4cc3bOIgombQw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":211965,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgBXroCRA9TVsSAnZWagAAOdUP/j5q6jaZ/oDoswCYvq22\n8GPWd7+pbSkujzynmf7tRkz8kKovtt5FE95MuH0kMb4G4LuQO2d1Iy2ci67V\n0cpi4BHkavJ13mTd320Cq0OZNuFcyJLaRqIaGz9QFjoyvOfvVrl9ZgKQZRHi\nAD7Sg+qbiVRG49Ree5pt97tz9C4iPO7m8+Q+RqYYyDhyTwraGV8HZHNUWRvG\nN38C2CXUJfxB0lBnfHQqaOwZZMSuf/3BnQp0B4mV3Ru6HokcMN/ZihLtwciD\nKRhi5TBECZWDfLJD1nnXpsrECqk426rDbjT9xNhlD2A9y6TYHuCKDjOLDVsi\nF8raURhlan3vfCcvgBlDwa2kIUcsKJx5Awp87SHFV4eJcpy01T1ch7ZoRaBI\nuFYCla86xNvNJuLmilu6wCymFlTTTedMkY0rZFb00m/2pgKmIV1D2FEhz2jf\nUrUQDvVDf5BB4+dNGF8LidFn0YZ59NXAEdDAoV6hyY6Nyu5Qzdaav1aIFsOD\nHVtjJqaVv+BA9favDv9byH5u8Nprwlul1u0rn7avHOVjXTRfLWkDzmOTADfq\nemtkSyNcsRWbJVR8eMqF0suZFtKXQLOWEKraON377NRAxK7JOTU06j/kllrq\n/FEwXs3snb69/yyQlCxiUxImcJ5TbxauDZLPT2MOuec7gNQB0w3kiHs9//M9\nhcfw\r\n=sY0M\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"b86ff10f2a20a9e5f810f31a694c7f020eb291ae","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v12.18.3+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"12.18.3","dependencies":{"terser":"5.5.1","postcss":"8.2.4","domutils":"2.4.4","lru-cache":"6.0.0","domhandler":"4.0.0","cross-fetch":"3.0.6","htmlparser2":"6.0.0","cssnano-simple":"1.2.1","dom-serializer":"1.2.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"2.7.4","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"2.5.4","@ampproject/toolbox-runtime-version":"2.7.4","@ampproject/toolbox-validator-rules":"2.7.4"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.7.5_1610971880187_0.40531051820472386","host":"s3://npm-registry-packages"}},"2.7.6":{"name":"@ampproject/toolbox-optimizer","version":"2.7.6","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.7.6","maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"dcab6a4cf0693ee2a17383e7c48cc6f94a35e50a","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.7.6.tgz","fileCount":47,"integrity":"sha512-vAz/gqM1Yi0vyZiYei4BTG3Opud6YoQiCYYjium2vuEU4d1zWYC8ZYhQNqwNZrUPViV9d5oxtpBjcWtTLKbckw==","signatures":[{"sig":"MEUCIQCp51GzKAXcpZcdH9p5zbKR2Q90FYU5oxIa23pQCBD9dwIgIBNPb77s9MP63E4BRI9um86KRNAsoQEMo4CrqiNZyiA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":214088,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgF/tOCRA9TVsSAnZWagAAXmUQAIKBZ+JUQLVC1jbGZPJp\nQkekwGvhhfhzLcPeXZ8hvHkZA4RMV8g7ul7UU/x2dzJy3IOv/KV46I1MqTLC\niHy3OCIIshzd5ATpomoM1ijGHvM+nGXEV+sb8qN4nx62lSwA4BGOFLYUX0ha\nM6pynnqN1CqILQgBUa7cSibtboGyuKTLJGJgJVtnypdVD+Al1h5HjYJRaULG\nORrGL54HK/BwuRt/YgnBLRcjXAHFVDBSxygbhfs+B7/1sGCy7NJeT5Awc7yn\n/NS3GnfQYMplkVKzqYbeSALzyvIYgs5tqt3jUemWd+QrQTEhSFIYFSSpadl2\nzvORjRcxsSuz6x410KW3b+SD6KuOioAubokTf9Fk2aqT0eMTEh0N7ZhDhz7A\n7ZbBd1pSwspH5snzdjyqrtemdVEDIgyCE40vOha5GpNGavFyWSBOfTntReu+\nGSt3vPogcl0CDT2/V1ZRQlIHV7lyI8fL704wRX5ZYBLwFQYDSLdFXiX8FUfS\n6bvSGz762jKBeTCeOJmZXP/PMbDEiwsCdnSLvSaJNkXp6jnutd+C+rjFioPj\nYoZj9PqKyQZmWqOpd6qZlgEpi33iXhm2h58Xc8+vTNF4/5hfBHjEz11Y1bLR\nmb6DXfZVeK5gXN4qo7l74jwMsOGOahzHuY+gQwNFh8P222QrmWkx7hGziIqO\nOQ3d\r\n=BeiC\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"bc8367e395b11f7b9ceb03562bd15982415a771d","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v14.15.4+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"14.15.4","dependencies":{"terser":"5.5.1","postcss":"8.2.4","domutils":"2.4.4","lru-cache":"6.0.0","domhandler":"4.0.0","cross-fetch":"3.0.6","htmlparser2":"6.0.0","cssnano-simple":"1.2.2","dom-serializer":"1.2.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"2.7.4","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"2.5.4","@ampproject/toolbox-runtime-version":"2.7.4","@ampproject/toolbox-validator-rules":"2.7.4"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.7.6_1612184397215_0.6388553551855833","host":"s3://npm-registry-packages"}},"2.8.0-canary.0":{"name":"@ampproject/toolbox-optimizer","version":"2.8.0-canary.0","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.0-canary.0","maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"74b4467afbe9fd7b172c6ec203bc9c38f5abfdae","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.0-canary.0.tgz","fileCount":47,"integrity":"sha512-zeh/QgrTdKl/IuMvdyo0PDRuXbQv+LZy/bTcexN+K8NQ7lD0PqDQMkfxBOycBO9bPmG3go/Qi/wEpSTf9dhZSQ==","signatures":[{"sig":"MEQCIFc7qO4CY/Oc038vBYFee2U4j4RMld2IN8LzSIOH9kRGAiBTgk65LxwCGkRdmMbt4yz4eRmquGgejhBG0XIl+mvHGw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":215140,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJrnjCRA9TVsSAnZWagAAHkkP/1s0ysA44rG0JEZwZGqJ\ne1+OgWW56evSGTB4DLkSX5BkS5nGPovwYbqimzCsaDR9UDt9r8JQUpLwDMkJ\nNaGymco+cjAX1NIw52F5Q0wtAhBAGtX6nKYprRaUX7eFGKbThsqYkGVdeHBQ\nVYHTqL5AHznca6K9p9qvMe5zWRelxi03enrOaDZIhTLjdEAQnDwBWYgpxT3/\nCvrTFwrGRrtZNgWg5gZr5r5ynvGmGRrNM+kB+7hPmkyhKRsBrronzhDaRF/f\nS7jMmueljA9GZNng18PBTIXuegBQUbzRKZdvz7hDPi1M5cvpdD2cBnlEvoJD\nsIcQ7PdgcI0jN+RVcxFCdChu/Yq9ECR8+SjGA8sjXz7fHavanzmM2/HQHOw2\n6OFXURVVLppKaYPGRTP6J+oaH6ioSdD4LOFt8AoT+ZMn4qBiCesKoY8kxnji\njrSu+SJKeS68fmB8CDeC5zvQokhnUAbzOJnro8YBdNCzrgvl4tNSukjfpA0i\n3OROWipw3fj44Cu3FRJTHhT7dvlrRRg6GRG3VZarffTOijBoeHElH8FxUZ8Q\n3GMq7489ZOICQbyWiTi/TzLxA7H9rnCEqnUVCXZ2twWMeUlKGVbf1kBvXYVq\ns34KLe4VC1W0utoKI6HPHurFXLh+JDkg1GKyYwfCbjdpjHckRkS5dbxVne8p\nDRcf\r\n=oN08\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to simplify creating AMP pages and improve AMP rendering performance. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n- [Server-side render AMP layouts](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/).\n- **Automatically import all missing AMP component scripts**.\n- **Automatically add any missing mandatory AMP tags**.\n- Auto detects and preloads hero images from amp-img, amp-iframe, amp-video, or amp-video-iframe.\n- Remove the AMP boilerplate (when possible).\n- Remove not needed whitespace.\n- Extract and move CSS keyframe animations to the bottom of the page.\n- Optimize AMP framework and custom font loading\n- Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n- AMP Optimizer will produce valid AMP.\n- AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\n### API\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>`;\n\nampOptimizer.transformHtml(originalHtml).then((optimizedHtml) => {\n  console.log(optimizedHtml);\n});\n```\n\nYou can find a sample implementation [here](/packages/optimizer/demo/simple). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](/packages/optimizer-express).\n\n### CLI\n\nAMP Optimizer can be used via the [AMP Toolbox CLI](/packages/cli/README.md):\n\n```shell\nnpm install @ampproject/toolbox-cli -g\namp optimize myFile.html\n```\n\nor run without installation via `npx`:\n\n```shell\nnpx @ampproject/toolbox-cli optimize myFile.html\n```\n\n### Options\n\nOptions are passed when creating a new AMP Optimizer instance:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  verbose: true\n});\n...\n```\n\nAvailable options are:\n\n- [autoAddMandatoryTags](#autoaddmandatorytags)\n- [autoExtensionImport](#autoextensionimport)\n- [esmModulesEnabled](#esmmodulesenabled)\n- [extensionVersions](#extensionversions)\n- [fetch](#fetch)\n- [format](#format)\n- [imageBasePath](#imagebasePath)\n- [imageOptimizer](#imageoptimizer)\n- [lts](#lts)\n- [markdown](#markdown)\n- [minify](#minify)\n- [optimizeHeroImages](#optimizeheroimages)\n- [preloadHeroImage](#preloadheroimage)\n- [verbose](#verbose)\n\n#### `autoAddMandatoryTags`\n\nAutomatically inject any missing markup required by AMP.\n\n- name: `autoAddMandatoryTags`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `autoExtensionImport`\n\nAutomatically import any missing AMP Extensions (e.g. amp-carousel).\n\n- name: `autoExtensionImport`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `esmModulesEnabled`\n\nEnables the smaller ESM module version of AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n- name: `esmModulesEnabled`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `extensionVersions`\n\nSpecify version numbers to use for automatically imported Extensions. If not defined, default to latest.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  extensionVersions: {\n    'amp-twitter': '0.1',\n  },\n});\n```\n\n- name: `extensionVersions`\n- valid options: `OBJECT`\n- default: `{}`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `format`\n\nSpecifies the AMP format of the input file. Defaults to `AMP`.\n\n- name: `format`\n- valid options: `[AMP|AMP4EMAIL|AMP4ADS]`\n- default: `AMP`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js), [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `experimentEsm`\n\nEnable [JavaScript Module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) support for AMP runtime and components. AMP Optimizer will generate module/nonmodule script imports for AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n**Warning: this will result in invalid AMP pages.**\n\n- name: `experimentEsm`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `imageBasePath`\n\nSpecifies a base path used to resolve an image during build,\nthis can be a file system path or URL prefix. You can also pass a function\n`(imgSrc, params) => '../img/' + imgSrc` for dynamically calculating the image path.\n\n- name: `imageBasePath`\n- valid options: `STRING|FUNCTION`\n- default: undefined\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `imageOptimizer`\n\nEnable automated image `srcset` generation by providing a function for calculating `srcset` URLs for a given image `src`. The function should return a URL pointing to a version of the `src` image with the given `width`. If no image is available, it should return a falsy value.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  imageOptimizer: (src, width) => `${src}?width=${width}`,\n});\n```\n\n- name: `imageOptimizer`\n- valid options: `FUNCTION`\n- default: undefined\n- used by: [OptimizeImages](lib/transformers/OptimizeImages.js)\n\n#### `lts`\n\nUse [long-term stable URLs](https://github.com/ampproject/amphtml/blob/main/contributing/lts-release.md) for downloading the AMP runtime and components.\n\n- name: `lts`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `markdown`\n\nThis transformer adds out-of-the-box markdown support. This allows\nusing AMP Optimizer to convert HTML documents created from Markdown\nfiles into valid AMP. A typical conversion flow would be:\n\nREADME.md => HTML => AMP Optimizer => valid AMP\n\nThe only thing this transformer does is converting `<img>` tags into\neither `amp-img` or `amp-anim` tags. All other Markdown features are\nalready supported by AMP. The transformer will try to resolve image\ndimensions from the actual files. Images larger than 320px will automatically\nget an intrinsic layout. For image detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n- name: `markdown`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `minify`\n\nMinifies the generated HTML output and inlined CSS.\n\n- name: `minify`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [MinifyHtml](lib/transformers/MinifyHtml.js), [SeparateKeyframes](lib/transformers/SeparateKeyframes.js)\n\n**Warning:** this setting is not recommended when running AMP Optimizer in your backend on every request as execution time can increase by up to 7x.\n\n#### `optimizeHeroImages`\n\nEnables hero image optimization. Hero images will either be auto detected or you can explicitly mark these by adding the `data-hero` attribute:\n\n```\n<amp-img data-hero src=\"foo.jpg\" ...>\n```\n\nThe maximum number of hero images that can be marked up using `data-hero` is `2`. This number can be increased using the `maxHeroImageCount` parameter.\n\nHero images are optimized by server-side rendering the `img` element inside the `amp-img` element. This can siginificantly improve image rendering performance and reduce the [largest contentful paint](https://web.dev/lcp/) (LCP) metric from [Core Web Vitals](https://web.dev/vitals/).\n\n- name: `optimizeHeroImages`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeHeroImages](lib/transformers/OptimizeHeroImages.js)\n\n#### `preloadHeroImage`\n\nDeprecated, use [optimizeHeroImages](#optimizeheroimages) instead. \n\n#### `verbose`\n\nEnable verbose mode with more detailed logging output.\n\n- name: `verbose`\n- valid options: `[true|false]`\n- default: `false`\n\n## Features\n\n### Image optimization\n\nAMP Optimizer helps you serve optimized images. For this to work, you need to provide a function that maps an image `src` to a resized `srcset` source value. The image resizing needs to either happen at build time (e.g. for static sites) or via a image hosting service such as [thumbor](https://github.com/thumbor/thumbor).\n\nHere is an example implementation that appends the image width to the `src`:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  // parameters are the amp-img `src` and the `width` of the to be generated srcset source value\n  imageOptimizer: (src, width) => {\n    // we cannot rename if the image does not have a file extension\n    const index = src.lastIndexOf('.');\n    if (index === -1) {\n      // return null means we won't generate a srcset source value for this width\n      return null;\n    }\n    const prefix = src.substring(0, index);\n    const postfix = src.substring(index, src.length);\n    return `${prefix}.${width}w${postfix}`;\n  };\n})\n```\n\nUsing this implementation, AMP Optimizer will transform the following `amp-img` declarations:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img src=\"image1.png\" width=\"400\" height=\"800\" layout=\"responsive\"></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                             image-2x.png 2x\"\n></amp-img>\n```\n\ninto:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img\n  src=\"image1.png\"\n  width=\"400\"\n  height=\"800\"\n  layout=\"responsive\"\n  srcset=\"image1.470w.png 470w, image1.820w.png 820w, image1.1440w.png 1440w\"\n></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                               image-2x.png 2x\"\n></amp-img>\n```\n\n**Important** when using `layout=responsive` use the `width` and `height` attribute to specify the minimum image dimensions. For example, for a full-bleed hero image on mobile, specify the width as`width=320`.\n\n### Auto add incomplete markup\n\nIt's possible to pass incomplete documents and AMP Optimizer will add any\nmissing tags and extension imports required by a valid AMP document.\n\n```js\nconst originalHtml = `\n  <h1>Hello World!</h1>\n  <amp-twitter width=\"375\"\n               height=\"472\"\n               layout=\"responsive\"\n               data-tweetid=\"1182321926473162752\">\n  </amp-twitter>\n`;\n\n// you can pass the canonical URL, default is `.`\nconst opts = {\n  canonical: '/example.html',\n};\nampOptimizer.transformHtml(originalHtml, opts).then((optimizedHtml) => {\n  // optimizedHtml will be a valid AMP document\n  console.log(optimizedHtml);\n});\n```\n\n### Automated Markdown conversion\n\nAMP Optimizer supports converting Markdown to AMPHTML. A typical conversion flow would be:\n\n```\nREADME.md => HTML => AMP Optimizer => valid AMP\n```\n\nThe AMP Optimizer converts `<img>` tags into `<amp-img>` or `<amp-anim>` tags when in Markdown mode. Enable Markdown mode via `markdown : true`. AMP Optimizer will try to resolve image dimensions from the actual files. Images wider than 320px will automatically get an `intrinsic` layout.\n\nAll other Markdown features are already supported by AMP.\n\nYou can pass an additional option `imageBasePath` to specify a base path used to resolve an image during build, this can be a file system path or URL prefix.\n\n**Important:** for image size detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n```shell\nnpm install probe-image-size --save-dev\n```\n\nExample:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst md = require('markdown-it')({\n  // don't sanitize html if you want to support AMP components in Markdown\n  html: true,\n});\n\n// enable markdown mode\nconst ampOptimizer = AmpOptimizer.create({\n  markdown: true,\n});\n\nconst markdown = `\n# Markdown 🤯\n\nHere is an image declared in Markdown syntax:\n\n![A random image](https://unsplash.it/800/600).\n\nYou can directly declare AMP components:\n\n<amp-twitter width=\"375\"\n             height=\"472\"\n             layout=\"responsive\"\n             data-tweetid=\"1182321926473162752\">\n</amp-twitter>\n\nAny missing extensions will be automatically imported.\n`;\n\nconst html = md.render(markdown);\n\nconst amphtml = await ampOptimizer.transformHtml(html, {\n  canonical: filePath,\n});\n```\n\nYou can find a working sample [here](/packages/optimizer/demo/markdown).\n\n## Extending AMP Optimizer with custom transformations\n\nAMP Optimizer supports custom HTML transformations:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst {createElement, firstChildByTag, appendChild} = AmpOptimizer.NodeUtils;\n\nclass CustomTransformer {\n  constructor(config) {\n    this.log_ = config.log.tag('CUSTOM');\n  }\n  transform(tree, params) {\n    this.log_.info('Running custom transformation for ', params.filePath);\n    const html = firstChildByTag(tree, 'html');\n    if (!html) return;\n    const head = firstChildByTag(html, 'head');\n    if (!head) return;\n    const desc = createElement('meta', {\n      name: 'description',\n      content: 'this is just a demo',\n    });\n    appendChild(head, desc);\n  }\n}\n\n// it's best to run custom transformers first\nconst customTransformations = [CustomTransformer, ...AmpOptimizer.TRANSFORMATIONS_AMP_FIRST];\n\n// pass custom transformers when creating the optimizer\nconst optimizer = AmpOptimizer.create({\n  transformations: customTransformations,\n});\n// you can add custom parameters on a per document basis\nconst transformedHtml = await optimizer.transformHtml(html, {\n  filePath,\n});\n```\n\nCheckout [the samples](/packages/optimizer/demo/simple/index.js) to learn how to customize AMP Optimizer.\n\n## Best Practices\n\n### Make sure to enable AMP Boilerplate removal\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n- if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```js\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true,\n});\n```\n\n... or for individual pages:\n\n```js\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true,\n});\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length).replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](/packages/runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion,\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n- `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n- `imageBasePath`: specifies a base path used to resolve an image during build.\n- `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n- `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp',\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Fallback API for amp-geo\n\nIdeally, when self-hosting the AMP framework, `amp-geo-0.1.js` should be patched at delivery time to replace `{{AMP_ISO_COUNTRY_HOTPATCH}}` with the ISO 3166-1 alpha-2 country code where the request originated ([reference](https://github.com/ampproject/amphtml/blob/main/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem)). If your host does not have this capability, you can instead rely on a web API to return the country at runtime. The web API must be secure (HTTPS), adhere to [AMP CORS guidelines](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests/), and return JSON in the following format:\n\n```js\n{\"country\": \"de\"}\n```\n\nwhere in this example, `de` is the ISO 3166-1 alpha-2 country code for Germany.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  // this will instruct amp-geo to fetch the user's country from an API\n  // which returns JSON in format: {\"country\": \"de\"}\n  geoApiUrl: 'https://example.com/geo',\n});\n\nconsole.log(optimizedHtml);\n```\n\n## Development & Testing\n\nAMP Optimizer uses a snapshot based testing approach. To execute the tests, run in the project root:\n\n```shell\nnpm run test:node\n```\n\nTransformer tests are located in:\n\n```\n- spec/transformers/valid/TransformerName/test-name/\n    expected_output.html\n    input.html\n```\n\nThe transformation input is defined in `input.html`, whereas `expected_output.html` contains the expected\noutcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing\na transformer implementation, run:\n\n```shell\nnpm run test:optimizer:snapshot\n```\n\nto store a new snapshot version in `expected_output.html`.\n","gitHead":"0ec5cb5ca31022687841bc344b29fa8e775310d8","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v14.15.4+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"14.15.4","dependencies":{"terser":"5.5.1","postcss":"8.2.6","domutils":"2.4.4","lru-cache":"6.0.0","domhandler":"4.0.0","cross-fetch":"3.0.6","htmlparser2":"6.0.0","cssnano-simple":"1.2.2","dom-serializer":"1.2.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"^2.8.0-canary.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.8.0-canary.0","@ampproject/toolbox-runtime-version":"^2.8.0-canary.0","@ampproject/toolbox-validator-rules":"^2.8.0-canary.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.0-canary.0_1613150690841_0.7714574100132467","host":"s3://npm-registry-packages"}},"2.8.0-canary.1":{"name":"@ampproject/toolbox-optimizer","version":"2.8.0-canary.1","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.0-canary.1","maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"969fbe56999b9984807bc98427bc1d8f7a38095d","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.0-canary.1.tgz","fileCount":47,"integrity":"sha512-Is7jKyLEHgYgfZ9NeaBuEGxHgSlPVt0VzCTNbp66x1XV3ddpeiEf2jjm7QXDGiQZVeuguIiXGXM8oXhicl0BeQ==","signatures":[{"sig":"MEUCIA79HooQBjU3TOOFc61hmeg80USJjyOZ8n/ZjNDekFmhAiEAqCepQc4OUkx31AcrvK+5smMEssRn7AZ/3vtdKxZ3JZA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":215150,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgJrxLCRA9TVsSAnZWagAAIC4P/0EHVbDLd1yb9vbwlIox\nHT+mt5AcLKHmr7i5HkUSnp6Oj4wwov8PctQGvMF2i4kHWoj8m2y+Yaw7QMSP\nT9WMjWe36Wk1WaOKo4zdsqVghE/d/KTGlZtH+RgIehDQIJ4GlkWIEpElVr71\n0TArqoU6RWJ0DggXQXsyd4xotl0XdK7phKTFGjwwN7W6vdFaoSDpyFONSgCZ\nIb8XZZyNmll5JNU8HWQH05tTWMarAq+b54tLFv9tiwLJKigr0EW7PjqkCT+I\n7QwGsGibhPm6qGgtqPhk5Bl1OQZ6xtpcj3Sa/KVemaI/KriuTsF5I4521kkq\ngRzUIBpZdSMeh32i2GI0yUcECOghREQZvDHwQawRSRKIfW7YXlg6F6dBfKfN\nzPqkcggoxh+vNMU3ZpxBXOVqjWet7yuAmzgyaDLr6ZRsyPD3ofMRV5BaPm6z\n6p+Vj0J5t+8Q/AqHpC1U7aPZVUnQ9FMXSfBsUwXEiYPpkfeOdkMqxHWEhNVh\nZ0O2WaYYcyztZu2TGXgB9TjpUsZoy0q/lTEMWse1IHdyOL1bD/CQmPW0pqBM\nsBO6VihhnA1GNGV/J+En2ZXXWdKtjBZNbD7+H3TK6WsL2YOr4yKcM1i3nIfh\nVf0m/F395wss4i8HrmwKojWkxgHCQTYAOnc+pt1PCUvlxfbqq4msJUj6xOY7\nq5wJ\r\n=aSpB\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to simplify creating AMP pages and improve AMP rendering performance. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n- [Server-side render AMP layouts](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/).\n- **Automatically import all missing AMP component scripts**.\n- **Automatically add any missing mandatory AMP tags**.\n- Auto detects and preloads hero images from amp-img, amp-iframe, amp-video, or amp-video-iframe.\n- Remove the AMP boilerplate (when possible).\n- Remove not needed whitespace.\n- Extract and move CSS keyframe animations to the bottom of the page.\n- Optimize AMP framework and custom font loading\n- Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n- AMP Optimizer will produce valid AMP.\n- AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\n### API\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>`;\n\nampOptimizer.transformHtml(originalHtml).then((optimizedHtml) => {\n  console.log(optimizedHtml);\n});\n```\n\nYou can find a sample implementation [here](/packages/optimizer/demo/simple). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](/packages/optimizer-express).\n\n### CLI\n\nAMP Optimizer can be used via the [AMP Toolbox CLI](/packages/cli/README.md):\n\n```shell\nnpm install @ampproject/toolbox-cli -g\namp optimize myFile.html\n```\n\nor run without installation via `npx`:\n\n```shell\nnpx @ampproject/toolbox-cli optimize myFile.html\n```\n\n### Options\n\nOptions are passed when creating a new AMP Optimizer instance:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  verbose: true\n});\n...\n```\n\nAvailable options are:\n\n- [autoAddMandatoryTags](#autoaddmandatorytags)\n- [autoExtensionImport](#autoextensionimport)\n- [esmModulesEnabled](#esmmodulesenabled)\n- [extensionVersions](#extensionversions)\n- [fetch](#fetch)\n- [format](#format)\n- [imageBasePath](#imagebasePath)\n- [imageOptimizer](#imageoptimizer)\n- [lts](#lts)\n- [markdown](#markdown)\n- [minify](#minify)\n- [optimizeHeroImages](#optimizeheroimages)\n- [preloadHeroImage](#preloadheroimage)\n- [verbose](#verbose)\n\n#### `autoAddMandatoryTags`\n\nAutomatically inject any missing markup required by AMP.\n\n- name: `autoAddMandatoryTags`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `autoExtensionImport`\n\nAutomatically import any missing AMP Extensions (e.g. amp-carousel).\n\n- name: `autoExtensionImport`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `esmModulesEnabled`\n\nEnables the smaller ESM module version of AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n- name: `esmModulesEnabled`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `extensionVersions`\n\nSpecify version numbers to use for automatically imported Extensions. If not defined, default to latest.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  extensionVersions: {\n    'amp-twitter': '0.1',\n  },\n});\n```\n\n- name: `extensionVersions`\n- valid options: `OBJECT`\n- default: `{}`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `format`\n\nSpecifies the AMP format of the input file. Defaults to `AMP`.\n\n- name: `format`\n- valid options: `[AMP|AMP4EMAIL|AMP4ADS]`\n- default: `AMP`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js), [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `experimentEsm`\n\nEnable [JavaScript Module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) support for AMP runtime and components. AMP Optimizer will generate module/nonmodule script imports for AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n**Warning: this will result in invalid AMP pages.**\n\n- name: `experimentEsm`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `imageBasePath`\n\nSpecifies a base path used to resolve an image during build,\nthis can be a file system path or URL prefix. You can also pass a function\n`(imgSrc, params) => '../img/' + imgSrc` for dynamically calculating the image path.\n\n- name: `imageBasePath`\n- valid options: `STRING|FUNCTION`\n- default: undefined\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `imageOptimizer`\n\nEnable automated image `srcset` generation by providing a function for calculating `srcset` URLs for a given image `src`. The function should return a URL pointing to a version of the `src` image with the given `width`. If no image is available, it should return a falsy value.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  imageOptimizer: (src, width) => `${src}?width=${width}`,\n});\n```\n\n- name: `imageOptimizer`\n- valid options: `FUNCTION`\n- default: undefined\n- used by: [OptimizeImages](lib/transformers/OptimizeImages.js)\n\n#### `lts`\n\nUse [long-term stable URLs](https://github.com/ampproject/amphtml/blob/main/contributing/lts-release.md) for downloading the AMP runtime and components.\n\n- name: `lts`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `markdown`\n\nThis transformer adds out-of-the-box markdown support. This allows\nusing AMP Optimizer to convert HTML documents created from Markdown\nfiles into valid AMP. A typical conversion flow would be:\n\nREADME.md => HTML => AMP Optimizer => valid AMP\n\nThe only thing this transformer does is converting `<img>` tags into\neither `amp-img` or `amp-anim` tags. All other Markdown features are\nalready supported by AMP. The transformer will try to resolve image\ndimensions from the actual files. Images larger than 320px will automatically\nget an intrinsic layout. For image detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n- name: `markdown`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `minify`\n\nMinifies the generated HTML output and inlined CSS.\n\n- name: `minify`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [MinifyHtml](lib/transformers/MinifyHtml.js), [SeparateKeyframes](lib/transformers/SeparateKeyframes.js)\n\n**Warning:** this setting is not recommended when running AMP Optimizer in your backend on every request as execution time can increase by up to 7x.\n\n#### `optimizeHeroImages`\n\nEnables hero image optimization. Hero images will either be auto detected or you can explicitly mark these by adding the `data-hero` attribute:\n\n```\n<amp-img data-hero src=\"foo.jpg\" ...>\n```\n\nThe maximum number of hero images that can be marked up using `data-hero` is `2`. This number can be increased using the `maxHeroImageCount` parameter.\n\nHero images are optimized by server-side rendering the `img` element inside the `amp-img` element. This can siginificantly improve image rendering performance and reduce the [largest contentful paint](https://web.dev/lcp/) (LCP) metric from [Core Web Vitals](https://web.dev/vitals/).\n\n- name: `optimizeHeroImages`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeHeroImages](lib/transformers/OptimizeHeroImages.js)\n\n#### `preloadHeroImage`\n\nDeprecated, use [optimizeHeroImages](#optimizeheroimages) instead. \n\n#### `verbose`\n\nEnable verbose mode with more detailed logging output.\n\n- name: `verbose`\n- valid options: `[true|false]`\n- default: `false`\n\n## Features\n\n### Image optimization\n\nAMP Optimizer helps you serve optimized images. For this to work, you need to provide a function that maps an image `src` to a resized `srcset` source value. The image resizing needs to either happen at build time (e.g. for static sites) or via a image hosting service such as [thumbor](https://github.com/thumbor/thumbor).\n\nHere is an example implementation that appends the image width to the `src`:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  // parameters are the amp-img `src` and the `width` of the to be generated srcset source value\n  imageOptimizer: (src, width) => {\n    // we cannot rename if the image does not have a file extension\n    const index = src.lastIndexOf('.');\n    if (index === -1) {\n      // return null means we won't generate a srcset source value for this width\n      return null;\n    }\n    const prefix = src.substring(0, index);\n    const postfix = src.substring(index, src.length);\n    return `${prefix}.${width}w${postfix}`;\n  };\n})\n```\n\nUsing this implementation, AMP Optimizer will transform the following `amp-img` declarations:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img src=\"image1.png\" width=\"400\" height=\"800\" layout=\"responsive\"></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                             image-2x.png 2x\"\n></amp-img>\n```\n\ninto:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img\n  src=\"image1.png\"\n  width=\"400\"\n  height=\"800\"\n  layout=\"responsive\"\n  srcset=\"image1.470w.png 470w, image1.820w.png 820w, image1.1440w.png 1440w\"\n></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                               image-2x.png 2x\"\n></amp-img>\n```\n\n**Important** when using `layout=responsive` use the `width` and `height` attribute to specify the minimum image dimensions. For example, for a full-bleed hero image on mobile, specify the width as`width=320`.\n\n### Auto add incomplete markup\n\nIt's possible to pass incomplete documents and AMP Optimizer will add any\nmissing tags and extension imports required by a valid AMP document.\n\n```js\nconst originalHtml = `\n  <h1>Hello World!</h1>\n  <amp-twitter width=\"375\"\n               height=\"472\"\n               layout=\"responsive\"\n               data-tweetid=\"1182321926473162752\">\n  </amp-twitter>\n`;\n\n// you can pass the canonical URL, default is `.`\nconst opts = {\n  canonical: '/example.html',\n};\nampOptimizer.transformHtml(originalHtml, opts).then((optimizedHtml) => {\n  // optimizedHtml will be a valid AMP document\n  console.log(optimizedHtml);\n});\n```\n\n### Automated Markdown conversion\n\nAMP Optimizer supports converting Markdown to AMPHTML. A typical conversion flow would be:\n\n```\nREADME.md => HTML => AMP Optimizer => valid AMP\n```\n\nThe AMP Optimizer converts `<img>` tags into `<amp-img>` or `<amp-anim>` tags when in Markdown mode. Enable Markdown mode via `markdown : true`. AMP Optimizer will try to resolve image dimensions from the actual files. Images wider than 320px will automatically get an `intrinsic` layout.\n\nAll other Markdown features are already supported by AMP.\n\nYou can pass an additional option `imageBasePath` to specify a base path used to resolve an image during build, this can be a file system path or URL prefix.\n\n**Important:** for image size detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n```shell\nnpm install probe-image-size --save-dev\n```\n\nExample:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst md = require('markdown-it')({\n  // don't sanitize html if you want to support AMP components in Markdown\n  html: true,\n});\n\n// enable markdown mode\nconst ampOptimizer = AmpOptimizer.create({\n  markdown: true,\n});\n\nconst markdown = `\n# Markdown 🤯\n\nHere is an image declared in Markdown syntax:\n\n![A random image](https://unsplash.it/800/600).\n\nYou can directly declare AMP components:\n\n<amp-twitter width=\"375\"\n             height=\"472\"\n             layout=\"responsive\"\n             data-tweetid=\"1182321926473162752\">\n</amp-twitter>\n\nAny missing extensions will be automatically imported.\n`;\n\nconst html = md.render(markdown);\n\nconst amphtml = await ampOptimizer.transformHtml(html, {\n  canonical: filePath,\n});\n```\n\nYou can find a working sample [here](/packages/optimizer/demo/markdown).\n\n## Extending AMP Optimizer with custom transformations\n\nAMP Optimizer supports custom HTML transformations:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst {createElement, firstChildByTag, appendChild} = AmpOptimizer.NodeUtils;\n\nclass CustomTransformer {\n  constructor(config) {\n    this.log_ = config.log.tag('CUSTOM');\n  }\n  transform(tree, params) {\n    this.log_.info('Running custom transformation for ', params.filePath);\n    const html = firstChildByTag(tree, 'html');\n    if (!html) return;\n    const head = firstChildByTag(html, 'head');\n    if (!head) return;\n    const desc = createElement('meta', {\n      name: 'description',\n      content: 'this is just a demo',\n    });\n    appendChild(head, desc);\n  }\n}\n\n// it's best to run custom transformers first\nconst customTransformations = [CustomTransformer, ...AmpOptimizer.TRANSFORMATIONS_AMP_FIRST];\n\n// pass custom transformers when creating the optimizer\nconst optimizer = AmpOptimizer.create({\n  transformations: customTransformations,\n});\n// you can add custom parameters on a per document basis\nconst transformedHtml = await optimizer.transformHtml(html, {\n  filePath,\n});\n```\n\nCheckout [the samples](/packages/optimizer/demo/simple/index.js) to learn how to customize AMP Optimizer.\n\n## Best Practices\n\n### Make sure to enable AMP Boilerplate removal\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n- if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```js\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true,\n});\n```\n\n... or for individual pages:\n\n```js\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true,\n});\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length).replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](/packages/runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion,\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n- `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n- `imageBasePath`: specifies a base path used to resolve an image during build.\n- `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n- `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp',\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Fallback API for amp-geo\n\nIdeally, when self-hosting the AMP framework, `amp-geo-0.1.js` should be patched at delivery time to replace `{{AMP_ISO_COUNTRY_HOTPATCH}}` with the ISO 3166-1 alpha-2 country code where the request originated ([reference](https://github.com/ampproject/amphtml/blob/main/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem)). If your host does not have this capability, you can instead rely on a web API to return the country at runtime. The web API must be secure (HTTPS), adhere to [AMP CORS guidelines](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests/), and return JSON in the following format:\n\n```js\n{\"country\": \"de\"}\n```\n\nwhere in this example, `de` is the ISO 3166-1 alpha-2 country code for Germany.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  // this will instruct amp-geo to fetch the user's country from an API\n  // which returns JSON in format: {\"country\": \"de\"}\n  geoApiUrl: 'https://example.com/geo',\n});\n\nconsole.log(optimizedHtml);\n```\n\n## Development & Testing\n\nAMP Optimizer uses a snapshot based testing approach. To execute the tests, run in the project root:\n\n```shell\nnpm run test:node\n```\n\nTransformer tests are located in:\n\n```\n- spec/transformers/valid/TransformerName/test-name/\n    expected_output.html\n    input.html\n```\n\nThe transformation input is defined in `input.html`, whereas `expected_output.html` contains the expected\noutcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing\na transformer implementation, run:\n\n```shell\nnpm run test:optimizer:snapshot\n```\n\nto store a new snapshot version in `expected_output.html`.\n","gitHead":"972aa9a6264f5baf0dfb698a236e130b92480867","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v14.15.4+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"14.15.4","dependencies":{"terser":"5.5.1","postcss":"8.2.6","domutils":"2.4.4","lru-cache":"6.0.0","domhandler":"4.0.0","cross-fetch":"3.0.6","htmlparser2":"6.0.0","cssnano-simple":"1.2.2","dom-serializer":"1.2.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"^2.8.0-canary.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.8.0-canary.0","@ampproject/toolbox-runtime-version":"^2.8.0-canary.0","@ampproject/toolbox-validator-rules":"^2.8.0-canary.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.0-canary.1_1613151306618_0.3793706033512019","host":"s3://npm-registry-packages"}},"2.8.0-canary.2":{"name":"@ampproject/toolbox-optimizer","version":"2.8.0-canary.2","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.0-canary.2","maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"f04a1b276c9857001727b93cf388888eabf84ef1","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.0-canary.2.tgz","fileCount":47,"integrity":"sha512-O8p+lcozNJtkBulW8YFZaEMInvc9m9M3BjXh9fCWL7ngPvkLXxgsHjYiQb2ZV1tjXKO8liDlieo+WoqtRRRgzA==","signatures":[{"sig":"MEQCIENA9SZxlksQn9NOobXobGLYDL7deon+AKl9xkXmlTblAiBR4sYpffFwgQ7MUCWc14LUH9p/Ewr27AIYbBnDQ2XXhw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":215696,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgKlsNCRA9TVsSAnZWagAA0CAP/0n6tNb1mQDKuGtG5y0B\n47qM0aZSEDp3ljIKeqXM/UKnZXKsPhKT/K8PYAvUUZbUI3bej7BxHFB5GlMh\n6bYcQ1vXJWphVimCGpQdHHEfKvLgo3amg9NEBwWpCCdPhDqh71ge7bxGoaFr\nQOwVf1+ZuP4rfaSJ0rbGoXE8ObqHlsi96WXZoq21lZciCe/MPUFKVrt7zxf2\njXZXYdmVsvsxx0J76C+1TG73btkC+0y0TicgjCAAhZXLYg+Rrmaz50QWeBAi\nH1MXIzQ2FcWX7cDoocUb/oMKkfH3v8ypzgJNCbucoWUrNMKX3tMBjNbH2tLG\nRlw9YEiwlOQFRfns1UGW62HyV2NFLdh5Z/WuRz5brNkNjQep1yhHYNBGkloe\nXsbgIyaJweC/N5q0xtpIkzounkzMXZsKVrbasClyuw7+3IptrOWsxakkW482\npHwQhgrkMuKddDuNeS+fi2e4DBO4HrUYN27NAoI+6l+2d/JwIfpmhe7PoRr6\nGimXEoqIT5uZCydQMnDUZDvTUwthfvyeO/JBplVQgHIiIpiZQBEbNamkDas4\neUQWH10fxQRU1yjwYRrmSj40Y04soF2ZSpBVCStIMUfqKy7PjKx1Wz2JsPlC\nLTak0/gssGfdmKQlDoTEE6aAlTJx8CWVxeH2ToMJtjpZEPype9l6vxavObx6\nK98g\r\n=xeIc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to simplify creating AMP pages and improve AMP rendering performance. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n- [Server-side render AMP layouts](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/).\n- **Automatically import all missing AMP component scripts**.\n- **Automatically add any missing mandatory AMP tags**.\n- Auto detects and preloads hero images from amp-img, amp-iframe, amp-video, or amp-video-iframe.\n- Remove the AMP boilerplate (when possible).\n- Remove not needed whitespace.\n- Extract and move CSS keyframe animations to the bottom of the page.\n- Optimize AMP framework and custom font loading\n- Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n- AMP Optimizer will produce valid AMP.\n- AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\n### API\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>`;\n\nampOptimizer.transformHtml(originalHtml).then((optimizedHtml) => {\n  console.log(optimizedHtml);\n});\n```\n\nYou can find a sample implementation [here](/packages/optimizer/demo/simple). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](/packages/optimizer-express).\n\n### CLI\n\nAMP Optimizer can be used via the [AMP Toolbox CLI](/packages/cli/README.md):\n\n```shell\nnpm install @ampproject/toolbox-cli -g\namp optimize myFile.html\n```\n\nor run without installation via `npx`:\n\n```shell\nnpx @ampproject/toolbox-cli optimize myFile.html\n```\n\n### Options\n\nOptions are passed when creating a new AMP Optimizer instance:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  verbose: true\n});\n...\n```\n\nAvailable options are:\n\n- [autoAddMandatoryTags](#autoaddmandatorytags)\n- [autoExtensionImport](#autoextensionimport)\n- [esmModulesEnabled](#esmmodulesenabled)\n- [extensionVersions](#extensionversions)\n- [fetch](#fetch)\n- [format](#format)\n- [imageBasePath](#imagebasePath)\n- [imageOptimizer](#imageoptimizer)\n- [lts](#lts)\n- [markdown](#markdown)\n- [minify](#minify)\n- [optimizeHeroImages](#optimizeheroimages)\n- [preloadHeroImage](#preloadheroimage)\n- [verbose](#verbose)\n\n#### `autoAddMandatoryTags`\n\nAutomatically inject any missing markup required by AMP.\n\n- name: `autoAddMandatoryTags`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `autoExtensionImport`\n\nAutomatically import any missing AMP Extensions (e.g. amp-carousel).\n\n- name: `autoExtensionImport`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `esmModulesEnabled`\n\nEnables the smaller ESM module version of AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n- name: `esmModulesEnabled`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `extensionVersions`\n\nSpecify version numbers to use for automatically imported Extensions. If not defined, default to latest.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  extensionVersions: {\n    'amp-twitter': '0.1',\n  },\n});\n```\n\n- name: `extensionVersions`\n- valid options: `OBJECT`\n- default: `{}`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `format`\n\nSpecifies the AMP format of the input file. Defaults to `AMP`.\n\n- name: `format`\n- valid options: `[AMP|AMP4EMAIL|AMP4ADS]`\n- default: `AMP`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js), [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `experimentEsm`\n\nEnable [JavaScript Module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) support for AMP runtime and components. AMP Optimizer will generate module/nonmodule script imports for AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n**Warning: this will result in invalid AMP pages.**\n\n- name: `experimentEsm`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `imageBasePath`\n\nSpecifies a base path used to resolve an image during build,\nthis can be a file system path or URL prefix. You can also pass a function\n`(imgSrc, params) => '../img/' + imgSrc` for dynamically calculating the image path.\n\n- name: `imageBasePath`\n- valid options: `STRING|FUNCTION`\n- default: undefined\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `imageOptimizer`\n\nEnable automated image `srcset` generation by providing a function for calculating `srcset` URLs for a given image `src`. The function should return a URL pointing to a version of the `src` image with the given `width`. If no image is available, it should return a falsy value.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  imageOptimizer: (src, width) => `${src}?width=${width}`,\n});\n```\n\n- name: `imageOptimizer`\n- valid options: `FUNCTION`\n- default: undefined\n- used by: [OptimizeImages](lib/transformers/OptimizeImages.js)\n\n#### `lts`\n\nUse [long-term stable URLs](https://github.com/ampproject/amphtml/blob/main/contributing/lts-release.md) for downloading the AMP runtime and components.\n\n- name: `lts`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `markdown`\n\nThis transformer adds out-of-the-box markdown support. This allows\nusing AMP Optimizer to convert HTML documents created from Markdown\nfiles into valid AMP. A typical conversion flow would be:\n\nREADME.md => HTML => AMP Optimizer => valid AMP\n\nThe only thing this transformer does is converting `<img>` tags into\neither `amp-img` or `amp-anim` tags. All other Markdown features are\nalready supported by AMP. The transformer will try to resolve image\ndimensions from the actual files. Images larger than 320px will automatically\nget an intrinsic layout. For image detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n- name: `markdown`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `minify`\n\nMinifies the generated HTML output and inlined CSS.\n\n- name: `minify`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [MinifyHtml](lib/transformers/MinifyHtml.js), [SeparateKeyframes](lib/transformers/SeparateKeyframes.js)\n\n**Warning:** this setting is not recommended when running AMP Optimizer in your backend on every request as execution time can increase by up to 7x.\n\n#### `optimizeHeroImages`\n\nEnables hero image optimization. Hero images will either be auto detected or you can explicitly mark these by adding the `data-hero` attribute:\n\n```\n<amp-img data-hero src=\"foo.jpg\" ...>\n```\n\nThe maximum number of hero images that can be marked up using `data-hero` is `2`. This number can be increased using the `maxHeroImageCount` parameter.\n\nHero images are optimized by server-side rendering the `img` element inside the `amp-img` element. This can siginificantly improve image rendering performance and reduce the [largest contentful paint](https://web.dev/lcp/) (LCP) metric from [Core Web Vitals](https://web.dev/vitals/).\n\n- name: `optimizeHeroImages`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeHeroImages](lib/transformers/OptimizeHeroImages.js)\n\n#### `preloadHeroImage`\n\nDeprecated, use [optimizeHeroImages](#optimizeheroimages) instead. \n\n#### `verbose`\n\nEnable verbose mode with more detailed logging output.\n\n- name: `verbose`\n- valid options: `[true|false]`\n- default: `false`\n\n## Features\n\n### Image optimization\n\nAMP Optimizer helps you serve optimized images. For this to work, you need to provide a function that maps an image `src` to a resized `srcset` source value. The image resizing needs to either happen at build time (e.g. for static sites) or via a image hosting service such as [thumbor](https://github.com/thumbor/thumbor).\n\nHere is an example implementation that appends the image width to the `src`:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  // parameters are the amp-img `src` and the `width` of the to be generated srcset source value\n  imageOptimizer: (src, width) => {\n    // we cannot rename if the image does not have a file extension\n    const index = src.lastIndexOf('.');\n    if (index === -1) {\n      // return null means we won't generate a srcset source value for this width\n      return null;\n    }\n    const prefix = src.substring(0, index);\n    const postfix = src.substring(index, src.length);\n    return `${prefix}.${width}w${postfix}`;\n  };\n})\n```\n\nUsing this implementation, AMP Optimizer will transform the following `amp-img` declarations:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img src=\"image1.png\" width=\"400\" height=\"800\" layout=\"responsive\"></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                             image-2x.png 2x\"\n></amp-img>\n```\n\ninto:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img\n  src=\"image1.png\"\n  width=\"400\"\n  height=\"800\"\n  layout=\"responsive\"\n  srcset=\"image1.470w.png 470w, image1.820w.png 820w, image1.1440w.png 1440w\"\n></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                               image-2x.png 2x\"\n></amp-img>\n```\n\n**Important** when using `layout=responsive` use the `width` and `height` attribute to specify the minimum image dimensions. For example, for a full-bleed hero image on mobile, specify the width as`width=320`.\n\n### Auto add incomplete markup\n\nIt's possible to pass incomplete documents and AMP Optimizer will add any\nmissing tags and extension imports required by a valid AMP document.\n\n```js\nconst originalHtml = `\n  <h1>Hello World!</h1>\n  <amp-twitter width=\"375\"\n               height=\"472\"\n               layout=\"responsive\"\n               data-tweetid=\"1182321926473162752\">\n  </amp-twitter>\n`;\n\n// you can pass the canonical URL, default is `.`\nconst opts = {\n  canonical: '/example.html',\n};\nampOptimizer.transformHtml(originalHtml, opts).then((optimizedHtml) => {\n  // optimizedHtml will be a valid AMP document\n  console.log(optimizedHtml);\n});\n```\n\n### Automated Markdown conversion\n\nAMP Optimizer supports converting Markdown to AMPHTML. A typical conversion flow would be:\n\n```\nREADME.md => HTML => AMP Optimizer => valid AMP\n```\n\nThe AMP Optimizer converts `<img>` tags into `<amp-img>` or `<amp-anim>` tags when in Markdown mode. Enable Markdown mode via `markdown : true`. AMP Optimizer will try to resolve image dimensions from the actual files. Images wider than 320px will automatically get an `intrinsic` layout.\n\nAll other Markdown features are already supported by AMP.\n\nYou can pass an additional option `imageBasePath` to specify a base path used to resolve an image during build, this can be a file system path or URL prefix.\n\n**Important:** for image size detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n```shell\nnpm install probe-image-size --save-dev\n```\n\nExample:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst md = require('markdown-it')({\n  // don't sanitize html if you want to support AMP components in Markdown\n  html: true,\n});\n\n// enable markdown mode\nconst ampOptimizer = AmpOptimizer.create({\n  markdown: true,\n});\n\nconst markdown = `\n# Markdown 🤯\n\nHere is an image declared in Markdown syntax:\n\n![A random image](https://unsplash.it/800/600).\n\nYou can directly declare AMP components:\n\n<amp-twitter width=\"375\"\n             height=\"472\"\n             layout=\"responsive\"\n             data-tweetid=\"1182321926473162752\">\n</amp-twitter>\n\nAny missing extensions will be automatically imported.\n`;\n\nconst html = md.render(markdown);\n\nconst amphtml = await ampOptimizer.transformHtml(html, {\n  canonical: filePath,\n});\n```\n\nYou can find a working sample [here](/packages/optimizer/demo/markdown).\n\n## Extending AMP Optimizer with custom transformations\n\nAMP Optimizer supports custom HTML transformations:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst {createElement, firstChildByTag, appendChild} = AmpOptimizer.NodeUtils;\n\nclass CustomTransformer {\n  constructor(config) {\n    this.log_ = config.log.tag('CUSTOM');\n  }\n  transform(tree, params) {\n    this.log_.info('Running custom transformation for ', params.filePath);\n    const html = firstChildByTag(tree, 'html');\n    if (!html) return;\n    const head = firstChildByTag(html, 'head');\n    if (!head) return;\n    const desc = createElement('meta', {\n      name: 'description',\n      content: 'this is just a demo',\n    });\n    appendChild(head, desc);\n  }\n}\n\n// it's best to run custom transformers first\nconst customTransformations = [CustomTransformer, ...AmpOptimizer.TRANSFORMATIONS_AMP_FIRST];\n\n// pass custom transformers when creating the optimizer\nconst optimizer = AmpOptimizer.create({\n  transformations: customTransformations,\n});\n// you can add custom parameters on a per document basis\nconst transformedHtml = await optimizer.transformHtml(html, {\n  filePath,\n});\n```\n\nCheckout [the samples](/packages/optimizer/demo/simple/index.js) to learn how to customize AMP Optimizer.\n\n## Best Practices\n\n### Make sure to enable AMP Boilerplate removal\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n- if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```js\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true,\n});\n```\n\n... or for individual pages:\n\n```js\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true,\n});\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length).replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](/packages/runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion,\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n- `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n- `imageBasePath`: specifies a base path used to resolve an image during build.\n- `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n- `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp',\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Fallback API for amp-geo\n\nIdeally, when self-hosting the AMP framework, `amp-geo-0.1.js` should be patched at delivery time to replace `{{AMP_ISO_COUNTRY_HOTPATCH}}` with the ISO 3166-1 alpha-2 country code where the request originated ([reference](https://github.com/ampproject/amphtml/blob/main/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem)). If your host does not have this capability, you can instead rely on a web API to return the country at runtime. The web API must be secure (HTTPS), adhere to [AMP CORS guidelines](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests/), and return JSON in the following format:\n\n```js\n{\"country\": \"de\"}\n```\n\nwhere in this example, `de` is the ISO 3166-1 alpha-2 country code for Germany.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  // this will instruct amp-geo to fetch the user's country from an API\n  // which returns JSON in format: {\"country\": \"de\"}\n  geoApiUrl: 'https://example.com/geo',\n});\n\nconsole.log(optimizedHtml);\n```\n\n## Development & Testing\n\nAMP Optimizer uses a snapshot based testing approach. To execute the tests, run in the project root:\n\n```shell\nnpm run test:node\n```\n\nTransformer tests are located in:\n\n```\n- spec/transformers/valid/TransformerName/test-name/\n    expected_output.html\n    input.html\n```\n\nThe transformation input is defined in `input.html`, whereas `expected_output.html` contains the expected\noutcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing\na transformer implementation, run:\n\n```shell\nnpm run test:optimizer:snapshot\n```\n\nto store a new snapshot version in `expected_output.html`.\n","gitHead":"fe10db3e56dda4ed952ac629319a6a4ac0e66f54","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v14.15.4+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"14.15.4","dependencies":{"terser":"5.6.0","postcss":"8.2.6","domutils":"2.4.4","lru-cache":"6.0.0","domhandler":"4.0.0","cross-fetch":"3.0.6","htmlparser2":"6.0.0","cssnano-simple":"1.2.2","dom-serializer":"1.2.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"^2.8.0-canary.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.8.0-canary.0","@ampproject/toolbox-runtime-version":"^2.8.0-canary.0","@ampproject/toolbox-validator-rules":"^2.8.0-canary.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.0-canary.2_1613388557115_0.3984547914260985","host":"s3://npm-registry-packages"}},"2.8.0-canary.3":{"name":"@ampproject/toolbox-optimizer","version":"2.8.0-canary.3","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.0-canary.3","maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"a122d8d1c96abe5346328be06886b861b49e947c","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.0-canary.3.tgz","fileCount":47,"integrity":"sha512-q4Z/Gp2TQXpevTwJdu6tvgsJmURf7Ti1auJYOGHes0ye0tTbyVsn3fI3d1r38V2ocDSVYLsOueUc+s8+41UPeQ==","signatures":[{"sig":"MEUCIQC8LrpzhH9+m3u2tBLFanq5Ui9jPyp4sVgzLmNK6KM9jwIgM/j7X5bWA3VWGhHX6oYGvIPVfX78i6jwRsGVkR7K+qU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":215150,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgKseUCRA9TVsSAnZWagAAK6UP/0ydu9ksYahV9/LA3eEz\nWeSKVeHRuwg1kbHVSQOjmTctYNN2+ssuBKJmPMa7O7Kym2uC5DBtjqRVt64b\njujozXNJP6coj4QQNLXJOa+ySdUSndZP0Dz+nWmpJLJxBPhiuFZKoC7OYMua\nYdgT7tNNw6BCTcBFekY7+cUp5BRlaU4QdmTNdJQrKD7MGhCLp/ngEfV4dJgm\n+F21NrgeuIDRPnEC1/9eSJjSR2My/HhEtk/FyXa+b3Xh1tuf3gyFgxOBNajg\nmKJWmWMKPjF8A80xsSz4MXsNVkohZr2jQH6D9IGmST84/1o2GXWFaUa8c70n\nMgWn6/uxqpkwhjo053yOrXZ1Ur5AVT2ufpdYUb1rL/NtnZOBpANRKGGPzWnD\nbA4UJByn9ToKvvdTVNuz6MtseQAOeH5bDyveel9Aur/vji5UOZZzv4WsPPTL\nOFY+eZmUjOTEtMy9vZ/6zEFSWdqzLfaT6cNdnpRAWOPrpLISaynZpomvnOAi\nL46061g73KtmnixlBrJHbtb5vOW8aMFq4GXb3dNJDF9PUaPtIFW7DWf0eidk\n4ByMpbkfed/6XM+1RE73TRwun0994Ipf28Un3Rl/RTIAR97eCCi4v3I/FERC\n/S777+leGNNcgrkUJDmHucAFrxBVrlgNjQ6+Hcs5DRKkby/o6qFrhRAnhBsW\noRHh\r\n=aRhD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to simplify creating AMP pages and improve AMP rendering performance. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n- [Server-side render AMP layouts](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/).\n- **Automatically import all missing AMP component scripts**.\n- **Automatically add any missing mandatory AMP tags**.\n- Auto detects and preloads hero images from amp-img, amp-iframe, amp-video, or amp-video-iframe.\n- Remove the AMP boilerplate (when possible).\n- Remove not needed whitespace.\n- Extract and move CSS keyframe animations to the bottom of the page.\n- Optimize AMP framework and custom font loading\n- Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n- AMP Optimizer will produce valid AMP.\n- AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\n### API\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>`;\n\nampOptimizer.transformHtml(originalHtml).then((optimizedHtml) => {\n  console.log(optimizedHtml);\n});\n```\n\nYou can find a sample implementation [here](/packages/optimizer/demo/simple). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](/packages/optimizer-express).\n\n### CLI\n\nAMP Optimizer can be used via the [AMP Toolbox CLI](/packages/cli/README.md):\n\n```shell\nnpm install @ampproject/toolbox-cli -g\namp optimize myFile.html\n```\n\nor run without installation via `npx`:\n\n```shell\nnpx @ampproject/toolbox-cli optimize myFile.html\n```\n\n### Options\n\nOptions are passed when creating a new AMP Optimizer instance:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  verbose: true\n});\n...\n```\n\nAvailable options are:\n\n- [autoAddMandatoryTags](#autoaddmandatorytags)\n- [autoExtensionImport](#autoextensionimport)\n- [esmModulesEnabled](#esmmodulesenabled)\n- [extensionVersions](#extensionversions)\n- [fetch](#fetch)\n- [format](#format)\n- [imageBasePath](#imagebasePath)\n- [imageOptimizer](#imageoptimizer)\n- [lts](#lts)\n- [markdown](#markdown)\n- [minify](#minify)\n- [optimizeHeroImages](#optimizeheroimages)\n- [preloadHeroImage](#preloadheroimage)\n- [verbose](#verbose)\n\n#### `autoAddMandatoryTags`\n\nAutomatically inject any missing markup required by AMP.\n\n- name: `autoAddMandatoryTags`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `autoExtensionImport`\n\nAutomatically import any missing AMP Extensions (e.g. amp-carousel).\n\n- name: `autoExtensionImport`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `esmModulesEnabled`\n\nEnables the smaller ESM module version of AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n- name: `esmModulesEnabled`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `extensionVersions`\n\nSpecify version numbers to use for automatically imported Extensions. If not defined, default to latest.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  extensionVersions: {\n    'amp-twitter': '0.1',\n  },\n});\n```\n\n- name: `extensionVersions`\n- valid options: `OBJECT`\n- default: `{}`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `format`\n\nSpecifies the AMP format of the input file. Defaults to `AMP`.\n\n- name: `format`\n- valid options: `[AMP|AMP4EMAIL|AMP4ADS]`\n- default: `AMP`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js), [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `experimentEsm`\n\nEnable [JavaScript Module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) support for AMP runtime and components. AMP Optimizer will generate module/nonmodule script imports for AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n**Warning: this will result in invalid AMP pages.**\n\n- name: `experimentEsm`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `imageBasePath`\n\nSpecifies a base path used to resolve an image during build,\nthis can be a file system path or URL prefix. You can also pass a function\n`(imgSrc, params) => '../img/' + imgSrc` for dynamically calculating the image path.\n\n- name: `imageBasePath`\n- valid options: `STRING|FUNCTION`\n- default: undefined\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `imageOptimizer`\n\nEnable automated image `srcset` generation by providing a function for calculating `srcset` URLs for a given image `src`. The function should return a URL pointing to a version of the `src` image with the given `width`. If no image is available, it should return a falsy value.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  imageOptimizer: (src, width) => `${src}?width=${width}`,\n});\n```\n\n- name: `imageOptimizer`\n- valid options: `FUNCTION`\n- default: undefined\n- used by: [OptimizeImages](lib/transformers/OptimizeImages.js)\n\n#### `lts`\n\nUse [long-term stable URLs](https://github.com/ampproject/amphtml/blob/main/contributing/lts-release.md) for downloading the AMP runtime and components.\n\n- name: `lts`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `markdown`\n\nThis transformer adds out-of-the-box markdown support. This allows\nusing AMP Optimizer to convert HTML documents created from Markdown\nfiles into valid AMP. A typical conversion flow would be:\n\nREADME.md => HTML => AMP Optimizer => valid AMP\n\nThe only thing this transformer does is converting `<img>` tags into\neither `amp-img` or `amp-anim` tags. All other Markdown features are\nalready supported by AMP. The transformer will try to resolve image\ndimensions from the actual files. Images larger than 320px will automatically\nget an intrinsic layout. For image detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n- name: `markdown`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `minify`\n\nMinifies the generated HTML output and inlined CSS.\n\n- name: `minify`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [MinifyHtml](lib/transformers/MinifyHtml.js), [SeparateKeyframes](lib/transformers/SeparateKeyframes.js)\n\n**Warning:** this setting is not recommended when running AMP Optimizer in your backend on every request as execution time can increase by up to 7x.\n\n#### `optimizeHeroImages`\n\nEnables hero image optimization. Hero images will either be auto detected or you can explicitly mark these by adding the `data-hero` attribute:\n\n```\n<amp-img data-hero src=\"foo.jpg\" ...>\n```\n\nThe maximum number of hero images that can be marked up using `data-hero` is `2`. This number can be increased using the `maxHeroImageCount` parameter.\n\nHero images are optimized by server-side rendering the `img` element inside the `amp-img` element. This can siginificantly improve image rendering performance and reduce the [largest contentful paint](https://web.dev/lcp/) (LCP) metric from [Core Web Vitals](https://web.dev/vitals/).\n\n- name: `optimizeHeroImages`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeHeroImages](lib/transformers/OptimizeHeroImages.js)\n\n#### `preloadHeroImage`\n\nDeprecated, use [optimizeHeroImages](#optimizeheroimages) instead. \n\n#### `verbose`\n\nEnable verbose mode with more detailed logging output.\n\n- name: `verbose`\n- valid options: `[true|false]`\n- default: `false`\n\n## Features\n\n### Image optimization\n\nAMP Optimizer helps you serve optimized images. For this to work, you need to provide a function that maps an image `src` to a resized `srcset` source value. The image resizing needs to either happen at build time (e.g. for static sites) or via a image hosting service such as [thumbor](https://github.com/thumbor/thumbor).\n\nHere is an example implementation that appends the image width to the `src`:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  // parameters are the amp-img `src` and the `width` of the to be generated srcset source value\n  imageOptimizer: (src, width) => {\n    // we cannot rename if the image does not have a file extension\n    const index = src.lastIndexOf('.');\n    if (index === -1) {\n      // return null means we won't generate a srcset source value for this width\n      return null;\n    }\n    const prefix = src.substring(0, index);\n    const postfix = src.substring(index, src.length);\n    return `${prefix}.${width}w${postfix}`;\n  };\n})\n```\n\nUsing this implementation, AMP Optimizer will transform the following `amp-img` declarations:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img src=\"image1.png\" width=\"400\" height=\"800\" layout=\"responsive\"></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                             image-2x.png 2x\"\n></amp-img>\n```\n\ninto:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img\n  src=\"image1.png\"\n  width=\"400\"\n  height=\"800\"\n  layout=\"responsive\"\n  srcset=\"image1.470w.png 470w, image1.820w.png 820w, image1.1440w.png 1440w\"\n></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                               image-2x.png 2x\"\n></amp-img>\n```\n\n**Important** when using `layout=responsive` use the `width` and `height` attribute to specify the minimum image dimensions. For example, for a full-bleed hero image on mobile, specify the width as`width=320`.\n\n### Auto add incomplete markup\n\nIt's possible to pass incomplete documents and AMP Optimizer will add any\nmissing tags and extension imports required by a valid AMP document.\n\n```js\nconst originalHtml = `\n  <h1>Hello World!</h1>\n  <amp-twitter width=\"375\"\n               height=\"472\"\n               layout=\"responsive\"\n               data-tweetid=\"1182321926473162752\">\n  </amp-twitter>\n`;\n\n// you can pass the canonical URL, default is `.`\nconst opts = {\n  canonical: '/example.html',\n};\nampOptimizer.transformHtml(originalHtml, opts).then((optimizedHtml) => {\n  // optimizedHtml will be a valid AMP document\n  console.log(optimizedHtml);\n});\n```\n\n### Automated Markdown conversion\n\nAMP Optimizer supports converting Markdown to AMPHTML. A typical conversion flow would be:\n\n```\nREADME.md => HTML => AMP Optimizer => valid AMP\n```\n\nThe AMP Optimizer converts `<img>` tags into `<amp-img>` or `<amp-anim>` tags when in Markdown mode. Enable Markdown mode via `markdown : true`. AMP Optimizer will try to resolve image dimensions from the actual files. Images wider than 320px will automatically get an `intrinsic` layout.\n\nAll other Markdown features are already supported by AMP.\n\nYou can pass an additional option `imageBasePath` to specify a base path used to resolve an image during build, this can be a file system path or URL prefix.\n\n**Important:** for image size detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n```shell\nnpm install probe-image-size --save-dev\n```\n\nExample:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst md = require('markdown-it')({\n  // don't sanitize html if you want to support AMP components in Markdown\n  html: true,\n});\n\n// enable markdown mode\nconst ampOptimizer = AmpOptimizer.create({\n  markdown: true,\n});\n\nconst markdown = `\n# Markdown 🤯\n\nHere is an image declared in Markdown syntax:\n\n![A random image](https://unsplash.it/800/600).\n\nYou can directly declare AMP components:\n\n<amp-twitter width=\"375\"\n             height=\"472\"\n             layout=\"responsive\"\n             data-tweetid=\"1182321926473162752\">\n</amp-twitter>\n\nAny missing extensions will be automatically imported.\n`;\n\nconst html = md.render(markdown);\n\nconst amphtml = await ampOptimizer.transformHtml(html, {\n  canonical: filePath,\n});\n```\n\nYou can find a working sample [here](/packages/optimizer/demo/markdown).\n\n## Extending AMP Optimizer with custom transformations\n\nAMP Optimizer supports custom HTML transformations:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst {createElement, firstChildByTag, appendChild} = AmpOptimizer.NodeUtils;\n\nclass CustomTransformer {\n  constructor(config) {\n    this.log_ = config.log.tag('CUSTOM');\n  }\n  transform(tree, params) {\n    this.log_.info('Running custom transformation for ', params.filePath);\n    const html = firstChildByTag(tree, 'html');\n    if (!html) return;\n    const head = firstChildByTag(html, 'head');\n    if (!head) return;\n    const desc = createElement('meta', {\n      name: 'description',\n      content: 'this is just a demo',\n    });\n    appendChild(head, desc);\n  }\n}\n\n// it's best to run custom transformers first\nconst customTransformations = [CustomTransformer, ...AmpOptimizer.TRANSFORMATIONS_AMP_FIRST];\n\n// pass custom transformers when creating the optimizer\nconst optimizer = AmpOptimizer.create({\n  transformations: customTransformations,\n});\n// you can add custom parameters on a per document basis\nconst transformedHtml = await optimizer.transformHtml(html, {\n  filePath,\n});\n```\n\nCheckout [the samples](/packages/optimizer/demo/simple/index.js) to learn how to customize AMP Optimizer.\n\n## Best Practices\n\n### Make sure to enable AMP Boilerplate removal\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n- if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```js\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true,\n});\n```\n\n... or for individual pages:\n\n```js\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true,\n});\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length).replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](/packages/runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion,\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n- `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n- `imageBasePath`: specifies a base path used to resolve an image during build.\n- `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n- `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp',\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Fallback API for amp-geo\n\nIdeally, when self-hosting the AMP framework, `amp-geo-0.1.js` should be patched at delivery time to replace `{{AMP_ISO_COUNTRY_HOTPATCH}}` with the ISO 3166-1 alpha-2 country code where the request originated ([reference](https://github.com/ampproject/amphtml/blob/main/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem)). If your host does not have this capability, you can instead rely on a web API to return the country at runtime. The web API must be secure (HTTPS), adhere to [AMP CORS guidelines](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests/), and return JSON in the following format:\n\n```js\n{\"country\": \"de\"}\n```\n\nwhere in this example, `de` is the ISO 3166-1 alpha-2 country code for Germany.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  // this will instruct amp-geo to fetch the user's country from an API\n  // which returns JSON in format: {\"country\": \"de\"}\n  geoApiUrl: 'https://example.com/geo',\n});\n\nconsole.log(optimizedHtml);\n```\n\n## Development & Testing\n\nAMP Optimizer uses a snapshot based testing approach. To execute the tests, run in the project root:\n\n```shell\nnpm run test:node\n```\n\nTransformer tests are located in:\n\n```\n- spec/transformers/valid/TransformerName/test-name/\n    expected_output.html\n    input.html\n```\n\nThe transformation input is defined in `input.html`, whereas `expected_output.html` contains the expected\noutcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing\na transformer implementation, run:\n\n```shell\nnpm run test:optimizer:snapshot\n```\n\nto store a new snapshot version in `expected_output.html`.\n","gitHead":"fec25a83d058b3afbe2f8bd5a6c2a6ca8efd430e","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v14.15.4+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"14.15.4","dependencies":{"terser":"5.6.0","postcss":"8.2.6","domutils":"2.4.4","lru-cache":"6.0.0","domhandler":"4.0.0","cross-fetch":"3.0.6","htmlparser2":"6.0.0","cssnano-simple":"1.2.2","dom-serializer":"1.2.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"^2.8.0-canary.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.8.0-canary.0","@ampproject/toolbox-runtime-version":"^2.8.0-canary.0","@ampproject/toolbox-validator-rules":"^2.8.0-canary.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.0-canary.3_1613416340214_0.7533919447871302","host":"s3://npm-registry-packages"}},"2.8.0-canary.4":{"name":"@ampproject/toolbox-optimizer","version":"2.8.0-canary.4","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.0-canary.4","maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"a7b1116f4ca76eb1fccac7a9d087a69de4fa9dce","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.0-canary.4.tgz","fileCount":48,"integrity":"sha512-sEueXom2OaR4HyYnIwVu0DFG1iAby/aKo+Dio0RjkoZZO07OfzQvcVsR+kypgANxfOr2XnyRnyUJKVmEiEDehA==","signatures":[{"sig":"MEQCIF6F3OR8ZgXBA+T9B7QJSL/7kIcGMCsenA2KhWWeAG6MAiA9LFrIunww8IbO9sLE4mm5UPGD0AR4pSewOKIjmGvLRA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":218369,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgPjWRCRA9TVsSAnZWagAA7FsP/RNi/eEQze05Te4ssEUh\nnHJyTJhswKuL+Q6+V2VBLk578kBSqDoHKZcEmE5HJ/hAaeS2VRtGY4b5vm5f\nqFyEg5c/RY1Q8PIFzuzImqAcRP6cgxtuexPvec9cCt08U5beBd668zBFlYA+\n2XUPzyMMuRf1joMee8k46GN91bTOzg5v4kgahDFS52ws0LJAtdVn/gLpza7a\nhMprGd2mUfio6uTuWdZykJkn5U0uvUv6+ujXPVFNJe9kcUVzZkuFCTvZcnm7\nQ2bQQddwMOZ2Y6YeGMjOHl07qAzZHm1jiBKbOA8k0A0/0X60TRUsQ+3w18DO\nl7BA0Kug+roLSKkPEbUKqotY7MFcaW21tQKT27nYGe+hb2zW+eDR60VKZk64\nn3U+49cvfPRDif5WszwLr2c/QuzgtQuj2XEL7iOXMAKYxIo0LTCRyDfqDRQS\nIS8y9KW95hpCN2jPYSCb0npdVl+ac+an5uXFMSrsaE8JTWILNhfylPQGLgJa\nK2LkcPG62JsM1zWQfZBfJLRqawS5twj/Y5yuFfCJJkrQY7WO67LSPo5P0ZyI\njQmg8bKkT5CjGRcYe5Q18G8zB4O3VQHP6F2+jVLhezU4bA3GVgvFR3X4HpKR\nTmJe22Wlcl7hmzfzXUyYToflZ40VY49yBdchiorUsCLqsgBKefAf/jNJaAS9\noQEQ\r\n=vx/q\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to simplify creating AMP pages and improve AMP rendering performance. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n- [Server-side render AMP layouts](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/).\n- **Automatically import all missing AMP component scripts**.\n- **Automatically add any missing mandatory AMP tags**.\n- Auto detects and preloads hero images from amp-img, amp-iframe, amp-video, or amp-video-iframe.\n- Remove the AMP boilerplate (when possible).\n- Remove not needed whitespace.\n- Extract and move CSS keyframe animations to the bottom of the page.\n- Optimize AMP framework and custom font loading\n- Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n- AMP Optimizer will produce valid AMP.\n- AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\n### API\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>`;\n\nampOptimizer.transformHtml(originalHtml).then((optimizedHtml) => {\n  console.log(optimizedHtml);\n});\n```\n\nYou can find a sample implementation [here](/packages/optimizer/demo/simple). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](/packages/optimizer-express).\n\n### CLI\n\nAMP Optimizer can be used via the [AMP Toolbox CLI](/packages/cli/README.md):\n\n```shell\nnpm install @ampproject/toolbox-cli -g\namp optimize myFile.html\n```\n\nor run without installation via `npx`:\n\n```shell\nnpx @ampproject/toolbox-cli optimize myFile.html\n```\n\n### Options\n\nOptions are passed when creating a new AMP Optimizer instance:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  verbose: true\n});\n...\n```\n\nAvailable options are:\n\n- [autoAddMandatoryTags](#autoaddmandatorytags)\n- [autoExtensionImport](#autoextensionimport)\n- [esmModulesEnabled](#esmmodulesenabled)\n- [extensionVersions](#extensionversions)\n- [fetch](#fetch)\n- [format](#format)\n- [imageBasePath](#imagebasePath)\n- [imageOptimizer](#imageoptimizer)\n- [lts](#lts)\n- [markdown](#markdown)\n- [minify](#minify)\n- [optimizeAmpBind](#optimizeampbind)\n- [optimizeHeroImages](#optimizeheroimages)\n- [preloadHeroImage](#preloadheroimage)\n- [verbose](#verbose)\n\n#### `autoAddMandatoryTags`\n\nAutomatically inject any missing markup required by AMP.\n\n- name: `autoAddMandatoryTags`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `autoExtensionImport`\n\nAutomatically import any missing AMP Extensions (e.g. amp-carousel).\n\n- name: `autoExtensionImport`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `esmModulesEnabled`\n\nEnables the smaller ESM module version of AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n- name: `esmModulesEnabled`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `extensionVersions`\n\nSpecify version numbers to use for automatically imported Extensions. If not defined, default to latest.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  extensionVersions: {\n    'amp-twitter': '0.1',\n  },\n});\n```\n\n- name: `extensionVersions`\n- valid options: `OBJECT`\n- default: `{}`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `format`\n\nSpecifies the AMP format of the input file. Defaults to `AMP`.\n\n- name: `format`\n- valid options: `[AMP|AMP4EMAIL|AMP4ADS]`\n- default: `AMP`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js), [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `experimentEsm`\n\nEnable [JavaScript Module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) support for AMP runtime and components. AMP Optimizer will generate module/nonmodule script imports for AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n**Warning: this will result in invalid AMP pages.**\n\n- name: `experimentEsm`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `imageBasePath`\n\nSpecifies a base path used to resolve an image during build,\nthis can be a file system path or URL prefix. You can also pass a function\n`(imgSrc, params) => '../img/' + imgSrc` for dynamically calculating the image path.\n\n- name: `imageBasePath`\n- valid options: `STRING|FUNCTION`\n- default: undefined\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `imageOptimizer`\n\nEnable automated image `srcset` generation by providing a function for calculating `srcset` URLs for a given image `src`. The function should return a URL pointing to a version of the `src` image with the given `width`. If no image is available, it should return a falsy value.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  imageOptimizer: (src, width) => `${src}?width=${width}`,\n});\n```\n\n- name: `imageOptimizer`\n- valid options: `FUNCTION`\n- default: undefined\n- used by: [OptimizeImages](lib/transformers/OptimizeImages.js)\n\n#### `lts`\n\nUse [long-term stable URLs](https://github.com/ampproject/amphtml/blob/main/contributing/lts-release.md) for downloading the AMP runtime and components.\n\n- name: `lts`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `markdown`\n\nThis transformer adds out-of-the-box markdown support. This allows\nusing AMP Optimizer to convert HTML documents created from Markdown\nfiles into valid AMP. A typical conversion flow would be:\n\nREADME.md => HTML => AMP Optimizer => valid AMP\n\nThe only thing this transformer does is converting `<img>` tags into\neither `amp-img` or `amp-anim` tags. All other Markdown features are\nalready supported by AMP. The transformer will try to resolve image\ndimensions from the actual files. Images larger than 320px will automatically\nget an intrinsic layout. For image detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n- name: `markdown`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `minify`\n\nMinifies the generated HTML output and inlined CSS.\n\n- name: `minify`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [MinifyHtml](lib/transformers/MinifyHtml.js), [SeparateKeyframes](lib/transformers/SeparateKeyframes.js)\n\n**Warning:** this setting is not recommended when running AMP Optimizer in your backend on every request as execution time can increase by up to 7x.\n\n#### `optimizeAmpBind`\n\nEnables a considerably faster scanning method in `amp-bind`, by injecting a `i-amphtml-binding` attribute on all elements with a bound attribute.\n\n- name: `optimizeAmpBind`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [OptimizeAmpBind](lib/transformers/OptimizeAmpBind.js)\n\n#### `optimizeHeroImages`\n\nEnables hero image optimization. Hero images will either be auto detected or you can explicitly mark these by adding the `data-hero` attribute:\n\n```\n<amp-img data-hero src=\"foo.jpg\" ...>\n```\n\nThe maximum number of hero images that can be marked up using `data-hero` is `2`. This number can be increased using the `maxHeroImageCount` parameter.\n\nHero images are optimized by server-side rendering the `img` element inside the `amp-img` element. This can siginificantly improve image rendering performance and reduce the [largest contentful paint](https://web.dev/lcp/) (LCP) metric from [Core Web Vitals](https://web.dev/vitals/).\n\n- name: `optimizeHeroImages`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeHeroImages](lib/transformers/OptimizeHeroImages.js)\n\n#### `preloadHeroImage`\n\nDeprecated, use [optimizeHeroImages](#optimizeheroimages) instead. \n\n#### `verbose`\n\nEnable verbose mode with more detailed logging output.\n\n- name: `verbose`\n- valid options: `[true|false]`\n- default: `false`\n\n## Features\n\n### Image optimization\n\nAMP Optimizer helps you serve optimized images. For this to work, you need to provide a function that maps an image `src` to a resized `srcset` source value. The image resizing needs to either happen at build time (e.g. for static sites) or via a image hosting service such as [thumbor](https://github.com/thumbor/thumbor).\n\nHere is an example implementation that appends the image width to the `src`:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  // parameters are the amp-img `src` and the `width` of the to be generated srcset source value\n  imageOptimizer: (src, width) => {\n    // we cannot rename if the image does not have a file extension\n    const index = src.lastIndexOf('.');\n    if (index === -1) {\n      // return null means we won't generate a srcset source value for this width\n      return null;\n    }\n    const prefix = src.substring(0, index);\n    const postfix = src.substring(index, src.length);\n    return `${prefix}.${width}w${postfix}`;\n  };\n})\n```\n\nUsing this implementation, AMP Optimizer will transform the following `amp-img` declarations:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img src=\"image1.png\" width=\"400\" height=\"800\" layout=\"responsive\"></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                             image-2x.png 2x\"\n></amp-img>\n```\n\ninto:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img\n  src=\"image1.png\"\n  width=\"400\"\n  height=\"800\"\n  layout=\"responsive\"\n  srcset=\"image1.470w.png 470w, image1.820w.png 820w, image1.1440w.png 1440w\"\n></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                               image-2x.png 2x\"\n></amp-img>\n```\n\n**Important** when using `layout=responsive` use the `width` and `height` attribute to specify the minimum image dimensions. For example, for a full-bleed hero image on mobile, specify the width as`width=320`.\n\n### Auto add incomplete markup\n\nIt's possible to pass incomplete documents and AMP Optimizer will add any\nmissing tags and extension imports required by a valid AMP document.\n\n```js\nconst originalHtml = `\n  <h1>Hello World!</h1>\n  <amp-twitter width=\"375\"\n               height=\"472\"\n               layout=\"responsive\"\n               data-tweetid=\"1182321926473162752\">\n  </amp-twitter>\n`;\n\n// you can pass the canonical URL, default is `.`\nconst opts = {\n  canonical: '/example.html',\n};\nampOptimizer.transformHtml(originalHtml, opts).then((optimizedHtml) => {\n  // optimizedHtml will be a valid AMP document\n  console.log(optimizedHtml);\n});\n```\n\n### Automated Markdown conversion\n\nAMP Optimizer supports converting Markdown to AMPHTML. A typical conversion flow would be:\n\n```\nREADME.md => HTML => AMP Optimizer => valid AMP\n```\n\nThe AMP Optimizer converts `<img>` tags into `<amp-img>` or `<amp-anim>` tags when in Markdown mode. Enable Markdown mode via `markdown : true`. AMP Optimizer will try to resolve image dimensions from the actual files. Images wider than 320px will automatically get an `intrinsic` layout.\n\nAll other Markdown features are already supported by AMP.\n\nYou can pass an additional option `imageBasePath` to specify a base path used to resolve an image during build, this can be a file system path or URL prefix.\n\n**Important:** for image size detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n```shell\nnpm install probe-image-size --save-dev\n```\n\nExample:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst md = require('markdown-it')({\n  // don't sanitize html if you want to support AMP components in Markdown\n  html: true,\n});\n\n// enable markdown mode\nconst ampOptimizer = AmpOptimizer.create({\n  markdown: true,\n});\n\nconst markdown = `\n# Markdown 🤯\n\nHere is an image declared in Markdown syntax:\n\n![A random image](https://unsplash.it/800/600).\n\nYou can directly declare AMP components:\n\n<amp-twitter width=\"375\"\n             height=\"472\"\n             layout=\"responsive\"\n             data-tweetid=\"1182321926473162752\">\n</amp-twitter>\n\nAny missing extensions will be automatically imported.\n`;\n\nconst html = md.render(markdown);\n\nconst amphtml = await ampOptimizer.transformHtml(html, {\n  canonical: filePath,\n});\n```\n\nYou can find a working sample [here](/packages/optimizer/demo/markdown).\n\n## Extending AMP Optimizer with custom transformations\n\nAMP Optimizer supports custom HTML transformations:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst {createElement, firstChildByTag, appendChild} = AmpOptimizer.NodeUtils;\n\nclass CustomTransformer {\n  constructor(config) {\n    this.log_ = config.log.tag('CUSTOM');\n  }\n  transform(tree, params) {\n    this.log_.info('Running custom transformation for ', params.filePath);\n    const html = firstChildByTag(tree, 'html');\n    if (!html) return;\n    const head = firstChildByTag(html, 'head');\n    if (!head) return;\n    const desc = createElement('meta', {\n      name: 'description',\n      content: 'this is just a demo',\n    });\n    appendChild(head, desc);\n  }\n}\n\n// it's best to run custom transformers first\nconst customTransformations = [CustomTransformer, ...AmpOptimizer.TRANSFORMATIONS_AMP_FIRST];\n\n// pass custom transformers when creating the optimizer\nconst optimizer = AmpOptimizer.create({\n  transformations: customTransformations,\n});\n// you can add custom parameters on a per document basis\nconst transformedHtml = await optimizer.transformHtml(html, {\n  filePath,\n});\n```\n\nCheckout [the samples](/packages/optimizer/demo/simple/index.js) to learn how to customize AMP Optimizer.\n\n## Best Practices\n\n### Make sure to enable AMP Boilerplate removal\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n- if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```js\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true,\n});\n```\n\n... or for individual pages:\n\n```js\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true,\n});\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length).replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](/packages/runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion,\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n- `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n- `imageBasePath`: specifies a base path used to resolve an image during build.\n- `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n- `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp',\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Fallback API for amp-geo\n\nIdeally, when self-hosting the AMP framework, `amp-geo-0.1.js` should be patched at delivery time to replace `{{AMP_ISO_COUNTRY_HOTPATCH}}` with the ISO 3166-1 alpha-2 country code where the request originated ([reference](https://github.com/ampproject/amphtml/blob/main/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem)). If your host does not have this capability, you can instead rely on a web API to return the country at runtime. The web API must be secure (HTTPS), adhere to [AMP CORS guidelines](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests/), and return JSON in the following format:\n\n```js\n{\"country\": \"de\"}\n```\n\nwhere in this example, `de` is the ISO 3166-1 alpha-2 country code for Germany.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  // this will instruct amp-geo to fetch the user's country from an API\n  // which returns JSON in format: {\"country\": \"de\"}\n  geoApiUrl: 'https://example.com/geo',\n});\n\nconsole.log(optimizedHtml);\n```\n\n## Development & Testing\n\nAMP Optimizer uses a snapshot based testing approach. To execute the tests, run in the project root:\n\n```shell\nnpm run test:node\n```\n\nTransformer tests are located in:\n\n```\n- spec/transformers/valid/TransformerName/test-name/\n    expected_output.html\n    input.html\n```\n\nThe transformation input is defined in `input.html`, whereas `expected_output.html` contains the expected\noutcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing\na transformer implementation, run:\n\n```shell\nnpm run test:optimizer:snapshot\n```\n\nto store a new snapshot version in `expected_output.html`.\n","gitHead":"0c3af53a631fffeffbe3da1f2d626172fc996f1b","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/3.22.1/node@v14.15.4+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"14.15.4","dependencies":{"terser":"5.6.0","postcss":"8.2.6","domutils":"2.4.4","lru-cache":"6.0.0","domhandler":"4.0.0","cross-fetch":"3.0.6","htmlparser2":"6.0.0","cssnano-simple":"1.2.2","dom-serializer":"1.2.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"^2.8.0-canary.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.8.0-canary.0","@ampproject/toolbox-runtime-version":"^2.8.0-canary.0","@ampproject/toolbox-validator-rules":"^2.8.0-canary.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.0-canary.4_1614689680681_0.969704634045788","host":"s3://npm-registry-packages"}},"2.8.0-canary.5":{"name":"@ampproject/toolbox-optimizer","version":"2.8.0-canary.5","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.0-canary.5","maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"fc2430105f01d86b9a9e3c1e375587c9a76ccc87","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.0-canary.5.tgz","fileCount":48,"integrity":"sha512-PtTNjkQiIwsCukcWwpYcQchBBNJZUfZVYr4vSv31WAkZbHxP9avR/gplgBTMkz1bBGdxgAu7M6qZ72lMkmTf5w==","signatures":[{"sig":"MEUCIBPri7HC2hxXFOOynzXStRxXMw9uLNRaN4thmvihe5vWAiEAvFIRNNeGR8rzvKlQaBustHkqQdlPe3hP9EwtLCKqAxE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":218573,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgS7mGCRA9TVsSAnZWagAAEMQP/3YBQoPeva1icBI0/hWW\n0raOvy4TCe1kaW6RLALmjdVlpn/t3Znm4vWEYsKQL8CINKXKdp6eF99ue8VR\nwoXjt/jOUkVlMZuD1tGVdj8z6H9MrZgbYk3j24oR5Iw9YDcstcnXqsuxVoyh\n0Lg8Ze8Xe1w9VEXDyyARTrEZI0HqR4UAZ0r77cL53Z6zFRW1Xc/00/d1AA54\nWWtZO1c4aPRspbf7IeQYoEWwx5hrlBY6J/bj+6Uo/1Gp/UesiGD536Dyqaga\nLjCaK25hNaHgG81q/UZvIOXKvtAaBv9Y7t4tgVzVot4jwKo3W/uBegsagEJo\nJVfDgN7WBdAspFIxxCUfUqyYfZerT3KVy39x4UDbMfQpzNG9OiJpw9c1pZGb\n39EBZb6bNY7V+47CywwU3mDpkabFDubQvgYsAuiUMCQ0bQz5ofOvlk9100BH\ngyYK3KZ7ObDKZ+hOyQK6fciBwmw9qF+W58RNfgngOYt0zdUT3CGgpCjS/+ND\nFISVIIYgWa1S4hXtWTvuw3NXOE2bQ5ypG5HY7Et77kEMJnSmfXJ/pT/0CZPp\ngKVw/9CFPZi+VPVVXCZXw0lNZH7S+fI/S0NVT5+nhGX+y3TSkE0/hXNgZulC\nR+No05KxkEzeLUQcnNd7shDtWju/l0h4ZVS1gyK3x9Z86A8RnXIdKNnOFioD\nNf+H\r\n=jCGt\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to simplify creating AMP pages and improve AMP rendering performance. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n- [Server-side render AMP layouts](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/).\n- **Automatically import all missing AMP component scripts**.\n- **Automatically add any missing mandatory AMP tags**.\n- Auto detects and preloads hero images from amp-img, amp-iframe, amp-video, or amp-video-iframe.\n- Remove the AMP boilerplate (when possible).\n- Remove not needed whitespace.\n- Extract and move CSS keyframe animations to the bottom of the page.\n- Optimize AMP framework and custom font loading\n- Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n- AMP Optimizer will produce valid AMP.\n- AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\n### API\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>`;\n\nampOptimizer.transformHtml(originalHtml).then((optimizedHtml) => {\n  console.log(optimizedHtml);\n});\n```\n\nYou can find a sample implementation [here](/packages/optimizer/demo/simple). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](/packages/optimizer-express).\n\n### CLI\n\nAMP Optimizer can be used via the [AMP Toolbox CLI](/packages/cli/README.md):\n\n```shell\nnpm install @ampproject/toolbox-cli -g\namp optimize myFile.html\n```\n\nor run without installation via `npx`:\n\n```shell\nnpx @ampproject/toolbox-cli optimize myFile.html\n```\n\n### Options\n\nOptions are passed when creating a new AMP Optimizer instance:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  verbose: true\n});\n...\n```\n\nAvailable options are:\n\n- [autoAddMandatoryTags](#autoaddmandatorytags)\n- [autoExtensionImport](#autoextensionimport)\n- [esmModulesEnabled](#esmmodulesenabled)\n- [extensionVersions](#extensionversions)\n- [fetch](#fetch)\n- [format](#format)\n- [imageBasePath](#imagebasePath)\n- [imageOptimizer](#imageoptimizer)\n- [lts](#lts)\n- [markdown](#markdown)\n- [minify](#minify)\n- [optimizeAmpBind](#optimizeampbind)\n- [optimizeHeroImages](#optimizeheroimages)\n- [preloadHeroImage](#preloadheroimage)\n- [verbose](#verbose)\n\n#### `autoAddMandatoryTags`\n\nAutomatically inject any missing markup required by AMP.\n\n- name: `autoAddMandatoryTags`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `autoExtensionImport`\n\nAutomatically import any missing AMP Extensions (e.g. amp-carousel).\n\n- name: `autoExtensionImport`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `esmModulesEnabled`\n\nEnables the smaller ESM module version of AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n**Warning: This is incompatible with AMP Packager unless using a prerelease version containing [this commit](https://github.com/ampproject/amppackager/commit/92387ecf97e420eec10b378282383c59f95ab45f).**\n\n- name: `esmModulesEnabled`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `extensionVersions`\n\nSpecify version numbers to use for automatically imported Extensions. If not defined, default to latest.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  extensionVersions: {\n    'amp-twitter': '0.1',\n  },\n});\n```\n\n- name: `extensionVersions`\n- valid options: `OBJECT`\n- default: `{}`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `format`\n\nSpecifies the AMP format of the input file. Defaults to `AMP`.\n\n- name: `format`\n- valid options: `[AMP|AMP4EMAIL|AMP4ADS]`\n- default: `AMP`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js), [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `experimentEsm`\n\nEnable [JavaScript Module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) support for AMP runtime and components. AMP Optimizer will generate module/nonmodule script imports for AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n**Warning: this will result in invalid AMP pages.**\n\n- name: `experimentEsm`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `imageBasePath`\n\nSpecifies a base path used to resolve an image during build,\nthis can be a file system path or URL prefix. You can also pass a function\n`(imgSrc, params) => '../img/' + imgSrc` for dynamically calculating the image path.\n\n- name: `imageBasePath`\n- valid options: `STRING|FUNCTION`\n- default: undefined\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `imageOptimizer`\n\nEnable automated image `srcset` generation by providing a function for calculating `srcset` URLs for a given image `src`. The function should return a URL pointing to a version of the `src` image with the given `width`. If no image is available, it should return a falsy value.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  imageOptimizer: (src, width) => `${src}?width=${width}`,\n});\n```\n\n- name: `imageOptimizer`\n- valid options: `FUNCTION`\n- default: undefined\n- used by: [OptimizeImages](lib/transformers/OptimizeImages.js)\n\n#### `lts`\n\nUse [long-term stable URLs](https://github.com/ampproject/amphtml/blob/main/contributing/lts-release.md) for downloading the AMP runtime and components.\n\n- name: `lts`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `markdown`\n\nThis transformer adds out-of-the-box markdown support. This allows\nusing AMP Optimizer to convert HTML documents created from Markdown\nfiles into valid AMP. A typical conversion flow would be:\n\nREADME.md => HTML => AMP Optimizer => valid AMP\n\nThe only thing this transformer does is converting `<img>` tags into\neither `amp-img` or `amp-anim` tags. All other Markdown features are\nalready supported by AMP. The transformer will try to resolve image\ndimensions from the actual files. Images larger than 320px will automatically\nget an intrinsic layout. For image detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n- name: `markdown`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `minify`\n\nMinifies the generated HTML output and inlined CSS.\n\n- name: `minify`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [MinifyHtml](lib/transformers/MinifyHtml.js), [SeparateKeyframes](lib/transformers/SeparateKeyframes.js)\n\n**Warning:** this setting is not recommended when running AMP Optimizer in your backend on every request as execution time can increase by up to 7x.\n\n#### `optimizeAmpBind`\n\nEnables a considerably faster scanning method in `amp-bind`, by injecting a `i-amphtml-binding` attribute on all elements with a bound attribute.\n\n- name: `optimizeAmpBind`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [OptimizeAmpBind](lib/transformers/OptimizeAmpBind.js)\n\n#### `optimizeHeroImages`\n\nEnables hero image optimization. Hero images will either be auto detected or you can explicitly mark these by adding the `data-hero` attribute:\n\n```\n<amp-img data-hero src=\"foo.jpg\" ...>\n```\n\nThe maximum number of hero images that can be marked up using `data-hero` is `2`. This number can be increased using the `maxHeroImageCount` parameter.\n\nHero images are optimized by server-side rendering the `img` element inside the `amp-img` element. This can siginificantly improve image rendering performance and reduce the [largest contentful paint](https://web.dev/lcp/) (LCP) metric from [Core Web Vitals](https://web.dev/vitals/).\n\n- name: `optimizeHeroImages`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeHeroImages](lib/transformers/OptimizeHeroImages.js)\n\n#### `preloadHeroImage`\n\nDeprecated, use [optimizeHeroImages](#optimizeheroimages) instead. \n\n#### `verbose`\n\nEnable verbose mode with more detailed logging output.\n\n- name: `verbose`\n- valid options: `[true|false]`\n- default: `false`\n\n## Features\n\n### Image optimization\n\nAMP Optimizer helps you serve optimized images. For this to work, you need to provide a function that maps an image `src` to a resized `srcset` source value. The image resizing needs to either happen at build time (e.g. for static sites) or via a image hosting service such as [thumbor](https://github.com/thumbor/thumbor).\n\nHere is an example implementation that appends the image width to the `src`:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  // parameters are the amp-img `src` and the `width` of the to be generated srcset source value\n  imageOptimizer: (src, width) => {\n    // we cannot rename if the image does not have a file extension\n    const index = src.lastIndexOf('.');\n    if (index === -1) {\n      // return null means we won't generate a srcset source value for this width\n      return null;\n    }\n    const prefix = src.substring(0, index);\n    const postfix = src.substring(index, src.length);\n    return `${prefix}.${width}w${postfix}`;\n  };\n})\n```\n\nUsing this implementation, AMP Optimizer will transform the following `amp-img` declarations:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img src=\"image1.png\" width=\"400\" height=\"800\" layout=\"responsive\"></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                             image-2x.png 2x\"\n></amp-img>\n```\n\ninto:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img\n  src=\"image1.png\"\n  width=\"400\"\n  height=\"800\"\n  layout=\"responsive\"\n  srcset=\"image1.470w.png 470w, image1.820w.png 820w, image1.1440w.png 1440w\"\n></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                               image-2x.png 2x\"\n></amp-img>\n```\n\n**Important** when using `layout=responsive` use the `width` and `height` attribute to specify the minimum image dimensions. For example, for a full-bleed hero image on mobile, specify the width as`width=320`.\n\n### Auto add incomplete markup\n\nIt's possible to pass incomplete documents and AMP Optimizer will add any\nmissing tags and extension imports required by a valid AMP document.\n\n```js\nconst originalHtml = `\n  <h1>Hello World!</h1>\n  <amp-twitter width=\"375\"\n               height=\"472\"\n               layout=\"responsive\"\n               data-tweetid=\"1182321926473162752\">\n  </amp-twitter>\n`;\n\n// you can pass the canonical URL, default is `.`\nconst opts = {\n  canonical: '/example.html',\n};\nampOptimizer.transformHtml(originalHtml, opts).then((optimizedHtml) => {\n  // optimizedHtml will be a valid AMP document\n  console.log(optimizedHtml);\n});\n```\n\n### Automated Markdown conversion\n\nAMP Optimizer supports converting Markdown to AMPHTML. A typical conversion flow would be:\n\n```\nREADME.md => HTML => AMP Optimizer => valid AMP\n```\n\nThe AMP Optimizer converts `<img>` tags into `<amp-img>` or `<amp-anim>` tags when in Markdown mode. Enable Markdown mode via `markdown : true`. AMP Optimizer will try to resolve image dimensions from the actual files. Images wider than 320px will automatically get an `intrinsic` layout.\n\nAll other Markdown features are already supported by AMP.\n\nYou can pass an additional option `imageBasePath` to specify a base path used to resolve an image during build, this can be a file system path or URL prefix.\n\n**Important:** for image size detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n```shell\nnpm install probe-image-size --save-dev\n```\n\nExample:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst md = require('markdown-it')({\n  // don't sanitize html if you want to support AMP components in Markdown\n  html: true,\n});\n\n// enable markdown mode\nconst ampOptimizer = AmpOptimizer.create({\n  markdown: true,\n});\n\nconst markdown = `\n# Markdown 🤯\n\nHere is an image declared in Markdown syntax:\n\n![A random image](https://unsplash.it/800/600).\n\nYou can directly declare AMP components:\n\n<amp-twitter width=\"375\"\n             height=\"472\"\n             layout=\"responsive\"\n             data-tweetid=\"1182321926473162752\">\n</amp-twitter>\n\nAny missing extensions will be automatically imported.\n`;\n\nconst html = md.render(markdown);\n\nconst amphtml = await ampOptimizer.transformHtml(html, {\n  canonical: filePath,\n});\n```\n\nYou can find a working sample [here](/packages/optimizer/demo/markdown).\n\n## Extending AMP Optimizer with custom transformations\n\nAMP Optimizer supports custom HTML transformations:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst {createElement, firstChildByTag, appendChild} = AmpOptimizer.NodeUtils;\n\nclass CustomTransformer {\n  constructor(config) {\n    this.log_ = config.log.tag('CUSTOM');\n  }\n  transform(tree, params) {\n    this.log_.info('Running custom transformation for ', params.filePath);\n    const html = firstChildByTag(tree, 'html');\n    if (!html) return;\n    const head = firstChildByTag(html, 'head');\n    if (!head) return;\n    const desc = createElement('meta', {\n      name: 'description',\n      content: 'this is just a demo',\n    });\n    appendChild(head, desc);\n  }\n}\n\n// it's best to run custom transformers first\nconst customTransformations = [CustomTransformer, ...AmpOptimizer.TRANSFORMATIONS_AMP_FIRST];\n\n// pass custom transformers when creating the optimizer\nconst optimizer = AmpOptimizer.create({\n  transformations: customTransformations,\n});\n// you can add custom parameters on a per document basis\nconst transformedHtml = await optimizer.transformHtml(html, {\n  filePath,\n});\n```\n\nCheckout [the samples](/packages/optimizer/demo/simple/index.js) to learn how to customize AMP Optimizer.\n\n## Best Practices\n\n### Make sure to enable AMP Boilerplate removal\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n- if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```js\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true,\n});\n```\n\n... or for individual pages:\n\n```js\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true,\n});\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length).replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](/packages/runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion,\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n- `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n- `imageBasePath`: specifies a base path used to resolve an image during build.\n- `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n- `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp',\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Fallback API for amp-geo\n\nIdeally, when self-hosting the AMP framework, `amp-geo-0.1.js` should be patched at delivery time to replace `{{AMP_ISO_COUNTRY_HOTPATCH}}` with the ISO 3166-1 alpha-2 country code where the request originated ([reference](https://github.com/ampproject/amphtml/blob/main/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem)). If your host does not have this capability, you can instead rely on a web API to return the country at runtime. The web API must be secure (HTTPS), adhere to [AMP CORS guidelines](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests/), and return JSON in the following format:\n\n```js\n{\"country\": \"de\"}\n```\n\nwhere in this example, `de` is the ISO 3166-1 alpha-2 country code for Germany.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  // this will instruct amp-geo to fetch the user's country from an API\n  // which returns JSON in format: {\"country\": \"de\"}\n  geoApiUrl: 'https://example.com/geo',\n});\n\nconsole.log(optimizedHtml);\n```\n\n## Development & Testing\n\nAMP Optimizer uses a snapshot based testing approach. To execute the tests, run in the project root:\n\n```shell\nnpm run test:node\n```\n\nTransformer tests are located in:\n\n```\n- spec/transformers/valid/TransformerName/test-name/\n    expected_output.html\n    input.html\n```\n\nThe transformation input is defined in `input.html`, whereas `expected_output.html` contains the expected\noutcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing\na transformer implementation, run:\n\n```shell\nnpm run test:optimizer:snapshot\n```\n\nto store a new snapshot version in `expected_output.html`.\n","gitHead":"2937a3e777701f9cec81b1cc36d1b9927a6b4403","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/4.0.0/node@v14.15.4+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"14.15.4","dependencies":{"terser":"5.6.0","postcss":"8.2.8","domutils":"2.5.0","lru-cache":"6.0.0","domhandler":"4.0.0","cross-fetch":"3.0.6","htmlparser2":"6.0.1","cssnano-simple":"1.2.2","dom-serializer":"1.2.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"^2.8.0-canary.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.8.0-canary.0","@ampproject/toolbox-runtime-version":"^2.8.0-canary.0","@ampproject/toolbox-validator-rules":"^2.8.0-canary.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.0-canary.5_1615575430169_0.9776225786675139","host":"s3://npm-registry-packages"}},"2.8.0-canary.6":{"name":"@ampproject/toolbox-optimizer","version":"2.8.0-canary.6","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.0-canary.6","maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"ab77cd5a44c6c736aedacb770106b01faa81d2f0","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.0-canary.6.tgz","fileCount":48,"integrity":"sha512-NXmdDRUD9I1Z/l2MRzbdZuUWvLOdFpJpLFyhkkmoKVoYf0ZxBF0EgHWgtDpv4BfjgIh5KNcxHwzmFFMLJcTUHg==","signatures":[{"sig":"MEUCIQDzKYs3l7oQ0og0mc59vcZAKaaHxvaItTcSkT4Lu+OPZAIgBjqonX6dkNulw7n8ScPXNdlnbKZJcGqziTu4Ieax8/Y=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":218573,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgU40HCRA9TVsSAnZWagAAd4gP/3Zwa3owEW0PCczOGycW\nHfmODZNqXzSWX+5dO1opaic38V0wFCooTbLG5VT0fJMg+k2aOh2/j0lussoY\nvW1uV1DgUxlMf1+N6LIye7lOqGiJm22nLlSM+stqDT6kUjhdNB8bjUBVEyGg\nacjGOt1mIma0RUw1LjX1tBIqVX+7vW7OD6dCwsqPbtqZFe916+kQx8rrjux6\nNQLImKyrCv6JxCmwEPsPqIgOBbWVnZnhxRkEelcBlChRRtEO7AmQTqE1LZ7U\nA2tJ6KdKK0srahWAvvqYDIhNMewsFxFMb6GSO2oQcoQljWgzDRoNqgCteM+h\n14JT7HHJGGUTwXq2kz155mYWYO13azmWXczxjBkjVJ7xnLiJPEyMLUjfNPUn\nk46KHXS5fHGaepMQU263u8fmIbKnLs5UWcMhFR8OlgjrJobHcnh8IS2T2RVq\nkuniAXOX4mKxV7ih+Ff1Xa6o3ug61zbQ7lW6mnE9wWTH/IJx8QBsdhH6LGpC\nVOErowkpXldp/ZOKe8bZx+cXH0vD07BKS0FdNYC1l7AN9V6DRyag7XEFZP71\n4iQpHznQiLNAXdquNS7F2cgHQyB5d5CmkF1cYSzr5PNqrINTAy7CK3AlnfEJ\nolZL9rmMI+ubB0g/r8OHlSkfHJp9O45/sm2lytqvZDzMM883vhcgyvpw/wll\nLlDi\r\n=ri8a\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to simplify creating AMP pages and improve AMP rendering performance. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n- [Server-side render AMP layouts](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/).\n- **Automatically import all missing AMP component scripts**.\n- **Automatically add any missing mandatory AMP tags**.\n- Auto detects and preloads hero images from amp-img, amp-iframe, amp-video, or amp-video-iframe.\n- Remove the AMP boilerplate (when possible).\n- Remove not needed whitespace.\n- Extract and move CSS keyframe animations to the bottom of the page.\n- Optimize AMP framework and custom font loading\n- Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n- AMP Optimizer will produce valid AMP.\n- AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\n### API\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>`;\n\nampOptimizer.transformHtml(originalHtml).then((optimizedHtml) => {\n  console.log(optimizedHtml);\n});\n```\n\nYou can find a sample implementation [here](/packages/optimizer/demo/simple). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](/packages/optimizer-express).\n\n### CLI\n\nAMP Optimizer can be used via the [AMP Toolbox CLI](/packages/cli/README.md):\n\n```shell\nnpm install @ampproject/toolbox-cli -g\namp optimize myFile.html\n```\n\nor run without installation via `npx`:\n\n```shell\nnpx @ampproject/toolbox-cli optimize myFile.html\n```\n\n### Options\n\nOptions are passed when creating a new AMP Optimizer instance:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  verbose: true\n});\n...\n```\n\nAvailable options are:\n\n- [autoAddMandatoryTags](#autoaddmandatorytags)\n- [autoExtensionImport](#autoextensionimport)\n- [esmModulesEnabled](#esmmodulesenabled)\n- [extensionVersions](#extensionversions)\n- [fetch](#fetch)\n- [format](#format)\n- [imageBasePath](#imagebasePath)\n- [imageOptimizer](#imageoptimizer)\n- [lts](#lts)\n- [markdown](#markdown)\n- [minify](#minify)\n- [optimizeAmpBind](#optimizeampbind)\n- [optimizeHeroImages](#optimizeheroimages)\n- [preloadHeroImage](#preloadheroimage)\n- [verbose](#verbose)\n\n#### `autoAddMandatoryTags`\n\nAutomatically inject any missing markup required by AMP.\n\n- name: `autoAddMandatoryTags`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `autoExtensionImport`\n\nAutomatically import any missing AMP Extensions (e.g. amp-carousel).\n\n- name: `autoExtensionImport`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `esmModulesEnabled`\n\nEnables the smaller ESM module version of AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n**Warning: This is incompatible with AMP Packager unless using a prerelease version containing [this commit](https://github.com/ampproject/amppackager/commit/92387ecf97e420eec10b378282383c59f95ab45f).**\n\n- name: `esmModulesEnabled`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `extensionVersions`\n\nSpecify version numbers to use for automatically imported Extensions. If not defined, default to latest.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  extensionVersions: {\n    'amp-twitter': '0.1',\n  },\n});\n```\n\n- name: `extensionVersions`\n- valid options: `OBJECT`\n- default: `{}`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `format`\n\nSpecifies the AMP format of the input file. Defaults to `AMP`.\n\n- name: `format`\n- valid options: `[AMP|AMP4EMAIL|AMP4ADS]`\n- default: `AMP`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js), [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `experimentEsm`\n\nEnable [JavaScript Module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) support for AMP runtime and components. AMP Optimizer will generate module/nonmodule script imports for AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n**Warning: this will result in invalid AMP pages.**\n\n- name: `experimentEsm`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `imageBasePath`\n\nSpecifies a base path used to resolve an image during build,\nthis can be a file system path or URL prefix. You can also pass a function\n`(imgSrc, params) => '../img/' + imgSrc` for dynamically calculating the image path.\n\n- name: `imageBasePath`\n- valid options: `STRING|FUNCTION`\n- default: undefined\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `imageOptimizer`\n\nEnable automated image `srcset` generation by providing a function for calculating `srcset` URLs for a given image `src`. The function should return a URL pointing to a version of the `src` image with the given `width`. If no image is available, it should return a falsy value.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  imageOptimizer: (src, width) => `${src}?width=${width}`,\n});\n```\n\n- name: `imageOptimizer`\n- valid options: `FUNCTION`\n- default: undefined\n- used by: [OptimizeImages](lib/transformers/OptimizeImages.js)\n\n#### `lts`\n\nUse [long-term stable URLs](https://github.com/ampproject/amphtml/blob/main/contributing/lts-release.md) for downloading the AMP runtime and components.\n\n- name: `lts`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `markdown`\n\nThis transformer adds out-of-the-box markdown support. This allows\nusing AMP Optimizer to convert HTML documents created from Markdown\nfiles into valid AMP. A typical conversion flow would be:\n\nREADME.md => HTML => AMP Optimizer => valid AMP\n\nThe only thing this transformer does is converting `<img>` tags into\neither `amp-img` or `amp-anim` tags. All other Markdown features are\nalready supported by AMP. The transformer will try to resolve image\ndimensions from the actual files. Images larger than 320px will automatically\nget an intrinsic layout. For image detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n- name: `markdown`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `minify`\n\nMinifies the generated HTML output and inlined CSS.\n\n- name: `minify`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [MinifyHtml](lib/transformers/MinifyHtml.js), [SeparateKeyframes](lib/transformers/SeparateKeyframes.js)\n\n**Warning:** this setting is not recommended when running AMP Optimizer in your backend on every request as execution time can increase by up to 7x.\n\n#### `optimizeAmpBind`\n\nEnables a considerably faster scanning method in `amp-bind`, by injecting a `i-amphtml-binding` attribute on all elements with a bound attribute.\n\n- name: `optimizeAmpBind`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [OptimizeAmpBind](lib/transformers/OptimizeAmpBind.js)\n\n#### `optimizeHeroImages`\n\nEnables hero image optimization. Hero images will either be auto detected or you can explicitly mark these by adding the `data-hero` attribute:\n\n```\n<amp-img data-hero src=\"foo.jpg\" ...>\n```\n\nThe maximum number of hero images that can be marked up using `data-hero` is `2`. This number can be increased using the `maxHeroImageCount` parameter.\n\nHero images are optimized by server-side rendering the `img` element inside the `amp-img` element. This can siginificantly improve image rendering performance and reduce the [largest contentful paint](https://web.dev/lcp/) (LCP) metric from [Core Web Vitals](https://web.dev/vitals/).\n\n- name: `optimizeHeroImages`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeHeroImages](lib/transformers/OptimizeHeroImages.js)\n\n#### `preloadHeroImage`\n\nDeprecated, use [optimizeHeroImages](#optimizeheroimages) instead. \n\n#### `verbose`\n\nEnable verbose mode with more detailed logging output.\n\n- name: `verbose`\n- valid options: `[true|false]`\n- default: `false`\n\n## Features\n\n### Image optimization\n\nAMP Optimizer helps you serve optimized images. For this to work, you need to provide a function that maps an image `src` to a resized `srcset` source value. The image resizing needs to either happen at build time (e.g. for static sites) or via a image hosting service such as [thumbor](https://github.com/thumbor/thumbor).\n\nHere is an example implementation that appends the image width to the `src`:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  // parameters are the amp-img `src` and the `width` of the to be generated srcset source value\n  imageOptimizer: (src, width) => {\n    // we cannot rename if the image does not have a file extension\n    const index = src.lastIndexOf('.');\n    if (index === -1) {\n      // return null means we won't generate a srcset source value for this width\n      return null;\n    }\n    const prefix = src.substring(0, index);\n    const postfix = src.substring(index, src.length);\n    return `${prefix}.${width}w${postfix}`;\n  };\n})\n```\n\nUsing this implementation, AMP Optimizer will transform the following `amp-img` declarations:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img src=\"image1.png\" width=\"400\" height=\"800\" layout=\"responsive\"></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                             image-2x.png 2x\"\n></amp-img>\n```\n\ninto:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img\n  src=\"image1.png\"\n  width=\"400\"\n  height=\"800\"\n  layout=\"responsive\"\n  srcset=\"image1.470w.png 470w, image1.820w.png 820w, image1.1440w.png 1440w\"\n></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                               image-2x.png 2x\"\n></amp-img>\n```\n\n**Important** when using `layout=responsive` use the `width` and `height` attribute to specify the minimum image dimensions. For example, for a full-bleed hero image on mobile, specify the width as`width=320`.\n\n### Auto add incomplete markup\n\nIt's possible to pass incomplete documents and AMP Optimizer will add any\nmissing tags and extension imports required by a valid AMP document.\n\n```js\nconst originalHtml = `\n  <h1>Hello World!</h1>\n  <amp-twitter width=\"375\"\n               height=\"472\"\n               layout=\"responsive\"\n               data-tweetid=\"1182321926473162752\">\n  </amp-twitter>\n`;\n\n// you can pass the canonical URL, default is `.`\nconst opts = {\n  canonical: '/example.html',\n};\nampOptimizer.transformHtml(originalHtml, opts).then((optimizedHtml) => {\n  // optimizedHtml will be a valid AMP document\n  console.log(optimizedHtml);\n});\n```\n\n### Automated Markdown conversion\n\nAMP Optimizer supports converting Markdown to AMPHTML. A typical conversion flow would be:\n\n```\nREADME.md => HTML => AMP Optimizer => valid AMP\n```\n\nThe AMP Optimizer converts `<img>` tags into `<amp-img>` or `<amp-anim>` tags when in Markdown mode. Enable Markdown mode via `markdown : true`. AMP Optimizer will try to resolve image dimensions from the actual files. Images wider than 320px will automatically get an `intrinsic` layout.\n\nAll other Markdown features are already supported by AMP.\n\nYou can pass an additional option `imageBasePath` to specify a base path used to resolve an image during build, this can be a file system path or URL prefix.\n\n**Important:** for image size detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n```shell\nnpm install probe-image-size --save-dev\n```\n\nExample:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst md = require('markdown-it')({\n  // don't sanitize html if you want to support AMP components in Markdown\n  html: true,\n});\n\n// enable markdown mode\nconst ampOptimizer = AmpOptimizer.create({\n  markdown: true,\n});\n\nconst markdown = `\n# Markdown 🤯\n\nHere is an image declared in Markdown syntax:\n\n![A random image](https://unsplash.it/800/600).\n\nYou can directly declare AMP components:\n\n<amp-twitter width=\"375\"\n             height=\"472\"\n             layout=\"responsive\"\n             data-tweetid=\"1182321926473162752\">\n</amp-twitter>\n\nAny missing extensions will be automatically imported.\n`;\n\nconst html = md.render(markdown);\n\nconst amphtml = await ampOptimizer.transformHtml(html, {\n  canonical: filePath,\n});\n```\n\nYou can find a working sample [here](/packages/optimizer/demo/markdown).\n\n## Extending AMP Optimizer with custom transformations\n\nAMP Optimizer supports custom HTML transformations:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst {createElement, firstChildByTag, appendChild} = AmpOptimizer.NodeUtils;\n\nclass CustomTransformer {\n  constructor(config) {\n    this.log_ = config.log.tag('CUSTOM');\n  }\n  transform(tree, params) {\n    this.log_.info('Running custom transformation for ', params.filePath);\n    const html = firstChildByTag(tree, 'html');\n    if (!html) return;\n    const head = firstChildByTag(html, 'head');\n    if (!head) return;\n    const desc = createElement('meta', {\n      name: 'description',\n      content: 'this is just a demo',\n    });\n    appendChild(head, desc);\n  }\n}\n\n// it's best to run custom transformers first\nconst customTransformations = [CustomTransformer, ...AmpOptimizer.TRANSFORMATIONS_AMP_FIRST];\n\n// pass custom transformers when creating the optimizer\nconst optimizer = AmpOptimizer.create({\n  transformations: customTransformations,\n});\n// you can add custom parameters on a per document basis\nconst transformedHtml = await optimizer.transformHtml(html, {\n  filePath,\n});\n```\n\nCheckout [the samples](/packages/optimizer/demo/simple/index.js) to learn how to customize AMP Optimizer.\n\n## Best Practices\n\n### Make sure to enable AMP Boilerplate removal\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n- if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```js\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true,\n});\n```\n\n... or for individual pages:\n\n```js\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true,\n});\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length).replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](/packages/runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion,\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n- `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n- `imageBasePath`: specifies a base path used to resolve an image during build.\n- `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n- `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp',\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Fallback API for amp-geo\n\nIdeally, when self-hosting the AMP framework, `amp-geo-0.1.js` should be patched at delivery time to replace `{{AMP_ISO_COUNTRY_HOTPATCH}}` with the ISO 3166-1 alpha-2 country code where the request originated ([reference](https://github.com/ampproject/amphtml/blob/main/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem)). If your host does not have this capability, you can instead rely on a web API to return the country at runtime. The web API must be secure (HTTPS), adhere to [AMP CORS guidelines](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests/), and return JSON in the following format:\n\n```js\n{\"country\": \"de\"}\n```\n\nwhere in this example, `de` is the ISO 3166-1 alpha-2 country code for Germany.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  // this will instruct amp-geo to fetch the user's country from an API\n  // which returns JSON in format: {\"country\": \"de\"}\n  geoApiUrl: 'https://example.com/geo',\n});\n\nconsole.log(optimizedHtml);\n```\n\n## Development & Testing\n\nAMP Optimizer uses a snapshot based testing approach. To execute the tests, run in the project root:\n\n```shell\nnpm run test:node\n```\n\nTransformer tests are located in:\n\n```\n- spec/transformers/valid/TransformerName/test-name/\n    expected_output.html\n    input.html\n```\n\nThe transformation input is defined in `input.html`, whereas `expected_output.html` contains the expected\noutcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing\na transformer implementation, run:\n\n```shell\nnpm run test:optimizer:snapshot\n```\n\nto store a new snapshot version in `expected_output.html`.\n","gitHead":"69468eb65804e741db4bfbd129c7c4dbfb4b7561","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/4.0.0/node@v14.15.4+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"14.15.4","dependencies":{"terser":"5.6.0","postcss":"8.2.8","domutils":"2.5.0","lru-cache":"6.0.0","domhandler":"4.0.0","cross-fetch":"3.0.6","htmlparser2":"6.0.1","cssnano-simple":"1.2.2","dom-serializer":"1.2.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"^2.8.0-canary.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.8.0-canary.0","@ampproject/toolbox-runtime-version":"^2.8.0-canary.0","@ampproject/toolbox-validator-rules":"^2.8.0-canary.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.0-canary.6_1616088327307_0.21422016328306137","host":"s3://npm-registry-packages"}},"2.8.0-canary.9":{"name":"@ampproject/toolbox-optimizer","version":"2.8.0-canary.9","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.0-canary.9","maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"f4da8e4afab991f9dd8139476b159ec45e1f55fb","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.0-canary.9.tgz","fileCount":48,"integrity":"sha512-Bp5kUN0c1bjYdvbe0LrESAVWjEL4I8g5GILF/4di7Qjd/aVP929NRrMbn+7wls3Ay0GzN1DeR+CGrsUPY5QW9g==","signatures":[{"sig":"MEUCIQCQM36NTHlaMZgNQtFmswaWNFvL9+4qrgdOX3+1ICh+YAIgP/Q9cOuZRmtJOZpYTpMIKYcpLnGYY62AcEl67TWYS+k=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":218751,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgWLFdCRA9TVsSAnZWagAAb0oQAJNEZ53ecj48cClLo0OX\nUw4dMqmZRYuylrkswZtGkhl1spGUI9wiUqi1bl2ghsREs9TFxntqEe4oZ3VF\n11F5u5dS9KCqyhEHaGdsUv7CNj6JTYXOEdfKuuglbKsGbqbXQ19ixNWZ3vFi\nnSAO7N2l/G2UWwvNg4hCMT/AbUEilJITOQCSidmgiwneI+qWQeunJ9Q8r14p\ngGhy2OtYNzP4dn/Ec9HzPgAB37u4QHEmkLoP7nNc/fuGCJm6v3GkuIYvzviU\nb2MBBOMnlzfRTm1IQRG4kFqRU7waItLJWPm3cxtPWkQQEOzW6xRJjOAf4fpV\n7Re5tGEI+Ge4CUeQenVUU9+SLnZkTxGKhZiRCqOvr/EF7NSOSgXdf5pMcrlb\nJE5w7t8OgjSLkbSLA00N4ofW7zSmrMxYDz3iY6kOCs3T1gTAFmHoVWAh+etS\n1jh7K2/isgGR04yIY4Ies6qZbLUaGK5WNkyRYtFL94JYZqLPesOD0/1fHTWQ\nzMG9gf6QSRnWMcu+bTsDMiVz7ovTQ89Mri2lEHUtt6g2HQWX5I6IztLD0mAT\ngNMBh0Gr0oKrxLrH7qaCI1ibY3Z95S3wnRUfPm1Zzrjk2zT3yfvkrsMr99qH\njxY8LCmfRFGccidZziDvkr3J5+TGUhEvJ5ZzuC0OIHoYxsYfISugWxzi0x3/\ncl5y\r\n=JqDW\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to simplify creating AMP pages and improve AMP rendering performance. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n- [Server-side render AMP layouts](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/).\n- **Automatically import all missing AMP component scripts**.\n- **Automatically add any missing mandatory AMP tags**.\n- Auto detects and preloads hero images from amp-img, amp-iframe, amp-video, or amp-video-iframe.\n- Remove the AMP boilerplate (when possible).\n- Remove not needed whitespace.\n- Extract and move CSS keyframe animations to the bottom of the page.\n- Optimize AMP framework and custom font loading\n- Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n- AMP Optimizer will produce valid AMP.\n- AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\n### API\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>`;\n\nampOptimizer.transformHtml(originalHtml).then((optimizedHtml) => {\n  console.log(optimizedHtml);\n});\n```\n\nYou can find a sample implementation [here](/packages/optimizer/demo/simple). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](/packages/optimizer-express).\n\n### CLI\n\nAMP Optimizer can be used via the [AMP Toolbox CLI](/packages/cli/README.md):\n\n```shell\nnpm install @ampproject/toolbox-cli -g\namp optimize myFile.html\n```\n\nor run without installation via `npx`:\n\n```shell\nnpx @ampproject/toolbox-cli optimize myFile.html\n```\n\n### Options\n\nOptions are passed when creating a new AMP Optimizer instance:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  verbose: true\n});\n...\n```\n\nAvailable options are:\n\n- [autoAddMandatoryTags](#autoaddmandatorytags)\n- [autoExtensionImport](#autoextensionimport)\n- [esmModulesEnabled](#esmmodulesenabled)\n- [extensionVersions](#extensionversions)\n- [fetch](#fetch)\n- [format](#format)\n- [imageBasePath](#imagebasePath)\n- [imageOptimizer](#imageoptimizer)\n- [lts](#lts)\n- [markdown](#markdown)\n- [minify](#minify)\n- [optimizeAmpBind](#optimizeampbind)\n- [optimizeHeroImages](#optimizeheroimages)\n- [preloadHeroImage](#preloadheroimage)\n- [verbose](#verbose)\n\n#### `autoAddMandatoryTags`\n\nAutomatically inject any missing markup required by AMP.\n\n- name: `autoAddMandatoryTags`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `autoExtensionImport`\n\nAutomatically import any missing AMP Extensions (e.g. amp-carousel).\n\n- name: `autoExtensionImport`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `esmModulesEnabled`\n\nEnables the smaller ESM module version of AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n**Warning: This is incompatible with AMP Packager unless using a prerelease version containing [this commit](https://github.com/ampproject/amppackager/commit/92387ecf97e420eec10b378282383c59f95ab45f).**\n\n- name: `esmModulesEnabled`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `extensionVersions`\n\nSpecify version numbers to use for automatically imported Extensions. If not defined, default to latest.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  extensionVersions: {\n    'amp-twitter': '0.1',\n  },\n});\n```\n\n- name: `extensionVersions`\n- valid options: `OBJECT`\n- default: `{}`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `format`\n\nSpecifies the AMP format of the input file. Defaults to `AMP`.\n\n- name: `format`\n- valid options: `[AMP|AMP4EMAIL|AMP4ADS]`\n- default: `AMP`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js), [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `experimentEsm`\n\nEnable [JavaScript Module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) support for AMP runtime and components. AMP Optimizer will generate module/nonmodule script imports for AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n**Warning: this will result in invalid AMP pages.**\n\n- name: `experimentEsm`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `imageBasePath`\n\nSpecifies a base path used to resolve an image during build,\nthis can be a file system path or URL prefix. You can also pass a function\n`(imgSrc, params) => '../img/' + imgSrc` for dynamically calculating the image path.\n\n- name: `imageBasePath`\n- valid options: `STRING|FUNCTION`\n- default: undefined\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `imageOptimizer`\n\nEnable automated image `srcset` generation by providing a function for calculating `srcset` URLs for a given image `src`. The function should return a URL pointing to a version of the `src` image with the given `width`. If no image is available, it should return a falsy value.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  imageOptimizer: (src, width) => `${src}?width=${width}`,\n});\n```\n\n- name: `imageOptimizer`\n- valid options: `FUNCTION`\n- default: undefined\n- used by: [OptimizeImages](lib/transformers/OptimizeImages.js)\n\n#### `lts`\n\nUse [long-term stable URLs](https://github.com/ampproject/amphtml/blob/main/contributing/lts-release.md) for downloading the AMP runtime and components.\n\n- name: `lts`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `markdown`\n\nThis transformer adds out-of-the-box markdown support. This allows\nusing AMP Optimizer to convert HTML documents created from Markdown\nfiles into valid AMP. A typical conversion flow would be:\n\nREADME.md => HTML => AMP Optimizer => valid AMP\n\nThe only thing this transformer does is converting `<img>` tags into\neither `amp-img` or `amp-anim` tags. All other Markdown features are\nalready supported by AMP. The transformer will try to resolve image\ndimensions from the actual files. Images larger than 320px will automatically\nget an intrinsic layout. For image detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n- name: `markdown`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `minify`\n\nMinifies the generated HTML output and inlined CSS.\n\n- name: `minify`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [MinifyHtml](lib/transformers/MinifyHtml.js), [SeparateKeyframes](lib/transformers/SeparateKeyframes.js)\n\n**Warning:** this setting is not recommended when running AMP Optimizer in your backend on every request as execution time can increase by up to 7x.\n\n#### `optimizeAmpBind`\n\nEnables a considerably faster scanning method in `amp-bind`, by injecting a `i-amphtml-binding` attribute on all elements with a bound attribute.\n\n- name: `optimizeAmpBind`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [OptimizeAmpBind](lib/transformers/OptimizeAmpBind.js)\n\n#### `optimizeHeroImages`\n\nEnables hero image optimization. Hero images will either be auto detected or you can explicitly mark these by adding the `data-hero` attribute:\n\n```\n<amp-img data-hero src=\"foo.jpg\" ...>\n```\n\nThe maximum number of hero images that can be marked up using `data-hero` is `2`. This number can be increased using the `maxHeroImageCount` parameter.\n\nHero images are optimized by server-side rendering the `img` element inside the `amp-img` element. This can siginificantly improve image rendering performance and reduce the [largest contentful paint](https://web.dev/lcp/) (LCP) metric from [Core Web Vitals](https://web.dev/vitals/).\n\n- name: `optimizeHeroImages`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeHeroImages](lib/transformers/OptimizeHeroImages.js)\n\n#### `preloadHeroImage`\n\nDeprecated, use [optimizeHeroImages](#optimizeheroimages) instead. \n\n#### `verbose`\n\nEnable verbose mode with more detailed logging output.\n\n- name: `verbose`\n- valid options: `[true|false]`\n- default: `false`\n\n## Features\n\n### Image optimization\n\nAMP Optimizer helps you serve optimized images. For this to work, you need to provide a function that maps an image `src` to a resized `srcset` source value. The image resizing needs to either happen at build time (e.g. for static sites) or via a image hosting service such as [thumbor](https://github.com/thumbor/thumbor).\n\nHere is an example implementation that appends the image width to the `src`:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  // parameters are the amp-img `src` and the `width` of the to be generated srcset source value\n  imageOptimizer: (src, width) => {\n    // we cannot rename if the image does not have a file extension\n    const index = src.lastIndexOf('.');\n    if (index === -1) {\n      // return null means we won't generate a srcset source value for this width\n      return null;\n    }\n    const prefix = src.substring(0, index);\n    const postfix = src.substring(index, src.length);\n    return `${prefix}.${width}w${postfix}`;\n  };\n})\n```\n\nUsing this implementation, AMP Optimizer will transform the following `amp-img` declarations:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img src=\"image1.png\" width=\"400\" height=\"800\" layout=\"responsive\"></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                             image-2x.png 2x\"\n></amp-img>\n```\n\ninto:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img\n  src=\"image1.png\"\n  width=\"400\"\n  height=\"800\"\n  layout=\"responsive\"\n  srcset=\"image1.470w.png 470w, image1.820w.png 820w, image1.1440w.png 1440w\"\n></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                               image-2x.png 2x\"\n></amp-img>\n```\n\n**Important** when using `layout=responsive` use the `width` and `height` attribute to specify the minimum image dimensions. For example, for a full-bleed hero image on mobile, specify the width as`width=320`.\n\n### Auto add incomplete markup\n\nIt's possible to pass incomplete documents and AMP Optimizer will add any\nmissing tags and extension imports required by a valid AMP document.\n\n```js\nconst originalHtml = `\n  <h1>Hello World!</h1>\n  <amp-twitter width=\"375\"\n               height=\"472\"\n               layout=\"responsive\"\n               data-tweetid=\"1182321926473162752\">\n  </amp-twitter>\n`;\n\n// you can pass the canonical URL, default is `.`\nconst opts = {\n  canonical: '/example.html',\n};\nampOptimizer.transformHtml(originalHtml, opts).then((optimizedHtml) => {\n  // optimizedHtml will be a valid AMP document\n  console.log(optimizedHtml);\n});\n```\n\n### Automated Markdown conversion\n\nAMP Optimizer supports converting Markdown to AMPHTML. A typical conversion flow would be:\n\n```\nREADME.md => HTML => AMP Optimizer => valid AMP\n```\n\nThe AMP Optimizer converts `<img>` tags into `<amp-img>` or `<amp-anim>` tags when in Markdown mode. Enable Markdown mode via `markdown : true`. AMP Optimizer will try to resolve image dimensions from the actual files. Images wider than 320px will automatically get an `intrinsic` layout.\n\nAll other Markdown features are already supported by AMP.\n\nYou can pass an additional option `imageBasePath` to specify a base path used to resolve an image during build, this can be a file system path or URL prefix.\n\n**Important:** for image size detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n```shell\nnpm install probe-image-size --save-dev\n```\n\nExample:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst md = require('markdown-it')({\n  // don't sanitize html if you want to support AMP components in Markdown\n  html: true,\n});\n\n// enable markdown mode\nconst ampOptimizer = AmpOptimizer.create({\n  markdown: true,\n});\n\nconst markdown = `\n# Markdown 🤯\n\nHere is an image declared in Markdown syntax:\n\n![A random image](https://unsplash.it/800/600).\n\nYou can directly declare AMP components:\n\n<amp-twitter width=\"375\"\n             height=\"472\"\n             layout=\"responsive\"\n             data-tweetid=\"1182321926473162752\">\n</amp-twitter>\n\nAny missing extensions will be automatically imported.\n`;\n\nconst html = md.render(markdown);\n\nconst amphtml = await ampOptimizer.transformHtml(html, {\n  canonical: filePath,\n});\n```\n\nYou can find a working sample [here](/packages/optimizer/demo/markdown).\n\n## Extending AMP Optimizer with custom transformations\n\nAMP Optimizer supports custom HTML transformations:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst {createElement, firstChildByTag, appendChild} = AmpOptimizer.NodeUtils;\n\nclass CustomTransformer {\n  constructor(config) {\n    this.log_ = config.log.tag('CUSTOM');\n  }\n  transform(tree, params) {\n    this.log_.info('Running custom transformation for ', params.filePath);\n    const html = firstChildByTag(tree, 'html');\n    if (!html) return;\n    const head = firstChildByTag(html, 'head');\n    if (!head) return;\n    const desc = createElement('meta', {\n      name: 'description',\n      content: 'this is just a demo',\n    });\n    appendChild(head, desc);\n  }\n}\n\n// it's best to run custom transformers first\nconst customTransformations = [CustomTransformer, ...AmpOptimizer.TRANSFORMATIONS_AMP_FIRST];\n\n// pass custom transformers when creating the optimizer\nconst optimizer = AmpOptimizer.create({\n  transformations: customTransformations,\n});\n// you can add custom parameters on a per document basis\nconst transformedHtml = await optimizer.transformHtml(html, {\n  filePath,\n});\n```\n\nCheckout [the samples](/packages/optimizer/demo/simple/index.js) to learn how to customize AMP Optimizer.\n\n## Best Practices\n\n### Make sure to enable AMP Boilerplate removal\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n- if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```js\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true,\n});\n```\n\n... or for individual pages:\n\n```js\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true,\n});\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length).replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](/packages/runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion,\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n- `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n- `imageBasePath`: specifies a base path used to resolve an image during build.\n- `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n- `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp',\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Fallback API for amp-geo\n\nIdeally, when self-hosting the AMP framework, `amp-geo-0.1.js` should be patched at delivery time to replace `{{AMP_ISO_COUNTRY_HOTPATCH}}` with the ISO 3166-1 alpha-2 country code where the request originated ([reference](https://github.com/ampproject/amphtml/blob/main/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem)). If your host does not have this capability, you can instead rely on a web API to return the country at runtime. The web API must be secure (HTTPS), adhere to [AMP CORS guidelines](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests/), and return JSON in the following format:\n\n```js\n{\"country\": \"de\"}\n```\n\nwhere in this example, `de` is the ISO 3166-1 alpha-2 country code for Germany.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  // this will instruct amp-geo to fetch the user's country from an API\n  // which returns JSON in format: {\"country\": \"de\"}\n  geoApiUrl: 'https://example.com/geo',\n});\n\nconsole.log(optimizedHtml);\n```\n\n## Development & Testing\n\nAMP Optimizer uses a snapshot based testing approach. To execute the tests, run in the project root:\n\n```shell\nnpm run test:node\n```\n\nTransformer tests are located in:\n\n```\n- spec/transformers/valid/TransformerName/test-name/\n    expected_output.html\n    input.html\n```\n\nThe transformation input is defined in `input.html`, whereas `expected_output.html` contains the expected\noutcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing\na transformer implementation, run:\n\n```shell\nnpm run test:optimizer:snapshot\n```\n\nto store a new snapshot version in `expected_output.html`.\n","gitHead":"0974deb66a4bf37062208de3f6dbeea1ba08fa36","scripts":{"postinstall":"node lib/warmup.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/4.0.0/node@v14.15.4+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"14.15.4","dependencies":{"terser":"5.6.1","postcss":"8.2.8","domutils":"2.5.0","lru-cache":"6.0.0","domhandler":"4.0.0","cross-fetch":"3.1.2","htmlparser2":"6.0.1","cssnano-simple":"1.2.2","dom-serializer":"1.2.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"^2.8.0-canary.9","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.8.0-canary.0","@ampproject/toolbox-runtime-version":"^2.8.0-canary.9","@ampproject/toolbox-validator-rules":"^2.8.0-canary.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.0-canary.9_1616425309342_0.8331894560236053","host":"s3://npm-registry-packages"}},"2.8.0-canary.15":{"name":"@ampproject/toolbox-optimizer","version":"2.8.0-canary.15","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.0-canary.15","maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"058869afd693ed1ea4b35d0f9d68bfd552772295","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.0-canary.15.tgz","fileCount":47,"integrity":"sha512-E9onJPuOPwvnbX3t3V2B+Tn+nLjyP3YEhn2kNUc0fKOiajBm1UKwOGidjmo2xQV3ph4cQuLz/lkfoNXgN3NxwQ==","signatures":[{"sig":"MEQCHzMXbQ/h4CINbyrwCENAuI10HZTF6sZEyshnA2HcRLsCIQDNYd8URjkN7MQZ4c1TthES2LnfT8jz8SbT1msyU16yHw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":217668,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgY4uGCRA9TVsSAnZWagAAB+0P/35l6FCZ8LJE6azq6h10\nQGv8NAv2oH/6MmLtUDj8hsrfpY6ZiATSyDR5idKy8aAEI4bRpnooBvBgz7UR\nQnzyuRA3BIn4/LAkrpBhBDLQX1vE2Zi9nFO8qiif1K8EKMumnBsz/bOB9Gu1\n+zVI8wlviIXzULequzOzSMhq1iZJsBPu5ysgC2uc03v1eAgoKjnrYKyMjmsD\nv+N1iBwENEhCmLCzQZUhJbRu5ewcRGtMAy4zutcSCf8vnBsDaCaLmMJ1pTuK\nX5sIpnMv9/rwN0nqtnBBuFwKjKIq7mLyLlM67VE4E62n6/sCPxtwICYkn+tp\nXwXLEO6RqLRHIVLeJ6TepRJMWuyda8dkbKeLRAznr6pZHsUDxT5EVRcLKWWo\nTLX/qrY+6HoQ22KoFgyfv3jQbVwG0IVBSrtAMVHWAODdGxzQcnS42bZIeMMV\ncmXfbCbpoT+Ush7Eox1KmydKqi2XsHisiG7b3s7CR3BMdjd1JXvqh6APDQFL\nj+T0+0O+8sxKZVLvzZF1840WaiULDDoKJgUVuqXtRdXcKNQjkMIKz56rF+dI\n/yXSfOpRGUhi0ZddopNjbgbIcOeigIfj76WJDNsoG0pFDGxxXuXpjqo0rmoJ\nAo0x8EX7UrfBDvWanXnCtXF7qY8J1p9eFUaH5btyWIAzJY+Cs07sARIey8IM\n49NW\r\n=tA+D\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to simplify creating AMP pages and improve AMP rendering performance. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n- [Server-side render AMP layouts](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/).\n- **Automatically import all missing AMP component scripts**.\n- **Automatically add any missing mandatory AMP tags**.\n- Auto detects and preloads hero images from amp-img, amp-iframe, amp-video, or amp-video-iframe.\n- Remove the AMP boilerplate (when possible).\n- Remove not needed whitespace.\n- Extract and move CSS keyframe animations to the bottom of the page.\n- Optimize AMP framework and custom font loading\n- Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n- AMP Optimizer will produce valid AMP.\n- AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\n### API\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>`;\n\nampOptimizer.transformHtml(originalHtml).then((optimizedHtml) => {\n  console.log(optimizedHtml);\n});\n```\n\nYou can find a sample implementation [here](/packages/optimizer/demo/simple). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](/packages/optimizer-express).\n\n### CLI\n\nAMP Optimizer can be used via the [AMP Toolbox CLI](/packages/cli/README.md):\n\n```shell\nnpm install @ampproject/toolbox-cli -g\namp optimize myFile.html\n```\n\nor run without installation via `npx`:\n\n```shell\nnpx @ampproject/toolbox-cli optimize myFile.html\n```\n\n### Options\n\nOptions are passed when creating a new AMP Optimizer instance:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  verbose: true\n});\n...\n```\n\nAvailable options are:\n\n- [autoAddMandatoryTags](#autoaddmandatorytags)\n- [autoExtensionImport](#autoextensionimport)\n- [esmModulesEnabled](#esmmodulesenabled)\n- [extensionVersions](#extensionversions)\n- [fetch](#fetch)\n- [format](#format)\n- [imageBasePath](#imagebasePath)\n- [imageOptimizer](#imageoptimizer)\n- [lts](#lts)\n- [markdown](#markdown)\n- [minify](#minify)\n- [optimizeAmpBind](#optimizeampbind)\n- [optimizeHeroImages](#optimizeheroimages)\n- [preloadHeroImage](#preloadheroimage)\n- [verbose](#verbose)\n\n#### `autoAddMandatoryTags`\n\nAutomatically inject any missing markup required by AMP.\n\n- name: `autoAddMandatoryTags`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `autoExtensionImport`\n\nAutomatically import any missing AMP Extensions (e.g. amp-carousel).\n\n- name: `autoExtensionImport`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `esmModulesEnabled`\n\nEnables the smaller ESM module version of AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n**Warning: This is incompatible with AMP Packager unless using a prerelease version containing [this commit](https://github.com/ampproject/amppackager/commit/92387ecf97e420eec10b378282383c59f95ab45f).**\n\n- name: `esmModulesEnabled`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `extensionVersions`\n\nSpecify version numbers to use for automatically imported Extensions. If not defined, default to latest.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  extensionVersions: {\n    'amp-twitter': '0.1',\n  },\n});\n```\n\n- name: `extensionVersions`\n- valid options: `OBJECT`\n- default: `{}`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `format`\n\nSpecifies the AMP format of the input file. Defaults to `AMP`.\n\n- name: `format`\n- valid options: `[AMP|AMP4EMAIL|AMP4ADS]`\n- default: `AMP`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js), [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `experimentEsm`\n\nEnable [JavaScript Module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) support for AMP runtime and components. AMP Optimizer will generate module/nonmodule script imports for AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n**Warning: this will result in invalid AMP pages.**\n\n- name: `experimentEsm`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `imageBasePath`\n\nSpecifies a base path used to resolve an image during build,\nthis can be a file system path or URL prefix. You can also pass a function\n`(imgSrc, params) => '../img/' + imgSrc` for dynamically calculating the image path.\n\n- name: `imageBasePath`\n- valid options: `STRING|FUNCTION`\n- default: undefined\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `imageOptimizer`\n\nEnable automated image `srcset` generation by providing a function for calculating `srcset` URLs for a given image `src`. The function should return a URL pointing to a version of the `src` image with the given `width`. If no image is available, it should return a falsy value.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  imageOptimizer: (src, width) => `${src}?width=${width}`,\n});\n```\n\n- name: `imageOptimizer`\n- valid options: `FUNCTION`\n- default: undefined\n- used by: [OptimizeImages](lib/transformers/OptimizeImages.js)\n\n#### `lts`\n\nUse [long-term stable URLs](https://github.com/ampproject/amphtml/blob/main/contributing/lts-release.md) for downloading the AMP runtime and components.\n\n- name: `lts`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `markdown`\n\nThis transformer adds out-of-the-box markdown support. This allows\nusing AMP Optimizer to convert HTML documents created from Markdown\nfiles into valid AMP. A typical conversion flow would be:\n\nREADME.md => HTML => AMP Optimizer => valid AMP\n\nThe only thing this transformer does is converting `<img>` tags into\neither `amp-img` or `amp-anim` tags. All other Markdown features are\nalready supported by AMP. The transformer will try to resolve image\ndimensions from the actual files. Images larger than 320px will automatically\nget an intrinsic layout. For image detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n- name: `markdown`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `minify`\n\nMinifies the generated HTML output and inlined CSS.\n\n- name: `minify`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [MinifyHtml](lib/transformers/MinifyHtml.js), [SeparateKeyframes](lib/transformers/SeparateKeyframes.js)\n\n**Warning:** this setting is not recommended when running AMP Optimizer in your backend on every request as execution time can increase by up to 7x.\n\n#### `optimizeAmpBind`\n\nEnables a considerably faster scanning method in `amp-bind`, by injecting a `i-amphtml-binding` attribute on all elements with a bound attribute.\n\n- name: `optimizeAmpBind`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [OptimizeAmpBind](lib/transformers/OptimizeAmpBind.js)\n\n#### `optimizeHeroImages`\n\nEnables hero image optimization. Hero images will either be auto detected or you can explicitly mark these by adding the `data-hero` attribute:\n\n```\n<amp-img data-hero src=\"foo.jpg\" ...>\n```\n\nThe maximum number of hero images that can be marked up using `data-hero` is `2`. This number can be increased using the `maxHeroImageCount` parameter.\n\nHero images are optimized by server-side rendering the `img` element inside the `amp-img` element. This can siginificantly improve image rendering performance and reduce the [largest contentful paint](https://web.dev/lcp/) (LCP) metric from [Core Web Vitals](https://web.dev/vitals/).\n\n- name: `optimizeHeroImages`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeHeroImages](lib/transformers/OptimizeHeroImages.js)\n\n#### `preloadHeroImage`\n\nDeprecated, use [optimizeHeroImages](#optimizeheroimages) instead. \n\n#### `verbose`\n\nEnable verbose mode with more detailed logging output.\n\n- name: `verbose`\n- valid options: `[true|false]`\n- default: `false`\n\n## Features\n\n### Image optimization\n\nAMP Optimizer helps you serve optimized images. For this to work, you need to provide a function that maps an image `src` to a resized `srcset` source value. The image resizing needs to either happen at build time (e.g. for static sites) or via a image hosting service such as [thumbor](https://github.com/thumbor/thumbor).\n\nHere is an example implementation that appends the image width to the `src`:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  // parameters are the amp-img `src` and the `width` of the to be generated srcset source value\n  imageOptimizer: (src, width) => {\n    // we cannot rename if the image does not have a file extension\n    const index = src.lastIndexOf('.');\n    if (index === -1) {\n      // return null means we won't generate a srcset source value for this width\n      return null;\n    }\n    const prefix = src.substring(0, index);\n    const postfix = src.substring(index, src.length);\n    return `${prefix}.${width}w${postfix}`;\n  };\n})\n```\n\nUsing this implementation, AMP Optimizer will transform the following `amp-img` declarations:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img src=\"image1.png\" width=\"400\" height=\"800\" layout=\"responsive\"></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                             image-2x.png 2x\"\n></amp-img>\n```\n\ninto:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img\n  src=\"image1.png\"\n  width=\"400\"\n  height=\"800\"\n  layout=\"responsive\"\n  srcset=\"image1.470w.png 470w, image1.820w.png 820w, image1.1440w.png 1440w\"\n></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                               image-2x.png 2x\"\n></amp-img>\n```\n\n**Important** when using `layout=responsive` use the `width` and `height` attribute to specify the minimum image dimensions. For example, for a full-bleed hero image on mobile, specify the width as`width=320`.\n\n### Auto add incomplete markup\n\nIt's possible to pass incomplete documents and AMP Optimizer will add any\nmissing tags and extension imports required by a valid AMP document.\n\n```js\nconst originalHtml = `\n  <h1>Hello World!</h1>\n  <amp-twitter width=\"375\"\n               height=\"472\"\n               layout=\"responsive\"\n               data-tweetid=\"1182321926473162752\">\n  </amp-twitter>\n`;\n\n// you can pass the canonical URL, default is `.`\nconst opts = {\n  canonical: '/example.html',\n};\nampOptimizer.transformHtml(originalHtml, opts).then((optimizedHtml) => {\n  // optimizedHtml will be a valid AMP document\n  console.log(optimizedHtml);\n});\n```\n\n### Automated Markdown conversion\n\nAMP Optimizer supports converting Markdown to AMPHTML. A typical conversion flow would be:\n\n```\nREADME.md => HTML => AMP Optimizer => valid AMP\n```\n\nThe AMP Optimizer converts `<img>` tags into `<amp-img>` or `<amp-anim>` tags when in Markdown mode. Enable Markdown mode via `markdown : true`. AMP Optimizer will try to resolve image dimensions from the actual files. Images wider than 320px will automatically get an `intrinsic` layout.\n\nAll other Markdown features are already supported by AMP.\n\nYou can pass an additional option `imageBasePath` to specify a base path used to resolve an image during build, this can be a file system path or URL prefix.\n\n**Important:** for image size detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n```shell\nnpm install probe-image-size --save-dev\n```\n\nExample:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst md = require('markdown-it')({\n  // don't sanitize html if you want to support AMP components in Markdown\n  html: true,\n});\n\n// enable markdown mode\nconst ampOptimizer = AmpOptimizer.create({\n  markdown: true,\n});\n\nconst markdown = `\n# Markdown 🤯\n\nHere is an image declared in Markdown syntax:\n\n![A random image](https://unsplash.it/800/600).\n\nYou can directly declare AMP components:\n\n<amp-twitter width=\"375\"\n             height=\"472\"\n             layout=\"responsive\"\n             data-tweetid=\"1182321926473162752\">\n</amp-twitter>\n\nAny missing extensions will be automatically imported.\n`;\n\nconst html = md.render(markdown);\n\nconst amphtml = await ampOptimizer.transformHtml(html, {\n  canonical: filePath,\n});\n```\n\nYou can find a working sample [here](/packages/optimizer/demo/markdown).\n\n## Extending AMP Optimizer with custom transformations\n\nAMP Optimizer supports custom HTML transformations:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst {createElement, firstChildByTag, appendChild} = AmpOptimizer.NodeUtils;\n\nclass CustomTransformer {\n  constructor(config) {\n    this.log_ = config.log.tag('CUSTOM');\n  }\n  transform(tree, params) {\n    this.log_.info('Running custom transformation for ', params.filePath);\n    const html = firstChildByTag(tree, 'html');\n    if (!html) return;\n    const head = firstChildByTag(html, 'head');\n    if (!head) return;\n    const desc = createElement('meta', {\n      name: 'description',\n      content: 'this is just a demo',\n    });\n    appendChild(head, desc);\n  }\n}\n\n// it's best to run custom transformers first\nconst customTransformations = [CustomTransformer, ...AmpOptimizer.TRANSFORMATIONS_AMP_FIRST];\n\n// pass custom transformers when creating the optimizer\nconst optimizer = AmpOptimizer.create({\n  transformations: customTransformations,\n});\n// you can add custom parameters on a per document basis\nconst transformedHtml = await optimizer.transformHtml(html, {\n  filePath,\n});\n```\n\nCheckout [the samples](/packages/optimizer/demo/simple/index.js) to learn how to customize AMP Optimizer.\n\n## Best Practices\n\n### Make sure to enable AMP Boilerplate removal\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n- if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```js\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true,\n});\n```\n\n... or for individual pages:\n\n```js\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true,\n});\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length).replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](/packages/runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion,\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n- `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n- `imageBasePath`: specifies a base path used to resolve an image during build.\n- `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n- `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp',\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Fallback API for amp-geo\n\nIdeally, when self-hosting the AMP framework, `amp-geo-0.1.js` should be patched at delivery time to replace `{{AMP_ISO_COUNTRY_HOTPATCH}}` with the ISO 3166-1 alpha-2 country code where the request originated ([reference](https://github.com/ampproject/amphtml/blob/main/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem)). If your host does not have this capability, you can instead rely on a web API to return the country at runtime. The web API must be secure (HTTPS), adhere to [AMP CORS guidelines](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests/), and return JSON in the following format:\n\n```js\n{\"country\": \"de\"}\n```\n\nwhere in this example, `de` is the ISO 3166-1 alpha-2 country code for Germany.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  // this will instruct amp-geo to fetch the user's country from an API\n  // which returns JSON in format: {\"country\": \"de\"}\n  geoApiUrl: 'https://example.com/geo',\n});\n\nconsole.log(optimizedHtml);\n```\n\n## Development & Testing\n\nAMP Optimizer uses a snapshot based testing approach. To execute the tests, run in the project root:\n\n```shell\nnpm run test:node\n```\n\nTransformer tests are located in:\n\n```\n- spec/transformers/valid/TransformerName/test-name/\n    expected_output.html\n    input.html\n```\n\nThe transformation input is defined in `input.html`, whereas `expected_output.html` contains the expected\noutcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing\na transformer implementation, run:\n\n```shell\nnpm run test:optimizer:snapshot\n```\n\nto store a new snapshot version in `expected_output.html`.\n","gitHead":"082fd3256f3664fb016b47d2d1b965d47ebb3f53","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/4.0.0/node@v14.15.4+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"14.15.4","dependencies":{"terser":"5.6.1","postcss":"8.2.8","domutils":"2.5.0","lru-cache":"6.0.0","domhandler":"4.0.0","cross-fetch":"3.1.2","htmlparser2":"6.0.1","cssnano-simple":"1.2.2","dom-serializer":"1.2.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"^2.8.0-canary.15","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.8.0-canary.0","@ampproject/toolbox-runtime-version":"^2.8.0-canary.15","@ampproject/toolbox-validator-rules":"^2.8.0-canary.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.0-canary.15_1617136517650_0.1567624460192396","host":"s3://npm-registry-packages"}},"2.8.0-canary.16":{"name":"@ampproject/toolbox-optimizer","version":"2.8.0-canary.16","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.0-canary.16","maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"52038a9af5f398d3f25ff7c9e9b8d3ab5eb1a181","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.0-canary.16.tgz","fileCount":47,"integrity":"sha512-R0rdsxFE4jlFq73BnTTMn42xj3ypVqUk/TNLgKD/NQHrPs2giVManAs9Oi1f66QIDlIrxmmXGsBaLjEWVEItNg==","signatures":[{"sig":"MEUCIHJmnecDdr8skunPwh83OWOuD1FFaA+w6VDv/L4tCuDCAiEA1jf0xPl7X8KSQq9M3im/Z+w5GaOfT3FOWc86+1n86m0=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":217613,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgb0InCRA9TVsSAnZWagAAdW4P/2bZ6+i8f0ahSA8YUSVa\n1oWiQdtEGL1uxTRclwF90iao+2ylzOW0tQ0aXLx+aXPm8q6npT8Ytz7gtdMY\nRKvdNtUu14LwYcGcoRf1m4bUoqU7ah6PFcXcA3vywiHup46SQio6+HpkQ7V4\nr//YzmA0GecBo9YU3kp7muPYuvhzbm/Oeo5y/xl+Re0l84tSKRg0UXXwfgIr\nQkGnhtjd/rQlffidhxsrO/vbpTKAo6XN3QC/jXkgpmkip/bb4Y9PU/ofwRhx\nxg28VbQtnYgYuOq3s5/TULuY5wwQVFYGiu365Ztp65+wLSSQkDYG0A+JuoKT\n36JnyQayYcUm1QrpEuqmJxB5S0apqdfLCd55iA0kIrBCX9kD5wXG0rFL5Ytc\n8KAqNuwcJdE2ISBkmO9Ssk+RESp01dVAOs69/GkHTAezCi6ilMJS1/vXNqpY\n8PHFXB7Z7xwjRkyo9lAQtqtwInj417rX0kOuwlTORKsGvEa6iSjd0RfEBjc0\nyS3LZZztNldajxSOJ/eS8ChZLuSAlMWJDpjfqVxtfR8XdTEsR75zLEd68hQ6\nA4HhsnOPqNFsLUodKQB+Kw8tnzWHtgiFk/chGDSAf80wztjRnpCAoa/1+QN4\nJ5FzAgk98tEKQMti4MVhxcNQ0ksG0t3EGoku4+cg+zM4rjvy7FcUDkyfX6bc\nDg2X\r\n=HNXP\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to simplify creating AMP pages and improve AMP rendering performance. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n- [Server-side render AMP layouts](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/).\n- **Automatically import all missing AMP component scripts**.\n- **Automatically add any missing mandatory AMP tags**.\n- Auto detects and preloads hero images from amp-img, amp-iframe, amp-video, or amp-video-iframe.\n- Remove the AMP boilerplate (when possible).\n- Remove not needed whitespace.\n- Extract and move CSS keyframe animations to the bottom of the page.\n- Optimize AMP framework and custom font loading\n- Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n- AMP Optimizer will produce valid AMP.\n- AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\n### API\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>`;\n\nampOptimizer.transformHtml(originalHtml).then((optimizedHtml) => {\n  console.log(optimizedHtml);\n});\n```\n\nYou can find a sample implementation [here](/packages/optimizer/demo/simple). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](/packages/optimizer-express).\n\n### CLI\n\nAMP Optimizer can be used via the [AMP Toolbox CLI](/packages/cli/README.md):\n\n```shell\nnpm install @ampproject/toolbox-cli -g\namp optimize myFile.html\n```\n\nor run without installation via `npx`:\n\n```shell\nnpx @ampproject/toolbox-cli optimize myFile.html\n```\n\n### Options\n\nOptions are passed when creating a new AMP Optimizer instance:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  verbose: true\n});\n...\n```\n\nAvailable options are:\n\n- [autoAddMandatoryTags](#autoaddmandatorytags)\n- [autoExtensionImport](#autoextensionimport)\n- [esmModulesEnabled](#esmmodulesenabled)\n- [extensionVersions](#extensionversions)\n- [fetch](#fetch)\n- [format](#format)\n- [imageBasePath](#imagebasePath)\n- [imageOptimizer](#imageoptimizer)\n- [lts](#lts)\n- [markdown](#markdown)\n- [minify](#minify)\n- [optimizeAmpBind](#optimizeampbind)\n- [optimizeHeroImages](#optimizeheroimages)\n- [preloadHeroImage](#preloadheroimage)\n- [verbose](#verbose)\n\n#### `autoAddMandatoryTags`\n\nAutomatically inject any missing markup required by AMP.\n\n- name: `autoAddMandatoryTags`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `autoExtensionImport`\n\nAutomatically import any missing AMP Extensions (e.g. amp-carousel).\n\n- name: `autoExtensionImport`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `esmModulesEnabled`\n\nEnables the smaller ESM module version of AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n**Warning: This is incompatible with AMP Packager unless using a prerelease version containing [this commit](https://github.com/ampproject/amppackager/commit/92387ecf97e420eec10b378282383c59f95ab45f).**\n\n- name: `esmModulesEnabled`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `extensionVersions`\n\nSpecify version numbers to use for automatically imported Extensions. If not defined, default to latest.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  extensionVersions: {\n    'amp-twitter': '0.1',\n  },\n});\n```\n\n- name: `extensionVersions`\n- valid options: `OBJECT`\n- default: `{}`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `format`\n\nSpecifies the AMP format of the input file. Defaults to `AMP`.\n\n- name: `format`\n- valid options: `[AMP|AMP4EMAIL|AMP4ADS]`\n- default: `AMP`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js), [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `imageBasePath`\n\nSpecifies a base path used to resolve an image during build,\nthis can be a file system path or URL prefix. You can also pass a function\n`(imgSrc, params) => '../img/' + imgSrc` for dynamically calculating the image path.\n\n- name: `imageBasePath`\n- valid options: `STRING|FUNCTION`\n- default: undefined\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `imageOptimizer`\n\nEnable automated image `srcset` generation by providing a function for calculating `srcset` URLs for a given image `src`. The function should return a URL pointing to a version of the `src` image with the given `width`. If no image is available, it should return a falsy value.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  imageOptimizer: (src, width) => `${src}?width=${width}`,\n});\n```\n\n- name: `imageOptimizer`\n- valid options: `FUNCTION`\n- default: undefined\n- used by: [OptimizeImages](lib/transformers/OptimizeImages.js)\n\n#### `lts`\n\nUse [long-term stable URLs](https://github.com/ampproject/amphtml/blob/main/contributing/lts-release.md) for downloading the AMP runtime and components.\n\n- name: `lts`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `markdown`\n\nThis transformer adds out-of-the-box markdown support. This allows\nusing AMP Optimizer to convert HTML documents created from Markdown\nfiles into valid AMP. A typical conversion flow would be:\n\nREADME.md => HTML => AMP Optimizer => valid AMP\n\nThe only thing this transformer does is converting `<img>` tags into\neither `amp-img` or `amp-anim` tags. All other Markdown features are\nalready supported by AMP. The transformer will try to resolve image\ndimensions from the actual files. Images larger than 320px will automatically\nget an intrinsic layout. For image detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n- name: `markdown`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `minify`\n\nMinifies the generated HTML output and inlined CSS.\n\n- name: `minify`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [MinifyHtml](lib/transformers/MinifyHtml.js), [SeparateKeyframes](lib/transformers/SeparateKeyframes.js)\n\n**Warning:** this setting is not recommended when running AMP Optimizer in your backend on every request as execution time can increase by up to 7x.\n\n#### `optimizeAmpBind`\n\nEnables a considerably faster scanning method in `amp-bind`, by injecting a `i-amphtml-binding` attribute on all elements with a bound attribute.\n\n- name: `optimizeAmpBind`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [OptimizeAmpBind](lib/transformers/OptimizeAmpBind.js)\n\n#### `optimizeHeroImages`\n\nEnables hero image optimization. Hero images will either be auto detected or you can explicitly mark these by adding the `data-hero` attribute:\n\n```\n<amp-img data-hero src=\"foo.jpg\" ...>\n```\n\nThe maximum number of hero images that can be marked up using `data-hero` is `2`. This number can be increased using the `maxHeroImageCount` parameter.\n\nHero images are optimized by server-side rendering the `img` element inside the `amp-img` element. This can siginificantly improve image rendering performance and reduce the [largest contentful paint](https://web.dev/lcp/) (LCP) metric from [Core Web Vitals](https://web.dev/vitals/).\n\n- name: `optimizeHeroImages`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeHeroImages](lib/transformers/OptimizeHeroImages.js)\n\n#### `preloadHeroImage`\n\nDeprecated, use [optimizeHeroImages](#optimizeheroimages) instead. \n\n#### `verbose`\n\nEnable verbose mode with more detailed logging output.\n\n- name: `verbose`\n- valid options: `[true|false]`\n- default: `false`\n\n## Features\n\n### Image optimization\n\nAMP Optimizer helps you serve optimized images. For this to work, you need to provide a function that maps an image `src` to a resized `srcset` source value. The image resizing needs to either happen at build time (e.g. for static sites) or via a image hosting service such as [thumbor](https://github.com/thumbor/thumbor).\n\nHere is an example implementation that appends the image width to the `src`:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  // parameters are the amp-img `src` and the `width` of the to be generated srcset source value\n  imageOptimizer: (src, width) => {\n    // we cannot rename if the image does not have a file extension\n    const index = src.lastIndexOf('.');\n    if (index === -1) {\n      // return null means we won't generate a srcset source value for this width\n      return null;\n    }\n    const prefix = src.substring(0, index);\n    const postfix = src.substring(index, src.length);\n    return `${prefix}.${width}w${postfix}`;\n  };\n})\n```\n\nUsing this implementation, AMP Optimizer will transform the following `amp-img` declarations:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img src=\"image1.png\" width=\"400\" height=\"800\" layout=\"responsive\"></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                             image-2x.png 2x\"\n></amp-img>\n```\n\ninto:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img\n  src=\"image1.png\"\n  width=\"400\"\n  height=\"800\"\n  layout=\"responsive\"\n  srcset=\"image1.470w.png 470w, image1.820w.png 820w, image1.1440w.png 1440w\"\n></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                               image-2x.png 2x\"\n></amp-img>\n```\n\n**Important** when using `layout=responsive` use the `width` and `height` attribute to specify the minimum image dimensions. For example, for a full-bleed hero image on mobile, specify the width as`width=320`.\n\n### Auto add incomplete markup\n\nIt's possible to pass incomplete documents and AMP Optimizer will add any\nmissing tags and extension imports required by a valid AMP document.\n\n```js\nconst originalHtml = `\n  <h1>Hello World!</h1>\n  <amp-twitter width=\"375\"\n               height=\"472\"\n               layout=\"responsive\"\n               data-tweetid=\"1182321926473162752\">\n  </amp-twitter>\n`;\n\n// you can pass the canonical URL, default is `.`\nconst opts = {\n  canonical: '/example.html',\n};\nampOptimizer.transformHtml(originalHtml, opts).then((optimizedHtml) => {\n  // optimizedHtml will be a valid AMP document\n  console.log(optimizedHtml);\n});\n```\n\n### Automated Markdown conversion\n\nAMP Optimizer supports converting Markdown to AMPHTML. A typical conversion flow would be:\n\n```\nREADME.md => HTML => AMP Optimizer => valid AMP\n```\n\nThe AMP Optimizer converts `<img>` tags into `<amp-img>` or `<amp-anim>` tags when in Markdown mode. Enable Markdown mode via `markdown : true`. AMP Optimizer will try to resolve image dimensions from the actual files. Images wider than 320px will automatically get an `intrinsic` layout.\n\nAll other Markdown features are already supported by AMP.\n\nYou can pass an additional option `imageBasePath` to specify a base path used to resolve an image during build, this can be a file system path or URL prefix.\n\n**Important:** for image size detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n```shell\nnpm install probe-image-size --save-dev\n```\n\nExample:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst md = require('markdown-it')({\n  // don't sanitize html if you want to support AMP components in Markdown\n  html: true,\n});\n\n// enable markdown mode\nconst ampOptimizer = AmpOptimizer.create({\n  markdown: true,\n});\n\nconst markdown = `\n# Markdown 🤯\n\nHere is an image declared in Markdown syntax:\n\n![A random image](https://unsplash.it/800/600).\n\nYou can directly declare AMP components:\n\n<amp-twitter width=\"375\"\n             height=\"472\"\n             layout=\"responsive\"\n             data-tweetid=\"1182321926473162752\">\n</amp-twitter>\n\nAny missing extensions will be automatically imported.\n`;\n\nconst html = md.render(markdown);\n\nconst amphtml = await ampOptimizer.transformHtml(html, {\n  canonical: filePath,\n});\n```\n\nYou can find a working sample [here](/packages/optimizer/demo/markdown).\n\n## Extending AMP Optimizer with custom transformations\n\nAMP Optimizer supports custom HTML transformations:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst {createElement, firstChildByTag, appendChild} = AmpOptimizer.NodeUtils;\n\nclass CustomTransformer {\n  constructor(config) {\n    this.log_ = config.log.tag('CUSTOM');\n  }\n  transform(tree, params) {\n    this.log_.info('Running custom transformation for ', params.filePath);\n    const html = firstChildByTag(tree, 'html');\n    if (!html) return;\n    const head = firstChildByTag(html, 'head');\n    if (!head) return;\n    const desc = createElement('meta', {\n      name: 'description',\n      content: 'this is just a demo',\n    });\n    appendChild(head, desc);\n  }\n}\n\n// it's best to run custom transformers first\nconst customTransformations = [CustomTransformer, ...AmpOptimizer.TRANSFORMATIONS_AMP_FIRST];\n\n// pass custom transformers when creating the optimizer\nconst optimizer = AmpOptimizer.create({\n  transformations: customTransformations,\n});\n// you can add custom parameters on a per document basis\nconst transformedHtml = await optimizer.transformHtml(html, {\n  filePath,\n});\n```\n\nCheckout [the samples](/packages/optimizer/demo/simple/index.js) to learn how to customize AMP Optimizer.\n\n## Best Practices\n\n### Make sure to enable AMP Boilerplate removal\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n- if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```js\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true,\n});\n```\n\n... or for individual pages:\n\n```js\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true,\n});\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length).replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](/packages/runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion,\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n- `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n- `imageBasePath`: specifies a base path used to resolve an image during build.\n- `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n- `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp',\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Fallback API for amp-geo\n\nIdeally, when self-hosting the AMP framework, `amp-geo-0.1.js` should be patched at delivery time to replace `{{AMP_ISO_COUNTRY_HOTPATCH}}` with the ISO 3166-1 alpha-2 country code where the request originated ([reference](https://github.com/ampproject/amphtml/blob/main/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem)). If your host does not have this capability, you can instead rely on a web API to return the country at runtime. The web API must be secure (HTTPS), adhere to [AMP CORS guidelines](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests/), and return JSON in the following format:\n\n```js\n{\"country\": \"de\"}\n```\n\nwhere in this example, `de` is the ISO 3166-1 alpha-2 country code for Germany.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  // this will instruct amp-geo to fetch the user's country from an API\n  // which returns JSON in format: {\"country\": \"de\"}\n  geoApiUrl: 'https://example.com/geo',\n});\n\nconsole.log(optimizedHtml);\n```\n\n## Development & Testing\n\nAMP Optimizer uses a snapshot based testing approach. To execute the tests, run in the project root:\n\n```shell\nnpm run test:node\n```\n\nTransformer tests are located in:\n\n```\n- spec/transformers/valid/TransformerName/test-name/\n    expected_output.html\n    input.html\n```\n\nThe transformation input is defined in `input.html`, whereas `expected_output.html` contains the expected\noutcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing\na transformer implementation, run:\n\n```shell\nnpm run test:optimizer:snapshot\n```\n\nto store a new snapshot version in `expected_output.html`.\n","gitHead":"882c791c8c27d0dfdb947a90932913bbc8f9c129","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/4.0.0/node@v14.15.4+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"14.15.4","dependencies":{"terser":"5.6.1","postcss":"8.2.8","domutils":"2.5.0","lru-cache":"6.0.0","domhandler":"4.0.0","cross-fetch":"3.1.2","htmlparser2":"6.0.1","cssnano-simple":"1.2.2","dom-serializer":"1.2.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"^2.8.0-canary.15","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.8.0-canary.0","@ampproject/toolbox-runtime-version":"^2.8.0-canary.15","@ampproject/toolbox-validator-rules":"^2.8.0-canary.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.0-canary.16_1617904167416_0.2550569424553748","host":"s3://npm-registry-packages"}},"2.8.0-canary.18":{"name":"@ampproject/toolbox-optimizer","version":"2.8.0-canary.18","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.0-canary.18","maintainers":[{"name":"kdwan","email":"kdwan@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"3f5bd54dd3ee777e659dc319b588958b87abd9cc","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.0-canary.18.tgz","fileCount":48,"integrity":"sha512-dHarqVVK8NfCRKiLzVjC57mry4MH/LqNEJj59gFiVsIUcU6qJVcwE4ltj/NYcKoEdf5NPi3HpVbxoP5HeKuvlg==","signatures":[{"sig":"MEUCIFf94TM1oCXLgCL/foBsk5qa5IL3qRIVMcx/Pvw6XfU8AiEAql22ayzSVZlxDrDG3tLHbX+MuI/nYeA74kGPOXuoCFQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":221443,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJglaKPCRA9TVsSAnZWagAAWlEP/jhXdGv/0bx/5mpDd9P6\npo2oecnD2HKyU7slTUj9djRahDQ/ShyzDh9ftHR3JEi1PVUmCkmxfy55zurE\nUBBTt5ioYGD0SU1FxSEX4pPHmihRckWFG+UHL4HxKI0/cX8qVjJnjiLSBrGN\nIVUVuly2AiwB1/u6cHhbD4IpyaEmNufRHm12VpvwdruLRx9/8kZ7u4biMg37\n+RnyyGn2ozflU2lFUs1aj1DJtRJgtIjmiaFdgmWQaKG9+P5BThcV6BUxyyvq\nV3xUlPMEdjPu53c0yPMAfDnE1JgwUcb2XzU94aywRCofvzjskcUJLYF/3sHp\nWfJ7k7GNEzdjLTORxU08YkAiflvzHx8qtumcSyqiMdHZ0qoI3FI5nmWt2egd\nX4Cq+G3VAtClHpQx3nC7xqhk1HNPMyskEmIWxS88xEmpUzZkLFzdE5lcFl7D\nHCBSwGXh7vWN4RdeqE51imSPNtElr08QOSdfM5a4QY70zUlMAg/S4kyZ9uOl\nNHRtCtLntE9R8zb/Br/MNRKOkewhUKoJ4uYbVt+Uo95rmLfVsetzzA+gXp+A\nyqmL9ov7x1myEXXhgH3Gu7ZGR826Jm6ghERQyMlXhB2vfZyX8l7s+5qjQvAs\ndSod83NJtgG655fHsTyAi6BFni0bHfrYM2bNoSc3mEIDIuXAV8RRjO7mES+h\nTOU3\r\n=UKx3\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to simplify creating AMP pages and improve AMP rendering performance. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n- [Server-side render AMP layouts](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/).\n- **Automatically import all missing AMP component scripts**.\n- **Automatically add any missing mandatory AMP tags**.\n- Auto detects and preloads hero images from amp-img, amp-iframe, amp-video, or amp-video-iframe.\n- Remove the AMP boilerplate (when possible).\n- Remove not needed whitespace.\n- Extract and move CSS keyframe animations to the bottom of the page.\n- Optimize AMP framework and custom font loading\n- Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n- AMP Optimizer will produce valid AMP.\n- AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\n### API\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>`;\n\nampOptimizer.transformHtml(originalHtml).then((optimizedHtml) => {\n  console.log(optimizedHtml);\n});\n```\n\nYou can find a sample implementation [here](/packages/optimizer/demo/simple). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](/packages/optimizer-express).\n\n### CLI\n\nAMP Optimizer can be used via the [AMP Toolbox CLI](/packages/cli/README.md):\n\n```shell\nnpm install @ampproject/toolbox-cli -g\namp optimize myFile.html\n```\n\nor run without installation via `npx`:\n\n```shell\nnpx @ampproject/toolbox-cli optimize myFile.html\n```\n\n### Options\n\nOptions are passed when creating a new AMP Optimizer instance:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  verbose: true\n});\n...\n```\n\nAvailable options are:\n\n- [autoAddMandatoryTags](#autoaddmandatorytags)\n- [autoExtensionImport](#autoextensionimport)\n- [esmModulesEnabled](#esmmodulesenabled)\n- [extensionVersions](#extensionversions)\n- [fetch](#fetch)\n- [format](#format)\n- [imageBasePath](#imagebasePath)\n- [imageOptimizer](#imageoptimizer)\n- [lts](#lts)\n- [markdown](#markdown)\n- [minify](#minify)\n- [optimizeAmpBind](#optimizeampbind)\n- [optimizeHeroImages](#optimizeheroimages)\n- [preloadHeroImage](#preloadheroimage)\n- [verbose](#verbose)\n\n#### `autoAddMandatoryTags`\n\nAutomatically inject any missing markup required by AMP.\n\n- name: `autoAddMandatoryTags`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `autoExtensionImport`\n\nAutomatically import any missing AMP Extensions (e.g. amp-carousel).\n\n- name: `autoExtensionImport`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `esmModulesEnabled`\n\nEnables the smaller ESM module version of AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n**Warning: This is incompatible with AMP Packager unless using a prerelease version containing [this commit](https://github.com/ampproject/amppackager/commit/92387ecf97e420eec10b378282383c59f95ab45f).**\n\n- name: `esmModulesEnabled`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `extensionVersions`\n\nSpecify version numbers to use for automatically imported Extensions. If not defined, default to latest.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  extensionVersions: {\n    'amp-twitter': '0.1',\n  },\n});\n```\n\n- name: `extensionVersions`\n- valid options: `OBJECT`\n- default: `{}`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `format`\n\nSpecifies the AMP format of the input file. Defaults to `AMP`.\n\n- name: `format`\n- valid options: `[AMP|AMP4EMAIL|AMP4ADS]`\n- default: `AMP`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js), [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `imageBasePath`\n\nSpecifies a base path used to resolve an image during build,\nthis can be a file system path or URL prefix. You can also pass a function\n`(imgSrc, params) => '../img/' + imgSrc` for dynamically calculating the image path.\n\n- name: `imageBasePath`\n- valid options: `STRING|FUNCTION`\n- default: undefined\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `imageOptimizer`\n\nEnable automated image `srcset` generation by providing a function for calculating `srcset` URLs for a given image `src`. The function should return a URL pointing to a version of the `src` image with the given `width`. If no image is available, it should return a falsy value.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  imageOptimizer: (src, width) => `${src}?width=${width}`,\n});\n```\n\n- name: `imageOptimizer`\n- valid options: `FUNCTION`\n- default: undefined\n- used by: [OptimizeImages](lib/transformers/OptimizeImages.js)\n\n#### `lts`\n\nUse [long-term stable URLs](https://github.com/ampproject/amphtml/blob/main/contributing/lts-release.md) for downloading the AMP runtime and components.\n\n- name: `lts`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `markdown`\n\nThis transformer adds out-of-the-box markdown support. This allows\nusing AMP Optimizer to convert HTML documents created from Markdown\nfiles into valid AMP. A typical conversion flow would be:\n\nREADME.md => HTML => AMP Optimizer => valid AMP\n\nThe only thing this transformer does is converting `<img>` tags into\neither `amp-img` or `amp-anim` tags. All other Markdown features are\nalready supported by AMP. The transformer will try to resolve image\ndimensions from the actual files. Images larger than 320px will automatically\nget an intrinsic layout. For image detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n- name: `markdown`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `minify`\n\nMinifies the generated HTML output and inlined CSS.\n\n- name: `minify`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [MinifyHtml](lib/transformers/MinifyHtml.js), [SeparateKeyframes](lib/transformers/SeparateKeyframes.js)\n\n**Warning:** this setting is not recommended when running AMP Optimizer in your backend on every request as execution time can increase by up to 7x.\n\n#### `optimizeAmpBind`\n\nEnables a considerably faster scanning method in `amp-bind`, by injecting a `i-amphtml-binding` attribute on all elements with a bound attribute.\n\n- name: `optimizeAmpBind`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeAmpBind](lib/transformers/OptimizeAmpBind.js)\n\n#### `optimizeHeroImages`\n\nEnables hero image optimization. Hero images will either be auto detected or you can explicitly mark these by adding the `data-hero` attribute:\n\n```\n<amp-img data-hero src=\"foo.jpg\" ...>\n```\n\nThe maximum number of hero images that can be marked up using `data-hero` is `2`. This number can be increased using the `maxHeroImageCount` parameter.\n\nHero images are optimized by server-side rendering the `img` element inside the `amp-img` element. This can siginificantly improve image rendering performance and reduce the [largest contentful paint](https://web.dev/lcp/) (LCP) metric from [Core Web Vitals](https://web.dev/vitals/).\n\n- name: `optimizeHeroImages`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeHeroImages](lib/transformers/OptimizeHeroImages.js)\n\n#### `preloadHeroImage`\n\nDeprecated, use [optimizeHeroImages](#optimizeheroimages) instead. \n\n#### `verbose`\n\nEnable verbose mode with more detailed logging output.\n\n- name: `verbose`\n- valid options: `[true|false]`\n- default: `false`\n\n## Features\n\n### Image optimization\n\nAMP Optimizer helps you serve optimized images. For this to work, you need to provide a function that maps an image `src` to a resized `srcset` source value. The image resizing needs to either happen at build time (e.g. for static sites) or via a image hosting service such as [thumbor](https://github.com/thumbor/thumbor).\n\nHere is an example implementation that appends the image width to the `src`:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  // parameters are the amp-img `src` and the `width` of the to be generated srcset source value\n  imageOptimizer: (src, width) => {\n    // we cannot rename if the image does not have a file extension\n    const index = src.lastIndexOf('.');\n    if (index === -1) {\n      // return null means we won't generate a srcset source value for this width\n      return null;\n    }\n    const prefix = src.substring(0, index);\n    const postfix = src.substring(index, src.length);\n    return `${prefix}.${width}w${postfix}`;\n  };\n})\n```\n\nUsing this implementation, AMP Optimizer will transform the following `amp-img` declarations:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img src=\"image1.png\" width=\"400\" height=\"800\" layout=\"responsive\"></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                             image-2x.png 2x\"\n></amp-img>\n```\n\ninto:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img\n  src=\"image1.png\"\n  width=\"400\"\n  height=\"800\"\n  layout=\"responsive\"\n  srcset=\"image1.470w.png 470w, image1.820w.png 820w, image1.1440w.png 1440w\"\n></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                               image-2x.png 2x\"\n></amp-img>\n```\n\n**Important** when using `layout=responsive` use the `width` and `height` attribute to specify the minimum image dimensions. For example, for a full-bleed hero image on mobile, specify the width as`width=320`.\n\n### Auto add incomplete markup\n\nIt's possible to pass incomplete documents and AMP Optimizer will add any\nmissing tags and extension imports required by a valid AMP document.\n\n```js\nconst originalHtml = `\n  <h1>Hello World!</h1>\n  <amp-twitter width=\"375\"\n               height=\"472\"\n               layout=\"responsive\"\n               data-tweetid=\"1182321926473162752\">\n  </amp-twitter>\n`;\n\n// you can pass the canonical URL, default is `.`\nconst opts = {\n  canonical: '/example.html',\n};\nampOptimizer.transformHtml(originalHtml, opts).then((optimizedHtml) => {\n  // optimizedHtml will be a valid AMP document\n  console.log(optimizedHtml);\n});\n```\n\n### Automated Markdown conversion\n\nAMP Optimizer supports converting Markdown to AMPHTML. A typical conversion flow would be:\n\n```\nREADME.md => HTML => AMP Optimizer => valid AMP\n```\n\nThe AMP Optimizer converts `<img>` tags into `<amp-img>` or `<amp-anim>` tags when in Markdown mode. Enable Markdown mode via `markdown : true`. AMP Optimizer will try to resolve image dimensions from the actual files. Images wider than 320px will automatically get an `intrinsic` layout.\n\nAll other Markdown features are already supported by AMP.\n\nYou can pass an additional option `imageBasePath` to specify a base path used to resolve an image during build, this can be a file system path or URL prefix.\n\n**Important:** for image size detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n```shell\nnpm install probe-image-size --save-dev\n```\n\nExample:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst md = require('markdown-it')({\n  // don't sanitize html if you want to support AMP components in Markdown\n  html: true,\n});\n\n// enable markdown mode\nconst ampOptimizer = AmpOptimizer.create({\n  markdown: true,\n});\n\nconst markdown = `\n# Markdown 🤯\n\nHere is an image declared in Markdown syntax:\n\n![A random image](https://unsplash.it/800/600).\n\nYou can directly declare AMP components:\n\n<amp-twitter width=\"375\"\n             height=\"472\"\n             layout=\"responsive\"\n             data-tweetid=\"1182321926473162752\">\n</amp-twitter>\n\nAny missing extensions will be automatically imported.\n`;\n\nconst html = md.render(markdown);\n\nconst amphtml = await ampOptimizer.transformHtml(html, {\n  canonical: filePath,\n});\n```\n\nYou can find a working sample [here](/packages/optimizer/demo/markdown).\n\n## Extending AMP Optimizer with custom transformations\n\nAMP Optimizer supports custom HTML transformations:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst {createElement, firstChildByTag, appendChild} = AmpOptimizer.NodeUtils;\n\nclass CustomTransformer {\n  constructor(config) {\n    this.log_ = config.log.tag('CUSTOM');\n  }\n  transform(tree, params) {\n    this.log_.info('Running custom transformation for ', params.filePath);\n    const html = firstChildByTag(tree, 'html');\n    if (!html) return;\n    const head = firstChildByTag(html, 'head');\n    if (!head) return;\n    const desc = createElement('meta', {\n      name: 'description',\n      content: 'this is just a demo',\n    });\n    appendChild(head, desc);\n  }\n}\n\n// it's best to run custom transformers first\nconst customTransformations = [CustomTransformer, ...AmpOptimizer.TRANSFORMATIONS_AMP_FIRST];\n\n// pass custom transformers when creating the optimizer\nconst optimizer = AmpOptimizer.create({\n  transformations: customTransformations,\n});\n// you can add custom parameters on a per document basis\nconst transformedHtml = await optimizer.transformHtml(html, {\n  filePath,\n});\n```\n\nCheckout [the samples](/packages/optimizer/demo/simple/index.js) to learn how to customize AMP Optimizer.\n\n## Best Practices\n\n### Make sure to enable AMP Boilerplate removal\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n- if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```js\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true,\n});\n```\n\n... or for individual pages:\n\n```js\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true,\n});\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length).replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](/packages/runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion,\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n- `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n- `imageBasePath`: specifies a base path used to resolve an image during build.\n- `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n- `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp',\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Fallback API for amp-geo\n\nIdeally, when self-hosting the AMP framework, `amp-geo-0.1.js` should be patched at delivery time to replace `{{AMP_ISO_COUNTRY_HOTPATCH}}` with the ISO 3166-1 alpha-2 country code where the request originated ([reference](https://github.com/ampproject/amphtml/blob/main/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem)). If your host does not have this capability, you can instead rely on a web API to return the country at runtime. The web API must be secure (HTTPS), adhere to [AMP CORS guidelines](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests/), and return JSON in the following format:\n\n```js\n{\"country\": \"de\"}\n```\n\nwhere in this example, `de` is the ISO 3166-1 alpha-2 country code for Germany.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  // this will instruct amp-geo to fetch the user's country from an API\n  // which returns JSON in format: {\"country\": \"de\"}\n  geoApiUrl: 'https://example.com/geo',\n});\n\nconsole.log(optimizedHtml);\n```\n\n## Development & Testing\n\nAMP Optimizer uses a snapshot based testing approach. To execute the tests, run in the project root:\n\n```shell\nnpm run test:node\n```\n\nTransformer tests are located in:\n\n```\n- spec/transformers/valid/TransformerName/test-name/\n    expected_output.html\n    input.html\n```\n\nThe transformation input is defined in `input.html`, whereas `expected_output.html` contains the expected\noutcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing\na transformer implementation, run:\n\n```shell\nnpm run test:optimizer:snapshot\n```\n\nto store a new snapshot version in `expected_output.html`.\n","gitHead":"5b7f8de1e96cc2eba0c9fd1073d4ce1fe3ab5243","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/4.0.0/node@v14.15.4+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"14.15.4","dependencies":{"terser":"5.6.1","postcss":"8.2.8","domutils":"2.5.0","lru-cache":"6.0.0","domhandler":"4.0.0","cross-fetch":"3.1.2","htmlparser2":"6.0.1","cssnano-simple":"1.2.2","dom-serializer":"1.2.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"^2.8.0-canary.15","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.8.0-canary.0","@ampproject/toolbox-runtime-version":"^2.8.0-canary.15","@ampproject/toolbox-validator-rules":"^2.8.0-canary.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.0-canary.18_1620419214793_0.16219296431978192","host":"s3://npm-registry-packages"}},"2.8.0-canary.19":{"name":"@ampproject/toolbox-optimizer","version":"2.8.0-canary.19","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.0-canary.19","maintainers":[{"name":"esth","email":"esther@amp.dev"},{"name":"ampprojectbot","email":"bot@amp.dev"},{"name":"kdwan","email":"kdwan@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"b7778f07deb585af8e017962106375fc9e0467a9","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.0-canary.19.tgz","fileCount":49,"integrity":"sha512-XvAGrDVngDTlQ1N9xXDoQk1FEXf3bMieV0RJ3Vr92tEYGMR683cowo2E0F89OjSW01n2szdPTfr2+SZC1NJrCA==","signatures":[{"sig":"MEQCIFknCziLADhoJgIX7nVhGHmA5dtvVVPZQqxQwowPjjEWAiAGXsw3O1E/VeBZl9gu6qvce+dJBXxAVy+L308kuQu1qw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":234325,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgpW8OCRA9TVsSAnZWagAA/AoP/0aypP6cJoo6zlmeN60I\ngSiHjBCJogeHe8st+LlrcMuLNAxBQ69IqRdvOzXhu01fojNx8xhsaBaZ5erI\n4x/1eIFrUKR563WHlqy4TYMuapwGWrz09BG3fkA4HcsuFYUhnk+VLJm+kfzf\nBRM/OewtitwXxntrjSw55OZfh8vgoi/FNbbI1Ic/7mMcSIFSoNS9S8mgt61E\nhORlrUZLTvfJsM5sc/gkETjKitaO0zyyqoK1MeDbfrcvxBtxdCe+PlbJlskZ\nYcDV4y10EwBJLgKQdC9QzuRr4NWdw8DYjt7xycU6KmMDK/2pcuJ02bYsr+tP\nZHdQcJ6Kmb3wGzL4T4JRaRxtrT7BNKjQFH10QeH8vrUFFm6LgSA/bHllzj31\njGDhF+DtRyF2SS2jRiN2jq6HjTl7v87gEHWs0oTz7eHLx4iv52NcTiJN0dK/\n0WCuwJ2O2f7ifYvAUzDSnVe58WEunCOc0ocNer9OZRnh7yIwpUzqghIVB1Y6\n0+SPFrOWDW6ilYfyGJmOERfNun0QSZUfzxfxrG0coPUj8CMaHf8rTRNYc0yB\nRqsOsnJF+uDOQs2zNkDIj24+A4LKSxk8G+ENhwvG/PHN7iETM2aIsClJR5+5\nQs3Zr9XW8RCt0bw+M+bk/8UQ4uOYxe/BTb43RSRzPZ1k2sENXtGzt0Mqskth\nwLct\r\n=K5br\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to simplify creating AMP pages and improve AMP rendering performance. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n- [Server-side render AMP layouts](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/).\n- **Automatically import all missing AMP component scripts**.\n- **Automatically add any missing mandatory AMP tags**.\n- Auto detects and preloads hero images from amp-img, amp-iframe, amp-video, or amp-video-iframe.\n- Remove the AMP boilerplate (when possible).\n- Remove not needed whitespace.\n- Extract and move CSS keyframe animations to the bottom of the page.\n- Optimize AMP framework and custom font loading\n- Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n- AMP Optimizer will produce valid AMP.\n- AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\n### API\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>`;\n\nampOptimizer.transformHtml(originalHtml).then((optimizedHtml) => {\n  console.log(optimizedHtml);\n});\n```\n\nYou can find a sample implementation [here](/packages/optimizer/demo/simple). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](/packages/optimizer-express).\n\n### CLI\n\nAMP Optimizer can be used via the [AMP Toolbox CLI](/packages/cli/README.md):\n\n```shell\nnpm install @ampproject/toolbox-cli -g\namp optimize myFile.html\n```\n\nor run without installation via `npx`:\n\n```shell\nnpx @ampproject/toolbox-cli optimize myFile.html\n```\n\n### Options\n\nOptions are passed when creating a new AMP Optimizer instance:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  verbose: true\n});\n...\n```\n\nAvailable options are:\n\n- [autoAddMandatoryTags](#autoaddmandatorytags)\n- [autoExtensionImport](#autoextensionimport)\n- [esmModulesEnabled](#esmmodulesenabled)\n- [extensionVersions](#extensionversions)\n- [fetch](#fetch)\n- [format](#format)\n- [imageBasePath](#imagebasePath)\n- [imageOptimizer](#imageoptimizer)\n- [lts](#lts)\n- [markdown](#markdown)\n- [minify](#minify)\n- [optimizeAmpBind](#optimizeampbind)\n- [optimizeHeroImages](#optimizeheroimages)\n- [preloadHeroImage](#preloadheroimage)\n- [verbose](#verbose)\n\n#### `autoAddMandatoryTags`\n\nAutomatically inject any missing markup required by AMP.\n\n- name: `autoAddMandatoryTags`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `autoExtensionImport`\n\nAutomatically import any missing AMP Extensions (e.g. amp-carousel).\n\n- name: `autoExtensionImport`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `esmModulesEnabled`\n\nEnables the smaller ESM module version of AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n**Warning: This is incompatible with AMP Packager unless using a prerelease version containing [this commit](https://github.com/ampproject/amppackager/commit/92387ecf97e420eec10b378282383c59f95ab45f).**\n\n- name: `esmModulesEnabled`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `extensionVersions`\n\nSpecify version numbers to use for automatically imported Extensions. If not defined, default to latest.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  extensionVersions: {\n    'amp-twitter': '0.1',\n  },\n});\n```\n\n- name: `extensionVersions`\n- valid options: `OBJECT`\n- default: `{}`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `format`\n\nSpecifies the AMP format of the input file. Defaults to `AMP`.\n\n- name: `format`\n- valid options: `[AMP|AMP4EMAIL|AMP4ADS]`\n- default: `AMP`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js), [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `imageBasePath`\n\nSpecifies a base path used to resolve an image during build,\nthis can be a file system path or URL prefix. You can also pass a function\n`(imgSrc, params) => '../img/' + imgSrc` for dynamically calculating the image path.\n\n- name: `imageBasePath`\n- valid options: `STRING|FUNCTION`\n- default: undefined\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `imageOptimizer`\n\nEnable automated image `srcset` generation by providing a function for calculating `srcset` URLs for a given image `src`. The function should return a URL pointing to a version of the `src` image with the given `width`. If no image is available, it should return a falsy value.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  imageOptimizer: (src, width) => `${src}?width=${width}`,\n});\n```\n\n- name: `imageOptimizer`\n- valid options: `FUNCTION`\n- default: undefined\n- used by: [OptimizeImages](lib/transformers/OptimizeImages.js)\n\n#### `lts`\n\nUse [long-term stable URLs](https://github.com/ampproject/amphtml/blob/main/contributing/lts-release.md) for downloading the AMP runtime and components.\n\n- name: `lts`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `markdown`\n\nThis transformer adds out-of-the-box markdown support. This allows\nusing AMP Optimizer to convert HTML documents created from Markdown\nfiles into valid AMP. A typical conversion flow would be:\n\nREADME.md => HTML => AMP Optimizer => valid AMP\n\nThe only thing this transformer does is converting `<img>` tags into\neither `amp-img` or `amp-anim` tags. All other Markdown features are\nalready supported by AMP. The transformer will try to resolve image\ndimensions from the actual files. Images larger than 320px will automatically\nget an intrinsic layout. For image detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n- name: `markdown`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `minify`\n\nMinifies the generated HTML output and inlined CSS.\n\n- name: `minify`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [MinifyHtml](lib/transformers/MinifyHtml.js), [SeparateKeyframes](lib/transformers/SeparateKeyframes.js)\n\n**Warning:** this setting is not recommended when running AMP Optimizer in your backend on every request as execution time can increase by up to 7x.\n\n#### `optimizeAmpBind`\n\nEnables a considerably faster scanning method in `amp-bind`, by injecting a `i-amphtml-binding` attribute on all elements with a bound attribute.\n\n- name: `optimizeAmpBind`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeAmpBind](lib/transformers/OptimizeAmpBind.js)\n\n#### `optimizeHeroImages`\n\nEnables hero image optimization. Hero images will either be auto detected or you can explicitly mark these by adding the `data-hero` attribute:\n\n```\n<amp-img data-hero src=\"foo.jpg\" ...>\n```\n\nThe maximum number of hero images that can be marked up using `data-hero` is `2`. This number can be increased using the `maxHeroImageCount` parameter.\n\nHero images are optimized by server-side rendering the `img` element inside the `amp-img` element. This can siginificantly improve image rendering performance and reduce the [largest contentful paint](https://web.dev/lcp/) (LCP) metric from [Core Web Vitals](https://web.dev/vitals/).\n\n- name: `optimizeHeroImages`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeHeroImages](lib/transformers/OptimizeHeroImages.js)\n\n#### `preloadHeroImage`\n\nDeprecated, use [optimizeHeroImages](#optimizeheroimages) instead. \n\n#### `verbose`\n\nEnable verbose mode with more detailed logging output.\n\n- name: `verbose`\n- valid options: `[true|false]`\n- default: `false`\n\n## Features\n\n### Image optimization\n\nAMP Optimizer helps you serve optimized images. For this to work, you need to provide a function that maps an image `src` to a resized `srcset` source value. The image resizing needs to either happen at build time (e.g. for static sites) or via a image hosting service such as [thumbor](https://github.com/thumbor/thumbor).\n\nHere is an example implementation that appends the image width to the `src`:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  // parameters are the amp-img `src` and the `width` of the to be generated srcset source value\n  imageOptimizer: (src, width) => {\n    // we cannot rename if the image does not have a file extension\n    const index = src.lastIndexOf('.');\n    if (index === -1) {\n      // return null means we won't generate a srcset source value for this width\n      return null;\n    }\n    const prefix = src.substring(0, index);\n    const postfix = src.substring(index, src.length);\n    return `${prefix}.${width}w${postfix}`;\n  };\n})\n```\n\nUsing this implementation, AMP Optimizer will transform the following `amp-img` declarations:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img src=\"image1.png\" width=\"400\" height=\"800\" layout=\"responsive\"></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                             image-2x.png 2x\"\n></amp-img>\n```\n\ninto:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img\n  src=\"image1.png\"\n  width=\"400\"\n  height=\"800\"\n  layout=\"responsive\"\n  srcset=\"image1.470w.png 470w, image1.820w.png 820w, image1.1440w.png 1440w\"\n></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                               image-2x.png 2x\"\n></amp-img>\n```\n\n**Important** when using `layout=responsive` use the `width` and `height` attribute to specify the minimum image dimensions. For example, for a full-bleed hero image on mobile, specify the width as`width=320`.\n\n### Auto add incomplete markup\n\nIt's possible to pass incomplete documents and AMP Optimizer will add any\nmissing tags and extension imports required by a valid AMP document.\n\n```js\nconst originalHtml = `\n  <h1>Hello World!</h1>\n  <amp-twitter width=\"375\"\n               height=\"472\"\n               layout=\"responsive\"\n               data-tweetid=\"1182321926473162752\">\n  </amp-twitter>\n`;\n\n// you can pass the canonical URL, default is `.`\nconst opts = {\n  canonical: '/example.html',\n};\nampOptimizer.transformHtml(originalHtml, opts).then((optimizedHtml) => {\n  // optimizedHtml will be a valid AMP document\n  console.log(optimizedHtml);\n});\n```\n\n### Automated Markdown conversion\n\nAMP Optimizer supports converting Markdown to AMPHTML. A typical conversion flow would be:\n\n```\nREADME.md => HTML => AMP Optimizer => valid AMP\n```\n\nThe AMP Optimizer converts `<img>` tags into `<amp-img>` or `<amp-anim>` tags when in Markdown mode. Enable Markdown mode via `markdown : true`. AMP Optimizer will try to resolve image dimensions from the actual files. Images wider than 320px will automatically get an `intrinsic` layout.\n\nAll other Markdown features are already supported by AMP.\n\nYou can pass an additional option `imageBasePath` to specify a base path used to resolve an image during build, this can be a file system path or URL prefix.\n\n**Important:** for image size detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n```shell\nnpm install probe-image-size --save-dev\n```\n\nExample:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst md = require('markdown-it')({\n  // don't sanitize html if you want to support AMP components in Markdown\n  html: true,\n});\n\n// enable markdown mode\nconst ampOptimizer = AmpOptimizer.create({\n  markdown: true,\n});\n\nconst markdown = `\n# Markdown 🤯\n\nHere is an image declared in Markdown syntax:\n\n![A random image](https://unsplash.it/800/600).\n\nYou can directly declare AMP components:\n\n<amp-twitter width=\"375\"\n             height=\"472\"\n             layout=\"responsive\"\n             data-tweetid=\"1182321926473162752\">\n</amp-twitter>\n\nAny missing extensions will be automatically imported.\n`;\n\nconst html = md.render(markdown);\n\nconst amphtml = await ampOptimizer.transformHtml(html, {\n  canonical: filePath,\n});\n```\n\nYou can find a working sample [here](/packages/optimizer/demo/markdown).\n\n## Extending AMP Optimizer with custom transformations\n\nAMP Optimizer supports custom HTML transformations:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst {createElement, firstChildByTag, appendChild} = AmpOptimizer.NodeUtils;\n\nclass CustomTransformer {\n  constructor(config) {\n    this.log_ = config.log.tag('CUSTOM');\n  }\n  transform(tree, params) {\n    this.log_.info('Running custom transformation for ', params.filePath);\n    const html = firstChildByTag(tree, 'html');\n    if (!html) return;\n    const head = firstChildByTag(html, 'head');\n    if (!head) return;\n    const desc = createElement('meta', {\n      name: 'description',\n      content: 'this is just a demo',\n    });\n    appendChild(head, desc);\n  }\n}\n\n// it's best to run custom transformers first\nconst customTransformations = [CustomTransformer, ...AmpOptimizer.TRANSFORMATIONS_AMP_FIRST];\n\n// pass custom transformers when creating the optimizer\nconst optimizer = AmpOptimizer.create({\n  transformations: customTransformations,\n});\n// you can add custom parameters on a per document basis\nconst transformedHtml = await optimizer.transformHtml(html, {\n  filePath,\n});\n```\n\nCheckout [the samples](/packages/optimizer/demo/simple/index.js) to learn how to customize AMP Optimizer.\n\n## Best Practices\n\n### Make sure to enable AMP Boilerplate removal\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n- if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```js\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true,\n});\n```\n\n... or for individual pages:\n\n```js\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true,\n});\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length).replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](/packages/runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion,\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n- `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n- `imageBasePath`: specifies a base path used to resolve an image during build.\n- `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n- `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp',\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Fallback API for amp-geo\n\nIdeally, when self-hosting the AMP framework, `amp-geo-0.1.js` should be patched at delivery time to replace `{{AMP_ISO_COUNTRY_HOTPATCH}}` with the ISO 3166-1 alpha-2 country code where the request originated ([reference](https://github.com/ampproject/amphtml/blob/main/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem)). If your host does not have this capability, you can instead rely on a web API to return the country at runtime. The web API must be secure (HTTPS), adhere to [AMP CORS guidelines](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests/), and return JSON in the following format:\n\n```js\n{\"country\": \"de\"}\n```\n\nwhere in this example, `de` is the ISO 3166-1 alpha-2 country code for Germany.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  // this will instruct amp-geo to fetch the user's country from an API\n  // which returns JSON in format: {\"country\": \"de\"}\n  geoApiUrl: 'https://example.com/geo',\n});\n\nconsole.log(optimizedHtml);\n```\n\n## Development & Testing\n\nAMP Optimizer uses a snapshot based testing approach. To execute the tests, run in the project root:\n\n```shell\nnpm run test:node\n```\n\nTransformer tests are located in:\n\n```\n- spec/transformers/valid/TransformerName/test-name/\n    expected_output.html\n    input.html\n```\n\nThe transformation input is defined in `input.html`, whereas `expected_output.html` contains the expected\noutcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing\na transformer implementation, run:\n\n```shell\nnpm run test:optimizer:snapshot\n```\n\nto store a new snapshot version in `expected_output.html`.\n","gitHead":"2c0197ab2ec605ec5046dbfe6f9840d41e38a604","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/4.0.0/node@v14.15.4+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"14.15.4","dependencies":{"terser":"5.6.1","postcss":"8.2.8","domutils":"2.5.0","lru-cache":"6.0.0","domhandler":"4.0.0","cross-fetch":"3.1.2","htmlparser2":"6.0.1","cssnano-simple":"1.2.2","dom-serializer":"1.2.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"^2.8.0-canary.15","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.8.0-canary.0","@ampproject/toolbox-runtime-version":"^2.8.0-canary.15","@ampproject/toolbox-validator-rules":"^2.8.0-canary.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.0-canary.19_1621454606297_0.9554801566823459","host":"s3://npm-registry-packages"}},"2.8.0-canary.20":{"name":"@ampproject/toolbox-optimizer","version":"2.8.0-canary.20","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.0-canary.20","maintainers":[{"name":"esth","email":"esther@amp.dev"},{"name":"ampprojectbot","email":"bot@amp.dev"},{"name":"kdwan","email":"kdwan@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"c1b5f2c75bf2f52ff700dd89c7dfbb60c48571d3","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.0-canary.20.tgz","fileCount":49,"integrity":"sha512-MY6CDjYGOPPMrkF4IL9NRwjrLaJI1M0PM9vhjHlvAVrB9kasnY72JegF1t5gkI17YlTFqfruJqgbslcoqbg1ZQ==","signatures":[{"sig":"MEYCIQDR0GQyPZeIhSyXi8yfJBxSYiynImGmSVgUwAv5+66rGgIhANZn1Qi7VNiIlVo8/H6+zbPDtdct6sG3WLjIXYgSmC5b","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":234368,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgpmgpCRA9TVsSAnZWagAAlfsQAJ9EQzsb/KvoITeODzL+\nO/wIZfiRUFyaacIjKbpLKIBpJ/Q15wGS47/9HKIXtPdh84l2OXPd0rMnExKt\nGJmhyH+G2EVmX5+B7GROOt+qIY4QYJxN7eeR2wNrP3Hmo2bW38xL/n+rgTlV\nROLhc58q9f0Mg+jz7NwiY4KZ9wUKeJVU6Kly6SL7sr7umqgS9WFNCAieh6EA\nFXkheaPlQHvvimgTLWFqNtfj4hBPhgJJC/LhtDEYYhCF+0iNHHwVJ8AcixM0\n2wWDIorhEQLNHtMQ8vh7H2QKhpsOf3NsoJ0BOOM4HW2/td0T5zN9k69fxWNe\nzF1jNDm6rviljSovDkwyGLc/F+QAKX4uDAvb5Ylggo1N5+E14zfeIkvZsNdl\nMs6jjS+UYULlgQfKXJW3mY1SNQQkQ0gLFCNzS9b9F/IrFPbjqIl4DyC4Lp3R\nX5i701I01Q8R7ic3raPQ6EXuS4+F3q5TWrEW7xQxV3yMsKcIMcmetYvmfl8s\n7LEMToYhVpLd2VfKSEt0OFKvNQir4BQOXpS23eVACpx7c8q+K977bqL1/3Yg\njwjbz73L4kV04I91NT5vAQrgWJ5HZQznC2xLHzzqdJOcmbsYk1gEeXuYU1ox\nUnolx2M+zL3Q+sLTx0O/4lI6V0MYc0DHOhEDA910v6Wv3HiKMu5q2DQCT91P\nolFx\r\n=V4Gp\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to simplify creating AMP pages and improve AMP rendering performance. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n- [Server-side render AMP layouts](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/).\n- **Automatically import all missing AMP component scripts**.\n- **Automatically add any missing mandatory AMP tags**.\n- Auto detects and preloads hero images from amp-img, amp-iframe, amp-video, or amp-video-iframe.\n- Remove the AMP boilerplate (when possible).\n- Remove not needed whitespace.\n- Extract and move CSS keyframe animations to the bottom of the page.\n- Optimize AMP framework and custom font loading\n- Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n- AMP Optimizer will produce valid AMP.\n- AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\n### API\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>`;\n\nampOptimizer.transformHtml(originalHtml).then((optimizedHtml) => {\n  console.log(optimizedHtml);\n});\n```\n\nYou can find a sample implementation [here](/packages/optimizer/demo/simple). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](/packages/optimizer-express).\n\n### CLI\n\nAMP Optimizer can be used via the [AMP Toolbox CLI](/packages/cli/README.md):\n\n```shell\nnpm install @ampproject/toolbox-cli -g\namp optimize myFile.html\n```\n\nor run without installation via `npx`:\n\n```shell\nnpx @ampproject/toolbox-cli optimize myFile.html\n```\n\n### Options\n\nOptions are passed when creating a new AMP Optimizer instance:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  verbose: true\n});\n...\n```\n\nAvailable options are:\n\n- [autoAddMandatoryTags](#autoaddmandatorytags)\n- [autoExtensionImport](#autoextensionimport)\n- [esmModulesEnabled](#esmmodulesenabled)\n- [extensionVersions](#extensionversions)\n- [fetch](#fetch)\n- [format](#format)\n- [imageBasePath](#imagebasePath)\n- [imageOptimizer](#imageoptimizer)\n- [lts](#lts)\n- [markdown](#markdown)\n- [minify](#minify)\n- [optimizeAmpBind](#optimizeampbind)\n- [optimizeHeroImages](#optimizeheroimages)\n- [preloadHeroImage](#preloadheroimage)\n- [verbose](#verbose)\n\n#### `autoAddMandatoryTags`\n\nAutomatically inject any missing markup required by AMP.\n\n- name: `autoAddMandatoryTags`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `autoExtensionImport`\n\nAutomatically import any missing AMP Extensions (e.g. amp-carousel).\n\n- name: `autoExtensionImport`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `esmModulesEnabled`\n\nEnables the smaller ESM module version of AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n**Warning: This is incompatible with AMP Packager unless using a prerelease version containing [this commit](https://github.com/ampproject/amppackager/commit/92387ecf97e420eec10b378282383c59f95ab45f).**\n\n- name: `esmModulesEnabled`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `extensionVersions`\n\nSpecify version numbers to use for automatically imported Extensions. If not defined, default to latest.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  extensionVersions: {\n    'amp-twitter': '0.1',\n  },\n});\n```\n\n- name: `extensionVersions`\n- valid options: `OBJECT`\n- default: `{}`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `format`\n\nSpecifies the AMP format of the input file. Defaults to `AMP`.\n\n- name: `format`\n- valid options: `[AMP|AMP4EMAIL|AMP4ADS]`\n- default: `AMP`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js), [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `imageBasePath`\n\nSpecifies a base path used to resolve an image during build,\nthis can be a file system path or URL prefix. You can also pass a function\n`(imgSrc, params) => '../img/' + imgSrc` for dynamically calculating the image path.\n\n- name: `imageBasePath`\n- valid options: `STRING|FUNCTION`\n- default: undefined\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `imageOptimizer`\n\nEnable automated image `srcset` generation by providing a function for calculating `srcset` URLs for a given image `src`. The function should return a URL pointing to a version of the `src` image with the given `width`. If no image is available, it should return a falsy value.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  imageOptimizer: (src, width) => `${src}?width=${width}`,\n});\n```\n\n- name: `imageOptimizer`\n- valid options: `FUNCTION`\n- default: undefined\n- used by: [OptimizeImages](lib/transformers/OptimizeImages.js)\n\n#### `lts`\n\nUse [long-term stable URLs](https://github.com/ampproject/amphtml/blob/main/contributing/lts-release.md) for downloading the AMP runtime and components.\n\n- name: `lts`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `markdown`\n\nThis transformer adds out-of-the-box markdown support. This allows\nusing AMP Optimizer to convert HTML documents created from Markdown\nfiles into valid AMP. A typical conversion flow would be:\n\nREADME.md => HTML => AMP Optimizer => valid AMP\n\nThe only thing this transformer does is converting `<img>` tags into\neither `amp-img` or `amp-anim` tags. All other Markdown features are\nalready supported by AMP. The transformer will try to resolve image\ndimensions from the actual files. Images larger than 320px will automatically\nget an intrinsic layout. For image detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n- name: `markdown`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `minify`\n\nMinifies the generated HTML output and inlined CSS.\n\n- name: `minify`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [MinifyHtml](lib/transformers/MinifyHtml.js), [SeparateKeyframes](lib/transformers/SeparateKeyframes.js)\n\n**Warning:** this setting is not recommended when running AMP Optimizer in your backend on every request as execution time can increase by up to 7x.\n\n#### `optimizeAmpBind`\n\nEnables a considerably faster scanning method in `amp-bind`, by injecting a `i-amphtml-binding` attribute on all elements with a bound attribute.\n\n- name: `optimizeAmpBind`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeAmpBind](lib/transformers/OptimizeAmpBind.js)\n\n#### `optimizeHeroImages`\n\nEnables hero image optimization. Hero images will either be auto detected or you can explicitly mark these by adding the `data-hero` attribute:\n\n```\n<amp-img data-hero src=\"foo.jpg\" ...>\n```\n\nThe maximum number of hero images that can be marked up using `data-hero` is `2`. This number can be increased using the `maxHeroImageCount` parameter.\n\nHero images are optimized by server-side rendering the `img` element inside the `amp-img` element. This can siginificantly improve image rendering performance and reduce the [largest contentful paint](https://web.dev/lcp/) (LCP) metric from [Core Web Vitals](https://web.dev/vitals/).\n\n- name: `optimizeHeroImages`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeHeroImages](lib/transformers/OptimizeHeroImages.js)\n\n#### `preloadHeroImage`\n\nDeprecated, use [optimizeHeroImages](#optimizeheroimages) instead. \n\n#### `verbose`\n\nEnable verbose mode with more detailed logging output.\n\n- name: `verbose`\n- valid options: `[true|false]`\n- default: `false`\n\n## Features\n\n### Image optimization\n\nAMP Optimizer helps you serve optimized images. For this to work, you need to provide a function that maps an image `src` to a resized `srcset` source value. The image resizing needs to either happen at build time (e.g. for static sites) or via a image hosting service such as [thumbor](https://github.com/thumbor/thumbor).\n\nHere is an example implementation that appends the image width to the `src`:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  // parameters are the amp-img `src` and the `width` of the to be generated srcset source value\n  imageOptimizer: (src, width) => {\n    // we cannot rename if the image does not have a file extension\n    const index = src.lastIndexOf('.');\n    if (index === -1) {\n      // return null means we won't generate a srcset source value for this width\n      return null;\n    }\n    const prefix = src.substring(0, index);\n    const postfix = src.substring(index, src.length);\n    return `${prefix}.${width}w${postfix}`;\n  };\n})\n```\n\nUsing this implementation, AMP Optimizer will transform the following `amp-img` declarations:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img src=\"image1.png\" width=\"400\" height=\"800\" layout=\"responsive\"></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                             image-2x.png 2x\"\n></amp-img>\n```\n\ninto:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img\n  src=\"image1.png\"\n  width=\"400\"\n  height=\"800\"\n  layout=\"responsive\"\n  srcset=\"image1.470w.png 470w, image1.820w.png 820w, image1.1440w.png 1440w\"\n></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                               image-2x.png 2x\"\n></amp-img>\n```\n\n**Important** when using `layout=responsive` use the `width` and `height` attribute to specify the minimum image dimensions. For example, for a full-bleed hero image on mobile, specify the width as`width=320`.\n\n### Auto add incomplete markup\n\nIt's possible to pass incomplete documents and AMP Optimizer will add any\nmissing tags and extension imports required by a valid AMP document.\n\n```js\nconst originalHtml = `\n  <h1>Hello World!</h1>\n  <amp-twitter width=\"375\"\n               height=\"472\"\n               layout=\"responsive\"\n               data-tweetid=\"1182321926473162752\">\n  </amp-twitter>\n`;\n\n// you can pass the canonical URL, default is `.`\nconst opts = {\n  canonical: '/example.html',\n};\nampOptimizer.transformHtml(originalHtml, opts).then((optimizedHtml) => {\n  // optimizedHtml will be a valid AMP document\n  console.log(optimizedHtml);\n});\n```\n\n### Automated Markdown conversion\n\nAMP Optimizer supports converting Markdown to AMPHTML. A typical conversion flow would be:\n\n```\nREADME.md => HTML => AMP Optimizer => valid AMP\n```\n\nThe AMP Optimizer converts `<img>` tags into `<amp-img>` or `<amp-anim>` tags when in Markdown mode. Enable Markdown mode via `markdown : true`. AMP Optimizer will try to resolve image dimensions from the actual files. Images wider than 320px will automatically get an `intrinsic` layout.\n\nAll other Markdown features are already supported by AMP.\n\nYou can pass an additional option `imageBasePath` to specify a base path used to resolve an image during build, this can be a file system path or URL prefix.\n\n**Important:** for image size detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n```shell\nnpm install probe-image-size --save-dev\n```\n\nExample:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst md = require('markdown-it')({\n  // don't sanitize html if you want to support AMP components in Markdown\n  html: true,\n});\n\n// enable markdown mode\nconst ampOptimizer = AmpOptimizer.create({\n  markdown: true,\n});\n\nconst markdown = `\n# Markdown 🤯\n\nHere is an image declared in Markdown syntax:\n\n![A random image](https://unsplash.it/800/600).\n\nYou can directly declare AMP components:\n\n<amp-twitter width=\"375\"\n             height=\"472\"\n             layout=\"responsive\"\n             data-tweetid=\"1182321926473162752\">\n</amp-twitter>\n\nAny missing extensions will be automatically imported.\n`;\n\nconst html = md.render(markdown);\n\nconst amphtml = await ampOptimizer.transformHtml(html, {\n  canonical: filePath,\n});\n```\n\nYou can find a working sample [here](/packages/optimizer/demo/markdown).\n\n## Extending AMP Optimizer with custom transformations\n\nAMP Optimizer supports custom HTML transformations:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst {createElement, firstChildByTag, appendChild} = AmpOptimizer.NodeUtils;\n\nclass CustomTransformer {\n  constructor(config) {\n    this.log_ = config.log.tag('CUSTOM');\n  }\n  transform(tree, params) {\n    this.log_.info('Running custom transformation for ', params.filePath);\n    const html = firstChildByTag(tree, 'html');\n    if (!html) return;\n    const head = firstChildByTag(html, 'head');\n    if (!head) return;\n    const desc = createElement('meta', {\n      name: 'description',\n      content: 'this is just a demo',\n    });\n    appendChild(head, desc);\n  }\n}\n\n// it's best to run custom transformers first\nconst customTransformations = [CustomTransformer, ...AmpOptimizer.TRANSFORMATIONS_AMP_FIRST];\n\n// pass custom transformers when creating the optimizer\nconst optimizer = AmpOptimizer.create({\n  transformations: customTransformations,\n});\n// you can add custom parameters on a per document basis\nconst transformedHtml = await optimizer.transformHtml(html, {\n  filePath,\n});\n```\n\nCheckout [the samples](/packages/optimizer/demo/simple/index.js) to learn how to customize AMP Optimizer.\n\n## Best Practices\n\n### Make sure to enable AMP Boilerplate removal\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n- if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```js\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true,\n});\n```\n\n... or for individual pages:\n\n```js\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true,\n});\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length).replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](/packages/runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion,\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n- `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n- `imageBasePath`: specifies a base path used to resolve an image during build.\n- `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n- `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp',\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Fallback API for amp-geo\n\nIdeally, when self-hosting the AMP framework, `amp-geo-0.1.js` should be patched at delivery time to replace `{{AMP_ISO_COUNTRY_HOTPATCH}}` with the ISO 3166-1 alpha-2 country code where the request originated ([reference](https://github.com/ampproject/amphtml/blob/main/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem)). If your host does not have this capability, you can instead rely on a web API to return the country at runtime. The web API must be secure (HTTPS), adhere to [AMP CORS guidelines](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests/), and return JSON in the following format:\n\n```js\n{\"country\": \"de\"}\n```\n\nwhere in this example, `de` is the ISO 3166-1 alpha-2 country code for Germany.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  // this will instruct amp-geo to fetch the user's country from an API\n  // which returns JSON in format: {\"country\": \"de\"}\n  geoApiUrl: 'https://example.com/geo',\n});\n\nconsole.log(optimizedHtml);\n```\n\n## Development & Testing\n\nAMP Optimizer uses a snapshot based testing approach. To execute the tests, run in the project root:\n\n```shell\nnpm run test:node\n```\n\nTransformer tests are located in:\n\n```\n- spec/transformers/valid/TransformerName/test-name/\n    expected_output.html\n    input.html\n```\n\nThe transformation input is defined in `input.html`, whereas `expected_output.html` contains the expected\noutcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing\na transformer implementation, run:\n\n```shell\nnpm run test:optimizer:snapshot\n```\n\nto store a new snapshot version in `expected_output.html`.\n","gitHead":"d83473087587b0ea71588bf89ab767b45ad09d09","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/4.0.0/node@v14.15.4+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"14.15.4","dependencies":{"terser":"5.6.1","postcss":"8.2.8","domutils":"2.5.0","lru-cache":"6.0.0","domhandler":"4.0.0","cross-fetch":"3.1.2","htmlparser2":"6.0.1","cssnano-simple":"1.2.2","dom-serializer":"1.2.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"^2.8.0-canary.15","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.8.0-canary.0","@ampproject/toolbox-runtime-version":"^2.8.0-canary.15","@ampproject/toolbox-validator-rules":"^2.8.0-canary.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.0-canary.20_1621518376692_0.9799318704077784","host":"s3://npm-registry-packages"}},"2.8.0-canary.21":{"name":"@ampproject/toolbox-optimizer","version":"2.8.0-canary.21","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.0-canary.21","maintainers":[{"name":"esth","email":"esther@amp.dev"},{"name":"ampprojectbot","email":"bot@amp.dev"},{"name":"kdwan","email":"kdwan@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"8cca8156ab380a69bf25a0a957513646bd7d1603","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.0-canary.21.tgz","fileCount":50,"integrity":"sha512-QdrtubvapxZpxOY1Gsz80tt5LUJjxbScipdGrPg7M8CcCNeMUWK81Vh+E6m23ukh4e8Ltj41K2gwpQzxOb7FFg==","signatures":[{"sig":"MEQCIHmgBPe7pqrWPzNp35U4/YNYmdM+bSE9IEmakFXKGLadAiAcCkTnhTw3CF4A3Rq+htGnKvQQZXyis5wR8M+JBOP7BQ==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":249344,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtM6vCRA9TVsSAnZWagAAs6UP/AmcSVPdu7SHHEABDivM\n+hP7AYX/fwRYRWED9kptC2qqizMwZ+FE+IMmUahn4MqkZkiIArzNYUwWYjBe\npzr8kqycXDTo68j82PZKDKtbG1HOEgsbg4vy5QG3LFO3kPj2fv6jP3dQNvC9\nbis/sj1iE2xZr/WKce59MKJr0pUv0D5sNXq1TclNvJF0fnLm4ea/+AdBEUXv\nYpeAMWhFDj8gbD7h3GLdpRHoLgSbrGibmzXDmVMyhwcnNL9YSJ47S9WZI7RH\nQXR7OtNIihuGHOacxFbz7iJTuBKR7Xa+Ix15ULc8kE3vTfhixR3hWLLmaCy8\n7EEs1cmiDUg1L4NNd+A7nTZ3tdKD3d5w43iKqeP6Kk9Xe8zEK7XHZFb9Dwm5\ntj3nGkTNxpVPsJO51tmORYdIfQ8+xfRkefp/qe24XGvHYfH0nFqSgky/hHWm\nR5QYJtFnMqRdlBDi2nGrtuPmPbKoDszAEPRq3BfktuN52O6pxdUc7dYXdUOU\n2i2llRO9aWIsHzpLj0pxWb+Mvx/uoEakhdMkO15kKmxbIrLfWRhZgAEXQXfM\nsZ8jDruLBE7G25+o4ItVH0LHYD7kppTnC1Od0ICFrvamQKE4JX6tdJ8V+tYt\njKdIyy8RZH/kI8otM8JJ9Lq3tUA4r5/g47nEdS4Dv1jja+GT72b1VrfIBf8q\nlszj\r\n=YMqD\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to simplify creating AMP pages and improve AMP rendering performance. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n- [Server-side render AMP layouts](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/).\n- **Automatically import all missing AMP component scripts**.\n- **Automatically add any missing mandatory AMP tags**.\n- Auto detects and preloads hero images from amp-img, amp-iframe, amp-video, or amp-video-iframe.\n- Remove the AMP boilerplate (when possible).\n- Remove not needed whitespace.\n- Extract and move CSS keyframe animations to the bottom of the page.\n- Optimize AMP framework and custom font loading\n- Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n- AMP Optimizer will produce valid AMP.\n- AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\n### API\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>`;\n\nampOptimizer.transformHtml(originalHtml).then((optimizedHtml) => {\n  console.log(optimizedHtml);\n});\n```\n\nYou can find a sample implementation [here](/packages/optimizer/demo/simple). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](/packages/optimizer-express).\n\n### CLI\n\nAMP Optimizer can be used via the [AMP Toolbox CLI](/packages/cli/README.md):\n\n```shell\nnpm install @ampproject/toolbox-cli -g\namp optimize myFile.html\n```\n\nor run without installation via `npx`:\n\n```shell\nnpx @ampproject/toolbox-cli optimize myFile.html\n```\n\n### Options\n\nOptions are passed when creating a new AMP Optimizer instance:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  verbose: true\n});\n...\n```\n\nAvailable options are:\n\n- [autoAddMandatoryTags](#autoaddmandatorytags)\n- [autoExtensionImport](#autoextensionimport)\n- [esmModulesEnabled](#esmmodulesenabled)\n- [extensionVersions](#extensionversions)\n- [fetch](#fetch)\n- [format](#format)\n- [imageBasePath](#imagebasePath)\n- [imageOptimizer](#imageoptimizer)\n- [lts](#lts)\n- [markdown](#markdown)\n- [minify](#minify)\n- [optimizeAmpBind](#optimizeampbind)\n- [optimizeHeroImages](#optimizeheroimages)\n- [preloadHeroImage](#preloadheroimage)\n- [verbose](#verbose)\n\n#### `autoAddMandatoryTags`\n\nAutomatically inject any missing markup required by AMP.\n\n- name: `autoAddMandatoryTags`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `autoExtensionImport`\n\nAutomatically import any missing AMP Extensions (e.g. amp-carousel).\n\n- name: `autoExtensionImport`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `esmModulesEnabled`\n\nEnables the smaller ESM module version of AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n**Warning: This is incompatible with AMP Packager unless using a prerelease version containing [this commit](https://github.com/ampproject/amppackager/commit/92387ecf97e420eec10b378282383c59f95ab45f).**\n\n- name: `esmModulesEnabled`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `extensionVersions`\n\nSpecify version numbers to use for automatically imported Extensions. If not defined, default to latest.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  extensionVersions: {\n    'amp-twitter': '0.1',\n  },\n});\n```\n\n- name: `extensionVersions`\n- valid options: `OBJECT`\n- default: `{}`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `format`\n\nSpecifies the AMP format of the input file. Defaults to `AMP`.\n\n- name: `format`\n- valid options: `[AMP|AMP4EMAIL|AMP4ADS]`\n- default: `AMP`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js), [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `imageBasePath`\n\nSpecifies a base path used to resolve an image during build,\nthis can be a file system path or URL prefix. You can also pass a function\n`(imgSrc, params) => '../img/' + imgSrc` for dynamically calculating the image path.\n\n- name: `imageBasePath`\n- valid options: `STRING|FUNCTION`\n- default: undefined\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `imageOptimizer`\n\nEnable automated image `srcset` generation by providing a function for calculating `srcset` URLs for a given image `src`. The function should return a URL pointing to a version of the `src` image with the given `width`. If no image is available, it should return a falsy value.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  imageOptimizer: (src, width) => `${src}?width=${width}`,\n});\n```\n\n- name: `imageOptimizer`\n- valid options: `FUNCTION`\n- default: undefined\n- used by: [OptimizeImages](lib/transformers/OptimizeImages.js)\n\n#### `lts`\n\nUse [long-term stable URLs](https://github.com/ampproject/amphtml/blob/main/contributing/lts-release.md) for downloading the AMP runtime and components.\n\n- name: `lts`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `markdown`\n\nThis transformer adds out-of-the-box markdown support. This allows\nusing AMP Optimizer to convert HTML documents created from Markdown\nfiles into valid AMP. A typical conversion flow would be:\n\nREADME.md => HTML => AMP Optimizer => valid AMP\n\nThe only thing this transformer does is converting `<img>` tags into\neither `amp-img` or `amp-anim` tags. All other Markdown features are\nalready supported by AMP. The transformer will try to resolve image\ndimensions from the actual files. Images larger than 320px will automatically\nget an intrinsic layout. For image detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n- name: `markdown`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `minify`\n\nMinifies the generated HTML output and inlined CSS.\n\n- name: `minify`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [MinifyHtml](lib/transformers/MinifyHtml.js), [SeparateKeyframes](lib/transformers/SeparateKeyframes.js)\n\n**Warning:** this setting is not recommended when running AMP Optimizer in your backend on every request as execution time can increase by up to 7x.\n\n#### `optimizeAmpBind`\n\nEnables a considerably faster scanning method in `amp-bind`, by injecting a `i-amphtml-binding` attribute on all elements with a bound attribute.\n\n- name: `optimizeAmpBind`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeAmpBind](lib/transformers/OptimizeAmpBind.js)\n\n#### `optimizeHeroImages`\n\nEnables hero image optimization. Hero images will either be auto detected or you can explicitly mark these by adding the `data-hero` attribute:\n\n```\n<amp-img data-hero src=\"foo.jpg\" ...>\n```\n\nThe maximum number of hero images that can be marked up using `data-hero` is `2`. This number can be increased using the `maxHeroImageCount` parameter.\n\nHero images are optimized by server-side rendering the `img` element inside the `amp-img` element. This can siginificantly improve image rendering performance and reduce the [largest contentful paint](https://web.dev/lcp/) (LCP) metric from [Core Web Vitals](https://web.dev/vitals/).\n\n- name: `optimizeHeroImages`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeHeroImages](lib/transformers/OptimizeHeroImages.js)\n\n#### `preloadHeroImage`\n\nDeprecated, use [optimizeHeroImages](#optimizeheroimages) instead. \n\n#### `verbose`\n\nEnable verbose mode with more detailed logging output.\n\n- name: `verbose`\n- valid options: `[true|false]`\n- default: `false`\n\n## Features\n\n### Image optimization\n\nAMP Optimizer helps you serve optimized images. For this to work, you need to provide a function that maps an image `src` to a resized `srcset` source value. The image resizing needs to either happen at build time (e.g. for static sites) or via a image hosting service such as [thumbor](https://github.com/thumbor/thumbor).\n\nHere is an example implementation that appends the image width to the `src`:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  // parameters are the amp-img `src` and the `width` of the to be generated srcset source value\n  imageOptimizer: (src, width) => {\n    // we cannot rename if the image does not have a file extension\n    const index = src.lastIndexOf('.');\n    if (index === -1) {\n      // return null means we won't generate a srcset source value for this width\n      return null;\n    }\n    const prefix = src.substring(0, index);\n    const postfix = src.substring(index, src.length);\n    return `${prefix}.${width}w${postfix}`;\n  };\n})\n```\n\nUsing this implementation, AMP Optimizer will transform the following `amp-img` declarations:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img src=\"image1.png\" width=\"400\" height=\"800\" layout=\"responsive\"></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                             image-2x.png 2x\"\n></amp-img>\n```\n\ninto:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img\n  src=\"image1.png\"\n  width=\"400\"\n  height=\"800\"\n  layout=\"responsive\"\n  srcset=\"image1.470w.png 470w, image1.820w.png 820w, image1.1440w.png 1440w\"\n></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                               image-2x.png 2x\"\n></amp-img>\n```\n\n**Important** when using `layout=responsive` use the `width` and `height` attribute to specify the minimum image dimensions. For example, for a full-bleed hero image on mobile, specify the width as`width=320`.\n\n### Auto add incomplete markup\n\nIt's possible to pass incomplete documents and AMP Optimizer will add any\nmissing tags and extension imports required by a valid AMP document.\n\n```js\nconst originalHtml = `\n  <h1>Hello World!</h1>\n  <amp-twitter width=\"375\"\n               height=\"472\"\n               layout=\"responsive\"\n               data-tweetid=\"1182321926473162752\">\n  </amp-twitter>\n`;\n\n// you can pass the canonical URL, default is `.`\nconst opts = {\n  canonical: '/example.html',\n};\nampOptimizer.transformHtml(originalHtml, opts).then((optimizedHtml) => {\n  // optimizedHtml will be a valid AMP document\n  console.log(optimizedHtml);\n});\n```\n\n### Automated Markdown conversion\n\nAMP Optimizer supports converting Markdown to AMPHTML. A typical conversion flow would be:\n\n```\nREADME.md => HTML => AMP Optimizer => valid AMP\n```\n\nThe AMP Optimizer converts `<img>` tags into `<amp-img>` or `<amp-anim>` tags when in Markdown mode. Enable Markdown mode via `markdown : true`. AMP Optimizer will try to resolve image dimensions from the actual files. Images wider than 320px will automatically get an `intrinsic` layout.\n\nAll other Markdown features are already supported by AMP.\n\nYou can pass an additional option `imageBasePath` to specify a base path used to resolve an image during build, this can be a file system path or URL prefix.\n\n**Important:** for image size detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n```shell\nnpm install probe-image-size --save-dev\n```\n\nExample:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst md = require('markdown-it')({\n  // don't sanitize html if you want to support AMP components in Markdown\n  html: true,\n});\n\n// enable markdown mode\nconst ampOptimizer = AmpOptimizer.create({\n  markdown: true,\n});\n\nconst markdown = `\n# Markdown 🤯\n\nHere is an image declared in Markdown syntax:\n\n![A random image](https://unsplash.it/800/600).\n\nYou can directly declare AMP components:\n\n<amp-twitter width=\"375\"\n             height=\"472\"\n             layout=\"responsive\"\n             data-tweetid=\"1182321926473162752\">\n</amp-twitter>\n\nAny missing extensions will be automatically imported.\n`;\n\nconst html = md.render(markdown);\n\nconst amphtml = await ampOptimizer.transformHtml(html, {\n  canonical: filePath,\n});\n```\n\nYou can find a working sample [here](/packages/optimizer/demo/markdown).\n\n## Extending AMP Optimizer with custom transformations\n\nAMP Optimizer supports custom HTML transformations:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst {createElement, firstChildByTag, appendChild} = AmpOptimizer.NodeUtils;\n\nclass CustomTransformer {\n  constructor(config) {\n    this.log_ = config.log.tag('CUSTOM');\n  }\n  transform(tree, params) {\n    this.log_.info('Running custom transformation for ', params.filePath);\n    const html = firstChildByTag(tree, 'html');\n    if (!html) return;\n    const head = firstChildByTag(html, 'head');\n    if (!head) return;\n    const desc = createElement('meta', {\n      name: 'description',\n      content: 'this is just a demo',\n    });\n    appendChild(head, desc);\n  }\n}\n\n// it's best to run custom transformers first\nconst customTransformations = [CustomTransformer, ...AmpOptimizer.TRANSFORMATIONS_AMP_FIRST];\n\n// pass custom transformers when creating the optimizer\nconst optimizer = AmpOptimizer.create({\n  transformations: customTransformations,\n});\n// you can add custom parameters on a per document basis\nconst transformedHtml = await optimizer.transformHtml(html, {\n  filePath,\n});\n```\n\nCheckout [the samples](/packages/optimizer/demo/simple/index.js) to learn how to customize AMP Optimizer.\n\n## Best Practices\n\n### Make sure to enable AMP Boilerplate removal\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n- if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```js\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true,\n});\n```\n\n... or for individual pages:\n\n```js\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true,\n});\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length).replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](/packages/runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion,\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n- `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n- `imageBasePath`: specifies a base path used to resolve an image during build.\n- `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n- `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp',\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Fallback API for amp-geo\n\nIdeally, when self-hosting the AMP framework, `amp-geo-0.1.js` should be patched at delivery time to replace `{{AMP_ISO_COUNTRY_HOTPATCH}}` with the ISO 3166-1 alpha-2 country code where the request originated ([reference](https://github.com/ampproject/amphtml/blob/main/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem)). If your host does not have this capability, you can instead rely on a web API to return the country at runtime. The web API must be secure (HTTPS), adhere to [AMP CORS guidelines](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests/), and return JSON in the following format:\n\n```js\n{\"country\": \"de\"}\n```\n\nwhere in this example, `de` is the ISO 3166-1 alpha-2 country code for Germany.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  // this will instruct amp-geo to fetch the user's country from an API\n  // which returns JSON in format: {\"country\": \"de\"}\n  geoApiUrl: 'https://example.com/geo',\n});\n\nconsole.log(optimizedHtml);\n```\n\n## Development & Testing\n\nAMP Optimizer uses a snapshot based testing approach. To execute the tests, run in the project root:\n\n```shell\nnpm run test:node\n```\n\nTransformer tests are located in:\n\n```\n- spec/transformers/valid/TransformerName/test-name/\n    expected_output.html\n    input.html\n```\n\nThe transformation input is defined in `input.html`, whereas `expected_output.html` contains the expected\noutcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing\na transformer implementation, run:\n\n```shell\nnpm run test:optimizer:snapshot\n```\n\nto store a new snapshot version in `expected_output.html`.\n","gitHead":"c85dd8829798b64e39aea67c98bbc8470dacb18f","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/4.0.0/node@v14.15.4+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"14.15.4","dependencies":{"terser":"5.6.1","postcss":"8.2.8","domutils":"2.5.0","lru-cache":"6.0.0","domhandler":"4.0.0","cross-fetch":"3.1.2","htmlparser2":"6.0.1","cssnano-simple":"1.2.2","dom-serializer":"1.2.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"^2.8.0-canary.15","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.8.0-canary.0","@ampproject/toolbox-runtime-version":"^2.8.0-canary.15","@ampproject/toolbox-validator-rules":"^2.8.0-canary.9"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.0-canary.21_1622462126443_0.576625466885911","host":"s3://npm-registry-packages"}},"2.8.0":{"name":"@ampproject/toolbox-optimizer","version":"2.8.0","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.0","maintainers":[{"name":"esth","email":"esther@amp.dev"},{"name":"ampprojectbot","email":"bot@amp.dev"},{"name":"kdwan","email":"kdwan@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"0ae286018d8eaf60dfcda41d6491c41996bfc513","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.0.tgz","fileCount":50,"integrity":"sha512-HYScvLVco3eJDpOOXxW8/1SBjpE6J3js9a/pStIHgQFAUGYtxWQsocXN+fEngi+gnE1awbdjTwtvBkWEidLqJQ==","signatures":[{"sig":"MEUCIF09Ev51XYekYKB5AIOuxtrMs3HObLuA9++9cwRcgKuYAiEA0BVGSBNlVoFzRTRxSH1Gm6ZdfqdJXX4JqZufAFdx6oU=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":249296,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgtPRJCRA9TVsSAnZWagAAyn8QAIlUgllkvIhkcRrSmOPx\nRflVLV/tzRYQCKXaGmluGuEY9mrDEArI2UsV+cZiG4WwOKfYVQCCFiGFdcuX\nP4+2XCjRmSAXy9Jfq+hHn26wdIIVnH2QZEPPbvbPUwnaTb/1VnvKyusyf94h\nt4XPxvBmnbqJZoGlZRWBIhCE4D6v0n3qYQVUigVJQStu/9KsvZvVw6syhH3J\nRrU/L+Dz4sTZGGnf331g3F34ox/f7exTsz2Mxy1U+AdHMgJrS63TeDoe3f/p\nDiOzFZn47w5ZCdhnXI8Q+jqBdAixfWd31mnPFTItZHy4kULW+WDmVebn8SZP\nfHoamPdbYk9/uclsdG0B9YAhJ5g/gNeXU/2OuVk4BKlyigO9VJTw6mL2nm5d\nwJydBBFTMoHydSRVvRQyMdI/7MFRyKcbLkyzjknLY60Giidcm5fkgQB2Mm64\n5IV2Rj/6YhsRrDLYSD+Vq4kNZUlUSFp7IKaU+U830nqbcXyihHOizesIy66z\n4J0PUoYZfh2iy29ou/fLLYXnnmrBCmun7/CKeg/gJiZIw2xZMovFyhdlP6Dx\nQo86PfQqF/pqYniE1q6lxW9eOzy4TIgmNFi3jTNL4h4qwaOKTAHsxyhriYdD\n3Ux3quBkL5DytNPmWeSgGTfWgvjuHfs/g5LrSgB39Rm5TE0tM8X+bTqRFq5W\nIrAm\r\n=E2mu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"6103eb57765445355421826791361ac9ed9cf4d8","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/4.0.0/node@v14.15.4+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"14.15.4","dependencies":{"terser":"5.6.1","postcss":"8.2.8","domutils":"2.5.0","lru-cache":"6.0.0","domhandler":"4.0.0","cross-fetch":"3.1.2","htmlparser2":"6.0.1","cssnano-simple":"1.2.2","dom-serializer":"1.2.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"^2.8.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"^2.8.0","@ampproject/toolbox-runtime-version":"^2.8.0","@ampproject/toolbox-validator-rules":"^2.8.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.0_1622471752764_0.5745655558748546","host":"s3://npm-registry-packages"}},"2.8.1-canary.0":{"name":"@ampproject/toolbox-optimizer","version":"2.8.1-canary.0","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.1-canary.0","maintainers":[{"name":"esth","email":"esther@amp.dev"},{"name":"ampprojectbot","email":"bot@amp.dev"},{"name":"kdwan","email":"kdwan@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"e40ae79769431b543ea5a870f453dc2dfc370de0","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.1-canary.0.tgz","fileCount":50,"integrity":"sha512-xI+yMYCKZakoenI2z5aMlBQj5Qmg+yQ0epbdga8HJ+I1ep1wSo0q+wEzEPJTjCkdvEqUZto170xoEFmEWu87Gw==","signatures":[{"sig":"MEUCIQCtXpxziwQTga1cnabLLYgX/aSuKqSY4LvKr9EE/5FJFQIgFX+2j0Z172nBAMwtGPeuQ28ldlnohbfxzwABRZQY4+Q=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":249302,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgte2MCRA9TVsSAnZWagAAHhoQAJ9gYg1HQhtyxynUvFaG\nLtqUs/40YMb6Id9p28QiPkDvZ3VENEWpkbv3nEaZVlm+eqWQPkC38M+LO5eb\nsmgmE8RDOYDRvB7qELn4DTBlsIZJSz3PPkfFdOx9ELi6/mw5N1+PyU2kn1UI\np4mPtr5ldx/Wj5VOQ4p7FkSzOj6FyLIiMm6Uu+RCBIWUN+7L6iR7leRJrxmG\ng3soL/AepSRThkrDY0iuSmxg0mNHUvqb4WSVVKUWs6tQfQlY43sWOLBGvTFw\nN5PGkkK2sY3h7LtpxAU4wOGngKcmmgeNSD4Ebbu6dDRRuWZ9rGHqk2+6YRwu\nHOifOwMye7QmmoHnKfMFma4MP2n7/M+YkNi9T4VH2SRrrkwSBPo+wIWYNF1v\nYRBfrGbDZ6ZrRpERkqkRx0HSkPSEaGF+ZWWopzJRfGNhCTYdfoIqLidGAvLR\np6HB5auxXkz3daRCRsEYW8OuXZ4O+iTN4ZlxCx4EC8CruG7fpEPAmmYT8z9h\nE1QY9jXWyuvI/2OOSRH8MB5V97FDEtfRsn3NQrzb7j8L0RFaUjrTpOpGf1TI\n6YoE03lOfrlxuhIQ07s90dnzYcoeEAgqFrV0f1ozBSzHbFFlGGTVPtfmR5h8\nwRxb9ybFdiPCVg8UnIQFdZjAJB4ABz+/9yjGWHpo7B+p7lSvf670Chr9Fgsk\nCmgH\r\n=0Khz\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to simplify creating AMP pages and improve AMP rendering performance. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n- [Server-side render AMP layouts](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/).\n- **Automatically import all missing AMP component scripts**.\n- **Automatically add any missing mandatory AMP tags**.\n- Auto detects and preloads hero images from amp-img, amp-iframe, amp-video, or amp-video-iframe.\n- Remove the AMP boilerplate (when possible).\n- Remove not needed whitespace.\n- Extract and move CSS keyframe animations to the bottom of the page.\n- Optimize AMP framework and custom font loading\n- Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n- AMP Optimizer will produce valid AMP.\n- AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\n### API\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>`;\n\nampOptimizer.transformHtml(originalHtml).then((optimizedHtml) => {\n  console.log(optimizedHtml);\n});\n```\n\nYou can find a sample implementation [here](/packages/optimizer/demo/simple). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](/packages/optimizer-express).\n\n### CLI\n\nAMP Optimizer can be used via the [AMP Toolbox CLI](/packages/cli/README.md):\n\n```shell\nnpm install @ampproject/toolbox-cli -g\namp optimize myFile.html\n```\n\nor run without installation via `npx`:\n\n```shell\nnpx @ampproject/toolbox-cli optimize myFile.html\n```\n\n### Options\n\nOptions are passed when creating a new AMP Optimizer instance:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  verbose: true\n});\n...\n```\n\nAvailable options are:\n\n- [autoAddMandatoryTags](#autoaddmandatorytags)\n- [autoExtensionImport](#autoextensionimport)\n- [esmModulesEnabled](#esmmodulesenabled)\n- [extensionVersions](#extensionversions)\n- [fetch](#fetch)\n- [format](#format)\n- [imageBasePath](#imagebasePath)\n- [imageOptimizer](#imageoptimizer)\n- [lts](#lts)\n- [markdown](#markdown)\n- [minify](#minify)\n- [optimizeAmpBind](#optimizeampbind)\n- [optimizeHeroImages](#optimizeheroimages)\n- [preloadHeroImage](#preloadheroimage)\n- [verbose](#verbose)\n\n#### `autoAddMandatoryTags`\n\nAutomatically inject any missing markup required by AMP.\n\n- name: `autoAddMandatoryTags`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `autoExtensionImport`\n\nAutomatically import any missing AMP Extensions (e.g. amp-carousel).\n\n- name: `autoExtensionImport`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `esmModulesEnabled`\n\nEnables the smaller ESM module version of AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n**Warning: This is incompatible with AMP Packager unless using a prerelease version containing [this commit](https://github.com/ampproject/amppackager/commit/92387ecf97e420eec10b378282383c59f95ab45f).**\n\n- name: `esmModulesEnabled`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `extensionVersions`\n\nSpecify version numbers to use for automatically imported Extensions. If not defined, default to latest.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  extensionVersions: {\n    'amp-twitter': '0.1',\n  },\n});\n```\n\n- name: `extensionVersions`\n- valid options: `OBJECT`\n- default: `{}`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `format`\n\nSpecifies the AMP format of the input file. Defaults to `AMP`.\n\n- name: `format`\n- valid options: `[AMP|AMP4EMAIL|AMP4ADS]`\n- default: `AMP`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js), [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `imageBasePath`\n\nSpecifies a base path used to resolve an image during build,\nthis can be a file system path or URL prefix. You can also pass a function\n`(imgSrc, params) => '../img/' + imgSrc` for dynamically calculating the image path.\n\n- name: `imageBasePath`\n- valid options: `STRING|FUNCTION`\n- default: undefined\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `imageOptimizer`\n\nEnable automated image `srcset` generation by providing a function for calculating `srcset` URLs for a given image `src`. The function should return a URL pointing to a version of the `src` image with the given `width`. If no image is available, it should return a falsy value.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  imageOptimizer: (src, width) => `${src}?width=${width}`,\n});\n```\n\n- name: `imageOptimizer`\n- valid options: `FUNCTION`\n- default: undefined\n- used by: [OptimizeImages](lib/transformers/OptimizeImages.js)\n\n#### `lts`\n\nUse [long-term stable URLs](https://github.com/ampproject/amphtml/blob/main/contributing/lts-release.md) for downloading the AMP runtime and components.\n\n- name: `lts`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `markdown`\n\nThis transformer adds out-of-the-box markdown support. This allows\nusing AMP Optimizer to convert HTML documents created from Markdown\nfiles into valid AMP. A typical conversion flow would be:\n\nREADME.md => HTML => AMP Optimizer => valid AMP\n\nThe only thing this transformer does is converting `<img>` tags into\neither `amp-img` or `amp-anim` tags. All other Markdown features are\nalready supported by AMP. The transformer will try to resolve image\ndimensions from the actual files. Images larger than 320px will automatically\nget an intrinsic layout. For image detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n- name: `markdown`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `minify`\n\nMinifies the generated HTML output and inlined CSS.\n\n- name: `minify`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [MinifyHtml](lib/transformers/MinifyHtml.js), [SeparateKeyframes](lib/transformers/SeparateKeyframes.js)\n\n**Warning:** this setting is not recommended when running AMP Optimizer in your backend on every request as execution time can increase by up to 7x.\n\n#### `optimizeAmpBind`\n\nEnables a considerably faster scanning method in `amp-bind`, by injecting a `i-amphtml-binding` attribute on all elements with a bound attribute.\n\n- name: `optimizeAmpBind`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeAmpBind](lib/transformers/OptimizeAmpBind.js)\n\n#### `optimizeHeroImages`\n\nEnables hero image optimization. Hero images will either be auto detected or you can explicitly mark these by adding the `data-hero` attribute:\n\n```\n<amp-img data-hero src=\"foo.jpg\" ...>\n```\n\nThe maximum number of hero images that can be marked up using `data-hero` is `2`. This number can be increased using the `maxHeroImageCount` parameter.\n\nHero images are optimized by server-side rendering the `img` element inside the `amp-img` element. This can siginificantly improve image rendering performance and reduce the [largest contentful paint](https://web.dev/lcp/) (LCP) metric from [Core Web Vitals](https://web.dev/vitals/).\n\n- name: `optimizeHeroImages`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeHeroImages](lib/transformers/OptimizeHeroImages.js)\n\n#### `preloadHeroImage`\n\nDeprecated, use [optimizeHeroImages](#optimizeheroimages) instead. \n\n#### `verbose`\n\nEnable verbose mode with more detailed logging output.\n\n- name: `verbose`\n- valid options: `[true|false]`\n- default: `false`\n\n## Features\n\n### Image optimization\n\nAMP Optimizer helps you serve optimized images. For this to work, you need to provide a function that maps an image `src` to a resized `srcset` source value. The image resizing needs to either happen at build time (e.g. for static sites) or via a image hosting service such as [thumbor](https://github.com/thumbor/thumbor).\n\nHere is an example implementation that appends the image width to the `src`:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  // parameters are the amp-img `src` and the `width` of the to be generated srcset source value\n  imageOptimizer: (src, width) => {\n    // we cannot rename if the image does not have a file extension\n    const index = src.lastIndexOf('.');\n    if (index === -1) {\n      // return null means we won't generate a srcset source value for this width\n      return null;\n    }\n    const prefix = src.substring(0, index);\n    const postfix = src.substring(index, src.length);\n    return `${prefix}.${width}w${postfix}`;\n  };\n})\n```\n\nUsing this implementation, AMP Optimizer will transform the following `amp-img` declarations:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img src=\"image1.png\" width=\"400\" height=\"800\" layout=\"responsive\"></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                             image-2x.png 2x\"\n></amp-img>\n```\n\ninto:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img\n  src=\"image1.png\"\n  width=\"400\"\n  height=\"800\"\n  layout=\"responsive\"\n  srcset=\"image1.470w.png 470w, image1.820w.png 820w, image1.1440w.png 1440w\"\n></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                               image-2x.png 2x\"\n></amp-img>\n```\n\n**Important** when using `layout=responsive` use the `width` and `height` attribute to specify the minimum image dimensions. For example, for a full-bleed hero image on mobile, specify the width as`width=320`.\n\n### Auto add incomplete markup\n\nIt's possible to pass incomplete documents and AMP Optimizer will add any\nmissing tags and extension imports required by a valid AMP document.\n\n```js\nconst originalHtml = `\n  <h1>Hello World!</h1>\n  <amp-twitter width=\"375\"\n               height=\"472\"\n               layout=\"responsive\"\n               data-tweetid=\"1182321926473162752\">\n  </amp-twitter>\n`;\n\n// you can pass the canonical URL, default is `.`\nconst opts = {\n  canonical: '/example.html',\n};\nampOptimizer.transformHtml(originalHtml, opts).then((optimizedHtml) => {\n  // optimizedHtml will be a valid AMP document\n  console.log(optimizedHtml);\n});\n```\n\n### Automated Markdown conversion\n\nAMP Optimizer supports converting Markdown to AMPHTML. A typical conversion flow would be:\n\n```\nREADME.md => HTML => AMP Optimizer => valid AMP\n```\n\nThe AMP Optimizer converts `<img>` tags into `<amp-img>` or `<amp-anim>` tags when in Markdown mode. Enable Markdown mode via `markdown : true`. AMP Optimizer will try to resolve image dimensions from the actual files. Images wider than 320px will automatically get an `intrinsic` layout.\n\nAll other Markdown features are already supported by AMP.\n\nYou can pass an additional option `imageBasePath` to specify a base path used to resolve an image during build, this can be a file system path or URL prefix.\n\n**Important:** for image size detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n```shell\nnpm install probe-image-size --save-dev\n```\n\nExample:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst md = require('markdown-it')({\n  // don't sanitize html if you want to support AMP components in Markdown\n  html: true,\n});\n\n// enable markdown mode\nconst ampOptimizer = AmpOptimizer.create({\n  markdown: true,\n});\n\nconst markdown = `\n# Markdown 🤯\n\nHere is an image declared in Markdown syntax:\n\n![A random image](https://unsplash.it/800/600).\n\nYou can directly declare AMP components:\n\n<amp-twitter width=\"375\"\n             height=\"472\"\n             layout=\"responsive\"\n             data-tweetid=\"1182321926473162752\">\n</amp-twitter>\n\nAny missing extensions will be automatically imported.\n`;\n\nconst html = md.render(markdown);\n\nconst amphtml = await ampOptimizer.transformHtml(html, {\n  canonical: filePath,\n});\n```\n\nYou can find a working sample [here](/packages/optimizer/demo/markdown).\n\n## Extending AMP Optimizer with custom transformations\n\nAMP Optimizer supports custom HTML transformations:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst {createElement, firstChildByTag, appendChild} = AmpOptimizer.NodeUtils;\n\nclass CustomTransformer {\n  constructor(config) {\n    this.log_ = config.log.tag('CUSTOM');\n  }\n  transform(tree, params) {\n    this.log_.info('Running custom transformation for ', params.filePath);\n    const html = firstChildByTag(tree, 'html');\n    if (!html) return;\n    const head = firstChildByTag(html, 'head');\n    if (!head) return;\n    const desc = createElement('meta', {\n      name: 'description',\n      content: 'this is just a demo',\n    });\n    appendChild(head, desc);\n  }\n}\n\n// it's best to run custom transformers first\nconst customTransformations = [CustomTransformer, ...AmpOptimizer.TRANSFORMATIONS_AMP_FIRST];\n\n// pass custom transformers when creating the optimizer\nconst optimizer = AmpOptimizer.create({\n  transformations: customTransformations,\n});\n// you can add custom parameters on a per document basis\nconst transformedHtml = await optimizer.transformHtml(html, {\n  filePath,\n});\n```\n\nCheckout [the samples](/packages/optimizer/demo/simple/index.js) to learn how to customize AMP Optimizer.\n\n## Best Practices\n\n### Make sure to enable AMP Boilerplate removal\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n- if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```js\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true,\n});\n```\n\n... or for individual pages:\n\n```js\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true,\n});\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length).replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](/packages/runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion,\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n- `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n- `imageBasePath`: specifies a base path used to resolve an image during build.\n- `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n- `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp',\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Fallback API for amp-geo\n\nIdeally, when self-hosting the AMP framework, `amp-geo-0.1.js` should be patched at delivery time to replace `{{AMP_ISO_COUNTRY_HOTPATCH}}` with the ISO 3166-1 alpha-2 country code where the request originated ([reference](https://github.com/ampproject/amphtml/blob/main/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem)). If your host does not have this capability, you can instead rely on a web API to return the country at runtime. The web API must be secure (HTTPS), adhere to [AMP CORS guidelines](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests/), and return JSON in the following format:\n\n```js\n{\"country\": \"de\"}\n```\n\nwhere in this example, `de` is the ISO 3166-1 alpha-2 country code for Germany.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  // this will instruct amp-geo to fetch the user's country from an API\n  // which returns JSON in format: {\"country\": \"de\"}\n  geoApiUrl: 'https://example.com/geo',\n});\n\nconsole.log(optimizedHtml);\n```\n\n## Development & Testing\n\nAMP Optimizer uses a snapshot based testing approach. To execute the tests, run in the project root:\n\n```shell\nnpm run test:node\n```\n\nTransformer tests are located in:\n\n```\n- spec/transformers/valid/TransformerName/test-name/\n    expected_output.html\n    input.html\n```\n\nThe transformation input is defined in `input.html`, whereas `expected_output.html` contains the expected\noutcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing\na transformer implementation, run:\n\n```shell\nnpm run test:optimizer:snapshot\n```\n\nto store a new snapshot version in `expected_output.html`.\n","gitHead":"5d38b310f5c3c467220b03e3217ee8dc6f5e37d9","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/4.0.0/node@v14.15.4+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"14.15.4","dependencies":{"terser":"5.5.1","postcss":"8.2.13","domutils":"2.5.0","lru-cache":"6.0.0","domhandler":"4.0.0","cross-fetch":"3.1.2","htmlparser2":"6.0.1","cssnano-simple":"2.0.0","dom-serializer":"1.2.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"2.8.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"2.8.0","@ampproject/toolbox-runtime-version":"2.8.0","@ampproject/toolbox-validator-rules":"2.8.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.1-canary.0_1622535564489_0.9908317225668626","host":"s3://npm-registry-packages"}},"2.8.1-canary.1":{"name":"@ampproject/toolbox-optimizer","version":"2.8.1-canary.1","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.1-canary.1","maintainers":[{"name":"esth","email":"esther@amp.dev"},{"name":"ampprojectbot","email":"bot@amp.dev"},{"name":"kdwan","email":"kdwan@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"97c3e000b0afe1f5398e7f985a104cf42a7134fc","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.1-canary.1.tgz","fileCount":50,"integrity":"sha512-1rbhVvaoinKWR7mJIsaLYpLqan8r76QJswXFHj/sSOderBCAC6yFwXw/AEWj8As+Jj1yEg8EmJXf53XZ7HFc3w==","signatures":[{"sig":"MEQCIC6MbxMEvih2h2SDl4Hd7okunfsNUl4mGfXrd3D8j8FzAiA7GAcRZ28tWoFRF3MpjZiBpBOm9StH0H0Ldc1WVjR3kA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":250018,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgv2buCRA9TVsSAnZWagAAFQsQAKTKw3c6H1nvmf7Axa7H\nOSbFFo/IXrxdcqs7U5Ws+cWsduHop/N3kZOn8vgzTDm39D5+hwjiGibraMGK\n/JHPYbk5xRSs8E73WtUJbO+3zYixqvXclq5+w7FFCvBfw+Y/q0UYkYFYX1ce\nctgrjdUUJFjzNDfj/czIGmL7Bm6pQ+YXJDUtFVXze8tBVcu0Qe2xS8cfI5UN\nLxvTgyNQlHioB1Hq1xLyNQ5MJfsc8LQbUcbAMRD3TEITUKVdZmz1ECG0GDBw\nIj64yjn9yJ7DabDzQhBtOuS8jR+RAQu5C/XlUZOzlDQDwiZNW2SMfoxaThiQ\nUbSo37ZyA3kryORQNsEeDnvHVHflEd9YYN7laUgipKf0WykUstNB3ADtCfAm\niFfs9AC8t0ir8DRj6LvHscUDHdk/BXggwzA0Tz77orQRj1gMcypm0I6eLTTi\nPELCaoLJB23OkL3QU4t6NvBILbx8aewT6b/Odtkrj2f+MFLmmeNur73LPOhH\nxz+WYeeO8szZMBt7j8RFDh6i9zKxfbmeBmPcwJH2c77bCYxU2QENsYKqZEYg\nqPVzYgGhtNumW/ZbdJFaRnPhilwCxoRNtau1f2u3NJyhMamy/mmD6YqrtEu6\n1Q297QVeo3hM1MOCnW9XPfnG4Yhcvy7r3TXNZyGVjK9m/3c1EdmFcsvpWe0a\nU7DH\r\n=mk2H\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to simplify creating AMP pages and improve AMP rendering performance. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n- [Server-side render AMP layouts](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/).\n- **Automatically import all missing AMP component scripts**.\n- **Automatically add any missing mandatory AMP tags**.\n- Auto detects and preloads hero images from amp-img, amp-iframe, amp-video, or amp-video-iframe.\n- Remove the AMP boilerplate (when possible).\n- Remove not needed whitespace.\n- Extract and move CSS keyframe animations to the bottom of the page.\n- Optimize AMP framework and custom font loading\n- Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n- AMP Optimizer will produce valid AMP.\n- AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\n### API\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>`;\n\nampOptimizer.transformHtml(originalHtml).then((optimizedHtml) => {\n  console.log(optimizedHtml);\n});\n```\n\nYou can find a sample implementation [here](/packages/optimizer/demo/simple). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](/packages/optimizer-express).\n\n### CLI\n\nAMP Optimizer can be used via the [AMP Toolbox CLI](/packages/cli/README.md):\n\n```shell\nnpm install @ampproject/toolbox-cli -g\namp optimize myFile.html\n```\n\nor run without installation via `npx`:\n\n```shell\nnpx @ampproject/toolbox-cli optimize myFile.html\n```\n\n### Options\n\nOptions are passed when creating a new AMP Optimizer instance:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  verbose: true\n});\n...\n```\n\nAvailable options are:\n\n- [autoAddMandatoryTags](#autoaddmandatorytags)\n- [autoExtensionImport](#autoextensionimport)\n- [esmModulesEnabled](#esmmodulesenabled)\n- [extensionVersions](#extensionversions)\n- [fetch](#fetch)\n- [format](#format)\n- [imageBasePath](#imagebasePath)\n- [imageOptimizer](#imageoptimizer)\n- [lts](#lts)\n- [markdown](#markdown)\n- [minify](#minify)\n- [optimizeAmpBind](#optimizeampbind)\n- [optimizeHeroImages](#optimizeheroimages)\n- [preloadHeroImage](#preloadheroimage)\n- [verbose](#verbose)\n\n#### `autoAddMandatoryTags`\n\nAutomatically inject any missing markup required by AMP.\n\n- name: `autoAddMandatoryTags`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `autoExtensionImport`\n\nAutomatically import any missing AMP Extensions (e.g. amp-carousel).\n\n- name: `autoExtensionImport`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `esmModulesEnabled`\n\nEnables the smaller ESM module version of AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n**Warning: This is incompatible with AMP Packager unless using a prerelease version containing [this commit](https://github.com/ampproject/amppackager/commit/92387ecf97e420eec10b378282383c59f95ab45f).**\n\n- name: `esmModulesEnabled`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `extensionVersions`\n\nSpecify version numbers to use for automatically imported Extensions. If not defined, default to latest.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  extensionVersions: {\n    'amp-twitter': '0.1',\n  },\n});\n```\n\n- name: `extensionVersions`\n- valid options: `OBJECT`\n- default: `{}`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `format`\n\nSpecifies the AMP format of the input file. Defaults to `AMP`.\n\n- name: `format`\n- valid options: `[AMP|AMP4EMAIL|AMP4ADS]`\n- default: `AMP`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js), [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `imageBasePath`\n\nSpecifies a base path used to resolve an image during build,\nthis can be a file system path or URL prefix. You can also pass a function\n`(imgSrc, params) => '../img/' + imgSrc` for dynamically calculating the image path.\n\n- name: `imageBasePath`\n- valid options: `STRING|FUNCTION`\n- default: undefined\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `imageOptimizer`\n\nEnable automated image `srcset` generation by providing a function for calculating `srcset` URLs for a given image `src`. The function should return a URL pointing to a version of the `src` image with the given `width`. If no image is available, it should return a falsy value.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  imageOptimizer: (src, width) => `${src}?width=${width}`,\n});\n```\n\n- name: `imageOptimizer`\n- valid options: `FUNCTION`\n- default: undefined\n- used by: [OptimizeImages](lib/transformers/OptimizeImages.js)\n\n#### `lts`\n\nUse [long-term stable URLs](https://github.com/ampproject/amphtml/blob/main/contributing/lts-release.md) for downloading the AMP runtime and components.\n\n- name: `lts`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `markdown`\n\nThis transformer adds out-of-the-box markdown support. This allows\nusing AMP Optimizer to convert HTML documents created from Markdown\nfiles into valid AMP. A typical conversion flow would be:\n\nREADME.md => HTML => AMP Optimizer => valid AMP\n\nThe only thing this transformer does is converting `<img>` tags into\neither `amp-img` or `amp-anim` tags. All other Markdown features are\nalready supported by AMP. The transformer will try to resolve image\ndimensions from the actual files. Images larger than 320px will automatically\nget an intrinsic layout. For image detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n- name: `markdown`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `minify`\n\nMinifies the generated HTML output and inlined CSS.\n\n- name: `minify`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [MinifyHtml](lib/transformers/MinifyHtml.js), [SeparateKeyframes](lib/transformers/SeparateKeyframes.js)\n\n**Warning:** this setting is not recommended when running AMP Optimizer in your backend on every request as execution time can increase by up to 7x.\n\n#### `optimizeAmpBind`\n\nEnables a considerably faster scanning method in `amp-bind`, by injecting a `i-amphtml-binding` attribute on all elements with a bound attribute.\n\n- name: `optimizeAmpBind`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeAmpBind](lib/transformers/OptimizeAmpBind.js)\n\n#### `optimizeHeroImages`\n\nEnables hero image optimization. Hero images will either be auto detected or you can explicitly mark these by adding the `data-hero` attribute:\n\n```\n<amp-img data-hero src=\"foo.jpg\" ...>\n```\n\nThe maximum number of hero images that can be marked up using `data-hero` is `2`. This number can be increased using the `maxHeroImageCount` parameter.\n\nHero images are optimized by server-side rendering the `img` element inside the `amp-img` element. This can siginificantly improve image rendering performance and reduce the [largest contentful paint](https://web.dev/lcp/) (LCP) metric from [Core Web Vitals](https://web.dev/vitals/).\n\n- name: `optimizeHeroImages`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeHeroImages](lib/transformers/OptimizeHeroImages.js)\n\n#### `preloadHeroImage`\n\nDeprecated, use [optimizeHeroImages](#optimizeheroimages) instead. \n\n#### `verbose`\n\nEnable verbose mode with more detailed logging output.\n\n- name: `verbose`\n- valid options: `[true|false]`\n- default: `false`\n\n## Features\n\n### Image optimization\n\nAMP Optimizer helps you serve optimized images. For this to work, you need to provide a function that maps an image `src` to a resized `srcset` source value. The image resizing needs to either happen at build time (e.g. for static sites) or via a image hosting service such as [thumbor](https://github.com/thumbor/thumbor).\n\nHere is an example implementation that appends the image width to the `src`:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  // parameters are the amp-img `src` and the `width` of the to be generated srcset source value\n  imageOptimizer: (src, width) => {\n    // we cannot rename if the image does not have a file extension\n    const index = src.lastIndexOf('.');\n    if (index === -1) {\n      // return null means we won't generate a srcset source value for this width\n      return null;\n    }\n    const prefix = src.substring(0, index);\n    const postfix = src.substring(index, src.length);\n    return `${prefix}.${width}w${postfix}`;\n  };\n})\n```\n\nUsing this implementation, AMP Optimizer will transform the following `amp-img` declarations:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img src=\"image1.png\" width=\"400\" height=\"800\" layout=\"responsive\"></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                             image-2x.png 2x\"\n></amp-img>\n```\n\ninto:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img\n  src=\"image1.png\"\n  width=\"400\"\n  height=\"800\"\n  layout=\"responsive\"\n  srcset=\"image1.470w.png 470w, image1.820w.png 820w, image1.1440w.png 1440w\"\n></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                               image-2x.png 2x\"\n></amp-img>\n```\n\n**Important** when using `layout=responsive` use the `width` and `height` attribute to specify the minimum image dimensions. For example, for a full-bleed hero image on mobile, specify the width as`width=320`.\n\n### Auto add incomplete markup\n\nIt's possible to pass incomplete documents and AMP Optimizer will add any\nmissing tags and extension imports required by a valid AMP document.\n\n```js\nconst originalHtml = `\n  <h1>Hello World!</h1>\n  <amp-twitter width=\"375\"\n               height=\"472\"\n               layout=\"responsive\"\n               data-tweetid=\"1182321926473162752\">\n  </amp-twitter>\n`;\n\n// you can pass the canonical URL, default is `.`\nconst opts = {\n  canonical: '/example.html',\n};\nampOptimizer.transformHtml(originalHtml, opts).then((optimizedHtml) => {\n  // optimizedHtml will be a valid AMP document\n  console.log(optimizedHtml);\n});\n```\n\n### Automated Markdown conversion\n\nAMP Optimizer supports converting Markdown to AMPHTML. A typical conversion flow would be:\n\n```\nREADME.md => HTML => AMP Optimizer => valid AMP\n```\n\nThe AMP Optimizer converts `<img>` tags into `<amp-img>` or `<amp-anim>` tags when in Markdown mode. Enable Markdown mode via `markdown : true`. AMP Optimizer will try to resolve image dimensions from the actual files. Images wider than 320px will automatically get an `intrinsic` layout.\n\nAll other Markdown features are already supported by AMP.\n\nYou can pass an additional option `imageBasePath` to specify a base path used to resolve an image during build, this can be a file system path or URL prefix.\n\n**Important:** for image size detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n```shell\nnpm install probe-image-size --save-dev\n```\n\nExample:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst md = require('markdown-it')({\n  // don't sanitize html if you want to support AMP components in Markdown\n  html: true,\n});\n\n// enable markdown mode\nconst ampOptimizer = AmpOptimizer.create({\n  markdown: true,\n});\n\nconst markdown = `\n# Markdown 🤯\n\nHere is an image declared in Markdown syntax:\n\n![A random image](https://unsplash.it/800/600).\n\nYou can directly declare AMP components:\n\n<amp-twitter width=\"375\"\n             height=\"472\"\n             layout=\"responsive\"\n             data-tweetid=\"1182321926473162752\">\n</amp-twitter>\n\nAny missing extensions will be automatically imported.\n`;\n\nconst html = md.render(markdown);\n\nconst amphtml = await ampOptimizer.transformHtml(html, {\n  canonical: filePath,\n});\n```\n\nYou can find a working sample [here](/packages/optimizer/demo/markdown).\n\n## Extending AMP Optimizer with custom transformations\n\nAMP Optimizer supports custom HTML transformations:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst {createElement, firstChildByTag, appendChild} = AmpOptimizer.NodeUtils;\n\nclass CustomTransformer {\n  constructor(config) {\n    this.log_ = config.log.tag('CUSTOM');\n  }\n  transform(tree, params) {\n    this.log_.info('Running custom transformation for ', params.filePath);\n    const html = firstChildByTag(tree, 'html');\n    if (!html) return;\n    const head = firstChildByTag(html, 'head');\n    if (!head) return;\n    const desc = createElement('meta', {\n      name: 'description',\n      content: 'this is just a demo',\n    });\n    appendChild(head, desc);\n  }\n}\n\n// it's best to run custom transformers first\nconst customTransformations = [CustomTransformer, ...AmpOptimizer.TRANSFORMATIONS_AMP_FIRST];\n\n// pass custom transformers when creating the optimizer\nconst optimizer = AmpOptimizer.create({\n  transformations: customTransformations,\n});\n// you can add custom parameters on a per document basis\nconst transformedHtml = await optimizer.transformHtml(html, {\n  filePath,\n});\n```\n\nCheckout [the samples](/packages/optimizer/demo/simple/index.js) to learn how to customize AMP Optimizer.\n\n## Best Practices\n\n### Make sure to enable AMP Boilerplate removal\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n- if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```js\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true,\n});\n```\n\n... or for individual pages:\n\n```js\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true,\n});\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length).replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](/packages/runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion,\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n- `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n- `imageBasePath`: specifies a base path used to resolve an image during build.\n- `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n- `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp',\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Fallback API for amp-geo\n\nIdeally, when self-hosting the AMP framework, `amp-geo-0.1.js` should be patched at delivery time to replace `{{AMP_ISO_COUNTRY_HOTPATCH}}` with the ISO 3166-1 alpha-2 country code where the request originated ([reference](https://github.com/ampproject/amphtml/blob/main/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem)). If your host does not have this capability, you can instead rely on a web API to return the country at runtime. The web API must be secure (HTTPS), adhere to [AMP CORS guidelines](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests/), and return JSON in the following format:\n\n```js\n{\"country\": \"de\"}\n```\n\nwhere in this example, `de` is the ISO 3166-1 alpha-2 country code for Germany.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  // this will instruct amp-geo to fetch the user's country from an API\n  // which returns JSON in format: {\"country\": \"de\"}\n  geoApiUrl: 'https://example.com/geo',\n});\n\nconsole.log(optimizedHtml);\n```\n\n## Development & Testing\n\nAMP Optimizer uses a snapshot based testing approach. To execute the tests, run in the project root:\n\n```shell\nnpm run test:node\n```\n\nTransformer tests are located in:\n\n```\n- spec/transformers/valid/TransformerName/test-name/\n    expected_output.html\n    input.html\n```\n\nThe transformation input is defined in `input.html`, whereas `expected_output.html` contains the expected\noutcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing\na transformer implementation, run:\n\n```shell\nnpm run test:optimizer:snapshot\n```\n\nto store a new snapshot version in `expected_output.html`.\n","gitHead":"6031314b0367d685f9b8c0769937bfb9b978b12c","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/4.0.0/node@v14.15.4+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"14.15.4","dependencies":{"terser":"5.5.1","postcss":"8.2.13","domutils":"2.5.0","lru-cache":"6.0.0","domhandler":"4.0.0","cross-fetch":"3.1.2","htmlparser2":"6.0.1","cssnano-simple":"2.0.0","dom-serializer":"1.2.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"2.8.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"2.8.0","@ampproject/toolbox-runtime-version":"2.8.0","@ampproject/toolbox-validator-rules":"2.8.0"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.1-canary.1_1623156462096_0.7585959625166256","host":"s3://npm-registry-packages"}},"2.8.1":{"name":"@ampproject/toolbox-optimizer","version":"2.8.1","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.1","maintainers":[{"name":"esth","email":"esther@amp.dev"},{"name":"ampprojectbot","email":"bot@amp.dev"},{"name":"kdwan","email":"kdwan@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"dfe5213a08f444b4b0207ad9e23497e71c334a6f","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.1.tgz","fileCount":50,"integrity":"sha512-mfydgqJRJDoTCdDS+af7W/w+HkdQG4DhXdqYvaYurPBVkdFCuA/ioZVFI1GwYqIo96j8EgBHUyhNYmQ0Pf7jMg==","signatures":[{"sig":"MEUCIFH7HvlmrVzGC3vBvMxauCv3SwUFE8L9HAs8m/ouLFtiAiEAxe1zv8KaPF9HxQp869SUpzypJ7hWnL7Ed7hbedHqLBc=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":250010,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgwhFaCRA9TVsSAnZWagAALoUP/353zg07+JYt57g90KRK\n8URwUT63DzPe7zV2+8+KN/MjFbTkViHvUhl/MCEJv5TsyU0TshsS3I5h0jz1\n0Kc4I5aSlbu6hTbJj+5r0RQPq/Yhiyi88XPQcSLqtvUjONgIQTQbi4KJIbhL\n11ZFrsXv0QlhDQsgXboP53+Hj6PudEmqFcH3Vki+//6M2P8Syf6y7aJhYlGY\nESk+syr0xnqyBvYz4BcIfpcHXQqIUj8u5v3MfspJB62Fq0JrDSPVOFNzdZIF\nbQeSdeqjz6T/cM3e1drTMPHyVljsrEdLRdSZy+IYjIQ2i+tjravx0fb/cx0K\nH6tT2caX2RB9m/T4M2ozxnQ9eAzh1kAkDV+1uJGBii01qYatz1Z48mWNQmOo\njIrpoo9wLGuYDTdxQ94ACg6B1oVX08icf/NIyghKi14U+Sub7JzfCV/JFvlq\nG1n8y9tDaPGgfZR/2GvN1vwaJWIKkaGbe5Yfu+rTCeOJvw0UwQg9wGweoCQt\nL/bdgx0DWAYAOHOQcjQ8ZRYhvRDsaLSRhI67ohaDVzS4k1uuhe0aoEEsIGhT\nWegcMW0Ju2lt6S8SeMeSjcVFlM+WeQe0n5Scx4GpRzPVamHhnKRfT57BMBdy\nGBsGTA/za1fs0Gn13C8vy1toQ0Xw+hazPsHqLystwG3S7v9OivqBHJmteJmy\nvD1r\r\n=iHHg\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"f2c2de8e3699ef107aed1d7a5320ee340495d66e","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/4.0.0/node@v14.15.4+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"14.15.4","dependencies":{"terser":"5.5.1","postcss":"8.2.13","domutils":"2.5.0","lru-cache":"6.0.0","domhandler":"4.0.0","cross-fetch":"3.1.2","htmlparser2":"6.0.1","cssnano-simple":"2.0.0","dom-serializer":"1.2.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"2.8.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"2.8.0","@ampproject/toolbox-runtime-version":"2.8.0","@ampproject/toolbox-validator-rules":"2.8.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.1_1623331162557_0.7927422379594586","host":"s3://npm-registry-packages"}},"2.8.2":{"name":"@ampproject/toolbox-optimizer","version":"2.8.2","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.2","maintainers":[{"name":"esth","email":"esther@amp.dev"},{"name":"ampprojectbot","email":"bot@amp.dev"},{"name":"kdwan","email":"kdwan@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"8448cc45d05739cf2f76b34ae909c140758b3df2","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.2.tgz","fileCount":50,"integrity":"sha512-/K2641horZB8FuI1JG00SBrt2WaxRTB2jDv93bDQqQ5nuvmt40aOxlwnSY6psuukv2fREv8CtG5PHe8laavyMw==","signatures":[{"sig":"MEYCIQCnOJzUYPulOXtknCel7ZrmvNAJ86t5BWhIV93pp1SsIAIhAODtGqfcapsgZ9iRpSl7GtB0UAMmFDoyDWEcG57KaUGm","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":251552,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg3zLyCRA9TVsSAnZWagAA4ZUP/2WXYTfIUBv7gzbISpDC\nYcffbLWsnE4IHaUPP4AKCXsU4VwIIda/Pz1piOsy1gAm1gytDBAvy/lW0BqO\nny0zJu0NBJcc6CNr5yIxSdEKFONDpIt4WWmnmgH2hNOJa+rq5OvUyxmkaUK3\nSddAHlIkX9TaLdHLgHtxNRM5lzwkRxpFYytFGIFgWKjTgjK5SohUiWOZdOl2\nkgbdK8Z/rMFW4gOfs0VYTnzhkuxrzlLuHRt8bHio3TqXOSV3mbdnj0HEKKJq\nXY65lWl+sTLUk/+xHgR5O955wQwC3MQ4M2/bJBMS9NRJcFf4+ONGPZD3dxQc\nQJmIV/tr7vQn6IFY9sRh5aAYFgbVX2sdGRa10WjeF4g9qLXwj4B8DCyjxjEV\nkuIr8DKUg2lzOI4nVG8TXstHpIIXfLA8Gw+r0GWY1XE2HI8zs8V40PBc7brr\nguNGoukfWnQLQ/OQFubruWj2YqcoNlnCTYzN6+EikHuLvFrkjlPq0A84xzfz\nS7sGSWGjeMaLajlh0Qp77R7c0IqUpwl39I9X1GZaFCs6N+R0VdtqIzJvCc4e\nmG1YEpM9P5IA9xoAiOWQojIW5KRq4z/B+REPLHHwXQBQwNIeGhs6TfDjDPYG\njHhI+qk3jyDjzvicBGScfGKWWfZyjgQGfYbHNWJ7IkBCh9dwveTugktand4z\niAPy\r\n=16uc\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"c71c9ad1180b982c1eb00c759b689a58ae5e7bc6","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/4.0.0/node@v14.15.4+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"14.15.4","dependencies":{"terser":"5.5.1","postcss":"8.2.13","domutils":"2.5.0","lru-cache":"6.0.0","domhandler":"4.0.0","cross-fetch":"3.1.2","htmlparser2":"6.0.1","cssnano-simple":"2.0.0","dom-serializer":"1.2.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"2.8.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"2.8.0","@ampproject/toolbox-runtime-version":"2.8.0","@ampproject/toolbox-validator-rules":"2.8.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.2_1625240306139_0.8272694104264764","host":"s3://npm-registry-packages"}},"2.8.3":{"name":"@ampproject/toolbox-optimizer","version":"2.8.3","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.3","maintainers":[{"name":"esth","email":"esther@amp.dev"},{"name":"ampprojectbot","email":"bot@amp.dev"},{"name":"kdwan","email":"kdwan@google.com"},{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"4d87a8abe92bd9603f6cdab56838017fe3e3ecf7","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.3.tgz","fileCount":50,"integrity":"sha512-kcs2vT1bY5hvgLfCn7xzlxZzYc/as2r7cy7YlPpxWSO3kcZQnO25HAQZO5+G+a+RKsd++rb4Ga9GhhZyIthfxQ==","signatures":[{"sig":"MEQCICoo8wiPjE5C/byNX/4XAeCPqo81jD1qap+i1YLvgB+BAiB+o/KtMK7GZ+2bgsBPxDKUMMqaNgTbmgRxEvuVnTqaSA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":251454,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJg7Ah3CRA9TVsSAnZWagAAq5EP/j7nXGaUOw6nhFL3oEER\n3AcFYXMQEUACRHPsEQewHEegt5BkfBqjr5DGMDJrpFT0NGjSQuGelOBlwPIv\nUBehRU+nEQZ0GIRT7y/9/Zh98Ils1Dg4WNYjlTBS8/KYIekNCRXUh9lrVxA5\nDvCNvwSpGcMH7psfWcSL1CHUw+UDICjVIfywJBNDv5wXSozMhM7wgEiGwvbe\nXzB6/orKoWpZls+vURxYs15da2Lm6zkxa4k5VILFPk/WUlmgoDr5QT7rJtEf\nquqXGe7LgparXLXZXytSJQxUR1oaW6p6/USjybvroOSkhrivCHqRllJTHPvy\nKq7NvfzJ2NZZg65vQUaC3Y8GrCNO75CJsw3n2gJ+zEhhrit8/IPacCHG0sQt\nw9KSWAI2J+E3k7V37Hx5UYIB/B5LO5BUKsSQxx9HesJZM0JXxUG/ZF5c5xKQ\nEcqEj1Me0rKd+CDQ/Qo5x+49t+wJbdQ+8D45QHP3kECgkCwBSRpcieRugot3\nZdPOSrPQawKI/OQRtDnJnLiifc1VhPez9YXAswljh+f44TtViOycrEq0Ixpp\ndLV8eoHDC4AZFXHltNtOsINMJrLWw6WO3JE/DV1weTfUzhzIj/I+nsbzKudq\nMW2mWIku+BObXuDAjP02jYrmVG7DYhMEKaYm0nUSwlzBiFJCtzxrT1TT+I2X\nE62r\r\n=/5Ks\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"bad2f34fdab3e00d3310f2a86fd5a1ca86caf465","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/4.0.0/node@v14.17.3+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"14.17.3","dependencies":{"terser":"5.5.1","postcss":"8.2.13","domutils":"2.5.0","lru-cache":"6.0.0","domhandler":"4.0.0","cross-fetch":"3.1.2","htmlparser2":"6.0.1","cssnano-simple":"2.0.0","dom-serializer":"1.2.0","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"2.8.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"2.8.0","@ampproject/toolbox-runtime-version":"2.8.0","@ampproject/toolbox-validator-rules":"2.8.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.3_1626081399012_0.2867607427516585","host":"s3://npm-registry-packages"}},"2.8.4":{"name":"@ampproject/toolbox-optimizer","version":"2.8.4","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.4","maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"},{"name":"kdwan","email":"kdwan@google.com"},{"name":"ampprojectbot","email":"bot@amp.dev"},{"name":"esth","email":"esther@amp.dev"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"0fcf836a4c33fc41162db8008e1e3e201a6e7352","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.4.tgz","fileCount":50,"integrity":"sha512-TuyN3hMEGu1S3MTUuKSteXgEIKxUyJKL+aFjZ7HmBRsTIaYyIMFuGosIwz5qSceg3Rk+9ROnMKuBYAwQWItyMA==","signatures":[{"sig":"MEQCIC4DeJI8k1ONo38wixperoaTBOyvonEpJ8siTseUrnLjAiAgEc+mXJJyO90zowYEPhJDk+XX9Ngq4wtgYQLRUYuY5g==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":251475,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJhlR89CRA9TVsSAnZWagAADo8P/06rXvQuZQb6QOwb7VTR\n1zilAfy7yPvN8gZcgxmP/G3mXV5x9R1i3BnfWrIJZL8kwOPrNKuKEgf6h5aK\nZYmL15LxNh/lFtKWKg1VZ3fG8W+KDkUuFZbu4d2XEEZLgJwcbr9VFPWTOQK5\nd0YPIlyUaUYSD825a4WJb02m1PyWOLxIxoFLd31GU+i+IsvuNepfeg3YigFO\nUTtie4/htx+g/6MfSn8deXQOx/4BYoaQ/83SFtGg8Kb294PGCNBuK6lLEqLn\nyhOSrf84OpuTFVYbehDW26mQqTruRlWHf6j0Z803VU+3IWN3o58HedaixlgE\nHPnpp0zJq9wMX/gHSjspjDHjTDMngQ7lg5bozLhRYnnFjczaVSmph3T+USiQ\nwhH3lgL4HgPkeNoY9t0XQBfXcRgsPbMOpT+EQ+zjLtQHNTMt+irwr81mpeIH\nGh3kFOni/zNtby8dT3ZY1yGNwzsBFTeDHZbdV0EXUEZ1/Vxb8JG7vqi8moJx\nKNFywWMDxELMqTiCfDgYU1TLqh08UvN4v05I62VkyIQU86W3mSv/McLxoDFo\nuas+Mk+TQgJq/7v0HjorqjE9I4Lyo8frFcOIk9ro5bR5r75kxW6cEwP/3DjD\n/9UcDS4RXkp1uqcykAQbkb/e2uJyCAVyeACzK/7U7KJkOyru072gLTlBp4hS\nTKy8\r\n=i59j\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"5dfab2df0c550698f03d7f45634cf4079f02c66e","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/4.0.0/node@v14.17.3+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"14.17.3","dependencies":{"terser":"5.5.1","postcss":"8.2.13","domutils":"2.8.0","lru-cache":"6.0.0","domhandler":"4.2.2","cross-fetch":"3.1.2","htmlparser2":"7.2.0","cssnano-simple":"2.0.0","dom-serializer":"1.3.2","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"2.8.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"2.8.0","@ampproject/toolbox-runtime-version":"2.8.0","@ampproject/toolbox-validator-rules":"2.8.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.4_1637162813030_0.6469299859119442","host":"s3://npm-registry-packages"}},"3.0.0-canary.2":{"name":"@ampproject/toolbox-optimizer","version":"3.0.0-canary.2","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@3.0.0-canary.2","maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"},{"name":"kdwan","email":"kdwan@google.com"},{"name":"ampprojectbot","email":"bot@amp.dev"},{"name":"esth","email":"esther@amp.dev"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"3a3f7bcd1ce0b81b80e5322c98362a28ee125a72","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-3.0.0-canary.2.tgz","fileCount":51,"integrity":"sha512-SbGs6shQEyDPZoDL4PPle8YB3WiF3Y0mTTPSaHBlVMrdjZcFIZCEkhN8w7kRCUNzSVtR448B134eLw/5Z0hOkw==","signatures":[{"sig":"MEQCIBaz58vDq1Ayf5N0FkXqGUi2FaLw/BO/bLjzMaQL39YcAiBzura7sPHFESENeYiXjIxF8rC5hZm2tobUb6yhMJ7VSg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":264627,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh3eqcCRA9TVsSAnZWagAAzoAP/i78n2CVEUwTKvlCS4pl\npgfY879TpOTHzymNeWrRrVL+YlDKcd2OSj7VxzcIjPN2Ohvb4gJT9AJAL4FA\nW0BSrBihZxfyBnFFvRQkOCuVqde4aTLPfi3xi46zIax15muJKq3TRYl6e6/g\nCcHTgeOHBfARVcj/dbWnkwDnGl+mEeVkEI/wmXPjoxwkNo0KJlG61PH74sXI\n7hqlfEPPv1y0diHoaBZlhrxKHcd6ydw6AcnJW+GzV6m0wRJ2G9GocTx49elN\nIT0GalJQtCJFo5nhHLeZvkyyNbEcDInKpNEvfb5pfCHhPOqya/BKYO8cF9Bw\ns7iExZ/B0ATE6VHGWUkpOPztaoakDQn5KXsotF2D1DYNzMbZMqOifB0CMVAD\nkfDof39yNkSXkm5SOtXmbRpJBXpJKx34QNoRWt0YrgvskEPqsMvGTd291PfQ\npBhO8Nq9QlUtBn/Bzig5p4/7qJpFf7SLPrQjiqRnr7FyJzC5GoZRomQPTgwe\n90POmRfYgilqE7PFLyTxofSl2op+vrwIYfqOM4Fu5s+hE40IAfTMVX+ZUUsP\nCboUkK/LIX7N3iph0K0uMgDa/rYZo+di48QQy/Pejhr1zm1zoHvMlQgfYyJ7\nH9sfiPr6s7AkU3RjjZk70IR1y9TPQpVrWKvkv/I2cmGWKstCe6JUh1Z4bC6t\n51o/\r\n=tRpN\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to simplify creating AMP pages and improve AMP rendering performance. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n- [Server-side render AMP layouts](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/).\n- **Automatically import all missing AMP component scripts**.\n- **Automatically add any missing mandatory AMP tags**.\n- Auto detects and preloads hero images from amp-img, amp-iframe, amp-video, or amp-video-iframe.\n- Remove the AMP boilerplate (when possible).\n- Remove not needed whitespace.\n- Extract and move CSS keyframe animations to the bottom of the page.\n- Optimize AMP framework and custom font loading\n- Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n- AMP Optimizer will produce valid AMP.\n- AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\nAMP Optimizer provides two different default modes:\n\n1. **Fast:** this will perform only critical optimizations minimizing bundle install size and execution time. Use this when running AMP Optimizer in your critical rendering path, for example, when running AMP Optimizer in your server.\n2. **Full:** this will perform all available optimizations, resulting in the best AMP performance. This mode offers a better AMP developer experience as missing AMP specific markup and imports are added automatically. This makes it a great choice when integrating it into a static site generator. **Important:** use only when running AMP Optimizer does not negatively affect the rendering time of your page, for example, when building a static site.\n\nPlease note: both modes just provide defaults and can be individually configured via these [options](#options). The main differences between these two modes are listed here:\n\n| Option                | Fast    | Full   |\n|-----------------------|---------|--------|\n| autoAddMandatoryTags  | `true` | `true` |\n| autoExtensionImport   | `false` | `true` |\n| markdown              | `false` | `true` |\n| minify                | `false` | `true` |\n| separateKeyframes     | `false` | `true` |\n\n\n### Using `fast` mode\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer \n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.createFastOptimizer();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>`;\n\nampOptimizer.transformHtml(originalHtml).then((optimizedHtml) => {\n  console.log(optimizedHtml);\n});\n```\n\nIt's also possible to add additional options, for example, to enable Markdown mode:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.createFastOptimizer({\n  autoExtensionImport: true,\n  imageBasePath: '../img',\n  markdown: true\n});\n\nconst originalHtml = `\n  <img src=\"img.jpg\"/>\n`;\n\nampOptimizer.transformHtml(originalHtml).then((optimizedHtml) => {\n  console.log(optimizedHtml);\n});\n```\n\n### Using `full` mode\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer terser postcss postcss-safe-parser cssnano-simple\n```\n\nNote: `full` mode requires additional dependencies for all features, such as CSS minification, to work. AMP Optimizer will skip transformations if dependencies are missing. Check the log output for debugging.\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.createFullOptimizer({\n  // optional parameters\n});\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>`;\n\nampOptimizer.transformHtml(originalHtml).then((optimizedHtml) => {\n  console.log(optimizedHtml);\n});\n```\n\n### CLI\n\nAMP Optimizer can be used via the [AMP Toolbox CLI](/packages/cli/README.md):\n\n```shell\nnpm install @ampproject/toolbox-cli -g\namp optimize myFile.html\n```\n\nor run without installation via `npx`:\n\n```shell\nnpx @ampproject/toolbox-cli optimize myFile.html\n```\n\n### Options\n\nAvailable options are:\n\n- [autoAddMandatoryTags](#autoaddmandatorytags)\n- [autoExtensionImport](#autoextensionimport)\n- [cache](#cache)\n- [esmModulesEnabled](#esmmodulesenabled)\n- [extensionVersions](#extensionversions)\n- [fetch](#fetch)\n- [format](#format)\n- [imageBasePath](#imagebasePath)\n- [imageOptimizer](#imageoptimizer)\n- [lts](#lts)\n- [markdown](#markdown)\n- [minify](#minify)\n- [optimizeAmpBind](#optimizeampbind)\n- [optimizeAmpStory](#optimizeampstory)\n- [optimizeHeroImages](#optimizeheroimages)\n- [preloadHeroImage](#preloadheroimage)\n- [separateKeyframes](#separateKeyframes)\n- [verbose](#verbose)\n\n#### `autoAddMandatoryTags`\n\nAutomatically inject any missing markup required by AMP.\n\n- name: `autoAddMandatoryTags`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `autoExtensionImport`\n\nAutomatically import any missing AMP Extensions (e.g. amp-carousel). This is not recommended if AMP Optimizer is run in the critical serving path.\n\n- name: `autoExtensionImport`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `cache`\n\nSpecifies the cache implementation to use for caching artifacts required during transformation (runtime CSS, validation rules, latest runtime version,...). A cache needs to conform to the following interface:\n\n```typescript\ninterface Cache {\n  set(key: Object, value: Object?): Promise<void>;\n  get(key: Object): Promise<Object?>;\n}\n```\n\nBy default, artifacts will be cached in a temporary directory. If there is no filesystem access available, the cache will fallback to an in memory implementation.\n\n- name: `cache`\n- valid options: `[Cache|false]` (`false` will disable caching)\n- default: [cache.js](lib/cache.js)\n\n#### `esmModulesEnabled`\n\nEnables the smaller ESM module version of AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n**Warning: This is incompatible with AMP Packager unless using a prerelease version containing [this commit](https://github.com/ampproject/amppackager/commit/92387ecf97e420eec10b378282383c59f95ab45f).**\n\n- name: `esmModulesEnabled`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `extensionVersions`\n\nSpecify version numbers to use for automatically imported Extensions. If not defined, default to latest.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  extensionVersions: {\n    'amp-twitter': '0.1',\n  },\n});\n```\n\n- name: `extensionVersions`\n- valid options: `OBJECT`\n- default: `{}`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `fetch`\n\nSpecifies the [fetch](https://fetch.spec.whatwg.org) implementation to use:\n\n- name: `fetch`\n- default: `node-fetch`\n\n#### `format`\n\nSpecifies the AMP format of the input file. Defaults to `AMP`.\n\n- name: `format`\n- valid options: `[AMP|AMP4EMAIL|AMP4ADS]`\n- default: `AMP`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js), [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `imageBasePath`\n\nSpecifies a base path used to resolve an image during build,\nthis can be a file system path or URL prefix. You can also pass a function\n`(imgSrc, params) => '../img/' + imgSrc` for dynamically calculating the image path.\n\n- name: `imageBasePath`\n- valid options: `STRING|FUNCTION`\n- default: undefined\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `imageOptimizer`\n\nEnable automated image `srcset` generation by providing a function for calculating `srcset` URLs for a given image `src`. The function should return a URL pointing to a version of the `src` image with the given `width`. If no image is available, it should return a falsy value.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  imageOptimizer: (src, width) => `${src}?width=${width}`,\n});\n```\n\n- name: `imageOptimizer`\n- valid options: `FUNCTION`\n- default: undefined\n- used by: [OptimizeImages](lib/transformers/OptimizeImages.js)\n\n#### `lts`\n\nUse [long-term stable URLs](https://github.com/ampproject/amphtml/blob/main/contributing/lts-release.md) for downloading the AMP runtime and components.\n\n- name: `lts`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `markdown`\n\nThis transformer adds out-of-the-box markdown support. This allows\nusing AMP Optimizer to convert HTML documents created from Markdown\nfiles into valid AMP. A typical conversion flow would be:\n\nREADME.md => HTML => AMP Optimizer => valid AMP\n\nThe only thing this transformer does is converting `<img>` tags into\neither `amp-img` or `amp-anim` tags. All other Markdown features are\nalready supported by AMP. The transformer will try to resolve image\ndimensions from the actual files. Images larger than 320px will automatically\nget an intrinsic layout. For image detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n- name: `markdown`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `minify`\n\nMinifies the generated HTML output. Requires [`terser`](https://www.npmjs.com/package/terser) to be installed when inlined `amp-script` should be minified. Combine with [separateKeyframes](#separateKeyframes) to also minify CSS.\n\n- name: `minify`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [MinifyHtml](lib/transformers/MinifyHtml.js), [SeparateKeyframes](lib/transformers/SeparateKeyframes.js)\n- additional dependency (when using amp-script): `npm install terser`\n\n#### `optimizeAmpBind`\n\nEnables a considerably faster scanning method in `amp-bind`, by injecting a `i-amphtml-binding` attribute on all elements with a bound attribute.\n\n- name: `optimizeAmpBind`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeAmpBind](lib/transformers/OptimizeAmpBind.js)\n\n### `optimizeAmpStory`\n\nEnables AMP Story optimizations such as linking to the amp-story-1.0.css, and server side rendering\nattributes.\n\n- name: `optimizeAmpStory`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [AmpStoryCssTransformer](lib/transformers/AmpStoryCssTransformer.js)\n\n#### `optimizeHeroImages`\n\nEnables hero image optimization. Hero images will either be auto detected or you can explicitly mark these by adding the `data-hero` attribute:\n\n```\n<amp-img data-hero src=\"foo.jpg\" ...>\n```\n\nThe maximum number of hero images that can be marked up using `data-hero` is `2`. This number can be increased using the `maxHeroImageCount` parameter.\n\nHero images are optimized by server-side rendering the `img` element inside the `amp-img` element. This can siginificantly improve image rendering performance and reduce the [largest contentful paint](https://web.dev/lcp/) (LCP) metric from [Core Web Vitals](https://web.dev/vitals/).\n\n- name: `optimizeHeroImages`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeHeroImages](lib/transformers/OptimizeHeroImages.js)\n\n#### `preloadHeroImage`\n\nDeprecated, use [optimizeHeroImages](#optimizeheroimages) instead. \n\n#### `separateKeyframes`\n\nAutomatically move keyframe animations to a separate `amp-keyframes` style block at the end of the document.  Putting keyframes declarations at the bottom of a document allows them to exceed size limitations and avoids blocking first contentful paint to parse them (see [documentation](https://amp.dev/documentation/guides-and-tutorials/learn/spec/amphtml/?format=ads#keyframes-stylesheet)).\n\n- name: `separateKeyframes`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [SeparateKeyframes](lib/transformers/SeparateKeyframes.js)\n- additional dependencies: `npm install postcss postcss-safe-parser cssnano-simple`\n\n#### `verbose`\n\nEnable verbose mode with more detailed logging output.\n\n- name: `verbose`\n- valid options: `[true|false]`\n- default: `false`\n\n## Features\n\n### Image optimization\n\nAMP Optimizer helps you serve optimized images. For this to work, you need to provide a function that maps an image `src` to a resized `srcset` source value. The image resizing needs to either happen at build time (e.g. for static sites) or via a image hosting service such as [thumbor](https://github.com/thumbor/thumbor).\n\nHere is an example implementation that appends the image width to the `src`:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  // parameters are the amp-img `src` and the `width` of the to be generated srcset source value\n  imageOptimizer: (src, width) => {\n    // we cannot rename if the image does not have a file extension\n    const index = src.lastIndexOf('.');\n    if (index === -1) {\n      // return null means we won't generate a srcset source value for this width\n      return null;\n    }\n    const prefix = src.substring(0, index);\n    const postfix = src.substring(index, src.length);\n    return `${prefix}.${width}w${postfix}`;\n  };\n})\n```\n\nUsing this implementation, AMP Optimizer will transform the following `amp-img` declarations:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img src=\"image1.png\" width=\"400\" height=\"800\" layout=\"responsive\"></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                             image-2x.png 2x\"\n></amp-img>\n```\n\ninto:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img\n  src=\"image1.png\"\n  width=\"400\"\n  height=\"800\"\n  layout=\"responsive\"\n  srcset=\"image1.470w.png 470w, image1.820w.png 820w, image1.1440w.png 1440w\"\n></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                               image-2x.png 2x\"\n></amp-img>\n```\n\n**Important** when using `layout=responsive` use the `width` and `height` attribute to specify the minimum image dimensions. For example, for a full-bleed hero image on mobile, specify the width as`width=320`.\n\n### Auto add incomplete markup\n\nIt's possible to pass incomplete documents and AMP Optimizer will add any\nmissing tags and extension imports required by a valid AMP document.\n\n```js\nconst originalHtml = `\n  <h1>Hello World!</h1>\n  <amp-twitter width=\"375\"\n               height=\"472\"\n               layout=\"responsive\"\n               data-tweetid=\"1182321926473162752\">\n  </amp-twitter>\n`;\n\n// you can pass the canonical URL, default is `.`\nconst opts = {\n  canonical: '/example.html',\n};\nampOptimizer.transformHtml(originalHtml, opts).then((optimizedHtml) => {\n  // optimizedHtml will be a valid AMP document\n  console.log(optimizedHtml);\n});\n```\n\n### Automated Markdown conversion\n\nAMP Optimizer supports converting Markdown to AMPHTML. A typical conversion flow would be:\n\n```\nREADME.md => HTML => AMP Optimizer => valid AMP\n```\n\nThe AMP Optimizer converts `<img>` tags into `<amp-img>` or `<amp-anim>` tags when in Markdown mode. Enable Markdown mode via `markdown : true`. AMP Optimizer will try to resolve image dimensions from the actual files. Images wider than 320px will automatically get an `intrinsic` layout.\n\nAll other Markdown features are already supported by AMP.\n\nYou can pass an additional option `imageBasePath` to specify a base path used to resolve an image during build, this can be a file system path or URL prefix.\n\n**Important:** for image size detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n```shell\nnpm install probe-image-size --save-dev\n```\n\nExample:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst md = require('markdown-it')({\n  // don't sanitize html if you want to support AMP components in Markdown\n  html: true,\n});\n\n// enable markdown mode\nconst ampOptimizer = AmpOptimizer.create({\n  markdown: true,\n});\n\nconst markdown = `\n# Markdown 🤯\n\nHere is an image declared in Markdown syntax:\n\n![A random image](https://unsplash.it/800/600).\n\nYou can directly declare AMP components:\n\n<amp-twitter width=\"375\"\n             height=\"472\"\n             layout=\"responsive\"\n             data-tweetid=\"1182321926473162752\">\n</amp-twitter>\n\nAny missing extensions will be automatically imported.\n`;\n\nconst html = md.render(markdown);\n\nconst amphtml = await ampOptimizer.transformHtml(html, {\n  canonical: filePath,\n});\n```\n\nYou can find a working sample [here](/packages/optimizer/demo/markdown).\n\n## Extending AMP Optimizer with custom transformations\n\nAMP Optimizer supports custom HTML transformations:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst {createElement, firstChildByTag, appendChild} = AmpOptimizer.NodeUtils;\n\nclass CustomTransformer {\n  constructor(config) {\n    this.log_ = config.log.tag('CUSTOM');\n  }\n  transform(tree, params) {\n    this.log_.info('Running custom transformation for ', params.filePath);\n    const html = firstChildByTag(tree, 'html');\n    if (!html) return;\n    const head = firstChildByTag(html, 'head');\n    if (!head) return;\n    const desc = createElement('meta', {\n      name: 'description',\n      content: 'this is just a demo',\n    });\n    appendChild(head, desc);\n  }\n}\n\n// it's best to run custom transformers first\nconst customTransformations = [CustomTransformer, ...AmpOptimizer.TRANSFORMATIONS_AMP_FIRST];\n\n// pass custom transformers when creating the optimizer\nconst optimizer = AmpOptimizer.create({\n  transformations: customTransformations,\n});\n// you can add custom parameters on a per document basis\nconst transformedHtml = await optimizer.transformHtml(html, {\n  filePath,\n});\n```\n\nCheckout [the samples](/packages/optimizer/demo/simple/index.js) to learn how to customize AMP Optimizer.\n\n## Best Practices\n\n### Make sure to enable AMP Boilerplate removal\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n- if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```js\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true,\n});\n```\n\n... or for individual pages:\n\n```js\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true,\n});\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length).replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](/packages/runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion,\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n- `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n- `imageBasePath`: specifies a base path used to resolve an image during build.\n- `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n- `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp',\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Fallback API for amp-geo\n\nIdeally, when self-hosting the AMP framework, `amp-geo-0.1.js` should be patched at delivery time to replace `{{AMP_ISO_COUNTRY_HOTPATCH}}` with the ISO 3166-1 alpha-2 country code where the request originated ([reference](https://github.com/ampproject/amphtml/blob/main/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem)). If your host does not have this capability, you can instead rely on a web API to return the country at runtime. The web API must be secure (HTTPS), adhere to [AMP CORS guidelines](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests/), and return JSON in the following format:\n\n```js\n{\"country\": \"de\"}\n```\n\nwhere in this example, `de` is the ISO 3166-1 alpha-2 country code for Germany.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  // this will instruct amp-geo to fetch the user's country from an API\n  // which returns JSON in format: {\"country\": \"de\"}\n  geoApiUrl: 'https://example.com/geo',\n});\n\nconsole.log(optimizedHtml);\n```\n\n## Development & Testing\n\nAMP Optimizer uses a snapshot based testing approach. To execute the tests, run in the project root:\n\n```shell\nnpm run test:node\n```\n\nTransformer tests are located in:\n\n```\n- spec/transformers/valid/TransformerName/test-name/\n    expected_output.html\n    input.html\n```\n\nThe transformation input is defined in `input.html`, whereas `expected_output.html` contains the expected\noutcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing\na transformer implementation, run:\n\n```shell\nnpm run test:optimizer:snapshot\n```\n\nto store a new snapshot version in `expected_output.html`.\n","gitHead":"0f63a77e8081f0fb7d6b63280e2fd8701836c4f9","scripts":{"build":"node build/downloadRuntimeParameters.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/4.0.0/node@v16.13.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"16.13.1","dependencies":{"domutils":"2.7.0","lru-cache":"6.0.0","domhandler":"4.2.0","node-fetch":"2.6.1","htmlparser2":"6.1.0","dom-serializer":"1.3.2","domelementtype":"2.2.0","https-proxy-agent":"5.0.0","@ampproject/toolbox-core":"3.0.0-canary.2","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"2.8.0","@ampproject/toolbox-runtime-version":"3.0.0-canary.2","@ampproject/toolbox-validator-rules":"3.0.0-canary.2"},"_hasShrinkwrap":false,"readmeFilename":"README.md","peerDependenciesMeta":{"jimp":{"optional":true},"terser":{"optional":true},"postcss":{"optional":true},"cssnano-simple":{"optional":true},"probe-image-size":{"optional":true},"postcss-safe-parser":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_3.0.0-canary.2_1641933468612_0.970000872804085","host":"s3://npm-registry-packages"}},"2.8.5":{"name":"@ampproject/toolbox-optimizer","version":"2.8.5","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.5","maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"},{"name":"kdwan","email":"kdwan@google.com"},{"name":"ampprojectbot","email":"bot@amp.dev"},{"name":"esth","email":"esther@amp.dev"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"f6109f72fb6bbb203679045862684dddbb5a0c7f","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.5.tgz","fileCount":50,"integrity":"sha512-Sq/bEkyVkYtmVS3GqH2lRqRCs2iHYwlr7LxKmJ4530kJz7c1El6z/HI59jA3PS+HxPQQo64aSCQ0Nb2OqPOqSQ==","signatures":[{"sig":"MEUCIDvliUjzA7fESeoHlOkiP0xMCgwUBB9d5Lgfrb4K/M8KAiEAoe99SDF8AesEzZU9+3pLM4KVmqaiHFQZUX67HGfxOAE=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":254015,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh30V/CRA9TVsSAnZWagAAba4P/jLagFtAyaySymMn/H3q\nqY13wDc215SdXWcmqt7w7sLRLS7iv+YonV0v20TwtaP34vK3iWWe0bhU9U3+\ngHQOTxgJTXd3lkyNt2FNZQwT0CPcLvIOe1sABO/V2KNl3nfF/6STJe4uEiiZ\n23QAOrs1psHTiGgvF4jw96L3v2xKVU06WwDIl6MlHgGSruyV1DFiGq6ZU8Bd\nxlbneEop+dTEqjHL0Rg4YwmH87EvfWB9oowlZQQlNZw0MSbQ3+USFvaqhnm4\nersDdOP6Ow5uE/KcwgYFHkNXekDG/Xm93Zn2Bvb+GNYBIYENgSon4Dq8jgsw\nx/G9nZLYoOsYstw9w3hxeY96+DqXAmi890SZNBMXJK0Cig0QsCbd/anyFlo3\nG72RnGzdhIoUWgyVAY7YZscDv5+5lfBpKjEuCAlQhnP6mVbhqs5AyPBChekL\nK0CxUevz5Evk5c0FPSU3RP8ikl3U/LKNBpKlzHYXDtHfUHww27IrI9ZEIB6s\nK+/uLjbskXpsp2WmL1iXRs9tTikasF8QEyZLIwxKLUNR7u+FV38Vbulab8dl\nBoTh2gaoMIqQlGwDtcVOdEuN/XFO0a2EkbF8jLAOfM4hLZadMb2nWNiFmJII\noWH9boVdlN1InJ/AEBH1ATvR+Dl8pqmenDMQQpUWH/Ym3U8WZDBcI/RrzZtI\nxH8m\r\n=4BC+\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"5be59bc968c6b5cc3cb1e6e5e6a1b19d36787170","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/4.0.0/node@v16.13.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"16.13.1","dependencies":{"terser":"5.5.1","postcss":"8.2.13","domutils":"2.8.0","lru-cache":"6.0.0","domhandler":"4.2.2","cross-fetch":"3.1.2","htmlparser2":"7.2.0","cssnano-simple":"2.0.0","dom-serializer":"1.3.2","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"2.8.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"2.8.0","@ampproject/toolbox-runtime-version":"2.8.0","@ampproject/toolbox-validator-rules":"2.8.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.5_1642022271707_0.42773874428543746","host":"s3://npm-registry-packages"}},"2.8.6":{"name":"@ampproject/toolbox-optimizer","version":"2.8.6","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.6","maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"},{"name":"kdwan","email":"kdwan@google.com"},{"name":"ampprojectbot","email":"bot@amp.dev"},{"name":"esth","email":"esther@amp.dev"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"d9d1cb879d109ad767d49eb341fcd9fd239d1b83","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.6.tgz","fileCount":50,"integrity":"sha512-qWTQ9Ws/rCBZc10zIE7aPfs6k8BMrmBq3tv+Nb9OU8NYPZZCITYd3nqM5N+MH7Uw3CL+c/o74oYnmDCQ/290Jg==","signatures":[{"sig":"MEYCIQDLOuKqNiYlegL9lrakYcnCCgJQi8mk1dIsY0XpO3/4sgIhAK1HtJb9khmUnuYOXbvQm4V9gbmJe1ch3nsLHheQpTE/","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":254038,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh3052CRA9TVsSAnZWagAAOEQP/R5IP9khALyZ7HSWTnRW\nbJkJvnuU+nd+wW2ZZHtdIkMy84cji+dfj/w93uyifXH4i+8WzRABmHdyPcWb\nxtaU+Et4r094U6kZoW6lNKM//3s/19aIjC6djMVCPdiWVxUvSVrgL09syyEq\nP7sFxy9sWMpkrZf5IAXkoc0P2L7M/KfsvPAbfOlYgpnLYUAJDzbH6cfFpTzn\nCaXpAvn8Ws5EJesInc6JjtS3RL/+dvlrjc7Om8L7+uDUPcj19hsrRYBVz6DE\nX/j+clgQPBdkiuQeeAX+jwaS4gE498UKW0tpMldq2w1Qm+/QM7ZNAu021F+B\nD8CijVfHQv3egKU2MHG91h1DQbIofCplkJNy0DemkzkaGMzUgm7Pv2I+9yK+\nAOyGOaesoJb1gjYnktW/wIekXkaqMvicKGV9zeVSdBsw8ywFTjxjeAIw46kL\nAMLvvLPabUAn6vfx3iOc3pZlTByFUF7oTKhrC9v+03zumSYZc/4Hd1TSRMpa\nI/PAoqFV8x1lwOJHCJGv+9P7KbMxaQ1Py9crDANmj7qUO6bts5rrqLUwPtat\nMHtjh9R7hZtXzXWsFQxcPdLZRvMwbHsq5qSjIqaI8FW9hneS4fV1DMqtehM6\nryT4uUD9gMVuCcRenQe+feYkXlKu48wG3lU3U8+4KYoMLXjESqRxMwGsnjOG\nwU3E\r\n=XqB/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"9e537758baa2424a96d920308df75eb24b6e3dec","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/4.0.0/node@v16.13.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"16.13.1","dependencies":{"json5":"^2.2.0","terser":"5.5.1","postcss":"8.2.13","domutils":"2.8.0","lru-cache":"6.0.0","domhandler":"4.2.2","cross-fetch":"3.1.2","htmlparser2":"7.2.0","cssnano-simple":"2.0.0","dom-serializer":"1.3.2","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"2.8.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"2.8.0","@ampproject/toolbox-runtime-version":"2.8.0","@ampproject/toolbox-validator-rules":"2.8.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.6_1642024566032_0.0832671185104834","host":"s3://npm-registry-packages"}},"2.8.7":{"name":"@ampproject/toolbox-optimizer","version":"2.8.7","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.7","maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"},{"name":"kdwan","email":"kdwan@google.com"},{"name":"ampprojectbot","email":"bot@amp.dev"},{"name":"esth","email":"esther@amp.dev"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"2e9ee8b3ea8c18e35348657d60ba093f13faa423","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.7.tgz","fileCount":50,"integrity":"sha512-t8C2sQHgzuGB+ixot1fBw5mXVDUKzDTSHFu5eC4vGJbY/bKNrJ058QuV72o6/4pSq8/T/o0EoYWQQqHlC1Ylvg==","signatures":[{"sig":"MEUCIBhWVrx1biFbxYxbXrk6EibWvf5O1mDsRMNCv0GjkDGPAiEAo8QgxenAx3dgSbWgjGUfhReUwwFuDulj0CX9QP7h/yk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":253138,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJh4aBDCRA9TVsSAnZWagAAf6YP/3SZxZvE8fkzj+BGwA38\nw7GJ/V22GHKlJITnu9Fvn8CCQromsM58bLyhCA9WvydTpsCz7fvQgq7Pgbc0\neoO7jtxH4LNUGljWEgCRPLtiZHocqnvvGB9wPFGG4zOjxQjdjNz3R8ZkPYwg\nl4wHhjKZef29m+F/iDLCOCljT8OoE7joVt7AZHCBeRNU9hYBLLHbd6rCb40X\nPpu/iNGT1iadjv/v5Iq+RhOzdD6ibUQDwH6xVlYNoB4vw7vJeKj40JQHRk7J\nZaDCXvPLDmuOsmfJB0DCsorWiXDC45Q7zHj7hzA+ZZY/TnY8S0TZYX579ZpA\n6bABouHxVVe6iSv8fzcrDcwz7LSQz5RyjdwaZgZGJYvABu9wWpELjHViJlMY\nt2/J3ytm4Lyy+manZohULzy//D4gZ3HoHFO2MRxumjKBcC5A+Agt35pNrdk+\n3VNPfXe9pCsd9FnAFnZNF7itfvVg24WAAUqQmY5QRdGbI36R/4khdDJ+BW5v\nJou8op/QiUe4b7dSsmT2hGPFlnfPz79NxZnzWGC5Nv4nh26FfHg2gcgscd2y\nO+hXYBp+iSz+t+qe1vq/iANT/2MTpb8isAb30Dq6WzeQ3CWWLRwALEvZ/tHw\n23TQOiR27r2mYr/uU68Mc6TuA4OYEMdhx6kRXktjAerckwj0NqWKDUplS3KA\nrsc/\r\n=/JZu\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"7c5297fe6a977432eac8b2f606e43abf6550aec3","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/4.0.0/node@v16.13.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"16.13.1","dependencies":{"terser":"5.5.1","postcss":"8.2.13","domutils":"2.8.0","lru-cache":"6.0.0","domhandler":"4.2.2","cross-fetch":"3.1.2","htmlparser2":"7.2.0","cssnano-simple":"2.0.0","dom-serializer":"1.3.2","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"2.8.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"2.8.0","@ampproject/toolbox-runtime-version":"2.8.0","@ampproject/toolbox-validator-rules":"2.8.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.7_1642176578950_0.7834882609907585","host":"s3://npm-registry-packages"}},"2.8.8":{"name":"@ampproject/toolbox-optimizer","version":"2.8.8","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.8","maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"},{"name":"kdwan","email":"kdwan@google.com"},{"name":"ampprojectbot","email":"bot@amp.dev"},{"name":"esth","email":"esther@amp.dev"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"4d23f7a19c6753ef7fab9038c686c9c5c840f098","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.8.tgz","fileCount":50,"integrity":"sha512-O/4Pm/N2TRgSZqHcQ+BSNCBlWCfp1WQYLlepYgTlTxqv09C62bPrgsG7BzpG+4rQQrgGOatapb0KRIkzvOxr+g==","signatures":[{"sig":"MEYCIQCyn9d/5bvK00ZIrw+3bHWsXCf+VlEeMFKTfzDyl1B3RAIhAOxJwG6UkMxS4EeUz0timEUfffESWbRGNxSY3C+P92Aj","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":253138,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJiBpRDCRA9TVsSAnZWagAAxWgP/20hXgBDY4ljHe8BbUsV\nJ0k78NWlIkijjStUTMFK4IJui95KEmKXZO6zIGRmqv7n+gNwRiPCBYyxsi0a\njLwtOcp32dfd9/rOsi6NYHEHidcaR6d563y4ysTSGUft1Lag+z+dMZOBaDrF\nXm9HuscjVbsfZLJr8RbfN82hALNkRV8fNgO4bjR/iDqb2IIECJEF3iVCoPkA\n11xM+QsmXW7erBKo/sjoGuOfSHLZbjwaUZR0BEskTUtP8GJWJMEwqb03FZ48\nrJndE7jcZpx2zXAg1XJ3vL0rDoeLNUKWuwVfnsx1bGG72bW9jEFzq9bdEjKE\n1LSxZLnDJR/Xkou8jimaDY5rbDjVFcHihT3lzFV4RqGhb1mzTNEKAkNAJdZV\nMGTYDMDWNsL4gAq+SEJ6dWyqZQt+mFdg8V9KAFnmaMNoA6C1he230PavmXsi\n2iPxU1ZaYaUGILiYXoi8MdPckF2FQ4qrtE2CAw4srOSHXxC6u+ZF6090FpAi\nRTiAjEBpB6aivI4n4XIG8BRDHaJ6HuJOdLS2bJtXGTAE541c6M9MNSIt19Su\n89ADBrLwZ99Ri6v4mGqe8G9+CC/LzD0HDxcCbjTIoIR8yPVZvzJsPYo6ctYA\nmxqbwnQvAHzAKFuD8zSZI1JV/zTLyi5g7kcoemTNdSKNLEJotsWJKg7kyriT\n/3n5\r\n=b6d/\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"a2f119379a740b54d19240a36197de3dc22b333d","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/4.0.0/node@v16.13.1+x64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"16.13.1","dependencies":{"terser":"5.5.1","postcss":"8.2.13","domutils":"2.8.0","lru-cache":"6.0.0","domhandler":"4.2.2","cross-fetch":"3.1.2","htmlparser2":"7.2.0","cssnano-simple":"2.0.0","dom-serializer":"1.3.2","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"2.8.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"2.8.0","@ampproject/toolbox-runtime-version":"2.8.0","@ampproject/toolbox-validator-rules":"2.8.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.8_1644598339327_0.054281395088250806","host":"s3://npm-registry-packages"}},"2.8.9":{"name":"@ampproject/toolbox-optimizer","version":"2.8.9","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.9","maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"},{"name":"kdwan","email":"kdwan@google.com"},{"name":"ampprojectbot","email":"bot@amp.dev"},{"name":"esth","email":"esther@amp.dev"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"f04f87a4c932b9d0898b61e912fddcb94636953d","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.9.tgz","fileCount":50,"integrity":"sha512-KCUSdPxJTuJIj5qCslKR01MWJzKVL4CfoF9sjMEIGY81gMTqqjTHxOVtHrS80bxFdOkghU4F6uTFEk7I6GnAMw==","signatures":[{"sig":"MEYCIQCnxSF1dcfFDe8NK/SxYFskFrmpPrynRNmTWb93wTGOLQIhALe2/6bGe1qam/tQf+BMPF5zC5zSxZPLaC8+4dHAyBtZ","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":253305,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiKmPfACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoSvw/9HWul0qo6byq1hBcwXIWpc0Awo+lWmMfkangzSApWW1tYO/oH\r\nEZHGolkkialR83xUxE9CGmYiGEplVdufC6m2XO+2UO7qp4u66I2/deXOYfci\r\neYcukbygABwWqPn4cb8Vwjc7cyJCGhGroaewM8qOCDUj0/rEarCnxN1urOY+\r\nop89s9CQvicFvtcK4lN24N3uavWmrEj5D0NU/ZfwFHqmb2N7nV4C5iG8nHP3\r\nS1cQiTtyId1EbScbP4WweJDEGRCMvLe3tKtEdR/JcPhkrMVrUp51Xh5tHmKR\r\nZelGjGr7WxyqenpTA/8bvW6W0fDSlcxAmJ/6WrFyd/es2OZDm+8pERdBhNYj\r\nyUzFtddwbg0//Yat88CH6v1PIqiDOc9ULOYU+oGNHd3ZoqWrag/PaReimtaH\r\n9F2eVmW3qkJ+QEPqEtYZVMsso5B6rgQmKYcSxQd/Cx+l5dCC4MI43zAgcDvX\r\nnptZPhEib9kzjT3MfYUme1QNOmiihQQ51Sk3woaUpoRGjHO58Nk2pc6V9bF6\r\n49omHE8kBHL+/LtUx+kY/2MuuyCxxxKpmcgTc83w98XH0lqaN182UxHA+IGw\r\ndWzfoA3IQjoDHNhW6MSNFRL3RUzSwETER8vHk3dY4tZ8a+YjKu3l4oLqjUst\r\nkOTikJYoJQ0UQpK1Kh5PajyR403ewPrMZCk=\r\n=JSCe\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"52fcf9a80314a678b7a16fdab77fff0c6ad06f67","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/4.0.0/node@v17.5.0+arm64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"17.5.0","dependencies":{"terser":"5.5.1","postcss":"8.2.13","domutils":"2.8.0","lru-cache":"6.0.0","domhandler":"4.2.2","cross-fetch":"3.1.2","htmlparser2":"7.2.0","cssnano-simple":"2.0.0","dom-serializer":"1.3.2","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"2.8.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"2.8.0","@ampproject/toolbox-runtime-version":"2.8.0","@ampproject/toolbox-validator-rules":"2.8.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.9_1646945247227_0.9961894792754404","host":"s3://npm-registry-packages"}},"2.8.10":{"name":"@ampproject/toolbox-optimizer","version":"2.8.10","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.8.10","maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"},{"name":"kdwan","email":"kdwan@google.com"},{"name":"ampprojectbot","email":"bot@amp.dev"},{"name":"esth","email":"esther@amp.dev"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"ef276c422be617991cd8cfbc70b56b51c5ffcc33","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.8.10.tgz","fileCount":50,"integrity":"sha512-GuXkLHmGJ3pko6aozHa3msxxahiUJM163eG8LEji/MTfNGf0QJCWSQ46a+yHG3tBzYtLexVHWU7A8QMfRbxWGw==","signatures":[{"sig":"MEUCIQCclaxJhN1UHnGB4L1aJQBdaP9GwKNgGkU+1jELXb9g4QIgFbhDT8p0qHP7xVlbBR5rPquEXd23GEzHewbsRNhEiSM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":253526,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJiMj4PACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmr+QA/+P/awlLBS4OQm4V4/WJQ2mFPj/X9yOmBfnNKukitoNUIpXs4T\r\n10mQlEw0MfP92/c5eh9WxFx8IGBM7v/lczJvYM8K9mmy2ob5D+Wn3t6c2Iid\r\nEaus7PhTTDv3XtzciZt+HEe44Q1gR0/X3K0GGX9AKJa0Z6MubEhmAOi8o9o2\r\nX3BCUwjLej0XZp7c8LJTiZto49Sm/O+dRTx+iwp4RMkNFotgJGowHJNIr33h\r\nHQuaOKIOTgxXo1TonRjmbFvzmwC/qp6zdaDyvXDFpJobh3jX/usz94J95ly6\r\ne8YGAXFl9If/lZCbjoJgoSd6RCiRM7lGjdnHZG6NmFzX7neLInejItC6CXD4\r\nBXRBy0s81e+J07yl04uVsfU1OrOdbzcuDejhTIfH1wbtxVwL/FIRNzfb4pnZ\r\ne3Nls8qFreITgLW0xianhG+IKpSwQL+3f/MdwZIcyQPoI7gNag6Bm5dq22qV\r\nP1v4QD+inPtMayzjwLoQnVH1HISy0247aaUGsonasDdCI0waBWh9mBZ5E+yI\r\n7M8aUpVCNqsNg/8/XtGt69CoSYu4Cjb2hYUOjL7rFFGqUQ2s1F3r/GeqfODb\r\nP8peso4LmKq6ZFSxsN3a4CpDhWADI3g5KPzhbniqG6o4EiRoLmpwIXm0HzXI\r\nnqYkfkjGM/DEVVjeY2A7lRvkY4ZMVezFWIw=\r\n=bT4v\r\n-----END PGP SIGNATURE-----\r\n"},"main":"index.js","gitHead":"0505268585abe293b688bd4de3a0101675189a3b","_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/4.0.0/node@v17.5.0+arm64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"17.5.0","dependencies":{"terser":"5.5.1","postcss":"8.2.13","domutils":"2.8.0","lru-cache":"6.0.0","domhandler":"4.2.2","cross-fetch":"3.1.2","htmlparser2":"7.2.0","cssnano-simple":"2.0.0","dom-serializer":"1.3.2","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"2.8.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"2.8.0","@ampproject/toolbox-runtime-version":"2.8.0","@ampproject/toolbox-validator-rules":"2.8.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.8.10_1647459855175_0.49277082566166874","host":"s3://npm-registry-packages"}},"2.9.0":{"name":"@ampproject/toolbox-optimizer","version":"2.9.0","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.9.0","maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"},{"name":"kdwan","email":"kdwan@google.com"},{"name":"ampprojectbot","email":"bot@amp.dev"},{"name":"esth","email":"esther@amp.dev"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"070dba21c77e43397c559ee4ea38015ce0227f65","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.9.0.tgz","fileCount":51,"integrity":"sha512-KJBMPU61V0i87f4+iJdmer4z9hohP5yoXgPn2sIlCQOnT6u762ZOvhLxwaN0yU6OwPI4/2UypfyNFd5ffREhqg==","signatures":[{"sig":"MEUCIQCOtHXYAe51ZlskAI+Kf+tTodLrnLrsANLAswB8g6dlngIgMceU/S3RLH1hdV4NcxjSeC6xcfJX16JjpxFemWAHSlk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":261392,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJitNGjACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpjDxAAi16uDkc8dvxrdE0wbysFkzeqwj4AamRAue0vaIKSop2XMtvr\r\nJm4msIF/q/h/z2ksyCWsbK0gSItmvveHRn50INuD68SjbXiDgjCR6bNZTwra\r\nGNTQ5J/h4hGEumesEFJIsdTaDxVwKzVHC92Gmk5j51eqCT+E9gpy529PoSgt\r\nFI5AOqXXmTVyJD2sf+WXSjAQWZhDevgsc7lCxfB75jFMCL+vXqfu7F0GwaQp\r\nckjYr4AASX26eiYROklDiDxl4P0e2FHnmsu8C/fkVJET6od2MaBwKfUXj5pN\r\nE4JOmWvacNlZJKJyrRO77mxjLTUCmWIH8azi93GpkprnLksZDsCyutNyd2o4\r\nbuBczuVDys175oI75O9+I4wLugqm6q09z5PXJK7+A09VuFA2zxru+T0Zpwm/\r\nn3SBM7nbdzvMKq+YUxDCKBQ2jdwfMRSyBycx9OimD+q96FF9QWh4jHcwXHsK\r\ng8RsAyRwK0JIjef3MDqLbNepM/bOzW2cMVTM9+ER0WD78Xra6bGe2X1qN/J0\r\n3hkky5MPcEgJQF9lV7zu5DC0V2oh44QQCaZ5iY2+JvYByhgkyTJqJkIG3NzC\r\nYwdnoXDaIRVIIF2xZIqB12dCu5ZFgx1itc35LMV93AaGmTA8wi/86lh08Lo4\r\nU4XWPOI08yiKiylOofluirMKcwm2ryLW4Ps=\r\n=d65l\r\n-----END PGP SIGNATURE-----\r\n"},"jest":{"testTimeout":60000},"main":"index.js","gitHead":"0826825fd8c5f3abac821ebe87a318e70f33c9dd","scripts":{"build":"npm-run-all downloadValidator downloadRuntimeParameters","downloadValidator":"curl https://cdn.ampproject.org/v0/validator_wasm.js --output spec/helpers/validator.js","downloadRuntimeParameters":"node build/downloadRuntimeParameters.js"},"_npmUser":{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"lerna/5.1.4/node@v18.2.0+arm64 (darwin)","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"18.2.0","dependencies":{"terser":"5.5.1","postcss":"8.2.13","domutils":"2.8.0","lru-cache":"6.0.0","domhandler":"4.2.2","cross-fetch":"3.1.5","htmlparser2":"7.2.0","cssnano-simple":"2.0.0","dom-serializer":"1.3.2","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"2.9.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"2.9.0","@ampproject/toolbox-runtime-version":"2.9.0","@ampproject/toolbox-validator-rules":"2.9.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.9.0_1656017315636_0.9920880832800274","host":"s3://npm-registry-packages"}},"2.10.0":{"name":"@ampproject/toolbox-optimizer","version":"2.10.0","keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","_id":"@ampproject/toolbox-optimizer@2.10.0","maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"},{"name":"kdwan","email":"kdwan@google.com"},{"name":"ampprojectbot","email":"bot@amp.dev"},{"name":"esth","email":"esther@amp.dev"}],"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"dist":{"shasum":"f997676048269ee0bc74de9467153e3b41af4f75","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.10.0.tgz","fileCount":50,"integrity":"sha512-UdBDAyHpAEEc4OcY+4/Qf/GGlu+JG+kJx83zWgSIXuVBS2NNiwSZ/6tIs8cRzvEzitMOkbvwcxI4StfNzHmt4w==","signatures":[{"sig":"MEQCIFZwKv9E7f6EZ5F0D70xVAlKNFNyYqraPDZEZbYF7vbAAiBRhpCDShrxDXToEwq9AzYBA31pY/zy1+Ex6HYmIBxuLA==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":250801},"jest":{"testTimeout":60000},"main":"index.js","gitHead":"6103eb57765445355421826791361ac9ed9cf4d8","scripts":{"build":"npm-run-all downloadValidator downloadRuntimeParameters","downloadValidator":"curl https://cdn.ampproject.org/v0/validator_wasm.js --output spec/helpers/validator.js","downloadRuntimeParameters":"node build/downloadRuntimeParameters.js"},"_npmUser":{"name":"ampproject-admin","email":"admin@ampproject.org"},"repository":{"url":"git+https://github.com/ampproject/amp-toolbox.git","type":"git","directory":"packages/optimizer"},"_npmVersion":"10.8.1","description":"Server-side rendering for AMPs.","directories":{},"_nodeVersion":"20.12.2","dependencies":{"terser":"5.5.1","postcss":"8.2.13","domutils":"2.8.0","lru-cache":"6.0.0","domhandler":"4.2.2","cross-fetch":"3.1.5","htmlparser2":"7.2.0","cssnano-simple":"2.0.0","dom-serializer":"1.3.2","abort-controller":"3.0.0","https-proxy-agent":"5.0.0","postcss-safe-parser":"5.0.2","@ampproject/toolbox-core":"2.10.0","normalize-html-whitespace":"1.0.0","@ampproject/toolbox-script-csp":"2.10.0","@ampproject/toolbox-runtime-version":"2.10.0","@ampproject/toolbox-validator-rules":"2.10.0"},"_hasShrinkwrap":false,"peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/toolbox-optimizer_2.10.0_1718320318366_0.017166894314076142","host":"s3://npm-registry-packages"}},"2.10.1":{"name":"@ampproject/toolbox-optimizer","version":"2.10.1","description":"Server-side rendering for AMPs.","main":"index.js","repository":{"type":"git","url":"git+https://github.com/ampproject/amp-toolbox.git","directory":"packages/optimizer"},"scripts":{"downloadValidator":"curl https://cdn.ampproject.org/v0/validator_wasm.js --output spec/helpers/validator.js","downloadRuntimeParameters":"node build/downloadRuntimeParameters.js","build":"npm-run-all downloadValidator downloadRuntimeParameters"},"keywords":["amp"],"author":{"name":"AMPHTML Team"},"license":"Apache-2.0","peerDependenciesMeta":{"jimp":{"optional":true},"probe-image-size":{"optional":true}},"dependencies":{"@ampproject/toolbox-core":"2.10.1","@ampproject/toolbox-runtime-version":"2.10.1","@ampproject/toolbox-script-csp":"2.10.1","@ampproject/toolbox-validator-rules":"2.10.1","abort-controller":"3.0.0","cross-fetch":"3.1.5","cssnano-simple":"2.0.0","dom-serializer":"1.3.2","domhandler":"4.2.2","domutils":"2.8.0","htmlparser2":"7.2.0","https-proxy-agent":"5.0.0","lru-cache":"6.0.0","normalize-html-whitespace":"1.0.0","postcss":"8.2.13","postcss-safe-parser":"5.0.2","terser":"5.5.1"},"bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","gitHead":"6103eb57765445355421826791361ac9ed9cf4d8","jest":{"testTimeout":60000},"_id":"@ampproject/toolbox-optimizer@2.10.1","_nodeVersion":"20.12.2","_npmVersion":"10.8.1","dist":{"integrity":"sha512-dPZzR0XO8Uii11nUEjA71yA2ip0fBMlkuazw7NuTBsg55oFwzE3DknZKptH7hl8CSA28SjbmrbdpvjZlw3u4PA==","shasum":"61b2c563223277b6cfaed50ac2f7d667d411b996","tarball":"https://registry.npmjs.org/@ampproject/toolbox-optimizer/-/toolbox-optimizer-2.10.1.tgz","fileCount":50,"unpackedSize":250801,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCuNFm7eX2zPiAsE3sMGIccDj92BXQjS6TaN0GodHTisQIgFKUC5jKY3hy+/no/G9L1lpkVzJLpR/UqjFbyZZIaiOU="}]},"_npmUser":{"name":"ampproject-admin","email":"admin@ampproject.org"},"directories":{},"maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"},{"name":"kdwan","email":"kdwan@google.com"},{"name":"ampprojectbot","email":"bot@amp.dev"},{"name":"esth","email":"esther@amp.dev"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/toolbox-optimizer_2.10.1_1718325422880_0.8348899576857478"},"_hasShrinkwrap":false}},"time":{"created":"2019-06-10T18:20:59.177Z","modified":"2024-06-14T00:37:03.651Z","1.0.0-beta.0":"2019-06-10T18:20:59.508Z","1.0.0-beta.1":"2019-06-20T08:56:11.485Z","1.0.0-beta.2":"2019-06-20T09:40:54.190Z","1.0.0-beta.3":"2019-06-20T09:50:22.705Z","1.0.0-beta.4":"2019-07-11T14:38:10.566Z","1.0.0-beta.5":"2019-07-15T15:36:05.943Z","1.0.0-beta.6":"2019-07-23T13:02:20.756Z","1.0.0-beta.7":"2019-07-24T13:51:38.537Z","1.0.0-beta.8":"2019-08-01T09:09:07.543Z","1.0.0-beta.9":"2019-08-01T09:15:58.479Z","1.0.0":"2019-08-06T19:59:56.161Z","1.0.1":"2019-08-06T20:11:13.983Z","1.1.0-beta.0":"2019-08-29T20:27:44.256Z","1.1.0-beta.1":"2019-09-19T19:20:41.392Z","1.1.0":"2019-10-08T14:14:19.262Z","1.1.1":"2019-10-08T14:43:26.045Z","1.1.2-alpha.0":"2019-10-09T19:55:37.488Z","1.1.2":"2019-10-15T13:28:14.950Z","2.0.0-alpha.0":"2020-01-22T14:30:30.115Z","2.0.0-alpha.1":"2020-01-22T14:44:24.505Z","2.0.0-alpha.2":"2020-01-22T14:56:11.385Z","2.0.0-alpha.3":"2020-01-23T10:01:18.325Z","2.0.0-alpha.4":"2020-01-28T15:26:06.168Z","2.0.0-alpha.5":"2020-01-28T21:54:53.807Z","2.0.0-alpha.6":"2020-01-31T12:23:28.908Z","2.0.0-alpha.7":"2020-02-13T21:13:42.337Z","2.0.0":"2020-02-13T21:24:47.420Z","2.0.1":"2020-03-18T11:49:22.937Z","2.1.0":"2020-04-01T08:59:54.899Z","2.2.0":"2020-04-03T08:23:39.415Z","2.3.0":"2020-04-15T09:31:45.453Z","2.3.1":"2020-05-05T19:56:05.561Z","2.4.0-alpha.0":"2020-05-06T19:49:53.126Z","2.4.0-alpha.1":"2020-05-07T11:08:03.329Z","2.4.0-alpha.2":"2020-05-08T13:35:19.148Z","2.4.0":"2020-05-13T20:30:40.309Z","2.5.0-alpha.0":"2020-05-19T06:36:38.419Z","2.5.0":"2020-05-29T11:55:01.604Z","2.5.1":"2020-06-09T07:30:54.506Z","2.5.2":"2020-06-11T18:44:59.613Z","2.5.3":"2020-06-15T07:47:35.946Z","2.5.4":"2020-06-22T19:52:14.787Z","2.5.5":"2020-06-26T17:55:17.917Z","2.5.6":"2020-07-07T16:57:23.973Z","2.5.7":"2020-07-08T08:23:35.997Z","2.5.8":"2020-07-08T09:07:05.851Z","2.5.9":"2020-07-08T12:21:25.736Z","2.5.10":"2020-07-08T12:43:07.055Z","2.5.11":"2020-07-14T11:57:45.588Z","2.5.12":"2020-07-15T18:39:09.145Z","2.5.14":"2020-07-24T06:50:34.824Z","2.6.0":"2020-08-10T12:44:03.179Z","2.7.0-alpha.0":"2020-08-14T19:18:48.800Z","2.7.0-alpha.1":"2020-08-19T16:38:48.396Z","2.7.0-alpha.4":"2020-10-26T09:54:40.745Z","2.7.1-alpha.0":"2020-12-01T19:11:42.742Z","2.7.1":"2020-12-02T14:55:01.031Z","2.7.2":"2020-12-15T19:50:56.816Z","2.7.3":"2020-12-17T15:12:23.995Z","2.7.4":"2020-12-23T11:50:45.792Z","2.7.5":"2021-01-18T12:11:20.336Z","2.7.6":"2021-02-01T12:59:57.374Z","2.8.0-canary.0":"2021-02-12T17:24:51.019Z","2.8.0-canary.1":"2021-02-12T17:35:06.767Z","2.8.0-canary.2":"2021-02-15T11:29:17.298Z","2.8.0-canary.3":"2021-02-15T19:12:20.375Z","2.8.0-canary.4":"2021-03-02T12:54:40.855Z","2.8.0-canary.5":"2021-03-12T18:57:10.341Z","2.8.0-canary.6":"2021-03-18T17:25:27.536Z","2.8.0-canary.9":"2021-03-22T15:01:49.514Z","2.8.0-canary.15":"2021-03-30T20:35:17.839Z","2.8.0-canary.16":"2021-04-08T17:49:27.567Z","2.8.0-canary.18":"2021-05-07T20:26:54.969Z","2.8.0-canary.19":"2021-05-19T20:03:26.437Z","2.8.0-canary.20":"2021-05-20T13:46:16.892Z","2.8.0-canary.21":"2021-05-31T11:55:26.624Z","2.8.0":"2021-05-31T14:35:52.864Z","2.8.1-canary.0":"2021-06-01T08:19:24.595Z","2.8.1-canary.1":"2021-06-08T12:47:42.293Z","2.8.1":"2021-06-10T13:19:22.709Z","2.8.2":"2021-07-02T15:38:26.270Z","2.8.3":"2021-07-12T09:16:39.139Z","2.8.4":"2021-11-17T15:26:53.153Z","3.0.0-canary.2":"2022-01-11T20:37:48.885Z","2.8.5":"2022-01-12T21:17:51.911Z","2.8.6":"2022-01-12T21:56:06.169Z","2.8.7":"2022-01-14T16:09:39.186Z","2.8.8":"2022-02-11T16:52:19.459Z","2.8.9":"2022-03-10T20:47:27.422Z","2.8.10":"2022-03-16T19:44:15.318Z","2.9.0":"2022-06-23T20:48:35.788Z","2.10.0":"2024-06-13T23:11:58.613Z","2.10.1":"2024-06-14T00:37:03.180Z"},"maintainers":[{"name":"ampproject-admin","email":"admin@ampproject.org"},{"name":"kristoferbaxter","email":"kbax@me.com"},{"name":"jridgewell","email":"justin+npm@ridgewell.name"},{"name":"choumx","email":"willchou@google.com"},{"name":"amp-toolbox","email":"amp-toolbox-eng@google.com"},{"name":"fstanis","email":"filip@stanis.me"},{"name":"erwinmombay","email":"erwin.mombay@gmail.com"},{"name":"samouri","email":"jzfisgreat@gmail.com"},{"name":"caroqliu","email":"carolineliu@google.com"},{"name":"rsimha","email":"rsimha@amp.dev"},{"name":"dvoytenko","email":"dvoytenko@gmail.com"},{"name":"patrickkettner","email":"patrickkettner@gmail.com"},{"name":"alanorozco","email":"orozcoalan@gmail.com"},{"name":"kdwan","email":"kdwan@google.com"},{"name":"ampprojectbot","email":"bot@amp.dev"},{"name":"esth","email":"esther@amp.dev"}],"author":{"name":"AMPHTML Team"},"repository":{"type":"git","url":"git+https://github.com/ampproject/amp-toolbox.git","directory":"packages/optimizer"},"keywords":["amp"],"license":"Apache-2.0","homepage":"https://github.com/ampproject/amp-toolbox/tree/main/packages/optimizer","bugs":{"url":"https://github.com/ampproject/amp-toolbox/issues"},"readme":"# AMP Optimizer\n\n[![npm version](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer.svg)](https://badge.fury.io/js/%40ampproject%2Ftoolbox-optimizer)\n\nAMP Optimizer is a tool to simplify creating AMP pages and improve AMP rendering performance. AMP Optimizer implements [AMP performance best practices](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp?format=websites) and supports [AMP server-side-rendering](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering?format=websites). By default, it will perform the following optimizations:\n\n- [Server-side render AMP layouts](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/).\n- **Automatically import all missing AMP component scripts**.\n- **Automatically add any missing mandatory AMP tags**.\n- Auto detects and preloads hero images from amp-img, amp-iframe, amp-video, or amp-video-iframe.\n- Remove the AMP boilerplate (when possible).\n- Remove not needed whitespace.\n- Extract and move CSS keyframe animations to the bottom of the page.\n- Optimize AMP framework and custom font loading\n- Generate CSP for inlined [`amp-script`](https://amp.dev/documentation/components/amp-script/) code.\n\nThe performance optimizations can improve page rendering times by up to 50%. You can read more about the potential performance gains in this [blog post](https://blog.amp.dev/2018/10/08/how-to-make-amp-even-faster/). To give it a try, check out [the online playground](https://toolbox-optimizer.glitch.me/).\n\n**Good to know:**\n\n- AMP Optimizer will produce valid AMP.\n- AMP Optimizer can be used in combination with [AMP Packager](https://github.com/ampproject/amppackager) to create SXGs.\n\n## Usage\n\n### API\n\nInstall via:\n\n```\nnpm install @ampproject/toolbox-optimizer\n```\n\nMinimal usage:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\n\nconst ampOptimizer = AmpOptimizer.create();\n\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n  ...\n</html>`;\n\nampOptimizer.transformHtml(originalHtml).then((optimizedHtml) => {\n  console.log(optimizedHtml);\n});\n```\n\nYou can find a sample implementation [here](/packages/optimizer/demo/simple). If you're using express to serve your site, you can use the [AMP Optimizer Middleware](/packages/optimizer-express).\n\n### CLI\n\nAMP Optimizer can be used via the [AMP Toolbox CLI](/packages/cli/README.md):\n\n```shell\nnpm install @ampproject/toolbox-cli -g\namp optimize myFile.html\n```\n\nor run without installation via `npx`:\n\n```shell\nnpx @ampproject/toolbox-cli optimize myFile.html\n```\n\n### Options\n\nOptions are passed when creating a new AMP Optimizer instance:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  verbose: true\n});\n...\n```\n\nAvailable options are:\n\n- [autoAddMandatoryTags](#autoaddmandatorytags)\n- [autoExtensionImport](#autoextensionimport)\n- [esmModulesEnabled](#esmmodulesenabled)\n- [extensionVersions](#extensionversions)\n- [fetch](#fetch)\n- [format](#format)\n- [imageBasePath](#imagebasePath)\n- [imageOptimizer](#imageoptimizer)\n- [lts](#lts)\n- [markdown](#markdown)\n- [minify](#minify)\n- [optimizeAmpBind](#optimizeampbind)\n- [optimizeHeroImages](#optimizeheroimages)\n- [preloadHeroImage](#preloadheroimage)\n- [verbose](#verbose)\n\n#### `autoAddMandatoryTags`\n\nAutomatically inject any missing markup required by AMP.\n\n- name: `autoAddMandatoryTags`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `autoExtensionImport`\n\nAutomatically import any missing AMP Extensions (e.g. amp-carousel).\n\n- name: `autoExtensionImport`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `esmModulesEnabled`\n\nEnables the smaller ESM module version of AMP runtime and components:\n\n```\n<script async nomodule src=\"https://cdn.ampproject.org/v0.js\"></script>\n<script async src=\"https://cdn.ampproject.org/v0.mjs\" type=\"module\" crossorigin=\"anonymous\"></script>\n```\n\n**Warning: This is incompatible with AMP Packager unless using a prerelease version containing [this commit](https://github.com/ampproject/amppackager/commit/92387ecf97e420eec10b378282383c59f95ab45f).**\n\n- name: `esmModulesEnabled`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `extensionVersions`\n\nSpecify version numbers to use for automatically imported Extensions. If not defined, default to latest.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  extensionVersions: {\n    'amp-twitter': '0.1',\n  },\n});\n```\n\n- name: `extensionVersions`\n- valid options: `OBJECT`\n- default: `{}`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js)\n\n#### `format`\n\nSpecifies the AMP format of the input file. Defaults to `AMP`.\n\n- name: `format`\n- valid options: `[AMP|AMP4EMAIL|AMP4ADS]`\n- default: `AMP`\n- used by: [AutoExtensionImport](lib/transformers/AutoExtensionImporter.js), [AddMandatoryTags](lib/transformers/AddMandatoryTags.js)\n\n#### `imageBasePath`\n\nSpecifies a base path used to resolve an image during build,\nthis can be a file system path or URL prefix. You can also pass a function\n`(imgSrc, params) => '../img/' + imgSrc` for dynamically calculating the image path.\n\n- name: `imageBasePath`\n- valid options: `STRING|FUNCTION`\n- default: undefined\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `imageOptimizer`\n\nEnable automated image `srcset` generation by providing a function for calculating `srcset` URLs for a given image `src`. The function should return a URL pointing to a version of the `src` image with the given `width`. If no image is available, it should return a falsy value.\n\nExample:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  imageOptimizer: (src, width) => `${src}?width=${width}`,\n});\n```\n\n- name: `imageOptimizer`\n- valid options: `FUNCTION`\n- default: undefined\n- used by: [OptimizeImages](lib/transformers/OptimizeImages.js)\n\n#### `lts`\n\nUse [long-term stable URLs](https://github.com/ampproject/amphtml/blob/main/contributing/lts-release.md) for downloading the AMP runtime and components.\n\n- name: `lts`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [RewriteAmpUrls](lib/transformers/RewriteAmpUrls.js)\n\n#### `markdown`\n\nThis transformer adds out-of-the-box markdown support. This allows\nusing AMP Optimizer to convert HTML documents created from Markdown\nfiles into valid AMP. A typical conversion flow would be:\n\nREADME.md => HTML => AMP Optimizer => valid AMP\n\nThe only thing this transformer does is converting `<img>` tags into\neither `amp-img` or `amp-anim` tags. All other Markdown features are\nalready supported by AMP. The transformer will try to resolve image\ndimensions from the actual files. Images larger than 320px will automatically\nget an intrinsic layout. For image detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n- name: `markdown`\n- valid options: `[true|false]`\n- default: `false`\n- used by: [Markdown](lib/transformers/Markdown.js)\n\n#### `minify`\n\nMinifies the generated HTML output and inlined CSS.\n\n- name: `minify`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [MinifyHtml](lib/transformers/MinifyHtml.js), [SeparateKeyframes](lib/transformers/SeparateKeyframes.js)\n\n**Warning:** this setting is not recommended when running AMP Optimizer in your backend on every request as execution time can increase by up to 7x.\n\n#### `optimizeAmpBind`\n\nEnables a considerably faster scanning method in `amp-bind`, by injecting a `i-amphtml-binding` attribute on all elements with a bound attribute.\n\n- name: `optimizeAmpBind`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeAmpBind](lib/transformers/OptimizeAmpBind.js)\n\n#### `optimizeHeroImages`\n\nEnables hero image optimization. Hero images will either be auto detected or you can explicitly mark these by adding the `data-hero` attribute:\n\n```\n<amp-img data-hero src=\"foo.jpg\" ...>\n```\n\nThe maximum number of hero images that can be marked up using `data-hero` is `2`. This number can be increased using the `maxHeroImageCount` parameter.\n\nHero images are optimized by server-side rendering the `img` element inside the `amp-img` element. This can siginificantly improve image rendering performance and reduce the [largest contentful paint](https://web.dev/lcp/) (LCP) metric from [Core Web Vitals](https://web.dev/vitals/).\n\n- name: `optimizeHeroImages`\n- valid options: `[true|false]`\n- default: `true`\n- used by: [OptimizeHeroImages](lib/transformers/OptimizeHeroImages.js)\n\n#### `preloadHeroImage`\n\nDeprecated, use [optimizeHeroImages](#optimizeheroimages) instead. \n\n#### `verbose`\n\nEnable verbose mode with more detailed logging output.\n\n- name: `verbose`\n- valid options: `[true|false]`\n- default: `false`\n\n## Features\n\n### Image optimization\n\nAMP Optimizer helps you serve optimized images. For this to work, you need to provide a function that maps an image `src` to a resized `srcset` source value. The image resizing needs to either happen at build time (e.g. for static sites) or via a image hosting service such as [thumbor](https://github.com/thumbor/thumbor).\n\nHere is an example implementation that appends the image width to the `src`:\n\n```js\nconst ampOptimizer = AmpOptimizer.create({\n  // parameters are the amp-img `src` and the `width` of the to be generated srcset source value\n  imageOptimizer: (src, width) => {\n    // we cannot rename if the image does not have a file extension\n    const index = src.lastIndexOf('.');\n    if (index === -1) {\n      // return null means we won't generate a srcset source value for this width\n      return null;\n    }\n    const prefix = src.substring(0, index);\n    const postfix = src.substring(index, src.length);\n    return `${prefix}.${width}w${postfix}`;\n  };\n})\n```\n\nUsing this implementation, AMP Optimizer will transform the following `amp-img` declarations:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img src=\"image1.png\" width=\"400\" height=\"800\" layout=\"responsive\"></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                             image-2x.png 2x\"\n></amp-img>\n```\n\ninto:\n\n```html\n<!-- Injects srcset for responsive layout -->\n<amp-img\n  src=\"image1.png\"\n  width=\"400\"\n  height=\"800\"\n  layout=\"responsive\"\n  srcset=\"image1.470w.png 470w, image1.820w.png 820w, image1.1440w.png 1440w\"\n></amp-img>\n<!-- Ignores existing srcset -->\n<amp-img\n  layout=\"fill\"\n  srcset=\"image-1x.png 1x,\n                               image-2x.png 2x\"\n></amp-img>\n```\n\n**Important** when using `layout=responsive` use the `width` and `height` attribute to specify the minimum image dimensions. For example, for a full-bleed hero image on mobile, specify the width as`width=320`.\n\n### Auto add incomplete markup\n\nIt's possible to pass incomplete documents and AMP Optimizer will add any\nmissing tags and extension imports required by a valid AMP document.\n\n```js\nconst originalHtml = `\n  <h1>Hello World!</h1>\n  <amp-twitter width=\"375\"\n               height=\"472\"\n               layout=\"responsive\"\n               data-tweetid=\"1182321926473162752\">\n  </amp-twitter>\n`;\n\n// you can pass the canonical URL, default is `.`\nconst opts = {\n  canonical: '/example.html',\n};\nampOptimizer.transformHtml(originalHtml, opts).then((optimizedHtml) => {\n  // optimizedHtml will be a valid AMP document\n  console.log(optimizedHtml);\n});\n```\n\n### Automated Markdown conversion\n\nAMP Optimizer supports converting Markdown to AMPHTML. A typical conversion flow would be:\n\n```\nREADME.md => HTML => AMP Optimizer => valid AMP\n```\n\nThe AMP Optimizer converts `<img>` tags into `<amp-img>` or `<amp-anim>` tags when in Markdown mode. Enable Markdown mode via `markdown : true`. AMP Optimizer will try to resolve image dimensions from the actual files. Images wider than 320px will automatically get an `intrinsic` layout.\n\nAll other Markdown features are already supported by AMP.\n\nYou can pass an additional option `imageBasePath` to specify a base path used to resolve an image during build, this can be a file system path or URL prefix.\n\n**Important:** for image size detection to work, an optional dependency\n`probe-image-size` needs to be installed via NPM.\n\n```shell\nnpm install probe-image-size --save-dev\n```\n\nExample:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst md = require('markdown-it')({\n  // don't sanitize html if you want to support AMP components in Markdown\n  html: true,\n});\n\n// enable markdown mode\nconst ampOptimizer = AmpOptimizer.create({\n  markdown: true,\n});\n\nconst markdown = `\n# Markdown 🤯\n\nHere is an image declared in Markdown syntax:\n\n![A random image](https://unsplash.it/800/600).\n\nYou can directly declare AMP components:\n\n<amp-twitter width=\"375\"\n             height=\"472\"\n             layout=\"responsive\"\n             data-tweetid=\"1182321926473162752\">\n</amp-twitter>\n\nAny missing extensions will be automatically imported.\n`;\n\nconst html = md.render(markdown);\n\nconst amphtml = await ampOptimizer.transformHtml(html, {\n  canonical: filePath,\n});\n```\n\nYou can find a working sample [here](/packages/optimizer/demo/markdown).\n\n## Extending AMP Optimizer with custom transformations\n\nAMP Optimizer supports custom HTML transformations:\n\n```js\nconst AmpOptimizer = require('@ampproject/toolbox-optimizer');\nconst {createElement, firstChildByTag, appendChild} = AmpOptimizer.NodeUtils;\n\nclass CustomTransformer {\n  constructor(config) {\n    this.log_ = config.log.tag('CUSTOM');\n  }\n  transform(tree, params) {\n    this.log_.info('Running custom transformation for ', params.filePath);\n    const html = firstChildByTag(tree, 'html');\n    if (!html) return;\n    const head = firstChildByTag(html, 'head');\n    if (!head) return;\n    const desc = createElement('meta', {\n      name: 'description',\n      content: 'this is just a demo',\n    });\n    appendChild(head, desc);\n  }\n}\n\n// it's best to run custom transformers first\nconst customTransformations = [CustomTransformer, ...AmpOptimizer.TRANSFORMATIONS_AMP_FIRST];\n\n// pass custom transformers when creating the optimizer\nconst optimizer = AmpOptimizer.create({\n  transformations: customTransformations,\n});\n// you can add custom parameters on a per document basis\nconst transformedHtml = await optimizer.transformHtml(html, {\n  filePath,\n});\n```\n\nCheckout [the samples](/packages/optimizer/demo/simple/index.js) to learn how to customize AMP Optimizer.\n\n## Best Practices\n\n### Make sure to enable AMP Boilerplate removal\n\nThe biggest performance gain results from [removing the AMP boilerplate code](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/server-side-rendering/#why-is-it-faster?). However, under some circumstances it's not possible to remove the boilerplate code:\n\n- if the`amp-experiment`, `amp-story` or `amp-dynamic-css-classes` components are used ([code](https://github.com/ampproject/amphtml/blob/62a9eab084ccd800d80a371e2cb29cd4f9e8576a/src/render-delaying-services.js#L39-L43)).\n\nTo find out, why the AMP boilerplate could not be removed, enable `verbose` mode:\n\n```js\n// globally\nconst optimizer = ampOptimizer.create({\n  verbose: true,\n});\n```\n\n... or for individual pages:\n\n```js\n// per transformation\nampOptimizer.transformHtml(originalHtml, {\n  verbose: true,\n});\n```\n\n### Transform AMP pages at build time if possible\n\nApplying the transformations to an AMP file consumes additional server resources. Also, since the entire file is needed to apply the transformations, it also becomes impossible to stream the response while applying it. In order to avoid server overhead, if the set of AMP files to be transformed is known in advance, transformations should be run at build time.\n\n### Cache transformed AMPs at runtime\n\nMost websites have a more dynamic nature though and are not able to apply the transformations statically. For such cases it is possible to run the transformations after AMP pages are rendered, e.g. in an Express middleware. In that case, to achieve best performance, it's best to cache transformed pages for subsequent requests. Caching can take place on the CDN level, on the site's internal infrastructure (eg: Memcached), or even on the server itself, if the set of pages is small enough to fit in memory.\n\n### Regenerate pages at least once a week\n\nAMP Optimizer inlines CSS styles required by AMP. To make sure, that the inlined CSS stays in sync with the latest AMP release, we recommend to re-generate pages at least once a weekOut-of-sync CSS will not break your page, but it could theoretically cause AMP components to briefly appear with the \"wrong\" styles, such as being visible when they should be hidden. The good news is that these glitches will only be temporary, because as soon as the AMP JS starts, it will check the inlined CSS and update it if required.\n\n## Experimental Features\n\n**Warning: these features are experimental and might result in invalid AMP pages.**\n\n### Paired AMP\n\nWhen using experimental features resulting in invalid AMP it's best to setup paired AMP mode. Paired AMP mode will add `<link rel=amphtml href=${ampUrl}>` to the transformed page, were `ampUrl` needs to point to the valid version of this page.\n\nExample:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,\n});\nconst ampFilePath = filePath.substring(1, filePath.length).replace('.html', '.amp.html');\nconst transformedHtml = await optimizer.transformHtml(html, {\n  // needed to calculate the `<link rel=amphtml href=${ampUrl}>`\n  ampUrl: ampFilePath,\n});\n```\n\n### Versioned AMP Runtime\n\nThe `ampRuntimeVersion` parameter will rewrite all AMP runtime and extension imports to the specified version. For example:\n\n```\nhttps://cdn.ampproject.org/v0.js\n```\n\nwill be replaced with:\n\n```\nhttps://cdn.ampproject.org/rtv/001515617716922/v0.js\n```\n\nVersioning the AMP runtime URLs has one main benefit: versioned AMP runtime URLs are served with a longer max-age than the unversioned ones. This means AMP pages served with versioned AMP runtime benefit from better browser caching.\n\n**Important:** when using versioned AMP runtime URLs make sure to invalidate all caches whenever a new AMP runtime is released. This is to ensure that your AMP pages always use the latest version of the AMP runtime.\n\nYou can use [@ampproject/toolbox-runtime-version](/packages/runtime-version) to retrieve the latest version of the AMP runtime. Here is a sample to apply the optimizations including versioning the URLs:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\nconst ampRuntimeVersion = await runtimeVersion.currentVersion();\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  ampRuntimeVersion: ampRuntimeVersion,\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Blurry image placeholders\n\nAdd placeholders for `amp-img` and `amp-video` posters. The placeholders are blurry versions of the corresponding original source. The blur will be displayed as the `<amp-img>` is rendering, and will fade out once the element is loaded. The current requirements of appending a blurry placeholder is for the element is to be a JPEG that is either responsive or a poster for an `amp-video`.\n\n**Important: blurry image placeholder computation is computationally expensive. Make sure to only use it for static or cached pages.**\n\nThis transformer supports the following options:\n\n- `blurredPlaceholders`: Enables blurry image placeholder generation. Default is `false`.\n- `imageBasePath`: specifies a base path used to resolve an image during build.\n- `maxBlurredPlaceholders`: Specifies the max number of blurred images. Defaults to 5.\n- `blurredPlaceholdersCacheSize`: Specifies the max number of blurred images to be cached\n  to avoid expensive recalculation. Set to 0 if caching should be disabled. Set to -1 if\n  all placeholders should be cached (good for static sites). Defaults to 30.\n\nUsage:\n\n```js\nconst optimizer = AmpOptimizer.create({\n  blurredPlaceholders: true,\n});\n```\n\n### Self-hosted AMP components\n\nIt's possible to rewrite the AMP framework and component imports to a different domain than `cdn.ampproject.org`.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\n// Additional options can be passed as the second argument\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  ampUrl: 'canonical.amp.html',\n  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js\n  ampUrlPrefix: '/amp',\n});\n\nconsole.log(optimizedHtml);\n```\n\n### Fallback API for amp-geo\n\nIdeally, when self-hosting the AMP framework, `amp-geo-0.1.js` should be patched at delivery time to replace `{{AMP_ISO_COUNTRY_HOTPATCH}}` with the ISO 3166-1 alpha-2 country code where the request originated ([reference](https://github.com/ampproject/amphtml/blob/main/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem)). If your host does not have this capability, you can instead rely on a web API to return the country at runtime. The web API must be secure (HTTPS), adhere to [AMP CORS guidelines](https://amp.dev/documentation/guides-and-tutorials/learn/amp-caches-and-cors/amp-cors-requests/), and return JSON in the following format:\n\n```js\n{\"country\": \"de\"}\n```\n\nwhere in this example, `de` is the ISO 3166-1 alpha-2 country code for Germany.\n\nExample:\n\n```js\nconst ampOptimizer = require('@ampproject/toolbox-optimizer');\n\n// The input string\nconst originalHtml = `\n<!doctype html>\n<html ⚡>\n...\n`;\n\nconst optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {\n  // this will instruct amp-geo to fetch the user's country from an API\n  // which returns JSON in format: {\"country\": \"de\"}\n  geoApiUrl: 'https://example.com/geo',\n});\n\nconsole.log(optimizedHtml);\n```\n\n## Development & Testing\n\nAMP Optimizer uses a snapshot based testing approach. To execute the tests, run in the project root:\n\n```shell\nnpm run test:node\n```\n\nTransformer tests are located in:\n\n```\n- spec/transformers/valid/TransformerName/test-name/\n    expected_output.html\n    input.html\n```\n\nThe transformation input is defined in `input.html`, whereas `expected_output.html` contains the expected\noutcome of the transformation. Don't edit `expected_output.html` manually, instead, after changing\na transformer implementation, run:\n\n```shell\nnpm run test:optimizer:snapshot\n```\n\nto store a new snapshot version in `expected_output.html`.\n","readmeFilename":"README.md"}