@stati/core 1.21.0 → 1.22.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/loader.d.ts +4 -0
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +49 -4
- package/dist/core/build.d.ts +6 -0
- package/dist/core/build.d.ts.map +1 -1
- package/dist/core/build.js +134 -24
- package/dist/core/dev.d.ts.map +1 -1
- package/dist/core/dev.js +86 -19
- package/dist/core/isg/builder.d.ts +4 -1
- package/dist/core/isg/builder.d.ts.map +1 -1
- package/dist/core/isg/builder.js +89 -2
- package/dist/core/isg/deps.d.ts +5 -0
- package/dist/core/isg/deps.d.ts.map +1 -1
- package/dist/core/isg/deps.js +38 -3
- package/dist/core/isg/dev-server-lock.d.ts +85 -0
- package/dist/core/isg/dev-server-lock.d.ts.map +1 -0
- package/dist/core/isg/dev-server-lock.js +248 -0
- package/dist/core/isg/hash.d.ts +4 -0
- package/dist/core/isg/hash.d.ts.map +1 -1
- package/dist/core/isg/hash.js +24 -1
- package/dist/core/isg/index.d.ts +3 -2
- package/dist/core/isg/index.d.ts.map +1 -1
- package/dist/core/isg/index.js +3 -2
- package/dist/core/markdown.d.ts +6 -0
- package/dist/core/markdown.d.ts.map +1 -1
- package/dist/core/markdown.js +23 -0
- package/dist/core/templates.js +5 -5
- 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/partial-validation.utils.js +2 -2
- package/dist/core/utils/paths.utils.d.ts +18 -0
- package/dist/core/utils/paths.utils.d.ts.map +1 -1
- package/dist/core/utils/paths.utils.js +23 -0
- package/dist/core/utils/tailwind-inventory.utils.d.ts +1 -16
- package/dist/core/utils/tailwind-inventory.utils.d.ts.map +1 -1
- package/dist/core/utils/tailwind-inventory.utils.js +35 -3
- package/dist/core/utils/typescript.utils.d.ts +9 -0
- package/dist/core/utils/typescript.utils.d.ts.map +1 -1
- package/dist/core/utils/typescript.utils.js +41 -0
- package/dist/env.d.ts +45 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +51 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/metrics/index.d.ts +1 -1
- package/dist/metrics/index.d.ts.map +1 -1
- package/dist/metrics/index.js +2 -0
- package/dist/metrics/recorder.d.ts.map +1 -1
- package/dist/metrics/types.d.ts +31 -0
- package/dist/metrics/types.d.ts.map +1 -1
- package/dist/metrics/utils/html-report.utils.d.ts +24 -0
- package/dist/metrics/utils/html-report.utils.d.ts.map +1 -0
- package/dist/metrics/utils/html-report.utils.js +1547 -0
- package/dist/metrics/utils/index.d.ts +1 -0
- package/dist/metrics/utils/index.d.ts.map +1 -1
- package/dist/metrics/utils/index.js +2 -0
- package/dist/metrics/utils/writer.utils.d.ts +6 -2
- package/dist/metrics/utils/writer.utils.d.ts.map +1 -1
- package/dist/metrics/utils/writer.utils.js +20 -4
- package/dist/search/generator.d.ts +1 -9
- package/dist/search/generator.d.ts.map +1 -1
- package/dist/search/generator.js +26 -2
- package/package.json +1 -1
package/dist/config/loader.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { StatiConfig } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Clear the config cache. Useful when config file changes are detected.
|
|
4
|
+
*/
|
|
5
|
+
export declare function clearConfigCache(): void;
|
|
2
6
|
/**
|
|
3
7
|
* Builds config file paths for a given directory.
|
|
4
8
|
* @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;AA6BrD;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AAqBD;;;;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,CA6GlF"}
|
package/dist/config/loader.js
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
1
|
+
import { existsSync, statSync } from 'node:fs';
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
4
|
import { validateISGConfig, ISGConfigurationError } from '../core/isg/validation.js';
|
|
5
5
|
import { compileStatiConfig, cleanupCompiledConfig } from '../core/utils/index.js';
|
|
6
6
|
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';
|
|
7
|
+
/**
|
|
8
|
+
* Config cache to prevent repeated dynamic imports during dev server rebuilds.
|
|
9
|
+
* Key is the resolved config file path.
|
|
10
|
+
*/
|
|
11
|
+
const configCache = new Map();
|
|
12
|
+
/**
|
|
13
|
+
* Clear the config cache. Useful when config file changes are detected.
|
|
14
|
+
*/
|
|
15
|
+
export function clearConfigCache() {
|
|
16
|
+
configCache.clear();
|
|
17
|
+
}
|
|
7
18
|
/**
|
|
8
19
|
* Default configuration values for Stati.
|
|
9
20
|
* Used as fallback when no configuration file is found.
|
|
@@ -62,14 +73,35 @@ export async function loadConfig(cwd = process.cwd()) {
|
|
|
62
73
|
if (!configPath) {
|
|
63
74
|
return DEFAULT_CONFIG;
|
|
64
75
|
}
|
|
76
|
+
const resolvedConfigPath = resolve(configPath);
|
|
77
|
+
// Check if we have a cached config and if it's still valid
|
|
78
|
+
const cached = configCache.get(resolvedConfigPath);
|
|
79
|
+
if (cached) {
|
|
80
|
+
try {
|
|
81
|
+
const currentMtime = statSync(resolvedConfigPath).mtimeMs;
|
|
82
|
+
if (currentMtime === cached.mtime) {
|
|
83
|
+
// Config file hasn't changed, return cached version
|
|
84
|
+
return cached.config;
|
|
85
|
+
}
|
|
86
|
+
// Config changed, clear this cache entry
|
|
87
|
+
configCache.delete(resolvedConfigPath);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// If we can't stat the file, clear cache and reload
|
|
91
|
+
configCache.delete(resolvedConfigPath);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
65
94
|
try {
|
|
66
95
|
let configModule;
|
|
67
96
|
let compiledPath;
|
|
97
|
+
// Get current mtime for cache-busting
|
|
98
|
+
const currentMtime = statSync(resolvedConfigPath).mtimeMs;
|
|
68
99
|
// If it's a .ts file, compile it first
|
|
69
100
|
if (configPath.endsWith('.ts')) {
|
|
70
101
|
try {
|
|
71
|
-
compiledPath = await compileStatiConfig(
|
|
72
|
-
|
|
102
|
+
compiledPath = await compileStatiConfig(resolvedConfigPath);
|
|
103
|
+
// Add cache-busting query parameter to force Node.js to re-evaluate the module
|
|
104
|
+
const configUrl = `${pathToFileURL(compiledPath).href}?t=${currentMtime}`;
|
|
73
105
|
configModule = await import(configUrl);
|
|
74
106
|
}
|
|
75
107
|
finally {
|
|
@@ -81,7 +113,8 @@ export async function loadConfig(cwd = process.cwd()) {
|
|
|
81
113
|
}
|
|
82
114
|
else {
|
|
83
115
|
// Existing logic for .js/.mjs
|
|
84
|
-
|
|
116
|
+
// Add cache-busting query parameter to force Node.js to re-evaluate the module
|
|
117
|
+
const configUrl = `${pathToFileURL(resolvedConfigPath).href}?t=${currentMtime}`;
|
|
85
118
|
configModule = await import(configUrl);
|
|
86
119
|
}
|
|
87
120
|
const userConfig = configModule.default || configModule;
|
|
@@ -106,6 +139,18 @@ export async function loadConfig(cwd = process.cwd()) {
|
|
|
106
139
|
}
|
|
107
140
|
throw error; // Re-throw non-ISG errors
|
|
108
141
|
}
|
|
142
|
+
// Cache the loaded config with its modification time
|
|
143
|
+
try {
|
|
144
|
+
const mtime = statSync(resolvedConfigPath).mtimeMs;
|
|
145
|
+
configCache.set(resolvedConfigPath, {
|
|
146
|
+
config: mergedConfig,
|
|
147
|
+
configPath: resolvedConfigPath,
|
|
148
|
+
mtime,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// If we can't stat, still return the config but don't cache
|
|
153
|
+
}
|
|
109
154
|
return mergedConfig;
|
|
110
155
|
}
|
|
111
156
|
catch (error) {
|
package/dist/core/build.d.ts
CHANGED
|
@@ -46,6 +46,12 @@ export interface BuildOptions {
|
|
|
46
46
|
coreVersion?: string | undefined;
|
|
47
47
|
/** Metrics collection options */
|
|
48
48
|
metrics?: MetricsOptions | undefined;
|
|
49
|
+
/** Skip updating cache entries after render (dev mode optimization) */
|
|
50
|
+
skipCacheUpdate?: boolean | undefined;
|
|
51
|
+
/** Skip saving cache manifest at end of build (dev mode optimization when caller already saved it) */
|
|
52
|
+
skipManifestSave?: boolean | undefined;
|
|
53
|
+
/** Skip copying static assets (dev mode optimization for template-only changes) */
|
|
54
|
+
skipAssetCopy?: boolean | undefined;
|
|
49
55
|
}
|
|
50
56
|
/**
|
|
51
57
|
* Extended build result including optional metrics.
|
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":"AA0DA,OAAO,KAAK,EAEV,UAAU,EACV,MAAM,EASP,MAAM,mBAAmB,CAAC;AAE3B,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,CAsB5E"}
|
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, autoInjectBundles, getBundlePathsForPage, formatBytes, } 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, autoInjectBundles, getBundlePathsForPage, formatBytes, } 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';
|
|
@@ -6,11 +6,11 @@ import { loadContent } from './content.js';
|
|
|
6
6
|
import { createMarkdownProcessor, renderMarkdown, extractToc } from './markdown.js';
|
|
7
7
|
import { createTemplateEngine, renderPage } from './templates.js';
|
|
8
8
|
import { buildNavigation } from './navigation.js';
|
|
9
|
-
import { loadCacheManifest, saveCacheManifest, shouldRebuildPage, createCacheEntry, updateCacheEntry, withBuildLock, computeNavigationHash, } from './isg/index.js';
|
|
9
|
+
import { loadCacheManifest, saveCacheManifest, shouldRebuildPage, createCacheEntry, updateCacheEntry, withBuildLock, computeNavigationHash, clearFileHashCache, clearTemplatePathCache, } from './isg/index.js';
|
|
10
10
|
import { generateSitemap, generateRobotsTxtFromConfig, autoInjectSEO, } from '../seo/index.js';
|
|
11
11
|
import { generateRSSFeeds, validateRSSConfig } from '../rss/index.js';
|
|
12
12
|
import { computeSearchIndexFilename, generateSearchIndex, writeSearchIndex, autoInjectSearchMeta, } from '../search/index.js';
|
|
13
|
-
import { getEnv } from '../env.js';
|
|
13
|
+
import { getEnv, isDevelopment } from '../env.js';
|
|
14
14
|
import { DEFAULT_OUT_DIR } from '../constants.js';
|
|
15
15
|
import { createMetricRecorder, noopMetricRecorder } from '../metrics/index.js';
|
|
16
16
|
/**
|
|
@@ -133,10 +133,19 @@ const defaultLogger = {
|
|
|
133
133
|
*/
|
|
134
134
|
export async function build(options = {}) {
|
|
135
135
|
const cacheDir = resolveCacheDir();
|
|
136
|
+
// Clear per-build caches at the start of each build
|
|
137
|
+
// This prevents stale data from previous builds and ensures consistent state
|
|
138
|
+
clearFileHashCache();
|
|
139
|
+
clearTemplatePathCache();
|
|
136
140
|
// Ensure cache directory exists before acquiring build lock
|
|
137
141
|
await ensureDir(cacheDir);
|
|
142
|
+
// In dev mode, bypass file-based build lock (dev server handles concurrency via isBuildingRef
|
|
143
|
+
// and dev server lock prevents multiple dev servers in the same directory)
|
|
144
|
+
if (isDevelopment()) {
|
|
145
|
+
return buildInternal(options);
|
|
146
|
+
}
|
|
138
147
|
// Use build lock to prevent concurrent builds, with force option to override
|
|
139
|
-
return
|
|
148
|
+
return withBuildLock(cacheDir, () => buildInternal(options), {
|
|
140
149
|
force: Boolean(options.force || options.clean), // Allow force if user explicitly requests it
|
|
141
150
|
timeout: 60000, // 1 minute timeout
|
|
142
151
|
});
|
|
@@ -218,6 +227,15 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
218
227
|
if (logger.startProgress) {
|
|
219
228
|
logger.startProgress(pages.length);
|
|
220
229
|
}
|
|
230
|
+
// Track timing for shouldRebuildPage
|
|
231
|
+
let totalShouldRebuildTime = 0;
|
|
232
|
+
let totalRenderTime = 0;
|
|
233
|
+
let totalFileWriteTime = 0;
|
|
234
|
+
let totalCacheEntryTime = 0;
|
|
235
|
+
// Batch pending writes for parallel execution (dev mode optimization)
|
|
236
|
+
const isDevMode = isDevelopment();
|
|
237
|
+
const pendingWrites = [];
|
|
238
|
+
const pendingCacheUpdates = [];
|
|
221
239
|
for (let i = 0; i < pages.length; i++) {
|
|
222
240
|
const page = pages[i];
|
|
223
241
|
if (!page)
|
|
@@ -238,7 +256,10 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
238
256
|
const cacheKey = relativePath.startsWith('/') ? relativePath : `/${relativePath}`;
|
|
239
257
|
const existingEntry = manifest.entries[cacheKey];
|
|
240
258
|
// Check if we should rebuild this page (considering ISG logic)
|
|
259
|
+
const shouldRebuildStart = performance.now();
|
|
241
260
|
const shouldRebuild = options.force || (await shouldRebuildPage(page, existingEntry, config, buildTime));
|
|
261
|
+
const shouldRebuildTime = performance.now() - shouldRebuildStart;
|
|
262
|
+
totalShouldRebuildTime += shouldRebuildTime;
|
|
242
263
|
if (!shouldRebuild) {
|
|
243
264
|
// Cache hit - skip rendering
|
|
244
265
|
cacheHits++;
|
|
@@ -279,7 +300,10 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
279
300
|
}),
|
|
280
301
|
};
|
|
281
302
|
// Render with template
|
|
303
|
+
const renderPageStart = performance.now();
|
|
282
304
|
const renderResult = await renderPage(page, htmlContent, config, eta, navigation, pages, assets, toc, logger);
|
|
305
|
+
const renderPageTime = performance.now() - renderPageStart;
|
|
306
|
+
totalRenderTime += renderPageTime;
|
|
283
307
|
let finalHtml = renderResult.html;
|
|
284
308
|
// Record templates loaded for this page
|
|
285
309
|
recorder.increment('templatesLoaded', renderResult.templatesLoaded);
|
|
@@ -312,15 +336,28 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
312
336
|
if (logger.updateProgress) {
|
|
313
337
|
logger.updateProgress('rendered', page.url, renderTime);
|
|
314
338
|
}
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
if (existingEntry) {
|
|
320
|
-
manifest.entries[cacheKey] = await updateCacheEntry(existingEntry, page, config, buildTime);
|
|
339
|
+
// In dev mode, batch writes for parallel execution to avoid Windows filesystem slowdown
|
|
340
|
+
if (isDevMode) {
|
|
341
|
+
pendingWrites.push({ outputPath, content: finalHtml });
|
|
342
|
+
pendingCacheUpdates.push({ cacheKey, page, existingEntry });
|
|
321
343
|
}
|
|
322
344
|
else {
|
|
323
|
-
|
|
345
|
+
// Production mode: write immediately
|
|
346
|
+
const fileWriteStart = performance.now();
|
|
347
|
+
await ensureDir(dirname(outputPath));
|
|
348
|
+
await writeFile(outputPath, finalHtml, 'utf-8');
|
|
349
|
+
totalFileWriteTime += performance.now() - fileWriteStart;
|
|
350
|
+
// Update cache manifest
|
|
351
|
+
if (!options.skipCacheUpdate) {
|
|
352
|
+
const cacheEntryStart = performance.now();
|
|
353
|
+
if (existingEntry) {
|
|
354
|
+
manifest.entries[cacheKey] = await updateCacheEntry(existingEntry, page, config, buildTime);
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
manifest.entries[cacheKey] = await createCacheEntry(page, config, buildTime);
|
|
358
|
+
}
|
|
359
|
+
totalCacheEntryTime += performance.now() - cacheEntryStart;
|
|
360
|
+
}
|
|
324
361
|
}
|
|
325
362
|
// Collect searchable page data if search is enabled
|
|
326
363
|
// Uses TOC entries and markdown content instead of parsing rendered HTML
|
|
@@ -334,6 +371,36 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
334
371
|
recorder.addToPhase('hookAfterRenderTotalMs', performance.now() - hookStart);
|
|
335
372
|
}
|
|
336
373
|
}
|
|
374
|
+
// In dev mode, execute batched writes in parallel
|
|
375
|
+
if (isDevMode && pendingWrites.length > 0) {
|
|
376
|
+
const fileWriteStart = performance.now();
|
|
377
|
+
// Ensure all directories exist first
|
|
378
|
+
const uniqueDirs = [...new Set(pendingWrites.map((w) => dirname(w.outputPath)))];
|
|
379
|
+
await Promise.all(uniqueDirs.map((dir) => ensureDir(dir)));
|
|
380
|
+
// Write all files in parallel
|
|
381
|
+
await Promise.all(pendingWrites.map((w) => writeFile(w.outputPath, w.content, 'utf-8')));
|
|
382
|
+
totalFileWriteTime = performance.now() - fileWriteStart;
|
|
383
|
+
// Update cache entries SEQUENTIALLY with fast update (reuse existing deps)
|
|
384
|
+
// This avoids expensive template dependency tracking on each incremental rebuild
|
|
385
|
+
if (!options.skipCacheUpdate) {
|
|
386
|
+
const cacheEntryStart = performance.now();
|
|
387
|
+
for (const { cacheKey, page, existingEntry } of pendingCacheUpdates) {
|
|
388
|
+
if (existingEntry) {
|
|
389
|
+
// Fast update: reuse existing deps, just update hashes and timestamp
|
|
390
|
+
manifest.entries[cacheKey] = await updateCacheEntry(existingEntry, page, config, buildTime, true);
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
manifest.entries[cacheKey] = await createCacheEntry(page, config, buildTime);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
totalCacheEntryTime = performance.now() - cacheEntryStart;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
// Record detailed phase timings in metrics
|
|
400
|
+
recorder.recordPhase('shouldRebuildTotalMs', totalShouldRebuildTime);
|
|
401
|
+
recorder.recordPhase('renderPageTotalMs', totalRenderTime);
|
|
402
|
+
recorder.recordPhase('fileWriteTotalMs', totalFileWriteTime);
|
|
403
|
+
recorder.recordPhase('cacheEntryTotalMs', totalCacheEntryTime);
|
|
337
404
|
// Display final progress summary
|
|
338
405
|
if (logger.endProgress) {
|
|
339
406
|
logger.endProgress();
|
|
@@ -369,13 +436,17 @@ async function copyStaticAssets(config, outDir, logger) {
|
|
|
369
436
|
/**
|
|
370
437
|
* Generates build statistics.
|
|
371
438
|
*/
|
|
372
|
-
async function generateBuildStats(pages, assetsCount, buildStartTime, outDir, cacheHits, cacheMisses, logger) {
|
|
439
|
+
async function generateBuildStats(pages, assetsCount, buildStartTime, outDir, cacheHits, cacheMisses, logger, recorder = noopMetricRecorder) {
|
|
373
440
|
const buildEndTime = Date.now();
|
|
441
|
+
const outputSizeStart = performance.now();
|
|
442
|
+
const outputSizeBytes = await getDirectorySize(outDir);
|
|
443
|
+
const dirSizeTime = performance.now() - outputSizeStart;
|
|
444
|
+
recorder.recordPhase('getDirectorySizeMs', dirSizeTime);
|
|
374
445
|
const buildStats = {
|
|
375
446
|
totalPages: pages.length,
|
|
376
447
|
assetsCount,
|
|
377
448
|
buildTimeMs: buildEndTime - buildStartTime,
|
|
378
|
-
outputSizeBytes
|
|
449
|
+
outputSizeBytes,
|
|
379
450
|
// Include ISG cache statistics
|
|
380
451
|
cacheHits,
|
|
381
452
|
cacheMisses,
|
|
@@ -427,6 +498,7 @@ async function buildInternal(options = {}) {
|
|
|
427
498
|
}
|
|
428
499
|
await ensureDir(outDir);
|
|
429
500
|
// Enable Tailwind class inventory tracking only if Tailwind is detected
|
|
501
|
+
const tailwindInitStart = performance.now();
|
|
430
502
|
const hasTailwind = await isTailwindUsed();
|
|
431
503
|
if (hasTailwind) {
|
|
432
504
|
enableInventoryTracking();
|
|
@@ -447,6 +519,8 @@ async function buildInternal(options = {}) {
|
|
|
447
519
|
logger.status(`Created inventory file for Tailwind scanner (will be populated after rendering)`);
|
|
448
520
|
}
|
|
449
521
|
}
|
|
522
|
+
const tailwindInitTime = performance.now() - tailwindInitStart;
|
|
523
|
+
recorder.recordPhase('tailwindInitMs', tailwindInitTime);
|
|
450
524
|
// Load cache manifest for ISG (after potential clean operation)
|
|
451
525
|
const endManifestLoadSpan = recorder.startSpan('cacheManifestLoadMs');
|
|
452
526
|
const { manifest } = await setupCacheAndManifest(cacheDir);
|
|
@@ -466,22 +540,35 @@ async function buildInternal(options = {}) {
|
|
|
466
540
|
// Store navigation hash in manifest for change detection in dev server
|
|
467
541
|
manifest.navigationHash = navigationHash;
|
|
468
542
|
// Compile TypeScript if enabled
|
|
543
|
+
// In dev mode, skip compilation since esbuild watcher handles it
|
|
469
544
|
let compiledBundles = [];
|
|
470
|
-
|
|
545
|
+
const isDevMode = isDevelopment();
|
|
546
|
+
if (config.typescript?.enabled && isDevMode) {
|
|
547
|
+
// In dev mode, detect existing bundles compiled by esbuild watcher
|
|
548
|
+
compiledBundles = await detectExistingBundles({
|
|
549
|
+
projectRoot: process.cwd(),
|
|
550
|
+
config: config.typescript,
|
|
551
|
+
outDir: config.outDir || DEFAULT_OUT_DIR,
|
|
552
|
+
mode: 'development',
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
else if (config.typescript?.enabled) {
|
|
471
556
|
const endTsSpan = recorder.startSpan('typescriptCompileMs');
|
|
472
557
|
compiledBundles = await compileTypeScript({
|
|
473
558
|
projectRoot: process.cwd(),
|
|
474
559
|
config: config.typescript,
|
|
475
560
|
outDir: config.outDir || DEFAULT_OUT_DIR,
|
|
476
|
-
mode:
|
|
561
|
+
mode: 'production',
|
|
477
562
|
logger,
|
|
478
563
|
});
|
|
479
564
|
endTsSpan();
|
|
480
565
|
}
|
|
481
566
|
// Pre-compute search index filename if search is enabled
|
|
567
|
+
// In dev mode, use a stable filename to simlplify testing and debugging
|
|
482
568
|
let searchIndexFilename;
|
|
483
569
|
if (config.search?.enabled) {
|
|
484
|
-
|
|
570
|
+
const buildId = isDevMode ? 'dev' : buildStartTime.toString();
|
|
571
|
+
searchIndexFilename = computeSearchIndexFilename(config.search, buildId);
|
|
485
572
|
}
|
|
486
573
|
// Process pages with ISG caching logic
|
|
487
574
|
if (logger.step) {
|
|
@@ -502,32 +589,46 @@ async function buildInternal(options = {}) {
|
|
|
502
589
|
logger.info(`Generating search index to ${searchIndexFilename}`);
|
|
503
590
|
const endSearchIndexSpan = recorder.startSpan('searchIndexGenerationMs');
|
|
504
591
|
const searchIndex = generateSearchIndex(searchablePages, config.search);
|
|
505
|
-
searchIndexMetadata = await writeSearchIndex(searchIndex, outDir, searchIndexFilename);
|
|
506
592
|
endSearchIndexSpan();
|
|
593
|
+
const writeStart = performance.now();
|
|
594
|
+
// In dev mode, skip write if content hasn't changed (template-only changes)
|
|
595
|
+
const skipIfUnchanged = isDevMode;
|
|
596
|
+
searchIndexMetadata = await writeSearchIndex(searchIndex, outDir, searchIndexFilename, skipIfUnchanged);
|
|
597
|
+
const writeTime = performance.now() - writeStart;
|
|
598
|
+
recorder.recordPhase('searchIndexWriteMs', writeTime);
|
|
507
599
|
logger.success(`Generated search index with ${searchIndexMetadata.documentCount} documents`);
|
|
508
600
|
}
|
|
509
601
|
// Record page rendering counts
|
|
510
602
|
recorder.increment('renderedPages', cacheMisses);
|
|
511
603
|
recorder.increment('cachedPages', cacheHits);
|
|
512
604
|
// Write Tailwind class inventory after all templates have been rendered (if Tailwind is used)
|
|
605
|
+
const tailwindStart = performance.now();
|
|
513
606
|
if (hasTailwind) {
|
|
514
607
|
const inventorySize = getInventorySize();
|
|
515
608
|
if (inventorySize > 0) {
|
|
516
|
-
|
|
609
|
+
// In dev mode, skip write if inventory size hasn't changed
|
|
610
|
+
const skipIfUnchanged = isDevMode;
|
|
611
|
+
await writeTailwindClassInventory(cacheDir, skipIfUnchanged);
|
|
517
612
|
logger.info('');
|
|
518
613
|
logger.status(`Generated Tailwind class inventory (${inventorySize} classes tracked)`);
|
|
519
614
|
}
|
|
520
615
|
// Disable inventory tracking after build
|
|
521
616
|
disableInventoryTracking();
|
|
522
617
|
}
|
|
523
|
-
|
|
618
|
+
const tailwindInventoryTime = performance.now() - tailwindStart;
|
|
619
|
+
recorder.recordPhase('tailwindInventoryMs', tailwindInventoryTime);
|
|
620
|
+
// Save updated cache manifest (skip if caller already saved it, e.g., dev mode template change)
|
|
524
621
|
const endManifestSaveSpan = recorder.startSpan('cacheManifestSaveMs');
|
|
525
|
-
|
|
622
|
+
if (!options.skipManifestSave) {
|
|
623
|
+
await saveCacheManifest(cacheDir, manifest);
|
|
624
|
+
}
|
|
526
625
|
endManifestSaveSpan();
|
|
527
|
-
// Copy static assets and count them
|
|
626
|
+
// Copy static assets and count them (skip for template-only changes in dev mode)
|
|
528
627
|
const endAssetSpan = recorder.startSpan('assetCopyMs');
|
|
529
628
|
let assetsCount = 0;
|
|
530
|
-
|
|
629
|
+
if (!options.skipAssetCopy) {
|
|
630
|
+
assetsCount = await copyStaticAssets(config, outDir, logger);
|
|
631
|
+
}
|
|
531
632
|
endAssetSpan();
|
|
532
633
|
recorder.increment('assetsCopied', assetsCount);
|
|
533
634
|
// Get current environment
|
|
@@ -616,8 +717,17 @@ async function buildInternal(options = {}) {
|
|
|
616
717
|
await config.hooks.afterAll({ config, pages });
|
|
617
718
|
endHookAfterAll();
|
|
618
719
|
}
|
|
619
|
-
//
|
|
620
|
-
const buildStats =
|
|
720
|
+
// Generate build statistics (skip in dev mode for performance)
|
|
721
|
+
const buildStats = isDevMode
|
|
722
|
+
? {
|
|
723
|
+
totalPages: pages.length,
|
|
724
|
+
assetsCount: 0,
|
|
725
|
+
buildTimeMs: Date.now() - buildStartTime,
|
|
726
|
+
outputSizeBytes: 0,
|
|
727
|
+
cacheHits,
|
|
728
|
+
cacheMisses,
|
|
729
|
+
}
|
|
730
|
+
: await generateBuildStats(pages, assetsCount, buildStartTime, outDir, cacheHits, cacheMisses, logger, recorder);
|
|
621
731
|
// Set ISG metrics
|
|
622
732
|
const totalPages = pages.length;
|
|
623
733
|
const cacheHitRate = totalPages > 0 ? cacheHits / totalPages : 0;
|
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":"AAMA,OAAO,KAAK,EAAe,MAAM,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/core/dev.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAe,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAkC7D,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;AAobD,wBAAsB,eAAe,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,SAAS,CAAC,CAigBxF"}
|