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 +4 -4
- data/README.md +15 -7
- data/lib/jekyll-imgflow/batch_manager.rb +2 -2
- data/lib/jekyll-imgflow/config.rb +28 -15
- data/lib/jekyll-imgflow/html_generator.rb +35 -8
- data/lib/jekyll-imgflow/imgflow_tag.rb +1 -1
- data/lib/jekyll-imgflow/manifest_manager.rb +2 -7
- data/lib/jekyll-imgflow/operation_processor.rb +38 -4
- data/lib/jekyll-imgflow/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 07f3c2dfe3416810827699ab3b82a29012283ad308ad215f034f1bb86d8d8a79
|
|
4
|
+
data.tar.gz: 6ee5f237e22490069f7b3dc6ef0d8ba63a2ad8ecd1fe8a90754034a03192b11f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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
|
|
217
|
-
output_path = path_resolver.
|
|
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
|
|
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&.
|
|
64
|
-
|
|
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
|
|
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
|
-
#
|
|
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].
|
|
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.
|
|
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 =
|
|
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
|
|
48
|
-
actual_output_path = @path_resolver.
|
|
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
|
-
#
|
|
72
|
-
relative_path =
|
|
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
|