@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 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 };
@@ -0,0 +1,3 @@
1
+ export default function isStringFilter(object) {
2
+ return typeof object === "string";
3
+ }
@@ -0,0 +1,10 @@
1
+ // see https://jeremias.codes/2025/02/markdown-filters-eleventy/
2
+ import markdownit from 'markdown-it';
3
+
4
+ const md = markdownit({
5
+ html: true
6
+ });
7
+
8
+ export const markdownFilter = (string) => {
9
+ return md.renderInline(string);
10
+ };
@@ -0,0 +1,6 @@
1
+ export default function relatedPostsFilter(collection = []) {
2
+ const page = this.ctx.page;
3
+ return collection.filter(post => {
4
+ return post.url !== page.url;
5
+ })
6
+ }
@@ -0,0 +1,9 @@
1
+ import { markdownFilter } from "./filters/markdown.js";
2
+ import relatedPostsFilter from "./filters/related-posts.js";
3
+ import isStringFilter from "./filters/isString.js";
4
+
5
+ export default {
6
+ markdownFilter,
7
+ relatedPostsFilter,
8
+ isStringFilter
9
+ }
@@ -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
+ }
@@ -0,0 +1,6 @@
1
+ import { registerDateGlobal } from "./globals/date.js";
2
+
3
+ /** @param {import("@11ty/eleventy").UserConfig} eleventyConfig */
4
+ export default function registerGlobals(eleventyConfig) {
5
+ registerDateGlobal(eleventyConfig);
6
+ }
@@ -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
+ }
@@ -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
+ }
@@ -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,3 @@
1
+ import { imageShortcode } from "./shortcodes/image.js";
2
+
3
+ export default { imageShortcode };
@@ -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
+ }