@backmatter/next 0.0.16 → 0.0.17

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.cjs CHANGED
@@ -84,6 +84,7 @@ var import_node_path2 = __toESM(require("node:path"));
84
84
  var DEFAULT_DIRS = {
85
85
  models: "models",
86
86
  blocks: "blocks",
87
+ richtext: "richtext",
87
88
  mdx: "mdx",
88
89
  layouts: "layouts",
89
90
  objects: "objects"
@@ -242,6 +243,7 @@ function renderModelsBarrel(input) {
242
243
  }
243
244
  function renderSyncBarrel(input) {
244
245
  const objects = input.objects ?? [];
246
+ const richtext = input.richtext ?? [];
245
247
  const lines = [GENERATED_HEADER, `import { createBackmatter } from "${input.sdk}";`];
246
248
  if (input.configFile) {
247
249
  lines.push(`import config from "${importSpecifier(input.outDir, input.configFile)}";`);
@@ -254,6 +256,9 @@ function renderSyncBarrel(input) {
254
256
  for (const b of input.blocks) {
255
257
  lines.push(`import ${b.name} from "${importSpecifier(input.outDir, b.file)}";`);
256
258
  }
259
+ for (const b of richtext) {
260
+ lines.push(`import richtext${b.name} from "${importSpecifier(input.outDir, b.file)}";`);
261
+ }
257
262
  for (const object of objects) {
258
263
  lines.push(`import ${object.importName} from "${importSpecifier(input.outDir, object.file)}";`);
259
264
  }
@@ -263,10 +268,12 @@ function renderSyncBarrel(input) {
263
268
  lines.push("");
264
269
  lines.push(`export const blocks = [${input.blocks.map((b) => b.name).join(", ")}] as const;`);
265
270
  lines.push("");
271
+ lines.push(`export const richtext = [${richtext.map((b) => `richtext${b.name}`).join(", ")}] as const;`);
272
+ lines.push("");
266
273
  lines.push(`export const objects = [${objects.map((o) => o.importName).join(", ")}] as const;`);
267
274
  lines.push("");
268
275
  lines.push("/** Typed content client: cms.query.{type}, cms.getDoc, cms.listDocs. */");
269
- lines.push("export const cms = createBackmatter({ ...config, models, blocks, objects, sync: true });");
276
+ lines.push("export const cms = createBackmatter({ ...config, models, blocks, richtext, objects, sync: true });");
270
277
  return `${lines.join(`
271
278
  `)}
272
279
  `;
@@ -284,6 +291,19 @@ function renderMdxBarrel(input) {
284
291
  `)}
285
292
  `;
286
293
  }
294
+ function renderRichtextBarrel(input) {
295
+ const lines = [GENERATED_HEADER];
296
+ for (const b of input.richtext) {
297
+ lines.push(`import ${b.name} from "${importSpecifier(input.outDir, b.file)}";`);
298
+ }
299
+ lines.push("");
300
+ lines.push("/** In-content blocks: <Image /> in MDX resolves to richtext/Image.tsx. */");
301
+ const entries = input.richtext.map((b) => `${b.name}: ${b.name}.component`);
302
+ lines.push(`export const richtextComponents = ${entries.length ? `{ ${entries.join(", ")} }` : "{}"};`);
303
+ return `${lines.join(`
304
+ `)}
305
+ `;
306
+ }
287
307
  function renderBlocksBarrel(input) {
288
308
  const lines = [GENERATED_HEADER, ""];
289
309
  lines.push('/** Composer loaders: JSON `{ type: "Hero" }` → dynamic import of blocks/Hero.tsx. */');
@@ -326,6 +346,7 @@ var REGISTRY_FILES = [
326
346
  "blocks.ts",
327
347
  "block-fallbacks.ts",
328
348
  "mdx.ts",
349
+ "richtext.ts",
329
350
  "layouts.ts",
330
351
  "sync.ts"
331
352
  ];
@@ -334,6 +355,7 @@ function generate(options) {
334
355
  const outDir = import_node_path2.default.resolve(root, options.outDir ?? DEFAULT_OUT_DIR);
335
356
  const models = listModelFiles(root, options.modelsDir ?? DEFAULT_DIRS.models);
336
357
  const { blocks, skipped: skippedBlocks } = listBlockFiles(root, options.blocksDir ?? DEFAULT_DIRS.blocks);
358
+ const { blocks: richtext, skipped: skippedRichtext } = listBlockFiles(root, options.richtextDir ?? DEFAULT_DIRS.richtext);
337
359
  const fallbacks = listBlockFallbackFiles(root, options.blocksDir ?? DEFAULT_DIRS.blocks);
338
360
  const { blocks: mdx, skipped: skippedMdx } = listMdxFiles(root, options.mdxDir ?? DEFAULT_DIRS.mdx);
339
361
  const layouts = listLayoutFiles(root, options.layoutsDir ?? DEFAULT_DIRS.layouts);
@@ -346,8 +368,9 @@ function generate(options) {
346
368
  "blocks.ts": renderBlocksBarrel({ outDir, blocks }),
347
369
  "block-fallbacks.ts": renderBlockFallbacksBarrel({ outDir, fallbacks }),
348
370
  "mdx.ts": renderMdxBarrel({ outDir, mdx }),
371
+ "richtext.ts": renderRichtextBarrel({ outDir, richtext }),
349
372
  "layouts.ts": renderLayoutsBarrel({ outDir, layouts }),
350
- "sync.ts": renderSyncBarrel({ outDir, models, blocks, objects, configFile, sdk })
373
+ "sync.ts": renderSyncBarrel({ outDir, models, blocks, richtext, objects, configFile, sdk })
351
374
  };
352
375
  const outputs = REGISTRY_FILES.map((name) => {
353
376
  const file = import_node_path2.default.join(outDir, name);
@@ -365,10 +388,11 @@ function generate(options) {
365
388
  changed: outputs.filter((o) => o.changed).map((o) => o.rel),
366
389
  models,
367
390
  blocks,
391
+ richtext,
368
392
  mdx,
369
393
  layouts,
370
394
  objects,
371
- skippedBlocks,
395
+ skippedBlocks: [...skippedBlocks, ...skippedRichtext],
372
396
  skippedMdx,
373
397
  configFile
374
398
  };
@@ -403,6 +427,7 @@ function resolveLoaderOptions(options = {}) {
403
427
  outDir: options.outDir ?? DEFAULT_OUT_DIR,
404
428
  modelsDir: options.modelsDir ?? DEFAULT_DIRS.models,
405
429
  blocksDir: options.blocksDir ?? DEFAULT_DIRS.blocks,
430
+ richtextDir: options.richtextDir ?? DEFAULT_DIRS.richtext,
406
431
  mdxDir: options.mdxDir ?? DEFAULT_DIRS.mdx,
407
432
  layoutsDir: options.layoutsDir ?? DEFAULT_DIRS.layouts,
408
433
  objectsDir: options.objectsDir ?? DEFAULT_DIRS.objects,
@@ -421,7 +446,7 @@ function registryMatchers(loaderOptions) {
421
446
  const escaped = rel.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
422
447
  return {
423
448
  glob: `**/${rel}/*.ts`,
424
- test: new RegExp(`(^|[\\\\/])${escaped.replace(/\//g, "[\\\\/]")}[\\\\/](index|blocks|block-fallbacks|mdx|layouts|sync)\\.ts$`)
449
+ test: new RegExp(`(^|[\\\\/])${escaped.replace(/\//g, "[\\\\/]")}[\\\\/](index|blocks|block-fallbacks|mdx|richtext|layouts|sync)\\.ts$`)
425
450
  };
426
451
  }
427
452
  function withBackmatter(nextConfig = {}, options = {}) {
package/dist/loader.cjs CHANGED
@@ -78,6 +78,7 @@ var import_node_path2 = __toESM(require("node:path"));
78
78
  var DEFAULT_DIRS = {
79
79
  models: "models",
80
80
  blocks: "blocks",
81
+ richtext: "richtext",
81
82
  mdx: "mdx",
82
83
  layouts: "layouts",
83
84
  objects: "objects"
@@ -236,6 +237,7 @@ function renderModelsBarrel(input) {
236
237
  }
237
238
  function renderSyncBarrel(input) {
238
239
  const objects = input.objects ?? [];
240
+ const richtext = input.richtext ?? [];
239
241
  const lines = [GENERATED_HEADER, `import { createBackmatter } from "${input.sdk}";`];
240
242
  if (input.configFile) {
241
243
  lines.push(`import config from "${importSpecifier(input.outDir, input.configFile)}";`);
@@ -248,6 +250,9 @@ function renderSyncBarrel(input) {
248
250
  for (const b of input.blocks) {
249
251
  lines.push(`import ${b.name} from "${importSpecifier(input.outDir, b.file)}";`);
250
252
  }
253
+ for (const b of richtext) {
254
+ lines.push(`import richtext${b.name} from "${importSpecifier(input.outDir, b.file)}";`);
255
+ }
251
256
  for (const object of objects) {
252
257
  lines.push(`import ${object.importName} from "${importSpecifier(input.outDir, object.file)}";`);
253
258
  }
@@ -257,10 +262,12 @@ function renderSyncBarrel(input) {
257
262
  lines.push("");
258
263
  lines.push(`export const blocks = [${input.blocks.map((b) => b.name).join(", ")}] as const;`);
259
264
  lines.push("");
265
+ lines.push(`export const richtext = [${richtext.map((b) => `richtext${b.name}`).join(", ")}] as const;`);
266
+ lines.push("");
260
267
  lines.push(`export const objects = [${objects.map((o) => o.importName).join(", ")}] as const;`);
261
268
  lines.push("");
262
269
  lines.push("/** Typed content client: cms.query.{type}, cms.getDoc, cms.listDocs. */");
263
- lines.push("export const cms = createBackmatter({ ...config, models, blocks, objects, sync: true });");
270
+ lines.push("export const cms = createBackmatter({ ...config, models, blocks, richtext, objects, sync: true });");
264
271
  return `${lines.join(`
265
272
  `)}
266
273
  `;
@@ -278,6 +285,19 @@ function renderMdxBarrel(input) {
278
285
  `)}
279
286
  `;
280
287
  }
288
+ function renderRichtextBarrel(input) {
289
+ const lines = [GENERATED_HEADER];
290
+ for (const b of input.richtext) {
291
+ lines.push(`import ${b.name} from "${importSpecifier(input.outDir, b.file)}";`);
292
+ }
293
+ lines.push("");
294
+ lines.push("/** In-content blocks: <Image /> in MDX resolves to richtext/Image.tsx. */");
295
+ const entries = input.richtext.map((b) => `${b.name}: ${b.name}.component`);
296
+ lines.push(`export const richtextComponents = ${entries.length ? `{ ${entries.join(", ")} }` : "{}"};`);
297
+ return `${lines.join(`
298
+ `)}
299
+ `;
300
+ }
281
301
  function renderBlocksBarrel(input) {
282
302
  const lines = [GENERATED_HEADER, ""];
283
303
  lines.push('/** Composer loaders: JSON `{ type: "Hero" }` → dynamic import of blocks/Hero.tsx. */');
@@ -320,6 +340,7 @@ var REGISTRY_FILES = [
320
340
  "blocks.ts",
321
341
  "block-fallbacks.ts",
322
342
  "mdx.ts",
343
+ "richtext.ts",
323
344
  "layouts.ts",
324
345
  "sync.ts"
325
346
  ];
@@ -328,6 +349,7 @@ function generate(options) {
328
349
  const outDir = import_node_path2.default.resolve(root, options.outDir ?? DEFAULT_OUT_DIR);
329
350
  const models = listModelFiles(root, options.modelsDir ?? DEFAULT_DIRS.models);
330
351
  const { blocks, skipped: skippedBlocks } = listBlockFiles(root, options.blocksDir ?? DEFAULT_DIRS.blocks);
352
+ const { blocks: richtext, skipped: skippedRichtext } = listBlockFiles(root, options.richtextDir ?? DEFAULT_DIRS.richtext);
331
353
  const fallbacks = listBlockFallbackFiles(root, options.blocksDir ?? DEFAULT_DIRS.blocks);
332
354
  const { blocks: mdx, skipped: skippedMdx } = listMdxFiles(root, options.mdxDir ?? DEFAULT_DIRS.mdx);
333
355
  const layouts = listLayoutFiles(root, options.layoutsDir ?? DEFAULT_DIRS.layouts);
@@ -340,8 +362,9 @@ function generate(options) {
340
362
  "blocks.ts": renderBlocksBarrel({ outDir, blocks }),
341
363
  "block-fallbacks.ts": renderBlockFallbacksBarrel({ outDir, fallbacks }),
342
364
  "mdx.ts": renderMdxBarrel({ outDir, mdx }),
365
+ "richtext.ts": renderRichtextBarrel({ outDir, richtext }),
343
366
  "layouts.ts": renderLayoutsBarrel({ outDir, layouts }),
344
- "sync.ts": renderSyncBarrel({ outDir, models, blocks, objects, configFile, sdk })
367
+ "sync.ts": renderSyncBarrel({ outDir, models, blocks, richtext, objects, configFile, sdk })
345
368
  };
346
369
  const outputs = REGISTRY_FILES.map((name) => {
347
370
  const file = import_node_path2.default.join(outDir, name);
@@ -359,10 +382,11 @@ function generate(options) {
359
382
  changed: outputs.filter((o) => o.changed).map((o) => o.rel),
360
383
  models,
361
384
  blocks,
385
+ richtext,
362
386
  mdx,
363
387
  layouts,
364
388
  objects,
365
- skippedBlocks,
389
+ skippedBlocks: [...skippedBlocks, ...skippedRichtext],
366
390
  skippedMdx,
367
391
  configFile
368
392
  };
@@ -374,11 +398,13 @@ function backmatterLoader(source) {
374
398
  const root = import_node_path3.default.resolve(options.root);
375
399
  const modelsDir = import_node_path3.default.resolve(root, options.modelsDir);
376
400
  const blocksDir = import_node_path3.default.resolve(root, options.blocksDir);
401
+ const richtextDir = import_node_path3.default.resolve(root, options.richtextDir);
377
402
  const mdxDir = import_node_path3.default.resolve(root, options.mdxDir);
378
403
  const layoutsDir = import_node_path3.default.resolve(root, options.layoutsDir);
379
404
  const objectsDir = import_node_path3.default.resolve(root, options.objectsDir);
380
405
  this.addContextDependency(modelsDir);
381
406
  this.addContextDependency(blocksDir);
407
+ this.addContextDependency(richtextDir);
382
408
  this.addContextDependency(mdxDir);
383
409
  this.addContextDependency(layoutsDir);
384
410
  this.addContextDependency(objectsDir);
@@ -388,6 +414,7 @@ function backmatterLoader(source) {
388
414
  outDir: options.outDir,
389
415
  modelsDir: options.modelsDir,
390
416
  blocksDir: options.blocksDir,
417
+ richtextDir: options.richtextDir,
391
418
  mdxDir: options.mdxDir,
392
419
  layoutsDir: options.layoutsDir,
393
420
  objectsDir: options.objectsDir,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backmatter/next",
3
- "version": "0.0.16",
3
+ "version": "0.0.17",
4
4
  "description": "Experimental Next.js adapter for Backmatter. APIs will break. Not ready for production.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -38,7 +38,7 @@
38
38
  "test": "bun test"
39
39
  },
40
40
  "dependencies": {
41
- "@backmatter/core": "^0.0.15",
41
+ "@backmatter/core": "^0.0.16",
42
42
  "@mdx-js/mdx": "^3.1.1",
43
43
  "remark-gfm": "^4.0.1"
44
44
  },
package/src/config.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  // resolve `import { cms } from ".backmatter"`.
6
6
  // 2. Registers `@backmatter/next/loader` for the registry files with Turbopack
7
7
  // (Next 16 default) and webpack (`next build --webpack`). The loader declares
8
- // `models/`, `blocks/`, and `mdx/` as context dependencies, so the bundler's
8
+ // `models/`, `blocks/`, `richtext/`, and `mdx/` as context dependencies, so the bundler's
9
9
  // own file watcher keeps the registry fresh — no polling, no second watcher process.
10
10
  import { existsSync } from "node:fs";
11
11
  import path from "node:path";
@@ -24,6 +24,7 @@ export type WithBackmatterOptions = {
24
24
  outDir?: string;
25
25
  modelsDir?: string;
26
26
  blocksDir?: string;
27
+ richtextDir?: string;
27
28
  mdxDir?: string;
28
29
  layoutsDir?: string;
29
30
  objectsDir?: string;
@@ -81,6 +82,7 @@ export function resolveLoaderOptions(options: WithBackmatterOptions = {}): Backm
81
82
  outDir: options.outDir ?? DEFAULT_OUT_DIR,
82
83
  modelsDir: options.modelsDir ?? DEFAULT_DIRS.models,
83
84
  blocksDir: options.blocksDir ?? DEFAULT_DIRS.blocks,
85
+ richtextDir: options.richtextDir ?? DEFAULT_DIRS.richtext,
84
86
  mdxDir: options.mdxDir ?? DEFAULT_DIRS.mdx,
85
87
  layoutsDir: options.layoutsDir ?? DEFAULT_DIRS.layouts,
86
88
  objectsDir: options.objectsDir ?? DEFAULT_DIRS.objects,
@@ -106,7 +108,7 @@ export function registryMatchers(loaderOptions: BackmatterLoaderOptions): { glob
106
108
  const escaped = rel.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
107
109
  return {
108
110
  glob: `**/${rel}/*.ts`,
109
- test: new RegExp(`(^|[\\\\/])${escaped.replace(/\//g, "[\\\\/]")}[\\\\/](index|blocks|block-fallbacks|mdx|layouts|sync)\\.ts$`),
111
+ test: new RegExp(`(^|[\\\\/])${escaped.replace(/\//g, "[\\\\/]")}[\\\\/](index|blocks|block-fallbacks|mdx|richtext|layouts|sync)\\.ts$`),
110
112
  };
111
113
  }
112
114
 
package/src/loader.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  // packages/next/src/loader.ts
2
- // Bundler loader for `.backmatter/index.ts`, `.backmatter/blocks.ts`, `.backmatter/mdx.ts`,
3
- // and `.backmatter/layouts.ts`.
2
+ // Bundler loader for `.backmatter/index.ts`, `.backmatter/blocks.ts`,
3
+ // `.backmatter/mdx.ts`, `.backmatter/richtext.ts`, and `.backmatter/layouts.ts`.
4
4
  //
5
5
  // This is the Tailwind-JIT mechanism: instead of running our own watcher we
6
6
  // tell the bundler that these modules depend on the `models/`, `blocks/`,
7
- // `mdx/`, and `layouts/` directories. webpack and Turbopack already watch the filesystem;
7
+ // `richtext/`, `mdx/`, and `layouts/` directories. webpack and Turbopack already watch the filesystem;
8
8
  // when a file is added, removed or renamed there they invalidate the module,
9
9
  // re-run this loader, and we regenerate the barrel from a fresh `readdir`.
10
10
  import path from "node:path";
@@ -15,6 +15,7 @@ export type BackmatterLoaderOptions = {
15
15
  outDir: string;
16
16
  modelsDir: string;
17
17
  blocksDir: string;
18
+ richtextDir: string;
18
19
  mdxDir: string;
19
20
  layoutsDir: string;
20
21
  objectsDir: string;
@@ -35,12 +36,14 @@ export default function backmatterLoader(this: BackmatterLoaderContext, source:
35
36
  const root = path.resolve(options.root);
36
37
  const modelsDir = path.resolve(root, options.modelsDir);
37
38
  const blocksDir = path.resolve(root, options.blocksDir);
39
+ const richtextDir = path.resolve(root, options.richtextDir);
38
40
  const mdxDir = path.resolve(root, options.mdxDir);
39
41
  const layoutsDir = path.resolve(root, options.layoutsDir);
40
42
  const objectsDir = path.resolve(root, options.objectsDir);
41
43
 
42
44
  this.addContextDependency(modelsDir);
43
45
  this.addContextDependency(blocksDir);
46
+ this.addContextDependency(richtextDir);
44
47
  this.addContextDependency(mdxDir);
45
48
  this.addContextDependency(layoutsDir);
46
49
  this.addContextDependency(objectsDir);
@@ -51,6 +54,7 @@ export default function backmatterLoader(this: BackmatterLoaderContext, source:
51
54
  outDir: options.outDir,
52
55
  modelsDir: options.modelsDir,
53
56
  blocksDir: options.blocksDir,
57
+ richtextDir: options.richtextDir,
54
58
  mdxDir: options.mdxDir,
55
59
  layoutsDir: options.layoutsDir,
56
60
  objectsDir: options.objectsDir,
package/src/routes.ts CHANGED
@@ -63,7 +63,7 @@ export type CatchAllRoute = {
63
63
  Page: (props: {
64
64
  params: Promise<CatchAllParams>;
65
65
  searchParams?: Promise<CatchAllSearchParams>;
66
- }) => Promise<ReactNode>;
66
+ }) => ReactNode;
67
67
  CmsRoute: (props: {
68
68
  params: Promise<CatchAllParams> | CatchAllParams;
69
69
  searchParams?: Promise<CatchAllSearchParams> | CatchAllSearchParams;
@@ -248,7 +248,7 @@ export function createCatchAll<Models extends readonly ModelDef[]>(
248
248
  params: Promise<CatchAllParams> | CatchAllParams;
249
249
  searchParams?: Promise<CatchAllSearchParams> | CatchAllSearchParams;
250
250
  }) => {
251
- const resolved = await cachedRouteParams(params);
251
+ const resolved = await params;
252
252
  const hit = resolve(resolved[CATCH_ALL_PARAM]);
253
253
  if (!hit) return failFor();
254
254
  const doc = await cms.getDoc(hit.locale, hit.type, hit.slug);
@@ -267,7 +267,7 @@ export function createCatchAll<Models extends readonly ModelDef[]>(
267
267
  params: Promise<CatchAllParams> | CatchAllParams;
268
268
  searchParams?: Promise<CatchAllSearchParams> | CatchAllSearchParams;
269
269
  }) => {
270
- const resolved = await cachedRouteParams(params);
270
+ const resolved = await params;
271
271
  const hit = resolve(resolved[CATCH_ALL_PARAM]);
272
272
  if (!hit) return null;
273
273
  const preview = await resolvePreviewState(cms, options.preview, searchParams);
@@ -284,11 +284,15 @@ export function createCatchAll<Models extends readonly ModelDef[]>(
284
284
  );
285
285
  };
286
286
 
287
- const Page: CatchAllRoute["Page"] = async (props) =>
287
+ const Page: CatchAllRoute["Page"] = (props) =>
288
288
  createElement(
289
289
  Fragment,
290
290
  null,
291
- await PublishedRoute(props),
291
+ createElement(
292
+ Suspense,
293
+ { fallback: createElement("div", { "aria-busy": "true", style: { minHeight: "100vh" } }) },
294
+ createElement(PublishedRoute, props),
295
+ ),
292
296
  createElement(Suspense, { fallback: null }, createElement(PreviewBranch, props)),
293
297
  );
294
298