@apleasantview/eleventy-plugin-baseline 0.1.0-next.40 → 0.1.0-next.41
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 +21 -23
- package/core/back-compat/options.js +69 -0
- package/core/content-graph/backlinks.js +65 -0
- package/core/content-graph/extractors.js +140 -0
- package/core/content-graph/graph.js +118 -0
- package/core/content-graph/index.js +2 -0
- package/core/content-graph/prepass.js +121 -0
- package/core/logging/banner.js +49 -0
- package/core/{logging.js → logging/index.js} +19 -2
- package/core/logging/quips.js +30 -0
- package/core/markdown/auto-heading-ids.js +86 -0
- package/core/markdown/index.js +5 -0
- package/core/markdown/safe-use.js +42 -0
- package/core/{wikilinks.js → markdown/wikilinks.js} +3 -3
- package/core/page-context/build.js +239 -0
- package/core/page-context/index.js +1 -0
- package/core/page-context/register.js +73 -0
- package/core/page-context/seo-helpers.js +56 -0
- package/core/schema.js +19 -1
- package/core/slug-index.js +2 -2
- package/core/state.js +73 -0
- package/core/{shortcodes/image.js → surface/image-shortcode.js} +4 -4
- package/core/surface/index.js +22 -0
- package/core/utils/add-trailing-slash.js +11 -0
- package/core/utils/ensure-dot-slash-dir.js +13 -0
- package/core/utils/normalize-languages.js +28 -0
- package/core/utils/resolve-field.js +9 -0
- package/core/utils/resolve-subdir.js +20 -0
- package/core/utils/slugify.js +15 -0
- package/core/utils/unique-by.js +25 -0
- package/core/virtual-dir.js +11 -10
- package/index.js +152 -115
- package/modules/assets/index.js +4 -2
- package/modules/assets/processors/esbuild-process.js +2 -2
- package/modules/assets/processors/postcss-process.js +2 -2
- package/modules/head/drivers/posthtml-head-elements.js +1 -3
- package/modules/head/index.js +7 -10
- package/modules/multilang/index.js +4 -2
- package/modules/navigator/index.js +33 -20
- package/modules/navigator/templates/navigator-core.html +1 -1
- package/modules/sitemap/index.js +7 -3
- package/package.json +4 -2
- package/core/filters/index.js +0 -4
- package/core/global-functions/index.js +0 -6
- package/core/page-context.js +0 -310
- package/core/shortcodes/index.js +0 -2
- package/core/utils/helpers.js +0 -75
- /package/core/{filters/markdown.js → markdown/markdownify.js} +0 -0
- /package/core/{filters → surface/filters}/isString.js +0 -0
- /package/core/{filters → surface/filters}/related-posts.js +0 -0
- /package/core/{global-functions/date.js → surface/global-date-function.js} +0 -0
package/index.js
CHANGED
|
@@ -3,36 +3,40 @@ import { createRequire } from 'node:module';
|
|
|
3
3
|
|
|
4
4
|
import { HtmlBasePlugin } from '@11ty/eleventy';
|
|
5
5
|
import { eleventyImageOnRequestDuringServePlugin } from '@11ty/eleventy-img';
|
|
6
|
+
import markdownItAttrs from 'markdown-it-attrs';
|
|
6
7
|
|
|
7
|
-
import { createLogger } from './core/logging.js';
|
|
8
|
+
import { createLogger, printBannerOnce } from './core/logging/index.js';
|
|
9
|
+
import { isLegacyShape, normalizeLegacyShape } from './core/back-compat/options.js';
|
|
10
|
+
import { settingsSchema } from './core/schema.js';
|
|
11
|
+
import { deriveBaselineState } from './core/state.js';
|
|
12
|
+
import { runPrepass, PREPASS_SENTINEL } from './core/content-graph/index.js';
|
|
13
|
+
import { registerVirtualDir } from './core/virtual-dir.js';
|
|
8
14
|
import { createContentMapStore } from './core/content-map-store.js';
|
|
9
15
|
import { createTranslationMapStore } from './core/translation-map-store.js';
|
|
10
16
|
import { createSlugIndex } from './core/slug-index.js';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { settingsSchema } from './core/schema.js';
|
|
15
|
-
|
|
16
|
-
import { registerGlobals } from './core/global-functions/index.js';
|
|
17
|
-
import { markdownFilter, relatedPostsFilter, isStringFilter } from './core/filters/index.js';
|
|
18
|
-
import { imageShortcode } from './core/shortcodes/index.js';
|
|
17
|
+
import { registerPageContext } from './core/page-context/index.js';
|
|
18
|
+
import { autoHeadingIds, safeUse, wikilinks } from './core/markdown/index.js';
|
|
19
|
+
import { slugify } from './core/utils/slugify.js';
|
|
19
20
|
import { assetsCore, headCore, multilangCore, navigatorCore, sitemapCore } from './modules.js';
|
|
21
|
+
import {
|
|
22
|
+
registerGlobals,
|
|
23
|
+
markdownFilter,
|
|
24
|
+
relatedPostsFilter,
|
|
25
|
+
isStringFilter,
|
|
26
|
+
imageShortcode
|
|
27
|
+
} from './core/surface/index.js';
|
|
20
28
|
|
|
21
29
|
const __require = createRequire(import.meta.url);
|
|
22
30
|
const { name, version } = __require('./package.json');
|
|
31
|
+
const eleventyVersion = process.env.ELEVENTY_VERSION;
|
|
32
|
+
// const absoluteRoot = process.env.ELEVENTY_ROOT; -> Safekeeping.
|
|
23
33
|
|
|
24
34
|
const mode = process.env.ELEVENTY_ENV;
|
|
35
|
+
// eslint-disable-next-line no-unused-vars
|
|
25
36
|
const isDev = mode === 'development';
|
|
37
|
+
// eslint-disable-next-line no-unused-vars
|
|
26
38
|
const isProd = mode === 'production';
|
|
27
39
|
|
|
28
|
-
const LEGACY_OPTION_KEYS = [
|
|
29
|
-
'verbose',
|
|
30
|
-
'enableNavigatorTemplate',
|
|
31
|
-
'enableSitemapTemplate',
|
|
32
|
-
'assetsESBuild',
|
|
33
|
-
'multilingual'
|
|
34
|
-
];
|
|
35
|
-
|
|
36
40
|
// Whitelist of reserved global data keys used internally across the plugin.
|
|
37
41
|
// Positive side effect is they all get listed in order and merge data to the same key.
|
|
38
42
|
// Also prevents name collision with filters.
|
|
@@ -44,37 +48,19 @@ const INTERNAL_KEYS = [
|
|
|
44
48
|
'_navigator',
|
|
45
49
|
'_sitemap',
|
|
46
50
|
'_snapshot',
|
|
47
|
-
'_pageContext'
|
|
51
|
+
'eleventyComputed._pageContext',
|
|
52
|
+
'eleventyComputed._node',
|
|
53
|
+
'eleventyComputed._edges',
|
|
54
|
+
'eleventyComputed._backlinks',
|
|
55
|
+
'eleventyComputed._outgoing'
|
|
48
56
|
];
|
|
49
57
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
*
|
|
53
|
-
* The original plugin API accepted a single merged configuration object.
|
|
54
|
-
* This helper detects that shape and enables safe normalization into
|
|
55
|
-
* the current (settings, options) contract.
|
|
56
|
-
*
|
|
57
|
-
* NOTE: arguments.length is required because default parameters mask arity.
|
|
58
|
-
*/
|
|
59
|
-
function looksLikeLegacyOptions(firstArg, argsLength) {
|
|
60
|
-
if (argsLength >= 2) return false;
|
|
61
|
-
if (!firstArg || typeof firstArg !== 'object') return false;
|
|
62
|
-
return LEGACY_OPTION_KEYS.some((key) => key in firstArg);
|
|
63
|
-
}
|
|
58
|
+
// Base logger outputs regardless of options.
|
|
59
|
+
const baseLog = createLogger(null, { verbose: true });
|
|
64
60
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
* - settings → site identity (content + SEO concerns)
|
|
69
|
-
* - options → runtime behavior flags
|
|
70
|
-
*/
|
|
71
|
-
function splitLegacyOptions(legacy) {
|
|
72
|
-
const { defaultLanguage, languages, ...rest } = legacy;
|
|
73
|
-
return {
|
|
74
|
-
settings: { defaultLanguage, languages },
|
|
75
|
-
options: rest
|
|
76
|
-
};
|
|
77
|
-
}
|
|
61
|
+
printBannerOnce(baseLog, { version, eleventyVersion });
|
|
62
|
+
|
|
63
|
+
let contentGraph = null;
|
|
78
64
|
|
|
79
65
|
/**
|
|
80
66
|
* Baseline (composition root)
|
|
@@ -118,36 +104,29 @@ function splitLegacyOptions(legacy) {
|
|
|
118
104
|
*/
|
|
119
105
|
export default function baseline(settings = {}, options = {}) {
|
|
120
106
|
// --- Legacy compatibility layer ---
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
settings = split.settings;
|
|
127
|
-
options = split.options;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Base logger outputs regardless of options.
|
|
131
|
-
const baseLog = createLogger(null, { verbose: true });
|
|
132
|
-
|
|
133
|
-
// Scoped logging.
|
|
134
|
-
function scopedLog(name) {
|
|
135
|
-
return createLogger(name, { verbose: options.verbose });
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (wasLegacy) {
|
|
139
|
-
baseLog.info('DEPRECATED: single-object plugin arg. Use baseline(settings, options) instead.');
|
|
107
|
+
if (isLegacyShape(settings, arguments.length)) {
|
|
108
|
+
const normalized = normalizeLegacyShape(settings);
|
|
109
|
+
settings = normalized.settings;
|
|
110
|
+
options = normalized.options;
|
|
111
|
+
baseLog.info('Single-object plugin arg is deprecated. Use baseline(settings, options).');
|
|
140
112
|
}
|
|
141
113
|
|
|
142
114
|
// Validate configuration shape (non-fatal).
|
|
143
115
|
const parsed = settingsSchema.safeParse(settings);
|
|
144
116
|
if (!parsed.success) {
|
|
145
117
|
for (const issue of parsed.error.issues) {
|
|
146
|
-
baseLog.info('settings:', `${issue.path.join('.')}
|
|
118
|
+
baseLog.info('settings:', `${issue.path.join('.')}, ${issue.message}`);
|
|
147
119
|
}
|
|
148
120
|
}
|
|
149
121
|
|
|
150
|
-
|
|
122
|
+
// Resolve state once, above the closure. Pure; no eleventyConfig.
|
|
123
|
+
const state = deriveBaselineState(settings, options, { mode });
|
|
124
|
+
baseLog.info('Settings and options resolved, modules loaded');
|
|
125
|
+
|
|
126
|
+
// Scoped logging.
|
|
127
|
+
function scopedLog(name) {
|
|
128
|
+
return createLogger(name, { verbose: state.options.verbose });
|
|
129
|
+
}
|
|
151
130
|
|
|
152
131
|
/**
|
|
153
132
|
* Eleventy plugin initializer.
|
|
@@ -160,26 +139,69 @@ export default function baseline(settings = {}, options = {}) {
|
|
|
160
139
|
try {
|
|
161
140
|
eleventyConfig.versionCheck('>=3.0');
|
|
162
141
|
} catch (e) {
|
|
163
|
-
baseLog.error('Eleventy version mismatch
|
|
142
|
+
baseLog.error('Eleventy version mismatch.', e.message);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// --- Pre-pass wiring ---
|
|
146
|
+
// One mechanic: the pre-pass runs at the start of every Eleventy
|
|
147
|
+
// build cycle via `eleventy.before`. Initial build, watch rebuild,
|
|
148
|
+
// production build — all the same path. Templates always render
|
|
149
|
+
// against a graph rebuilt from current source. The sentinel keeps
|
|
150
|
+
// the inner Eleventy from re-attaching the hook on re-entry.
|
|
151
|
+
if (process.env[PREPASS_SENTINEL] !== '1') {
|
|
152
|
+
const prepassLog = scopedLog('pre-pass');
|
|
153
|
+
|
|
154
|
+
// Origins HtmlBasePlugin may have rewritten internal hrefs to.
|
|
155
|
+
// Stripped during link extraction so backlinks key on path-only.
|
|
156
|
+
const knownOrigins = new Set(['http://localhost:8080']);
|
|
157
|
+
for (const candidate of [settings.url, process.env.URL]) {
|
|
158
|
+
if (!candidate) continue;
|
|
159
|
+
try {
|
|
160
|
+
knownOrigins.add(new URL(candidate).origin);
|
|
161
|
+
} catch {
|
|
162
|
+
prepassLog.info('No known origins, using localhost only');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
eleventyConfig.on('eleventy.before', async () => {
|
|
167
|
+
contentGraph = await runPrepass(
|
|
168
|
+
eleventyConfig.directories?.input,
|
|
169
|
+
eleventyConfig.directories?.output,
|
|
170
|
+
scopedLog,
|
|
171
|
+
{ quietMode: true, knownOrigins }
|
|
172
|
+
);
|
|
173
|
+
});
|
|
164
174
|
}
|
|
165
175
|
|
|
166
176
|
INTERNAL_KEYS.forEach((key) => {
|
|
177
|
+
// We leave eleventyComputed callback kEys alone, the rest are reserved-empty.
|
|
178
|
+
if (
|
|
179
|
+
key === 'eleventyComputed._pageContext' ||
|
|
180
|
+
key === 'eleventyComputed._node' ||
|
|
181
|
+
key === 'eleventyComputed._edges' ||
|
|
182
|
+
key === 'eleventyComputed._backlinks' ||
|
|
183
|
+
key === 'eleventyComputed._outgoing'
|
|
184
|
+
)
|
|
185
|
+
return;
|
|
167
186
|
eleventyConfig.addGlobalData(key, {});
|
|
168
187
|
});
|
|
169
188
|
|
|
170
189
|
const env = {
|
|
171
|
-
name: 'Eleventy Baseline',
|
|
172
|
-
package: name,
|
|
173
190
|
version,
|
|
174
|
-
|
|
191
|
+
name: 'Eleventy Baseline',
|
|
192
|
+
env: {
|
|
193
|
+
mode,
|
|
194
|
+
package: name
|
|
195
|
+
}
|
|
175
196
|
};
|
|
176
197
|
|
|
177
198
|
eleventyConfig.addGlobalData('_baseline', {
|
|
178
|
-
env
|
|
199
|
+
...env,
|
|
200
|
+
options: state.options
|
|
179
201
|
});
|
|
180
202
|
|
|
181
203
|
if (!settings.url) {
|
|
182
|
-
baseLog.warn('settings.url missing
|
|
204
|
+
baseLog.warn('settings.url missing, canonical URLs will be relative');
|
|
183
205
|
}
|
|
184
206
|
|
|
185
207
|
registerGlobals(eleventyConfig);
|
|
@@ -188,38 +210,12 @@ export default function baseline(settings = {}, options = {}) {
|
|
|
188
210
|
baseHref: process.env.URL || eleventyConfig.pathPrefix
|
|
189
211
|
});
|
|
190
212
|
|
|
191
|
-
// ---
|
|
213
|
+
// --- Feature exposure to templates ---
|
|
192
214
|
const hasImageTransformPlugin = eleventyConfig.hasPlugin('eleventyImageTransformPlugin');
|
|
193
215
|
|
|
194
|
-
const state = {
|
|
195
|
-
settings: {
|
|
196
|
-
title: settings.title,
|
|
197
|
-
tagline: settings.tagline,
|
|
198
|
-
url: settings.url,
|
|
199
|
-
noindex: settings.noindex ?? false,
|
|
200
|
-
defaultLanguage: settings.defaultLanguage,
|
|
201
|
-
languages: settings.languages,
|
|
202
|
-
head: settings.head
|
|
203
|
-
},
|
|
204
|
-
|
|
205
|
-
options: {
|
|
206
|
-
verbose: options.verbose ?? false,
|
|
207
|
-
multilang: options.multilingual ?? false,
|
|
208
|
-
sitemap: options.sitemap ?? options.enableSitemapTemplate ?? true,
|
|
209
|
-
navigator: options.navigator ?? options.enableNavigatorTemplate ?? isDev,
|
|
210
|
-
head: {
|
|
211
|
-
titleSeparator: options.head?.titleSeparator,
|
|
212
|
-
showGenerator: options.head?.showGenerator
|
|
213
|
-
},
|
|
214
|
-
assets: {
|
|
215
|
-
esbuild: options.assets?.esbuild ?? options.assetsESBuild ?? {}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
|
|
220
216
|
eleventyConfig.addGlobalData('_baseline', {
|
|
221
217
|
features: {
|
|
222
|
-
...state.
|
|
218
|
+
...state.features,
|
|
223
219
|
hasImageTransformPlugin
|
|
224
220
|
}
|
|
225
221
|
});
|
|
@@ -234,6 +230,9 @@ export default function baseline(settings = {}, options = {}) {
|
|
|
234
230
|
outputDir: ''
|
|
235
231
|
});
|
|
236
232
|
|
|
233
|
+
const virtualDirLog = scopedLog('virtual-dir');
|
|
234
|
+
virtualDirLog.info('Virtual directories mounted');
|
|
235
|
+
|
|
237
236
|
const directories = {
|
|
238
237
|
input: eleventyConfig.directories?.input,
|
|
239
238
|
output: eleventyConfig.directories?.output,
|
|
@@ -277,6 +276,9 @@ export default function baseline(settings = {}, options = {}) {
|
|
|
277
276
|
get contentMap() {
|
|
278
277
|
return contentMapStore.get();
|
|
279
278
|
},
|
|
279
|
+
get contentGraph() {
|
|
280
|
+
return contentGraph;
|
|
281
|
+
},
|
|
280
282
|
translationMap: translationMapStore,
|
|
281
283
|
slugIndex
|
|
282
284
|
},
|
|
@@ -287,34 +289,68 @@ export default function baseline(settings = {}, options = {}) {
|
|
|
287
289
|
// Page context registry
|
|
288
290
|
const pageContextRegistry = registerPageContext(eleventyConfig, coreContext);
|
|
289
291
|
|
|
290
|
-
//
|
|
292
|
+
// --- Content graph ---
|
|
293
|
+
// Cascade hookup for the content graph. Reads via the runtime getter so
|
|
294
|
+
// serve-mode rebuilds reassigning `contentGraph` are picked up.
|
|
295
|
+
function getNode(pageUrl) {
|
|
296
|
+
return coreContext.runtime.contentGraph?.nodes?.[pageUrl];
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function getEdges() {
|
|
300
|
+
return coreContext.runtime.contentGraph?.edges ?? [];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
eleventyConfig.addGlobalData('eleventyComputed._node', () => (data) => {
|
|
304
|
+
const pageUrl = data.page?.url;
|
|
305
|
+
if (!pageUrl) return undefined;
|
|
306
|
+
|
|
307
|
+
return getNode(pageUrl);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
eleventyConfig.addGlobalData('eleventyComputed._backlinks', () => (data) => {
|
|
311
|
+
const edges = getEdges();
|
|
312
|
+
|
|
313
|
+
const pageUrl = data.page?.url;
|
|
314
|
+
if (!pageUrl) return [];
|
|
315
|
+
|
|
316
|
+
return edges.filter((edge) => edge.to === pageUrl);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
eleventyConfig.addGlobalData('eleventyComputed._outgoing', () => (data) => {
|
|
320
|
+
const edges = getEdges();
|
|
321
|
+
|
|
322
|
+
const pageUrl = data.page?.url;
|
|
323
|
+
if (!pageUrl) return [];
|
|
324
|
+
|
|
325
|
+
return edges.filter((edge) => edge.from === pageUrl);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// --- Markdown engine ---
|
|
329
|
+
// Order matters: attrs first so manual ids are visible to auto-heading-ids'
|
|
330
|
+
// seed pass; wikilinks last since it parses inline tokens independently.
|
|
331
|
+
const mdLog = scopedLog('markdown');
|
|
291
332
|
eleventyConfig.amendLibrary('md', (md) => {
|
|
292
|
-
md
|
|
333
|
+
safeUse(md, 'curly_attributes', markdownItAttrs, undefined, mdLog);
|
|
334
|
+
safeUse(md, 'baseline_auto_heading_ids', autoHeadingIds, { slugify }, mdLog);
|
|
335
|
+
safeUse(md, 'baseline_wikilinks', wikilinks, { slugIndex, pageContextRegistry, translationMapStore }, mdLog);
|
|
293
336
|
});
|
|
294
337
|
|
|
338
|
+
// --- Snapshots ---
|
|
295
339
|
coreContext.snapshots = {
|
|
296
340
|
contentMap: () => contentMapStore.snapshot(),
|
|
297
341
|
pageContext: () => pageContextRegistry.snapshot()
|
|
298
342
|
};
|
|
299
343
|
|
|
300
|
-
// --- Module activation ---
|
|
301
|
-
const features = {
|
|
302
|
-
multilang: Boolean(state.options.multilang),
|
|
303
|
-
sitemap: Boolean(state.options.sitemap),
|
|
304
|
-
navigator: Boolean(state.options.navigator),
|
|
305
|
-
head: true,
|
|
306
|
-
assets: true
|
|
307
|
-
};
|
|
308
|
-
|
|
309
344
|
// --- Module registry ---
|
|
310
345
|
const moduleRegistry = [
|
|
311
|
-
{ when: features.multilang, name: 'multilang', plugin: multilangCore },
|
|
312
|
-
{ when: features.sitemap, name: 'sitemap', plugin: sitemapCore },
|
|
346
|
+
{ when: state.features.multilang, name: 'multilang', plugin: multilangCore },
|
|
347
|
+
{ when: state.features.sitemap, name: 'sitemap', plugin: sitemapCore },
|
|
313
348
|
{ name: 'navigator', plugin: navigatorCore },
|
|
314
|
-
{ when: features.head, name: 'head', plugin: headCore, consumes: { pageContext: true } },
|
|
315
|
-
{ when: features.assets, name: 'assets', plugin: assetsCore }
|
|
349
|
+
{ when: state.features.head, name: 'head', plugin: headCore, consumes: { pageContext: true } },
|
|
350
|
+
{ when: state.features.assets, name: 'assets', plugin: assetsCore }
|
|
316
351
|
];
|
|
317
352
|
|
|
353
|
+
const active = [];
|
|
318
354
|
for (const entry of moduleRegistry) {
|
|
319
355
|
const { when = true, name, plugin, consumes = {} } = entry;
|
|
320
356
|
if (!when) continue;
|
|
@@ -325,6 +361,7 @@ export default function baseline(settings = {}, options = {}) {
|
|
|
325
361
|
};
|
|
326
362
|
|
|
327
363
|
eleventyConfig.addPlugin(plugin, moduleContext);
|
|
364
|
+
active.push(name);
|
|
328
365
|
}
|
|
329
366
|
|
|
330
367
|
// --- Filters ---
|
package/modules/assets/index.js
CHANGED
|
@@ -49,7 +49,7 @@ export function assetsCore(eleventyConfig, moduleContext) {
|
|
|
49
49
|
const parsed = optionsSchema.safeParse(options.assets);
|
|
50
50
|
if (!parsed.success) {
|
|
51
51
|
for (const issue of parsed.error.issues) {
|
|
52
|
-
log.info('options:', `${issue.path.join('.')}
|
|
52
|
+
log.info('options:', `${issue.path.join('.')}, ${issue.message}`);
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -63,13 +63,15 @@ export function assetsCore(eleventyConfig, moduleContext) {
|
|
|
63
63
|
const watchGlob = TemplatePath.join(assetsDirectory, '**/*.{css,js,svg,png,jpeg,jpg,webp,gif,avif}');
|
|
64
64
|
|
|
65
65
|
if (!assetsDirectory) {
|
|
66
|
-
log.warn('
|
|
66
|
+
log.warn('directories.assets is unset, registerVirtualDir must run before this plugin');
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
// Watch common asset formats so edits trigger reloads during --serve.
|
|
71
71
|
eleventyConfig.addWatchTarget(watchGlob);
|
|
72
72
|
|
|
73
|
+
log.info('Assets pipeline registered');
|
|
74
|
+
|
|
73
75
|
// --- JS (esbuild) ---
|
|
74
76
|
// Register js as a template format. Only index.js files under assets/js/
|
|
75
77
|
// are compiled; everything else (11tydata.js, non-entry scripts) is skipped
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as esbuild from 'esbuild';
|
|
2
|
-
import { createLogger } from '../../../core/logging.js';
|
|
2
|
+
import { createLogger } from '../../../core/logging/index.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* esbuild processor (processor)
|
|
@@ -61,7 +61,7 @@ export default async function assetsESbuild(jsFilePath, options = {}) {
|
|
|
61
61
|
// Return raw JS; markup wrapping is handled by the plugin registration.
|
|
62
62
|
return result.outputFiles[0].text;
|
|
63
63
|
} catch (error) {
|
|
64
|
-
log.error('esbuild failed
|
|
64
|
+
log.error('esbuild failed.', error);
|
|
65
65
|
// Surface a safe JS comment so the caller can decide how to wrap it.
|
|
66
66
|
return '/* Error processing JS */';
|
|
67
67
|
}
|
|
@@ -2,7 +2,7 @@ import fs from 'fs/promises';
|
|
|
2
2
|
import postcss from 'postcss';
|
|
3
3
|
import loadPostCSSConfig from 'postcss-load-config';
|
|
4
4
|
import fallbackPostCSSConfig from '../configs/postcss.config.js';
|
|
5
|
-
import { createLogger } from '../../../core/logging.js';
|
|
5
|
+
import { createLogger } from '../../../core/logging/index.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* PostCSS processor (processor)
|
|
@@ -79,7 +79,7 @@ export default async function assetsPostCSS(cssFilePath) {
|
|
|
79
79
|
// Return raw CSS; markup wrapping is handled in the plugin registration.
|
|
80
80
|
return result.css;
|
|
81
81
|
} catch (error) {
|
|
82
|
-
log.error('PostCSS failed
|
|
82
|
+
log.error('PostCSS failed.', error);
|
|
83
83
|
// Surface a safe CSS string so the caller can decide how to wrap it.
|
|
84
84
|
return '/* Error processing CSS */';
|
|
85
85
|
}
|
|
@@ -41,10 +41,9 @@ import { dedupeMeta, dedupeLink } from '../utils/dedupe.js';
|
|
|
41
41
|
* @param {Object} args.options - Head options (titleSeparator, showGenerator).
|
|
42
42
|
* @param {string} args.placeholderTag - Placeholder element to replace.
|
|
43
43
|
* @param {string} args.eol - End-of-line separator interleaved between nodes.
|
|
44
|
-
* @param {Object} args.log - Scoped logger.
|
|
45
44
|
* @returns {(tree: Object) => Object} PostHTML plugin function.
|
|
46
45
|
*/
|
|
47
|
-
export function renderHead({ seeds, alternates, options, placeholderTag, eol
|
|
46
|
+
export function renderHead({ seeds, alternates, options, placeholderTag, eol }) {
|
|
48
47
|
const defaults = emitMeta(seeds.meta, seeds.render, options);
|
|
49
48
|
const extras = emitExtras(seeds.head, alternates);
|
|
50
49
|
|
|
@@ -52,7 +51,6 @@ export function renderHead({ seeds, alternates, options, placeholderTag, eol, lo
|
|
|
52
51
|
const sorted = capoSort(deduped);
|
|
53
52
|
|
|
54
53
|
return function rendererPlugin(tree) {
|
|
55
|
-
// log.info('injecting head for', seeds.page.inputPath || seeds.page.url);
|
|
56
54
|
tree.match({ tag: placeholderTag }, () => ({
|
|
57
55
|
tag: 'head',
|
|
58
56
|
content: interleaveEOL(sorted, eol)
|
package/modules/head/index.js
CHANGED
|
@@ -2,6 +2,8 @@ import { renderHead } from './drivers/posthtml-head-elements.js';
|
|
|
2
2
|
import { buildAlternates } from './utils/alternates.js';
|
|
3
3
|
import { optionsSchema } from './schema.js';
|
|
4
4
|
|
|
5
|
+
import chalk from 'kleur';
|
|
6
|
+
|
|
5
7
|
// Internal constants — not user-facing.
|
|
6
8
|
const PLACEHOLDER_TAG = 'baseline-head';
|
|
7
9
|
const EOL = '\n';
|
|
@@ -52,7 +54,7 @@ export function headCore(eleventyConfig, moduleContext) {
|
|
|
52
54
|
const parsed = optionsSchema.safeParse(options.head);
|
|
53
55
|
if (!parsed.success) {
|
|
54
56
|
for (const issue of parsed.error.issues) {
|
|
55
|
-
log.info('options:', `${issue.path.join('.')}
|
|
57
|
+
log.info('options:', `${issue.path.join('.')}, ${issue.message}`);
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
60
|
|
|
@@ -68,23 +70,19 @@ export function headCore(eleventyConfig, moduleContext) {
|
|
|
68
70
|
const headStats = { pages: new Set() };
|
|
69
71
|
|
|
70
72
|
eleventyConfig.on('eleventy.after', () => {
|
|
71
|
-
log.info({
|
|
72
|
-
message: 'Head injection summary',
|
|
73
|
-
totalPages: headStats.pages.size,
|
|
74
|
-
sample: Array.from(headStats.pages).slice(0, 10)
|
|
75
|
-
});
|
|
73
|
+
log.info(chalk.green(`Head injected into ${headStats.pages.size} pages`));
|
|
76
74
|
headStats.pages.clear();
|
|
77
75
|
});
|
|
78
76
|
|
|
79
77
|
// --- Transform-time: compose and inject. ---
|
|
80
|
-
log.info('Injecting heads
|
|
78
|
+
log.info('Injecting heads');
|
|
81
79
|
eleventyConfig.htmlTransformer.addPosthtmlPlugin('html', function (context) {
|
|
82
80
|
headStats.pages.add(context?.page?.inputPath || context?.outputPath);
|
|
83
81
|
|
|
84
82
|
const key = context?.page?.url ?? context?.page?.inputPath;
|
|
85
83
|
const seeds = pageContextRegistry?.getByKey(key);
|
|
86
84
|
if (!seeds) {
|
|
87
|
-
log.warn('
|
|
85
|
+
log.warn('No head seeds for', context?.page?.inputPath || context?.outputPath);
|
|
88
86
|
return (tree) => tree;
|
|
89
87
|
}
|
|
90
88
|
|
|
@@ -99,8 +97,7 @@ export function headCore(eleventyConfig, moduleContext) {
|
|
|
99
97
|
alternates,
|
|
100
98
|
options: headOptions,
|
|
101
99
|
placeholderTag: PLACEHOLDER_TAG,
|
|
102
|
-
eol: EOL
|
|
103
|
-
log
|
|
100
|
+
eol: EOL
|
|
104
101
|
});
|
|
105
102
|
});
|
|
106
103
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { I18nPlugin } from '@11ty/eleventy';
|
|
2
2
|
import { DeepCopy } from '@11ty/eleventy-utils';
|
|
3
|
-
import { normalizeLanguages } from '../../core/utils/
|
|
3
|
+
import { normalizeLanguages } from '../../core/utils/normalize-languages.js';
|
|
4
4
|
import i18nTranslationsFor from './filters/i18n-translations-for.js';
|
|
5
5
|
import i18nTranslationIn from './filters/i18n-translation-in.js';
|
|
6
6
|
import i18nDefaultTranslation from './filters/i18n-default-translation.js';
|
|
@@ -63,10 +63,12 @@ export function multilangCore(eleventyConfig, moduleContext) {
|
|
|
63
63
|
const isMultilingual = options.multilang === true && defaultLanguage && hasLanguages;
|
|
64
64
|
|
|
65
65
|
if (!isMultilingual) {
|
|
66
|
-
log.info('inactive
|
|
66
|
+
log.info('Multilang inactive, needs options.multilang, settings.defaultLanguage, and languages');
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
log.info(`Multilang active: ${Object.keys(languages).join('/')} (default: ${defaultLanguage})`);
|
|
71
|
+
|
|
70
72
|
// Register Eleventy's built-in I18nPlugin for locale-aware URL resolution.
|
|
71
73
|
eleventyConfig.addPlugin(I18nPlugin, {
|
|
72
74
|
defaultLanguage: defaultLanguage,
|
|
@@ -10,39 +10,42 @@ const __dirname = path.dirname(__filename);
|
|
|
10
10
|
/**
|
|
11
11
|
* Navigator (module)
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* Two roles, one module: the public read surface for plugin-produced
|
|
14
|
+
* cross-page data (the content graph and its enriched backlinks), and the
|
|
15
|
+
* debug surface for inspecting Eleventy and Baseline runtime state.
|
|
16
16
|
*
|
|
17
17
|
* Architecture layer:
|
|
18
18
|
* module
|
|
19
19
|
*
|
|
20
20
|
* System role:
|
|
21
|
-
* Read-only window
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* Read-only window over the runtime substrate. Surfaces the content graph
|
|
22
|
+
* for templates that need cross-page reads, and snapshots from the
|
|
23
|
+
* page-context registry and content-map store for debugging. Writes
|
|
24
|
+
* nothing back.
|
|
24
25
|
*
|
|
25
26
|
* Lifecycle:
|
|
26
|
-
* build-time → register
|
|
27
|
-
* optional virtual debug page
|
|
27
|
+
* build-time → register `_navigator` ({ nodes, edges, backlinks }),
|
|
28
|
+
* debug globals, filters, and the optional virtual debug page
|
|
28
29
|
* cascade-time → eleventyComputed `_snapshot` resolves contentMap and
|
|
29
30
|
* pageContext on each page
|
|
30
31
|
*
|
|
31
32
|
* Why this exists:
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
33
|
+
* Templates need an addressable cross-page surface for graph reads, and
|
|
34
|
+
* render-time inspection of cascade state has no built-in equivalent.
|
|
35
|
+
* One module owns both vocabularies so feature modules stay narrow.
|
|
35
36
|
*
|
|
36
37
|
* Scope:
|
|
37
|
-
* Owns the `
|
|
38
|
+
* Owns the `_navigator` global (`{ nodes, edges, backlinks }`, the public
|
|
39
|
+
* read surface), the debug globals `_runtime` and `_ctx`, computed `_snapshot`,
|
|
38
40
|
* debug filters (`_inspect`, `_json`, `_keys`), and the optional virtual
|
|
39
41
|
* page at /navigator-core.html.
|
|
40
|
-
* Does not own the data it surfaces (page-context registry,
|
|
41
|
-
* store).
|
|
42
|
+
* Does not own the data it surfaces (content graph, page-context registry,
|
|
43
|
+
* content-map store).
|
|
42
44
|
*
|
|
43
45
|
* Data flow:
|
|
44
|
-
*
|
|
45
|
-
* `_snapshot` + virtual page →
|
|
46
|
+
* runtime.contentGraph + snapshots + this.ctx → `_navigator` + debug
|
|
47
|
+
* globals + computed `_snapshot` + virtual page → templates and
|
|
48
|
+
* developers
|
|
46
49
|
*
|
|
47
50
|
* Note: `_snapshot.contentMap` is null on the navigator template itself
|
|
48
51
|
* because it renders before `eleventy.contentMap` fires. Read `_snapshot`
|
|
@@ -51,23 +54,25 @@ const __dirname = path.dirname(__filename);
|
|
|
51
54
|
* @param {import("@11ty/eleventy").UserConfig} eleventyConfig
|
|
52
55
|
* @param {Object} moduleContext
|
|
53
56
|
* @param {Object} moduleContext.state - Resolved plugin state.
|
|
57
|
+
* @param {Object} moduleContext.runtime - Lazy access layer; reads contentGraph.
|
|
54
58
|
* @param {Object} moduleContext.snapshots - Thunks: { contentMap, pageContext }.
|
|
55
59
|
*/
|
|
56
60
|
export function navigatorCore(eleventyConfig, moduleContext) {
|
|
57
|
-
const { state, snapshots, log, env } = moduleContext;
|
|
61
|
+
const { state, runtime, snapshots, log, env } = moduleContext;
|
|
58
62
|
const { settings, options } = state;
|
|
59
63
|
|
|
60
64
|
// Structural-only options check: log on mismatch, do not throw.
|
|
61
65
|
const parsed = optionsSchema.safeParse(options.navigator);
|
|
62
66
|
if (!parsed.success) {
|
|
63
67
|
for (const issue of parsed.error.issues) {
|
|
64
|
-
log.info('options:', `${issue.path.join('.')}
|
|
68
|
+
log.info('options:', `${issue.path.join('.')}, ${issue.message}`);
|
|
65
69
|
}
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
// Boolean shorthand activates the virtual page; object form lets users tune.
|
|
69
73
|
const navigatorOpts = options.navigator && typeof options.navigator === 'object' ? options.navigator : {};
|
|
70
|
-
const renderTemplate =
|
|
74
|
+
const renderTemplate =
|
|
75
|
+
navigatorOpts.template ?? (typeof options.navigator === 'boolean' ? options.navigator : env.mode === 'development');
|
|
71
76
|
const inspectorDepth = navigatorOpts.inspectorDepth ?? 4;
|
|
72
77
|
|
|
73
78
|
eleventyConfig.addGlobalData('eleventyComputed._snapshot', () => {
|
|
@@ -77,6 +82,14 @@ export function navigatorCore(eleventyConfig, moduleContext) {
|
|
|
77
82
|
});
|
|
78
83
|
});
|
|
79
84
|
|
|
85
|
+
// Public read surface for plugin-produced cross-page data. Templates can
|
|
86
|
+
// paginate over `_navigator.backlinks` or read `_navigator.graph` directly.
|
|
87
|
+
eleventyConfig.addGlobalData('_navigator', () => ({
|
|
88
|
+
nodes: runtime.contentGraph?.nodes ?? {},
|
|
89
|
+
edges: runtime.contentGraph?.edges ?? {},
|
|
90
|
+
backlinks: runtime.contentGraph?.backlinks ?? {}
|
|
91
|
+
}));
|
|
92
|
+
|
|
80
93
|
/**
|
|
81
94
|
* Nunjucks Global: _runtime
|
|
82
95
|
*
|
|
@@ -125,7 +138,7 @@ export function navigatorCore(eleventyConfig, moduleContext) {
|
|
|
125
138
|
inspectorDepth
|
|
126
139
|
});
|
|
127
140
|
|
|
128
|
-
log.info('Navigator
|
|
141
|
+
log.info('Navigator mounted at /navigator-core.html');
|
|
129
142
|
}
|
|
130
143
|
|
|
131
144
|
/**
|
|
@@ -41,7 +41,7 @@ permalink: /navigator-core.html
|
|
|
41
41
|
<details>
|
|
42
42
|
<summary><strong>Page Context</strong></summary>
|
|
43
43
|
<pre>
|
|
44
|
-
{{-
|
|
44
|
+
{{- _pageContext | _inspect({ depth: null }) -}}
|
|
45
45
|
</pre>
|
|
46
46
|
</details>
|
|
47
47
|
{% for key, value in _runtime() %}
|