@moku-labs/web 1.1.0 → 1.3.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 CHANGED
@@ -211,7 +211,7 @@ Each plugin is small, single-purpose, and documented on its own. **Click a name
211
211
  | [`content`](src/plugins/content/README.md) | node-only | Markdown → sanitized HTML pipeline, frontmatter, reading time, locale-keyed `Article` model |
212
212
  | [`build`](src/plugins/build/README.md) | node-only | SSG orchestrator: pages, feeds (RSS/Atom/JSON), sitemap, OG images → `dist/` |
213
213
  | [`deploy`](src/plugins/deploy/README.md) | node-only | Cloudflare Pages: `wrangler.jsonc` scaffolding, secret scrubbing, deploy |
214
- | [`cli`](src/plugins/cli/README.md) | node-only | Developer CLI — `build` / `serve` / `preview` / `deploy` with a boxed Panel UI + live progress |
214
+ | [`cli`](src/plugins/cli/README.md) | node-only | Developer CLI — `build` / `serve` / `preview` / `deploy` with the animated Velocity Panel UI (lockup + version banner, live phase tree, boxed panels, live pulse) |
215
215
  | [`data`](src/plugins/data/README.md) | optional provider | Agnostic `page path → JSON` contract: `write()` on Node, `at()` in the browser, for DATA nav |
216
216
  | [`env`](src/plugins/env/README.md) | core | Multi-provider environment / secret injection, validated and frozen at `onInit` |
217
217
  | [`log`](src/plugins/log/README.md) | core | Structured logging + an in-memory trace with an `expect()` DSL for testable workflows |
@@ -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.
@@ -2072,6 +2072,24 @@ type ContentApiContext = {
2072
2072
  defaultLocale: () => string; /** The resolved content source (merged from `config.providers`). */
2073
2073
  provider: ContentProvider;
2074
2074
  };
2075
+ /**
2076
+ * Options for {@link Api.loadAll}.
2077
+ *
2078
+ * @example
2079
+ * ```ts
2080
+ * await app.content.loadAll({ reuse: true });
2081
+ * ```
2082
+ */
2083
+ type LoadAllOptions = {
2084
+ /**
2085
+ * Reuse already-cached articles for slugs NOT dropped by a preceding `invalidate()`,
2086
+ * re-reading + re-rendering (Shiki) ONLY the invalidated (dirty) articles. The
2087
+ * post-sort `contentId` ordinals are always recomputed across the full set, so order +
2088
+ * ids match a full load. Default `false` (a full load that re-reads every article).
2089
+ * Used by dev incremental rebuilds; a fresh process / production build never reuses.
2090
+ */
2091
+ reuse?: boolean;
2092
+ };
2075
2093
  /**
2076
2094
  * Public API for the content plugin.
2077
2095
  *
@@ -2084,8 +2102,10 @@ type Api = {
2084
2102
  /**
2085
2103
  * Load every article across every active locale, returning a locale-keyed
2086
2104
  * map of date-descending Article arrays. Emits content:ready.
2105
+ *
2106
+ * @param options - Optional load behaviour ({@link LoadAllOptions}); omit for a full load.
2087
2107
  */
2088
- loadAll(): Promise<Map<string, Article[]>>;
2108
+ loadAll(options?: LoadAllOptions): Promise<Map<string, Article[]>>;
2089
2109
  /**
2090
2110
  * Resolve and render a single article for one locale, with locale fallback.
2091
2111
  *
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,26 @@ 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
+ * With `{ reuse: true }` (dev incremental rebuild) cached articles are reused for
4413
+ * every slug a preceding `invalidate()` did not drop, so only the dirty articles
4414
+ * re-read + re-run the Markdown/Shiki pipeline; the `contentId` ordinals are still
4415
+ * recomputed across the FULL sorted set, so ids + order match a full load.
4416
+ *
4417
+ * @param options - Optional load behaviour (`reuse`); omit for a full load.
4406
4418
  * @returns A locale-keyed map of date-descending articles.
4407
4419
  * @example
4408
4420
  * ```ts
4409
4421
  * const byLocale = await api.loadAll();
4410
4422
  * ```
4411
4423
  */
4412
- async loadAll() {
4424
+ async loadAll(options) {
4425
+ const reuse = options?.reuse === true;
4413
4426
  const slugs = await ctx.provider.slugs();
4414
4427
  const locales = ctx.locales();
4415
4428
  const result = /* @__PURE__ */ new Map();
4416
4429
  let total = 0;
4417
4430
  for (const locale of locales) {
4418
- const present = await loadAndFilterArticles(ctx, slugs, locale);
4431
+ const present = await loadAndFilterArticles(ctx, slugs, locale, reuse ? ctx.state.articles.get(locale) : void 0);
4419
4432
  const cache = /* @__PURE__ */ new Map();
4420
4433
  let index = 0;
4421
4434
  for (const article of present) {