├── .github ├── FUNDING.yml └── workflows │ ├── gempush.yml │ └── ruby.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── Gemfile ├── Gemfile.lock ├── LICENSE.md ├── README.md ├── Rakefile ├── jekyll-loading-lazy.gemspec ├── lib ├── jekyll-loading-lazy.rb └── jekyll-loading-lazy │ └── version.rb └── spec ├── fixtures └── unit │ ├── _config.yml │ ├── _docs │ ├── document-with-include.md │ └── document-with-liquid-tag.md │ ├── _includes │ └── include.html │ ├── _layouts │ └── default.html │ ├── _posts │ ├── 2018-05-22-post-with-multiple-markdown-images.md │ ├── 2020-04-16-post-with-iframe.md │ ├── 2020-04-16-post-with-img.md │ ├── 2020-04-16-post-with-loading-iframe.md │ └── 2020-04-16-post-with-loading-img.md │ ├── index.md │ └── nothing.html ├── jekyll-loading_spec.rb └── spec_helper.rb /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: gildesmarais 2 | -------------------------------------------------------------------------------- /.github/workflows/gempush.yml: -------------------------------------------------------------------------------- 1 | name: Ruby Gem 2 | 3 | on: 4 | release: 5 | types: 6 | - created 7 | 8 | jobs: 9 | build: 10 | name: Build + Publish 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up Ruby 2.5 17 | uses: actions/setup-ruby@v1 18 | with: 19 | version: 2.5.x 20 | 21 | - name: setup bundler, rubocop and rspec 22 | run: | 23 | gem install bundler 24 | bundle config path vendor/bundle 25 | bundle config --global frozen 1 26 | bundle install --jobs 4 --retry 3 27 | bundle exec rubocop --fail-fast 28 | bundle exec rake 29 | 30 | - name: Publish to RubyGems 31 | run: | 32 | mkdir -p $HOME/.gem 33 | touch $HOME/.gem/credentials 34 | chmod 0600 $HOME/.gem/credentials 35 | printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials 36 | gem build *.gemspec 37 | gem push *.gem 38 | env: 39 | GEM_HOST_API_KEY: ${{secrets.RUBYGEMS_AUTH_TOKEN}} 40 | -------------------------------------------------------------------------------- /.github/workflows/ruby.yml: -------------------------------------------------------------------------------- 1 | name: rubocop, rspec and build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v1 16 | 17 | - name: Set up Ruby 2.5 18 | uses: actions/setup-ruby@v1 19 | with: 20 | ruby-version: 2.5.x 21 | 22 | - name: Cache gems 23 | uses: actions/cache@preview 24 | with: 25 | path: vendor/bundle 26 | key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }} 27 | restore-keys: | 28 | ${{ runner.os }}-gem- 29 | 30 | - name: setup bundler 31 | run: | 32 | gem install bundler 33 | bundle config path vendor/bundle 34 | bundle config --global frozen 1 35 | - name: bundle install 36 | run: bundle install --jobs 4 --retry 3 37 | 38 | - name: run rubocop 39 | run: bundle exec rubocop --fail-fast 40 | 41 | - name: run rspec 42 | run: bundle exec rake 43 | 44 | - name: build 45 | run: bundle exec rake build 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /*.gem 2 | spec/fixtures/unit/.jekyll-cache 3 | pkg 4 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | --order random 4 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: rubocop-jekyll 2 | 3 | inherit_gem: 4 | rubocop-jekyll: .rubocop.yml 5 | 6 | AllCops: 7 | TargetRubyVersion: 2.5 8 | Exclude: 9 | - vendor/**/* 10 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gemspec 6 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | jekyll-loading-lazy (0.1.1) 5 | jekyll (>= 3.0, < 5.0) 6 | nokogiri (>= 1.10, < 2.0) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | addressable (2.8.0) 12 | public_suffix (>= 2.0.2, < 5.0) 13 | ast (2.4.0) 14 | colorator (1.1.0) 15 | concurrent-ruby (1.1.6) 16 | diff-lcs (1.3) 17 | em-websocket (0.5.1) 18 | eventmachine (>= 0.12.9) 19 | http_parser.rb (~> 0.6.0) 20 | eventmachine (1.2.7) 21 | ffi (1.12.2) 22 | forwardable-extended (2.6.0) 23 | http_parser.rb (0.6.0) 24 | i18n (1.8.2) 25 | concurrent-ruby (~> 1.0) 26 | jaro_winkler (1.5.4) 27 | jekyll (4.0.0) 28 | addressable (~> 2.4) 29 | colorator (~> 1.0) 30 | em-websocket (~> 0.5) 31 | i18n (>= 0.9.5, < 2) 32 | jekyll-sass-converter (~> 2.0) 33 | jekyll-watch (~> 2.0) 34 | kramdown (~> 2.1) 35 | kramdown-parser-gfm (~> 1.0) 36 | liquid (~> 4.0) 37 | mercenary (~> 0.3.3) 38 | pathutil (~> 0.9) 39 | rouge (~> 3.0) 40 | safe_yaml (~> 1.0) 41 | terminal-table (~> 1.8) 42 | jekyll-sass-converter (2.1.0) 43 | sassc (> 2.0.1, < 3.0) 44 | jekyll-watch (2.2.1) 45 | listen (~> 3.0) 46 | kramdown (2.3.1) 47 | rexml 48 | kramdown-parser-gfm (1.1.0) 49 | kramdown (~> 2.0) 50 | liquid (4.0.3) 51 | listen (3.2.1) 52 | rb-fsevent (~> 0.10, >= 0.10.3) 53 | rb-inotify (~> 0.9, >= 0.9.10) 54 | mercenary (0.3.6) 55 | mini_portile2 (2.6.1) 56 | nokogiri (1.12.5) 57 | mini_portile2 (~> 2.6.1) 58 | racc (~> 1.4) 59 | parallel (1.19.1) 60 | parser (2.7.1.1) 61 | ast (~> 2.4.0) 62 | pathutil (0.16.2) 63 | forwardable-extended (~> 2.6) 64 | public_suffix (4.0.6) 65 | racc (1.5.2) 66 | rainbow (3.0.0) 67 | rake (12.3.3) 68 | rb-fsevent (0.10.3) 69 | rb-inotify (0.10.1) 70 | ffi (~> 1.0) 71 | rexml (3.2.5) 72 | rouge (3.18.0) 73 | rspec (3.9.0) 74 | rspec-core (~> 3.9.0) 75 | rspec-expectations (~> 3.9.0) 76 | rspec-mocks (~> 3.9.0) 77 | rspec-core (3.9.1) 78 | rspec-support (~> 3.9.1) 79 | rspec-expectations (3.9.1) 80 | diff-lcs (>= 1.2.0, < 2.0) 81 | rspec-support (~> 3.9.0) 82 | rspec-mocks (3.9.1) 83 | diff-lcs (>= 1.2.0, < 2.0) 84 | rspec-support (~> 3.9.0) 85 | rspec-support (3.9.2) 86 | rubocop (0.80.1) 87 | jaro_winkler (~> 1.5.1) 88 | parallel (~> 1.10) 89 | parser (>= 2.7.0.1) 90 | rainbow (>= 2.2.2, < 4.0) 91 | rexml 92 | ruby-progressbar (~> 1.7) 93 | unicode-display_width (>= 1.4.0, < 1.7) 94 | rubocop-jekyll (0.11.0) 95 | rubocop (>= 0.68.0, < 0.81.0) 96 | rubocop-performance (~> 1.2) 97 | rubocop-performance (1.5.2) 98 | rubocop (>= 0.71.0) 99 | ruby-progressbar (1.10.1) 100 | safe_yaml (1.0.5) 101 | sassc (2.3.0) 102 | ffi (~> 1.9) 103 | terminal-table (1.8.0) 104 | unicode-display_width (~> 1.1, >= 1.1.1) 105 | unicode-display_width (1.6.1) 106 | 107 | PLATFORMS 108 | ruby 109 | 110 | DEPENDENCIES 111 | bundler (~> 2.0) 112 | jekyll-loading-lazy! 113 | rake (~> 12.0) 114 | rspec (~> 3.0) 115 | rubocop (< 1.0) 116 | rubocop-jekyll (< 1.0) 117 | 118 | BUNDLED WITH 119 | 2.1.4 120 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Gil Desmarais and approved contributors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jekyll-loading-lazy [![](http://img.shields.io/liberapay/goal/gildesmarais.svg?logo=liberapa)](https://liberapay.com/gildesmarais/donate) 2 | 3 | This plugin adds `loading="lazy"` to all `img` and `iframe` tags on 4 | your [Jekyll site](https://jekyllrb.com/). No configuration needed. 5 | If a `loading` attribute is already present nothing is changed. 6 | 7 | `loading="lazy"` causes images and iframes to load lazily without any JavaScript. 8 | [Browser support](https://caniuse.com/#feat=loading-lazy-attr) is growing. 9 | If a browser does not support the `loading` attribute, it will load the resource 10 | just like it would normally. 11 | 12 | ⭐ If you like it, star it or [sponsor it](https://liberapay.com/gildesmarais/donate). 💓 13 | 14 | ## Installation 15 | 16 | 1. Add the following to your site's `Gemfile`: 17 | 18 | ```ruby 19 | gem 'jekyll-loading-lazy' 20 | ``` 21 | 22 | 2. add the following to your site's `_config.yml`: 23 | 24 | ```yml 25 | plugins: 26 | - jekyll-loading-lazy 27 | ``` 28 | 29 | **Note**: if `jekyll --version` is less than `3.5` use: 30 | 31 | ```yml 32 | gems: 33 | - jekyll-loading-lazy 34 | ``` 35 | 36 | 3. In your terminal, execute: 37 | 38 | ```bash 39 | bundle 40 | ``` 41 | 42 | 4. (re)start your Jekyll server with: 43 | 44 | ```bash 45 | jekyll serve 46 | ``` 47 | 48 | ## Usage 49 | 50 | Install the plugin as described above. That's basically all there is. 51 | 52 | **💡 Tip:** Note that the `github-pages` gem runs in `safe` mode and only allows [a defined set of plugins](https://pages.github.com/versions/). To use this gem in GitHub Pages, you need to build your site locally or use a CI (e.g. [Github Workflow](https://help.github.com/en/actions/configuring-and-managing-workflows/configuring-a-workflow)) and deploy to your `gh-pages` branch. [Click here for more information.](https://jekyllrb.com/docs/continuous-integration/github-actions/) 53 | 54 | ### Prevent lazy loading 55 | 56 | In case you want to prevent loading some images/iframes lazily, add `loading="eager"` to their tags. This might be useful to prevent flickering of images during navigation (e.g. the site's logo). 57 | 58 | See the following examples to prevent lazy loading. 59 | 60 |
61 | Markdown example 62 | 63 | ```markdown 64 | ![an example](/image.jpg){:loading='eager'} 65 | ``` 66 | 67 | This example assumes you're using Kramdown (Jekyll is using it by default). 68 | 69 |
70 | 71 |
72 | HTML example 73 | 74 | ```html 75 | an example 76 | ``` 77 | 78 |
79 | 80 | ## Contributing 81 | 82 | 1. [Fork this repository](https://github.com/gildesmarais/jekyll-loading-lazy/fork) 83 | 2. Create your branch (`git checkout -b feat/my-new-feature`) 84 | 3. Commit your changes (`git commit -m 'Add cool feature'`) 85 | 4. Push to the branch (git push origin feat/my-new-feature) 86 | 5. Create a new Pull Request 87 | 88 | ### Testing 89 | 90 | ```bash 91 | rake 92 | ``` 93 | 94 | ## Credits 95 | 96 | Thanks to @keithmifsud's 97 | [`jekyll-target-blank`](https://github.com/keithmifsud/jekyll-target-blank) 98 | whereon this Jekyll plugin largely bases. 99 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/gem_tasks" 4 | require "rspec/core/rake_task" 5 | 6 | RSpec::Core::RakeTask.new(:spec) 7 | 8 | task :default => %w(spec) 9 | -------------------------------------------------------------------------------- /jekyll-loading-lazy.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path("lib", __dir__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require "jekyll-loading-lazy/version" 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = "jekyll-loading-lazy" 9 | spec.version = Jekyll::LoadingLazy::VERSION 10 | spec.authors = ["Gil Desmarais"] 11 | spec.email = %w(jekyll-loading-lazy@desmarais.de) 12 | spec.summary = 'Automatically adds loading="lazy" to and 7 | -------------------------------------------------------------------------------- /spec/fixtures/unit/_posts/2020-04-16-post-with-img.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Post with html tag 4 | --- 5 | 6 | 7 | -------------------------------------------------------------------------------- /spec/fixtures/unit/_posts/2020-04-16-post-with-loading-iframe.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Post with html 7 | -------------------------------------------------------------------------------- /spec/fixtures/unit/_posts/2020-04-16-post-with-loading-img.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Post with html tag having loading attribute 4 | --- 5 | 6 | 7 | -------------------------------------------------------------------------------- /spec/fixtures/unit/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Just a page 4 | --- 5 | 6 | ![This is an image](https://via.placeholder.com/150). 7 | -------------------------------------------------------------------------------- /spec/fixtures/unit/nothing.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Nothing 4 | --- 5 | 6 |

Nothing to do in here.

7 | -------------------------------------------------------------------------------- /spec/jekyll-loading_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe(Jekyll::LoadingLazy) do 4 | Jekyll.logger.log_level = :error 5 | 6 | let(:site) do 7 | Jekyll::Site.new(Jekyll.configuration( 8 | "skip_config_files" => false, 9 | "source" => unit_fixtures_dir, 10 | "destination" => unit_fixtures_dir("_site") 11 | )) 12 | end 13 | 14 | let(:posts) { site.posts.docs.sort.reverse } 15 | 16 | let(:post_with_multiple_markdown_images) do 17 | find_by_title(posts, "Post with multiple markdown images") 18 | end 19 | 20 | let(:post_with_img) { find_by_title(posts, "Post with html tag") } 21 | 22 | let(:post_with_loading_img) do 23 | find_by_title(posts, "Post with html tag having loading attribute") 24 | end 25 | 26 | let(:post_with_iframe) { find_by_title(posts, "Post with html 83 | HTML 84 | end 85 | end 86 | context "with img with liquid tags" do 87 | it "adds loading attribute" do 88 | expect(document_with_liquid_tag.output).to include(<<~HTML) 89 |

This is an image with a liquid tag.

90 | HTML 91 | end 92 | end 93 | 94 | context "with img within includes" do 95 | it "adds loading attribute" do 96 | expect(document_with_include.output).to include(<<~HTML) 97 |

This is a document with an include: This is an include. It has an image.

98 | HTML 99 | end 100 | end 101 | end 102 | 103 | context "with loading attribute present" do 104 | context "with img" do 105 | it "does not set loading=lazy" do 106 | expect(post_with_loading_img.output).to include(<<~HTML) 107 |

108 | HTML 109 | end 110 | end 111 | 112 | context "with iframe" do 113 | it "does not set loading=lazy" do 114 | expect(post_with_loading_iframe.output).to include(<<~HTML) 115 | 116 | HTML 117 | end 118 | end 119 | end 120 | 121 | context "without any img/iframe" do 122 | it "does not change the markup" do 123 | expect(page_nothing).to include(<<~HTML) 124 |

Nothing to do in here.

125 | HTML 126 | end 127 | end 128 | end 129 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "jekyll-loading-lazy" 4 | 5 | RSpec.configure do |_config| 6 | UNIT_FIXTURES_DIR = File.expand_path("fixtures/unit", __dir__).freeze 7 | 8 | def unit_fixtures_dir(*paths) 9 | File.join(UNIT_FIXTURES_DIR, *paths) 10 | end 11 | 12 | def find_by_title(docs, title) 13 | docs.find { |d| d.data["title"] == title } 14 | end 15 | end 16 | --------------------------------------------------------------------------------