@stati/core 1.27.1 → 1.28.0
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/dist/config/errors.d.ts +10 -0
- package/dist/config/errors.d.ts.map +1 -0
- package/dist/config/errors.js +10 -0
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +15 -2
- package/dist/constants.d.ts +26 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +23 -0
- package/dist/core/build.d.ts.map +1 -1
- package/dist/core/build.js +98 -26
- package/dist/core/isg/builder.d.ts +21 -6
- package/dist/core/isg/builder.d.ts.map +1 -1
- package/dist/core/isg/builder.js +59 -17
- package/dist/core/isg/deps.d.ts +23 -2
- package/dist/core/isg/deps.d.ts.map +1 -1
- package/dist/core/isg/deps.js +54 -8
- package/dist/core/isg/hash.d.ts +10 -0
- package/dist/core/isg/hash.d.ts.map +1 -1
- package/dist/core/isg/hash.js +14 -0
- package/dist/core/isg/index.d.ts +2 -1
- package/dist/core/isg/index.d.ts.map +1 -1
- package/dist/core/isg/index.js +1 -1
- package/dist/core/isg/manifest.js +8 -0
- package/dist/core/markdown.d.ts +10 -1
- package/dist/core/markdown.d.ts.map +1 -1
- package/dist/core/markdown.js +13 -3
- package/dist/core/templates.d.ts +12 -1
- package/dist/core/templates.d.ts.map +1 -1
- package/dist/core/templates.js +61 -6
- package/dist/core/utils/fs.utils.d.ts +21 -1
- package/dist/core/utils/fs.utils.d.ts.map +1 -1
- package/dist/core/utils/fs.utils.js +61 -0
- package/dist/core/utils/index.d.ts +1 -1
- package/dist/core/utils/index.d.ts.map +1 -1
- package/dist/core/utils/index.js +1 -1
- package/dist/core/utils/template-discovery.utils.d.ts +8 -6
- package/dist/core/utils/template-discovery.utils.d.ts.map +1 -1
- package/dist/core/utils/template-discovery.utils.js +43 -12
- package/dist/images/config.d.ts +52 -0
- package/dist/images/config.d.ts.map +1 -0
- package/dist/images/config.js +200 -0
- package/dist/images/index.d.ts +15 -0
- package/dist/images/index.d.ts.map +1 -0
- package/dist/images/index.js +11 -0
- package/dist/images/manifest.d.ts +36 -0
- package/dist/images/manifest.d.ts.map +1 -0
- package/dist/images/manifest.js +69 -0
- package/dist/images/markdown-rule.d.ts +26 -0
- package/dist/images/markdown-rule.d.ts.map +1 -0
- package/dist/images/markdown-rule.js +60 -0
- package/dist/images/markup.d.ts +39 -0
- package/dist/images/markup.d.ts.map +1 -0
- package/dist/images/markup.js +72 -0
- package/dist/images/og.d.ts +52 -0
- package/dist/images/og.d.ts.map +1 -0
- package/dist/images/og.js +165 -0
- package/dist/images/plan.d.ts +101 -0
- package/dist/images/plan.d.ts.map +1 -0
- package/dist/images/plan.js +128 -0
- package/dist/images/provider.d.ts +35 -0
- package/dist/images/provider.d.ts.map +1 -0
- package/dist/images/provider.js +70 -0
- package/dist/images/resolve.d.ts +45 -0
- package/dist/images/resolve.d.ts.map +1 -0
- package/dist/images/resolve.js +51 -0
- package/dist/images/runtime.d.ts +258 -0
- package/dist/images/runtime.d.ts.map +1 -0
- package/dist/images/runtime.js +659 -0
- package/dist/images/sources.d.ts +47 -0
- package/dist/images/sources.d.ts.map +1 -0
- package/dist/images/sources.js +97 -0
- package/dist/images/utils/image-probe.utils.d.ts +30 -0
- package/dist/images/utils/image-probe.utils.d.ts.map +1 -0
- package/dist/images/utils/image-probe.utils.js +307 -0
- package/dist/images/utils/index.d.ts +7 -0
- package/dist/images/utils/index.d.ts.map +1 -0
- package/dist/images/utils/index.js +5 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/metrics/noop.d.ts.map +1 -1
- package/dist/metrics/noop.js +3 -0
- package/dist/metrics/recorder.d.ts.map +1 -1
- package/dist/metrics/recorder.js +3 -0
- package/dist/metrics/types.d.ts +8 -0
- package/dist/metrics/types.d.ts.map +1 -1
- package/dist/seo/auto-inject.d.ts +3 -0
- package/dist/seo/auto-inject.d.ts.map +1 -1
- package/dist/seo/auto-inject.js +1 -0
- package/dist/seo/generator.d.ts +24 -0
- package/dist/seo/generator.d.ts.map +1 -1
- package/dist/seo/generator.js +65 -1
- package/dist/seo/index.d.ts +1 -1
- package/dist/seo/index.d.ts.map +1 -1
- package/dist/seo/index.js +1 -1
- package/dist/types/config.d.ts +5 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/content.d.ts +21 -2
- package/dist/types/content.d.ts.map +1 -1
- package/dist/types/images.d.ts +185 -0
- package/dist/types/images.d.ts.map +1 -0
- package/dist/types/images.js +8 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/isg.d.ts +7 -0
- package/dist/types/isg.d.ts.map +1 -1
- package/dist/types/seo.d.ts +9 -0
- package/dist/types/seo.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/core/isg/builder.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { computeContentHash, computeFileHash, computeInputsHash } from './hash.js';
|
|
2
|
-
import { trackTemplateDependencies } from './deps.js';
|
|
1
|
+
import { computeContentHash, computeFileHash, computeBinaryFileHash, computeInputsHash, } from './hash.js';
|
|
2
|
+
import { trackTemplateDependencies, resolveLayoutDependency } from './deps.js';
|
|
3
3
|
import { computeEffectiveTTL, computeNextRebuildAt, isPageFrozen } from './ttl.js';
|
|
4
4
|
import { validatePageISGOverrides, extractNumericOverride } from './validation.js';
|
|
5
5
|
import { getFrontMatterDate } from '../utils/index.js';
|
|
@@ -55,8 +55,35 @@ function isValidCacheEntry(entry) {
|
|
|
55
55
|
if (entry.maxAgeCapDays !== undefined && typeof entry.maxAgeCapDays !== 'number') {
|
|
56
56
|
return false;
|
|
57
57
|
}
|
|
58
|
+
for (const list of [entry.assetDeps, entry.images]) {
|
|
59
|
+
if (list !== undefined && (!Array.isArray(list) || !list.every((v) => typeof v === 'string'))) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
58
63
|
return true;
|
|
59
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Hashes render-discovered dependencies by content. Missing files hash to a sentinel.
|
|
67
|
+
*/
|
|
68
|
+
async function hashAssetDeps(assetDeps) {
|
|
69
|
+
const hashes = [];
|
|
70
|
+
for (const assetPath of assetDeps ?? []) {
|
|
71
|
+
hashes.push(await computeBinaryFileHash(assetPath));
|
|
72
|
+
}
|
|
73
|
+
return hashes;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Returns the entry with its `assetDeps` and `images` replaced by this render's,
|
|
77
|
+
* dropping the fields when a render found none.
|
|
78
|
+
*/
|
|
79
|
+
function withRenderedAssets(entry, rendered) {
|
|
80
|
+
const { assetDeps: _assetDeps, images: _images, ...rest } = entry;
|
|
81
|
+
return {
|
|
82
|
+
...rest,
|
|
83
|
+
...(rendered && rendered.assetDeps.length > 0 && { assetDeps: [...rendered.assetDeps] }),
|
|
84
|
+
...(rendered && rendered.images.length > 0 && { images: [...rendered.images] }),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
60
87
|
/**
|
|
61
88
|
* Determines if a page should be rebuilt based on ISG logic.
|
|
62
89
|
* Checks content changes, dependency changes, TTL expiration, and freeze status.
|
|
@@ -65,19 +92,20 @@ function isValidCacheEntry(entry) {
|
|
|
65
92
|
* @param page - The page model to check
|
|
66
93
|
* @param entry - Existing cache entry, or undefined if not cached
|
|
67
94
|
* @param config - Stati configuration
|
|
95
|
+
* @param allPages - All pages in the site, used to resolve the same layout as rendering
|
|
68
96
|
* @param now - Current date/time
|
|
69
97
|
* @param renderInputsHash - Optional hash of asset URLs and injection settings
|
|
70
98
|
* @returns True if the page should be rebuilt
|
|
71
99
|
*
|
|
72
100
|
* @example
|
|
73
101
|
* ```typescript
|
|
74
|
-
* const shouldRebuild = await shouldRebuildPage(page, cacheEntry, config, new Date());
|
|
102
|
+
* const shouldRebuild = await shouldRebuildPage(page, cacheEntry, config, pages, new Date());
|
|
75
103
|
* if (shouldRebuild) {
|
|
76
104
|
* console.log('Page needs rebuilding');
|
|
77
105
|
* }
|
|
78
106
|
* ```
|
|
79
107
|
*/
|
|
80
|
-
export async function shouldRebuildPage(page, entry, config, now, renderInputsHash) {
|
|
108
|
+
export async function shouldRebuildPage(page, entry, config, allPages, now, renderInputsHash) {
|
|
81
109
|
if (config.isg?.enabled === false) {
|
|
82
110
|
return true;
|
|
83
111
|
}
|
|
@@ -96,7 +124,7 @@ export async function shouldRebuildPage(page, entry, config, now, renderInputsHa
|
|
|
96
124
|
// Track dependencies with error handling
|
|
97
125
|
let deps;
|
|
98
126
|
try {
|
|
99
|
-
deps = await trackTemplateDependencies(page, config);
|
|
127
|
+
deps = await trackTemplateDependencies(page, config, allPages);
|
|
100
128
|
}
|
|
101
129
|
catch (error) {
|
|
102
130
|
if (error instanceof Error && error.name === 'CircularDependencyError') {
|
|
@@ -132,6 +160,8 @@ export async function shouldRebuildPage(page, entry, config, now, renderInputsHa
|
|
|
132
160
|
console.warn(`${dependencyErrors} dependency errors for ${page.url}, forcing rebuild`);
|
|
133
161
|
return true;
|
|
134
162
|
}
|
|
163
|
+
// Files found during the last render, such as images, are hashed by content
|
|
164
|
+
depsHashes.push(...(await hashAssetDeps(entry.assetDeps)));
|
|
135
165
|
const currentInputsHash = computeInputsHash(currentContentHash, depsHashes, renderInputsHash);
|
|
136
166
|
// If inputs changed, always rebuild
|
|
137
167
|
if (currentInputsHash !== entry.inputsHash) {
|
|
@@ -211,16 +241,18 @@ export async function shouldRebuildPage(page, entry, config, now, renderInputsHa
|
|
|
211
241
|
*
|
|
212
242
|
* @param page - The page model
|
|
213
243
|
* @param config - Stati configuration
|
|
244
|
+
* @param allPages - All pages in the site, used to resolve the same layout as rendering
|
|
214
245
|
* @param renderedAt - When the page was rendered
|
|
215
246
|
* @param renderInputsHash - Optional hash of asset URLs and injection settings
|
|
247
|
+
* @param rendered - Files the render discovered, such as images
|
|
216
248
|
* @returns New cache entry
|
|
217
249
|
*
|
|
218
250
|
* @example
|
|
219
251
|
* ```typescript
|
|
220
|
-
* const entry = await createCacheEntry(page, config, new Date());
|
|
252
|
+
* const entry = await createCacheEntry(page, config, pages, new Date());
|
|
221
253
|
* ```
|
|
222
254
|
*/
|
|
223
|
-
export async function createCacheEntry(page, config, renderedAt, renderInputsHash) {
|
|
255
|
+
export async function createCacheEntry(page, config, allPages, renderedAt, renderInputsHash, rendered) {
|
|
224
256
|
const timings = {};
|
|
225
257
|
let start = performance.now();
|
|
226
258
|
// Validate page-level ISG overrides first
|
|
@@ -232,7 +264,7 @@ export async function createCacheEntry(page, config, renderedAt, renderInputsHas
|
|
|
232
264
|
timings.contentHash = performance.now() - start;
|
|
233
265
|
// Track all template dependencies
|
|
234
266
|
start = performance.now();
|
|
235
|
-
const deps = await trackTemplateDependencies(page, config);
|
|
267
|
+
const deps = await trackTemplateDependencies(page, config, allPages);
|
|
236
268
|
timings.trackDeps = performance.now() - start;
|
|
237
269
|
// Compute hashes for all dependencies
|
|
238
270
|
start = performance.now();
|
|
@@ -243,6 +275,7 @@ export async function createCacheEntry(page, config, renderedAt, renderInputsHas
|
|
|
243
275
|
depsHashes.push(depHash);
|
|
244
276
|
}
|
|
245
277
|
}
|
|
278
|
+
depsHashes.push(...(await hashAssetDeps(rendered?.assetDeps)));
|
|
246
279
|
timings.depsHashes = performance.now() - start;
|
|
247
280
|
const inputsHash = computeInputsHash(contentHash, depsHashes, renderInputsHash);
|
|
248
281
|
// Extract tags from front matter
|
|
@@ -285,7 +318,7 @@ export async function createCacheEntry(page, config, renderedAt, renderInputsHas
|
|
|
285
318
|
if (maxAgeCapDays !== undefined) {
|
|
286
319
|
cacheEntry.maxAgeCapDays = maxAgeCapDays;
|
|
287
320
|
}
|
|
288
|
-
return cacheEntry;
|
|
321
|
+
return withRenderedAssets(cacheEntry, rendered);
|
|
289
322
|
}
|
|
290
323
|
/**
|
|
291
324
|
* Checks if the template structure has changed by comparing the layout file's
|
|
@@ -339,26 +372,33 @@ async function hasTemplateStructureChanged(entry) {
|
|
|
339
372
|
* @param entry - Existing cache entry
|
|
340
373
|
* @param page - The page model
|
|
341
374
|
* @param config - Stati configuration
|
|
375
|
+
* @param allPages - All pages in the site, used to resolve the same layout as rendering
|
|
342
376
|
* @param renderedAt - When the page was rendered
|
|
343
377
|
* @param fastUpdate - If true, skip expensive dep tracking (dev mode optimization)
|
|
344
378
|
* @param renderInputsHash - Optional hash of asset URLs and injection settings
|
|
379
|
+
* @param rendered - Files the render discovered, such as images
|
|
345
380
|
* @returns Updated cache entry
|
|
346
381
|
*
|
|
347
382
|
* @example
|
|
348
383
|
* ```typescript
|
|
349
|
-
* const updatedEntry = await updateCacheEntry(existingEntry, page, config, new Date());
|
|
384
|
+
* const updatedEntry = await updateCacheEntry(existingEntry, page, config, pages, new Date());
|
|
350
385
|
* ```
|
|
351
386
|
*/
|
|
352
|
-
export async function updateCacheEntry(entry, page, config, renderedAt, fastUpdate = false, renderInputsHash) {
|
|
387
|
+
export async function updateCacheEntry(entry, page, config, allPages, renderedAt, fastUpdate = false, renderInputsHash, rendered) {
|
|
353
388
|
// In fast update mode (dev), reuse existing deps to avoid expensive tracking
|
|
354
389
|
if (fastUpdate && entry.deps.length > 0) {
|
|
355
390
|
// Detect if template structure has changed (new includes/extends added)
|
|
356
391
|
// by checking if the layout file has been modified or if template content
|
|
357
392
|
// contains different dependency patterns
|
|
358
393
|
const structureChanged = await hasTemplateStructureChanged(entry);
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
394
|
+
// The page can also switch layouts without any template file changing, for example
|
|
395
|
+
// when a collection gains its first child page or a front matter layout is edited.
|
|
396
|
+
// deps[0] is always the layout, so compare it against the layout used now.
|
|
397
|
+
const layoutChanged = !structureChanged &&
|
|
398
|
+
(await resolveLayoutDependency(page, config, allPages)) !== entry.deps[0];
|
|
399
|
+
if (structureChanged || layoutChanged) {
|
|
400
|
+
// Template structure or layout changed - fall through to full dependency tracking
|
|
401
|
+
// This ensures we pick up new includes/extends and the newly selected layout
|
|
362
402
|
}
|
|
363
403
|
else {
|
|
364
404
|
// Structure unchanged - safe to reuse existing deps
|
|
@@ -372,16 +412,18 @@ export async function updateCacheEntry(entry, page, config, renderedAt, fastUpda
|
|
|
372
412
|
depsHashes.push(depHash);
|
|
373
413
|
}
|
|
374
414
|
}
|
|
415
|
+
// Asset deps come from this render, not from the previous entry
|
|
416
|
+
depsHashes.push(...(await hashAssetDeps(rendered?.assetDeps)));
|
|
375
417
|
const inputsHash = computeInputsHash(contentHash, depsHashes, renderInputsHash);
|
|
376
|
-
return {
|
|
418
|
+
return withRenderedAssets({
|
|
377
419
|
...entry,
|
|
378
420
|
inputsHash,
|
|
379
421
|
renderedAt: renderedAt.toISOString(),
|
|
380
|
-
};
|
|
422
|
+
}, rendered);
|
|
381
423
|
}
|
|
382
424
|
}
|
|
383
425
|
// Full update - create new entry and preserve original publishedAt
|
|
384
|
-
const newEntry = await createCacheEntry(page, config, renderedAt, renderInputsHash);
|
|
426
|
+
const newEntry = await createCacheEntry(page, config, allPages, renderedAt, renderInputsHash, rendered);
|
|
385
427
|
// Preserve original publishedAt if no new one is specified
|
|
386
428
|
if (!newEntry.publishedAt && entry.publishedAt) {
|
|
387
429
|
newEntry.publishedAt = entry.publishedAt;
|
package/dist/core/isg/deps.d.ts
CHANGED
|
@@ -22,13 +22,14 @@ export declare class CircularDependencyError extends Error {
|
|
|
22
22
|
*
|
|
23
23
|
* @param page - The page model to track dependencies for
|
|
24
24
|
* @param config - Stati configuration
|
|
25
|
+
* @param allPages - All pages in the site, used to detect collection index pages the same way rendering does
|
|
25
26
|
* @returns Array of absolute paths to actually-used template files (POSIX format)
|
|
26
27
|
* @throws {CircularDependencyError} When circular dependencies are detected in templates
|
|
27
28
|
*
|
|
28
29
|
* @example
|
|
29
30
|
* ```typescript
|
|
30
31
|
* try {
|
|
31
|
-
* const deps = await trackTemplateDependencies(page, config);
|
|
32
|
+
* const deps = await trackTemplateDependencies(page, config, allPages);
|
|
32
33
|
* console.log(`Page depends on ${deps.length} template files`);
|
|
33
34
|
* } catch (error) {
|
|
34
35
|
* if (error instanceof CircularDependencyError) {
|
|
@@ -37,7 +38,27 @@ export declare class CircularDependencyError extends Error {
|
|
|
37
38
|
* }
|
|
38
39
|
* ```
|
|
39
40
|
*/
|
|
40
|
-
export declare function trackTemplateDependencies(page: PageModel, config: StatiConfig): Promise<string[]>;
|
|
41
|
+
export declare function trackTemplateDependencies(page: PageModel, config: StatiConfig, allPages: PageModel[]): Promise<string[]>;
|
|
42
|
+
/**
|
|
43
|
+
* Resolves the layout a page renders with, in the form stored as the first entry of its
|
|
44
|
+
* cache dependencies. Uses the same lookup as rendering, so callers can tell whether the
|
|
45
|
+
* page's layout has changed since its dependencies were recorded.
|
|
46
|
+
*
|
|
47
|
+
* @param page - The page model to resolve the layout for
|
|
48
|
+
* @param config - Stati configuration
|
|
49
|
+
* @param allPages - All pages in the site, used to detect collection index pages the same way rendering does
|
|
50
|
+
* @returns Absolute path to the layout file (POSIX format), or null if no layout applies
|
|
51
|
+
*/
|
|
52
|
+
export declare function resolveLayoutDependency(page: PageModel, config: StatiConfig, allPages: PageModel[]): Promise<string | null>;
|
|
53
|
+
/**
|
|
54
|
+
* Resolves the Open Graph template a page renders with, when Open Graph images are on and the
|
|
55
|
+
* page does not opt out. Pages that use the built-in template have no file dependency.
|
|
56
|
+
*
|
|
57
|
+
* @param page - The page model
|
|
58
|
+
* @param config - Stati configuration
|
|
59
|
+
* @returns Absolute path to the template (POSIX format), or null
|
|
60
|
+
*/
|
|
61
|
+
export declare function resolveOgTemplateDependency(page: PageModel, config: StatiConfig): Promise<string | null>;
|
|
41
62
|
/**
|
|
42
63
|
* Resolves a template name to its file path.
|
|
43
64
|
* Used for explicit layout specifications in front matter.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deps.d.ts","sourceRoot":"","sources":["../../../src/core/isg/deps.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"deps.d.ts","sourceRoot":"","sources":["../../../src/core/isg/deps.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAgBnE;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAI7C;AAED;;GAEG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;aAE9B,eAAe,EAAE,MAAM,EAAE;gBAAzB,eAAe,EAAE,MAAM,EAAE,EACzC,OAAO,EAAE,MAAM;CAKlB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,SAAS,EAAE,GACpB,OAAO,CAAC,MAAM,EAAE,CAAC,CA+CnB;AAED;;;;;;;;;GASG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,SAAS,EAAE,GACpB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAexB;AAED;;;;;;;GAOG;AACH,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAexB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CASxB"}
|
package/dist/core/isg/deps.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { join, dirname, relative, posix } from 'node:path';
|
|
2
2
|
import { pathExists, readFile, isCollectionIndexPage, discoverLayout, resolveSrcDir, } from '../utils/index.js';
|
|
3
3
|
import glob from 'fast-glob';
|
|
4
|
-
import { TEMPLATE_EXTENSION } from '../../constants.js';
|
|
4
|
+
import { TEMPLATE_EXTENSION, DEFAULT_OG_TEMPLATE } from '../../constants.js';
|
|
5
|
+
import { clearOgTemplateCache, findOgTemplatePath, isOgImageExcluded } from '../../images/og.js';
|
|
5
6
|
/**
|
|
6
7
|
* Per-build cache for template path resolution (glob results).
|
|
7
8
|
* Cleared at the start of each build via clearTemplatePathCache().
|
|
@@ -19,6 +20,7 @@ const templateContentCache = new Map();
|
|
|
19
20
|
export function clearTemplatePathCache() {
|
|
20
21
|
templatePathCache.clear();
|
|
21
22
|
templateContentCache.clear();
|
|
23
|
+
clearOgTemplateCache();
|
|
22
24
|
}
|
|
23
25
|
/**
|
|
24
26
|
* Error thrown when a circular dependency is detected in templates.
|
|
@@ -42,13 +44,14 @@ export class CircularDependencyError extends Error {
|
|
|
42
44
|
*
|
|
43
45
|
* @param page - The page model to track dependencies for
|
|
44
46
|
* @param config - Stati configuration
|
|
47
|
+
* @param allPages - All pages in the site, used to detect collection index pages the same way rendering does
|
|
45
48
|
* @returns Array of absolute paths to actually-used template files (POSIX format)
|
|
46
49
|
* @throws {CircularDependencyError} When circular dependencies are detected in templates
|
|
47
50
|
*
|
|
48
51
|
* @example
|
|
49
52
|
* ```typescript
|
|
50
53
|
* try {
|
|
51
|
-
* const deps = await trackTemplateDependencies(page, config);
|
|
54
|
+
* const deps = await trackTemplateDependencies(page, config, allPages);
|
|
52
55
|
* console.log(`Page depends on ${deps.length} template files`);
|
|
53
56
|
* } catch (error) {
|
|
54
57
|
* if (error instanceof CircularDependencyError) {
|
|
@@ -57,7 +60,7 @@ export class CircularDependencyError extends Error {
|
|
|
57
60
|
* }
|
|
58
61
|
* ```
|
|
59
62
|
*/
|
|
60
|
-
export async function trackTemplateDependencies(page, config) {
|
|
63
|
+
export async function trackTemplateDependencies(page, config, allPages) {
|
|
61
64
|
// Early return if required config values are missing
|
|
62
65
|
if (!config.srcDir) {
|
|
63
66
|
console.warn('Config srcDir is missing, cannot track template dependencies');
|
|
@@ -65,21 +68,25 @@ export async function trackTemplateDependencies(page, config) {
|
|
|
65
68
|
}
|
|
66
69
|
const deps = [];
|
|
67
70
|
const srcDir = resolveSrcDir(config);
|
|
68
|
-
const relativePath = relative(srcDir, page.sourcePath);
|
|
69
71
|
// Track dependencies with circular detection
|
|
70
72
|
// The visited set will contain all templates that are actually used (not just accessible)
|
|
71
73
|
const visited = new Set();
|
|
72
74
|
const currentPath = new Set();
|
|
73
75
|
// 1. Find the layout file that will be used for this page
|
|
74
|
-
const
|
|
75
|
-
if (
|
|
76
|
-
// Normalize to POSIX format for consistent manifest output across platforms
|
|
77
|
-
const absoluteLayoutPath = posix.join(srcDir.replace(/\\/g, '/'), layoutPath);
|
|
76
|
+
const absoluteLayoutPath = await resolveLayoutDependency(page, config, allPages);
|
|
77
|
+
if (absoluteLayoutPath) {
|
|
78
78
|
deps.push(absoluteLayoutPath);
|
|
79
79
|
// Recursively traverse the template dependency tree
|
|
80
80
|
// This populates 'visited' with all actually-used templates (not just accessible ones)
|
|
81
81
|
await collectTemplateDependencies(absoluteLayoutPath, srcDir, visited, currentPath);
|
|
82
82
|
}
|
|
83
|
+
// The Open Graph template renders with the page, so it and its partials are dependencies too.
|
|
84
|
+
// It comes after the layout, which must stay first.
|
|
85
|
+
const ogTemplatePath = await resolveOgTemplateDependency(page, config);
|
|
86
|
+
if (ogTemplatePath) {
|
|
87
|
+
deps.push(ogTemplatePath);
|
|
88
|
+
await collectTemplateDependencies(ogTemplatePath, srcDir, visited, currentPath);
|
|
89
|
+
}
|
|
83
90
|
// 2. Add all actually-used templates from the visited set
|
|
84
91
|
// Filter to only include underscore directories (partials/components)
|
|
85
92
|
// and normalize paths to POSIX format
|
|
@@ -93,6 +100,45 @@ export async function trackTemplateDependencies(page, config) {
|
|
|
93
100
|
}
|
|
94
101
|
return deps;
|
|
95
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Resolves the layout a page renders with, in the form stored as the first entry of its
|
|
105
|
+
* cache dependencies. Uses the same lookup as rendering, so callers can tell whether the
|
|
106
|
+
* page's layout has changed since its dependencies were recorded.
|
|
107
|
+
*
|
|
108
|
+
* @param page - The page model to resolve the layout for
|
|
109
|
+
* @param config - Stati configuration
|
|
110
|
+
* @param allPages - All pages in the site, used to detect collection index pages the same way rendering does
|
|
111
|
+
* @returns Absolute path to the layout file (POSIX format), or null if no layout applies
|
|
112
|
+
*/
|
|
113
|
+
export async function resolveLayoutDependency(page, config, allPages) {
|
|
114
|
+
if (!config.srcDir) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
const srcDir = resolveSrcDir(config);
|
|
118
|
+
const layoutPath = await discoverLayout(relative(srcDir, page.sourcePath), config, page.frontMatter.layout, isCollectionIndexPage(page, allPages));
|
|
119
|
+
// Normalize to POSIX format for consistent manifest output across platforms
|
|
120
|
+
return layoutPath ? posix.join(srcDir.replace(/\\/g, '/'), layoutPath) : null;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Resolves the Open Graph template a page renders with, when Open Graph images are on and the
|
|
124
|
+
* page does not opt out. Pages that use the built-in template have no file dependency.
|
|
125
|
+
*
|
|
126
|
+
* @param page - The page model
|
|
127
|
+
* @param config - Stati configuration
|
|
128
|
+
* @returns Absolute path to the template (POSIX format), or null
|
|
129
|
+
*/
|
|
130
|
+
export async function resolveOgTemplateDependency(page, config) {
|
|
131
|
+
const og = config.images ? config.images.og : undefined;
|
|
132
|
+
if (!og || !config.srcDir) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const options = og === true ? {} : og;
|
|
136
|
+
if (isOgImageExcluded(page, options.exclude ?? [])) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
const templatePath = await findOgTemplatePath(page.sourcePath, resolveSrcDir(config), options.template ?? DEFAULT_OG_TEMPLATE);
|
|
140
|
+
return templatePath ? templatePath.replace(/\\/g, '/') : null;
|
|
141
|
+
}
|
|
96
142
|
/**
|
|
97
143
|
* Resolves a template name to its file path.
|
|
98
144
|
* Used for explicit layout specifications in front matter.
|
package/dist/core/isg/hash.d.ts
CHANGED
|
@@ -33,6 +33,16 @@ export declare function computeContentHash(content: string, frontMatter: Record<
|
|
|
33
33
|
* ```
|
|
34
34
|
*/
|
|
35
35
|
export declare function computeFileHash(filePath: string): Promise<string | null>;
|
|
36
|
+
/**
|
|
37
|
+
* Computes a hash of a binary file, such as an image a page displays.
|
|
38
|
+
* Unlike {@link computeFileHash}, it hashes raw bytes, and a missing file hashes to a
|
|
39
|
+
* fixed sentinel: a page that references a missing image stays cached until the file
|
|
40
|
+
* appears, and then rebuilds.
|
|
41
|
+
*
|
|
42
|
+
* @param filePath - Absolute path to the file
|
|
43
|
+
* @returns `sha256-…` for an existing file, `missing:<path>` otherwise
|
|
44
|
+
*/
|
|
45
|
+
export declare function computeBinaryFileHash(filePath: string): Promise<string>;
|
|
36
46
|
/**
|
|
37
47
|
* Computes a combined hash from content hash and dependency hashes.
|
|
38
48
|
* This represents the complete input state for a page.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../../src/core/isg/hash.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../../src/core/isg/hash.ts"],"names":[],"mappings":"AAgCA;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAiBhG;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA8B9E;AAED;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAG7E;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAAE,EACpB,gBAAgB,CAAC,EAAE,MAAM,GACxB,MAAM,CASR;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,MAAM,CAqCnE"}
|
package/dist/core/isg/hash.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { readFile, pathExists } from '../utils/index.js';
|
|
3
|
+
import { inspectSource } from '../../images/sources.js';
|
|
3
4
|
/**
|
|
4
5
|
* Creates a SHA-256 hash instance, updates it with data, and returns the hex digest.
|
|
5
6
|
* Internal utility to eliminate duplicate hash creation patterns.
|
|
@@ -104,6 +105,19 @@ export async function computeFileHash(filePath) {
|
|
|
104
105
|
return null;
|
|
105
106
|
}
|
|
106
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Computes a hash of a binary file, such as an image a page displays.
|
|
110
|
+
* Unlike {@link computeFileHash}, it hashes raw bytes, and a missing file hashes to a
|
|
111
|
+
* fixed sentinel: a page that references a missing image stays cached until the file
|
|
112
|
+
* appears, and then rebuilds.
|
|
113
|
+
*
|
|
114
|
+
* @param filePath - Absolute path to the file
|
|
115
|
+
* @returns `sha256-…` for an existing file, `missing:<path>` otherwise
|
|
116
|
+
*/
|
|
117
|
+
export async function computeBinaryFileHash(filePath) {
|
|
118
|
+
const info = await inspectSource(filePath);
|
|
119
|
+
return info ? `sha256-${info.hash}` : `missing:${filePath.replace(/\\/g, '/')}`;
|
|
120
|
+
}
|
|
107
121
|
/**
|
|
108
122
|
* Computes a combined hash from content hash and dependency hashes.
|
|
109
123
|
* This represents the complete input state for a page.
|
package/dist/core/isg/index.d.ts
CHANGED
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export { loadCacheManifest, saveCacheManifest, createEmptyManifest } from './manifest.js';
|
|
10
10
|
export { shouldRebuildPage, createCacheEntry, updateCacheEntry } from './builder.js';
|
|
11
|
+
export type { RenderedAssets } from './builder.js';
|
|
11
12
|
export { BuildLockManager, withBuildLock } from './build-lock.js';
|
|
12
13
|
export { DevServerLockManager } from './dev-server-lock.js';
|
|
13
14
|
export { CircularDependencyError, trackTemplateDependencies, resolveTemplatePath, clearTemplatePathCache, } from './deps.js';
|
|
14
|
-
export { computeContentHash, computeFileHash, computeInputsHash, computeNavigationHash, clearFileHashCache, } from './hash.js';
|
|
15
|
+
export { computeContentHash, computeFileHash, computeBinaryFileHash, computeInputsHash, computeNavigationHash, clearFileHashCache, } from './hash.js';
|
|
15
16
|
export { getSafeCurrentTime, parseSafeDate, computeEffectiveTTL, computeNextRebuildAt, isPageFrozen, applyAgingRules, } from './ttl.js';
|
|
16
17
|
export { ISGConfigurationError, validateISGConfig, validatePageISGOverrides, extractNumericOverride, } from './validation.js';
|
|
17
18
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/isg/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAG1F,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/isg/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAG1F,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrF,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAGnD,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAClE,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAG5D,OAAO,EACL,uBAAuB,EACvB,yBAAyB,EACzB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,qBAAqB,EACrB,iBAAiB,EACjB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EACZ,eAAe,GAChB,MAAM,UAAU,CAAC;AAGlB,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC"}
|
package/dist/core/isg/index.js
CHANGED
|
@@ -16,7 +16,7 @@ export { DevServerLockManager } from './dev-server-lock.js';
|
|
|
16
16
|
// Dependency tracking
|
|
17
17
|
export { CircularDependencyError, trackTemplateDependencies, resolveTemplatePath, clearTemplatePathCache, } from './deps.js';
|
|
18
18
|
// Hash computation
|
|
19
|
-
export { computeContentHash, computeFileHash, computeInputsHash, computeNavigationHash, clearFileHashCache, } from './hash.js';
|
|
19
|
+
export { computeContentHash, computeFileHash, computeBinaryFileHash, computeInputsHash, computeNavigationHash, clearFileHashCache, } from './hash.js';
|
|
20
20
|
// TTL and aging
|
|
21
21
|
export { getSafeCurrentTime, parseSafeDate, computeEffectiveTTL, computeNextRebuildAt, isPageFrozen, applyAgingRules, } from './ttl.js';
|
|
22
22
|
// Validation
|
|
@@ -229,6 +229,14 @@ function validateCacheEntry(entry, path) {
|
|
|
229
229
|
console.warn(`Invalid cache entry for ${path}: "maxAgeCapDays" must be a number if present`);
|
|
230
230
|
return false;
|
|
231
231
|
}
|
|
232
|
+
for (const field of ['assetDeps', 'images']) {
|
|
233
|
+
const value = entryObj[field];
|
|
234
|
+
if (value !== undefined &&
|
|
235
|
+
(!Array.isArray(value) || !value.every((item) => typeof item === 'string'))) {
|
|
236
|
+
console.warn(`Invalid cache entry for ${path}: "${field}" must be an array of strings`);
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
232
240
|
// Validate that deps and tags arrays contain strings
|
|
233
241
|
if (!entryObj.deps.every((dep) => typeof dep === 'string')) {
|
|
234
242
|
console.warn(`Invalid cache entry for ${path}: all "deps" must be strings`);
|
package/dist/core/markdown.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import MarkdownIt from 'markdown-it';
|
|
2
2
|
import type { StatiConfig, TocEntry } from '../types/index.js';
|
|
3
|
+
import type { PageImages } from '../images/runtime.js';
|
|
3
4
|
/**
|
|
4
5
|
* Result of rendering markdown content.
|
|
5
6
|
*/
|
|
@@ -29,13 +30,21 @@ export declare function createMarkdownProcessor(config: StatiConfig): Promise<Ma
|
|
|
29
30
|
* @returns Array of TOC entries extracted from headings (levels 2-6)
|
|
30
31
|
*/
|
|
31
32
|
export declare function extractToc(content: string, md: MarkdownIt): TocEntry[];
|
|
33
|
+
/**
|
|
34
|
+
* Options for {@link renderMarkdown}.
|
|
35
|
+
*/
|
|
36
|
+
export interface RenderMarkdownOptions {
|
|
37
|
+
/** Image state of the page being rendered; without it images are left as authored */
|
|
38
|
+
images?: PageImages | undefined;
|
|
39
|
+
}
|
|
32
40
|
/**
|
|
33
41
|
* Renders markdown content to HTML with optional TOC extraction.
|
|
34
42
|
*
|
|
35
43
|
* @param content - The markdown content to render
|
|
36
44
|
* @param md - The configured MarkdownIt instance
|
|
37
45
|
* @param tocEnabled - Whether to extract TOC and inject heading anchors (default: true)
|
|
46
|
+
* @param options - Page-specific rendering state
|
|
38
47
|
* @returns Object containing rendered HTML and TOC entries
|
|
39
48
|
*/
|
|
40
|
-
export declare function renderMarkdown(content: string, md: MarkdownIt, tocEnabled?: boolean): MarkdownResult;
|
|
49
|
+
export declare function renderMarkdown(content: string, md: MarkdownIt, tocEnabled?: boolean, options?: RenderMarkdownOptions): MarkdownResult;
|
|
41
50
|
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/core/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,aAAa,CAAC;AAKrC,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAI/D;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,GAAG,EAAE,QAAQ,EAAE,CAAC;CACjB;AAQD;;;GAGG;AACH,wBAAgB,2BAA2B,IAAI,IAAI,CAElD;AAmBD;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/core/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,aAAa,CAAC;AAKrC,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAI/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,GAAG,EAAE,QAAQ,EAAE,CAAC;CACjB;AAQD;;;GAGG;AACH,wBAAgB,2BAA2B,IAAI,IAAI,CAElD;AAmBD;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAsDtF;AA6ED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,GAAG,QAAQ,EAAE,CAGtE;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,qFAAqF;IACrF,MAAM,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;CACjC;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,UAAU,EACd,UAAU,GAAE,OAAc,EAC1B,OAAO,GAAE,qBAA0B,GAClC,cAAc,CAkBhB"}
|
package/dist/core/markdown.js
CHANGED
|
@@ -4,6 +4,7 @@ import { pathToFileURL } from 'node:url';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { slugify } from './utils/index.js';
|
|
6
6
|
import { isDevelopment } from '../env.js';
|
|
7
|
+
import { installImageRule, selectEagerImage, IMAGES_ENV_KEY } from '../images/markdown-rule.js';
|
|
7
8
|
/**
|
|
8
9
|
* Cached markdown processor for dev mode to avoid re-creating on each rebuild.
|
|
9
10
|
* This significantly improves rebuild performance by avoiding repeated plugin loading.
|
|
@@ -48,6 +49,9 @@ export async function createMarkdownProcessor(config) {
|
|
|
48
49
|
linkify: true,
|
|
49
50
|
typographer: true,
|
|
50
51
|
});
|
|
52
|
+
// Image dimensions, lazy loading and responsive variants. Installed first so plugins and
|
|
53
|
+
// markdown.configure can still replace the image rule.
|
|
54
|
+
installImageRule(md);
|
|
51
55
|
// Apply plugins from array format
|
|
52
56
|
if (config.markdown?.plugins) {
|
|
53
57
|
for (const plugin of config.markdown.plugins) {
|
|
@@ -168,14 +172,20 @@ export function extractToc(content, md) {
|
|
|
168
172
|
* @param content - The markdown content to render
|
|
169
173
|
* @param md - The configured MarkdownIt instance
|
|
170
174
|
* @param tocEnabled - Whether to extract TOC and inject heading anchors (default: true)
|
|
175
|
+
* @param options - Page-specific rendering state
|
|
171
176
|
* @returns Object containing rendered HTML and TOC entries
|
|
172
177
|
*/
|
|
173
|
-
export function renderMarkdown(content, md, tocEnabled = true) {
|
|
178
|
+
export function renderMarkdown(content, md, tocEnabled = true, options = {}) {
|
|
179
|
+
// The same env object goes to parsing and rendering so renderer rules see the page's state
|
|
180
|
+
const env = options.images ? { [IMAGES_ENV_KEY]: options.images } : {};
|
|
174
181
|
// Parse content into tokens
|
|
175
|
-
const tokens = md.parse(content,
|
|
182
|
+
const tokens = md.parse(content, env);
|
|
176
183
|
// Extract TOC and inject anchor IDs
|
|
177
184
|
const toc = extractAndInjectAnchors(tokens, tocEnabled);
|
|
185
|
+
if (options.images) {
|
|
186
|
+
options.images.eagerToken = selectEagerImage(tokens);
|
|
187
|
+
}
|
|
178
188
|
// Render tokens to HTML
|
|
179
|
-
const html = md.renderer.render(tokens, md.options,
|
|
189
|
+
const html = md.renderer.render(tokens, md.options, env);
|
|
180
190
|
return { html, toc };
|
|
181
191
|
}
|
package/dist/core/templates.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Eta } from 'eta';
|
|
2
2
|
import type { StatiConfig, PageModel, NavNode, TocEntry, Logger } from '../types/index.js';
|
|
3
|
+
import type { PageImages } from '../images/runtime.js';
|
|
4
|
+
import type { GeneratedOgImage } from '../types/index.js';
|
|
3
5
|
/**
|
|
4
6
|
* Result of rendering a page template.
|
|
5
7
|
*/
|
|
@@ -8,6 +10,15 @@ export interface RenderResult {
|
|
|
8
10
|
html: string;
|
|
9
11
|
/** Number of templates loaded (layout + partials) */
|
|
10
12
|
templatesLoaded: number;
|
|
13
|
+
/** Open Graph image generated for the page, if any */
|
|
14
|
+
ogImage?: GeneratedOgImage | undefined;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Page-specific state for {@link renderPage}.
|
|
18
|
+
*/
|
|
19
|
+
export interface RenderPageExtras {
|
|
20
|
+
/** Image state of the page; without it `stati.image()` emits a plain `<img>` */
|
|
21
|
+
images?: PageImages | undefined;
|
|
11
22
|
}
|
|
12
23
|
/**
|
|
13
24
|
* Clear the cached template engine. Call this when templates change in dev mode
|
|
@@ -15,5 +26,5 @@ export interface RenderResult {
|
|
|
15
26
|
*/
|
|
16
27
|
export declare function clearTemplateEngineCache(): void;
|
|
17
28
|
export declare function createTemplateEngine(config: StatiConfig): Eta;
|
|
18
|
-
export declare function renderPage(page: PageModel, body: string, config: StatiConfig, eta: Eta, navigation?: NavNode[], allPages?: PageModel[], assets?: import('../types/index.js').StatiAssets, toc?: TocEntry[], logger?: Logger): Promise<RenderResult>;
|
|
29
|
+
export declare function renderPage(page: PageModel, body: string, config: StatiConfig, eta: Eta, navigation?: NavNode[], allPages?: PageModel[], assets?: import('../types/index.js').StatiAssets, toc?: TocEntry[], logger?: Logger, extras?: RenderPageExtras): Promise<RenderResult>;
|
|
19
30
|
//# sourceMappingURL=templates.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/core/templates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAG1B,OAAO,KAAK,EACV,WAAW,EACX,SAAS,EACT,OAAO,EAEP,QAAQ,EACR,MAAM,EACP,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/core/templates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAG1B,OAAO,KAAK,EACV,WAAW,EACX,SAAS,EACT,OAAO,EAEP,QAAQ,EACR,MAAM,EACP,MAAM,mBAAmB,CAAC;AAkB3B,OAAO,KAAK,EAAE,UAAU,EAAqB,MAAM,sBAAsB,CAAC;AAC1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,eAAe,EAAE,MAAM,CAAC;IACxB,sDAAsD;IACtD,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;CACxC;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,gFAAgF;IAChF,MAAM,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;CACjC;AAgOD;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,GAAG,CA2B7D;AAED,wBAAsB,UAAU,CAC9B,IAAI,EAAE,SAAS,EACf,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,EACnB,GAAG,EAAE,GAAG,EACR,UAAU,CAAC,EAAE,OAAO,EAAE,EACtB,QAAQ,CAAC,EAAE,SAAS,EAAE,EACtB,MAAM,CAAC,EAAE,OAAO,mBAAmB,EAAE,WAAW,EAChD,GAAG,CAAC,EAAE,QAAQ,EAAE,EAChB,MAAM,CAAC,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,gBAAgB,GACxB,OAAO,CAAC,YAAY,CAAC,CA0PvB"}
|