├── .nvmrc ├── .husky ├── .gitignore ├── commit-msg └── post-merge ├── .npmignore ├── commitlint.config.js ├── docs ├── _config.yml ├── old │ ├── example-screenshot.png │ ├── examples │ │ ├── 04-images-dimensions │ │ │ ├── my-logo.png │ │ │ ├── colorful-netherlands.jpg │ │ │ ├── styles.css │ │ │ ├── run.js │ │ │ ├── page.html │ │ │ └── page-enhanced.html │ │ ├── 01-without-images-responsiver │ │ │ ├── my-logo.png │ │ │ ├── colorful-netherlands.jpg │ │ │ ├── styles.css │ │ │ └── page.html │ │ ├── 02-images-responsiver-default │ │ │ ├── my-logo.png │ │ │ ├── colorful-netherlands.jpg │ │ │ ├── styles.css │ │ │ ├── run.js │ │ │ ├── page.html │ │ │ └── page-enhanced.html │ │ ├── 03-images-responsiver-simple-configuration │ │ │ ├── my-logo.png │ │ │ ├── colorful-netherlands.jpg │ │ │ ├── styles.css │ │ │ ├── run.js │ │ │ ├── page.html │ │ │ └── page-enhanced.html │ │ └── 99_memory_load_test │ │ │ ├── run.js │ │ │ └── page.html │ ├── examples.md │ ├── installation.md │ ├── debugging.md │ ├── tutorial-01-without-images-responsiver.md │ ├── tutorial-06-even-better-responsive-images.md │ ├── index.md │ ├── tutorial-04-images-dimensions.md │ ├── tutorial-02-images-responsiver-default.md │ ├── nicolashoizeycom.md │ ├── tutorial-03-images-responsiver-simple-configuration.md │ └── tutorial-05-images-urls.md ├── assets │ └── css │ │ └── style.scss ├── images-responsiver │ └── index.md ├── _layouts │ └── default.html └── index.md ├── .prettierrc.js ├── .github ├── PULL_REQUEST_TEMPLATE │ └── pull_request_template.md ├── .kodiak.toml ├── workflows │ ├── upgrade-dependencies.yml │ └── tests.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── .gitignore ├── sync-readme.sh ├── jest.config.js ├── .eslintrc.js ├── .editorconfig ├── __tests__ ├── __snapshots__ │ ├── 01-no-image.test.js.snap │ ├── 04-simple-options-fallback.test.js.snap │ ├── 02-no-transform.test.js.snap │ ├── 07-sizes-attribute.test.js.snap │ ├── 06-data-attributes.test.js.snap │ ├── 04-simple-options-steps.test.js.snap │ ├── 99-tutorial.test.js.snap │ ├── 04-simple-options-srcset.test.js.snap │ ├── 05-advanced-options.test.js.snap │ └── 03-no-options.test.js.snap ├── 07-sizes-attribute.test.js ├── 04-simple-options-fallback.test.js ├── 06-data-attributes.test.js ├── 01-no-image.test.js ├── 02-no-transform.test.js ├── 99-tutorial.test.js ├── 04-simple-options-steps.test.js ├── 03-no-options.test.js ├── 04-simple-options-srcset.test.js └── 05-advanced-options.test.js ├── LICENSE.md ├── package.json ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md ├── README.md ├── CHANGELOG.md └── index.js /.nvmrc: -------------------------------------------------------------------------------- 1 | 16 2 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | __tests__ 2 | docs 3 | .env 4 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "" 5 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-dinky 2 | title: Images Responsiver 3 | google_analytics: UA-1655999-14 4 | -------------------------------------------------------------------------------- /docs/old/example-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhoizey/images-responsiver/HEAD/docs/old/example-screenshot.png -------------------------------------------------------------------------------- /.husky/post-merge: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx git-pull-run -p 'package-lock.json' -c 'npm install' 5 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | semi: true, 4 | singleQuote: true, 5 | trailingComma: 'es5', 6 | }; 7 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Fixes # 2 | 3 | Changes proposed in thi pull request: 4 | 5 | - 6 | 7 | @nhoizey 8 | -------------------------------------------------------------------------------- /docs/old/examples/04-images-dimensions/my-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhoizey/images-responsiver/HEAD/docs/old/examples/04-images-dimensions/my-logo.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Dependency directories 6 | node_modules/ 7 | 8 | # dotenv environment variables file 9 | .env 10 | .env.test 11 | -------------------------------------------------------------------------------- /sync-readme.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "Updating the Table of Content…" 3 | doctoc --github ./README.md 4 | echo "Synchronizing with the docs…" 5 | cp ./README.md ./docs/index.md 6 | -------------------------------------------------------------------------------- /docs/old/examples/01-without-images-responsiver/my-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhoizey/images-responsiver/HEAD/docs/old/examples/01-without-images-responsiver/my-logo.png -------------------------------------------------------------------------------- /docs/old/examples/02-images-responsiver-default/my-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhoizey/images-responsiver/HEAD/docs/old/examples/02-images-responsiver-default/my-logo.png -------------------------------------------------------------------------------- /docs/old/examples/04-images-dimensions/colorful-netherlands.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhoizey/images-responsiver/HEAD/docs/old/examples/04-images-dimensions/colorful-netherlands.jpg -------------------------------------------------------------------------------- /docs/old/examples/01-without-images-responsiver/colorful-netherlands.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhoizey/images-responsiver/HEAD/docs/old/examples/01-without-images-responsiver/colorful-netherlands.jpg -------------------------------------------------------------------------------- /docs/old/examples/02-images-responsiver-default/colorful-netherlands.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhoizey/images-responsiver/HEAD/docs/old/examples/02-images-responsiver-default/colorful-netherlands.jpg -------------------------------------------------------------------------------- /docs/old/examples/03-images-responsiver-simple-configuration/my-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhoizey/images-responsiver/HEAD/docs/old/examples/03-images-responsiver-simple-configuration/my-logo.png -------------------------------------------------------------------------------- /docs/old/examples/03-images-responsiver-simple-configuration/colorful-netherlands.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhoizey/images-responsiver/HEAD/docs/old/examples/03-images-responsiver-simple-configuration/colorful-netherlands.jpg -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // Automatically clear mock calls and instances between every test 3 | clearMocks: true, 4 | // The test environment that will be used for testing 5 | testEnvironment: 'node', 6 | verbose: true, 7 | }; 8 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['plugin:prettier/recommended'], 3 | plugins: ['prettier'], 4 | parserOptions: { 5 | ecmaVersion: 2018, 6 | sourceType: 'module', 7 | }, 8 | rules: { 'prettier/prettier': 'error' }, 9 | settings: {}, 10 | }; 11 | -------------------------------------------------------------------------------- /docs/old/examples/04-images-dimensions/styles.css: -------------------------------------------------------------------------------- 1 | img { 2 | max-width: 100%; 3 | height: auto; 4 | } 5 | 6 | .container { 7 | width: 90vw; 8 | max-width: 40em; 9 | margin: 0 auto; 10 | } 11 | 12 | .logo { 13 | max-width: 20%; 14 | float: right; 15 | margin: 0 0 1em 1em; 16 | } 17 | -------------------------------------------------------------------------------- /docs/old/examples/01-without-images-responsiver/styles.css: -------------------------------------------------------------------------------- 1 | img { 2 | max-width: 100%; 3 | height: auto; 4 | } 5 | 6 | .container { 7 | width: 90vw; 8 | max-width: 40em; 9 | margin: 0 auto; 10 | } 11 | 12 | .logo { 13 | width: 20%; 14 | float: right; 15 | margin: 0 0 1em 1em; 16 | } 17 | -------------------------------------------------------------------------------- /docs/old/examples/02-images-responsiver-default/styles.css: -------------------------------------------------------------------------------- 1 | img { 2 | max-width: 100%; 3 | height: auto; 4 | } 5 | 6 | .container { 7 | width: 90vw; 8 | max-width: 40em; 9 | margin: 0 auto; 10 | } 11 | 12 | .logo { 13 | width: 20%; 14 | float: right; 15 | margin: 0 0 1em 1em; 16 | } 17 | -------------------------------------------------------------------------------- /docs/old/examples/03-images-responsiver-simple-configuration/styles.css: -------------------------------------------------------------------------------- 1 | img { 2 | max-width: 100%; 3 | height: auto; 4 | } 5 | 6 | .container { 7 | width: 90vw; 8 | max-width: 40em; 9 | margin: 0 auto; 10 | } 11 | 12 | .logo { 13 | width: 20%; 14 | float: right; 15 | margin: 0 0 1em 1em; 16 | } 17 | -------------------------------------------------------------------------------- /.github/.kodiak.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [merge] 4 | automerge_label = 'automerge 🤞' 5 | 6 | # https://kodiakhq.com/docs/recipes#better-merge-messages 7 | [merge.message] 8 | title = "pull_request_title" 9 | body = "pull_request_body" 10 | include_pr_number = true 11 | body_type = "markdown" 12 | strip_html_comments = true 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | insert_final_newline = true 9 | indent_style = "tab" 10 | indent_size = 2 11 | 12 | [*.svg] 13 | insert_final_newline = false 14 | 15 | [*.html] 16 | insert_final_newline = false 17 | -------------------------------------------------------------------------------- /docs/old/examples.md: -------------------------------------------------------------------------------- 1 | | **[<< Back home](/images-responsiver/#documentation)** | **[< Tutorial step 6](/images-responsiver/tutorial-06-even-better-responsive-images.html)** | 2 | 3 | # Examples 4 | 5 | ## Actual usage on some sites 6 | 7 | - [nicolas-hoizey.com](/images-responsiver/nicolashoizeycom.html) 8 | 9 | | **[Debugging >](/images-responsiver/debugging.html)** | **[Back home >>](/images-responsiver/#documentation)** | 10 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-dependencies.yml: -------------------------------------------------------------------------------- 1 | name: Upgrade dependencies 2 | 3 | on: 4 | schedule: 5 | # https://crontab.guru/#0_8_*_*_1 6 | - cron: '0 8 * * 1' 7 | workflow_dispatch: 8 | 9 | concurrency: 10 | group: upgrade-dependencies 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | upgrade-dependencies: 15 | uses: nhoizey/upgrade-dependencies/.github/workflows/upgrade-dependencies.yml@main 16 | secrets: inherit 17 | -------------------------------------------------------------------------------- /docs/assets/css/style.scss: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | @import '{{ site.theme }}'; 5 | 6 | body { 7 | font-size: 1rem; 8 | line-height: 1.5; 9 | background-color: white; 10 | } 11 | 12 | pre.highlight, 13 | code { 14 | background-color: #fff4d1; 15 | font-size: 1em; 16 | padding: 0.1em 0.2em; 17 | } 18 | 19 | h1.header { 20 | font-size: 1.6rem; 21 | } 22 | 23 | // Ugly fixes 24 | header li { 25 | height: auto; 26 | } 27 | 28 | a.buttons { 29 | display: inline-block; 30 | } 31 | -------------------------------------------------------------------------------- /docs/images-responsiver/index.md: -------------------------------------------------------------------------------- 1 | # Images Responsiver 2 | 3 | ⚠️ **Images Responsiver documentation is awaiting a full rewrite.** 4 | 5 | In the mean time, most of [the documentation for `eleventy-plugin-images-responsiver`](https://nhoizey.github.io/images-responsiver/eleventy-plugin-images-responsiver), the Eleventy plugin that uses it, **should be enough** to understand how it works. 6 | 7 | "Just" replace any Markdown with the HTML it is transformed into, and use the same configuration to get the same result. 8 | -------------------------------------------------------------------------------- /docs/old/installation.md: -------------------------------------------------------------------------------- 1 | | **[<< Back home](/images-responsiver/#documentation)** | 2 | 3 | # Installation 4 | 5 | To install and use `images-responsiver`, you’ll need [npm](http://npmjs.com) (which comes with [Node.js](https://nodejs.org/en/download/)) installed on your computer. 6 | 7 | From your command line, install `images-responsiver` as a dev dependency: 8 | 9 | ``` 10 | npm install images-responsiver --save-dev 11 | ``` 12 | 13 | | **[Tutorial >](/images-responsiver/tutorial-01-without-images-responsiver.html)** | **[Back home >>](/images-responsiver/#documentation)** | 14 | -------------------------------------------------------------------------------- /docs/old/examples/02-images-responsiver-default/run.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // get dependencies 4 | const fs = require('fs'); 5 | const prettier = require('prettier'); 6 | const imagesResponsiver = require('../../../index.js'); 7 | 8 | // get the HTML content of the source file 9 | const src = fs.readFileSync('./page.html', { encoding: 'utf8' }); 10 | 11 | // run images-responsiver on the source HTML string 12 | const dist = imagesResponsiver(src); 13 | 14 | // write the result into a new file 15 | fs.writeFileSync( 16 | 'page-enhanced.html', 17 | prettier.format(dist, { parser: 'html' }) 18 | ); 19 | -------------------------------------------------------------------------------- /docs/old/examples/99_memory_load_test/run.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // get dependencies 4 | const fs = require('fs'); 5 | const imagesResponsiver = require('../../../index.js'); 6 | 7 | const toMB = (bytes) => Math.floor(bytes / 1024 / 1024); 8 | 9 | // get the HTML content of the source file 10 | const src = fs.readFileSync('./page.html', { encoding: 'utf8' }); 11 | 12 | let dist; 13 | let mbUsed; 14 | 15 | // make loops to track memory usage 16 | for (let i = 0; i < 10000; i++) { 17 | dist = imagesResponsiver(src, {}); 18 | if (!(i % 10)) { 19 | mbUsed = toMB(process.memoryUsage().heapUsed); 20 | console.log(`Tracking memory: ${mbUsed} MB at iteration #${i}`); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '[Feature request] ' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /docs/old/examples/99_memory_load_test/page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Responsive images made easy 7 | 8 | 9 | 10 | 11 |
12 | 13 |

Here is a simple image:

14 |

15 |

Here is another simple image:

16 |

17 |

Yet another image:

18 |

19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/01-no-image.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`no image keeps intact HTML without image 1`] = ` 4 | " 5 | 6 | 7 |

Hello

8 | 9 | 10 | " 11 | `; 12 | 13 | exports[`no image keeps intact HTML without image with HTML entity 1`] = ` 14 | " 15 | 16 | 17 |

Hello ©

18 | 19 | 20 | " 21 | `; 22 | 23 | exports[`no image keeps intact HTML without image with inline style and custom property 1`] = ` 24 | " 25 | 26 | 27 |

Hello

28 | 29 | 30 | " 31 | `; 32 | 33 | exports[`no image malformed HTML without image 1`] = ` 34 | "

Hello

mister

35 | " 36 | `; 37 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests CI 2 | on: [push, pull_request] 3 | jobs: 4 | tests: 5 | runs-on: ubuntu-latest 6 | 7 | steps: 8 | - uses: actions/checkout@v2 9 | 10 | - name: Get npm cache directory 11 | id: npm-cache 12 | run: | 13 | echo "::set-output name=dir::$(npm config get cache)" 14 | 15 | - name: Use cache 16 | uses: actions/cache@v2 17 | with: 18 | path: ${{ steps.npm-cache.outputs.dir }} 19 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 20 | restore-keys: ${{ runner.os }}-node- 21 | 22 | - name: Select Node.js version 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: '12' 26 | 27 | - name: Install dependencies 28 | run: npm ci 29 | 30 | - name: Run tests 31 | run: npm test 32 | -------------------------------------------------------------------------------- /docs/old/examples/04-images-dimensions/run.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // get dependencies 4 | const fs = require('fs'); 5 | const prettier = require('prettier'); 6 | const imagesResponsiver = require('../../../index.js'); 7 | 8 | // get the HTML content of the source file 9 | const src = fs.readFileSync('./page.html', { encoding: 'utf8' }); 10 | 11 | // define images-responsiver options 12 | const options = { 13 | default: { 14 | sizes: '(max-width: 45em) 90vw, 40em', 15 | }, 16 | logo: { 17 | minWidth: 58, 18 | maxWidth: 512, 19 | steps: 3, 20 | fallbackWidth: 128, 21 | sizes: '(max-width: 45em) 18vw, 8em', 22 | }, 23 | }; 24 | 25 | // run images-responsiver on the source HTML string 26 | const dist = imagesResponsiver(src, options); 27 | 28 | // write the result into a new file 29 | fs.writeFileSync( 30 | 'page-enhanced.html', 31 | prettier.format(dist, { parser: 'html' }) 32 | ); 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '[Bug] ' 5 | labels: 'type: bug 🐛' 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 15 | 1. Install '…' 16 | 2. Configure '…' 17 | 3. Run '…' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots / logs** 24 | If applicable, add screenshots and/or console logs to help explain your problem. 25 | 26 | **Environment** 27 | 28 | - OS and version: [e.g. macOS 10.14.5] 29 | - Eleventy version: [e.g. 0.11.0] 30 | - `images-responsiver` or `eleventy-plugin-images-responsiver` version: [e.g. 1.8.2] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/04-simple-options-fallback.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`image with simple fallback option width attribute inferior to the fallback 1`] = ` 4 | " 5 | 6 | 7 | 14 | 15 | 16 | " 17 | `; 18 | 19 | exports[`image with simple fallback option width attribute superior to the fallback 1`] = ` 20 | " 21 | 22 | 23 | 30 | 31 | 32 | " 33 | `; 34 | -------------------------------------------------------------------------------- /docs/old/examples/03-images-responsiver-simple-configuration/run.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // get dependencies 4 | const fs = require('fs'); 5 | const prettier = require('prettier'); 6 | const imagesResponsiver = require('../../../index.js'); 7 | 8 | // get the HTML content of the source file 9 | const src = fs.readFileSync('./page.html', { encoding: 'utf8' }); 10 | 11 | // define images-responsiver options 12 | const options = { 13 | default: { 14 | sizes: '(max-width: 45em) 90vw, 40em', 15 | }, 16 | logo: { 17 | minWidth: 58, 18 | maxWidth: 512, 19 | steps: 3, 20 | fallbackWidth: 128, 21 | sizes: '(max-width: 45em) 18vw, 8em', 22 | }, 23 | }; 24 | 25 | // run images-responsiver on the source HTML string 26 | const dist = imagesResponsiver(src, options); 27 | 28 | // write the result into a new file 29 | fs.writeFileSync( 30 | 'page-enhanced.html', 31 | prettier.format(dist, { parser: 'html' }) 32 | ); 33 | -------------------------------------------------------------------------------- /__tests__/07-sizes-attribute.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const prettier = require('prettier'); 4 | const imagesResponsiver = require('../index'); 5 | 6 | function cleanHtml(html) { 7 | return prettier.format(html, { parser: 'html' }); 8 | } 9 | 10 | describe('sizes attribute', () => { 11 | test('existing sizes attribute is untouched', () => { 12 | const content = ` 13 | `; 14 | const transformed = imagesResponsiver(content); 15 | 16 | expect(cleanHtml(transformed)).toMatchSnapshot(); 17 | }); 18 | 19 | test('existing sizes attribute is overriden', () => { 20 | const content = ` 21 | `; 22 | const transformed = imagesResponsiver(content, { 23 | default: { 24 | sizesOverride: true, 25 | }, 26 | }); 27 | 28 | expect(cleanHtml(transformed)).toMatchSnapshot(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/02-no-transform.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`image that can't be transformed do nothing on SVG image 1`] = ` 4 | " 5 | 6 | 7 | 8 | 9 | 10 | " 11 | `; 12 | 13 | exports[`image that can't be transformed do nothing on image with already a srcset 1`] = ` 14 | " 15 | 16 | 17 | 18 | 19 | 20 | " 21 | `; 22 | 23 | exports[`image that can't be transformed do nothing on image with data-responsiver="false" 1`] = ` 24 | " 25 | 26 | 27 | 28 | 29 | 30 | " 31 | `; 32 | 33 | exports[`image that can't be transformed do nothing on image without a src 1`] = ` 34 | " 35 | 36 | 37 | \\"not 38 | 39 | 40 | " 41 | `; 42 | -------------------------------------------------------------------------------- /__tests__/04-simple-options-fallback.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const prettier = require('prettier'); 4 | const imagesResponsiver = require('../index'); 5 | 6 | function cleanHtml(html) { 7 | return prettier.format(html, { parser: 'html' }); 8 | } 9 | 10 | describe('image with simple fallback option', () => { 11 | test('width attribute superior to the fallback', () => { 12 | const content = ` 13 | `; 14 | const transformed = imagesResponsiver(content, { 15 | default: { 16 | fallbackWidth: 480, 17 | }, 18 | }); 19 | expect(cleanHtml(transformed)).toMatchSnapshot(); 20 | }); 21 | 22 | test('width attribute inferior to the fallback', () => { 23 | const content = ` 24 | `; 25 | const transformed = imagesResponsiver(content, { 26 | default: { 27 | fallbackWidth: 480, 28 | }, 29 | }); 30 | expect(cleanHtml(transformed)).toMatchSnapshot(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/07-sizes-attribute.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`sizes attribute existing sizes attribute is overriden 1`] = ` 4 | " 5 | 6 | 7 | 19 | 20 | 21 | " 22 | `; 23 | 24 | exports[`sizes attribute existing sizes attribute is untouched 1`] = ` 25 | " 26 | 27 | 28 | 40 | 41 | 42 | " 43 | `; 44 | -------------------------------------------------------------------------------- /docs/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% seo %} 6 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |

17 | {{ site.title | default: site.github.repository_name }} 20 |

21 |

22 | {{ site.description | default: site.github.project_tagline }} 23 |

24 | 34 |
35 | 36 |
{{ content }}
37 |
38 | 39 | 40 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Nicolas Hoizey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/old/examples/01-without-images-responsiver/page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | images-responsiver 7 | 8 | 9 | 10 | 11 |
12 |

images-responsiver

13 | 14 |

15 | images-responsiver is 16 | a simple solution for most responsive images needs. It 17 | transforms simple <img src="…" /> HTML tags into 18 | better responsive images syntax with srcset and 19 | sizes attributes. 20 |

21 |

Here is a simple image:

22 |

23 | A photo of colorful houses in Groningen, The Netherlands 27 |

28 |

29 | Download the photo for free 30 | on Unsplash. 31 |

32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /docs/old/examples/02-images-responsiver-default/page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | images-responsiver 7 | 8 | 9 | 10 | 11 |
12 |

images-responsiver

13 | 14 |

15 | images-responsiver is 16 | a simple solution for most responsive images needs. It 17 | transforms simple <img src="…" /> HTML tags into 18 | better responsive images syntax with srcset and 19 | sizes attributes. 20 |

21 |

Here is a simple image:

22 |

23 | A photo of colorful houses in Groningen, The Netherlands 27 |

28 |

29 | Download the photo for free 30 | on Unsplash. 31 |

32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /docs/old/examples/03-images-responsiver-simple-configuration/page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | images-responsiver 7 | 8 | 9 | 10 | 11 |
12 |

images-responsiver

13 | 19 |

20 | images-responsiver is 21 | a simple solution for most responsive images needs. It 22 | transforms simple <img src="…" /> HTML tags into 23 | better responsive images syntax with srcset and 24 | sizes attributes. 25 |

26 |

Here is a simple image:

27 |

28 | A photo of colorful houses in Groningen, The Netherlands 32 |

33 |

34 | Download the photo for free 35 | on Unsplash. 36 |

37 |
38 | 39 | 40 | -------------------------------------------------------------------------------- /__tests__/06-data-attributes.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const prettier = require('prettier'); 4 | const imagesResponsiver = require('../index'); 5 | 6 | function cleanHtml(html) { 7 | return prettier.format(html, { parser: 'html' }); 8 | } 9 | 10 | describe('data attributes', () => { 11 | test('both src and data-src, only src used', () => { 12 | const content = ` 13 | `; 14 | const transformed = imagesResponsiver(content); 15 | 16 | expect(cleanHtml(transformed)).toMatchSnapshot(); 17 | }); 18 | 19 | test('both data-src and data-srcset, no action', () => { 20 | const content = ` 21 | `; 22 | const transformed = imagesResponsiver(content); 23 | 24 | expect(cleanHtml(transformed)).toMatchSnapshot(); 25 | }); 26 | 27 | test('data-src but no src nor data-srcset', () => { 28 | const content = ` 29 | `; 30 | const transformed = imagesResponsiver(content); 31 | 32 | expect(cleanHtml(transformed)).toMatchSnapshot(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /docs/old/examples/04-images-dimensions/page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | images-responsiver 7 | 8 | 9 | 10 | 11 |
12 |

images-responsiver

13 | 21 |

22 | images-responsiver is 23 | a simple solution for most responsive images needs. It 24 | transforms simple <img src="…" /> HTML tags into 25 | better responsive images syntax with srcset and 26 | sizes attributes. 27 |

28 |

Here is a simple image:

29 |

30 | A photo of colorful houses in Groningen, The Netherlands 36 |

37 |

38 | Download the photo for free 39 | on Unsplash. 40 |

41 |
42 | 43 | 44 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/06-data-attributes.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`data attributes both data-src and data-srcset, no action 1`] = ` 4 | " 5 | 6 | 7 | 11 | 12 | 13 | " 14 | `; 15 | 16 | exports[`data attributes both src and data-src, only src used 1`] = ` 17 | " 18 | 19 | 20 | 33 | 34 | 35 | " 36 | `; 37 | 38 | exports[`data attributes data-src but no src nor data-srcset 1`] = ` 39 | " 40 | 41 | 42 | 49 | 50 | 51 | " 52 | `; 53 | -------------------------------------------------------------------------------- /__tests__/01-no-image.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const prettier = require('prettier'); 4 | const imagesResponsiver = require('../index'); 5 | 6 | function cleanHtml(html) { 7 | return prettier.format(html, { parser: 'html' }); 8 | } 9 | 10 | describe('no image', () => { 11 | test('malformed HTML without image', () => { 12 | const content = `

Hello

mister

`; 13 | const transformed = imagesResponsiver(content); 14 | expect(cleanHtml(transformed)).toMatchSnapshot(); 15 | }); 16 | 17 | test('keeps intact HTML without image', () => { 18 | const content = `

Hello

`; 19 | const transformed = imagesResponsiver(content); 20 | expect(cleanHtml(transformed)).toMatchSnapshot(); 21 | }); 22 | 23 | test('keeps intact HTML without image with inline style and custom property', () => { 24 | const content = `

Hello

`; 25 | const transformed = imagesResponsiver(content); 26 | expect(cleanHtml(transformed)).toMatchSnapshot(); 27 | }); 28 | 29 | test('keeps intact HTML without image with HTML entity', () => { 30 | const content = `

Hello ©

`; 31 | const transformed = imagesResponsiver(content); 32 | expect(cleanHtml(transformed)).toMatchSnapshot(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /__tests__/02-no-transform.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const prettier = require('prettier'); 4 | const imagesResponsiver = require('../index'); 5 | 6 | function cleanHtml(html) { 7 | return prettier.format(html, { parser: 'html' }); 8 | } 9 | 10 | describe("image that can't be transformed", () => { 11 | test('do nothing on SVG image', () => { 12 | const content = ``; 13 | const transformed = imagesResponsiver(content); 14 | expect(cleanHtml(transformed)).toMatchSnapshot(); 15 | }); 16 | 17 | test('do nothing on image without a src', () => { 18 | const content = `not really an image`; 19 | const transformed = imagesResponsiver(content); 20 | expect(cleanHtml(transformed)).toMatchSnapshot(); 21 | }); 22 | 23 | test('do nothing on image with already a srcset', () => { 24 | const content = ``; 25 | const transformed = imagesResponsiver(content); 26 | expect(cleanHtml(transformed)).toMatchSnapshot(); 27 | }); 28 | 29 | test('do nothing on image with data-responsiver="false"', () => { 30 | const content = ``; 31 | const transformed = imagesResponsiver(content); 32 | expect(cleanHtml(transformed)).toMatchSnapshot(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "images-responsiver", 3 | "version": "1.13.0", 4 | "description": "Global solution for responsive images, transforming simple image HTML syntax into better responsive images syntax.", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/nhoizey/images-responsiver.git" 9 | }, 10 | "scripts": { 11 | "lint": "eslint . --ext js --quiet --fix", 12 | "test": "jest", 13 | "test-update": "jest --updateSnapshot", 14 | "prepare": "husky install" 15 | }, 16 | "keywords": [ 17 | "website", 18 | "blog", 19 | "html", 20 | "image", 21 | "images", 22 | "responsive", 23 | "RWD", 24 | "srcset", 25 | "sizes", 26 | "Cloudinary" 27 | ], 28 | "author": "Nicolas Hoizey", 29 | "license": "MIT", 30 | "dependencies": { 31 | "debug": "^4.4.0", 32 | "deepmerge": "^4.3.1", 33 | "linkedom": "^0.11.2", 34 | "lodash.clonedeep": "^4.5.0" 35 | }, 36 | "bugs": { 37 | "url": "https://github.com/nhoizey/images-responsiver/issues" 38 | }, 39 | "homepage": "https://nhoizey.github.io/images-responsiver/", 40 | "devDependencies": { 41 | "@commitlint/cli": "^12.1.4", 42 | "@commitlint/config-conventional": "^13.2.0", 43 | "eslint": "^7.32.0", 44 | "eslint-config-prettier": "^8.10.0", 45 | "eslint-plugin-prettier": "^4.2.1", 46 | "husky": "^7.0.4", 47 | "jest": "^28.1.3", 48 | "prettier": "^2.8.8" 49 | }, 50 | "gitHead": "e59f7fb0422676b8928998b5233c4824ed695c10" 51 | } 52 | -------------------------------------------------------------------------------- /__tests__/99-tutorial.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const prettier = require('prettier'); 4 | const imagesResponsiver = require('../index'); 5 | 6 | function cleanHtml(html) { 7 | return prettier.format(html, { parser: 'html' }); 8 | } 9 | 10 | describe('tutorial 2', () => { 11 | test('selector', () => { 12 | const content = ` 13 | 14 | 15 | 16 | 17 | images-responsiver 18 | 19 | 20 | 21 | 22 |
23 |

images-responsiver

24 |

25 |

images-responsiver is a simple solution for most responsive images needs. It transforms simple <img src="…" /> HTML tags into better responsive images syntax with srcset and sizes attributes.

26 |

Here is a simple image:

27 |

A photo of colorful houses in Groningen, The Netherlands

28 |

Download the photo for free on Unsplash.

29 |
30 | 31 | 32 | `; 33 | const transformed = imagesResponsiver(content); 34 | 35 | expect(cleanHtml(transformed)).toMatchSnapshot(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /docs/old/debugging.md: -------------------------------------------------------------------------------- 1 | | **[<< Back home](/images-responsiver/#documentation)** | **[< Examples](/images-responsiver/examples.html)** | 2 | 3 | # Debugging 4 | 5 | You can run `images-responsiver` in debug mode to get informations and warnings about your HTML or options. 6 | 7 | Add `DEBUG=*` in front of the command to get messages in the console. For example, replace `node run.js` with `DEBUG=* node run.js` to get messages like these: 8 | 9 | ```bash 10 | images-responsiver:info Transforming my-logo.png +0ms 11 | images-responsiver:warning The image should have a width attribute: my-logo.png +0ms 12 | images-responsiver:info Transforming colorful-netherlands.jpg +2ms 13 | images-responsiver:warning The image should have a width attribute: colorful-netherlands.jpg +1ms 14 | ``` 15 | 16 | `images-responsiver` uses the great [debug](https://github.com/visionmedia/debug) module to provide these debug messages. 17 | 18 | You can use different values for `DEBUG=`: 19 | 20 | | `DEBUG=*` | get all messages, even from other packages | 21 | | `DEBUG=images-responsiver:*` | get all messages, only from `images-responsiver` | 22 | | `DEBUG=images-responsiver:info` | get only information messages from `images-responsiver` | 23 | | `DEBUG=images-responsiver:warning` | get only warning messages from `images-responsiver` | 24 | | `DEBUG=images-responsiver:error` | get only error messages from `images-responsiver` | 25 | | `DEBUG=images-responsiver:error,images-responsiver:warning` or `DEBUG=images-responsiver:*,-images-responsiver:info` | get only warning and error messages from `images-responsiver` | 26 | 27 | | **[Back home >>](/images-responsiver/#documentation)** | 28 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/04-simple-options-steps.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`image with simple step options simple image 1`] = ` 4 | " 5 | 6 | 7 | 13 | 14 | 15 | " 16 | `; 17 | 18 | exports[`image with simple step options simple image with bad number of steps 1`] = ` 19 | " 20 | 21 | 22 | 28 | 29 | 30 | " 31 | `; 32 | 33 | exports[`image with simple step options simple image with minWidth = maxWidth 1`] = ` 34 | " 35 | 36 | 37 | 43 | 44 | 45 | " 46 | `; 47 | 48 | exports[`image with simple step options simple image with minWidth > maxWidth 1`] = ` 49 | " 50 | 51 | 52 | 64 | 65 | 66 | " 67 | `; 68 | -------------------------------------------------------------------------------- /__tests__/04-simple-options-steps.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const prettier = require('prettier'); 4 | const imagesResponsiver = require('../index'); 5 | 6 | function cleanHtml(html) { 7 | return prettier.format(html, { parser: 'html' }); 8 | } 9 | 10 | describe('image with simple step options', () => { 11 | test('simple image', () => { 12 | const content = ``; 13 | const transformed = imagesResponsiver(content, { 14 | default: { 15 | minWidth: 120, 16 | maxWidth: 320, 17 | steps: 3, 18 | }, 19 | }); 20 | expect(cleanHtml(transformed)).toMatchSnapshot(); 21 | }); 22 | 23 | test('simple image with bad number of steps', () => { 24 | const content = ``; 25 | const transformed = imagesResponsiver(content, { 26 | default: { 27 | steps: 1, 28 | }, 29 | }); 30 | expect(cleanHtml(transformed)).toMatchSnapshot(); 31 | }); 32 | 33 | test('simple image with minWidth = maxWidth', () => { 34 | const content = ``; 35 | const transformed = imagesResponsiver(content, { 36 | default: { 37 | minWidth: 320, 38 | maxWidth: 320, 39 | }, 40 | }); 41 | expect(cleanHtml(transformed)).toMatchSnapshot(); 42 | }); 43 | 44 | test('simple image with minWidth > maxWidth', () => { 45 | const content = ``; 46 | const transformed = imagesResponsiver(content, { 47 | default: { 48 | minWidth: 640, 49 | maxWidth: 320, 50 | }, 51 | }); 52 | expect(cleanHtml(transformed)).toMatchSnapshot(); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /docs/old/examples/03-images-responsiver-simple-configuration/page-enhanced.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | images-responsiver 7 | 8 | 9 | 10 |
11 |

images-responsiver

12 | 20 |

21 | images-responsiver is 22 | a simple solution for most responsive images needs. It 23 | transforms simple <img src="…" /> HTML tags 24 | into better responsive images syntax with srcset and 25 | sizes attributes. 26 |

27 |

Here is a simple image:

28 |

29 | A photo of colorful houses in Groningen, The Netherlands 42 |

43 |

44 | Download the photo for free 45 | on Unsplash. 46 |

47 |
48 | 49 | 50 | -------------------------------------------------------------------------------- /docs/old/examples/02-images-responsiver-default/page-enhanced.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | images-responsiver 7 | 8 | 9 | 10 |
11 |

images-responsiver

12 | 26 |

27 | images-responsiver is 28 | a simple solution for most responsive images needs. It 29 | transforms simple <img src="…" /> HTML tags 30 | into better responsive images syntax with srcset and 31 | sizes attributes. 32 |

33 |

Here is a simple image:

34 |

35 | A photo of colorful houses in Groningen, The Netherlands 48 |

49 |

50 | Download the photo for free 51 | on Unsplash. 52 |

53 |
54 | 55 | 56 | -------------------------------------------------------------------------------- /docs/old/examples/04-images-dimensions/page-enhanced.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | images-responsiver 7 | 8 | 9 | 10 |
11 |

images-responsiver

12 | 22 |

23 | images-responsiver is 24 | a simple solution for most responsive images needs. It 25 | transforms simple <img src="…" /> HTML tags 26 | into better responsive images syntax with srcset and 27 | sizes attributes. 28 |

29 |

Here is a simple image:

30 |

31 | A photo of colorful houses in Groningen, The Netherlands 46 |

47 |

48 | Download the photo for free 49 | on Unsplash. 50 |

51 |
52 | 53 | 54 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribute 2 | 3 | ## Introduction 4 | 5 | First, thank you for considering contributing to `images-responsiver`! It's people like you that make the open source community such a great community! 😊 6 | 7 | We welcome any type of contribution, not only code. You can help with 8 | 9 | - **QA**: file bug reports, the more details you can give the better (e.g. screenshots, console logs, etc.) 10 | - **Marketing**: writing blog posts, howto's, printing stickers, … 11 | - **Community**: presenting the project at meetups, organizing a dedicated meetup for the local community, … 12 | - **Code**: take a look at the [open issues](https://github.com/nhoizey/images-responsiver/issues). Even if you can't write code, commenting on them, showing that you care about a given issue matters. It helps us triage them. 13 | 14 | ## Your First Contribution 15 | 16 | Working on your first Pull Request? You can learn how from this _free_ series, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github). 17 | 18 | ## Submitting code 19 | 20 | Any code change should be submitted as a pull request. The description should explain what the code does and give steps to execute it. The pull request should also contain tests. 21 | 22 | ## Code review process 23 | 24 | The bigger the pull request, the longer it will take to review and merge. Try to [break down large pull requests in smaller chunks](https://oncletom.io/2013/the-55-commits-syndrome/) that are easier to review and merge. 25 | 26 | It is also always helpful to have some context for your pull request. What was the purpose? Why does it matter to you? 27 | 28 | ## Questions 29 | 30 | If you have any questions, [create an issue](https://github.com/nhoizey/images-responsiver/issues/new/choose) (protip: do a quick search first to see if someone else didn't ask the same question before!). 31 | 32 | ## Credits 33 | 34 | ### Contributors 35 | 36 | Thank you to [all the people who have already contributed](https://github.com/nhoizey/images-responsiver/graphs/contributors) to `images-responsiver`! 37 | 38 | 39 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/99-tutorial.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`tutorial 2 selector 1`] = ` 4 | " 5 | 6 | 7 | 8 | 9 | images-responsiver 10 | 11 | 12 | 13 | 14 |
15 |

images-responsiver

16 |

17 | \\"My 31 |

32 |

33 | images-responsiver is 34 | a simple solution for most responsive images needs. It 35 | transforms simple <img src=\\"…\\" /> HTML tags into 36 | better responsive images syntax with srcset and 37 | sizes attributes. 38 |

39 |

Here is a simple image:

40 |

41 | \\"A 54 |

55 |

56 | Download the photo for free 57 | on Unsplash. 58 |

59 |
60 | 61 | 62 | " 63 | `; 64 | -------------------------------------------------------------------------------- /__tests__/03-no-options.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const prettier = require('prettier'); 4 | const imagesResponsiver = require('../index'); 5 | 6 | function cleanHtml(html) { 7 | return prettier.format(html, { parser: 'html' }); 8 | } 9 | 10 | describe('image without options', () => { 11 | test('simple image', () => { 12 | const content = `

content before content after

`; 13 | const transformed = imagesResponsiver(content); 14 | expect(cleanHtml(transformed)).toMatchSnapshot(); 15 | }); 16 | 17 | test('simple image in subfolder', () => { 18 | const content = `

content before content after

`; 19 | const transformed = imagesResponsiver(content); 20 | expect(cleanHtml(transformed)).toMatchSnapshot(); 21 | }); 22 | 23 | test('simple image with absolute URL', () => { 24 | const content = `

content before content after

`; 25 | const transformed = imagesResponsiver(content); 26 | expect(cleanHtml(transformed)).toMatchSnapshot(); 27 | }); 28 | 29 | test('width attribute superior to the fallback', () => { 30 | const content = ` 31 | `; 32 | const transformed = imagesResponsiver(content); 33 | expect(cleanHtml(transformed)).toMatchSnapshot(); 34 | }); 35 | 36 | test('width attribute inferior to the fallback', () => { 37 | const content = ` 38 | `; 39 | const transformed = imagesResponsiver(content); 40 | expect(cleanHtml(transformed)).toMatchSnapshot(); 41 | }); 42 | 43 | test('width attribute inferior to the minWidth', () => { 44 | const content = ` 45 | `; 46 | const transformed = imagesResponsiver(content); 47 | expect(cleanHtml(transformed)).toMatchSnapshot(); 48 | }); 49 | 50 | test('2 images', () => { 51 | const content = ` 52 | `; 53 | const transformed = imagesResponsiver(content); 54 | 55 | expect(cleanHtml(transformed)).toMatchSnapshot(); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/04-simple-options-srcset.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`image with simple srcset options simple image 1`] = ` 4 | " 5 | 6 | 7 | 13 | 14 | 15 | " 16 | `; 17 | 18 | exports[`image with simple srcset options simple image with only one value 1`] = ` 19 | " 20 | 21 | 22 | 28 | 29 | 30 | " 31 | `; 32 | 33 | exports[`image with simple srcset options simple image with values duplicates 1`] = ` 34 | " 35 | 36 | 37 | 43 | 44 | 45 | " 46 | `; 47 | 48 | exports[`image with simple srcset options simple image with width inferior to all values 1`] = ` 49 | " 50 | 51 | 52 | 59 | 60 | 61 | " 62 | `; 63 | 64 | exports[`image with simple srcset options simple image with width inferior to one value 1`] = ` 65 | " 66 | 67 | 68 | 75 | 76 | 77 | " 78 | `; 79 | 80 | exports[`image with simple srcset options simple image with width superior to all values 1`] = ` 81 | " 82 | 83 | 84 | 91 | 92 | 93 | " 94 | `; 95 | -------------------------------------------------------------------------------- /__tests__/04-simple-options-srcset.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const prettier = require('prettier'); 4 | const imagesResponsiver = require('../index'); 5 | 6 | function cleanHtml(html) { 7 | return prettier.format(html, { parser: 'html' }); 8 | } 9 | 10 | describe('image with simple srcset options', () => { 11 | test('simple image', () => { 12 | const content = ``; 13 | const transformed = imagesResponsiver(content, { 14 | default: { 15 | widthsList: [120, 180, 320], 16 | }, 17 | }); 18 | expect(cleanHtml(transformed)).toMatchSnapshot(); 19 | }); 20 | 21 | test('simple image with only one value', () => { 22 | const content = ``; 23 | const transformed = imagesResponsiver(content, { 24 | default: { 25 | widthsList: [320], 26 | }, 27 | }); 28 | expect(cleanHtml(transformed)).toMatchSnapshot(); 29 | }); 30 | 31 | test('simple image with values duplicates', () => { 32 | const content = ``; 33 | const transformed = imagesResponsiver(content, { 34 | default: { 35 | widthsList: [320, 320], 36 | }, 37 | }); 38 | expect(cleanHtml(transformed)).toMatchSnapshot(); 39 | }); 40 | 41 | test('simple image with width superior to all values', () => { 42 | const content = ``; 43 | const transformed = imagesResponsiver(content, { 44 | default: { 45 | widthsList: [320, 640, 1024], 46 | }, 47 | }); 48 | expect(cleanHtml(transformed)).toMatchSnapshot(); 49 | }); 50 | 51 | test('simple image with width inferior to one value', () => { 52 | const content = ``; 53 | const transformed = imagesResponsiver(content, { 54 | default: { 55 | widthsList: [320, 800, 1280], 56 | }, 57 | }); 58 | expect(cleanHtml(transformed)).toMatchSnapshot(); 59 | }); 60 | 61 | test('simple image with width inferior to all values', () => { 62 | const content = ``; 63 | const transformed = imagesResponsiver(content, { 64 | default: { 65 | widthsList: [640, 1024, 1280], 66 | }, 67 | }); 68 | expect(cleanHtml(transformed)).toMatchSnapshot(); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /__tests__/05-advanced-options.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const prettier = require('prettier'); 4 | const imagesResponsiver = require('../index'); 5 | 6 | function cleanHtml(html) { 7 | return prettier.format(html, { parser: 'html' }); 8 | } 9 | 10 | describe('images with advanced options', () => { 11 | test('selector', () => { 12 | const content = ` 13 | `; 14 | const transformed = imagesResponsiver(content, { 15 | default: { 16 | selector: 'img:not(.notransform)', 17 | fallbackWidth: 480, 18 | }, 19 | }); 20 | 21 | expect(cleanHtml(transformed)).toMatchSnapshot(); 22 | }); 23 | 24 | test("selector in wrong place don't break it", () => { 25 | const content = ` 26 | `; 27 | const transformed = imagesResponsiver(content, { 28 | transform: { 29 | selector: '.yes', 30 | classes: ['transformed'], 31 | }, 32 | }); 33 | 34 | expect(cleanHtml(transformed)).toMatchSnapshot(); 35 | }); 36 | 37 | test('resizedImageUrl', () => { 38 | const content = ` 39 | `; 40 | const transformed = imagesResponsiver(content, { 41 | default: { 42 | resizedImageUrl: (src, width) => `${src}?w=${width}`, 43 | fallbackWidth: 480, 44 | }, 45 | }); 46 | 47 | expect(cleanHtml(transformed)).toMatchSnapshot(); 48 | }); 49 | 50 | test('runBefore', () => { 51 | const content = ` 52 | `; 53 | const transformed = imagesResponsiver(content, { 54 | default: { 55 | runBefore: (image, document) => { 56 | image.setAttribute('src', 'another-test.png'); 57 | }, 58 | }, 59 | }); 60 | 61 | expect(cleanHtml(transformed)).toMatchSnapshot(); 62 | }); 63 | 64 | test('runAfter', () => { 65 | const content = ` 66 | `; 67 | const transformed = imagesResponsiver(content, { 68 | default: { 69 | runAfter: (image, document) => { 70 | image.dataset.after = 'hello!'; 71 | }, 72 | }, 73 | }); 74 | 75 | expect(cleanHtml(transformed)).toMatchSnapshot(); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /docs/old/tutorial-01-without-images-responsiver.md: -------------------------------------------------------------------------------- 1 | | **[<< Back home](/images-responsiver/#documentation)** | **[< Installation](/images-responsiver/installation.html)** | 2 | 3 | # Tutorial step 1: Default behavior without images-responsiver 4 | 5 | --- 6 | 7 | - **Step 1: Default behavior without `images-responsiver`** 8 | - [Step 2: Better behavior with `images-responsiver` and default configuration](https://nhoizey.github.io/images-responsiver/tutorial-02-images-responsiver-default.html) 9 | - [Step 3: Enhanced behavior with some configuration](https://nhoizey.github.io/images-responsiver/tutorial-03-images-responsiver-simple-configuration.html) 10 | - [Step 4: Making it more robust with image dimensions](https://nhoizey.github.io/images-responsiver/tutorial-04-images-dimensions.html) 11 | - [Step 5: Dealing with images filenames and URLs](https://nhoizey.github.io/images-responsiver/tutorial-05-images-urls.html) 12 | - [Step 6: Even better responsive images](https://nhoizey.github.io/images-responsiver/tutorial-06-even-better-responsive-images.html) 13 | 14 | --- 15 | 16 | Let's say you have this HTML file: 17 | 18 | 19 | 20 | And this CSS file: 21 | 22 | 23 | 24 | We want the content to occupy 90% of the available space (but no more than `40em`, better for readability of multi-lines text), and the logo to use 50% of this content width, floated on the right. 25 | 26 | You can [open it here](https://nhoizey.github.io/images-responsiver/examples/01-without-images-responsiver/page.html): 27 | 28 | ![A screenshot of the example page](example-screenshot.png) 29 | 30 | The page looks exactly how you want, thanks to the clean HTML structure and the CSS rules. 31 | 32 | But each image is available in only one single dimension (large probably), even if people with many different devices/browsers, with different viewport widths, would rather download only what's necessary. 33 | 34 | Now let's try to run `images-responsiver` on the HTML to enhance it. 35 | 36 | | **[Tutorial step 2 >](/images-responsiver/tutorial-02-images-responsiver-default.html)** | **[Back home >>](/images-responsiver/#documentation)** | 37 | -------------------------------------------------------------------------------- /docs/old/tutorial-06-even-better-responsive-images.md: -------------------------------------------------------------------------------- 1 | | **[<< Back home](/images-responsiver/#documentation)** | **[< Tutorial step 5](/images-responsiver/tutorial-05-images-urls.html)** | 2 | 3 | # Tutorial step 6: Even better responsive images 4 | 5 | --- 6 | 7 | - [Step 1: Default behavior without `images-responsiver`](https://nhoizey.github.io/images-responsiver/tutorial-01-without-images-responsiver.html) 8 | - [Step 2: Better behavior with `images-responsiver` and default configuration](https://nhoizey.github.io/images-responsiver/tutorial-02-images-responsiver-default.html) 9 | - [Step 3: Enhanced behavior with some configuration](https://nhoizey.github.io/images-responsiver/tutorial-03-images-responsiver-simple-configuration.html) 10 | - [Step 4: Making it more robust with image dimensions](https://nhoizey.github.io/images-responsiver/tutorial-04-images-dimensions.html) 11 | - [Step 5: Dealing with images filenames and URLs](https://nhoizey.github.io/images-responsiver/tutorial-05-images-urls.html) 12 | - **Step 6: Even better responsive images** 13 | 14 | --- 15 | 16 | ## Adding classes 17 | 18 | When we added the `data-responsiver` attribute to the source image, we added some complexity because there was already a class, which could feel redundant: 19 | 20 | ```html 21 | 22 | ``` 23 | 24 | You can use an additional parameter to simplify this: 25 | 26 | ```javascript 27 | const options = { 28 | … 29 | logo: { 30 | … 31 | classes: ['logo'], 32 | }, 33 | }; 34 | ``` 35 | 36 | With this configuration, the class(es) will be added by `images-responsiver`, so your source HTML doesn't need it anymore: 37 | 38 | ```html 39 | My logo 40 | ``` 41 | 42 | ## Adding attributes 43 | 44 | You might also want to add the same attribute to every image, or at least every image using one specific preset. 45 | 46 | You can do that with the additional `attributes` parameter. 47 | 48 | For example, if you want to benefit from recent [native lazy-loading in modern browsers](https://web.dev/native-lazy-loading/), you can further enhance your configuration with this: 49 | 50 | ```javascript 51 | const options = { 52 | default: { 53 | attributes: { 54 | loading: 'lazy', 55 | }, 56 | } 57 | … 58 | logo: { 59 | … 60 | classes: ['logo'], 61 | }, 62 | }; 63 | ``` 64 | 65 | ## Running hooks before and after transformation 66 | 67 | _To be continued…_ 68 | 69 | ## Targeting images to transform 70 | 71 | 72 | 73 | | **[Examples >](/images-responsiver/examples.html)** | **[Back home >>](/images-responsiver/#documentation)** | 74 | -------------------------------------------------------------------------------- /docs/old/index.md: -------------------------------------------------------------------------------- 1 | # images-responsiver 2 | 3 | [![Build Status](https://travis-ci.org/nhoizey/images-responsiver.svg?branch=main)](https://travis-ci.org/nhoizey/images-responsiver) 4 | [![GitHub stars](https://img.shields.io/github/stars/nhoizey/images-responsiver.svg?style=social)](https://github.com/nhoizey/images-responsiver/stargazers) 5 | 6 | **`images-responsiver` is a simple solution for most responsive images needs**. 7 | 8 | Responsive Images are difficult to implement, but they're **required to provide a good performance to Web users**. 9 | 10 | `images-responsiver` transforms simple `` HTML tags into better responsive images syntax with `srcset` and `sizes` attributes. 11 | 12 | Knowing that [`` is only required for rare advanced usages](https://cloudfour.com/thinks/dont-use-picture-most-of-the-time/), **`images-responsiver` should be enough for most use cases**, known as Resolution Switching. Read [this article on Cloudfour's blog to know more of the theory](https://cloudfour.com/thinks/responsive-images-the-simple-way/). 13 | 14 | `images-responsiver` is also available as a plugin for [Eleventy](https://www.11ty.dev/), a great Static Site Generator: [eleventy-plugin-images-responsiver](https://github.com/nhoizey/eleventy-plugin-images-responsiver). It allows authors to use the simple and standard Markdown syntax for images (`![alt text](image.jpg)`) and yet get responsive images in the generated HTML, with `srcset` and `sizes` attributes. 15 | 16 | ## Documentation 17 | 18 | - [Installation](https://nhoizey.github.io/images-responsiver/installation.html) 19 | - Tutorial 20 | - [Step 1: Default behavior without `images-responsiver`](https://nhoizey.github.io/images-responsiver/tutorial-01-without-images-responsiver.html) 21 | - [Step 2: Better behavior with `images-responsiver` and default configuration](https://nhoizey.github.io/images-responsiver/tutorial-02-images-responsiver-default.html) 22 | - [Step 3: Enhanced behavior with some configuration](https://nhoizey.github.io/images-responsiver/tutorial-03-images-responsiver-simple-configuration.html) 23 | - [Step 4: Making it more robust with image dimensions](https://nhoizey.github.io/images-responsiver/tutorial-04-images-dimensions.html) 24 | - [Step 5: Dealing with images filenames and URLs](https://nhoizey.github.io/images-responsiver/tutorial-05-images-urls.html) 25 | - [Step 6: Even better responsive images](https://nhoizey.github.io/images-responsiver/tutorial-06-even-better-responsive-images.html) 26 | - [Examples](https://nhoizey.github.io/images-responsiver/examples.html) 27 | - [Debugging](https://nhoizey.github.io/images-responsiver/debugging.html) 28 | 29 | ## Authors 30 | 31 | - [Nicolas Hoizey](https://github.com/nhoizey): Idea and initial work, maintainer 32 | 33 | See also the list of [contributors](https://github.com/nhoizey/images-responsiver/contributors) who participated in this project. 34 | 35 | ## License 36 | 37 | This project is licensed under the [MIT License](LICENSE.md). 38 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/05-advanced-options.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`images with advanced options resizedImageUrl 1`] = ` 4 | " 5 | 6 | 7 | 14 | 15 | 16 | " 17 | `; 18 | 19 | exports[`images with advanced options runAfter 1`] = ` 20 | " 21 | 22 | 23 | 36 | 37 | 38 | " 39 | `; 40 | 41 | exports[`images with advanced options runBefore 1`] = ` 42 | " 43 | 44 | 45 | 57 | 58 | 59 | " 60 | `; 61 | 62 | exports[`images with advanced options selector 1`] = ` 63 | " 64 | 65 | 66 | 78 | 79 | 80 | " 81 | `; 82 | 83 | exports[`images with advanced options selector in wrong place don't break it 1`] = ` 84 | " 85 | 86 | 87 | 112 | 113 | 114 | " 115 | `; 116 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at nicolas@hoizey.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /docs/old/tutorial-04-images-dimensions.md: -------------------------------------------------------------------------------- 1 | | **[<< Back home](/images-responsiver/#documentation)** | **[< Examples](/images-responsiver/examples.html)** | 2 | 3 | # Tutorial step 4: Making it more robust with image dimensions 4 | 5 | --- 6 | 7 | - [Step 1: Default behavior without `images-responsiver`](https://nhoizey.github.io/images-responsiver/tutorial-01-without-images-responsiver.html) 8 | - [Step 2: Better behavior with `images-responsiver` and default configuration](https://nhoizey.github.io/images-responsiver/tutorial-02-images-responsiver-default.html) 9 | - [Step 3: Enhanced behavior with some configuration](https://nhoizey.github.io/images-responsiver/tutorial-03-images-responsiver-simple-configuration.html) 10 | - **Step 4: Making it more robust with image dimensions** 11 | - [Step 5: Dealing with images filenames and URLs](https://nhoizey.github.io/images-responsiver/tutorial-05-images-urls.html) 12 | - [Step 6: Even better responsive images](https://nhoizey.github.io/images-responsiver/tutorial-06-even-better-responsive-images.html) 13 | 14 | --- 15 | 16 | We might still have an issue: even if we set a maximum width lower than `1280px` (like `512px` for the logo), we should not be able to define a width that is larger than the actual width of the pristine image, the largest we have before any computing. If we do that, we lie to the browser, and it might render the image at the width we told him, instead of the actual one, resulting in bad rendered quality. 17 | 18 | So we should be able to tell `images-responsiver` about the actual width of the pristine image. 19 | 20 | Why invent a new parameter? We already have the `width` attribute in HTML, let's use it, `images-responsiver` can read it. 21 | 22 | _Note: it's anyway always a good idea to have the `width` and `height` attributes defined in images, as [it will enhance the page rendering performance](https://www.youtube.com/watch?v=4-d_SoCHeWE)._ 23 | 24 | Actually, if you run `images-responsiver` in debug mode (learn [more about debugging here](/images-responsiver/debugging.html)), you'll get warnings about missing `width` attributes. Instead of just `node run.js`, run `DEBUG=* node run.js`, and you'll get this in the console: 25 | 26 | ```bash 27 | images-responsiver:info Transforming my-logo.png +0ms 28 | images-responsiver:warning The image should have a width attribute: my-logo.png +0ms 29 | images-responsiver:info Transforming colorful-netherlands.jpg +2ms 30 | images-responsiver:warning The image should have a width attribute: colorful-netherlands.jpg +1ms 31 | ``` 32 | 33 | If the pristine image for the logo is `400px` wide, and the other pristine image is `1600px` wide, here's our new source HTML: 34 | 35 | 36 | 37 | If we run the exact same Node.js script on it: 38 | 39 | 40 | 41 | The result is further improved: 42 | 43 | 44 | 45 | We should also update the CSS so that we don't try to render the image larger than it is. `width` can be replaced with `max-width`: 46 | 47 | 48 | 49 | Ok, but where and when are my-logo-58.png, my-logo-285.png, etc. generated? 50 | 51 | Let's see… 52 | 53 | | **[Tutorial step 5 >](/images-responsiver/tutorial-05-images-urls.html)** | **[Back home >>](/images-responsiver/#documentation)** | 54 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/03-no-options.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`image without options 2 images 1`] = ` 4 | " 5 | 6 | 7 | 20 | 21 | 22 | " 23 | `; 24 | 25 | exports[`image without options simple image 1`] = ` 26 | " 27 | 28 | 29 |

30 | content before 31 | 43 | content after 44 |

45 | 46 | 47 | " 48 | `; 49 | 50 | exports[`image without options simple image in subfolder 1`] = ` 51 | " 52 | 53 | 54 |

55 | content before 56 | 68 | content after 69 |

70 | 71 | 72 | " 73 | `; 74 | 75 | exports[`image without options simple image with absolute URL 1`] = ` 76 | " 77 | 78 | 79 |

80 | content before 81 | 93 | content after 94 |

95 | 96 | 97 | " 98 | `; 99 | 100 | exports[`image without options width attribute inferior to the fallback 1`] = ` 101 | " 102 | 103 | 104 | 111 | 112 | 113 | " 114 | `; 115 | 116 | exports[`image without options width attribute inferior to the minWidth 1`] = ` 117 | " 118 | 119 | 120 | 127 | 128 | 129 | " 130 | `; 131 | 132 | exports[`image without options width attribute superior to the fallback 1`] = ` 133 | " 134 | 135 | 136 | 143 | 144 | 145 | " 146 | `; 147 | -------------------------------------------------------------------------------- /docs/old/tutorial-02-images-responsiver-default.md: -------------------------------------------------------------------------------- 1 | | **[<< Back home](/images-responsiver/#documentation)** | **[< Tutorial step 1](/images-responsiver/tutorial-01-without-images-responsiver.html)** | 2 | 3 | # Tutorial step 2: Better behavior with images-responsiver and default configuration 4 | 5 | --- 6 | 7 | - [Step 1: Default behavior without `images-responsiver`](https://nhoizey.github.io/images-responsiver/tutorial-01-without-images-responsiver.html) 8 | - **Step 2: Better behavior with `images-responsiver` and default configuration** 9 | - [Step 3: Enhanced behavior with some configuration](https://nhoizey.github.io/images-responsiver/tutorial-03-images-responsiver-simple-configuration.html) 10 | - [Step 4: Making it more robust with image dimensions](https://nhoizey.github.io/images-responsiver/tutorial-04-images-dimensions.html) 11 | - [Step 5: Dealing with images filenames and URLs](https://nhoizey.github.io/images-responsiver/tutorial-05-images-urls.html) 12 | - [Step 6: Even better responsive images](https://nhoizey.github.io/images-responsiver/tutorial-06-even-better-responsive-images.html) 13 | 14 | --- 15 | 16 | You can use this Node.js script: 17 | 18 | 19 | 20 | Run it from the command line: 21 | 22 | ```bash 23 | node run.js 24 | ``` 25 | 26 | You'll get the enhanced page in this new HTML file: 27 | 28 | 29 | 30 | _Note: a `pristine` value is added to the image's dataset with the original URL, in case you want to do anything else with it later (provide a "zoom" link for example)._ 31 | 32 | The situation is better, because users with small viewports (and reasonable screen densities) will download smaller images. 33 | 34 | _Note: `images-responsiver` don't do anything to:_ 35 | 36 | - _SVG images_ 37 | - _bitmap images that don't have any `src` attribute_ 38 | - _bitmap images that already have a `srcset` attribute_ 39 | 40 | But there are a few issues: 41 | 42 | - The `sizes` attributes with a `100vw` value tells the browser that the image will be rendered on the full width of the viewport, but that's not what we want: 43 | - We know the second image (`colorful-netherlands.jpg`) should occupy only the width of the content, which per CSS rules is `90vw` with a maximum of `40em`. This `40em` width for the content is reached when the viewport reaches `40 / 0.9 = 45em` (rounded). So we should be able to set a `sizes` attribute with the value `(max-width: 45em) 90vw, 40em` (or `(min-width: 45em) 40em, 90vw` but the result is the same). 44 | - For the logo (the first image), we need one fifth of the content width, so the `sizes` attribute value is simple math from the previous one: `(max-width: 45em) 18vw, 8em`. 45 | - If the maximum width for the logo is `8em` on largest viewports, and most users have a browser with a default root font size of `16px`, these `8em` are computed to `128px`. Let's consider the user is on a high density display ("Retina" in Apple language), so double it, and that [the user might need to increase the font size for readability](https://nicolas-hoizey.com/articles/2018/06/15/users-do-change-font-size/), so double it a second time. We then need an image with a maximum useful width of `512px`. We see the HTML tells the browser that the maximum width for the image is `1280px` (the `w` descriptor is the "`w`idth in pixels"). Far beyond what we need! We should be able to set a maximum (and sometimes a minimum) for the sequence of image widths in the `srcset` attribute. 46 | 47 | These issues exist because there is no default configuration that would be correct for all use cases. 48 | 49 | Let's tell the `images-responsiver` what to do with the images… 50 | 51 | | **[Tutorial step 3 >](/images-responsiver/tutorial-03-images-responsiver-simple-configuration.html)** | **[Back home >>](/images-responsiver/#documentation)** | 52 | -------------------------------------------------------------------------------- /docs/old/nicolashoizeycom.md: -------------------------------------------------------------------------------- 1 | | **[<< Back home](/images-responsiver/#documentation)** | **[< Examples](/images-responsiver/examples.html)** | 2 | 3 | # Usage on nicolas-hoizey.com 4 | 5 | I use [Eleventy](https://www.11ty.dev/) to build my own site , and `eleventy-plugin-images-responsiver` to provide access to `images-responsiver`. 6 | 7 | Each article has it’s own folder, with the Markdown file and image(s). 8 | 9 | For example, [here](https://github.com/nhoizey/nicolas-hoizey.com/tree/main/src/articles/2020/01/10/can-we-monitor-user-happiness-on-the-web-with-performance-tools): 10 | 11 | ``` 12 | src/ 13 | articles/ 14 | 2020/ 15 | 01/ 16 | 10/ 17 | can-we-monitor-user-happiness-on-the-web-with-performance-tools/ 18 | index.md 19 | speedcurve-user-happiness-monitoring.png 20 | ``` 21 | 22 | I use the same folder hierarchy in the source as in the site, so **I don’t have any permalink to compute** (small performance gain?) for HTML files. 23 | 24 | I [added image extensions to `templateFormats`](https://github.com/nhoizey/nicolas-hoizey.com/blob/a25262c221ff8f19e129352fd67df89092514a1d/.eleventy.js#L290) so that Eleventy copy them for me in the same hierarchy: 25 | 26 | ```javascript 27 | module.exports = function(eleventyConfig) { 28 | … 29 | return { 30 | templateFormats: ['md', 'njk', 'jpg', 'png', 'gif', 'kmz', 'zip', 'scss'], 31 | … 32 | }; 33 | }; 34 | ``` 35 | 36 | Where [I add the plugin](https://github.com/nhoizey/nicolas-hoizey.com/blob/a25262c221ff8f19e129352fd67df89092514a1d/.eleventy.js#L261-L263): 37 | 38 | ```javascript 39 | const imagesResponsiver = require('eleventy-plugin-images-responsiver'); 40 | const imagesResponsiverConfig = require('./src/_data/images-responsiver-config.js'); 41 | eleventyConfig.addPlugin(imagesResponsiver, imagesResponsiverConfig); 42 | ``` 43 | 44 | I use [these options](https://github.com/nhoizey/nicolas-hoizey.com/blob/a441e2972d8cb6bff76697ea596522ec98f5ff76/src/_data/images-responsiver-config.js). 45 | 46 | The [`runBefore` hook](https://github.com/nhoizey/nicolas-hoizey.com/blob/a441e2972d8cb6bff76697ea596522ec98f5ff76/src/_data/images-responsiver-config.js#L6-L39) finds images in the page HTML, computes the image dimensions to add the `width`/`height` attributes (good for performance) and the full URL: 47 | 48 | ```javascript 49 | const runBeforeHook = (image, document) => { 50 | let documentBody = document.querySelector('body'); 51 | let srcPath = documentBody.getAttribute('data-img-src'); 52 | // TODO: get "dist/" from config 53 | let distPath = documentBody 54 | .getAttribute('data-img-dist') 55 | .replace(/^dist/, ''); 56 | 57 | let imageSrc = image.getAttribute('src'); 58 | 59 | let imageUrl = ''; 60 | 61 | if (imageSrc.match(/^(https?:)?\/\//)) { 62 | // TODO: find a way to get a remote image's dimensions 63 | // TODO: some images are local but have an absolute URL 64 | imageUrl = imageSrc; 65 | } else { 66 | let imageDimensions; 67 | if (imageSrc[0] === '/') { 68 | // TODO: get "src/" from Eleventy config 69 | imageDimensions = imageSize('./src' + imageSrc); 70 | imageUrl = site.url + imageSrc; 71 | } else { 72 | // This is a relative URL 73 | imageDimensions = imageSize(srcPath + imageSrc); 74 | imageUrl = site.url + distPath + imageSrc; 75 | } 76 | image.setAttribute('width', imageDimensions.width); 77 | image.setAttribute('height', imageDimensions.height); 78 | image.setAttribute('src', imageUrl); 79 | } 80 | 81 | image.dataset.responsiver = image.className; 82 | }; 83 | ``` 84 | 85 | The plugin then [transforms the URL as I told it](https://github.com/nhoizey/nicolas-hoizey.com/blob/a441e2972d8cb6bff76697ea596522ec98f5ff76/src/_data/images-responsiver-config.js#L73-L74): 86 | 87 | ```javascript 88 | resizedImageUrl: (src, width) => 89 | `https://res.cloudinary.com/nho/image/fetch/q_auto,f_auto,w_${width}/${src}`, 90 | ``` 91 | 92 | Here, I’m using Cloudinary ([sign-up for free](https://nho.io/cloudinary-signup), at least to try) to resize (and optimize) images, so I don’t have to compute any image on my local build. 93 | 94 | | **[Examples >](/images-responsiver/examples.html)** | **[Back home >>](/images-responsiver/#documentation)** | 95 | -------------------------------------------------------------------------------- /docs/old/tutorial-03-images-responsiver-simple-configuration.md: -------------------------------------------------------------------------------- 1 | | **[<< Back home](/images-responsiver/#documentation)** | **[< Tutorial step 2](/images-responsiver/tutorial-02-images-responsiver-default.html)** | 2 | 3 | # Tutorial step 3: Enhanced behavior with some configuration 4 | 5 | --- 6 | 7 | - [Step 1: Default behavior without `images-responsiver`](https://nhoizey.github.io/images-responsiver/tutorial-01-without-images-responsiver.html) 8 | - [Step 2: Better behavior with `images-responsiver` and default configuration](https://nhoizey.github.io/images-responsiver/tutorial-02-images-responsiver-default.html) 9 | - **Step 3: Enhanced behavior with some configuration** 10 | - [Step 4: Making it more robust with image dimensions](https://nhoizey.github.io/images-responsiver/tutorial-04-images-dimensions.html) 11 | - [Step 5: Dealing with images filenames and URLs](https://nhoizey.github.io/images-responsiver/tutorial-05-images-urls.html) 12 | - [Step 6: Even better responsive images](https://nhoizey.github.io/images-responsiver/tutorial-06-even-better-responsive-images.html) 13 | 14 | --- 15 | 16 | We need different `sizes` attribute values for different image use cases, but we don't want to repeat them for each images, and we want to provide content authors (even if that's us) with something as simple as possible so that they can focus on content. 17 | 18 | Let's define **presets**, and configuration options attached to them. 19 | 20 | First, we define a `default` preset that will be used for all images where the author doesn't set anything special. 21 | 22 | ```javascript 23 | const options = { 24 | default: { 25 | sizes: '(max-width: 45em) 90vw, 40em', 26 | }, 27 | }; 28 | ``` 29 | 30 | We set the default `sizes` attribute value, considering we will never have any image larger than the content. It overrides `images-responsiver`'s default value of `100vw`. 31 | 32 | We now need to add a specific preset for the logo, which has different needs: 33 | 34 | ```javascript 35 | const options = { 36 | default: { 37 | sizes: '(max-width: 45em) 90vw, 40em', 38 | }, 39 | logo: { 40 | minWidth: 58, 41 | maxWidth: 512, 42 | steps: 3, 43 | fallbackWidth: 128, 44 | sizes: '(max-width: 45em) 18vw, 8em', 45 | }, 46 | }; 47 | ``` 48 | 49 | The logo takes one fifth of the content width, so on small `320px` viewports with normal screen density it needs `320px * 90% * 1/5 = 58px` (rounded), and on largest viewports on Retina screens (with font-size doubled), it needs `512px` as we computed earlier. 50 | 51 | We also tell `images-responsiver` with `steps` that 3 different image widths should be enough, instead of the default 5. 52 | 53 | Finally, `fallbackWidth` is the width of the image we put in the `src` attribute for compatibility with really old browsers. Most of these browsers are desktop ones, so don't use a value too small. 54 | 55 | We also need a way for content authors to specify what preset an image should use, if the default one is not enough. We will use a `data-responsiver` data attribute, with the name of the preset as the value. 56 | 57 | So for example, we change the HTML for the logo from: 58 | 59 | ```html 60 | 61 | ``` 62 | 63 | To: 64 | 65 | ```html 66 | 67 | ``` 68 | 69 | _Note: Each image can use multiple presets in the `data-responsiver` attribute, each value separated by a space (like classes). Settings from each preset surcharges the previous one(s), in the order they're declared._ 70 | 71 | We can now run this updated Node.js script: 72 | 73 | 74 | 75 | Here's the new enhanced HTML that we get: 76 | 77 | 78 | 79 | This is really much better, the browser will download images with much smaller dimensions (and weight), yet large enough for a great rendering. 80 | 81 | | **[Tutorial step 4 >](/images-responsiver/tutorial-04-images-dimensions.html)** | **[Back home >>](/images-responsiver/#documentation)** | 82 | -------------------------------------------------------------------------------- /docs/old/tutorial-05-images-urls.md: -------------------------------------------------------------------------------- 1 | | **[<< Back home](/images-responsiver/#documentation)** | **[< Tutorial step 4](/images-responsiver/tutorial-04-images-dimensions.html)** | 2 | 3 | # Tutorial step 5: Dealing with images filenames and URLs 4 | 5 | --- 6 | 7 | - [Step 1: Default behavior without `images-responsiver`](https://nhoizey.github.io/images-responsiver/tutorial-01-without-images-responsiver.html) 8 | - [Step 2: Better behavior with `images-responsiver` and default configuration](https://nhoizey.github.io/images-responsiver/tutorial-02-images-responsiver-default.html) 9 | - [Step 3: Enhanced behavior with some configuration](https://nhoizey.github.io/images-responsiver/tutorial-03-images-responsiver-simple-configuration.html) 10 | - [Step 4: Making it more robust with image dimensions](https://nhoizey.github.io/images-responsiver/tutorial-04-images-dimensions.html) 11 | - **Step 5: Dealing with images filenames and URLs** 12 | - [Step 6: Even better responsive images](https://nhoizey.github.io/images-responsiver/tutorial-06-even-better-responsive-images.html) 13 | 14 | --- 15 | 16 | `images-responsiver` doesn't transform images files, it "only" transforms HTML. That's already a lot, as you should have noticed. 17 | 18 | You have to define how these multiple width images are generated: 19 | 20 | - you can transform them yourself with an asynchronous batch script, but that might be difficult if you don't know the widths there will be in the HTML 21 | - you can parse the enhanced HTML to list the images you need, either outside `images-responsiver` or thanks to the `runAfter` parameter, which is a hook function that runs at the end of the transformation 22 | - you can use dynamic image rendering, computing the required image on the server when it is requested by the browser 23 | - either with your self hosted solution, with [a simple PHP script](https://css-tricks.com/snippets/php/server-side-image-resizer/) for example, or [thumbor](http://thumbor.org/), an "open-source smart on-demand image cropping, resizing and filters" solution 24 | - or with an image CDN like Cloudinary, Imgix, Akamai Image Manager, etc. 25 | 26 | Some of these solutions might require specific URL for the images to compute, they might not be compatible with image names like `my-logo-58.png`. 27 | 28 | ## Defining your own URL format 29 | 30 | That's why you can use the `resizedImageUrl` function in `images-responsiver` default preset. This is the default function: 31 | 32 | ```javascript 33 | (src, width) => src.replace(/^(.*)(\.[^\.]+)$/, '$1-' + width + '$2'); 34 | ``` 35 | 36 | It transforms `(my-logo.png, 58)` into `my-logo-58.png`. 37 | 38 | You can define your own simple function to replace the default one. 39 | 40 | For example, if the width has to be a `w` query parameter: 41 | 42 | ```javascript 43 | const options = { 44 | default: { 45 | resizedImageUrl: (src, width) => `${src}?w=${width}`, 46 | }, 47 | }; 48 | ``` 49 | 50 | It will transform `(my-logo.png, 58)` into `my-logo.png?w=58`. 51 | 52 | ## Using an image CDN 53 | 54 | Relying on a third party service might make some fear of losing control, but resizing and optimizing images as much and as good as such services is really hard, and it requires computing power and storage space. Here, it requires "just" an URL. 55 | 56 | ### Using Cloudinary 57 | 58 | For Cloudinary ([sign-up for free](https://nho.io/cloudinary-signup), it should be enough for most personal sites), like explained in [the exemple usage from nicolas-hoizey.com](./nicolashoizeycom.html), here is the `resizedImageUrl` function: 59 | 60 | ```javascript 61 | (src, width) => 62 | `https://res.cloudinary.com/nho/image/fetch/q_auto,f_auto,w_${width}/${src}`, 63 | ``` 64 | 65 | Here, `nho` is the cloud name, linked to my own Cloudinary account. 66 | 67 | This URL will: 68 | 69 | - resize the pristine image (`${src}`) to the desired width (`w_${width}`), 70 | - chose the best compression level without sacrificing quality (`q_auto`), 71 | - and chose the best encoding format depending of the browser capacity (`f_auto`), for example `WebP`, even if the pristine image is a `JPEG`. 72 | 73 | ### Using other image CDNs 74 | 75 | Feel free to submit a [Pull Request](https://github.com/nhoizey/images-responsiver/pulls) to enhance documentation with other Image CDN examples. 76 | 77 | | **[Tutorial step 6 >](/images-responsiver/tutorial-06-even-better-responsive-images.html)** | **[Back home >>](/images-responsiver/#documentation)** | 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Images Responsiver 2 | 3 | [![GitHub stars](https://img.shields.io/github/stars/nhoizey/images-responsiver.svg?style=social)](https://github.com/nhoizey/images-responsiver/stargazers) 4 | [![Follow @nhoizey@mamot.fr](https://img.shields.io/mastodon/follow/000262395?domain=https%3A%2F%2Fmamot.fr&style=social)](https://mamot.fr/@nhoizey) 5 | 6 | Images Responsiver tries to **help developers** make it **easy for content authors** to tackle **responsive images needs**. 7 | 8 | 9 | 10 | 11 | - [Why does this project exist?](#why-does-this-project-exist) 12 | - [A Node.js package](#a-nodejs-package) 13 | - [A plugin for Eleventy](#a-plugin-for-eleventy) 14 | - [Contributing](#contributing) 15 | - [Tools and automations](#tools-and-automations) 16 | - [License](#license) 17 | - [Authors](#authors) 18 | 19 | 20 | 21 | ## Why does this project exist? 22 | 23 | As Steve Jobs [once said](https://www.youtube.com/watch?v=oeqPrUmVz-o): 24 | 25 | > You've got to start with the customer experience and run backwards to the technology. 26 | 27 | **Responsive images are one of the most difficult topics nowadays** for front end developers and content authors, they involve multiple features and syntaxes, but they are **required to provide a good performance to Web users**. 28 | 29 | Most content authors should not have to learn the complex responsive images HTML syntax, how and it is used by browsers to load the right image for current viewing context. 30 | 31 | ### A Node.js package 32 | 33 | Images Responsiver transforms plain, simple `` HTML tags into better responsive images syntax with `srcset` and `sizes` attributes. 34 | 35 | Knowing that [`` is only required for rare advanced usages](https://cloudfour.com/thinks/dont-use-picture-most-of-the-time/), **Images Responsiver should be enough for most use cases**, where the need is known as Resolution Switching. 36 | 37 | Read [this article on Cloudfour's blog to know more of the theory](https://cloudfour.com/thinks/responsive-images-the-simple-way/). 38 | 39 | ### A plugin for Eleventy 40 | 41 | Images Responsiver is [also available as a plugin](https://nhoizey.github.io/eleventy-plugin-images-responsiver/) for [Eleventy](https://www.11ty.dev/), an awesome Static Site Generator. 42 | 43 | It allows authors to use the simple and **standard Markdown syntax for images** — `![alt text](image.jpg)` — and yet get responsive images in the generated HTML. 44 | 45 | ## Contributing 46 | 47 | First, thank you for considering contributing to `images-responsiver`! It's people like you that make the open source community such a great community! 😊 48 | 49 | There are many ways to contribute to this project. [Get started here](https://github.com/nhoizey/images-responsiver/blob/main/CONTRIBUTING.md). 50 | 51 | ## Tools and automations 52 | 53 | - Tests are run by [jest](https://jestjs.io/) and written in [the `__tests__` folder](https://github.com/nhoizey/images-responsiver/tree/main/packages/images-responsiver/__tests__) for each package. 54 | - Pull Requests are checked with tests run on GitHub workflows (see [configuration](https://github.com/nhoizey/images-responsiver/blob/main/.github/workflows/tests.yml)) 55 | - Dependencies updates are automated with [dependabot](https://github.blog/2020-06-01-keep-all-your-packages-up-to-date-with-dependabot/) (see [configuration](https://github.com/nhoizey/images-responsiver/blob/main/.github/dependabot.yml)) 56 | - Pull Requests are rebased as necessary, and merged automaticaly by [Kodiak](https://kodiakhq.com/) if there is an `automerge 🤞` label (set by dependabot for example) and GitHub workflow check is OK (see [configuration](https://github.com/nhoizey/images-responsiver/blob/main/.kodiak.toml)) 57 | - The documentation is written as Markdown files in [the `docs/` folder](https://github.com/nhoizey/images-responsiver/tree/main/docs), and transformed into HTML by [Jekyll](https://jekyllrb.com/) powered [GitHub Pages](https://pages.github.com/): 58 | 59 | ## License 60 | 61 | This project is licensed under the [MIT License](https://github.com/nhoizey/images-responsiver/blob/main/LICENSE.md). 62 | 63 | ## Authors 64 | 65 | - [Nicolas Hoizey](https://github.com/nhoizey): Idea and initial work, maintainer 66 | 67 | See also the list of [contributors](https://github.com/nhoizey/images-responsiver/contributors) who participated in this project. 68 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Images Responsiver 2 | 3 | [![Build Status](https://travis-ci.org/nhoizey/images-responsiver.svg?branch=main)](https://travis-ci.org/nhoizey/images-responsiver) 4 | [![GitHub stars](https://img.shields.io/github/stars/nhoizey/images-responsiver.svg?style=social)](https://github.com/nhoizey/images-responsiver/stargazers) 5 | [![license](https://img.shields.io/github/license/nhoizey/images-responsiver)](https://github.com/nhoizey/images-responsiver/blob/main/LICENSE.md) 6 | ![Libraries.io dependency status for latest release, scoped npm package](https://img.shields.io/librariesio/release/npm/images-responsiver) 7 | [![Pull requests welcome](https://img.shields.io/badge/PRs-welcome-blueviolet)](https://github.com/nhoizey/images-responsiver/blob/main/CONTRIBUTING.md) 8 | 9 | Images Responsiver tries to **help developers** make it **easy for content authors** to tackle **responsive images needs**. 10 | 11 | 12 | 13 | 14 | - [Why does this project exist?](#why-does-this-project-exist) 15 | - [A Node.js package](#a-nodejs-package) 16 | - [A plugin for Eleventy](#a-plugin-for-eleventy) 17 | - [Contributing](#contributing) 18 | - [Tools and automations](#tools-and-automations) 19 | - [License](#license) 20 | - [Authors](#authors) 21 | 22 | 23 | 24 | ## Why does this project exist? 25 | 26 | As Steve Jobs [once said](https://www.youtube.com/watch?v=oeqPrUmVz-o): 27 | 28 | > You've got to start with the customer experience and run backwards to the technology. 29 | 30 | **Responsive images are one of the most difficult topics nowadays** for front end developers and content authors, they involve multiple features and syntaxes, but they are **required to provide a good performance to Web users**. 31 | 32 | Most content authors should not have to learn the complex responsive images HTML syntax, how and it is used by browsers to load the right image for current viewing context. 33 | 34 | ### A Node.js package 35 | 36 | Images Responsiver transforms plain, simple `` HTML tags into better responsive images syntax with `srcset` and `sizes` attributes. 37 | 38 | Knowing that [`` is only required for rare advanced usages](https://cloudfour.com/thinks/dont-use-picture-most-of-the-time/), **Images Responsiver should be enough for most use cases**, where the need is known as Resolution Switching. 39 | 40 | Read [this article on Cloudfour's blog to know more of the theory](https://cloudfour.com/thinks/responsive-images-the-simple-way/). 41 | 42 | ### A plugin for Eleventy 43 | 44 | Images Responsiver is [also available as a plugin](https://nhoizey.github.io/eleventy-plugin-images-responsiver/) for [Eleventy](https://www.11ty.dev/), an awesome Static Site Generator. 45 | 46 | It allows authors to use the simple and **standard Markdown syntax for images** — `![alt text](image.jpg)` — and yet get responsive images in the generated HTML. 47 | 48 | ## Contributing 49 | 50 | First, thank you for considering contributing to `images-responsiver`! It's people like you that make the open source community such a great community! 😊 51 | 52 | There are many ways to contribute to this project. [Get started here](https://github.com/nhoizey/images-responsiver/blob/main/CONTRIBUTING.md). 53 | 54 | ## Tools and automations 55 | 56 | - Tests are run by [jest](https://jestjs.io/) and written in [the `__tests__` folder](https://github.com/nhoizey/images-responsiver/tree/main/packages/images-responsiver/__tests__) for each package. 57 | - Pull Requests are checked with tests run on GitHub workflows (see [configuration](https://github.com/nhoizey/images-responsiver/blob/main/.github/workflows/tests.yml)) 58 | - Dependencies updates are automated with [dependabot](https://github.blog/2020-06-01-keep-all-your-packages-up-to-date-with-dependabot/) (see [configuration](https://github.com/nhoizey/images-responsiver/blob/main/.github/dependabot.yml)) 59 | - Pull Requests are rebased as necessary, and merged automaticaly by [Kodiak](https://kodiakhq.com/) if there is an `automerge 🤞` label (set by dependabot for example) and GitHub workflow check is OK (see [configuration](https://github.com/nhoizey/images-responsiver/blob/main/.kodiak.toml)) 60 | - The documentation is written as Markdown files in [the `docs/` folder](https://github.com/nhoizey/images-responsiver/tree/main/docs), and transformed into HTML by [Jekyll](https://jekyllrb.com/) powered [GitHub Pages](https://pages.github.com/): 61 | 62 | ## License 63 | 64 | This project is licensed under the [MIT License](https://github.com/nhoizey/images-responsiver/blob/main/LICENSE.md). 65 | 66 | ## Authors 67 | 68 | - [Nicolas Hoizey](https://github.com/nhoizey): Idea and initial work, maintainer 69 | 70 | See also the list of [contributors](https://github.com/nhoizey/images-responsiver/contributors) who participated in this project. 71 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | # 1.12.1 6 | 7 | - Split the monorepo into two individual repos: 8 | - [`images-responsiver`](https://github.com/nhoizey/images-responsiver/) 9 | - [`eleventy-plugin-images-responsiver`](https://github.com/nhoizey/eleventy-plugin-images-responsiver/) 10 | 11 | # Before 1.12.1 12 | 13 | Before release 1.12.1, version numbers were synchronized for [`images-responsiver`](https://github.com/nhoizey/images-responsiver/) and [`eleventy-plugin-images-responsiver`](https://github.com/nhoizey/eleventy-plugin-images-responsiver/). 14 | 15 | ## 1.12.0 16 | 17 | - add a sizesOverride setting to allow setting sizes attribute in the source HTML ([add0043](https://github.com/nhoizey/images-responsiver/commit/add0043ed2fbaf9e13d5a2ebf324aeea2766ff9a)), closes [#191](https://github.com/nhoizey/images-responsiver/issues/191) 18 | 19 | ## 1.11.0 20 | 21 | - support a "false" value for the data-responsiver attribute ([745d613](https://github.com/nhoizey/images-responsiver/commit/745d61398c8019bdb9876eafb1f7b50fc816c992)) 22 | 23 | ## 1.10.0 24 | 25 | - add support for data-src for lazy loaded images ([c5e6f6e](https://github.com/nhoizey/images-responsiver/commit/c5e6f6e8efe84e05645b2fd8bf5d1388ce6fa739)) 26 | 27 | ## 1.9.0 28 | 29 | - Replace basicHTML with LinkeDOM #138 30 | - basicHTML is deprecated, LinkeDOM recommended instead 31 | - Eleventy build with eleventy-plugin-images-responsiver is now **[approximatively 30 % faster](https://github.com/nhoizey/images-responsiver/issues/138#issuecomment-868592521)** 🎉 32 | 33 | ## 1.8.4 34 | 35 | - Update BasicHTML dependency to fix issue with inline CSS custom properties https://github.com/WebReflection/basicHTML/issues/56 36 | 37 | ## 1.8.3 38 | 39 | - Enhance basicHTML usage 40 | - Enhance docs 41 | 42 | ## 1.8.2 43 | 44 | - fix: ignores files with `permalink=false` that don't generate any HTML #90 45 | - fix: don't add a second `` tag if there's already one 98d75b7777dfc5b72603d8fdabacb85eff344f30 46 | 47 | ## 1.8.0 48 | 49 | - Upgrade basicHTML to 2.3.0 5095851880c1c9a916c41008eef009cf66a3db23 50 | - Make sure the parent is not a picture 979bad85619586fa3005227bdf9c27137dfc5ae0 51 | - Don't set twice the same image width in srcset 979bad85619586fa3005227bdf9c27137dfc5ae0 52 | 53 | ## 1.6.4 54 | 55 | - basichtml is now fixed! 👍 9211b73 56 | 57 | ## 1.6.3 58 | 59 | - Quick fix for BasicHTML issue 8ff43eb 60 | 61 | ## 1.6.2 62 | 63 | - Remove current documentation, waiting for full rewrite 1176884 64 | 65 | ## 1.6.1 66 | 67 | - Make sure people don't put a `selector` property in a preset other than default 789cabf 68 | - Add link to Cloudfour article 0dc4fb1 69 | 70 | ## 1.6.0 71 | 72 | - Add tests and organize them a26a1c4 73 | - Allow setting a list of widths instead of min and max f7787a4 74 | 75 | ## 1.5.1 76 | 77 | - Fix link to homepage 78b2673 78 | - Add debugging doc to readme 4e8eda9 79 | 80 | ## 1.5.0 81 | 82 | - Add docs about debugging ddcc735 83 | - Better default maxWidth 913bae0 84 | - Don't put images larger than pristine image in srcset e3f0cd9 85 | - Add debug dependency for better reporting of warnings c8c90fa 86 | 87 | ## 1.4.0 88 | 89 | - Split tests into several sets 29e9c65 90 | - Make sure maxWidth > minWidth 8736ebb 91 | - Make sure there are at least 2 steps for minWidth and maxWidth 1f80193 92 | - Make it clear the plugin is required when using Eleventy cfb1b59 93 | 94 | ## 1.3.1 95 | 96 | - Remove tests from the package e856e9c 97 | 98 | ## 1.3.0 99 | 100 | - Remove one useless level in settings 09ba8e8 101 | 102 | ## 1.2.2 103 | 104 | - Remove docs from npm package bf377bc 105 | 106 | ## 1.2.1 107 | 108 | - Make sure it works for images with width inferior to minWidth option 515e00d 109 | 110 | ## 1.2.0 111 | 112 | - 🐛 Make sure settings for one image are not reused for the next one a0b9a04 113 | - 🦋 Enhance example code with Prettier 97b2f3f 114 | - Add comments in scripts 79411f7 115 | - Split tutorial in several steps 6a446a8 116 | - Use clean snapshots with Prettier c19504f 117 | - Options parameter is optional 24f9f53 118 | 119 | ## 1.1.0 120 | 121 | - Prettier db4126e 122 | - Filter out images without a src, or not SVG, or with already a srcset 823ed6a 123 | 124 | ## 1.0.0 125 | 126 | - Pass document to the hooks 7eb4d36 127 | - Fine tune ESLint and Prettier configuration fedcfe6 128 | - Lint and make prettier aaa5d0a 129 | 130 | ## 0.15.0 131 | 132 | This release adds `runBefore` and `runAfter` hook methods to manipulate the image (geet data, modify it, etc.) before and after it's been "responsivised". 133 | 134 | - be strict for tests 0af2952 135 | - Upgrade BasicHTML in lock file 25da11c 136 | - Add runBefore and runAfter hook methods 39ed728 137 | - Upgrade BasicHTML module 0a95d38 138 | - Rename the example ddbcf10 139 | 140 | ## 0.14.0 141 | 142 | This release replaces JSDOM with BasicHTML to use less memory. 143 | Thanks @ziir! 🙏 144 | 145 | - Add Prettier config d57ccdb 146 | - Upgrading BasicHTML 3d0def7 147 | - Merge pull request #4 from nhoizey/pr/3 3bbd7b2 148 | - Merge pull request #3 from ziir/use-basichtml 34771bd 149 | - Fix tests 4f38a05 150 | - Use basicHTML over JSDOM 47d9f66 151 | - These should not be here a80ad35 152 | 153 | ## 0.13.2 154 | 155 | - Add a bad example for memory issue 0ea9bf4 156 | - Options can be empty 00e6851 157 | - Make sure to load the right page 0d60d4f 158 | - Arrange doccs a0f4cbd 159 | - Remove useless preset 466777a 160 | 161 | ## 0.13.1 162 | 163 | - Additional documentation 002ebbf 164 | - Simplify the example c1cd462 165 | 166 | ## 0.13.0 167 | 168 | - Add documentation 6a36911 169 | - Use the real jsdom package 8a82e9b 170 | - Rename license file bd4ce85 171 | - Update tests d4d26a6 172 | - No need for a message, this is overly verbose 6e0cba7 173 | - Why would it not work with relative URLs? 99ee24a 174 | - No need for a message, this is not a linter (bis) 0e251b7 175 | - No need for a message, this is not a linter dba53e3 176 | - This is now useless 9e00717 177 | - These settings have to be managed differently 76b33c9 178 | - Load jsdom only when needed b66964f 179 | - Try to clean memory 818a3bb 180 | - Use jsdom's serialize() method adbc94f 181 | - Use the dataset API 16a5d4a 182 | - Use the image's dataset instead of a class to define preset(s) to use 43ac9e9 183 | - Provide an example 0e83d49 184 | 185 | ## 0.12.0 186 | 187 | - Provide the main entry point 1991d2a 188 | - Remove leftovers 😅 f4e8bf1 189 | 190 | ## 0.11.0 191 | 192 | - Typo in the method name e0364f9 193 | 194 | ## 0.10.0 195 | 196 | - CSS typo ef8ad4a 197 | - Don't change images that already have a srcset attribute ef09d8b 198 | 199 | ## 0.9.0 200 | 201 | - Exit ASAP if the image can't be transformed 0937e32 202 | - Add tests for relative URL and SVG files ea97b83 203 | 204 | ## 0.8.0 205 | 206 | - Don't try anything on SVG images 76ee8e8 207 | 208 | ## 0.7.1 209 | 210 | - Typo 9407e75 211 | 212 | ## 0.7.0 213 | 214 | - More explicit messages 265a20e 215 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { parseHTML } = require('linkedom'); 4 | 5 | const deepmerge = require('deepmerge'); 6 | const clonedeep = require('lodash.clonedeep'); 7 | const overwriteMerge = (destinationArray, sourceArray, options) => sourceArray; 8 | const debug = require('debug'); 9 | 10 | const error = debug('images-responsiver:error'); 11 | const warning = debug('images-responsiver:warning'); 12 | const info = debug('images-responsiver:info'); 13 | 14 | const defaultSettings = { 15 | selector: ':not(picture) > img', 16 | resizedImageUrl: (src, width) => 17 | src.replace(/^(.*)(\.[^\.]+)$/, '$1-' + width + '$2'), 18 | runBefore: (image) => image, 19 | runAfter: (image) => image, 20 | fallbackWidth: 640, 21 | minWidth: 320, 22 | maxWidth: 1280, 23 | steps: 5, 24 | sizes: '100vw', 25 | sizesOverride: false, 26 | classes: [], 27 | attributes: {}, 28 | }; 29 | 30 | const imagesResponsiver = (html, options = {}, url = '') => { 31 | // Default settings 32 | let globalSettings = defaultSettings; 33 | 34 | // Overhide default settings with a "default" preset 35 | if (options.default !== undefined) { 36 | globalSettings = deepmerge(globalSettings, options.default, { 37 | arrayMerge: overwriteMerge, 38 | }); 39 | } 40 | 41 | const { document } = parseHTML(html); 42 | 43 | [...document.querySelectorAll(globalSettings.selector)] 44 | .filter((image) => { 45 | // Filter out images with data-responsiver="false" 46 | if ( 47 | 'responsiver' in image.dataset && 48 | image.dataset.responsiver === 'false' 49 | ) { 50 | return false; 51 | } 52 | 53 | // Filter out images with no src nor data-src 54 | if (!image.hasAttribute('src') && !('src' in image.dataset)) { 55 | return false; 56 | } 57 | 58 | // Filter out images with already a srcset 59 | if (image.hasAttribute('srcset')) { 60 | return false; 61 | } 62 | 63 | // Filter out images with no src and already a data-srcset 64 | if (!image.hasAttribute('src') && 'srcset' in image.dataset) { 65 | return false; 66 | } 67 | 68 | // Filter out images with a SVG src 69 | if ( 70 | image.hasAttribute('src') && 71 | image.getAttribute('src').endsWith('.svg') 72 | ) { 73 | return false; 74 | } 75 | 76 | // Filter out images with an Data URI src and no data-src or data-srcset 77 | // 78 | // For JS-based lazy loading, we need to use the data-src attribute, 79 | // but use an inline image as a placeholder to avoid a "broken" image 80 | if ( 81 | image.hasAttribute('src') && 82 | image.getAttribute('src').startsWith('data:image/') && 83 | !('src' in image.dataset) && 84 | !('srcset' in image.dataset) 85 | ) { 86 | return false; 87 | } 88 | 89 | return true; 90 | }) 91 | .forEach((image) => { 92 | let imageSettings = clonedeep(globalSettings); 93 | 94 | imageSettings.runBefore(image, document, url); 95 | 96 | // Overhide settings with presets named in the image classes 97 | if (image.dataset && 'responsiver' in image.dataset) { 98 | // TODO: Merging preset settings to previous settings should be easier 99 | image.dataset.responsiver.split(' ').forEach((preset) => { 100 | if (options[preset] !== undefined) { 101 | if ('selector' in options[preset]) { 102 | error( 103 | `The 'selector' property can't be used in the '${preset}' preset. It can be used only in the 'default' preset` 104 | ); 105 | delete options[preset].selector; 106 | } 107 | let presetClasses = options[preset].classes || []; 108 | let existingClasses = imageSettings.classes; 109 | imageSettings = deepmerge(imageSettings, options[preset], { 110 | arrayMerge: overwriteMerge, 111 | }); 112 | imageSettings.classes = [...existingClasses, ...presetClasses]; 113 | } 114 | }); 115 | delete image.dataset.responsiver; 116 | } 117 | 118 | let isData = false; 119 | if (!image.hasAttribute('src')) { 120 | isData = true; 121 | } 122 | 123 | const imageSrc = isData ? image.dataset.src : image.getAttribute('src'); 124 | info(`Transforming ${imageSrc}`); 125 | 126 | const imageWidth = image.getAttribute('width'); 127 | if (imageWidth === null) { 128 | warning(`The image should have a width attribute: ${imageSrc}`); 129 | } 130 | 131 | let srcsetList = []; 132 | if ( 133 | imageSettings.widthsList !== undefined && 134 | imageSettings.widthsList.length > 0 135 | ) { 136 | // Priority to the list of image widths for srcset 137 | // Make sure there are no duplicates, and sort in ascending order 138 | imageSettings.widthsList = [...new Set(imageSettings.widthsList)].sort( 139 | (a, b) => a - b 140 | ); 141 | const widthsListLength = imageSettings.widthsList.length; 142 | if (imageWidth !== null) { 143 | // Filter out widths superiors to the image's width 144 | imageSettings.widthsList = imageSettings.widthsList.filter( 145 | (width) => width <= imageWidth 146 | ); 147 | if ( 148 | imageSettings.widthsList.length < widthsListLength && 149 | (imageSettings.widthsList.length === 0 || 150 | imageSettings.widthsList[imageSettings.widthsList.length - 1] !== 151 | imageWidth) 152 | ) { 153 | // At least one value was removed because superior to the image's width 154 | // Let's replace it/them with the image's width 155 | imageSettings.widthsList.push(imageWidth); 156 | } 157 | } 158 | // generate the srcset attribute 159 | srcsetList = imageSettings.widthsList.map( 160 | (width) => 161 | `${imageSettings.resizedImageUrl(imageSrc, width)} ${width}w` 162 | ); 163 | } else { 164 | // We don't have a list of widths for srcset, we have to compute them 165 | 166 | // Make sure there are at least 2 steps for minWidth and maxWidth 167 | if (imageSettings.steps < 2) { 168 | warning( 169 | `Steps should be >= 2: ${imageSettings.steps} step for ${imageSrc}` 170 | ); 171 | imageSettings.steps = 2; 172 | } 173 | 174 | // Make sure maxWidth > minWidth 175 | // (even if there would be no issue in `srcset` order) 176 | if (imageSettings.minWidth > imageSettings.maxWidth) { 177 | warning(`Combined options have minWidth > maxWidth for ${imageSrc}`); 178 | let tempMin = imageSettings.minWidth; 179 | imageSettings.minWidth = imageSettings.maxWidth; 180 | imageSettings.maxWidth = tempMin; 181 | } 182 | 183 | if (imageWidth !== null) { 184 | if (imageWidth < imageSettings.minWidth) { 185 | warning( 186 | `The image is smaller than minWidth: ${imageWidth} < ${imageSettings.minWidth}` 187 | ); 188 | imageSettings.minWidth = imageWidth; 189 | } 190 | if (imageWidth < imageSettings.fallbackWidth) { 191 | warning( 192 | `The image is smaller than fallbackWidth: ${imageWidth} < ${imageSettings.fallbackWidth}` 193 | ); 194 | imageSettings.fallbackWidth = imageWidth; 195 | } 196 | } 197 | // generate the srcset attribute 198 | let previousStepWidth = 0; 199 | for (let i = 0; i < imageSettings.steps; i++) { 200 | let stepWidth = Math.ceil( 201 | imageSettings.minWidth + 202 | ((imageSettings.maxWidth - imageSettings.minWidth) / 203 | (imageSettings.steps - 1)) * 204 | i 205 | ); 206 | if (imageWidth !== null && stepWidth >= imageWidth) { 207 | warning( 208 | `The image is smaller than maxWidth: ${imageWidth} < ${imageSettings.maxWidth}` 209 | ); 210 | srcsetList.push( 211 | `${imageSettings.resizedImageUrl( 212 | imageSrc, 213 | imageWidth 214 | )} ${imageWidth}w` 215 | ); 216 | break; 217 | } 218 | if (stepWidth === previousStepWidth) { 219 | // Don't set twice the same image width 220 | continue; 221 | } 222 | previousStepWidth = stepWidth; 223 | srcsetList.push( 224 | `${imageSettings.resizedImageUrl( 225 | imageSrc, 226 | stepWidth 227 | )} ${stepWidth}w` 228 | ); 229 | } 230 | } 231 | 232 | if (imageSettings.classes.length > 0) { 233 | image.classList.add(...imageSettings.classes); 234 | } 235 | 236 | // Change the image source 237 | image.setAttribute( 238 | isData ? 'data-src' : 'src', 239 | imageSettings.resizedImageUrl(imageSrc, imageSettings.fallbackWidth) 240 | ); 241 | 242 | image.setAttribute( 243 | isData ? 'data-srcset' : 'srcset', 244 | srcsetList.join(', ') 245 | ); 246 | 247 | // add sizes attribute 248 | if (!image.hasAttribute('sizes') || imageSettings.sizesOverride) { 249 | image.setAttribute('sizes', imageSettings.sizes); 250 | } 251 | 252 | // add 'data-pristine' attribute with URL of the pristine image 253 | image.dataset.pristine = imageSrc; 254 | 255 | // Add attributes from the preset 256 | if (Object.keys(imageSettings.attributes).length > 0) { 257 | for (const attribute in imageSettings.attributes) { 258 | if (imageSettings.attributes[attribute] !== null) { 259 | image.setAttribute(attribute, imageSettings.attributes[attribute]); 260 | } 261 | } 262 | } 263 | 264 | imageSettings.runAfter(image, document, url); 265 | }); 266 | 267 | return document.toString(); 268 | }; 269 | 270 | module.exports = imagesResponsiver; 271 | --------------------------------------------------------------------------------