@apleasantview/eleventy-plugin-baseline 0.1.0-next.32 → 0.1.0-next.33
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.
- package/core/debug.js +18 -3
- package/core/filters/isString.js +5 -0
- package/core/filters/markdown.js +6 -0
- package/core/filters/related-posts.js +6 -0
- package/core/helpers.js +6 -97
- package/core/logging.js +2 -2
- package/core/modules.js +0 -4
- package/core/shortcodes/image.js +162 -144
- package/eleventy.config.js +34 -22
- package/modules/assets-core/plugins/assets-core.js +126 -13
- package/modules/assets-esbuild/{filters/inline-esbuild.js → process.js} +10 -1
- package/modules/assets-postcss/process.js +49 -0
- package/modules/head-core/drivers/posthtml-head-elements.js +13 -18
- package/modules/head-core/plugins/head-core.js +19 -1
- package/modules/head-core/utils/head-utils.js +76 -10
- package/modules/multilang-core/plugins/multilang-core.js +26 -9
- package/modules/navigator-core/plugins/navigator-core.js +19 -1
- package/modules/navigator-core/templates/navigator-core.html +4 -4
- package/modules/sitemap-core/plugins/sitemap-core.js +23 -0
- package/modules/sitemap-core/templates/sitemap-core.html +27 -18
- package/modules/sitemap-core/templates/sitemap-index.html +1 -1
- package/package.json +1 -1
- package/modules/assets-esbuild/plugins/assets-esbuild.js +0 -71
- package/modules/assets-postcss/filters/inline-postcss.js +0 -38
- package/modules/assets-postcss/plugins/assets-postcss.js +0 -75
package/core/debug.js
CHANGED
|
@@ -1,18 +1,33 @@
|
|
|
1
1
|
import { inspect as utilInspect } from 'node:util';
|
|
2
2
|
|
|
3
3
|
// Adapted from pdehaan - https://github.com/pdehaan/eleventy-plugin-debug
|
|
4
|
-
const debugOptions =
|
|
5
|
-
space: 0
|
|
6
|
-
});
|
|
4
|
+
const debugOptions = { space: 0 };
|
|
7
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Pretty-print an object using Node's util.inspect.
|
|
8
|
+
* @param {*} obj - Value to inspect.
|
|
9
|
+
* @param {Object} [options={}] - Options forwarded to util.inspect.
|
|
10
|
+
* @returns {string}
|
|
11
|
+
*/
|
|
8
12
|
function inspect(obj, options = {}) {
|
|
9
13
|
return utilInspect(obj, options);
|
|
10
14
|
}
|
|
11
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Serialize an object to JSON.
|
|
18
|
+
* @param {*} obj - Value to serialize.
|
|
19
|
+
* @param {number} [space] - Indentation level (default 0, compact).
|
|
20
|
+
* @returns {string}
|
|
21
|
+
*/
|
|
12
22
|
function json(obj, space = debugOptions.space) {
|
|
13
23
|
return JSON.stringify(obj, null, space);
|
|
14
24
|
}
|
|
15
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Return an object's own keys, sorted alphabetically.
|
|
28
|
+
* @param {Object} obj
|
|
29
|
+
* @returns {string[]}
|
|
30
|
+
*/
|
|
16
31
|
function keys(obj) {
|
|
17
32
|
return Object.keys(obj).sort();
|
|
18
33
|
}
|
package/core/filters/isString.js
CHANGED
package/core/filters/markdown.js
CHANGED
|
@@ -3,6 +3,12 @@ import markdownit from 'markdown-it';
|
|
|
3
3
|
|
|
4
4
|
const md = markdownit();
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Render a string as inline Markdown (no wrapping <p> tag).
|
|
8
|
+
* @param {string} string - Markdown source.
|
|
9
|
+
* @returns {string} HTML output.
|
|
10
|
+
*/
|
|
6
11
|
export const markdownFilter = (string) => {
|
|
12
|
+
if (!string) return '';
|
|
7
13
|
return md.renderInline(string);
|
|
8
14
|
};
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filter the current page out of a collection.
|
|
3
|
+
* Uses `this.ctx.page` from the Nunjucks runtime to identify the current page.
|
|
4
|
+
* @param {Array<Object>} [collection=[]] - Collection to filter.
|
|
5
|
+
* @returns {Array<Object>} Collection without the current page.
|
|
6
|
+
*/
|
|
1
7
|
export default function relatedPostsFilter(collection = []) {
|
|
2
8
|
const page = this?.ctx?.page;
|
|
3
9
|
if (!page?.url) return collection;
|
package/core/helpers.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
1
|
import { TemplatePath } from '@11ty/eleventy-utils';
|
|
3
2
|
|
|
4
3
|
/**
|
|
@@ -14,11 +13,12 @@ export function addTrailingSlash(path) {
|
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
/**
|
|
17
|
-
* Resolves the assets directory
|
|
18
|
-
*
|
|
19
|
-
* @param {string} inputDir - The input directory (e.g., "./src/")
|
|
20
|
-
* @param {string}
|
|
21
|
-
* @
|
|
16
|
+
* Resolves the assets directory paths from config.
|
|
17
|
+
* Joins inputDir/outputDir with rawDir, normalizes, and adds trailing slashes.
|
|
18
|
+
* @param {string} inputDir - The input directory (e.g., "./src/").
|
|
19
|
+
* @param {string} outputDir - The output directory (e.g., "./dist/").
|
|
20
|
+
* @param {string} rawDir - Raw directory value from config (e.g., "assets").
|
|
21
|
+
* @returns {{assetsDir: string, assetsOutputDir: string}}
|
|
22
22
|
*/
|
|
23
23
|
export function resolveAssetsDir(inputDir, outputDir, rawDir) {
|
|
24
24
|
// Join input/output with assets subdir and normalize
|
|
@@ -34,94 +34,3 @@ export function resolveAssetsDir(inputDir, outputDir, rawDir) {
|
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
/**
|
|
38
|
-
* Builds glob patterns for fast-glob (absolute paths)
|
|
39
|
-
* @param {string[]} patterns - User-provided patterns
|
|
40
|
-
* @param {string} assetsDir - Assets directory (relative, e.g., "./src/assets/")
|
|
41
|
-
* @returns {string[]} Absolute glob patterns
|
|
42
|
-
*/
|
|
43
|
-
export function buildGlobPatterns(patterns, assetsDir) {
|
|
44
|
-
const assetsDirAbsolute = TemplatePath.absolutePath(TemplatePath.stripLeadingDotSlash(assetsDir));
|
|
45
|
-
|
|
46
|
-
return patterns.map((pattern) => {
|
|
47
|
-
const normalized = TemplatePath.standardizeFilePath(pattern);
|
|
48
|
-
return normalized.startsWith('/') || path.isAbsolute(normalized)
|
|
49
|
-
? normalized
|
|
50
|
-
: TemplatePath.join(assetsDirAbsolute, normalized);
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Extracts file metadata from a file path
|
|
56
|
-
* @param {string} filePath - Normalized file path
|
|
57
|
-
* @returns {{basename: string, fileSlug: string, inputFileExtension: string}}
|
|
58
|
-
*/
|
|
59
|
-
export function extractFileMetadata(filePath) {
|
|
60
|
-
const ext = path.extname(filePath); // Returns extension with dot (e.g., ".css") or ""
|
|
61
|
-
const inputFileExtension = ext && ext.length > 0 ? ext.slice(1) : '';
|
|
62
|
-
const basename = TemplatePath.getLastPathSegment(filePath, false);
|
|
63
|
-
const fileSlug = ext ? basename.slice(0, -ext.length) : basename;
|
|
64
|
-
|
|
65
|
-
return { basename, fileSlug, inputFileExtension };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Creates a collection item from a relative file path
|
|
70
|
-
* @param {string} inputPath - Relative path from project root (e.g., "./src/assets/css/index.css")
|
|
71
|
-
* @param {string} inputDir - Input directory (e.g., "./src/")
|
|
72
|
-
* @param {string} outputDir - Output directory (e.g., "./dist/")
|
|
73
|
-
* @param {string} assetsDirRelative - Assets directory relative to input (e.g., "assets")
|
|
74
|
-
* @param {string} passthroughOutput - Output path for passthrough
|
|
75
|
-
* @param {boolean} passthrough - Whether passthrough is enabled
|
|
76
|
-
* @returns {object} Collection item
|
|
77
|
-
*/
|
|
78
|
-
export function createCollectionItem(
|
|
79
|
-
inputPath,
|
|
80
|
-
inputDir,
|
|
81
|
-
outputDir,
|
|
82
|
-
assetsDirRelative,
|
|
83
|
-
passthroughOutput,
|
|
84
|
-
passthrough
|
|
85
|
-
) {
|
|
86
|
-
const { basename, fileSlug, inputFileExtension } = extractFileMetadata(inputPath);
|
|
87
|
-
|
|
88
|
-
// Get path relative to input directory
|
|
89
|
-
// e.g., inputPath = "./src/assets/css/index.css", inputDir = "./src/"
|
|
90
|
-
// relToInput = "assets/css/index.css"
|
|
91
|
-
const relToInput = TemplatePath.stripLeadingSubPath(inputPath, TemplatePath.addLeadingDotSlash(inputDir));
|
|
92
|
-
|
|
93
|
-
// outputPath: prepend output directory (with leading ./)
|
|
94
|
-
// e.g., relToInput = "assets/css/index.css", outputDir = "./dist/"
|
|
95
|
-
// outputPath = "./dist/assets/css/index.css"
|
|
96
|
-
const outputPath = TemplatePath.addLeadingDotSlash(
|
|
97
|
-
TemplatePath.normalize(TemplatePath.join(TemplatePath.addLeadingDotSlash(outputDir), relToInput))
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
// relToAssets: path relative to assets directory for URL generation
|
|
101
|
-
// e.g., inputPath = "./src/assets/css/index.css", assetsDirRelative = "assets"
|
|
102
|
-
// relToAssets = "css/index.css"
|
|
103
|
-
const assetsDirPath = TemplatePath.addLeadingDotSlash(TemplatePath.join(inputDir, assetsDirRelative));
|
|
104
|
-
const relToAssets = TemplatePath.stripLeadingSubPath(inputPath, assetsDirPath);
|
|
105
|
-
|
|
106
|
-
const url = passthrough ? TemplatePath.join(passthroughOutput, relToAssets).replace(/\/$/, '') : undefined;
|
|
107
|
-
|
|
108
|
-
// filePathStem: path relative to input without extension, with leading slash
|
|
109
|
-
// e.g., relToInput = "assets/css/index.css"
|
|
110
|
-
// filePathStem = "/assets/css/index"
|
|
111
|
-
const filePathStem =
|
|
112
|
-
'/' +
|
|
113
|
-
(inputFileExtension
|
|
114
|
-
? relToInput.slice(0, -inputFileExtension.length - 1) // Remove extension and dot
|
|
115
|
-
: relToInput);
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
inputPath,
|
|
119
|
-
outputPath,
|
|
120
|
-
basename,
|
|
121
|
-
fileSlug,
|
|
122
|
-
inputFileExtension,
|
|
123
|
-
filePathStem,
|
|
124
|
-
dir: TemplatePath.getDirFromFilePath(inputPath),
|
|
125
|
-
url
|
|
126
|
-
};
|
|
127
|
-
}
|
package/core/logging.js
CHANGED
|
@@ -25,8 +25,8 @@ export function logIfVerbose(verbose, message, ...args) {
|
|
|
25
25
|
* @param {boolean} verbose - Whether verbose logging is enabled
|
|
26
26
|
* @param {string} message - Warning message
|
|
27
27
|
*/
|
|
28
|
-
export function warnIfVerbose(verbose, message) {
|
|
28
|
+
export function warnIfVerbose(verbose, message, ...args) {
|
|
29
29
|
if (verbose) {
|
|
30
|
-
console.warn(`[eleventy-plugin-baseline] WARN ${message}
|
|
30
|
+
console.warn(`[eleventy-plugin-baseline] WARN ${message}`, ...args);
|
|
31
31
|
}
|
|
32
32
|
}
|
package/core/modules.js
CHANGED
|
@@ -5,8 +5,6 @@ import { EleventyHtmlBasePlugin } from '@11ty/eleventy';
|
|
|
5
5
|
import multilangCore from '../modules/multilang-core/plugins/multilang-core.js';
|
|
6
6
|
import navigatorCore from '../modules/navigator-core/plugins/navigator-core.js';
|
|
7
7
|
import assetsCore from '../modules/assets-core/plugins/assets-core.js';
|
|
8
|
-
import assetsPostCSS from '../modules/assets-postcss/plugins/assets-postcss.js';
|
|
9
|
-
import assetsESBuild from '../modules/assets-esbuild/plugins/assets-esbuild.js';
|
|
10
8
|
import headCore from '../modules/head-core/plugins/head-core.js';
|
|
11
9
|
import sitemapCore from '../modules/sitemap-core/plugins/sitemap-core.js';
|
|
12
10
|
|
|
@@ -15,8 +13,6 @@ export default {
|
|
|
15
13
|
multilangCore,
|
|
16
14
|
navigatorCore,
|
|
17
15
|
assetsCore,
|
|
18
|
-
assetsPostCSS,
|
|
19
|
-
assetsESBuild,
|
|
20
16
|
headCore,
|
|
21
17
|
sitemapCore
|
|
22
18
|
};
|
package/core/shortcodes/image.js
CHANGED
|
@@ -1,144 +1,162 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import Image from '@11ty/eleventy-img';
|
|
3
|
-
|
|
4
|
-
const DEFAULT_WIDTHS = [320, 640, 960, 1280, 1920, 'auto'];
|
|
5
|
-
const DEFAULT_FORMATS = ['avif', 'webp'];
|
|
6
|
-
const DEFAULT_SIZES = '(max-width: 768px) 100vw, 768px';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* @param {
|
|
25
|
-
* @param {string}
|
|
26
|
-
* @param {
|
|
27
|
-
* @param {string} [options.
|
|
28
|
-
* @param {
|
|
29
|
-
* @param {string} [options.
|
|
30
|
-
* @param {string} [options.
|
|
31
|
-
* @param {
|
|
32
|
-
* @param {string} [options.
|
|
33
|
-
* @param {
|
|
34
|
-
* @param {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import Image from '@11ty/eleventy-img';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_WIDTHS = [320, 640, 960, 1280, 1920, 'auto'];
|
|
5
|
+
const DEFAULT_FORMATS = ['avif', 'webp'];
|
|
6
|
+
const DEFAULT_SIZES = '(max-width: 768px) 100vw, 768px';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Pick the smallest and largest rendition from eleventy-img metadata.
|
|
10
|
+
* Uses the first available format; entries are ordered smallest → largest.
|
|
11
|
+
* @param {Object} metadata - eleventy-img metadata keyed by format.
|
|
12
|
+
* @returns {{lowsrc: Object, highsrc: Object}} Smallest and largest rendition.
|
|
13
|
+
*/
|
|
14
|
+
function pickRenditions(metadata) {
|
|
15
|
+
const firstFormat = Object.values(metadata)[0];
|
|
16
|
+
const lowsrc = firstFormat?.[0];
|
|
17
|
+
const highsrc = firstFormat?.[firstFormat.length - 1];
|
|
18
|
+
return { lowsrc, highsrc };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Responsive image shortcode using @11ty/eleventy-img.
|
|
23
|
+
*
|
|
24
|
+
* @param {Object} options
|
|
25
|
+
* @param {string} options.src Required image source (local or remote).
|
|
26
|
+
* @param {string} options.alt Required alt text (empty string allowed for decorative).
|
|
27
|
+
* @param {string} [options.caption=""] Optional caption; enables figure wrapper when non-empty.
|
|
28
|
+
* @param {("lazy"|"eager")} [options.loading="lazy"] Loading behavior.
|
|
29
|
+
* @param {string} [options.containerClass=""] Class applied to <picture>.
|
|
30
|
+
* @param {string} [options.imageClass=""] Class applied to <img>.
|
|
31
|
+
* @param {Array<number|string>} [options.widths=DEFAULT_WIDTHS] Widths passed to eleventy-img.
|
|
32
|
+
* @param {string} [options.sizes=DEFAULT_SIZES] Sizes attribute used on sources.
|
|
33
|
+
* @param {string[]} [options.formats=DEFAULT_FORMATS] Output formats (order matters).
|
|
34
|
+
* @param {string} [options.outputDir] Output directory for generated assets.
|
|
35
|
+
* @param {string} [options.urlPath="/media/"] Public URL base for generated assets.
|
|
36
|
+
* @param {Object} [options.attrs={}] Extra attributes applied to <img>; `class` merges with imageClass.
|
|
37
|
+
* @param {string} [options.style] Inline style applied to <img>. Separate from attrs.style — if both are passed, attrs.style takes precedence via restAttrs spread.
|
|
38
|
+
* @param {boolean} [options.figure=true] Wrap in <figure> when caption is provided.
|
|
39
|
+
* @param {boolean} [options.setDimensions=true] When false, omit width/height on <img>.
|
|
40
|
+
*/
|
|
41
|
+
export async function imageShortcode(options = {}) {
|
|
42
|
+
const outputBase = this?.eleventy?.directories?.output || 'dist';
|
|
43
|
+
const {
|
|
44
|
+
src,
|
|
45
|
+
alt,
|
|
46
|
+
caption = '',
|
|
47
|
+
loading = 'lazy',
|
|
48
|
+
containerClass = '',
|
|
49
|
+
imageClass = '',
|
|
50
|
+
style,
|
|
51
|
+
widths = DEFAULT_WIDTHS,
|
|
52
|
+
sizes = DEFAULT_SIZES,
|
|
53
|
+
formats = DEFAULT_FORMATS,
|
|
54
|
+
outputDir = path.join('.', outputBase, 'media'),
|
|
55
|
+
urlPath = '/media/',
|
|
56
|
+
attrs = {},
|
|
57
|
+
figure = true,
|
|
58
|
+
setDimensions = true
|
|
59
|
+
} = options;
|
|
60
|
+
// Read from global data set during plugin init. When true, `eleventy:ignore`
|
|
61
|
+
// is added to the <img> (line 140) to prevent double-processing.
|
|
62
|
+
const hasImageTransformPlugin = this.ctx._baseline.hasImageTransformPlugin;
|
|
63
|
+
|
|
64
|
+
// --- Validation and normalization ---
|
|
65
|
+
|
|
66
|
+
if (!src) throw new Error(`imageShortcode: src is required (received ${JSON.stringify(src)})`);
|
|
67
|
+
if (alt == null) {
|
|
68
|
+
console.warn('imageShortcode: alt is required (use empty string for decorative images)');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const normalizedCaption = String(caption);
|
|
72
|
+
const normalizedAlt = alt == null ? '' : String(alt);
|
|
73
|
+
|
|
74
|
+
const inputDir = this?.eleventy?.directories?.input;
|
|
75
|
+
const isRemote = /^https?:\/\//i.test(src);
|
|
76
|
+
// Note: remote URLs rely on eleventy-img's built-in fetch — no timeout/retry control at shortcode level.
|
|
77
|
+
const resolvedSrc = !isRemote && inputDir ? path.join(inputDir, src.replace(/^\//, '')) : src;
|
|
78
|
+
|
|
79
|
+
const imageOptions = {
|
|
80
|
+
widths: [...widths],
|
|
81
|
+
formats: [...formats],
|
|
82
|
+
outputDir,
|
|
83
|
+
urlPath,
|
|
84
|
+
filenameFormat(id, srcPath, width, format) {
|
|
85
|
+
const extension = path.extname(srcPath);
|
|
86
|
+
const name = path.basename(srcPath, extension);
|
|
87
|
+
return `${name}-${id.slice(0, 6)}-${width}w.${format}`;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// --- Image processing ---
|
|
92
|
+
// In serve mode, `transformOnRequest` defers processing to first browser request
|
|
93
|
+
// for faster dev startup. If it fails, retry without it — this is an edge case
|
|
94
|
+
// but one that has bitten in practice. In build mode, errors surface immediately.
|
|
95
|
+
|
|
96
|
+
let metadata;
|
|
97
|
+
try {
|
|
98
|
+
metadata = await Image(resolvedSrc, {
|
|
99
|
+
transformOnRequest: process.env.ELEVENTY_RUN_MODE === 'serve',
|
|
100
|
+
...imageOptions
|
|
101
|
+
});
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (process.env.ELEVENTY_RUN_MODE === 'serve') {
|
|
104
|
+
console.warn(`imageShortcode: transformOnRequest failed for ${src}, retrying.\n > ${error?.message || error}`);
|
|
105
|
+
metadata = await Image(resolvedSrc, imageOptions);
|
|
106
|
+
} else {
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const { lowsrc, highsrc } = pickRenditions(metadata);
|
|
112
|
+
if (!lowsrc || !highsrc) {
|
|
113
|
+
throw new Error(`imageShortcode: no renditions produced for ${src}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// --- HTML assembly ---
|
|
117
|
+
// One <source> per format, each carrying the full srcset for that format.
|
|
118
|
+
const sourceTags = Object.values(metadata)
|
|
119
|
+
.map((formatEntries) => {
|
|
120
|
+
const type = formatEntries[0].sourceType;
|
|
121
|
+
const srcset = formatEntries.map((entry) => entry.srcset).join(', ');
|
|
122
|
+
return `<source type="${type}" srcset="${srcset}" sizes="${sizes}">`;
|
|
123
|
+
})
|
|
124
|
+
.join('\n');
|
|
125
|
+
|
|
126
|
+
// Pull `class` out of attrs so it can merge with imageClass. Remaining attrs
|
|
127
|
+
// and `eleventy:ignore` (if needed) are spread onto imageAttributes below.
|
|
128
|
+
const { class: attrClass, ...restAttrs } = attrs;
|
|
129
|
+
const combinedClass = [imageClass, attrClass].filter(Boolean).join(' ').trim() || undefined;
|
|
130
|
+
|
|
131
|
+
const imageAttributes = {
|
|
132
|
+
src: lowsrc.url,
|
|
133
|
+
alt: normalizedAlt,
|
|
134
|
+
loading,
|
|
135
|
+
decoding: loading === 'eager' ? 'sync' : 'async',
|
|
136
|
+
class: combinedClass,
|
|
137
|
+
style,
|
|
138
|
+
...(setDimensions ? { width: highsrc.width, height: highsrc.height } : {}),
|
|
139
|
+
...restAttrs,
|
|
140
|
+
...(hasImageTransformPlugin ? { 'eleventy:ignore': true } : {})
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// Build the attribute string, dropping any empty/null values to keep output clean.
|
|
144
|
+
const imgAttrString = Object.entries(imageAttributes)
|
|
145
|
+
.filter(([, value]) => value !== undefined && value !== null && value !== '')
|
|
146
|
+
.map(([key, value]) => (value === true ? key : `${key}="${value}"`))
|
|
147
|
+
.join(' ');
|
|
148
|
+
|
|
149
|
+
const pictureClass = containerClass && containerClass.trim() ? ` class="${containerClass.trim()}"` : '';
|
|
150
|
+
|
|
151
|
+
const picture = `<picture${pictureClass}>
|
|
152
|
+
${sourceTags}
|
|
153
|
+
<img ${imgAttrString}>
|
|
154
|
+
</picture>`;
|
|
155
|
+
|
|
156
|
+
if (!figure || !normalizedCaption) return picture;
|
|
157
|
+
|
|
158
|
+
return `<figure>
|
|
159
|
+
${picture}
|
|
160
|
+
<figcaption>${normalizedCaption}</figcaption>
|
|
161
|
+
</figure>`;
|
|
162
|
+
}
|