grunt-responsive-images-extender
Advanced tools
Comparing version 0.1.0 to 1.0.0
@@ -38,3 +38,24 @@ /* | ||
options: { | ||
useSizes: true | ||
sizes: [{ | ||
selector: '.fig-hero img', | ||
sizeList: [{ | ||
cond: 'max-width: 30em', | ||
size: '100vw' | ||
},{ | ||
cond: 'max-width: 50em', | ||
size: '50vw' | ||
},{ | ||
cond: 'default', | ||
size: 'calc(33vw - 100px)' | ||
}] | ||
},{ | ||
selector: '[alt]', | ||
sizeList: [{ | ||
cond: 'max-width: 20em', | ||
size: '80vw' | ||
},{ | ||
cond: 'default', | ||
size: '90vw' | ||
}] | ||
}] | ||
}, | ||
@@ -61,3 +82,3 @@ files: { | ||
options: { | ||
useSizes: true, | ||
ignore: ['.ignore-me'], | ||
srcset: [{ | ||
@@ -79,2 +100,15 @@ suffix: '-200', | ||
value: '2x' | ||
}], | ||
sizes: [{ | ||
selector: '.fig-hero img', | ||
sizeList: [{ | ||
cond: 'max-width: 30em', | ||
size: '100vw' | ||
},{ | ||
cond: 'max-width: 50em', | ||
size: '50vw' | ||
},{ | ||
cond: 'default', | ||
size: 'calc(33vw - 100px)' | ||
}] | ||
}] | ||
@@ -81,0 +115,0 @@ }, |
{ | ||
"name": "grunt-responsive-images-extender", | ||
"description": "Extend HTML image tags with srcset and sizes attributes to leverage native responsive images.", | ||
"version": "0.1.0", | ||
"version": "1.0.0", | ||
"homepage": "https://github.com/smaxtastic/grunt-responsive-images-extender", | ||
@@ -40,4 +40,18 @@ "author": { | ||
"keywords": [ | ||
"gruntplugin", "grunt", "responsive", "img", "image", "images", "extend", "convert", "converter", "srcset", "sizes", "responsivedesign" | ||
] | ||
} | ||
"gruntplugin", | ||
"grunt", | ||
"responsive", | ||
"img", | ||
"image", | ||
"images", | ||
"extend", | ||
"convert", | ||
"converter", | ||
"srcset", | ||
"sizes", | ||
"responsivedesign" | ||
], | ||
"dependencies": { | ||
"cheerio": "^0.17.0" | ||
} | ||
} |
101
README.md
@@ -28,2 +28,4 @@ # grunt-responsive-images-extender | ||
This plugin uses [Cheerio](https://github.com/cheeriojs/cheerio) to traverse and modify the DOM. | ||
In your project's Gruntfile, add a section named `responsive_images_extender` to the data object passed into `grunt.initConfig()`. | ||
@@ -52,17 +54,36 @@ | ||
* **options.useSizes**<br> | ||
*Type:* `Boolean`<br> | ||
*Default:* `false`<br> | ||
Determines whether a `sizes` attribute is added. If set to `true`, the `options.sizes` array is used to build the attribute value. | ||
* **options.sizes**<br> | ||
*Type:* `Array`<br> | ||
*Default:* `[{cond: 'max-width: 30em', size: '100vw'}, {cond: 'max-width: 50em', size: '50vw'}, {cond: 'default', size: 'calc(33vw - 100px)'}]`<br> | ||
*Default:* none<br> | ||
An array of objects containing the conditions and sizes of our size tableau. The default values are adopted from said [article by Yoav Weiss](https://dev.opera.com/articles/native-responsive-images/). | ||
An array of objects containing the selectors (standard CSS selectors, like `.some-class`, `#an-id` or `img[src^="http://"]`) and their respective size tableau. An example could look like this: | ||
```js | ||
sizes: [{ | ||
selector: '#post-header', | ||
sizeList: [{ | ||
cond: 'max-width: 30em', | ||
size: '100vw' | ||
},{ | ||
cond: 'max-width: 50em', | ||
size: '50vw' | ||
},{ | ||
cond: 'default', | ||
size: 'calc(33vw - 100px)' | ||
}] | ||
},{ | ||
selector: '.hero img', | ||
sizeList: [{ | ||
cond: 'max-width: 20em', | ||
size: '80vw' | ||
},{ | ||
cond: 'default', | ||
size: '90vw' | ||
}] | ||
}] | ||
``` | ||
If you want to set a default size value, make sure to set the condition to `default` and add the object at the end of the array. Otherwise the default value renders the following media conditions obsolete, since the browser walks through the list specified in `sizes` and looks for the first matching one. | ||
This array is optional and only used when `options.useSizes` is set to `true`. Otherwise the browser assumes the size `100vw` for all images. | ||
This array is optional and without specifying one the browser assumes the size `100vw` for all images. | ||
@@ -75,6 +96,12 @@ * **options.srcsetRetina**<br> | ||
* **options.ignore**<br> | ||
*Type:* `Array`<br> | ||
*Default:* `[]`<br> | ||
An array of selectors you want to ignore. | ||
### Usage Examples | ||
#### Default Options | ||
Using the default options will make the task search for HTML `<img>` tags that have no `width` or `srcset` attribute already. Once found | ||
Using the default options will make the task search for HTML `<img>` tags that have no `width` or `srcset` attribute already. | ||
@@ -121,3 +148,2 @@ ```js | ||
options: { | ||
useSizes: true, | ||
srcset: [{ | ||
@@ -139,2 +165,15 @@ suffix: '-200', | ||
value: '2x' | ||
}], | ||
sizes: [{ | ||
selector: '.article-img', | ||
sizeList: [{ | ||
cond: 'max-width: 30em', | ||
size: '100vw' | ||
},{ | ||
cond: 'max-width: 50em', | ||
size: '50vw' | ||
},{ | ||
cond: 'default', | ||
size: 'calc(33vw - 100px)' | ||
}] | ||
}] | ||
@@ -156,3 +195,3 @@ }, | ||
```html | ||
<img alt="A simple image" src="simple.jpg" title="A simple image"> | ||
<img alt="A simple image" src="simple.jpg" class="article-img"> | ||
@@ -165,3 +204,3 @@ <img src="non_responsive.png" width="150"> | ||
```html | ||
<img alt="A simple image" src="simple.jpg" | ||
<img alt="A simple image" src="simple.jpg" class=".article-img" | ||
srcset="simple-200.jpg 200w, | ||
@@ -172,11 +211,30 @@ simple-400.jpg 400w, | ||
(max-width: 50em) 50vw, | ||
calc(33vw - 100px)" | ||
title="A simple image"> | ||
calc(33vw - 100px)"> | ||
<img src="non_responsive.png" | ||
<img src="non_responsive.png" width="150" | ||
srcset="non_responsive_x1.5.png 1.5x, | ||
non_responsive_x2.png 2x" | ||
width="150"> | ||
non_responsive_x2.png 2x"> | ||
``` | ||
#### Ignoring images | ||
Sometimes you want to exclude certain images from the algorithm. You can achieve this with the `ignore` option: | ||
```js | ||
grunt.initConfig({ | ||
responsive_images_extender: { | ||
ignoring: { | ||
options: { | ||
ignore: ['.icons', '#logo', 'figure img'] | ||
}, | ||
files: [{ | ||
expand: true, | ||
src: ['**/*.{html,htm,php}'], | ||
cwd: 'src/', | ||
dest: 'build/' | ||
}] | ||
} | ||
} | ||
}); | ||
``` | ||
Please see this task's [Gruntfile](https://github.com/smaxtastic/grunt-responsive-images-extender/blob/master/Gruntfile.js) for more usage examples. | ||
@@ -199,4 +257,9 @@ | ||
*0.0.1* | ||
*1.0.0* | ||
* The `sizes` option allows users to specify multiple sizes for different selectors, for example for hero images, article images or icons. | ||
* Added the `ignore` option. | ||
*0.1.0* | ||
* Initial commit and first version |
@@ -7,2 +7,7 @@ /* | ||
* Licensed under the MIT license. | ||
* | ||
* Extend HTML image tags with srcset and sizes attributes to leverage native responsive images. | ||
* | ||
* @author Stephan Max (http://twitter.com/smaxtastic) | ||
* @version 0.1.0 | ||
*/ | ||
@@ -13,15 +18,7 @@ | ||
module.exports = function(grunt) { | ||
var cheerio = require('cheerio'); | ||
var DEFAULT_OPTIONS = { | ||
useSizes: false, | ||
sizes: [{ | ||
cond: 'max-width: 30em', | ||
size: '100vw' | ||
},{ | ||
cond: 'max-width: 50em', | ||
size: '50vw' | ||
},{ | ||
cond: 'default', | ||
size: 'calc(33vw - 100px)' | ||
}], | ||
ignore: [], | ||
srcset: [{ | ||
@@ -40,5 +37,5 @@ suffix: '-small', | ||
grunt.registerMultiTask('responsive_images_extender', 'Extend HTML image tags with srcset and sizes attributes to leverage native responsive images.', function() { | ||
var numOfFiles = this.files.length; | ||
var options = this.options(DEFAULT_OPTIONS); | ||
var processedImgs = 0; | ||
var numOfFiles = this.files.length, | ||
options = this.options(DEFAULT_OPTIONS), | ||
processedImages = 0; | ||
@@ -54,23 +51,9 @@ function buildAttributeList(optionList, buildAttribute) { | ||
function parseAndExtendImg(content, options) { | ||
var numberOfMatches = 0; | ||
var findImg = /(<img[^>]+src=")(.+)(\.[^"]+)("[^>]*>)/gi; | ||
var $ = cheerio.load(content), | ||
images = $('img:not(' + options.ignore.join(', ') + ')'); | ||
content = content.replace(findImg, function(match, front, filePath, fileExt, remainder) { | ||
images.each(function() { | ||
var separatorPos, filePath, fileExt, | ||
image = $(this); | ||
var sources, sizes; | ||
function buildReplacement(srcset, sizes) { | ||
var replacement = front + filePath + fileExt; | ||
if (typeof srcset !== 'undefined') { | ||
replacement += '" srcset="' + srcset; | ||
} | ||
if (typeof sizes !== 'undefined') { | ||
replacement += '" sizes="' + sizes; | ||
} | ||
return replacement + remainder; | ||
} | ||
function buildSrc(option) { | ||
@@ -89,6 +72,7 @@ return filePath + option.suffix + fileExt + ' ' + option.value; | ||
var retinaReady = 'srcsetRetina' in options; | ||
var isNonResponsive = match.indexOf('width') !== -1; | ||
var hasSrcset = match.indexOf('srcset') !== -1; | ||
var hasSizes = match.indexOf('sizes') !== -1; | ||
var retinaReady = 'srcsetRetina' in options, | ||
useSizes = 'sizes' in options, | ||
isNonResponsive = image.attr('width') !== undefined, | ||
hasSrcset = image.attr('srcset') !== undefined, | ||
hasSizes = image.attr('sizes') !== undefined; | ||
@@ -99,42 +83,41 @@ // Don't process <img> tags unnecessarily | ||
(!isNonResponsive && hasSrcset && hasSizes) || | ||
(hasSrcset && !options.useSizes)) { | ||
return match; | ||
(hasSrcset && !useSizes)) { | ||
return; | ||
} | ||
filePath = image.attr('src'); | ||
if (filePath === undefined) { | ||
grunt.log.verbose.error('Found an image without a source: ' + $.html(image)); | ||
return; | ||
} | ||
separatorPos = filePath.lastIndexOf('.'); | ||
fileExt = filePath.slice(separatorPos); | ||
filePath = filePath.slice(0, separatorPos); | ||
if (isNonResponsive) { | ||
sources = buildAttributeList(options.srcsetRetina, buildSrc); | ||
grunt.log.verbose.ok( | ||
'Detected width attribute for ' + filePath + fileExt + | ||
' (not responsive, but retina-ready)' | ||
); | ||
image.attr('srcset', buildAttributeList(options.srcsetRetina, buildSrc)); | ||
grunt.log.verbose.ok('Detected width attribute for ' + filePath + fileExt + ' (not responsive, but retina-ready)'); | ||
} | ||
else { | ||
if (!hasSrcset) { | ||
sources = buildAttributeList(options.srcset, buildSrc); | ||
grunt.log.verbose.ok( | ||
'Extend ' + filePath + fileExt + ' with srcset attribute' | ||
); | ||
image.attr('srcset', buildAttributeList(options.srcset, buildSrc)); | ||
grunt.log.verbose.ok('Extend ' + filePath + fileExt + ' with srcset attribute'); | ||
} | ||
if (!hasSizes && options.useSizes) { | ||
sizes = buildAttributeList(options.sizes, buildSize); | ||
grunt.log.verbose.ok( | ||
'Extend ' + filePath + fileExt + ' with sizes attribute' | ||
); | ||
if (!hasSizes && useSizes) { | ||
options.sizes.some(function (s) { | ||
if (image.is(s.selector)) { | ||
image.attr('sizes', buildAttributeList(s.sizeList, buildSize)); | ||
grunt.log.verbose.ok('Extend ' + filePath + fileExt + ' with sizes attribute (selector: ' + s.selector + ')'); | ||
return true; | ||
} | ||
}); | ||
} | ||
} | ||
numberOfMatches++; | ||
return buildReplacement(sources, sizes); | ||
}); | ||
return {content: content, count: numberOfMatches}; | ||
return {content: $.html(), count: images.length}; | ||
} | ||
grunt.log.writeln( | ||
'Found ' + | ||
numOfFiles.toString().cyan + ' ' + | ||
grunt.util.pluralize(numOfFiles, 'file/files') | ||
); | ||
grunt.log.writeln('Found ' + numOfFiles.toString().cyan + ' ' + grunt.util.pluralize(numOfFiles, 'file/files')); | ||
@@ -146,14 +129,8 @@ this.files.forEach(function(f) { | ||
grunt.file.write(f.dest, result.content); | ||
processedImgs += result.count; | ||
processedImages += result.count; | ||
}); | ||
grunt.log.writeln( | ||
'Processed ' + | ||
processedImgs.toString().cyan + | ||
' <img> ' + | ||
grunt.util.pluralize(processedImgs, 'tag/tags') | ||
); | ||
grunt.log.writeln('Processed ' + processedImages.toString().cyan + ' <img> ' + grunt.util.pluralize(processedImages, 'tag/tags')); | ||
}); | ||
}; |
@@ -5,25 +5,4 @@ 'use strict'; | ||
/* | ||
======== A Handy Little Nodeunit Reference ======== | ||
https://github.com/caolan/nodeunit | ||
Test methods: | ||
test.expect(numAssertions) | ||
test.done() | ||
Test assertions: | ||
test.ok(value, [message]) | ||
test.equal(actual, expected, [message]) | ||
test.notEqual(actual, expected, [message]) | ||
test.deepEqual(actual, expected, [message]) | ||
test.notDeepEqual(actual, expected, [message]) | ||
test.strictEqual(actual, expected, [message]) | ||
test.notStrictEqual(actual, expected, [message]) | ||
test.throws(block, [error], [message]) | ||
test.doesNotThrow(block, [error], [message]) | ||
test.ifError(value) | ||
*/ | ||
exports.responsive_images_extender = { | ||
setUp: function(done) { | ||
// setup here if necessary | ||
done(); | ||
@@ -36,3 +15,3 @@ }, | ||
var expected = grunt.file.read('test/expected/default_options'); | ||
test.equal(actual, expected, 'should describe what the default behavior is.'); | ||
test.equal(actual, expected, 'Should describe what the default behavior is.'); | ||
@@ -46,3 +25,3 @@ test.done(); | ||
var expected = grunt.file.read('test/expected/retina'); | ||
test.equal(actual, expected, 'should describe what the retina behavior is.'); | ||
test.equal(actual, expected, 'Should describe what the retina behavior is.'); | ||
@@ -56,3 +35,3 @@ test.done(); | ||
var expected = grunt.file.read('test/expected/use_sizes'); | ||
test.equal(actual, expected, 'should describe what the sizes attribute behavior is.'); | ||
test.equal(actual, expected, 'Should describe what the sizes attribute behavior is.'); | ||
@@ -66,3 +45,3 @@ test.done(); | ||
var expected = grunt.file.read('test/expected/all'); | ||
test.equal(actual, expected, 'should describe what the complete behavior is.'); | ||
test.equal(actual, expected, 'Should describe what the complete behavior is.'); | ||
@@ -69,0 +48,0 @@ test.done(); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
21812
0
256
2
266
+ Addedcheerio@^0.17.0
+ AddedCSSselect@0.4.1(transitive)
+ AddedCSSwhat@0.4.7(transitive)
+ Addedcheerio@0.17.0(transitive)
+ Addedcore-util-is@1.0.3(transitive)
+ Addeddom-serializer@0.0.1(transitive)
+ Addeddomelementtype@1.1.3(transitive)
+ Addeddomhandler@2.2.1(transitive)
+ Addeddomutils@1.4.31.5.1(transitive)
+ Addedentities@1.0.01.1.2(transitive)
+ Addedhtmlparser2@3.7.3(transitive)
+ Addedisarray@0.0.1(transitive)
+ Addedreadable-stream@1.1.14(transitive)
+ Addedstring_decoder@0.10.31(transitive)