├── .gitignore ├── test ├── assets │ ├── image.png │ ├── image-2x.png │ ├── do_nothing.png │ ├── image-1440.png │ └── image-thumbnail.png ├── fixtures │ ├── testing.html │ └── testing2.html ├── expected │ ├── all.html │ ├── default_options.html │ ├── use_sizes.html │ ├── polyfill_lazyloading.html │ └── multi_src.html └── responsive_images_extender_test.js ├── .jshintrc ├── LICENSE-MIT ├── package.json ├── Gruntfile.js ├── tasks └── responsive_images_extender.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | tmp 4 | .nvmrc 5 | -------------------------------------------------------------------------------- /test/assets/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephanmax/grunt-responsive-images-extender/HEAD/test/assets/image.png -------------------------------------------------------------------------------- /test/assets/image-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephanmax/grunt-responsive-images-extender/HEAD/test/assets/image-2x.png -------------------------------------------------------------------------------- /test/assets/do_nothing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephanmax/grunt-responsive-images-extender/HEAD/test/assets/do_nothing.png -------------------------------------------------------------------------------- /test/assets/image-1440.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephanmax/grunt-responsive-images-extender/HEAD/test/assets/image-1440.png -------------------------------------------------------------------------------- /test/assets/image-thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stephanmax/grunt-responsive-images-extender/HEAD/test/assets/image-thumbnail.png -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "boss": true, 11 | "eqnull": true, 12 | "node": true 13 | } 14 | -------------------------------------------------------------------------------- /test/fixtures/testing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Testing | grunt-responsive-images-extender 6 | 11 | 12 | 13 | A simple image 14 | 15 | Sizes please 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /test/fixtures/testing2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Testing | grunt-responsive-images-extender 6 | 11 | 12 | 13 | A simple image 14 | 15 | Sizes please 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Stephan Max 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grunt-responsive-images-extender", 3 | "description": "Extend HTML image tags with srcset and sizes attributes to leverage native responsive images.", 4 | "version": "3.0.0", 5 | "homepage": "https://github.com/stephanmax/grunt-responsive-images-extender", 6 | "author": { 7 | "name": "Stephan Max", 8 | "email": "stephan.max@gmail.com", 9 | "url": "http://stephanmax.is" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/stephanmax/grunt-responsive-images-extender.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/stephanmax/grunt-responsive-images-extender/issues" 17 | }, 18 | "license": "MIT", 19 | "engines": { 20 | "node": ">= 6.0.0" 21 | }, 22 | "scripts": { 23 | "test": "grunt test" 24 | }, 25 | "devDependencies": { 26 | "grunt": "~0.4.0", 27 | "grunt-contrib-clean": "^0.5.0", 28 | "grunt-contrib-jshint": "^0.9.2", 29 | "grunt-contrib-nodeunit": "^0.3.3" 30 | }, 31 | "peerDependencies": { 32 | "grunt": ">=0.4.0" 33 | }, 34 | "keywords": [ 35 | "gruntplugin", 36 | "grunt", 37 | "responsive", 38 | "responsivedesign", 39 | "rwd", 40 | "img", 41 | "image", 42 | "images", 43 | "extend", 44 | "convert", 45 | "converter", 46 | "srcset", 47 | "sizes" 48 | ], 49 | "dependencies": { 50 | "cheerio": "^0.19.0", 51 | "image-size": "^0.3.5" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/expected/all.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Testing | grunt-responsive-images-extender 6 | 11 | 12 | 13 | A simple image 14 | 15 | Sizes please 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /test/expected/default_options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Testing | grunt-responsive-images-extender 6 | 11 | 12 | 13 | A simple image 14 | 15 | Sizes please 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /test/expected/use_sizes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Testing | grunt-responsive-images-extender 6 | 11 | 12 | 13 | A simple image 14 | 15 | Sizes please 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /test/expected/polyfill_lazyloading.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Testing | grunt-responsive-images-extender 6 | 11 | 12 | 13 | A simple image 14 | 15 | Sizes please 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /test/responsive_images_extender_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | 5 | exports.responsive_images_extender = { 6 | setUp: function(done) { 7 | done(); 8 | }, 9 | default_options: function(test) { 10 | test.expect(1); 11 | 12 | var actual = grunt.file.read('test/tmp/default_options.html'); 13 | var expected = grunt.file.read('test/expected/default_options.html'); 14 | test.equal(actual, expected, 'Should describe what the default behavior is.'); 15 | 16 | test.done(); 17 | }, 18 | retina: function(test) { 19 | test.expect(1); 20 | 21 | var actual = grunt.file.read('test/tmp/polyfill_lazyloading.html'); 22 | var expected = grunt.file.read('test/expected/polyfill_lazyloading.html'); 23 | test.equal(actual, expected, 'Should describe what the polyfill and lazyloading behavior is.'); 24 | 25 | test.done(); 26 | }, 27 | use_sizes: function(test) { 28 | test.expect(1); 29 | 30 | var actual = grunt.file.read('test/tmp/use_sizes.html'); 31 | var expected = grunt.file.read('test/expected/use_sizes.html'); 32 | test.equal(actual, expected, 'Should describe what the sizes attribute behavior is.'); 33 | 34 | test.done(); 35 | }, 36 | all: function(test) { 37 | test.expect(1); 38 | 39 | var actual = grunt.file.read('test/tmp/all.html'); 40 | var expected = grunt.file.read('test/expected/all.html'); 41 | test.equal(actual, expected, 'Should describe what the complete behavior is.'); 42 | 43 | test.done(); 44 | }, 45 | multi_src: function(test) { 46 | test.expect(1); 47 | 48 | var actual = grunt.file.read('test/tmp/multi_src.html'); 49 | var expected = grunt.file.read('test/expected/multi_src.html'); 50 | test.equal(actual, expected, 'Should describe what the multi file src behavior is.'); 51 | 52 | test.done(); 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /test/expected/multi_src.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Testing | grunt-responsive-images-extender 6 | 11 | 12 | 13 | A simple image 14 | 15 | Sizes please 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | Testing | grunt-responsive-images-extender 42 | 47 | 48 | 49 | A simple image 50 | 51 | Sizes please 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
62 | 63 |
64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-responsive-images-extender 3 | * https://github.com/smaxtastic/grunt-responsive-images-extender 4 | * 5 | * Copyright (c) 2014 Stephan Max 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | module.exports = function(grunt) { 12 | 13 | grunt.initConfig({ 14 | jshint: { 15 | all: [ 16 | 'Gruntfile.js', 17 | 'tasks/*.js', 18 | '<%= nodeunit.tests %>' 19 | ], 20 | options: { 21 | jshintrc: '.jshintrc' 22 | } 23 | }, 24 | 25 | clean: { 26 | tests: ['test/tmp'] 27 | }, 28 | 29 | // Four example configurations to be run (and then tested) 30 | responsive_images_extender: { 31 | options: { 32 | baseDir: 'test' 33 | }, 34 | default_options: { 35 | files: [{ 36 | src: ['test/fixtures/testing.html'], 37 | dest: 'test/tmp/default_options.html' 38 | }] 39 | }, 40 | use_sizes: { 41 | options: { 42 | sizes: [{ 43 | selector: '.fig-hero img', 44 | sizeList: [{ 45 | cond: 'max-width: 30em', 46 | size: '100vw' 47 | },{ 48 | cond: 'max-width: 50em', 49 | size: '50vw' 50 | },{ 51 | cond: 'default', 52 | size: 'calc(33vw - 100px)' 53 | }] 54 | },{ 55 | selector: '[alt]', 56 | sizeList: [{ 57 | cond: 'max-width: 20em', 58 | size: '80vw' 59 | },{ 60 | cond: 'default', 61 | size: '90vw' 62 | }] 63 | }] 64 | }, 65 | files: [{ 66 | src: ['test/fixtures/testing.html'], 67 | dest: 'test/tmp/use_sizes.html' 68 | }] 69 | }, 70 | polyfill_lazyloading: { 71 | options: { 72 | srcsetAttributeName: 'data-srcset', 73 | srcAttribute: 'none' 74 | }, 75 | files: [{ 76 | src: ['test/fixtures/testing.html'], 77 | dest: 'test/tmp/polyfill_lazyloading.html' 78 | }] 79 | }, 80 | all: { 81 | options: { 82 | ignore: ['.ignore-me'], 83 | srcAttribute: 'smallest', 84 | sizes: [{ 85 | selector: '.fig-hero img', 86 | sizeList: [{ 87 | cond: 'max-width: 30em', 88 | size: '100vw' 89 | },{ 90 | cond: 'max-width: 50em', 91 | size: '50vw' 92 | },{ 93 | cond: 'default', 94 | size: 'calc(33vw - 100px)' 95 | }] 96 | }] 97 | }, 98 | files: [{ 99 | src: ['test/fixtures/testing.html'], 100 | dest: 'test/tmp/all.html' 101 | }] 102 | }, 103 | multi_src: { 104 | files: [{ 105 | src: ['test/fixtures/testing.html', 'test/fixtures/testing2.html'], 106 | dest: 'test/tmp/multi_src.html' 107 | }] 108 | } 109 | }, 110 | 111 | nodeunit: { 112 | tests: ['test/*_test.js'] 113 | } 114 | 115 | }); 116 | 117 | grunt.loadTasks('tasks'); 118 | 119 | grunt.loadNpmTasks('grunt-contrib-jshint'); 120 | grunt.loadNpmTasks('grunt-contrib-clean'); 121 | grunt.loadNpmTasks('grunt-contrib-nodeunit'); 122 | 123 | grunt.registerTask('test', ['clean', 'responsive_images_extender', 'nodeunit']); 124 | 125 | grunt.registerTask('default', ['jshint', 'test']); 126 | }; 127 | -------------------------------------------------------------------------------- /tasks/responsive_images_extender.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-responsive-images-extender 3 | * https://github.com/smaxtastic/grunt-responsive-images-extender 4 | * 5 | * Copyright (c) 2014 Stephan Max 6 | * Licensed under the MIT license. 7 | * 8 | * Extend HTML image tags with srcset and sizes attributes to leverage native responsive images. 9 | * 10 | * @author Stephan Max (http://stephanmax.is) 11 | * @version 2.0.0 12 | */ 13 | 14 | module.exports = function(grunt) { 15 | 'use strict'; 16 | 17 | var fs = require('fs'); 18 | var path = require('path'); 19 | var cheerio = require('cheerio'); 20 | var sizeOf = require('image-size'); 21 | 22 | var DEFAULT_OPTIONS = { 23 | separator: '-', 24 | baseDir: '', 25 | ignore: [], 26 | srcsetAttributeName: 'srcset' 27 | }; 28 | 29 | grunt.registerMultiTask('responsive_images_extender', 'Extend HTML image tags with srcset and sizes attributes to leverage native responsive images.', function() { 30 | var numOfFiles = this.files.length; 31 | var options = this.options(DEFAULT_OPTIONS); 32 | var imgCount = 0; 33 | 34 | var parseAndExtendImg = function(filepath) { 35 | var content = grunt.file.read(filepath); 36 | var $ = cheerio.load(content, {decodeEntities: false}); 37 | var imgElems = $('img:not(' + options.ignore.join(', ') + ')'); 38 | 39 | imgElems.each(function() { 40 | var normalizeImagePath = function(src) { 41 | var pathPrefix; 42 | 43 | if (path.isAbsolute(src)) { 44 | pathPrefix = options.baseDir; 45 | } 46 | else { 47 | pathPrefix = path.dirname(filepath); 48 | } 49 | 50 | return path.parse(path.join(pathPrefix, src)); 51 | }; 52 | 53 | var findMatchingImages = function(path) { 54 | var files = fs.readdirSync(path.dir); 55 | var imageMatch = new RegExp(path.name + '(' + options.separator + '[^' + options.separator + ']*)?' + path.ext + '$'); 56 | 57 | return files.filter(function(filename) { 58 | return imageMatch.test(filename); 59 | }); 60 | }; 61 | 62 | var buildSrcMap = function(imageNames) { 63 | var srcMap = {}; 64 | 65 | imageNames.forEach(function(imageName) { 66 | srcMap[imageName] = sizeOf(path.join(imagePath.dir, imageName)).width; 67 | }); 68 | 69 | return srcMap; 70 | }; 71 | 72 | var buildSrcset = function(srcMap, width) { 73 | var srcset = []; 74 | var candidate; 75 | 76 | for (var img in srcMap) { 77 | candidate = path.posix.join(path.dirname(imgSrc), img); 78 | if (width !== undefined) { 79 | candidate += ' ' + Math.round(srcMap[img] / width * 100) / 100 + 'x'; 80 | } 81 | else { 82 | candidate += ' ' + srcMap[img] + 'w'; 83 | } 84 | srcset.push(candidate); 85 | } 86 | 87 | if (options.srcsetAttributeName !== DEFAULT_OPTIONS.srcsetAttributeName) { 88 | imgElem.attr(DEFAULT_OPTIONS.srcsetAttributeName, null); 89 | } 90 | 91 | return srcset.join(', '); 92 | }; 93 | 94 | var buildSizes = function(sizeList) { 95 | var sizes = []; 96 | 97 | sizeList.forEach(function(s) { 98 | var actualSize = srcMap[imagePath.name + imagePath.ext] + 'px'; 99 | var cond = s.cond.replace('%size%', actualSize); 100 | var size = s.size.replace('%size%', actualSize); 101 | 102 | sizes.push( 103 | cond === 'default' ? size : '(' + cond + ') ' + size 104 | ); 105 | }); 106 | 107 | return sizes.join(', '); 108 | }; 109 | 110 | var setSrcAttribute = function() { 111 | switch (options.srcAttribute) { 112 | case 'none': 113 | imgElem.attr('src', null); 114 | break; 115 | case 'smallest': 116 | var smallestImage = Object.keys(srcMap).map(function(k) { 117 | return [k, srcMap[k]]; 118 | }).reduce(function(a, b) { 119 | return b[1] < a[1] ? b : a; 120 | }); 121 | imgElem.attr('src', path.posix.join(path.dirname(imgSrc), smallestImage[0])); 122 | break; 123 | default: 124 | } 125 | }; 126 | 127 | var imgElem = $(this); 128 | var imgWidth = imgElem.attr('width'); 129 | var imgSrc = imgElem.attr('src'); 130 | 131 | var useSizes = 'sizes' in options; 132 | var isResponsive = imgWidth === undefined; 133 | var hasSrcset = imgElem.attr(options.srcsetAttributeName) !== undefined; 134 | var hasSizes = imgElem.attr('sizes') !== undefined; 135 | 136 | var imagePath; 137 | var imageMatches; 138 | var srcMap; 139 | 140 | if (hasSrcset && (!isResponsive || (isResponsive && hasSizes) || !useSizes)) { 141 | return; 142 | } 143 | 144 | imagePath = normalizeImagePath(imgSrc); 145 | imageMatches = findMatchingImages(imagePath); 146 | 147 | switch (imageMatches.length) { 148 | case 0: 149 | grunt.verbose.error('Found no file for ' + imgSrc.cyan); 150 | return; 151 | case 1: 152 | grunt.verbose.error('Found only one file for ' + imgSrc.cyan); 153 | return; 154 | default: 155 | grunt.verbose.ok('Found ' + imageMatches.length.cyan + ' files for ' + imgSrc.cyan + ': ' + imageMatches); 156 | } 157 | 158 | srcMap = buildSrcMap(imageMatches); 159 | 160 | if (!isResponsive && imgWidth > 0) { 161 | imgElem.attr(options.srcsetAttributeName, buildSrcset(srcMap, imgWidth)); 162 | setSrcAttribute(); 163 | } 164 | else { 165 | if (!hasSrcset) { 166 | imgElem.attr(options.srcsetAttributeName, buildSrcset(srcMap)); 167 | setSrcAttribute(); 168 | } 169 | 170 | if (!hasSizes && useSizes) { 171 | options.sizes.some(function (s) { 172 | if (imgElem.is(s.selector)) { 173 | imgElem.attr('sizes', buildSizes(s.sizeList)); 174 | setSrcAttribute(); 175 | return true; 176 | } 177 | }); 178 | } 179 | } 180 | }); 181 | 182 | return {content: $.html(), count: imgElems.length}; 183 | }; 184 | 185 | this.files.forEach(function(file) { 186 | var contents = file.src.filter(function(filepath) { 187 | if (!grunt.file.exists(filepath)) { 188 | grunt.log.warn('Source file "' + filepath + '" not found.'); 189 | return false; 190 | } else { 191 | return true; 192 | } 193 | }).map(function(filepath) { 194 | var result = parseAndExtendImg(filepath); 195 | imgCount += result.count; 196 | return result.content; 197 | }).join('\n'); 198 | 199 | grunt.file.write(file.dest, contents); 200 | }); 201 | 202 | grunt.log.ok('Processed ' + imgCount.toString().cyan + ' ' + grunt.util.pluralize(imgCount, 'tag/tags')); 203 | }); 204 | 205 | }; 206 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ⛔️ *This repository has been archived and is no longer actively maintained, due to the slow but steady death of the task runner Grunt*. 2 | 3 | # grunt-responsive-images-extender 4 | 5 | > Extend HTML image tags with srcset and sizes attributes to leverage native responsive images. 6 | 7 | ## Getting Started 8 | This plugin requires Grunt `~0.4.0` 9 | 10 | If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command: 11 | 12 | ```shell 13 | npm install grunt-responsive-images-extender --save-dev 14 | ``` 15 | 16 | Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript: 17 | 18 | ```js 19 | grunt.loadNpmTasks('grunt-responsive-images-extender'); 20 | ``` 21 | 22 | ## The "responsive_images_extender" task 23 | 24 | ### Overview 25 | 26 | The responsive_images_extender task will scan your source files for HTML `` tags and extend them with `srcset` and optional `sizes` attributes to leverage native responsive images as described in *Yoav Weiss*' [article](https://dev.opera.com/articles/native-responsive-images/). 27 | 28 | It is therefore the perfect complement to the [responsive_images](https://github.com/andismith/grunt-responsive-images/) task that generates images with different resolutions. Used in combination you enable the browser to make an informed decision which image to download and render. 29 | 30 | This plugin uses [Cheerio](https://github.com/cheeriojs/cheerio) to traverse/modify the DOM and [image-size](https://github.com/netroy/image-size) to read the image sizes straight from the image files. You **don't have to configure** the `srcset` or `srcsetRetina` option anymore, since they get built automatically based on the present image files. 31 | 32 | In your project's Gruntfile, add a section named `responsive_images_extender` to the data object passed into `grunt.initConfig()`. 33 | 34 | ```js 35 | grunt.initConfig({ 36 | responsive_images_extender: { 37 | options: { 38 | // Task-specific options go here. 39 | }, 40 | your_target: { 41 | // Target-specific file lists and/or options go here. 42 | }, 43 | }, 44 | }); 45 | ``` 46 | 47 | ### Options 48 | 49 | * **options.separator**
50 | *Type:* `String`
51 | *Default:* `'-'`
52 | 53 | The separator used for naming your resized images. 54 | 55 | * **options.baseDir**
56 | *Type:* `String`
57 | *Default:* `''`
58 | 59 | The base directory of the site you are serving. This enables Grunt to access your image files when you use absolute paths to reference them in your HTML code. Ignore this option if you are using relative paths only. 60 | 61 | * **options.sizes**
62 | *Type:* `Array`
63 | *Default:* none
64 | 65 | 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: 66 | 67 | ```js 68 | sizes: [{ 69 | selector: '#post-header', 70 | sizeList: [{ 71 | cond: 'max-width: 30em', 72 | size: '100vw' 73 | },{ 74 | cond: 'max-width: 50em', 75 | size: '50vw' 76 | },{ 77 | cond: 'default', 78 | size: 'calc(33vw - 100px)' 79 | }] 80 | },{ 81 | selector: '.hero img', 82 | sizeList: [{ 83 | cond: 'max-width: 20em', 84 | size: '80vw' 85 | },{ 86 | cond: 'default', 87 | size: '90vw' 88 | }] 89 | }] 90 | ``` 91 | 92 | 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. 93 | 94 | This array is optional and without specifying one the browser assumes the size `100vw` for all images. 95 | 96 | You can use the placeholder `%size%` which gets replaced by the actual width of the current image (this can come in handy to specify a maximum width with a breakpoint, e.g. `(min-width: 400px) 400px`). 97 | 98 | * **options.ignore**
99 | *Type:* `Array`
100 | *Default:* `[]`
101 | 102 | An array of standard CSS selectors of image tags you want to ignore. 103 | 104 | * **options.srcsetAttributeName**
105 | *Type:* `String`
106 | *Default:* `'srcset'`
107 | 108 | Overwrite the name of the `srcset` attribute with something else, for example some lazy loaders require `data-srcset`. 109 | 110 | * **options.srcAttribute**
111 | *Type:* `String`
112 | *Default:* none
113 | 114 | Set the `src` attribute to: 115 | 116 | - `'none'`: Delete the `src` attribute which is necessary to avoid duplicate downloads when you are using a polyfill. *Please note that this is not valid HTML, though.* 117 | - `'smallest'`: Set the `src` fallback to the smallest image. 118 | - Do not use this option to leave `src` untouched. 119 | 120 | ### Usage Examples 121 | 122 | #### Default Options 123 | 124 | ```js 125 | grunt.initConfig({ 126 | responsive_images_extender: { 127 | target: { 128 | options: {}, 129 | files: [{ 130 | expand: true, 131 | src: ['**/*.{html,htm,php}'], 132 | cwd: 'src/', 133 | dest: 'build/' 134 | }] 135 | } 136 | } 137 | }); 138 | ``` 139 | 140 | This configuration will turn this HTML code 141 | 142 | ```html 143 | A simple image 144 | ``` 145 | 146 | into this (the image sizes are arbitrarily chosen and read directly from the files): 147 | 148 | ```html 149 | A simple image 155 | ``` 156 | 157 | #### Custom Options 158 | Use the options to refine your tasks, e.g. to add a `sizes` attribute, a different separator, or a different `src` value. `` tags with a `width` attribute automatically trigger the use of `x` descriptors. 159 | 160 | ```js 161 | grunt.initConfig({ 162 | responsive_images_extender: { 163 | complete: { 164 | options: { 165 | separator: '@', 166 | baseDir: 'build', 167 | srcAttribute: 'smallest', 168 | sizes: [{ 169 | selector: '.article-img', 170 | sizeList: [{ 171 | cond: 'max-width: 30em', 172 | size: '100vw' 173 | },{ 174 | cond: 'max-width: 50em', 175 | size: '50vw' 176 | },{ 177 | cond: 'default', 178 | size: 'calc(33vw - 100px)' 179 | }] 180 | }] 181 | }, 182 | files: [{ 183 | expand: true, 184 | src: ['**/*.{html,htm,php}'], 185 | cwd: 'src/', 186 | dest: 'build/' 187 | }] 188 | } 189 | } 190 | }); 191 | ``` 192 | 193 | Above configuration would turn the following HTML chunk 194 | 195 | ```html 196 | A simple image 197 | 198 | 199 | ``` 200 | 201 | into this: 202 | 203 | ```html 204 | A simple image 212 | 213 | 218 | ``` 219 | 220 | #### Ignoring images 221 | Sometimes you want to exclude certain images from the algorithm. You can achieve this with the `ignore` option: 222 | 223 | ```js 224 | grunt.initConfig({ 225 | responsive_images_extender: { 226 | ignoring: { 227 | options: { 228 | ignore: ['.icons', '#logo', 'figure img'] 229 | }, 230 | files: [{ 231 | expand: true, 232 | src: ['**/*.{html,htm,php}'], 233 | cwd: 'src/', 234 | dest: 'build/' 235 | }] 236 | } 237 | } 238 | }); 239 | ``` 240 | 241 | Please see this task's [Gruntfile](https://github.com/smaxtastic/grunt-responsive-images-extender/blob/master/Gruntfile.js) for more usage examples. 242 | 243 | ## Related Work 244 | 245 | * **grunt-responsive-images** 246 | 247 | Use this [task](https://github.com/andismith/grunt-responsive-images/) to generate images with different sizes. 248 | 249 | * **grunt-responsive-images-converter** 250 | 251 | This [task](https://github.com/miller/grunt-responsive-images-converter/) can be used to convert images in markdown files into a `` tag. Unfortunately, it is limited to markdown files. Also, read [here](http://blog.cloudfour.com/dont-use-picture-most-of-the-time/) why `` is not the smartest thing in most cases. 252 | 253 | ## Contributing 254 | In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/). 255 | 256 | ## Release History 257 | 258 | *3.0.0* 259 | * Fixed file path to be a string rather than an array (as requested by `path` module since Node 6) 260 | * Allow for multiple source files per destination file 261 | * Adjusted files config in Gruntfile (see the [Grunt docs](https://gruntjs.com/configuring-tasks#files-array-format)) 262 | 263 | *2.0.1* 264 | * Switched to POSIX-compatible paths (thanks to [sprrw](https://github.com/sprrw)) 265 | 266 | *2.0.0* 267 | 268 | * `srcset` is built automatically based on the image sizes read directly from the files. `x` descriptors are triggered for images with `width` attribute. 269 | * Removed `srcset` and `srcsetRetina` option. 270 | * Added the `srcAttribute` option to delete `src` for polyfills or set the smallest image as a fallback. 271 | * Added the `srcsetAttributeName` option to use for example `data-srcset` for lazy loaders. 272 | * Added the `%size%` placeholder to use the image size inside the `sizes` rules 273 | 274 | *1.0.0* 275 | 276 | * The `sizes` option allows users to specify multiple sizes for different selectors, for example for hero images, article images or icons. 277 | * Added the `ignore` option. 278 | 279 | *0.1.0* 280 | 281 | * Initial commit and first version 282 | --------------------------------------------------------------------------------