jekyll-imgflow 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4a8e1eb8739d53b1ebcdbc440d4c77279ce895794ffb1beda8de9df159227d91
4
- data.tar.gz: 730145b3e3017a1a63281f7e68c518cdc82cce708e55d942cdabdba002900d7c
3
+ metadata.gz: 07f3c2dfe3416810827699ab3b82a29012283ad308ad215f034f1bb86d8d8a79
4
+ data.tar.gz: 6ee5f237e22490069f7b3dc6ef0d8ba63a2ad8ecd1fe8a90754034a03192b11f
5
5
  SHA512:
6
- metadata.gz: 23a45cd5749d4c389b65ffdbd54ca3240465441a325861d739acfedcaf342bd3e111eb04165b925d790f8b862a79d40c9bcbe85979181b184ea7bc3253065e00
7
- data.tar.gz: 9b71032b277287766586518ef618cde9aa0d787f2ed3e7b061b609685f68221a46a7d5a09b53e8a4907512bf11611028a9b8add638f37477b08b607e9f38aa89
6
+ metadata.gz: e8da1e7ca9f599f6e1dcfbe5b2a44277ec53edd42b57e34159af1090e0fafcf068b361ac0713ba8932e4acb213f3569a268ffc1471c43c21ffd5845734769315
7
+ data.tar.gz: 4c7014c3888b5d4a2c94022f89eab3a383edf315fe34b556f3b727d7483f883b0b40ac772ab05fd51d37ab5d900122385d0887052bc6508085a2513dce339d0d
data/README.md CHANGED
@@ -16,17 +16,26 @@ gem 'jekyll-imgflow'
16
16
  # Install
17
17
  bundle install
18
18
 
19
- # Configure (_config.yml)
19
+ # Configure (_config.yml) — only originals and output are required,
20
+ # everything else has sensible defaults:
20
21
  imgflow:
21
22
  originals: "assets/images/originals"
22
23
  output: "assets/images/optimized"
23
- sizes: {sm: 400, md: 800, lg: 1200}
24
- formats: [webp, avif, jpg]
25
24
 
26
25
  # Use in templates
27
26
  {% imgflow photo.jpg resize width:800 %}
28
27
  ```
29
28
 
29
+ > **Full configuration options:** `quality`, `backend_priority`, `formats`,
30
+ > `fallback_format`, `sizes`, provider URLs, and more — see the
31
+ > [Installation Guide](docs/installation.md#4-add-to-_configyml)
32
+ > and [Configuration Reference](docs/ARCHITECTURE.md).
33
+
34
+ > **Note:** Image references require exact filenames (or paths relative to
35
+ > `originals`). There is no fuzzy matching or autocomplete yet — if you type
36
+ > `photo.jpg` but the file is `photo.jpeg`, the build will fail with an error.
37
+ > Future versions may add "did you mean" suggestions.
38
+
30
39
  ## ✨ Key Features
31
40
 
32
41
  - **Multiple Providers**: [Sharp](https://github.com/lovell/sharp), [ImageMagick](https://github.com/ImageMagick/ImageMagick), [LibVips](https://github.com/libvips/libvips), [Imgproxy](https://github.com/imgproxy/imgproxy), [Weserv](https://github.com/weserv/images), [Flyimg](https://github.com/flyimg/flyimg)
@@ -112,18 +121,17 @@ gem 'jekyll-imgflow'
112
121
  # Install
113
122
  bundle install
114
123
 
115
- # Configure (_config.yml)
124
+ # Configure (_config.yml) — only originals and output required:
116
125
  imgflow:
117
126
  originals: "assets/images/originals"
118
127
  output: "assets/images/optimized"
119
- sizes: {sm: 400, md: 800, lg: 1200}
120
- formats: [webp, avif, jpg]
121
128
 
122
129
  # Use in templates
123
130
  {% imgflow photo.jpg resize width:800 %}
124
131
  ```
125
132
 
126
- **See:** [installation.md](docs/installation.md) for detailed installation instructions and configuration options
133
+ **See:** [installation.md](docs/installation.md) for detailed installation and
134
+ [all configuration options](docs/installation.md#4-add-to-_configyml)
127
135
 
128
136
  ## 🐳 Docker Setup (Recommended)
129
137
 
@@ -213,8 +213,8 @@ module JekyllImgFlow
213
213
  # Use FilenameGenerator to generate proper filename
214
214
  operations = { width: width, format: format, quality: config.quality }
215
215
  filename = filename_generator.generate_filename(original_name, operations)
216
- # Write to _site during build (after Jekyll copies assets)
217
- output_path = path_resolver.resolve_output_path(filename)
216
+ # Write to source directory so Jekyll copies files to _site during write phase
217
+ output_path = path_resolver.resolve_source_output_path(filename)
218
218
 
219
219
  tasks << {
220
220
  original_name: original_name,
@@ -2,10 +2,29 @@
2
2
 
3
3
  module JekyllImgFlow
4
4
  class Config
5
+ # Sensible defaults — users only need to configure `originals` and `output`
6
+ # (or even nothing at all). All other values fall back to these.
7
+ DEFAULT_ORIGINALS = "assets/images/originals"
8
+ DEFAULT_OUTPUT = "assets/images/optimized"
9
+ DEFAULT_INPUT_FORMATS = %w[jpg jpeg png webp avif gif tiff tif svg].freeze
10
+ # Output format priority: avif > webp > png > jpg (fallback).
11
+ # The browser picks the first <source> format it supports, so avif is
12
+ # served to modern browsers, webp as a fallback for avif, png for
13
+ # transparency, and jpg as the universal <img> fallback.
14
+ DEFAULT_FORMATS = %w[avif webp png jpg].freeze
15
+ DEFAULT_SIZES = { "sm" => 400, "md" => 800, "lg" => 1200, "xl" => 2000 }.freeze
16
+ DEFAULT_QUALITY = 85
17
+ # Ordered by speed (see docs/providers.md benchmark):
18
+ # sharp (14s) → libvips (22s) → imagemagick (31s) → imgproxy (31s) → weserv (30s) → flyimg
19
+ DEFAULT_BACKEND_PRIORITY = %w[sharp libvips imagemagick imgproxy weserv flyimg].freeze
20
+ # Format used for the <img> fallback in <picture> elements.
21
+ # All other formats in `formats` get <source> tags for browsers that support them.
22
+ DEFAULT_FALLBACK_FORMAT = "jpg"
23
+
5
24
  attr_reader :site, :originals, :output, :input_formats, :sizes, :formats,
6
25
  :quality, :backend_priority, :imgproxy_url,
7
26
  :image_compressor_url, :weserv_url, :flyimg_url,
8
- :optimize_qualities
27
+ :optimize_qualities, :fallback_format
9
28
 
10
29
  def initialize(site)
11
30
  shared = site.config["shared_images_configs"] || {}
@@ -13,20 +32,14 @@ module JekyllImgFlow
13
32
  cfg = shared.merge(overrides)
14
33
 
15
34
  @site = site
16
- @originals = cfg["originals"]
17
- @output = cfg["output"]
18
- @input_formats = cfg["input_formats"]
19
- @sizes = cfg["sizes"]
20
- @formats = cfg["formats"]
21
- @quality = cfg["quality"]
22
- @backend_priority = cfg["backend_priority"]
23
-
24
- # Validate required config fields
25
- raise ArgumentError, "No originals configured in unified config" unless @originals
26
- raise ArgumentError, "No output configured in unified config" unless @output
27
- raise ArgumentError, "No input_formats configured in unified config" unless @input_formats
28
- raise ArgumentError, "No formats configured in unified config" unless @formats
29
- raise ArgumentError, "No sizes configured in unified config" unless @sizes
35
+ @originals = cfg["originals"] || DEFAULT_ORIGINALS
36
+ @output = cfg["output"] || DEFAULT_OUTPUT
37
+ @input_formats = cfg["input_formats"] || DEFAULT_INPUT_FORMATS
38
+ @sizes = cfg["sizes"] || DEFAULT_SIZES
39
+ @formats = cfg["formats"] || DEFAULT_FORMATS
40
+ @quality = cfg["quality"] || DEFAULT_QUALITY
41
+ @backend_priority = cfg["backend_priority"] || DEFAULT_BACKEND_PRIORITY
42
+ @fallback_format = cfg["fallback_format"] || DEFAULT_FALLBACK_FORMAT
30
43
 
31
44
  @imgproxy_url = cfg["imgproxy_url"]
32
45
  @image_compressor_url = cfg["image_compressor_url"]
@@ -58,13 +58,13 @@ module JekyllImgFlow
58
58
 
59
59
  private
60
60
 
61
- # Get fallback formats from config or use sensible defaults
61
+ # Get fallback formats only the configured fallback format (e.g. jpg).
62
+ # These are skipped in the <source> loop and used for the <img> fallback.
63
+ # All other formats (webp, avif, etc.) get <source> tags.
62
64
  def fallback_formats
63
- if @config&.formats
64
- # Convert format names to extensions and add dots
65
- @config.formats.map { |fmt| ".#{fmt}" }
65
+ if @config&.fallback_format
66
+ [".#{@config.fallback_format}"]
66
67
  else
67
- # Fallback to common formats if no config available
68
68
  %w[.jpg .jpeg .png]
69
69
  end
70
70
  end
@@ -242,13 +242,40 @@ module JekyllImgFlow
242
242
  "<div#{parent_attrs}>#{html}</div>"
243
243
  end
244
244
 
245
- # Group results by file extension/format
245
+ # Group results by file extension, ordered by config format priority
246
+ # (avif → webp → png → jpg). The browser picks the first <source> format
247
+ # it supports, so the order of <source> tags determines format priority.
246
248
  def group_by_format(results)
247
- results.group_by { |r| File.extname(r).downcase }
249
+ grouped = results.group_by { |r| File.extname(r).downcase }
250
+ sort_format_groups(grouped)
248
251
  end
249
252
 
250
- # Find fallback image (prefer jpg/jpeg/png)
253
+ # Sort format groups by config format priority (first = highest priority).
254
+ # Formats not in config.formats are appended at the end alphabetically.
255
+ def sort_format_groups(grouped)
256
+ priority = build_format_priority
257
+ grouped.sort_by { |ext, _| priority.index(ext) || Float::INFINITY }
258
+ end
259
+
260
+ # Build a lookup of format extensions ordered by config.formats priority.
261
+ # E.g. ["avif", "webp", "png", "jpg"] → [".avif", ".webp", ".png", ".jpg"]
262
+ def build_format_priority
263
+ return %w[.avif .webp .png .jpg] unless @config&.formats
264
+
265
+ @config.formats.map { |fmt| ".#{fmt.downcase}" }
266
+ end
267
+
268
+ # Find fallback image — prefer the configured fallback format,
269
+ # then any legacy format (jpg/jpeg/png), then first result.
251
270
  def find_fallback_image(results)
271
+ return results.first if results.empty?
272
+
273
+ preferred = @config&.fallback_format
274
+ if preferred
275
+ found = results.find { |r| r.end_with?(".#{preferred}") }
276
+ return found if found
277
+ end
278
+
252
279
  results.find { |r| r.match?(/\.(jpe?g|png)$/i) } || results.first
253
280
  end
254
281
 
@@ -97,7 +97,7 @@ module Jekyll
97
97
  end
98
98
 
99
99
  filename = components[:filename_generator].generate_filename(input_path, params)
100
- output_path = components[:path_resolver].resolve_output_path(filename)
100
+ output_path = components[:path_resolver].resolve_source_output_path(filename)
101
101
 
102
102
  # Determine version type
103
103
  version_type = determine_version_type(params, components[:config])
@@ -114,7 +114,7 @@ module JekyllImgFlow
114
114
 
115
115
  # Get optimized directory path
116
116
  config = JekyllImgFlow::Config.new(@site)
117
- optimized_dir = File.join(@site.dest, config.output)
117
+ optimized_dir = File.join(@site.source, config.output)
118
118
 
119
119
  # Safety guard: prevent deletion of _site itself
120
120
  raise "Refusing to delete optimized directory: path resolves to site dest or is empty" if optimized_dir == @site.dest || optimized_dir.empty?
@@ -261,12 +261,7 @@ module JekyllImgFlow
261
261
  cleaned = []
262
262
 
263
263
  orphans.each do |orphan|
264
- output_file = if orphan["output"].start_with?("/")
265
- orphan["output"]
266
- else
267
- File.join(@site.dest,
268
- orphan["output"])
269
- end
264
+ output_file = File.join(@site.source, orphan["output"])
270
265
  if File.exist?(output_file)
271
266
  File.delete(output_file)
272
267
  cleaned << orphan["output"]
@@ -44,8 +44,8 @@ module JekyllImgFlow
44
44
 
45
45
  # Generate filename using FilenameGenerator (JPT compatible)
46
46
  filename = @filename_generator.generate_filename(input_path, params)
47
- # Write to _site during build (after Jekyll copies assets)
48
- actual_output_path = @path_resolver.resolve_output_path(filename)
47
+ # Write to source directory so Jekyll copies files to _site during write phase
48
+ actual_output_path = @path_resolver.resolve_source_output_path(filename)
49
49
 
50
50
  # Determine version type
51
51
  version_type = determine_version_type(params)
@@ -68,8 +68,8 @@ module JekyllImgFlow
68
68
 
69
69
  # Register in manifest
70
70
  if @manifest
71
- # Convert absolute path to relative for manifest storage
72
- relative_path = actual_output_path.sub(@path_resolver.site_dest, "")
71
+ # Store relative path (with leading /) for manifest storage
72
+ relative_path = "/#{@path_resolver.resolve_relative_output_path(filename)}"
73
73
 
74
74
  provider_name = @provider.class.provider_name
75
75
  @manifest.register_version(
@@ -83,9 +83,43 @@ module JekyllImgFlow
83
83
  )
84
84
  end
85
85
 
86
+ # Register as Jekyll static file so Jekyll copies it to _site during
87
+ # the write phase. Without this, files created during pre_render (or
88
+ # during render via imgflow tags) are not picked up by Jekyll's static
89
+ # file reader, which runs before pre_render. This would leave _site
90
+ # without optimized images until a second build.
91
+ register_jekyll_static_file(actual_output_path)
92
+
86
93
  actual_output_path
87
94
  end
88
95
 
96
+ # Register a generated file as a Jekyll::StaticFile so Jekyll copies it
97
+ # to _site during the write phase. No-op for mock/test sites.
98
+ # @param file_path [String] Absolute path to the generated file in source
99
+ def register_jekyll_static_file(file_path)
100
+ site = @config&.site
101
+ return unless site.is_a?(Jekyll::Site)
102
+
103
+ relative = file_path.delete_prefix("#{site.source}/")
104
+ return if relative == file_path # not under site source
105
+
106
+ add_static_file(site, relative) unless static_file_registered?(site, relative)
107
+ end
108
+
109
+ # @param site [Jekyll::Site] Jekyll site object
110
+ # @param relative [String] Path relative to site source
111
+ def static_file_registered?(site, relative)
112
+ site.static_files.any? { |sf| sf.relative_path == "/#{relative}" }
113
+ end
114
+
115
+ # @param site [Jekyll::Site] Jekyll site object
116
+ # @param relative [String] Path relative to site source
117
+ def add_static_file(site, relative)
118
+ site.static_files << Jekyll::StaticFile.new(
119
+ site, site.source, File.dirname(relative), File.basename(relative)
120
+ )
121
+ end
122
+
89
123
  # Process multiple operations on an image in sequence (batch)
90
124
  # @param operations [Array<Hash>] Array of operations to process
91
125
  # @param input_path [String] Path to input image
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JekyllImgFlow
4
- VERSION = "0.1.6"
4
+ VERSION = "0.1.7"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-imgflow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Svend Gundestrup