hexo-generator-amp
Advanced tools
Comparing version 1.0.1 to 1.0.2
16
index.js
'use strict'; | ||
var assign = require('object-assign'); | ||
var ic = require('./lib/copyAssets.js'); | ||
var pathFn = require('path'); | ||
var hexo_env = require('hexo-env'); | ||
var isEnableAMP = true; | ||
var assign = require('object-assign'); | ||
var pathFn = require('path'); | ||
var hexo_env = require('hexo-env'); | ||
var ic = require('./lib/copyAssets.js'); | ||
var lg = require('./lib/log.js'); | ||
var isEnableAMP = true; | ||
lg.setConfig(hexo.config); | ||
ic.setConfig(hexo.config); | ||
if(hexo.config.generator_amp && hexo.config.generator_amp.onlyForDeploy){ | ||
@@ -29,3 +33,3 @@ isEnableAMP = hexo.config.generator_amp.onlyForDeploy && (hexo_env.env(hexo) == 'production'); | ||
}else{ | ||
console.log("\u001b[31m[hexo-generator-amp] (error) please setting option.\u001b[0m "); | ||
lg.log("error", "Please set the generator_amp option." , "_config.yml"); | ||
return null; | ||
@@ -32,0 +36,0 @@ } |
@@ -6,3 +6,5 @@ | ||
var mkdirp = require('mkdirp'); | ||
var lg = require('./log.js'); | ||
var absolutePathReg = /^[a-zA-Z0-9]*?\:\/\// | ||
var config; | ||
@@ -16,4 +18,3 @@ module.exports.initCopy = function(assetDirName, assetFiles){ | ||
mkdirp.sync( hexoAssetPath_sampleDir ); | ||
console.log("\u001b[32m[hexo-generator-amp] (initial copy asset)\u001b[0m asset dir: "+ hexoAssetPath_sampleDir); | ||
lg.log("info", "hexo-generator-amp's template has been copied to the your project.",hexoAssetPath_sampleDir); | ||
} | ||
@@ -43,3 +44,3 @@ for(var i=0; i< assetFiles.length; i++){ | ||
}else{ | ||
console.log("\u001b[31m[hexo-generator-amp] (error) not found asset file. \u001b[0mplease check setting option, and path: "+ pathFn.join(hexoAssetPath , copyFiles[i]) ); | ||
lg.log("error", "Not found the hexo-generator-amp's asset files.", pathFn.join(hexoAssetPath , copyFiles[i]) ); | ||
return false | ||
@@ -50,5 +51,10 @@ } | ||
}else{ | ||
console.log("\u001b[31m[hexo-generator-amp] (error) not found asset Directory. please check setting option, and path: "+ hexoAssetPath ); | ||
lg.log("error", "Not found the hexo-generator-amp's asset directory. dir: " + hexoAssetPath , "_config.yml" ); | ||
return false; | ||
} | ||
}; | ||
module.exports.setConfig = function(inConfig){ | ||
config = inConfig; | ||
lg.setConfig(config); | ||
}; |
@@ -6,2 +6,3 @@ | ||
var gs = require('./imageSize.js'); | ||
var lg = require('./log.js'); | ||
@@ -21,2 +22,4 @@ module.exports = function(data){ | ||
var frontMatterImg = false; | ||
lg.setConfig(config); | ||
gs.setConfig(config); | ||
data.eyeCatchImage = ""; | ||
@@ -42,3 +45,3 @@ data.titleImageForAmp = ""; | ||
}else{ | ||
console.log("\u001b[31m[hexo-generator-amp] (error) Please setting ampSettings.titleImage.width and height option. Check this the file. : "+"\u001b[0m"+data.source ); | ||
lg.log("error", "Please check the front-matter options (ampSettings.titleImage.width and height option)." , data.source); | ||
} | ||
@@ -126,3 +129,3 @@ }else{ | ||
if( Number(imgWidth) < 696 ){ | ||
if(config.generator_amp.warningLog)console.log("\u001b[33m[hexo-generator-amp] (warning) Images should be at least 696 pixels wide. \n img path: \u001b[0m"+data.eyeCatchImage+"\n ( Please check this the file. : "+data.source + " )"); | ||
lg.log("warn", "The following image should be at least 696 pixels wide. img path: " + data.eyeCatchImage , data.source); | ||
isIncompleteProperty = true; | ||
@@ -129,0 +132,0 @@ } |
@@ -7,5 +7,6 @@ 'use strict'; | ||
var fs = require('fs'); | ||
var minify = require('html-minifier').minify; | ||
var cheerio = require('cheerio'); | ||
var gs = require('./imageSize.js'); | ||
var minify = require('html-minifier').minify; | ||
var jsdom = require('jsdom').jsdom; //9.5.0 | ||
var lg = require('./log.js'); | ||
var absolutePathReg = /^[a-zA-Z0-9]*?\:\/\// | ||
@@ -17,7 +18,9 @@ | ||
var config = this.config; | ||
var ampEjsTmplPath = ""; //pathFn.join(__dirname, ejsPath); | ||
var cssFilePath = ""; //pathFn.join(__dirname, cssPath); | ||
var ampEjsTmplPath = ""; | ||
var cssFilePath = ""; | ||
var template = ""; | ||
var templateDir = ""; //__dirname; | ||
var templateDir = ""; | ||
var idDecorator = 0; | ||
lg.setConfig(config); | ||
gs.setConfig(config); | ||
@@ -36,3 +39,3 @@ //------------------------------------ | ||
}else{ | ||
console.log("\u001b[31m[hexo-generator-amp] (error) not found template file.\u001b[0m please check setting option. path: "+ ampEjsTmplPath); | ||
lg.log("error", "Not found the template file. Please check the options." , ampEjsTmplPath); | ||
return null; | ||
@@ -54,3 +57,3 @@ } | ||
}else{ | ||
console.log("\u001b[31m[hexo-generator-amp] (error) not found css file.\u001b[0m please check setting option. path: "+ cssFilePath); | ||
lg.log("error", "Not found the css file. Please check the options. " , cssFilePath); | ||
return null; | ||
@@ -92,3 +95,3 @@ } | ||
}else{ | ||
console.log("\u001b[31m[hexo-generator-amp] (error) Please setting generator_amp.logo_topImage.width and height option. \u001b[0mCheck this the file. : "+"_config.yml" ); | ||
lg.log("error", "Please setting the generator_amp.logo_topImage.width and height option." , "_config.yml"); | ||
return null; | ||
@@ -132,3 +135,3 @@ } | ||
//------------------------------------ | ||
@@ -233,3 +236,3 @@ // escape | ||
if(imgSrc == "" || imgSrc =='src=""'){ | ||
console.log("\u001b[33m[hexo-generator-amp] (warning) .md should contain image src attribute.\n Please check this the file. : "+"\u001b[0m"+post.source); | ||
lg.log("error", "<img> should contain image src attribute." , post.source); | ||
}else{ | ||
@@ -240,3 +243,3 @@ if(imgWidth == "" || imgHeight == ""){ | ||
//External image file | ||
if(config.generator_amp.warningLog)console.log("\u001b[33m[hexo-generator-amp] (warning) .md should contain image file and width height attribute. \n img path: \u001b[0m"+ imgPath + "\n ( Please check this the file. : "+post.source + " )"); | ||
lg.log("warn", "<img> should contain image file and width height attribute. img path: "+imgPath , post.source); | ||
}else{ | ||
@@ -329,30 +332,31 @@ //Local image files | ||
//------------------------------------ | ||
var document = jsdom(replaceStr); | ||
var document = document.defaultView.document; | ||
[].slice.call(document.querySelectorAll('video')).forEach(function(video) { | ||
var ampVideo = document.createElement('amp-video'); | ||
var $ = cheerio.load(replaceStr); | ||
var flg = false; | ||
$("video").each(function(i){ | ||
// I check required attributes in <amp-video> . | ||
if( !$(this).attr( "width" ) || !$(this).attr("height") || !$(this).attr("poster") ){ | ||
lg.log("warn", "<video> should contain width and height and poster attribute. " , post.source); | ||
} | ||
// createElement | ||
var ampVideo = cheerio.load( "<amp-video></amp-video>" ); | ||
// copy attributes | ||
[].slice.call(video.attributes).forEach(function(attribute) { | ||
ampVideo.setAttribute(attribute.name, attribute.value); | ||
}); | ||
ampVideo("amp-video").attr( $(this).attr() ); | ||
// move children | ||
[].slice.call(video.querySelectorAll('source')).forEach(function(child) { | ||
ampVideo.appendChild(child); | ||
}); | ||
//set layout attribute | ||
ampVideo.setAttribute("layout","responsive"); | ||
//set controls attribute | ||
if(!ampVideo.getAttribute("autoplay") && !ampVideo.getAttribute("controls")){ | ||
ampVideo.setAttribute("controls",""); | ||
ampVideo("amp-video").append( $(this).contents() ); | ||
// (required attribute) set layout attribute | ||
ampVideo("amp-video").attr( "layout" , "responsive" ); | ||
// (required attribute) set controls attribute | ||
if( !ampVideo("amp-video").attr( "autoplay" ) && !ampVideo("amp-video").attr( "controls" ) ){ | ||
ampVideo("amp-video").attr( "controls" , "" ); | ||
} | ||
// I verify required attributes in <amp-video> . | ||
if(!ampVideo.getAttribute("width") || !ampVideo.getAttribute("height") || !ampVideo.getAttribute("poster")){ | ||
if(config.generator_amp.warningLog)console.log("\u001b[33m[hexo-generator-amp] (warning) <video> should contain width and height and poster attribute. \n \u001b[0m( Please check this the file. : "+post.source + " )"); | ||
} | ||
video.parentNode.replaceChild(ampVideo, video); | ||
// replace element | ||
$(this).replaceWith( ampVideo.html().replace(/\<br\>/g,"") ); | ||
flg = true; | ||
}); | ||
replaceStr = document.body.innerHTML; | ||
if(flg){ | ||
replaceStr = $.html(); | ||
} | ||
@@ -363,3 +367,2 @@ | ||
//------------------------------------ | ||
replaceStr = replaceStr.replace(/\<[\s]*style[^\<]*\<\/[\s]*style[\s]*\>/g, ""); | ||
@@ -401,3 +404,2 @@ // replaceStr = replaceStr.replace(/\<[\s]*script[^\<]*\<\/[\s]*script[\s]*\>/g, ""); | ||
// get year | ||
@@ -434,29 +436,29 @@ var nowD = new Date(); | ||
var minified_option = { | ||
"caseSensitive" : false, | ||
"collapseBooleanAttributes" : true , | ||
"collapseInlineTagWhitespace" : false, | ||
"collapseWhitespace" : true , | ||
"conservativeCollapse" : true , | ||
"decodeEntities" : true, | ||
"html5" : true, | ||
"includeAutoGeneratedTags" : false, | ||
"keepClosingSlash" : false, | ||
"minifyCSS" : true, | ||
"minifyJS" : true, | ||
"preserveLineBreaks" : false, | ||
"preventAttributesEscaping" : false, | ||
"processConditionalComments" : true, | ||
"processScripts": ["text/html" ,"application/ld+json" ,"application/json"], | ||
"removeAttributeQuotes" : true, | ||
"removeComments" : true, | ||
"removeEmptyAttributes" : true, | ||
"removeEmptyElements" : false, | ||
"removeOptionalTags" : true, | ||
"removeRedundantAttributes" : true, | ||
"removeScriptTypeAttributes" : true, | ||
"removeStyleLinkTypeAttributes" : true, | ||
"removeTagWhitespace" : true, | ||
"sortAttributes" : true, | ||
"sortClassName" : true, | ||
"useShortDoctype" : true | ||
"caseSensitive" : false, | ||
"collapseBooleanAttributes" : true , | ||
"collapseInlineTagWhitespace" : false, | ||
"collapseWhitespace" : true , | ||
"conservativeCollapse" : true , | ||
"decodeEntities" : true, | ||
"html5" : true, | ||
"includeAutoGeneratedTags" : false, | ||
"keepClosingSlash" : false, | ||
"minifyCSS" : true, | ||
"minifyJS" : true, | ||
"preserveLineBreaks" : false, | ||
"preventAttributesEscaping" : false, | ||
"processConditionalComments" : true, | ||
"processScripts" : ["text/html" ,"application/ld+json" ,"application/json"], | ||
"removeAttributeQuotes" : true, | ||
"removeComments" : true, | ||
"removeEmptyAttributes" : true, | ||
"removeEmptyElements" : false, | ||
"removeOptionalTags" : true, | ||
"removeRedundantAttributes" : true, | ||
"removeScriptTypeAttributes" : true, | ||
"removeStyleLinkTypeAttributes": true, | ||
"removeTagWhitespace" : true, | ||
"sortAttributes" : true, | ||
"sortClassName" : true, | ||
"useShortDoctype" : true | ||
}; | ||
@@ -472,3 +474,2 @@ | ||
return { | ||
@@ -475,0 +476,0 @@ path: post.path+"amp/index.html", |
@@ -6,4 +6,7 @@ | ||
var imgSize = require('image-size'); | ||
var lg = require('./log.js'); | ||
var config; | ||
module.exports.getSizeInfo = function(imgsrc , data){ | ||
var imgWidth; | ||
@@ -33,3 +36,3 @@ var imgHeight; | ||
}else{ | ||
console.log("\u001b[31m[hexo-generator-amp] (error) no such file or directory. \n img path: "+"\u001b[0m"+ imgDevPath + "\n ( Please check this the file. : " + data.source +" )"); | ||
lg.log("error", "no such file or directory. img path: "+imgDevPath , data.source); | ||
return null; | ||
@@ -39,2 +42,7 @@ } | ||
return {"w":imgWidth, "h":imgHeight}; | ||
}; | ||
module.exports.setConfig = function(inConfig){ | ||
config = inConfig; | ||
lg.setConfig(config); | ||
}; |
{ | ||
"name": "hexo-generator-amp", | ||
"version": "1.0.1", | ||
"version": "1.0.2", | ||
"main": "index", | ||
"dependencies": { | ||
"cheerio": "^0.19.0", | ||
"cheerio": "^0.22.0", | ||
"ejs": "^2.4.1", | ||
"hexo-env": "^0.0.1", | ||
"hexo-pagination": "^0.0.2", | ||
"hexo-env": "^0.0.1", | ||
"html-minifier": "^2.1.2", | ||
"image-size" : "^0.5.0", | ||
"jsdom": "^9.5.0", | ||
"image-size": "^0.5.0", | ||
"log-util": "^1.1.2", | ||
"mkdirp": "^0.5.1", | ||
@@ -32,3 +32,5 @@ "object-assign": "^2.0.0" | ||
"hexo", | ||
"blog", | ||
"amp", | ||
"html", | ||
"google", | ||
@@ -35,0 +37,0 @@ "mobile", |
# hexo-generator-amp | ||
AMP ⚡ HTML (Accelerated Mobile Pages) generator for [Hexo](https://github.com/hexojs/hexo). | ||
AMP ⚡ HTML (Accelerated Mobile Pages Project HTML) generator for [Hexo](https://github.com/hexojs/hexo). | ||
## Orverview | ||
`hexo-generator-amp` helps you hexo's projects that automatically generates AMP. | ||
Output file path is the `./amp/index.html`. Also, You can freely choose the template(.ejs) and style(.css). | ||
This plugin automatically generates [AMP HTML](https://www.ampproject.org/docs/get_started/about-amp.html) pages. | ||
Output file path is `./your-parmalink/amp/`. Also, You can freely choose the template(.ejs) and style(.css). | ||
Documents : [read me](https://tea3.github.io/p/published-hexo-generator-amp/) | ||
## DEMO | ||
DEMO : [HTML page](https://tea3.github.io/p/hexo-markdown-notation/index.html) | [generated AMP HTML page](https://tea3.github.io/p/hexo-markdown-notation/amp/index.html#development=1) | ||
DEMO : [HTML page](https://tea3.github.io/p/hexo-markdown-notation/) | [generated AMP HTML page](https://tea3.github.io/p/hexo-markdown-notation/amp/#development=1) | ||
### Supports the following external services | ||
- [Youtube](https://github.com/ampproject/amphtml/blob/master/examples/youtube.amp.html) | ||
- [Vimeo](https://github.com/ampproject/amphtml/blob/master/examples/vimeo.amp.html) | ||
- [Instagram](https://github.com/ampproject/amphtml/blob/master/examples/instagram.amp.html) | ||
## Installation | ||
@@ -29,6 +20,8 @@ | ||
#### 1. Edit your theme | ||
## Usage | ||
Then add this theme in your `themes/(your-theme)/layout/_partial/head.ejs`. | ||
### 1. Edit your theme | ||
First, add the following in your template files. For example , Please edit `themes/(your-theme)/layout/_partial/head.ejs`. | ||
``` ejs | ||
@@ -41,8 +34,8 @@ <% if (is_post() && config.generator_amp){ %> | ||
#### 2. Add your config file | ||
### 2. Add your config file | ||
Please set the following options . Please edit your config file (`_config.yml`). | ||
Please set the following options. Please edit `_config.yml`. | ||
``` yaml | ||
# Quick start Settings of hexo-amp-generator | ||
# The following settings is the quick start options. | ||
generator_amp: | ||
@@ -63,6 +56,16 @@ templateDir: amp-template | ||
#### 3. Validate AMP | ||
### 3. Run server | ||
Output file path is the `./amp/index.html`. Validate your AMP pages. Please see below | ||
Starts a local server. By default, this is at `http://localhost:4000/`. | ||
``` bash | ||
$ hexo server | ||
``` | ||
### 4. Validate AMP Pages | ||
This plugin generated the AMP HTML. Output file path is `./your-parmalink/amp/`. | ||
Now validate your AMP pages. Open your AMP page in your browser. Open the Chrome DevTools console and check for validation errors. Please Append `http://localhost:4000/your-parmalink/amp/#development=1` to the URL. Please see below for the details. | ||
> Accelerated Mobile Pages Project - [Validate AMP Pages](https://www.ampproject.org/docs/guides/validate.html) | ||
@@ -75,3 +78,3 @@ | ||
`hexo-generator-amp` can set the following options. | ||
This plugin can set the following options. Please edit `_config.yml`. | ||
@@ -118,4 +121,2 @@ ``` yaml | ||
### A description of the options | ||
@@ -202,3 +203,11 @@ | ||
## Supports external services | ||
Supports the following external services. | ||
- [Youtube](https://github.com/ampproject/amphtml/blob/master/examples/youtube.amp.html) | ||
- [Vimeo](https://github.com/ampproject/amphtml/blob/master/examples/vimeo.amp.html) | ||
- [Instagram](https://github.com/ampproject/amphtml/blob/master/examples/instagram.amp.html) | ||
## License | ||
@@ -205,0 +214,0 @@ |
94066
16
2388
211
+ Addedlog-util@^1.1.2
+ Addedansi-regex@2.1.1(transitive)
+ Addedansi-styles@2.2.1(transitive)
+ Addedchalk@1.1.3(transitive)
+ Addedcheerio@0.22.0(transitive)
+ Addedcss-select@1.2.0(transitive)
+ Addedcss-what@2.1.3(transitive)
+ Addeddateformat@2.2.0(transitive)
+ Addeddomhandler@2.4.2(transitive)
+ Addedescape-string-regexp@1.0.5(transitive)
+ Addedhas-ansi@2.0.0(transitive)
+ Addedhtmlparser2@3.10.1(transitive)
+ Addedlodash.assignin@4.2.0(transitive)
+ Addedlodash.bind@4.2.1(transitive)
+ Addedlodash.defaults@4.2.0(transitive)
+ Addedlodash.filter@4.6.0(transitive)
+ Addedlodash.flatten@4.4.0(transitive)
+ Addedlodash.foreach@4.5.0(transitive)
+ Addedlodash.map@4.6.0(transitive)
+ Addedlodash.merge@4.6.2(transitive)
+ Addedlodash.pick@4.4.0(transitive)
+ Addedlodash.reduce@4.6.0(transitive)
+ Addedlodash.reject@4.6.0(transitive)
+ Addedlodash.some@4.6.0(transitive)
+ Addedlog-util@1.2.0(transitive)
+ Addedreadable-stream@3.6.2(transitive)
+ Addedstring_decoder@1.3.0(transitive)
+ Addedstrip-ansi@3.0.1(transitive)
+ Addedsupports-color@2.0.0(transitive)
+ Addedutil-deprecate@1.0.2(transitive)
- Removedjsdom@^9.5.0
- Removedabab@1.0.4(transitive)
- Removedacorn@4.0.13(transitive)
- Removedacorn-globals@3.1.0(transitive)
- Removedajv@6.12.6(transitive)
- Removedarray-equal@1.0.2(transitive)
- Removedasn1@0.2.6(transitive)
- Removedassert-plus@1.0.0(transitive)
- Removedasynckit@0.4.0(transitive)
- Removedaws-sign2@0.7.0(transitive)
- Removedaws4@1.13.2(transitive)
- Removedbcrypt-pbkdf@1.0.2(transitive)
- Removedcaseless@0.12.0(transitive)
- Removedcheerio@0.19.0(transitive)
- Removedcombined-stream@1.0.8(transitive)
- Removedcontent-type-parser@1.0.2(transitive)
- Removedcore-util-is@1.0.21.0.3(transitive)
- Removedcss-select@1.0.0(transitive)
- Removedcss-what@1.0.0(transitive)
- Removedcssom@0.3.8(transitive)
- Removedcssstyle@0.2.37(transitive)
- Removeddashdash@1.14.1(transitive)
- Removeddeep-is@0.1.4(transitive)
- Removeddelayed-stream@1.0.0(transitive)
- Removeddomhandler@2.3.0(transitive)
- Removeddomutils@1.4.3(transitive)
- Removedecc-jsbn@0.1.2(transitive)
- Removedentities@1.0.0(transitive)
- Removedescodegen@1.14.3(transitive)
- Removedesprima@4.0.1(transitive)
- Removedestraverse@4.3.0(transitive)
- Removedesutils@2.0.3(transitive)
- Removedextend@3.0.2(transitive)
- Removedextsprintf@1.3.0(transitive)
- Removedfast-deep-equal@3.1.3(transitive)
- Removedfast-json-stable-stringify@2.1.0(transitive)
- Removedfast-levenshtein@2.0.6(transitive)
- Removedforever-agent@0.6.1(transitive)
- Removedform-data@2.3.3(transitive)
- Removedgetpass@0.1.7(transitive)
- Removedhar-schema@2.0.0(transitive)
- Removedhar-validator@5.1.5(transitive)
- Removedhtml-encoding-sniffer@1.0.2(transitive)
- Removedhtmlparser2@3.8.3(transitive)
- Removedhttp-signature@1.2.0(transitive)
- Removediconv-lite@0.4.24(transitive)
- Removedis-typedarray@1.0.0(transitive)
- Removedisarray@0.0.1(transitive)
- Removedisstream@0.1.2(transitive)
- Removedjsbn@0.1.1(transitive)
- Removedjsdom@9.12.0(transitive)
- Removedjson-schema@0.4.0(transitive)
- Removedjson-schema-traverse@0.4.1(transitive)
- Removedjson-stringify-safe@5.0.1(transitive)
- Removedjsprim@1.4.2(transitive)
- Removedlevn@0.3.0(transitive)
- Removedlodash@3.10.1(transitive)
- Removedmime-db@1.52.0(transitive)
- Removedmime-types@2.1.35(transitive)
- Removednwmatcher@1.4.4(transitive)
- Removedoauth-sign@0.9.0(transitive)
- Removedoptionator@0.8.3(transitive)
- Removedparse5@1.5.1(transitive)
- Removedperformance-now@2.1.0(transitive)
- Removedprelude-ls@1.1.2(transitive)
- Removedpsl@1.15.0(transitive)
- Removedpunycode@2.3.1(transitive)
- Removedqs@6.5.3(transitive)
- Removedreadable-stream@1.1.14(transitive)
- Removedrequest@2.88.2(transitive)
- Removedsafer-buffer@2.1.2(transitive)
- Removedsax@1.4.1(transitive)
- Removedsource-map@0.6.1(transitive)
- Removedsshpk@1.18.0(transitive)
- Removedstring_decoder@0.10.31(transitive)
- Removedsymbol-tree@3.2.4(transitive)
- Removedtough-cookie@2.5.0(transitive)
- Removedtr46@0.0.3(transitive)
- Removedtunnel-agent@0.6.0(transitive)
- Removedtweetnacl@0.14.5(transitive)
- Removedtype-check@0.3.2(transitive)
- Removeduri-js@4.4.1(transitive)
- Removeduuid@3.4.0(transitive)
- Removedverror@1.10.0(transitive)
- Removedwebidl-conversions@3.0.14.0.2(transitive)
- Removedwhatwg-encoding@1.0.5(transitive)
- Removedwhatwg-url@4.8.0(transitive)
- Removedword-wrap@1.2.5(transitive)
- Removedxml-name-validator@2.0.1(transitive)
Updatedcheerio@^0.22.0