@stati/core 1.29.2 → 2.0.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/README.md +10 -13
- package/dist/config/loader.d.ts +9 -0
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +77 -1
- package/dist/core/build.d.ts.map +1 -1
- package/dist/core/build.js +39 -29
- package/dist/core/content.d.ts.map +1 -1
- package/dist/core/content.js +9 -7
- package/dist/core/dev.d.ts.map +1 -1
- package/dist/core/dev.js +156 -197
- package/dist/core/eta-plugin.d.ts +60 -0
- package/dist/core/eta-plugin.d.ts.map +1 -0
- package/dist/core/eta-plugin.js +143 -0
- package/dist/core/isg/builder.d.ts +37 -19
- package/dist/core/isg/builder.d.ts.map +1 -1
- package/dist/core/isg/builder.js +70 -153
- package/dist/core/isg/deps.d.ts +8 -70
- package/dist/core/isg/deps.d.ts.map +1 -1
- package/dist/core/isg/deps.js +10 -401
- package/dist/core/isg/hash.d.ts.map +1 -1
- package/dist/core/isg/hash.js +2 -1
- package/dist/core/isg/index.d.ts +2 -2
- package/dist/core/isg/index.d.ts.map +1 -1
- package/dist/core/isg/index.js +3 -3
- package/dist/core/isg/manifest.d.ts.map +1 -1
- package/dist/core/isg/manifest.js +6 -1
- package/dist/core/markdown.d.ts +1 -1
- package/dist/core/markdown.d.ts.map +1 -1
- package/dist/core/markdown.js +3 -2
- package/dist/core/template-index.d.ts +209 -0
- package/dist/core/template-index.d.ts.map +1 -0
- package/dist/core/template-index.js +484 -0
- package/dist/core/template-render.d.ts +137 -0
- package/dist/core/template-render.d.ts.map +1 -0
- package/dist/core/template-render.js +444 -0
- package/dist/core/templates.d.ts +29 -11
- package/dist/core/templates.d.ts.map +1 -1
- package/dist/core/templates.js +115 -196
- package/dist/core/utils/context-validation.utils.d.ts +10 -6
- package/dist/core/utils/context-validation.utils.d.ts.map +1 -1
- package/dist/core/utils/context-validation.utils.js +39 -16
- package/dist/core/utils/date.utils.d.ts +1 -1
- package/dist/core/utils/date.utils.js +1 -1
- package/dist/core/utils/dev-css-watcher.utils.d.ts.map +1 -1
- package/dist/core/utils/dev-css-watcher.utils.js +3 -2
- package/dist/core/utils/error-overlay.utils.d.ts.map +1 -1
- package/dist/core/utils/error-overlay.utils.js +7 -45
- package/dist/core/utils/front-matter.utils.d.ts +50 -0
- package/dist/core/utils/front-matter.utils.d.ts.map +1 -0
- package/dist/core/utils/front-matter.utils.js +157 -0
- package/dist/core/utils/index.d.ts +4 -5
- package/dist/core/utils/index.d.ts.map +1 -1
- package/dist/core/utils/index.js +5 -7
- package/dist/core/utils/paths.utils.d.ts +3 -3
- package/dist/core/utils/paths.utils.js +6 -6
- package/dist/core/utils/tailwind-inventory.utils.d.ts +2 -2
- package/dist/core/utils/tailwind-inventory.utils.d.ts.map +1 -1
- package/dist/core/utils/tailwind-inventory.utils.js +15 -6
- package/dist/core/utils/template-discovery.utils.d.ts +3 -14
- package/dist/core/utils/template-discovery.utils.d.ts.map +1 -1
- package/dist/core/utils/template-discovery.utils.js +2 -62
- package/dist/core/utils/template-errors.utils.d.ts +14 -1
- package/dist/core/utils/template-errors.utils.d.ts.map +1 -1
- package/dist/core/utils/template-errors.utils.js +45 -4
- package/dist/core/utils/theme.utils.d.ts +17 -0
- package/dist/core/utils/theme.utils.d.ts.map +1 -0
- package/dist/core/utils/theme.utils.js +59 -0
- package/dist/images/index.d.ts +1 -2
- package/dist/images/index.d.ts.map +1 -1
- package/dist/images/index.js +0 -1
- package/dist/images/markdown-rule.d.ts +1 -2
- package/dist/images/markdown-rule.d.ts.map +1 -1
- package/dist/images/og.d.ts +2 -15
- package/dist/images/og.d.ts.map +1 -1
- package/dist/images/og.js +4 -47
- package/dist/images/runtime.d.ts +24 -5
- package/dist/images/runtime.d.ts.map +1 -1
- package/dist/images/runtime.js +14 -12
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/metrics/noop.d.ts.map +1 -1
- package/dist/metrics/noop.js +4 -1
- package/dist/metrics/recorder.d.ts.map +1 -1
- package/dist/metrics/recorder.js +11 -8
- package/dist/metrics/types.d.ts +31 -5
- package/dist/metrics/types.d.ts.map +1 -1
- package/dist/metrics/utils/html-report.utils.d.ts +2 -1
- package/dist/metrics/utils/html-report.utils.d.ts.map +1 -1
- package/dist/metrics/utils/html-report.utils.js +1115 -1136
- package/dist/types/config.d.ts +18 -6
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/content.d.ts +83 -16
- package/dist/types/content.d.ts.map +1 -1
- package/dist/types/index.d.ts +4 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/isg.d.ts +48 -2
- package/dist/types/isg.d.ts.map +1 -1
- package/package.json +6 -6
- package/dist/core/utils/callable-partials.utils.d.ts +0 -60
- package/dist/core/utils/callable-partials.utils.d.ts.map +0 -1
- package/dist/core/utils/callable-partials.utils.js +0 -127
- package/dist/core/utils/partial-validation.utils.d.ts +0 -9
- package/dist/core/utils/partial-validation.utils.d.ts.map +0 -1
- package/dist/core/utils/partial-validation.utils.js +0 -107
package/README.md
CHANGED
|
@@ -260,12 +260,9 @@ export default defineConfig({
|
|
|
260
260
|
},
|
|
261
261
|
},
|
|
262
262
|
|
|
263
|
-
//
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
formatDate: (date) => new Date(date).toLocaleDateString('en-US'),
|
|
267
|
-
slugify: (text) => text.toLowerCase().replace(/\s+/g, '-'),
|
|
268
|
-
},
|
|
263
|
+
// Template helpers, called as stati.helpers.formatDate()
|
|
264
|
+
helpers: {
|
|
265
|
+
formatDate: (date) => new Date(date).toLocaleDateString('en-US'),
|
|
269
266
|
},
|
|
270
267
|
|
|
271
268
|
// Incremental Static Generation (enabled by default)
|
|
@@ -588,7 +585,7 @@ import type { TemplateContext, CacheManifest } from '@stati/core/types';
|
|
|
588
585
|
Stati supports rich Markdown features out of the box:
|
|
589
586
|
|
|
590
587
|
- **Front-matter** - YAML (default) or JSON (`---json`) metadata
|
|
591
|
-
- **Plugins** -
|
|
588
|
+
- **Plugins** - Any markdown-it plugin that supports markdown-it 15
|
|
592
589
|
- **Syntax highlighting** - Code blocks get `language-*` classes; add a plugin such as `markdown-it-prism` to highlight them
|
|
593
590
|
- **Custom rendering** - Configure parser behavior
|
|
594
591
|
- **Draft mode** - Mark pages as `draft: true`
|
|
@@ -611,10 +608,10 @@ export default defineConfig({
|
|
|
611
608
|
|
|
612
609
|
Powered by [Eta](https://eta.js.org) for fast, flexible templates:
|
|
613
610
|
|
|
614
|
-
- **Layouts** -
|
|
615
|
-
- **
|
|
616
|
-
- **
|
|
617
|
-
- **
|
|
611
|
+
- **Layouts** - Chosen by folder or front matter, sharing one page shell through `stati.layout()`
|
|
612
|
+
- **Includes** - Reusable templates with props and content through `stati.include()`
|
|
613
|
+
- **Helpers** - Your own functions, called as `stati.helpers.name()`
|
|
614
|
+
- **Typed context** - The `TemplateContext` type describes everything templates can read
|
|
618
615
|
- **Live reload** - The browser reloads when a template changes
|
|
619
616
|
|
|
620
617
|
**Template structure:**
|
|
@@ -627,11 +624,11 @@ Powered by [Eta](https://eta.js.org) for fast, flexible templates:
|
|
|
627
624
|
<title><%= stati.page.title %> - <%= stati.site.title %></title>
|
|
628
625
|
</head>
|
|
629
626
|
<body>
|
|
630
|
-
<%~ stati.partials
|
|
627
|
+
<%~ stati.include('partials/header') %>
|
|
631
628
|
<main>
|
|
632
629
|
<%~ stati.content %>
|
|
633
630
|
</main>
|
|
634
|
-
<%~ stati.partials
|
|
631
|
+
<%~ stati.include('partials/footer') %>
|
|
635
632
|
</body>
|
|
636
633
|
</html>
|
|
637
634
|
```
|
package/dist/config/loader.d.ts
CHANGED
|
@@ -3,6 +3,15 @@ import type { StatiConfig } from '../types/index.js';
|
|
|
3
3
|
* Clear the config cache. Useful when config file changes are detected.
|
|
4
4
|
*/
|
|
5
5
|
export declare function clearConfigCache(): void;
|
|
6
|
+
/**
|
|
7
|
+
* Checks the top-level options: each must be one Stati knows, and `helpers` must map names to
|
|
8
|
+
* functions.
|
|
9
|
+
*
|
|
10
|
+
* @param userConfig - The configuration as the config file exports it
|
|
11
|
+
* @throws {ConfigValidationError} For an unknown option, naming similar ones, or `helpers` that
|
|
12
|
+
* are not an object of functions
|
|
13
|
+
*/
|
|
14
|
+
export declare function validateConfigOptions(userConfig: Record<string, unknown>): void;
|
|
6
15
|
/**
|
|
7
16
|
* Builds config file paths for a given directory.
|
|
8
17
|
* @param cwd - Directory to search for config files
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAmCrD;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AAqDD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CA6B/E;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAExD;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,UAAU,CAAC,GAAG,GAAE,MAAsB,GAAG,OAAO,CAAC,WAAW,CAAC,CA6IlF"}
|
package/dist/config/loader.js
CHANGED
|
@@ -4,7 +4,7 @@ import { pathToFileURL } from 'node:url';
|
|
|
4
4
|
import { validateISGConfig, ISGConfigurationError } from '../core/isg/validation.js';
|
|
5
5
|
import { validateImagesConfig } from '../images/config.js';
|
|
6
6
|
import { ConfigValidationError } from './errors.js';
|
|
7
|
-
import { compileStatiConfig, cleanupCompiledConfig } from '../core/utils/index.js';
|
|
7
|
+
import { compileStatiConfig, cleanupCompiledConfig, findSimilarNames, } from '../core/utils/index.js';
|
|
8
8
|
import { DEFAULT_SRC_DIR, DEFAULT_OUT_DIR, DEFAULT_STATIC_DIR, DEFAULT_SITE_TITLE, DEFAULT_DEV_BASE_URL, DEFAULT_TTL_SECONDS, DEFAULT_MAX_AGE_CAP_DAYS, CONFIG_FILE_PATTERNS, } from '../constants.js';
|
|
9
9
|
/**
|
|
10
10
|
* Config cache to prevent repeated dynamic imports during dev server rebuilds.
|
|
@@ -35,6 +35,70 @@ const DEFAULT_CONFIG = {
|
|
|
35
35
|
maxAgeCapDays: DEFAULT_MAX_AGE_CAP_DAYS,
|
|
36
36
|
},
|
|
37
37
|
};
|
|
38
|
+
/**
|
|
39
|
+
* Every top-level `StatiConfig` option. Typed as a Record so a new option fails to compile
|
|
40
|
+
* until it is added.
|
|
41
|
+
*/
|
|
42
|
+
const CONFIG_KEYS = {
|
|
43
|
+
srcDir: true,
|
|
44
|
+
outDir: true,
|
|
45
|
+
staticDir: true,
|
|
46
|
+
site: true,
|
|
47
|
+
markdown: true,
|
|
48
|
+
helpers: true,
|
|
49
|
+
isg: true,
|
|
50
|
+
collections: true,
|
|
51
|
+
seo: true,
|
|
52
|
+
sitemap: true,
|
|
53
|
+
robots: true,
|
|
54
|
+
rss: true,
|
|
55
|
+
search: true,
|
|
56
|
+
typescript: true,
|
|
57
|
+
images: true,
|
|
58
|
+
dev: true,
|
|
59
|
+
preview: true,
|
|
60
|
+
hooks: true,
|
|
61
|
+
};
|
|
62
|
+
function describeConfigValue(value) {
|
|
63
|
+
if (value === null || value === undefined)
|
|
64
|
+
return String(value);
|
|
65
|
+
if (Array.isArray(value))
|
|
66
|
+
return 'an array';
|
|
67
|
+
if (typeof value === 'object')
|
|
68
|
+
return 'an object';
|
|
69
|
+
return typeof value === 'string' ? `string '${value}'` : `${typeof value} ${String(value)}`;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Checks the top-level options: each must be one Stati knows, and `helpers` must map names to
|
|
73
|
+
* functions.
|
|
74
|
+
*
|
|
75
|
+
* @param userConfig - The configuration as the config file exports it
|
|
76
|
+
* @throws {ConfigValidationError} For an unknown option, naming similar ones, or `helpers` that
|
|
77
|
+
* are not an object of functions
|
|
78
|
+
*/
|
|
79
|
+
export function validateConfigOptions(userConfig) {
|
|
80
|
+
const known = Object.keys(CONFIG_KEYS);
|
|
81
|
+
for (const key of Object.keys(userConfig)) {
|
|
82
|
+
if (!Object.hasOwn(CONFIG_KEYS, key)) {
|
|
83
|
+
const similar = findSimilarNames(key, known);
|
|
84
|
+
throw new ConfigValidationError(similar.length > 0
|
|
85
|
+
? `Unknown option ${key}. Did you mean ${similar.join(' or ')}?`
|
|
86
|
+
: `Unknown option ${key}. Allowed options: ${known.join(', ')}.`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const helpers = userConfig.helpers;
|
|
90
|
+
if (helpers === undefined) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (typeof helpers !== 'object' || helpers === null || Array.isArray(helpers)) {
|
|
94
|
+
throw new ConfigValidationError(`helpers must be an object of functions, such as helpers: { formatDate: (date) => ... } (got ${describeConfigValue(helpers)}).`);
|
|
95
|
+
}
|
|
96
|
+
for (const [name, helper] of Object.entries(helpers)) {
|
|
97
|
+
if (typeof helper !== 'function') {
|
|
98
|
+
throw new ConfigValidationError(`helpers.${name} must be a function that templates call as stati.helpers.${name}() (got ${describeConfigValue(helper)}).`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
38
102
|
/**
|
|
39
103
|
* Builds config file paths for a given directory.
|
|
40
104
|
* @param cwd - Directory to search for config files
|
|
@@ -151,6 +215,18 @@ export async function loadConfig(cwd = process.cwd()) {
|
|
|
151
215
|
}
|
|
152
216
|
throw error;
|
|
153
217
|
}
|
|
218
|
+
// Unknown options and helpers
|
|
219
|
+
try {
|
|
220
|
+
validateConfigOptions(userConfig);
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
if (error instanceof ConfigValidationError) {
|
|
224
|
+
throw new ConfigValidationError(`Invalid configuration in ${configPath}:\n${error.message}`, {
|
|
225
|
+
cause: error,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
154
230
|
// Cache the loaded config with its modification time
|
|
155
231
|
try {
|
|
156
232
|
const mtime = statSync(resolvedConfigPath).mtimeMs;
|
package/dist/core/build.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/core/build.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/core/build.ts"],"names":[],"mappings":"AA0EA,OAAO,KAAK,EAEV,UAAU,EACV,MAAM,EAQP,MAAM,mBAAmB,CAAC;AAK3B,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxE;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,gCAAgC;IAChC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,iDAAiD;IACjD,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,uCAAuC;IACvC,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,qCAAqC;IACrC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,8BAA8B;IAC9B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,+BAA+B;IAC/B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,iCAAiC;IACjC,OAAO,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IACrC,uEAAuE;IACvE,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACtC,sGAAsG;IACtG,gBAAgB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,mFAAmF;IACnF,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACrC;AAED;;GAEG;AACH,MAAM,WAAW,WAAY,SAAQ,UAAU;IAC7C,wDAAwD;IACxD,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAkGD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAsB,KAAK,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,CAqB5E"}
|
package/dist/core/build.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ensureDir, writeFile, remove, pathExists, stat, readdir, copyFile, resolveOutDir, resolveStaticDir, resolveCacheDir, enableInventoryTracking, disableInventoryTracking, clearInventory, writeTailwindClassInventory, getInventorySize, isTailwindUsed, loadPreviousInventory, compileTypeScript, detectExistingBundles, removeStaleBundleFiles, autoInjectBundles, getBundlePathsForPage, formatBytes, isVirtualPage, getPageOutputFile, } from './utils/index.js';
|
|
1
|
+
import { ensureDir, writeFile, remove, pathExists, stat, readdir, copyFile, resolveOutDir, resolveStaticDir, resolveCacheDir, enableInventoryTracking, disableInventoryTracking, clearInventory, writeTailwindClassInventory, getInventorySize, isTailwindUsed, loadPreviousInventory, compileTypeScript, detectExistingBundles, removeStaleBundleFiles, autoInjectBundles, getBundlePathsForPage, formatBytes, isVirtualPage, getPageOutputFile, resolveSrcDir, } from './utils/index.js';
|
|
2
2
|
import { join, dirname, relative, posix } from 'node:path';
|
|
3
3
|
import { performance } from 'node:perf_hooks';
|
|
4
4
|
import { loadConfig } from '../config/loader.js';
|
|
@@ -7,8 +7,9 @@ import { expandPages } from './expand.js';
|
|
|
7
7
|
import { createSummaryHasher, resolveCollectionPath, resolveSummaryFields, } from './collections.js';
|
|
8
8
|
import { createMarkdownProcessor, renderMarkdown, extractToc } from './markdown.js';
|
|
9
9
|
import { createTemplateEngine, renderPage } from './templates.js';
|
|
10
|
+
import { loadTemplateIndex } from './template-index.js';
|
|
10
11
|
import { buildNavigation } from './navigation.js';
|
|
11
|
-
import { loadCacheManifest, saveCacheManifest, shouldRebuildPage, createCacheEntry, updateCacheEntry, hasRecordedReads, pruneOrphanedPages, withBuildLock, computeNavigationHash, computeContentHash, clearFileHashCache,
|
|
12
|
+
import { loadCacheManifest, saveCacheManifest, shouldRebuildPage, createCacheEntry, updateCacheEntry, hasRecordedReads, pruneOrphanedPages, withBuildLock, computeNavigationHash, computeContentHash, clearFileHashCache, } from './isg/index.js';
|
|
12
13
|
import { generateSitemap, generateRobotsTxtFromConfig, autoInjectSEO, } from '../seo/index.js';
|
|
13
14
|
import { generateRSSFeeds, validateRSSConfig } from '../rss/index.js';
|
|
14
15
|
import { computeSearchIndexFilename, generateSearchIndex, writeSearchIndex, removeStaleSearchIndexes, autoInjectSearchMeta, isGeneratedPageExcludedFromSearch, } from '../search/index.js';
|
|
@@ -139,7 +140,6 @@ export async function build(options = {}) {
|
|
|
139
140
|
// Clear per-build caches at the start of each build
|
|
140
141
|
// This prevents stale data from previous builds and ensures consistent state
|
|
141
142
|
clearFileHashCache();
|
|
142
|
-
clearTemplatePathCache();
|
|
143
143
|
// Ensure cache directory exists before acquiring build lock
|
|
144
144
|
await ensureDir(cacheDir);
|
|
145
145
|
// In dev mode, bypass file-based build lock (dev server handles concurrency via isBuildingRef
|
|
@@ -229,8 +229,7 @@ async function writeSearchIndexBeforeRendering(pages, config, md, outDir, logger
|
|
|
229
229
|
const isDevMode = isDevelopment();
|
|
230
230
|
const endSearchIndexSpan = recorder.startSpan('searchIndexGenerationMs');
|
|
231
231
|
// TOC entries and markdown content, the same data a render produces, without rendering.
|
|
232
|
-
// Sorted by URL
|
|
233
|
-
// would change the index content, and with it the name, on every build.
|
|
232
|
+
// Sorted by URL, so the index content, and with it the name, changes only when pages do.
|
|
234
233
|
const tocEnabled = config.markdown?.toc !== false;
|
|
235
234
|
const byUrl = (a, b) => a.url.localeCompare(b.url) || a.sourcePath.localeCompare(b.sourcePath);
|
|
236
235
|
const searchablePages = pages
|
|
@@ -290,9 +289,11 @@ function hashableData(value) {
|
|
|
290
289
|
/**
|
|
291
290
|
* Processes pages with ISG caching logic.
|
|
292
291
|
*/
|
|
293
|
-
async function processPagesWithCache(pages, manifest, config, outDir, md, eta, navigation, buildTime, options, logger, compiledBundles, recorder = noopMetricRecorder, imageRuntime = null, navigationHash = computeNavigationHash(navigation)) {
|
|
292
|
+
async function processPagesWithCache(pages, manifest, config, outDir, md, eta, navigation, buildTime, options, logger, compiledBundles, recorder = noopMetricRecorder, imageRuntime = null, navigationHash = computeNavigationHash(navigation), templateIndex) {
|
|
294
293
|
let cacheHits = 0;
|
|
295
294
|
let cacheMisses = 0;
|
|
295
|
+
// Every .eta file under the source directory, read once for all pages
|
|
296
|
+
const index = templateIndex ?? (await loadTemplateIndex(resolveSrcDir(config)));
|
|
296
297
|
// Build context
|
|
297
298
|
const buildContext = { config, pages };
|
|
298
299
|
// Run beforeAll hook
|
|
@@ -326,7 +327,9 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
326
327
|
}
|
|
327
328
|
// Track timing for shouldRebuildPage
|
|
328
329
|
let totalShouldRebuildTime = 0;
|
|
330
|
+
let totalMarkdownTime = 0;
|
|
329
331
|
let totalRenderTime = 0;
|
|
332
|
+
let totalPostRenderTime = 0;
|
|
330
333
|
let totalFileWriteTime = 0;
|
|
331
334
|
let totalCacheEntryTime = 0;
|
|
332
335
|
// Batch pending writes for parallel execution (dev mode optimization)
|
|
@@ -402,7 +405,7 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
402
405
|
(existingEntry !== undefined && !readsKnown) ||
|
|
403
406
|
// A cached page whose generated images are gone is rebuilt to regenerate them
|
|
404
407
|
(imageRuntime !== null && !imageRuntime.outputsExist(existingEntry?.images)) ||
|
|
405
|
-
(await shouldRebuildPage(page, existingEntry, config, pages, buildTime, renderInputsHash));
|
|
408
|
+
(await shouldRebuildPage(page, existingEntry, config, pages, buildTime, renderInputsHash, index));
|
|
406
409
|
const shouldRebuildTime = performance.now() - shouldRebuildStart;
|
|
407
410
|
totalShouldRebuildTime += shouldRebuildTime;
|
|
408
411
|
if (!shouldRebuild) {
|
|
@@ -427,18 +430,23 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
427
430
|
// Image state for this page: markup, dependencies and generated files
|
|
428
431
|
const pageImages = imageRuntime?.createPageContext(page);
|
|
429
432
|
// Render markdown to HTML with TOC extraction
|
|
433
|
+
const markdownStart = performance.now();
|
|
430
434
|
const tocEnabled = config.markdown?.toc !== false;
|
|
431
435
|
const { html: htmlContent, toc } = renderMarkdown(page.content, md, tocEnabled, {
|
|
432
436
|
images: pageImages,
|
|
433
437
|
});
|
|
438
|
+
totalMarkdownTime += performance.now() - markdownStart;
|
|
434
439
|
// Render with template
|
|
435
440
|
const renderPageStart = performance.now();
|
|
436
|
-
const renderResult = await renderPage(page, htmlContent, config, eta, navigation, pages, assets, toc, logger, { images: pageImages });
|
|
441
|
+
const renderResult = await renderPage(page, htmlContent, config, eta, navigation, pages, assets, toc, logger, { images: pageImages, templateIndex: index });
|
|
437
442
|
const renderPageTime = performance.now() - renderPageStart;
|
|
438
443
|
totalRenderTime += renderPageTime;
|
|
444
|
+
const postRenderStart = performance.now();
|
|
439
445
|
let finalHtml = renderResult.html;
|
|
440
|
-
// Record templates
|
|
446
|
+
// Record the templates this page used and how often templates rendered
|
|
441
447
|
recorder.increment('templatesLoaded', renderResult.templatesLoaded);
|
|
448
|
+
recorder.increment('templateRenders', renderResult.templateRenders);
|
|
449
|
+
recorder.increment('includeCacheHits', renderResult.includeCacheHits);
|
|
442
450
|
// Auto-inject SEO tags if enabled
|
|
443
451
|
if (config.seo?.autoInject !== false) {
|
|
444
452
|
const injectOptions = {
|
|
@@ -474,13 +482,16 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
474
482
|
assetDeps: committedImages?.assetDeps ?? [],
|
|
475
483
|
images: committedImages?.images ?? [],
|
|
476
484
|
reads: renderResult.reads,
|
|
485
|
+
templates: renderResult.templates,
|
|
486
|
+
templateLookups: renderResult.templateLookups,
|
|
477
487
|
};
|
|
478
488
|
const renderedInputsHash = renderInputsHashFor(renderResult.reads);
|
|
479
|
-
|
|
489
|
+
totalPostRenderTime += performance.now() - postRenderStart;
|
|
490
|
+
const renderTime = performance.now() - startTime;
|
|
480
491
|
// Record page timing for rendered pages (includes template count)
|
|
481
|
-
recorder.recordPageTiming(page.url, renderTime, false, renderResult.templatesLoaded);
|
|
492
|
+
recorder.recordPageTiming(page.url, renderTime, false, renderResult.templatesLoaded, renderResult.templateRenders);
|
|
482
493
|
if (logger.updateProgress) {
|
|
483
|
-
logger.updateProgress('rendered', page.url, renderTime);
|
|
494
|
+
logger.updateProgress('rendered', page.url, Math.round(renderTime));
|
|
484
495
|
}
|
|
485
496
|
// In dev mode, batch writes for parallel execution to avoid Windows filesystem slowdown
|
|
486
497
|
if (isDevMode) {
|
|
@@ -502,12 +513,9 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
502
513
|
// Update cache manifest
|
|
503
514
|
if (!options.skipCacheUpdate) {
|
|
504
515
|
const cacheEntryStart = performance.now();
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
else {
|
|
509
|
-
manifest.entries[cacheKey] = await createCacheEntry(page, config, pages, buildTime, renderedInputsHash, renderedAssets);
|
|
510
|
-
}
|
|
516
|
+
manifest.entries[cacheKey] = existingEntry
|
|
517
|
+
? await updateCacheEntry(existingEntry, page, config, buildTime, renderedInputsHash, renderedAssets)
|
|
518
|
+
: await createCacheEntry(page, config, buildTime, renderedInputsHash, renderedAssets);
|
|
511
519
|
totalCacheEntryTime += performance.now() - cacheEntryStart;
|
|
512
520
|
}
|
|
513
521
|
}
|
|
@@ -527,26 +535,22 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
527
535
|
// Write all files in parallel
|
|
528
536
|
await Promise.all(pendingWrites.map((w) => writeFile(w.outputPath, w.content, 'utf-8')));
|
|
529
537
|
totalFileWriteTime = performance.now() - fileWriteStart;
|
|
530
|
-
// Update cache entries
|
|
531
|
-
// This avoids expensive template dependency tracking on each incremental rebuild
|
|
538
|
+
// Update cache entries sequentially, from the templates each render recorded
|
|
532
539
|
if (!options.skipCacheUpdate) {
|
|
533
540
|
const cacheEntryStart = performance.now();
|
|
534
541
|
for (const { cacheKey, page, existingEntry, renderInputsHash, renderedAssets, } of pendingCacheUpdates) {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
renderInputsHash, renderedAssets);
|
|
539
|
-
}
|
|
540
|
-
else {
|
|
541
|
-
manifest.entries[cacheKey] = await createCacheEntry(page, config, pages, buildTime, renderInputsHash, renderedAssets);
|
|
542
|
-
}
|
|
542
|
+
manifest.entries[cacheKey] = existingEntry
|
|
543
|
+
? await updateCacheEntry(existingEntry, page, config, buildTime, renderInputsHash, renderedAssets)
|
|
544
|
+
: await createCacheEntry(page, config, buildTime, renderInputsHash, renderedAssets);
|
|
543
545
|
}
|
|
544
546
|
totalCacheEntryTime = performance.now() - cacheEntryStart;
|
|
545
547
|
}
|
|
546
548
|
}
|
|
547
549
|
// Record detailed phase timings in metrics
|
|
548
550
|
recorder.recordPhase('shouldRebuildTotalMs', totalShouldRebuildTime);
|
|
551
|
+
recorder.recordPhase('markdownRenderTotalMs', totalMarkdownTime);
|
|
549
552
|
recorder.recordPhase('renderPageTotalMs', totalRenderTime);
|
|
553
|
+
recorder.recordPhase('postRenderTotalMs', totalPostRenderTime);
|
|
550
554
|
recorder.recordPhase('fileWriteTotalMs', totalFileWriteTime);
|
|
551
555
|
recorder.recordPhase('cacheEntryTotalMs', totalCacheEntryTime);
|
|
552
556
|
// Display final progress summary
|
|
@@ -732,9 +736,15 @@ async function buildInternal(options = {}) {
|
|
|
732
736
|
if (logger.step) {
|
|
733
737
|
console.log(); // Add spacing before page processing
|
|
734
738
|
}
|
|
739
|
+
// Every template under the source directory, read once per build; a dev server session
|
|
740
|
+
// keeps one between rebuilds and updates it as templates are added and deleted
|
|
741
|
+
const endTemplateIndexSpan = recorder.startSpan('templateIndexMs');
|
|
742
|
+
const templateIndex = await loadTemplateIndex(resolveSrcDir(config));
|
|
743
|
+
endTemplateIndexSpan();
|
|
744
|
+
recorder.increment('templatesIndexed', templateIndex.size);
|
|
735
745
|
const endPageRenderSpan = recorder.startSpan('pageRenderingMs');
|
|
736
746
|
const buildTime = new Date();
|
|
737
|
-
const pageProcessingResult = await processPagesWithCache(pages, manifest, config, outDir, md, eta, navigation, buildTime, options, logger, compiledBundles, recorder, imageRuntime, navigationHash);
|
|
747
|
+
const pageProcessingResult = await processPagesWithCache(pages, manifest, config, outDir, md, eta, navigation, buildTime, options, logger, compiledBundles, recorder, imageRuntime, navigationHash, templateIndex);
|
|
738
748
|
endPageRenderSpan();
|
|
739
749
|
const { cacheHits, cacheMisses, searchIndexFilename } = pageProcessingResult;
|
|
740
750
|
// Remove the output of pages that no longer exist, such as a deleted post or a tag no post
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../src/core/content.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../src/core/content.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGhE;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EACnB,aAAa,CAAC,EAAE,OAAO,GACtB,OAAO,CAAC,SAAS,EAAE,CAAC,CAsDtB"}
|
package/dist/core/content.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import glob from '
|
|
2
|
-
import { readFile, resolveSrcDir, getFrontMatterDate } from './utils/index.js';
|
|
3
|
-
import matter from 'gray-matter';
|
|
1
|
+
import { glob } from 'tinyglobby';
|
|
2
|
+
import { readFile, resolveSrcDir, getFrontMatterDate, parseFrontMatter } from './utils/index.js';
|
|
4
3
|
import { relative, dirname, basename } from 'node:path';
|
|
5
4
|
import { MARKDOWN_EXTENSION } from '../constants.js';
|
|
6
5
|
/**
|
|
@@ -21,12 +20,15 @@ import { MARKDOWN_EXTENSION } from '../constants.js';
|
|
|
21
20
|
*/
|
|
22
21
|
export async function loadContent(config, includeDrafts) {
|
|
23
22
|
const contentDir = resolveSrcDir(config);
|
|
24
|
-
// Exclude folders starting with underscore from content discovery
|
|
25
|
-
|
|
23
|
+
// Exclude folders starting with underscore from content discovery. Sorted, because the
|
|
24
|
+
// file system lists files in no fixed order, and pages that sort equally in listings and
|
|
25
|
+
// feeds keep the order they were found in.
|
|
26
|
+
const files = (await glob('**/*.md', {
|
|
26
27
|
cwd: contentDir,
|
|
27
28
|
absolute: true,
|
|
28
29
|
ignore: ['**/_*/**', '_*/**'],
|
|
29
|
-
|
|
30
|
+
expandDirectories: false,
|
|
31
|
+
})).sort();
|
|
30
32
|
const pages = [];
|
|
31
33
|
for (const file of files) {
|
|
32
34
|
const content = await readFile(file, 'utf-8');
|
|
@@ -34,7 +36,7 @@ export async function loadContent(config, includeDrafts) {
|
|
|
34
36
|
console.warn(`Skipping file ${file}: could not read content`);
|
|
35
37
|
continue;
|
|
36
38
|
}
|
|
37
|
-
const { data: frontMatter, content: markdown } =
|
|
39
|
+
const { data: frontMatter, content: markdown } = parseFrontMatter(content, file);
|
|
38
40
|
// Skip drafts unless explicitly included
|
|
39
41
|
if (frontMatter.draft && !includeDrafts) {
|
|
40
42
|
continue;
|
package/dist/core/dev.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/core/dev.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAe,MAAM,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/core/dev.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAe,MAAM,EAAE,MAAM,mBAAmB,CAAC;AA4C7D,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;CACb;AAyaD,wBAAsB,eAAe,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,SAAS,CAAC,CAwdxF"}
|