@apleasantview/eleventy-plugin-baseline 0.1.0-next.32 → 0.1.0-next.39
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/README.md +48 -23
- package/core/content-map-store.js +51 -0
- package/core/filters/index.js +4 -0
- package/core/filters/isString.js +6 -1
- package/core/filters/markdown.js +6 -0
- package/core/filters/related-posts.js +7 -1
- package/core/global-functions/index.js +6 -0
- package/core/logging.js +25 -25
- package/core/page-context.js +310 -0
- package/core/registry.js +110 -0
- package/core/schema.js +37 -0
- package/core/shortcodes/image.js +167 -144
- package/core/shortcodes/index.js +2 -0
- package/core/slug-index.js +61 -0
- package/core/translation-map-store.js +46 -0
- package/core/types.js +73 -0
- package/core/utils/helpers.js +75 -0
- package/core/utils/pick.js +7 -0
- package/core/virtual-dir.js +111 -0
- package/core/wikilinks.js +152 -0
- package/index.js +364 -0
- package/modules/assets/index.js +162 -0
- package/modules/assets/processors/esbuild-process.js +35 -0
- package/modules/assets/processors/postcss-process.js +52 -0
- package/modules/assets/schema.js +14 -0
- package/modules/head/drivers/capo-adapter.js +72 -0
- package/modules/head/drivers/posthtml-head-elements.js +140 -0
- package/modules/head/index.js +106 -0
- package/modules/head/schema.js +42 -0
- package/modules/head/utils/alternates.js +11 -0
- package/modules/head/utils/dedupe.js +47 -0
- package/modules/multilang/index.js +149 -0
- package/modules/navigator/index.js +140 -0
- package/modules/navigator/schema.js +13 -0
- package/modules/{navigator-core → navigator}/templates/navigator-core.html +14 -8
- package/modules/navigator/utils/debug.js +41 -0
- package/modules/sitemap/index.js +121 -0
- package/modules/sitemap/templates/sitemap-core.html +34 -0
- package/modules/{sitemap-core → sitemap}/templates/sitemap-index.html +2 -2
- package/modules.js +6 -0
- package/package.json +15 -6
- package/core/debug.js +0 -20
- package/core/filters.js +0 -9
- package/core/globals.js +0 -6
- package/core/helpers.js +0 -127
- package/core/modules.js +0 -22
- package/core/shortcodes.js +0 -3
- package/eleventy.config.js +0 -157
- package/modules/assets-core/plugins/assets-core.js +0 -84
- package/modules/assets-esbuild/filters/inline-esbuild.js +0 -24
- 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/modules/head-core/drivers/posthtml-head-elements.js +0 -132
- package/modules/head-core/plugins/head-core.js +0 -57
- package/modules/head-core/utils/head-utils.js +0 -183
- package/modules/multilang-core/plugins/multilang-core.js +0 -101
- package/modules/navigator-core/plugins/navigator-core.js +0 -39
- package/modules/sitemap-core/plugins/sitemap-core.js +0 -65
- package/modules/sitemap-core/templates/sitemap-core.html +0 -25
- /package/core/{globals → global-functions}/date.js +0 -0
- /package/modules/{assets-postcss/fallback → assets/configs}/postcss.config.js +0 -0
- /package/modules/{multilang-core → multilang}/filters/i18n-default-translation.js +0 -0
- /package/modules/{multilang-core → multilang}/filters/i18n-translation-in.js +0 -0
- /package/modules/{multilang-core → multilang}/filters/i18n-translations-for.js +0 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { I18nPlugin } from '@11ty/eleventy';
|
|
2
|
+
import { DeepCopy } from '@11ty/eleventy-utils';
|
|
3
|
+
import { normalizeLanguages } from '../../core/utils/helpers.js';
|
|
4
|
+
import i18nTranslationsFor from './filters/i18n-translations-for.js';
|
|
5
|
+
import i18nTranslationIn from './filters/i18n-translation-in.js';
|
|
6
|
+
import i18nDefaultTranslation from './filters/i18n-default-translation.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Multilang (module)
|
|
10
|
+
*
|
|
11
|
+
* Language infrastructure. Normalises language config, builds translation
|
|
12
|
+
* relationships, attaches per-page locale data, and exposes cross-language
|
|
13
|
+
* lookup filters. Active only when options.multilingual is true and both
|
|
14
|
+
* defaultLanguage and at least one languages entry are set; otherwise the
|
|
15
|
+
* module exits early.
|
|
16
|
+
*
|
|
17
|
+
* Architecture layer:
|
|
18
|
+
* module
|
|
19
|
+
*
|
|
20
|
+
* System role:
|
|
21
|
+
* Wraps Eleventy's I18nPlugin and feeds the translation-map store that
|
|
22
|
+
* head reads at transform-time. Sitemap reuses the same normalised
|
|
23
|
+
* language map.
|
|
24
|
+
*
|
|
25
|
+
* Lifecycle:
|
|
26
|
+
* build-time → normalise languages, attach I18nPlugin, register filters
|
|
27
|
+
* and computed page.locale
|
|
28
|
+
* cascade-time → translationsMap and translations collections build the
|
|
29
|
+
* per-translationKey map and write it to the store
|
|
30
|
+
*
|
|
31
|
+
* Why this exists:
|
|
32
|
+
* I18nPlugin handles locale-aware routing but not translation
|
|
33
|
+
* relationships. Head needs a transform-time-readable hreflang map; the
|
|
34
|
+
* collection populates it once and the store carries it across the
|
|
35
|
+
* lifecycle boundary.
|
|
36
|
+
*
|
|
37
|
+
* Scope:
|
|
38
|
+
* Owns language normalisation, page.locale computation, the translations
|
|
39
|
+
* and translationsMap collections, and the i18n filters
|
|
40
|
+
* (i18nTranslationsFor, i18nTranslationIn, i18nDefaultTranslation).
|
|
41
|
+
* Does not own URL routing (I18nPlugin) or hreflang rendering (head).
|
|
42
|
+
*
|
|
43
|
+
* Data flow:
|
|
44
|
+
* settings.languages + page.lang/translationKey → normalisation +
|
|
45
|
+
* I18nPlugin → collections + computed page.locale + translation-map
|
|
46
|
+
* store → head, sitemap
|
|
47
|
+
*
|
|
48
|
+
* @param {import("@11ty/eleventy/src/UserConfig.js").default} eleventyConfig
|
|
49
|
+
* @param {Object} moduleContext
|
|
50
|
+
*/
|
|
51
|
+
export function multilangCore(eleventyConfig, moduleContext) {
|
|
52
|
+
const { state, runtime, log } = moduleContext;
|
|
53
|
+
const { settings, options } = state;
|
|
54
|
+
|
|
55
|
+
// --- Language normalization ---
|
|
56
|
+
// Accept languages as array or object; normalize to object map.
|
|
57
|
+
// Drives collection building, locale data, and sitemap-core language config.
|
|
58
|
+
const normalizeLanguageCode = (lang) => (lang || '').toLowerCase().trim();
|
|
59
|
+
const defaultLanguage = normalizeLanguageCode(settings.defaultLanguage);
|
|
60
|
+
const languages = normalizeLanguages(settings, log);
|
|
61
|
+
const hasLanguages = languages && Object.keys(languages).length > 0;
|
|
62
|
+
|
|
63
|
+
const isMultilingual = options.multilang === true && defaultLanguage && hasLanguages;
|
|
64
|
+
|
|
65
|
+
if (!isMultilingual) {
|
|
66
|
+
log.info('inactive: requires options.multilingual + settings.defaultLanguage + languages');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Register Eleventy's built-in I18nPlugin for locale-aware URL resolution.
|
|
71
|
+
eleventyConfig.addPlugin(I18nPlugin, {
|
|
72
|
+
defaultLanguage: defaultLanguage,
|
|
73
|
+
errorMode: 'allow-fallback'
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Computed locale data: every page gets a page.locale object with its
|
|
77
|
+
// resolved lang, translationKey, and whether it's the default language.
|
|
78
|
+
eleventyConfig.addGlobalData('eleventyComputed.page.locale', () => {
|
|
79
|
+
return (data) => {
|
|
80
|
+
const translationKey = data.translationKey;
|
|
81
|
+
const lang = normalizeLanguageCode(data.lang || data.language || defaultLanguage);
|
|
82
|
+
const isDefaultLang = lang === defaultLanguage;
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
translationKey,
|
|
86
|
+
lang,
|
|
87
|
+
isDefaultLang
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Build a set of allowed language codes for validation during collection building.
|
|
93
|
+
const allowedLanguages = new Set(Object.keys(languages).map(normalizeLanguageCode));
|
|
94
|
+
|
|
95
|
+
// Build both the map (keyed by translationKey → lang) and the flat list.
|
|
96
|
+
// Shared logic for both collections — called once per collection registration.
|
|
97
|
+
const buildTranslations = (collection) => {
|
|
98
|
+
const map = {};
|
|
99
|
+
const list = [];
|
|
100
|
+
|
|
101
|
+
for (const page of collection.getAll()) {
|
|
102
|
+
const translationKey = page.data.translationKey;
|
|
103
|
+
if (!translationKey) continue;
|
|
104
|
+
|
|
105
|
+
const lang = page.data.lang || page.data.language || defaultLanguage;
|
|
106
|
+
if (!lang) continue;
|
|
107
|
+
|
|
108
|
+
if (allowedLanguages.size && !allowedLanguages.has(lang)) {
|
|
109
|
+
log.info(`Unknown lang "${lang}" in ${page.inputPath}`);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const locale = { locale: { translationKey, lang, isDefaultLang: lang === defaultLanguage } };
|
|
114
|
+
const safeCopy = DeepCopy(page, locale);
|
|
115
|
+
list.push(safeCopy);
|
|
116
|
+
|
|
117
|
+
if (!map[translationKey]) map[translationKey] = {};
|
|
118
|
+
map[translationKey][lang] = {
|
|
119
|
+
title: page.data.title,
|
|
120
|
+
url: page.url,
|
|
121
|
+
lang,
|
|
122
|
+
isDefaultLang: lang === defaultLanguage,
|
|
123
|
+
data: page.data
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return { map, list };
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// --- Collections ---
|
|
131
|
+
|
|
132
|
+
// Map form: translationsMap[translationKey][lang] → page metadata.
|
|
133
|
+
eleventyConfig.addCollection('translationsMap', (collection) => {
|
|
134
|
+
const map = buildTranslations(collection).map;
|
|
135
|
+
runtime.translationMap.set(map);
|
|
136
|
+
return map;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Flat list: all translatable pages with locale data attached.
|
|
140
|
+
eleventyConfig.addCollection('translations', (collection) => {
|
|
141
|
+
return buildTranslations(collection).list;
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// --- Filters ---
|
|
145
|
+
// Relational helpers for cross-language lookups in templates.
|
|
146
|
+
eleventyConfig.addFilter('i18nTranslationsFor', i18nTranslationsFor);
|
|
147
|
+
eleventyConfig.addFilter('i18nTranslationIn', i18nTranslationIn);
|
|
148
|
+
eleventyConfig.addFilter('i18nDefaultTranslation', i18nDefaultTranslation);
|
|
149
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import debug from './utils/debug.js';
|
|
5
|
+
import { optionsSchema } from './schema.js';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Navigator (module)
|
|
12
|
+
*
|
|
13
|
+
* Debug surface. Exposes Eleventy and Baseline runtime state to templates so
|
|
14
|
+
* developers can inspect data shape, scope contents, and lifecycle output
|
|
15
|
+
* without leaving the page.
|
|
16
|
+
*
|
|
17
|
+
* Architecture layer:
|
|
18
|
+
* module
|
|
19
|
+
*
|
|
20
|
+
* System role:
|
|
21
|
+
* Read-only window into the runtime substrate. Pulls snapshots from the
|
|
22
|
+
* page-context registry and content-map store via the module context;
|
|
23
|
+
* does not write back.
|
|
24
|
+
*
|
|
25
|
+
* Lifecycle:
|
|
26
|
+
* build-time → register Nunjucks globals, debug filters, and the
|
|
27
|
+
* optional virtual debug page
|
|
28
|
+
* cascade-time → eleventyComputed `_snapshot` resolves contentMap and
|
|
29
|
+
* pageContext on each page
|
|
30
|
+
*
|
|
31
|
+
* Why this exists:
|
|
32
|
+
* Render-time inspection of cascade state has no built-in surface.
|
|
33
|
+
* Centralising globals and filters under a debug-only module keeps the
|
|
34
|
+
* inspection vocabulary stable and out of feature modules.
|
|
35
|
+
*
|
|
36
|
+
* Scope:
|
|
37
|
+
* Owns the `_runtime` and `_ctx` Nunjucks globals, computed `_snapshot`,
|
|
38
|
+
* debug filters (`_inspect`, `_json`, `_keys`), and the optional virtual
|
|
39
|
+
* page at /navigator-core.html.
|
|
40
|
+
* Does not own the data it surfaces (page-context registry, content-map
|
|
41
|
+
* store).
|
|
42
|
+
*
|
|
43
|
+
* Data flow:
|
|
44
|
+
* snapshots (contentMap, pageContext) + this.ctx → globals + computed
|
|
45
|
+
* `_snapshot` + virtual page → developer
|
|
46
|
+
*
|
|
47
|
+
* Note: `_snapshot.contentMap` is null on the navigator template itself
|
|
48
|
+
* because it renders before `eleventy.contentMap` fires. Read `_snapshot`
|
|
49
|
+
* from any ordinary page for a populated contentMap.
|
|
50
|
+
*
|
|
51
|
+
* @param {import("@11ty/eleventy").UserConfig} eleventyConfig
|
|
52
|
+
* @param {Object} moduleContext
|
|
53
|
+
* @param {Object} moduleContext.state - Resolved plugin state.
|
|
54
|
+
* @param {Object} moduleContext.snapshots - Thunks: { contentMap, pageContext }.
|
|
55
|
+
*/
|
|
56
|
+
export function navigatorCore(eleventyConfig, moduleContext) {
|
|
57
|
+
const { state, snapshots, log, env } = moduleContext;
|
|
58
|
+
const { settings, options } = state;
|
|
59
|
+
|
|
60
|
+
// Structural-only options check: log on mismatch, do not throw.
|
|
61
|
+
const parsed = optionsSchema.safeParse(options.navigator);
|
|
62
|
+
if (!parsed.success) {
|
|
63
|
+
for (const issue of parsed.error.issues) {
|
|
64
|
+
log.info('options:', `${issue.path.join('.')} — ${issue.message}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Boolean shorthand activates the virtual page; object form lets users tune.
|
|
69
|
+
const navigatorOpts = options.navigator && typeof options.navigator === 'object' ? options.navigator : {};
|
|
70
|
+
const renderTemplate = env.mode === 'development' ?? navigatorOpts.template ?? Boolean(options.navigator);
|
|
71
|
+
const inspectorDepth = navigatorOpts.inspectorDepth ?? 4;
|
|
72
|
+
|
|
73
|
+
eleventyConfig.addGlobalData('eleventyComputed._snapshot', () => {
|
|
74
|
+
return () => ({
|
|
75
|
+
contentMap: snapshots.contentMap(),
|
|
76
|
+
pageContext: snapshots.pageContext()
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Nunjucks Global: _runtime
|
|
82
|
+
*
|
|
83
|
+
* Exposes internal Nunjucks runtime state:
|
|
84
|
+
* - env → environment instance
|
|
85
|
+
* - ctx → current render context
|
|
86
|
+
* - globals → registered global values
|
|
87
|
+
*/
|
|
88
|
+
eleventyConfig.addNunjucksGlobal('_runtime', function () {
|
|
89
|
+
return {
|
|
90
|
+
env: this.env,
|
|
91
|
+
ctx: this.ctx,
|
|
92
|
+
globals: this.env?.globals
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Nunjucks Global: _ctx
|
|
98
|
+
*
|
|
99
|
+
* Direct reference to the template execution context.
|
|
100
|
+
* Useful for debugging data shape at render time.
|
|
101
|
+
*/
|
|
102
|
+
eleventyConfig.addNunjucksGlobal('_ctx', function () {
|
|
103
|
+
return this.ctx;
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Virtual Debug Template
|
|
108
|
+
*
|
|
109
|
+
* Registers a synthetic Eleventy page that dumps runtime context.
|
|
110
|
+
* This is only enabled when explicitly configured via options.
|
|
111
|
+
*/
|
|
112
|
+
if (renderTemplate) {
|
|
113
|
+
const templatePath = path.join(__dirname, './templates/navigator-core.html');
|
|
114
|
+
const virtualTemplateContent = fs.readFileSync(templatePath, 'utf-8');
|
|
115
|
+
|
|
116
|
+
eleventyConfig.addTemplate('navigator-core.html', virtualTemplateContent, {
|
|
117
|
+
permalink: '/navigator-core.html',
|
|
118
|
+
title: 'Navigator Core',
|
|
119
|
+
description: 'Eleventy + Baseline internals',
|
|
120
|
+
layout: null,
|
|
121
|
+
eleventyExcludeFromCollections: true,
|
|
122
|
+
_internal: false,
|
|
123
|
+
|
|
124
|
+
// Debug control surface
|
|
125
|
+
inspectorDepth
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
log.info('Navigator template registered at /navigator-core.html');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Debug Filters
|
|
133
|
+
*
|
|
134
|
+
* Lightweight helpers for inspecting values in templates.
|
|
135
|
+
* These are intentionally prefixed to avoid collisions.
|
|
136
|
+
*/
|
|
137
|
+
eleventyConfig.addFilter('_inspect', debug.inspect);
|
|
138
|
+
eleventyConfig.addFilter('_json', debug.json);
|
|
139
|
+
eleventyConfig.addFilter('_keys', debug.keys);
|
|
140
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as z from 'zod';
|
|
2
|
+
|
|
3
|
+
// Structural schema for the `options.navigator` slice. Accepts boolean
|
|
4
|
+
// shorthand or object form; permissive on unknown keys, typed on the keys
|
|
5
|
+
// the module reads. Non-throwing at the call site.
|
|
6
|
+
|
|
7
|
+
export const optionsSchema = z.union([
|
|
8
|
+
z.boolean(),
|
|
9
|
+
z.looseObject({
|
|
10
|
+
template: z.boolean().optional(),
|
|
11
|
+
inspectorDepth: z.number().int().min(0).optional()
|
|
12
|
+
})
|
|
13
|
+
]).optional();
|
|
@@ -4,7 +4,7 @@ permalink: /navigator-core.html
|
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
<!DOCTYPE html>
|
|
7
|
-
<html lang="{{ lang | default(
|
|
7
|
+
<html lang="{{ lang | default(settings.defaultLanguage) }}">
|
|
8
8
|
|
|
9
9
|
<head>
|
|
10
10
|
<baseline-head></baseline-head>
|
|
@@ -28,9 +28,9 @@ permalink: /navigator-core.html
|
|
|
28
28
|
</a>
|
|
29
29
|
<header>
|
|
30
30
|
<div class="_navigator__wrapper">
|
|
31
|
-
<h1>{{
|
|
31
|
+
<h1>{{ settings.title }} Navigator</h1>
|
|
32
32
|
<div>
|
|
33
|
-
<p>{{
|
|
33
|
+
<p>{{ settings.tagline }}</p>
|
|
34
34
|
</div>
|
|
35
35
|
</div>
|
|
36
36
|
</header>
|
|
@@ -38,11 +38,17 @@ permalink: /navigator-core.html
|
|
|
38
38
|
<div class="_navigator__wrapper">
|
|
39
39
|
<p><a id="go-back" href=""><span style="vertical-align: text-bottom;">←</span> Go back</a></p>
|
|
40
40
|
<h2><u>Navigator</u></h2>
|
|
41
|
-
|
|
41
|
+
<details>
|
|
42
|
+
<summary><strong>Page Context</strong></summary>
|
|
43
|
+
<pre>
|
|
44
|
+
{{- _snapshot.pageContext | _inspect({ depth: null }) -}}
|
|
45
|
+
</pre>
|
|
46
|
+
</details>
|
|
47
|
+
{% for key, value in _runtime() %}
|
|
42
48
|
<details>
|
|
43
49
|
<summary><strong>{{ key }}</strong></summary>
|
|
44
50
|
{% if value | isString %}
|
|
45
|
-
<pre>{{ value
|
|
51
|
+
<pre>{{ value }}</pre>
|
|
46
52
|
{% else %}
|
|
47
53
|
<pre>{{ value | _inspect({ depth: inspectorDepth }) }}</pre>
|
|
48
54
|
{% endif %}
|
|
@@ -56,9 +62,9 @@ permalink: /navigator-core.html
|
|
|
56
62
|
history.back();
|
|
57
63
|
});
|
|
58
64
|
</script>
|
|
65
|
+
<footer>
|
|
66
|
+
<div class="_navigator__wrapper"><small>Navigator Core — Eleventy Plugin Baseline</small></div>
|
|
67
|
+
</footer>
|
|
59
68
|
</body>
|
|
60
|
-
<footer>
|
|
61
|
-
<div class="_navigator__wrapper"><small>Navigator Core — Eleventy Plugin Baseline</small></div>
|
|
62
|
-
</footer>
|
|
63
69
|
|
|
64
70
|
</html>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { inspect as utilInspect } from 'node:util';
|
|
2
|
+
|
|
3
|
+
// Adapted from pdehaan - https://github.com/pdehaan/eleventy-plugin-debug
|
|
4
|
+
const debugOptions = { space: 0 };
|
|
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
|
+
*/
|
|
12
|
+
function inspect(obj, options = {}) {
|
|
13
|
+
return utilInspect(obj, {
|
|
14
|
+
depth: 4,
|
|
15
|
+
maxArrayLength: 10,
|
|
16
|
+
breakLength: 80,
|
|
17
|
+
compact: true,
|
|
18
|
+
...options
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Serialize an object to JSON.
|
|
24
|
+
* @param {*} obj - Value to serialize.
|
|
25
|
+
* @param {number} [space] - Indentation level (default 0, compact).
|
|
26
|
+
* @returns {string}
|
|
27
|
+
*/
|
|
28
|
+
function json(obj, space = debugOptions.space) {
|
|
29
|
+
return JSON.stringify(obj, null, space);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Return an object's own keys, sorted alphabetically.
|
|
34
|
+
* @param {Object} obj
|
|
35
|
+
* @returns {string[]}
|
|
36
|
+
*/
|
|
37
|
+
function keys(obj) {
|
|
38
|
+
return Object.keys(obj).sort();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default { inspect, json, keys };
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { normalizeLanguages } from '../../core/utils/helpers.js';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Sitemap (module)
|
|
11
|
+
*
|
|
12
|
+
* Sitemap generation and per-page sitemap metadata. Layers on Eleventy
|
|
13
|
+
* collections rather than rendering independently. In multilingual mode it
|
|
14
|
+
* partitions per-language sitemaps and emits a sitemap index; otherwise it
|
|
15
|
+
* emits a single flat sitemap.
|
|
16
|
+
*
|
|
17
|
+
* Architecture layer:
|
|
18
|
+
* module
|
|
19
|
+
*
|
|
20
|
+
* System role:
|
|
21
|
+
* Reads the same normalised language map as multilang (via
|
|
22
|
+
* core/utils/helpers.js) and emits virtual templates that Eleventy
|
|
23
|
+
* renders to XML. Pages opt out via `noindex` in the cascade.
|
|
24
|
+
*
|
|
25
|
+
* Lifecycle:
|
|
26
|
+
* build-time → register virtual sitemap templates (single, per-lang,
|
|
27
|
+
* or index)
|
|
28
|
+
* cascade-time → eleventyComputed `page.sitemap` resolves ignore /
|
|
29
|
+
* changefreq / priority on each page
|
|
30
|
+
*
|
|
31
|
+
* Why this exists:
|
|
32
|
+
* Eleventy has no built-in sitemap. Multilingual sites also need
|
|
33
|
+
* partitioning plus an index, which only makes sense once language config
|
|
34
|
+
* is normalised the same way multilang sees it.
|
|
35
|
+
*
|
|
36
|
+
* Scope:
|
|
37
|
+
* Owns computed page.sitemap and the virtual sitemap templates
|
|
38
|
+
* (single-language /sitemap.xml, or per-lang /{lang}/sitemap.xml plus a
|
|
39
|
+
* /sitemap.xml index).
|
|
40
|
+
* Does not own language normalisation (core/utils/helpers.js) or noindex
|
|
41
|
+
* propagation through the cascade.
|
|
42
|
+
*
|
|
43
|
+
* Data flow:
|
|
44
|
+
* settings.languages + page data → computed page.sitemap + virtual
|
|
45
|
+
* templates → /sitemap.xml or per-language + index
|
|
46
|
+
*
|
|
47
|
+
* @param {import("@11ty/eleventy").UserConfig} eleventyConfig
|
|
48
|
+
* @param {Object} moduleContext
|
|
49
|
+
*/
|
|
50
|
+
export function sitemapCore(eleventyConfig, moduleContext) {
|
|
51
|
+
const { state, log } = moduleContext;
|
|
52
|
+
const { settings, options } = state;
|
|
53
|
+
|
|
54
|
+
// --- Language normalization ---
|
|
55
|
+
// Accept languages as array or object; normalize to object map.
|
|
56
|
+
// Drives collection building, locale data, and sitemap-core language config.
|
|
57
|
+
const normalizeLanguageCode = (lang) => (lang || '').toLowerCase().trim();
|
|
58
|
+
const defaultLanguage = normalizeLanguageCode(settings.defaultLanguage);
|
|
59
|
+
const languages = normalizeLanguages(settings, log);
|
|
60
|
+
const hasLanguages = languages && Object.keys(languages).length > 0;
|
|
61
|
+
const isMultilingual = options.multilang === true && defaultLanguage && hasLanguages;
|
|
62
|
+
|
|
63
|
+
// Computed sitemap data: every page gets a page.sitemap object.
|
|
64
|
+
// Pages set noindex in frontmatter or site data to be excluded.
|
|
65
|
+
eleventyConfig.addGlobalData('eleventyComputed.page.sitemap', () => {
|
|
66
|
+
return (data) => ({
|
|
67
|
+
ignore: data.noindex ?? data.page?.noindex ?? data.settings?.noindex ?? false,
|
|
68
|
+
changefreq: '',
|
|
69
|
+
priority: -1
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// --- Virtual sitemap templates ---
|
|
74
|
+
// Read template sources synchronously (same constraint as navigator-core).
|
|
75
|
+
// In multilingual mode: one sitemap per language + a sitemap index.
|
|
76
|
+
// In single-language mode: one flat sitemap at /sitemap.xml.
|
|
77
|
+
// Activation gate lives in the composition root via features.sitemap;
|
|
78
|
+
// the module is only registered when enabled.
|
|
79
|
+
const templatePath = path.join(__dirname, './templates/sitemap-core.html');
|
|
80
|
+
const indexTemplatePath = path.join(__dirname, './templates/sitemap-index.html');
|
|
81
|
+
const baseContent = fs.readFileSync(templatePath, 'utf-8');
|
|
82
|
+
const indexContent = fs.readFileSync(indexTemplatePath, 'utf-8');
|
|
83
|
+
|
|
84
|
+
const langKeys = Object.keys(languages || {});
|
|
85
|
+
const multilingual = isMultilingual;
|
|
86
|
+
|
|
87
|
+
if (multilingual && langKeys.length > 1) {
|
|
88
|
+
for (const lang of langKeys) {
|
|
89
|
+
eleventyConfig.addTemplate(`_baseline/sitemap-core-${lang}.html`, baseContent, {
|
|
90
|
+
permalink: `${lang}/sitemap.xml`,
|
|
91
|
+
title: '',
|
|
92
|
+
description: '',
|
|
93
|
+
layout: null,
|
|
94
|
+
eleventyExcludeFromCollections: true,
|
|
95
|
+
isMultilingual: multilingual,
|
|
96
|
+
sitemapLang: lang,
|
|
97
|
+
_internal: true
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
eleventyConfig.addTemplate('_baseline/sitemap-index.html', indexContent, {
|
|
102
|
+
permalink: '/sitemap.xml',
|
|
103
|
+
title: '',
|
|
104
|
+
description: '',
|
|
105
|
+
layout: null,
|
|
106
|
+
eleventyExcludeFromCollections: true,
|
|
107
|
+
isMultilingual: multilingual,
|
|
108
|
+
languages: languages,
|
|
109
|
+
_internal: true
|
|
110
|
+
});
|
|
111
|
+
} else {
|
|
112
|
+
eleventyConfig.addTemplate('_baseline/sitemap-core.html', baseContent, {
|
|
113
|
+
permalink: '/sitemap.xml',
|
|
114
|
+
title: '',
|
|
115
|
+
description: '',
|
|
116
|
+
layout: null,
|
|
117
|
+
eleventyExcludeFromCollections: true,
|
|
118
|
+
_internal: true
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
|
2
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">
|
|
3
|
+
{%- if not settings.noindex %}
|
|
4
|
+
{%- for item in collections.all %}
|
|
5
|
+
{%- if not item.data.eleventyExcludeFromCollections and (not item.data.sitemap or item.data.sitemap.ignore != true) and item.data.noindex != true %}
|
|
6
|
+
{%- set pageLang = item.data.lang or settings.defaultLanguage %}
|
|
7
|
+
{%- if (not isMultilingual) or (not sitemapLang) or (pageLang == sitemapLang) %}
|
|
8
|
+
{%- set absoluteUrl = item.url | htmlBaseUrl %}
|
|
9
|
+
{%- set lastmod = item.data.sitemap and item.data.sitemap.lastmod or item.date %}
|
|
10
|
+
<url>
|
|
11
|
+
<loc>{{ absoluteUrl }}</loc>
|
|
12
|
+
{%- if lastmod %}
|
|
13
|
+
<lastmod>{{ date.toUTCISO(lastmod) }}</lastmod>
|
|
14
|
+
{%- endif %}
|
|
15
|
+
{%- if item.data.sitemap and item.data.sitemap.changefreq %}
|
|
16
|
+
<changefreq>{{ item.data.sitemap.changefreq }}</changefreq>
|
|
17
|
+
{%- endif %}
|
|
18
|
+
{%- if item.data.sitemap and item.data.sitemap.priority is defined %}
|
|
19
|
+
<priority>{{ item.data.sitemap.priority }}</priority>
|
|
20
|
+
{%- endif %}
|
|
21
|
+
{%- if item.data.translationKey and collections.translationsMap and collections.translationsMap[item.data.translationKey] %}
|
|
22
|
+
{%- for language, entry in collections.translationsMap[item.data.translationKey] %}
|
|
23
|
+
<xhtml:link rel="alternate" hreflang="{{ entry.lang }}" href="{{ entry.url | htmlBaseUrl }}" />
|
|
24
|
+
{%- if entry.isDefaultLang %}
|
|
25
|
+
<xhtml:link rel="alternate" hreflang="x-default" href="{{ entry.url | htmlBaseUrl }}" />
|
|
26
|
+
{%- endif %}
|
|
27
|
+
{%- endfor %}
|
|
28
|
+
{%- endif %}
|
|
29
|
+
</url>
|
|
30
|
+
{%- endif %}
|
|
31
|
+
{%- endif %}
|
|
32
|
+
{%- endfor %}
|
|
33
|
+
{%- endif %}
|
|
34
|
+
</urlset>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
|
2
2
|
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
3
|
-
{%- if not
|
|
4
|
-
{%- set langs =
|
|
3
|
+
{%- if not settings.noindex %}
|
|
4
|
+
{%- set langs = languages %}
|
|
5
5
|
{%- if langs %}
|
|
6
6
|
{%- for lang, cfg in langs %}
|
|
7
7
|
<sitemap>
|
package/modules.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Modules barrel.
|
|
2
|
+
export { assetsCore } from './modules/assets/index.js';
|
|
3
|
+
export { headCore } from './modules/head/index.js';
|
|
4
|
+
export { multilangCore } from './modules/multilang/index.js';
|
|
5
|
+
export { navigatorCore } from './modules/navigator/index.js';
|
|
6
|
+
export { sitemapCore } from './modules/sitemap/index.js';
|
package/package.json
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apleasantview/eleventy-plugin-baseline",
|
|
3
|
-
"version": "0.1.0-next.
|
|
3
|
+
"version": "0.1.0-next.39",
|
|
4
4
|
"description": "An experimental Swiss army knife toolkit for Eleventy",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "eleventy.config.js",
|
|
7
6
|
"exports": {
|
|
8
|
-
".": "./
|
|
7
|
+
".": "./index.js",
|
|
9
8
|
"./package.json": "./package.json"
|
|
10
9
|
},
|
|
11
10
|
"files": [
|
|
12
|
-
"
|
|
11
|
+
"index.js",
|
|
12
|
+
"modules.js",
|
|
13
13
|
"core/**",
|
|
14
14
|
"modules/**",
|
|
15
15
|
"README.md",
|
|
16
|
-
"LICENSE"
|
|
16
|
+
"LICENSE",
|
|
17
|
+
"!**/__tests__/**",
|
|
18
|
+
"!**/*.test.js"
|
|
17
19
|
],
|
|
18
20
|
"engines": {
|
|
19
21
|
"node": ">=20"
|
|
@@ -38,14 +40,21 @@
|
|
|
38
40
|
"@11ty/eleventy-img": "^6.0.4"
|
|
39
41
|
},
|
|
40
42
|
"dependencies": {
|
|
43
|
+
"@11ty/eleventy-utils": "^2.0.7",
|
|
44
|
+
"@rviscomi/capo.js": "^2.1.0",
|
|
41
45
|
"cssnano": "^7.1.2",
|
|
42
46
|
"dotenv": "^17.2.3",
|
|
43
47
|
"esbuild": "0.27.0",
|
|
48
|
+
"kleur": "^4.1.5",
|
|
49
|
+
"luxon": "^3.7.2",
|
|
50
|
+
"markdown-it": "^14.1.1",
|
|
44
51
|
"postcss": "^8.5.6",
|
|
45
52
|
"postcss-import": "^16.1.1",
|
|
46
53
|
"postcss-import-ext-glob": "^2.1.1",
|
|
47
54
|
"postcss-load-config": "^6.0.1",
|
|
48
|
-
"postcss-preset-env": "^10.4.0"
|
|
55
|
+
"postcss-preset-env": "^10.4.0",
|
|
56
|
+
"slugify": "^1.6.6",
|
|
57
|
+
"zod": "^4.3.6"
|
|
49
58
|
},
|
|
50
59
|
"sideEffects": false
|
|
51
60
|
}
|
package/core/debug.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
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
DELETED