@apleasantview/eleventy-plugin-baseline 0.1.0-next.8
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/LICENSE +21 -0
- package/README.md +37 -0
- package/core/debug.js +20 -0
- package/core/filters/isString.js +3 -0
- package/core/filters/markdown.js +10 -0
- package/core/filters/related-posts.js +6 -0
- package/core/filters.js +9 -0
- package/core/globals/date.js +18 -0
- package/core/globals.js +6 -0
- package/core/helpers.js +139 -0
- package/core/logging.js +32 -0
- package/core/modules.js +22 -0
- package/core/shortcodes/image.js +120 -0
- package/core/shortcodes.js +3 -0
- package/eleventy.config.js +98 -0
- package/modules/assets-core/plugins/assets-core.js +81 -0
- package/modules/assets-esbuild/filters/inline-esbuild.js +21 -0
- package/modules/assets-esbuild/plugins/assets-esbuild.js +61 -0
- package/modules/assets-postcss/fallback/postcss.config.js +23 -0
- package/modules/assets-postcss/filters/inline-postcss.js +31 -0
- package/modules/assets-postcss/plugins/assets-postcss.js +62 -0
- package/modules/head-core/drivers/posthtml-head-elements.js +148 -0
- package/modules/head-core/plugins/head-core.js +60 -0
- package/modules/head-core/utils/head-utils.js +193 -0
- package/modules/multilang-core/plugins/multilang-core.js +35 -0
- package/modules/navigator-core/plugins/navigator-core.js +37 -0
- package/modules/navigator-core/templates/navigator-core.html +48 -0
- package/modules/sitemap-core/plugins/sitemap-core.js +65 -0
- package/modules/sitemap-core/templates/sitemap-core.html +25 -0
- package/modules/sitemap-core/templates/sitemap-index.html +15 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Cristovao Verstraeten (a pleasant view)
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Eleventy Plugin Baseline
|
|
2
|
+
|
|
3
|
+
An experimental Swiss army knife toolkit for Eleventy. Bundles handy helpers for assets, head/meta, navigation, sitemaps, debugging, and more — without turning into a full theme.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
If you already have Eleventy and eleventy-img installed:
|
|
8
|
+
```bash
|
|
9
|
+
npm install @apleasantview/eleventy-plugin-baseline
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
For a fresh project (install Eleventy and eleventy-img too):
|
|
13
|
+
```bash
|
|
14
|
+
npm install @11ty/eleventy @11ty/eleventy-img @apleasantview/eleventy-plugin-baseline
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
In your Eleventy config (ESM):
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
import baseline from "eleventy-plugin-baseline";
|
|
23
|
+
|
|
24
|
+
export default function (eleventyConfig) {
|
|
25
|
+
eleventyConfig.addPlugin(baseline, {
|
|
26
|
+
// verbose: false,
|
|
27
|
+
// enableNavigatorTemplate: false,
|
|
28
|
+
// enableSitemapTemplate: true,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Requires Eleventy 3.x.
|
|
34
|
+
|
|
35
|
+
## License
|
|
36
|
+
|
|
37
|
+
MIT. See `LICENSE`.
|
package/core/debug.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { inspect as utilInspect } from "node:util";
|
|
2
|
+
|
|
3
|
+
// Adapted from pdehaan - https://github.com/pdehaan/eleventy-plugin-debug
|
|
4
|
+
const debugOptions = Object.assign({
|
|
5
|
+
space: 0
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
function inspect(obj, options = {}) {
|
|
9
|
+
return utilInspect(obj, options);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function json(obj, space = debugOptions.space) {
|
|
13
|
+
return JSON.stringify(obj, null, space);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function keys(obj) {
|
|
17
|
+
return Object.keys(obj).sort();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default { inspect, json, keys };
|
package/core/filters.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { DateTime } from "luxon";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Register Nunjucks global "date" with helper methods.
|
|
5
|
+
* Currently exposes date.toUTCISO(value) -> UTC ISO string without milliseconds.
|
|
6
|
+
*
|
|
7
|
+
* @param {import("@11ty/eleventy").UserConfig} eleventyConfig
|
|
8
|
+
*/
|
|
9
|
+
export function registerDateGlobal(eleventyConfig) {
|
|
10
|
+
eleventyConfig.addNunjucksGlobal("date", {
|
|
11
|
+
toUTCISO(value) {
|
|
12
|
+
if (!value) return "";
|
|
13
|
+
const jsDate = value instanceof Date ? value : new Date(value);
|
|
14
|
+
if (Number.isNaN(jsDate.getTime())) return "";
|
|
15
|
+
return DateTime.fromJSDate(jsDate).toUTC().toISO({ suppressMilliseconds: true });
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
package/core/globals.js
ADDED
package/core/helpers.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { TemplatePath } from "@11ty/eleventy-utils";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Helper function to add trailing slash to a path
|
|
6
|
+
* @param {string} path
|
|
7
|
+
* @returns {string}
|
|
8
|
+
*/
|
|
9
|
+
export function addTrailingSlash(path) {
|
|
10
|
+
if (path.slice(-1) === "/") {
|
|
11
|
+
return path;
|
|
12
|
+
}
|
|
13
|
+
return path + "/";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Resolves the assets directory path from config
|
|
18
|
+
* Follows Eleventy's pattern: join inputDir + rawDir, then normalize and add trailing slash
|
|
19
|
+
* @param {string} inputDir - The input directory (e.g., "./src/")
|
|
20
|
+
* @param {string} rawDir - Raw directory value from config (e.g., "assets")
|
|
21
|
+
* @returns {{assetsDir: string, assetsDirRelative: string}}
|
|
22
|
+
*/
|
|
23
|
+
export function resolveAssetsDir(inputDir, outputDir, rawDir) {
|
|
24
|
+
// Join input/output with assets subdir and normalize
|
|
25
|
+
const joinedInput = TemplatePath.join(inputDir, rawDir || "");
|
|
26
|
+
const joinedOutput = TemplatePath.join(outputDir, rawDir || "");
|
|
27
|
+
|
|
28
|
+
const assetsDir = addTrailingSlash(TemplatePath.standardizeFilePath(joinedInput));
|
|
29
|
+
const assetsOutputDir = addTrailingSlash(TemplatePath.standardizeFilePath(joinedOutput));
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
assetsDir,
|
|
33
|
+
assetsOutputDir,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
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(
|
|
45
|
+
TemplatePath.stripLeadingDotSlash(assetsDir)
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
return patterns.map((pattern) => {
|
|
49
|
+
const normalized = TemplatePath.standardizeFilePath(pattern);
|
|
50
|
+
return normalized.startsWith("/") || path.isAbsolute(normalized)
|
|
51
|
+
? normalized
|
|
52
|
+
: TemplatePath.join(assetsDirAbsolute, normalized);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Extracts file metadata from a file path
|
|
58
|
+
* @param {string} filePath - Normalized file path
|
|
59
|
+
* @returns {{basename: string, fileSlug: string, inputFileExtension: string}}
|
|
60
|
+
*/
|
|
61
|
+
export function extractFileMetadata(filePath) {
|
|
62
|
+
const ext = path.extname(filePath); // Returns extension with dot (e.g., ".css") or ""
|
|
63
|
+
const inputFileExtension = ext && ext.length > 0 ? ext.slice(1) : "";
|
|
64
|
+
const basename = TemplatePath.getLastPathSegment(filePath, false);
|
|
65
|
+
const fileSlug = ext ? basename.slice(0, -ext.length) : basename;
|
|
66
|
+
|
|
67
|
+
return { basename, fileSlug, inputFileExtension };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Creates a collection item from a relative file path
|
|
72
|
+
* @param {string} inputPath - Relative path from project root (e.g., "./src/assets/css/index.css")
|
|
73
|
+
* @param {string} inputDir - Input directory (e.g., "./src/")
|
|
74
|
+
* @param {string} outputDir - Output directory (e.g., "./dist/")
|
|
75
|
+
* @param {string} assetsDirRelative - Assets directory relative to input (e.g., "assets")
|
|
76
|
+
* @param {string} passthroughOutput - Output path for passthrough
|
|
77
|
+
* @param {boolean} passthrough - Whether passthrough is enabled
|
|
78
|
+
* @returns {object} Collection item
|
|
79
|
+
*/
|
|
80
|
+
export function createCollectionItem(
|
|
81
|
+
inputPath,
|
|
82
|
+
inputDir,
|
|
83
|
+
outputDir,
|
|
84
|
+
assetsDirRelative,
|
|
85
|
+
passthroughOutput,
|
|
86
|
+
passthrough
|
|
87
|
+
) {
|
|
88
|
+
const { basename, fileSlug, inputFileExtension } = extractFileMetadata(inputPath);
|
|
89
|
+
|
|
90
|
+
// Get path relative to input directory
|
|
91
|
+
// e.g., inputPath = "./src/assets/css/index.css", inputDir = "./src/"
|
|
92
|
+
// relToInput = "assets/css/index.css"
|
|
93
|
+
const relToInput = TemplatePath.stripLeadingSubPath(
|
|
94
|
+
inputPath,
|
|
95
|
+
TemplatePath.addLeadingDotSlash(inputDir)
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
// outputPath: prepend output directory (with leading ./)
|
|
99
|
+
// e.g., relToInput = "assets/css/index.css", outputDir = "./dist/"
|
|
100
|
+
// outputPath = "./dist/assets/css/index.css"
|
|
101
|
+
const outputPath = TemplatePath.addLeadingDotSlash(
|
|
102
|
+
TemplatePath.normalize(
|
|
103
|
+
TemplatePath.join(
|
|
104
|
+
TemplatePath.addLeadingDotSlash(outputDir),
|
|
105
|
+
relToInput
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// relToAssets: path relative to assets directory for URL generation
|
|
111
|
+
// e.g., inputPath = "./src/assets/css/index.css", assetsDirRelative = "assets"
|
|
112
|
+
// relToAssets = "css/index.css"
|
|
113
|
+
const assetsDirPath = TemplatePath.addLeadingDotSlash(
|
|
114
|
+
TemplatePath.join(inputDir, assetsDirRelative)
|
|
115
|
+
);
|
|
116
|
+
const relToAssets = TemplatePath.stripLeadingSubPath(inputPath, assetsDirPath);
|
|
117
|
+
|
|
118
|
+
const url = passthrough
|
|
119
|
+
? TemplatePath.join(passthroughOutput, relToAssets).replace(/\/$/, "")
|
|
120
|
+
: undefined;
|
|
121
|
+
|
|
122
|
+
// filePathStem: path relative to input without extension, with leading slash
|
|
123
|
+
// e.g., relToInput = "assets/css/index.css"
|
|
124
|
+
// filePathStem = "/assets/css/index"
|
|
125
|
+
const filePathStem = "/" + (inputFileExtension
|
|
126
|
+
? relToInput.slice(0, -inputFileExtension.length - 1) // Remove extension and dot
|
|
127
|
+
: relToInput);
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
inputPath,
|
|
131
|
+
outputPath,
|
|
132
|
+
basename,
|
|
133
|
+
fileSlug,
|
|
134
|
+
inputFileExtension,
|
|
135
|
+
filePathStem,
|
|
136
|
+
dir: TemplatePath.getDirFromFilePath(inputPath),
|
|
137
|
+
url,
|
|
138
|
+
};
|
|
139
|
+
}
|
package/core/logging.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gets verbose flag from Eleventy global data
|
|
3
|
+
* @param {import("@11ty/eleventy").UserConfig} eleventyConfig
|
|
4
|
+
* @returns {boolean}
|
|
5
|
+
*/
|
|
6
|
+
export function getVerbose(eleventyConfig) {
|
|
7
|
+
const baselineData = eleventyConfig.globalData?._baseline;
|
|
8
|
+
return !!baselineData?.verbose;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Logs a message if verbose mode is enabled
|
|
13
|
+
* @param {boolean} verbose - Whether verbose logging is enabled
|
|
14
|
+
* @param {string} message - Message to log
|
|
15
|
+
* @param {...any} args - Additional arguments
|
|
16
|
+
*/
|
|
17
|
+
export function logIfVerbose(verbose, message, ...args) {
|
|
18
|
+
if (verbose) {
|
|
19
|
+
console.log(`[eleventy-plugin-baseline] INFO ${message}`, ...args);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Logs a warning if verbose mode is enabled
|
|
25
|
+
* @param {boolean} verbose - Whether verbose logging is enabled
|
|
26
|
+
* @param {string} message - Warning message
|
|
27
|
+
*/
|
|
28
|
+
export function warnIfVerbose(verbose, message) {
|
|
29
|
+
if (verbose) {
|
|
30
|
+
console.warn(`[eleventy-plugin-baseline] WARN ${message}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
package/core/modules.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Eleventy plugins
|
|
2
|
+
import { EleventyHtmlBasePlugin } from "@11ty/eleventy";
|
|
3
|
+
|
|
4
|
+
// Custom plugins
|
|
5
|
+
import multilangCore from "../modules/multilang-core/plugins/multilang-core.js";
|
|
6
|
+
import navigatorCore from "../modules/navigator-core/plugins/navigator-core.js";
|
|
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
|
+
import headCore from "../modules/head-core/plugins/head-core.js";
|
|
11
|
+
import sitemapCore from "../modules/sitemap-core/plugins/sitemap-core.js";
|
|
12
|
+
|
|
13
|
+
export default {
|
|
14
|
+
EleventyHtmlBasePlugin,
|
|
15
|
+
multilangCore,
|
|
16
|
+
navigatorCore,
|
|
17
|
+
assetsCore,
|
|
18
|
+
assetsPostCSS,
|
|
19
|
+
assetsESBuild,
|
|
20
|
+
headCore,
|
|
21
|
+
sitemapCore
|
|
22
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import Image from "@11ty/eleventy-img";
|
|
3
|
+
|
|
4
|
+
const DEFAULT_WIDTHS = [320, 640, 960, 1280];
|
|
5
|
+
const DEFAULT_FORMATS = ["avif", "webp", "jpeg"];
|
|
6
|
+
const DEFAULT_SIZES = "(max-width: 768px) 100vw, 768px";
|
|
7
|
+
const DEFAULT_OUTPUT = {
|
|
8
|
+
outputDir: "./dist/media/",
|
|
9
|
+
urlPath: "/media/",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function pickRenditions(metadata) {
|
|
13
|
+
// Use the first available format; first entry is smallest, last is largest.
|
|
14
|
+
const firstFormat = Object.values(metadata)[0];
|
|
15
|
+
const lowsrc = firstFormat?.[0];
|
|
16
|
+
const highsrc = firstFormat?.[firstFormat.length - 1];
|
|
17
|
+
return { lowsrc, highsrc };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Responsive image shortcode using @11ty/eleventy-img.
|
|
22
|
+
*
|
|
23
|
+
* @param {Object} options
|
|
24
|
+
* @param {string} options.src Required image source (local or remote).
|
|
25
|
+
* @param {string} options.alt Required alt text (empty string allowed for decorative).
|
|
26
|
+
* @param {string} [options.caption=""] Optional caption; enables figure wrapper when non-empty.
|
|
27
|
+
* @param {("lazy"|"eager")} [options.loading="lazy"] Loading behavior.
|
|
28
|
+
* @param {string} [options.containerClass=""] Class applied to <picture>.
|
|
29
|
+
* @param {string} [options.imageClass=""] Class applied to <img>.
|
|
30
|
+
* @param {Array<number|string>} [options.widths=DEFAULT_WIDTHS] Widths passed to eleventy-img.
|
|
31
|
+
* @param {string} [options.sizes=DEFAULT_SIZES] Sizes attribute used on sources.
|
|
32
|
+
* @param {string[]} [options.formats=DEFAULT_FORMATS] Output formats (order matters).
|
|
33
|
+
* @param {string} [options.outputDir=DEFAULT_OUTPUT.outputDir] Output directory for generated assets.
|
|
34
|
+
* @param {string} [options.urlPath=DEFAULT_OUTPUT.urlPath] Public URL base for generated assets.
|
|
35
|
+
* @param {Object} [options.attrs={}] Extra attributes applied to <img>; `class` merges with imageClass.
|
|
36
|
+
* @param {boolean} [options.figure=true] Wrap in <figure> when caption is provided.
|
|
37
|
+
* @param {boolean} [options.setDimensions=true] When false, omit width/height on <img>.
|
|
38
|
+
*/
|
|
39
|
+
export async function imageShortcode(options = {}) {
|
|
40
|
+
const {
|
|
41
|
+
src,
|
|
42
|
+
alt,
|
|
43
|
+
caption = "",
|
|
44
|
+
loading = "lazy",
|
|
45
|
+
containerClass = "",
|
|
46
|
+
imageClass = "",
|
|
47
|
+
widths = DEFAULT_WIDTHS,
|
|
48
|
+
sizes = DEFAULT_SIZES,
|
|
49
|
+
formats = DEFAULT_FORMATS,
|
|
50
|
+
outputDir = DEFAULT_OUTPUT.outputDir,
|
|
51
|
+
urlPath = DEFAULT_OUTPUT.urlPath,
|
|
52
|
+
attrs = {},
|
|
53
|
+
figure = true,
|
|
54
|
+
setDimensions = true,
|
|
55
|
+
} = options;
|
|
56
|
+
|
|
57
|
+
if (!src) throw new Error("imageShortcode: src is required");
|
|
58
|
+
if (alt === undefined) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
"imageShortcode: alt is required (use empty string for decorative images)",
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const metadata = await Image(src, {
|
|
65
|
+
widths: [...widths],
|
|
66
|
+
formats: [...formats],
|
|
67
|
+
outputDir,
|
|
68
|
+
urlPath,
|
|
69
|
+
filenameFormat(id, srcPath, width, format) {
|
|
70
|
+
const extension = path.extname(srcPath);
|
|
71
|
+
const name = path.basename(srcPath, extension);
|
|
72
|
+
return `${name}-${width}w.${format}`;
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const { lowsrc, highsrc } = pickRenditions(metadata);
|
|
77
|
+
if (!lowsrc || !highsrc) {
|
|
78
|
+
throw new Error(`imageShortcode: no renditions produced for ${src}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const sourceTags = Object.values(metadata)
|
|
82
|
+
.map((formatEntries) => {
|
|
83
|
+
const type = formatEntries[0].sourceType;
|
|
84
|
+
const srcset = formatEntries.map((entry) => entry.srcset).join(", ");
|
|
85
|
+
return `<source type="${type}" srcset="${srcset}" sizes="${sizes}">`;
|
|
86
|
+
})
|
|
87
|
+
.join("\n");
|
|
88
|
+
|
|
89
|
+
const { class: attrClass, ...restAttrs } = attrs;
|
|
90
|
+
const combinedClass = [imageClass, attrClass].filter(Boolean).join(" ").trim() || undefined;
|
|
91
|
+
|
|
92
|
+
const imageAttributes = {
|
|
93
|
+
src: lowsrc.url,
|
|
94
|
+
alt,
|
|
95
|
+
loading,
|
|
96
|
+
decoding: loading === "eager" ? "sync" : "async",
|
|
97
|
+
class: combinedClass,
|
|
98
|
+
...(setDimensions ? { width: highsrc.width, height: highsrc.height } : {}),
|
|
99
|
+
...restAttrs,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const imgAttrString = Object.entries(imageAttributes)
|
|
103
|
+
.filter(([, value]) => value !== undefined && value !== null && value !== "")
|
|
104
|
+
.map(([key, value]) => `${key}="${value}"`)
|
|
105
|
+
.join(" ");
|
|
106
|
+
|
|
107
|
+
const pictureClass = containerClass && containerClass.trim() ? ` class="${containerClass.trim()}"` : "";
|
|
108
|
+
|
|
109
|
+
const picture = `<picture${pictureClass}>
|
|
110
|
+
${sourceTags}
|
|
111
|
+
<img ${imgAttrString}>
|
|
112
|
+
</picture>`;
|
|
113
|
+
|
|
114
|
+
if (!figure || !caption) return picture;
|
|
115
|
+
|
|
116
|
+
return `<figure>
|
|
117
|
+
${picture}
|
|
118
|
+
<figcaption>${caption}</figcaption>
|
|
119
|
+
</figure>`;
|
|
120
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import globals from "./core/globals.js";
|
|
3
|
+
import debug from "./core/debug.js";
|
|
4
|
+
import filters from "./core/filters.js";
|
|
5
|
+
import modules from "./core/modules.js";
|
|
6
|
+
import shortcodes from "./core/shortcodes.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Eleventy Plugin Baseline.
|
|
10
|
+
*
|
|
11
|
+
* @typedef {Object} BaselineOptions
|
|
12
|
+
* @property {boolean} [verbose=false] Enable extra logging from the plugin.
|
|
13
|
+
* @property {boolean} [enableNavigatorTemplate=false] Register navigator template/routes.
|
|
14
|
+
* @property {boolean} [enableSitemapTemplate=true] Register sitemap template/routes.
|
|
15
|
+
* @property {boolean} [multilingual=false] Enable multilang core (requires defaultLanguage + languages).
|
|
16
|
+
* @property {string} [defaultLanguage] IETF/BCP47 default language code (used when multilingual=true).
|
|
17
|
+
* @property {Record<string, unknown>} [languages={}] Language definition map (shape not enforced; only presence/keys checked).
|
|
18
|
+
*
|
|
19
|
+
* @param {BaselineOptions} [options={}] Custom options for the plugin.
|
|
20
|
+
* @returns {(eleventyConfig: UserConfig) => void}
|
|
21
|
+
*/
|
|
22
|
+
export default function baseline(options = {}) {
|
|
23
|
+
/** @param {import("@11ty/eleventy").UserConfig} eleventyConfig */
|
|
24
|
+
return async function (eleventyConfig) {
|
|
25
|
+
try {
|
|
26
|
+
// Emit a warning message if the application is not using Eleventy 3.0 or newer (including prereleases).
|
|
27
|
+
eleventyConfig.versionCheck(">=3.0");
|
|
28
|
+
} catch (e) {
|
|
29
|
+
console.log(`[eleventy-plugin-baseline] WARN Eleventy plugin compatibility: ${e.message}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const userOptions = {
|
|
33
|
+
verbose: options.verbose ?? false,
|
|
34
|
+
enableNavigatorTemplate: options.enableNavigatorTemplate ?? false,
|
|
35
|
+
enableSitemapTemplate: options.enableSitemapTemplate ?? true,
|
|
36
|
+
multilingual: options.multilingual ?? false,
|
|
37
|
+
...options
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Core functions.
|
|
41
|
+
// Languages are expected as an object map; if missing or invalid, skip.
|
|
42
|
+
const languages = userOptions.languages && typeof userOptions.languages === "object"
|
|
43
|
+
? userOptions.languages : null;
|
|
44
|
+
const hasLanguages = languages && Object.keys(languages).length > 0;
|
|
45
|
+
const isMultilingual = userOptions.multilingual === true && userOptions.defaultLanguage && hasLanguages;
|
|
46
|
+
|
|
47
|
+
eleventyConfig.addGlobalData("_baseline", userOptions);
|
|
48
|
+
globals(eleventyConfig);
|
|
49
|
+
eleventyConfig.addPassthroughCopy({ "./src/static": "/" }, { failOnError: true });
|
|
50
|
+
eleventyConfig.addPreprocessor("drafts", "*", (data, content) => {
|
|
51
|
+
if (data.draft && process.env.ELEVENTY_RUN_MODE === "build") {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (isMultilingual) {
|
|
57
|
+
eleventyConfig.addPlugin(modules.multilangCore, {
|
|
58
|
+
defaultLanguage: userOptions.defaultLanguage,
|
|
59
|
+
languages
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Modules.
|
|
64
|
+
eleventyConfig.addPlugin(modules.EleventyHtmlBasePlugin, { baseHref: process.env.URL || eleventyConfig.pathPrefix });
|
|
65
|
+
eleventyConfig.addPlugin(modules.assetsCore);
|
|
66
|
+
eleventyConfig.addPlugin(modules.assetsPostCSS);
|
|
67
|
+
eleventyConfig.addPlugin(modules.assetsESBuild);
|
|
68
|
+
eleventyConfig.addPlugin(modules.headCore);
|
|
69
|
+
eleventyConfig.addPlugin(modules.sitemapCore, { enableSitemapTemplate: userOptions.enableSitemapTemplate, multilingual: isMultilingual, languages });
|
|
70
|
+
|
|
71
|
+
// Filters — Module filters might move to their respective module.
|
|
72
|
+
eleventyConfig.addFilter("markdownify", filters.markdownFilter);
|
|
73
|
+
eleventyConfig.addFilter("relatedPosts", filters.relatedPostsFilter);
|
|
74
|
+
eleventyConfig.addFilter("isString", filters.isStringFilter);
|
|
75
|
+
|
|
76
|
+
// Shortcodes.
|
|
77
|
+
eleventyConfig.addShortcode("image", shortcodes.imageShortcode);
|
|
78
|
+
|
|
79
|
+
// Debug filters and navigators.
|
|
80
|
+
eleventyConfig.addFilter("_inspect", debug.inspect);
|
|
81
|
+
eleventyConfig.addFilter("_json", debug.json);
|
|
82
|
+
eleventyConfig.addFilter("_keys", debug.keys);
|
|
83
|
+
eleventyConfig.addPlugin(modules.navigatorCore, { enableNavigatorTemplate: userOptions.enableNavigatorTemplate });
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const config = {
|
|
88
|
+
dir: {
|
|
89
|
+
input: "src",
|
|
90
|
+
output: "dist",
|
|
91
|
+
data: "_data",
|
|
92
|
+
includes: "_includes",
|
|
93
|
+
assets: "assets"
|
|
94
|
+
},
|
|
95
|
+
htmlTemplateEngine: "njk",
|
|
96
|
+
markdownTemplateEngine: "njk",
|
|
97
|
+
templateFormats: ["html", "njk", "md"]
|
|
98
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { TemplatePath } from "@11ty/eleventy-utils";
|
|
2
|
+
import { addTrailingSlash, resolveAssetsDir } from "../../../core/helpers.js";
|
|
3
|
+
import { warnIfVerbose, getVerbose } from "../../../core/logging.js";
|
|
4
|
+
|
|
5
|
+
const syncCacheFromDirectories = (cache, dirs, rawDir) => {
|
|
6
|
+
const inputDir = TemplatePath.addLeadingDotSlash(dirs.input || "./");
|
|
7
|
+
const outputDir = TemplatePath.addLeadingDotSlash(dirs.output || "./");
|
|
8
|
+
const { assetsDir, assetsOutputDir } = resolveAssetsDir(inputDir, outputDir, rawDir);
|
|
9
|
+
|
|
10
|
+
cache.input = addTrailingSlash(inputDir);
|
|
11
|
+
cache.output = addTrailingSlash(outputDir);
|
|
12
|
+
cache.assetsInput = assetsDir;
|
|
13
|
+
cache.assetsOutput = assetsOutputDir;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const ensureCache = (cache, eleventyConfig, rawDir, verbose) => {
|
|
17
|
+
if (cache.assetsInput) return;
|
|
18
|
+
syncCacheFromDirectories(cache, eleventyConfig.dir || {}, rawDir);
|
|
19
|
+
warnIfVerbose(verbose, "Fallback directory resolution");
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* eleventy-plugin-assets-core
|
|
24
|
+
*
|
|
25
|
+
* Resolve assets input/output directories, register a virtual
|
|
26
|
+
* `directories.assets`, expose the resolved paths via global data, and add
|
|
27
|
+
* a watch target under the resolved assets input directory.
|
|
28
|
+
*
|
|
29
|
+
* Options:
|
|
30
|
+
* - verbose (boolean, default global baseline verbose): enable verbose logs.
|
|
31
|
+
*/
|
|
32
|
+
/** @param {import("@11ty/eleventy").UserConfig} eleventyConfig */
|
|
33
|
+
export default function assetsCore(eleventyConfig, options = {}) {
|
|
34
|
+
const verbose = getVerbose(eleventyConfig) || options.verbose || false;
|
|
35
|
+
const userKey = "assets";
|
|
36
|
+
|
|
37
|
+
// Extract raw directory value from config (can be done early)
|
|
38
|
+
const rawDir = eleventyConfig.dir?.[userKey] || userKey;
|
|
39
|
+
|
|
40
|
+
// Cache object (raw values; normalized later by syncCacheFromDirectories)
|
|
41
|
+
const cache = {
|
|
42
|
+
input: eleventyConfig.dir?.input || null,
|
|
43
|
+
output: eleventyConfig.dir?.output || null,
|
|
44
|
+
assetsInput: eleventyConfig.dir?.assets ?? userKey ?? null,
|
|
45
|
+
assetsOutput: null,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
syncCacheFromDirectories(cache, eleventyConfig.dir || {}, rawDir);
|
|
49
|
+
|
|
50
|
+
eleventyConfig.on("eleventy.directories", (directories) => {
|
|
51
|
+
syncCacheFromDirectories(cache, directories, rawDir);
|
|
52
|
+
|
|
53
|
+
// Add a virtual directory key only if not already defined/configurable.
|
|
54
|
+
const existing = Object.getOwnPropertyDescriptor(eleventyConfig.directories, userKey);
|
|
55
|
+
if (existing && existing.configurable === false) {
|
|
56
|
+
warnIfVerbose(verbose, `directories[${userKey}] already defined; skipping`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
Object.defineProperty(eleventyConfig.directories, userKey, {
|
|
61
|
+
get() {
|
|
62
|
+
return cache.assetsInput;
|
|
63
|
+
},
|
|
64
|
+
enumerable: true,
|
|
65
|
+
configurable: false,
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
eleventyConfig.addGlobalData("_baseline.assets", () => {
|
|
70
|
+
ensureCache(cache, eleventyConfig, rawDir, verbose);
|
|
71
|
+
return {
|
|
72
|
+
input: cache.assetsInput,
|
|
73
|
+
output: cache.assetsOutput
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Watch target — use resolved assets input dir.
|
|
78
|
+
ensureCache(cache, eleventyConfig, rawDir, verbose);
|
|
79
|
+
const watchGlob = TemplatePath.join(cache.assetsInput, "**/*.{css,js,svg,png,jpeg}");
|
|
80
|
+
eleventyConfig.addWatchTarget(watchGlob);
|
|
81
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import * as esbuild from "esbuild";
|
|
3
|
+
|
|
4
|
+
export default async function inlineESbuild(jsFilePath) {
|
|
5
|
+
try {
|
|
6
|
+
// let jsContent = await fs.readFile(jsFilePath, 'utf8');
|
|
7
|
+
|
|
8
|
+
let result = await esbuild.build({
|
|
9
|
+
entryPoints: [jsFilePath],
|
|
10
|
+
bundle: true,
|
|
11
|
+
minify: true,
|
|
12
|
+
target: "es2020",
|
|
13
|
+
write: false
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return `<script>${result.outputFiles[0].text}</script>`;
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.error(error);
|
|
19
|
+
return `<script>/* Error processing JS */</script>`;
|
|
20
|
+
}
|
|
21
|
+
}
|