@moku-labs/web 1.2.0 → 1.3.1
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/browser.d.mts +37 -5
- package/dist/browser.mjs +29 -6
- package/dist/index.cjs +572 -101
- package/dist/index.d.cts +165 -17
- package/dist/index.d.mts +165 -17
- package/dist/index.mjs +572 -101
- package/package.json +2 -2
package/dist/browser.d.mts
CHANGED
|
@@ -1853,7 +1853,7 @@ type DataProvider = {
|
|
|
1853
1853
|
*/
|
|
1854
1854
|
declare const dataPlugin: import("@moku-labs/core").PluginInstance<"data", DataConfig, DataState, DataProvider, {}> & Record<never, never>;
|
|
1855
1855
|
declare namespace types_d_exports {
|
|
1856
|
-
export { Api, Article, ArticleCard, ComputedFields, Config, ContentApiContext, ContentEvents, ContentProvider, ContentProviderState, FileSystemContentOptions, Frontmatter, State };
|
|
1856
|
+
export { Api, Article, ArticleCard, ComputedFields, Config, ContentApiContext, ContentEvents, ContentProvider, ContentProviderState, FileSystemContentOptions, Frontmatter, LoadAllOptions, State };
|
|
1857
1857
|
}
|
|
1858
1858
|
/**
|
|
1859
1859
|
* YAML frontmatter parsed from each article file.
|
|
@@ -2028,11 +2028,18 @@ type Config = {
|
|
|
2028
2028
|
*
|
|
2029
2029
|
* @example
|
|
2030
2030
|
* ```ts
|
|
2031
|
-
* { articles: new Map() }
|
|
2031
|
+
* { articles: new Map(), loadedAll: null }
|
|
2032
2032
|
* ```
|
|
2033
2033
|
*/
|
|
2034
2034
|
type State = {
|
|
2035
2035
|
/** Article cache keyed locale -> (slug -> Article). Starts empty. */articles: Map<string, Map<string, Article>>;
|
|
2036
|
+
/**
|
|
2037
|
+
* Memoized full `loadAll()` result, or `null` when not yet loaded / invalidated. List-route
|
|
2038
|
+
* loaders call `loadAll()` once PER PAGE, so without this every page re-reads + re-renders
|
|
2039
|
+
* every article (the dev-loop killer). The memo makes repeated calls O(1); `invalidate()`
|
|
2040
|
+
* clears it so a dev rebuild reloads (re-resolving only the changed slugs). Starts `null`.
|
|
2041
|
+
*/
|
|
2042
|
+
loadedAll: Map<string, Article[]> | null;
|
|
2036
2043
|
};
|
|
2037
2044
|
/**
|
|
2038
2045
|
* Notification-only events emitted by the content plugin.
|
|
@@ -2072,6 +2079,26 @@ type ContentApiContext = {
|
|
|
2072
2079
|
defaultLocale: () => string; /** The resolved content source (merged from `config.providers`). */
|
|
2073
2080
|
provider: ContentProvider;
|
|
2074
2081
|
};
|
|
2082
|
+
/**
|
|
2083
|
+
* Options for {@link Api.loadAll}.
|
|
2084
|
+
*
|
|
2085
|
+
* @example
|
|
2086
|
+
* ```ts
|
|
2087
|
+
* await app.content.loadAll({ reuse: true });
|
|
2088
|
+
* ```
|
|
2089
|
+
*/
|
|
2090
|
+
type LoadAllOptions = {
|
|
2091
|
+
/**
|
|
2092
|
+
* Reuse the per-build memo + per-slug cache (re-resolving only slugs a preceding
|
|
2093
|
+
* `invalidate()` dropped). Default `true` — this is what keeps repeated `loadAll()` calls
|
|
2094
|
+
* (a list route's loader runs once per page) cheap, and makes a dev rebuild re-render only
|
|
2095
|
+
* changed articles. Set `false` to force a FRESH full reload (cold build / an
|
|
2096
|
+
* unclassifiable change), which re-reads + re-renders every article and rebuilds the memo.
|
|
2097
|
+
* The post-sort `contentId` ordinals are always recomputed across the full set, so order +
|
|
2098
|
+
* ids match a full load either way.
|
|
2099
|
+
*/
|
|
2100
|
+
reuse?: boolean;
|
|
2101
|
+
};
|
|
2075
2102
|
/**
|
|
2076
2103
|
* Public API for the content plugin.
|
|
2077
2104
|
*
|
|
@@ -2082,10 +2109,15 @@ type ContentApiContext = {
|
|
|
2082
2109
|
*/
|
|
2083
2110
|
type Api = {
|
|
2084
2111
|
/**
|
|
2085
|
-
* Load every article across every active locale, returning a locale-keyed
|
|
2086
|
-
*
|
|
2112
|
+
* Load every article across every active locale, returning a locale-keyed map of
|
|
2113
|
+
* date-descending Article arrays. Emits content:ready (once per actual load). Cache-first
|
|
2114
|
+
* + memoized: repeated calls (e.g. a list route's loader on every page) return the SAME
|
|
2115
|
+
* cached result with no re-read — so treat the result as READ-ONLY (do not sort/mutate it
|
|
2116
|
+
* in place; slice/copy first). Pass `{ reuse: false }` to force a fresh full reload.
|
|
2117
|
+
*
|
|
2118
|
+
* @param options - Optional load behaviour ({@link LoadAllOptions}); default reuses the cache.
|
|
2087
2119
|
*/
|
|
2088
|
-
loadAll(): Promise<Map<string, Article[]>>;
|
|
2120
|
+
loadAll(options?: LoadAllOptions): Promise<Map<string, Article[]>>;
|
|
2089
2121
|
/**
|
|
2090
2122
|
* Resolve and render a single article for one locale, with locale fallback.
|
|
2091
2123
|
*
|
package/dist/browser.mjs
CHANGED
|
@@ -4355,18 +4355,24 @@ function isPublished(article, isProduction) {
|
|
|
4355
4355
|
* locale collection: existing files only, drafts dropped in production, sorted
|
|
4356
4356
|
* date-descending. The single load+filter+sort step behind {@link createContentApi.loadAll}.
|
|
4357
4357
|
*
|
|
4358
|
+
* When a `cached` map is supplied (incremental dev rebuild), a slug already present in it
|
|
4359
|
+
* is reused as-is (skipping the re-read + Markdown/Shiki re-render); only slugs absent
|
|
4360
|
+
* from it — the ones a preceding `invalidate()` dropped, plus any never-loaded — are
|
|
4361
|
+
* resolved fresh. With no `cached` map every slug is resolved (the full load).
|
|
4362
|
+
*
|
|
4358
4363
|
* @param ctx - Kernel-free domain context (provider + i18n helpers + stage).
|
|
4359
4364
|
* @param slugs - Every known article slug from the provider.
|
|
4360
4365
|
* @param locale - The locale to resolve and collect.
|
|
4366
|
+
* @param cached - Optional per-locale article cache to reuse for unchanged slugs.
|
|
4361
4367
|
* @returns The published (date-descending) articles for this locale.
|
|
4362
4368
|
* @example
|
|
4363
4369
|
* ```ts
|
|
4364
4370
|
* const present = await loadAndFilterArticles(ctx, slugs, "en");
|
|
4365
4371
|
* ```
|
|
4366
4372
|
*/
|
|
4367
|
-
async function loadAndFilterArticles(ctx, slugs, locale) {
|
|
4373
|
+
async function loadAndFilterArticles(ctx, slugs, locale, cached) {
|
|
4368
4374
|
const isProduction = ctx.global.stage === "production";
|
|
4369
|
-
return (await Promise.all(slugs.map((slug) => resolveArticle(ctx, slug, locale)))).filter((article) => article !== null).filter((article) => isPublished(article, isProduction)).toSorted(byDateDescending);
|
|
4375
|
+
return (await Promise.all(slugs.map((slug) => cached?.get(slug) ?? resolveArticle(ctx, slug, locale)))).filter((article) => article !== null).filter((article) => isPublished(article, isProduction)).toSorted(byDateDescending);
|
|
4370
4376
|
}
|
|
4371
4377
|
/**
|
|
4372
4378
|
* Derive the article slug from a source file path — the parent directory name
|
|
@@ -4403,19 +4409,31 @@ function createContentApi(ctx) {
|
|
|
4403
4409
|
* Load every article across every active locale (locale fallback, production
|
|
4404
4410
|
* draft exclusion, date sort, `contentId` after sort), cache them, emit `content:ready`.
|
|
4405
4411
|
*
|
|
4412
|
+
* Cache-first by default: repeated calls return the per-build memo (list-route loaders
|
|
4413
|
+
* call this once PER PAGE — without the memo every page would re-read + re-render every
|
|
4414
|
+
* article, the dev-loop killer), and a rebuild after `invalidate()` re-resolves only the
|
|
4415
|
+
* dropped slugs while reusing the cached articles for the rest (`contentId` ordinals are
|
|
4416
|
+
* still recomputed across the FULL sorted set, so ids + order match a full load). Pass
|
|
4417
|
+
* `{ reuse: false }` to force a FRESH full reload (cold build / an unclassifiable change
|
|
4418
|
+
* where the caller cannot pinpoint what changed) — this bypasses the memo + per-slug cache.
|
|
4419
|
+
*
|
|
4420
|
+
* @param options - Optional load behaviour (`reuse`, default `true`).
|
|
4406
4421
|
* @returns A locale-keyed map of date-descending articles.
|
|
4407
4422
|
* @example
|
|
4408
4423
|
* ```ts
|
|
4409
4424
|
* const byLocale = await api.loadAll();
|
|
4410
4425
|
* ```
|
|
4411
4426
|
*/
|
|
4412
|
-
async loadAll() {
|
|
4427
|
+
async loadAll(options) {
|
|
4428
|
+
const reuse = options?.reuse !== false;
|
|
4429
|
+
const memo = ctx.state.loadedAll;
|
|
4430
|
+
if (reuse && memo !== null) return memo;
|
|
4413
4431
|
const slugs = await ctx.provider.slugs();
|
|
4414
4432
|
const locales = ctx.locales();
|
|
4415
4433
|
const result = /* @__PURE__ */ new Map();
|
|
4416
4434
|
let total = 0;
|
|
4417
4435
|
for (const locale of locales) {
|
|
4418
|
-
const present = await loadAndFilterArticles(ctx, slugs, locale);
|
|
4436
|
+
const present = await loadAndFilterArticles(ctx, slugs, locale, reuse ? ctx.state.articles.get(locale) : void 0);
|
|
4419
4437
|
const cache = /* @__PURE__ */ new Map();
|
|
4420
4438
|
let index = 0;
|
|
4421
4439
|
for (const article of present) {
|
|
@@ -4427,6 +4445,7 @@ function createContentApi(ctx) {
|
|
|
4427
4445
|
result.set(locale, present);
|
|
4428
4446
|
total += present.length;
|
|
4429
4447
|
}
|
|
4448
|
+
ctx.state.loadedAll = result;
|
|
4430
4449
|
ctx.emit("content:ready", {
|
|
4431
4450
|
locales,
|
|
4432
4451
|
articleCount: total
|
|
@@ -4490,6 +4509,7 @@ function createContentApi(ctx) {
|
|
|
4490
4509
|
if (slug === void 0) continue;
|
|
4491
4510
|
for (const cache of ctx.state.articles.values()) cache.delete(slug);
|
|
4492
4511
|
}
|
|
4512
|
+
if (accepted.length > 0) ctx.state.loadedAll = null;
|
|
4493
4513
|
ctx.emit("content:invalidated", { paths: accepted });
|
|
4494
4514
|
},
|
|
4495
4515
|
/**
|
|
@@ -4559,14 +4579,17 @@ const contentEvents = (register) => ({
|
|
|
4559
4579
|
* @param _ctx - Minimal context with global and config.
|
|
4560
4580
|
* @param _ctx.global - Global plugin registry.
|
|
4561
4581
|
* @param _ctx.config - Resolved plugin configuration.
|
|
4562
|
-
* @returns Fresh content shell state: an empty article cache.
|
|
4582
|
+
* @returns Fresh content shell state: an empty article cache + an empty loadAll memo.
|
|
4563
4583
|
* @example
|
|
4564
4584
|
* ```ts
|
|
4565
4585
|
* const state = createContentState({ global: {}, config: { providers: [] } });
|
|
4566
4586
|
* ```
|
|
4567
4587
|
*/
|
|
4568
4588
|
function createContentState(_ctx) {
|
|
4569
|
-
return {
|
|
4589
|
+
return {
|
|
4590
|
+
articles: /* @__PURE__ */ new Map(),
|
|
4591
|
+
loadedAll: null
|
|
4592
|
+
};
|
|
4570
4593
|
}
|
|
4571
4594
|
//#endregion
|
|
4572
4595
|
//#region src/plugins/content/validate.ts
|