@riebeckite/honox 0.0.32 → 0.0.33

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/index.js CHANGED
@@ -293,20 +293,17 @@ import {
293
293
  // src/content_assets.ts
294
294
  import fs5 from "node:fs/promises";
295
295
  import path7 from "node:path";
296
- import {
297
- isIgnoredContentPath as isIgnoredContentPath2,
298
- isImagePath as isImagePath2,
299
- normalizeContentPath as normalizeContentPath2
300
- } from "@riebeckite/core";
296
+ import { isImagePath as isImagePath2, normalizeContentPath as normalizeContentPath2 } from "@riebeckite/core";
297
+ import { isIgnoredContentPath as isIgnoredContentPath2 } from "@riebeckite/node";
301
298
 
302
299
  // src/content_watch.ts
303
300
  import path6 from "node:path";
304
301
  import {
305
302
  isAttachmentPath,
306
- isIgnoredContentPath,
307
303
  isImagePath,
308
304
  normalizeContentPath
309
305
  } from "@riebeckite/core";
306
+ import { isIgnoredContentPath } from "@riebeckite/node";
310
307
 
311
308
  // src/runtime_modules.ts
312
309
  var riebeckiteConfigModuleId = "virtual:riebeckite/config";
@@ -355,11 +352,15 @@ export const config = resolveHonoxConfig(
355
352
  }
356
353
  function createContentModule() {
357
354
  return `import { ContentManager } from "@riebeckite/core";
355
+ import { FileSystemContentSource } from "@riebeckite/node";
358
356
  import { config } from ${JSON.stringify(riebeckiteConfigModuleId)};
359
357
 
360
358
  export const content = new ContentManager(
361
- config.content.directory,
362
- config.content.exclude,
359
+ config.content.source ?? new FileSystemContentSource(
360
+ config.content.directory,
361
+ config.content.exclude,
362
+ ),
363
+ [],
363
364
  { config, plugins: config.plugins },
364
365
  );
365
366
  `;
@@ -996,7 +997,7 @@ async function writeSsgOutputMetrics(metrics) {
996
997
  }
997
998
 
998
999
  // src/ssg/output_cache.ts
999
- var faultInjectionCounts = /* @__PURE__ */ new Map();
1000
+ var outputCacheValidationConcurrency = 12;
1000
1001
  function decideOutputCacheRejection(decision) {
1001
1002
  if (!decision.changeSetPresent) return "change-set-unavailable";
1002
1003
  if (decision.changeSetRequiresFullRegeneration)
@@ -1112,7 +1113,6 @@ async function saveOutputCache(path11, state, outputDirectory, timingsMs) {
1112
1113
  const entriesPath = outputCacheEntriesPath(path11);
1113
1114
  const temporaryPath = `${path11}.${randomUUID2()}.tmp`;
1114
1115
  try {
1115
- await waitForFaultInjection("metadata-save-start");
1116
1116
  const outputs = await measure(
1117
1117
  timingsMs,
1118
1118
  "outputCacheEntriesWrite",
@@ -1140,7 +1140,6 @@ async function saveOutputCache(path11, state, outputDirectory, timingsMs) {
1140
1140
  "outputCacheRename",
1141
1141
  () => replaceFile(temporaryPath, path11)
1142
1142
  );
1143
- await waitForFaultInjection("metadata-committed");
1144
1143
  const checksum = createHash3("sha256").update(serialized).digest("hex");
1145
1144
  if (state.finalOutputInventory && state.configFile) {
1146
1145
  await saveFastPathCache(
@@ -1197,7 +1196,6 @@ async function saveOutputCacheEntries(directory, outputDirectory, outputs, final
1197
1196
  const source = resolveOutputCacheFile(outputDirectory, outputPath2);
1198
1197
  const cachePath = outputCacheEntryName(outputPath2);
1199
1198
  const destination = join3(directory, cachePath);
1200
- await waitForFaultInjection("entry-create-start");
1201
1199
  await replaceOutputCacheEntry(source, destination, timingsMs);
1202
1200
  const checksum = finalOutputInventory?.[outputPath2];
1203
1201
  if (!checksum) {
@@ -1216,20 +1214,43 @@ async function validateOutputCacheEntries(outputCachePath, entries, timingsMs) {
1216
1214
  try {
1217
1215
  const directory = outputCacheEntriesPath(outputCachePath);
1218
1216
  if (!await isSafeOutputCacheEntriesDirectory(directory)) return false;
1219
- for (const [outputPath2, entry] of Object.entries(entries)) {
1220
- if (!isSafeLogicalOutputPath(outputPath2) || entry.cachePath !== outputCacheEntryName(outputPath2) || !isChecksum(entry.checksum)) {
1221
- return false;
1217
+ const entriesToValidate = Object.entries(entries);
1218
+ let nextEntryIndex = 0;
1219
+ let valid = true;
1220
+ const validateEntry = async () => {
1221
+ while (valid) {
1222
+ const next = entriesToValidate[nextEntryIndex++];
1223
+ if (!next) return;
1224
+ const [outputPath2, entry] = next;
1225
+ if (!isSafeLogicalOutputPath(outputPath2) || entry.cachePath !== outputCacheEntryName(outputPath2) || !isChecksum(entry.checksum)) {
1226
+ valid = false;
1227
+ return;
1228
+ }
1229
+ const path11 = resolveOutputCacheFile(directory, entry.cachePath);
1230
+ if (!await isRegularFile(path11)) {
1231
+ valid = false;
1232
+ return;
1233
+ }
1234
+ if (await measure(
1235
+ timingsMs,
1236
+ "outputCacheEntryChecksum",
1237
+ () => checksumFile(path11)
1238
+ ) !== entry.checksum)
1239
+ valid = false;
1222
1240
  }
1223
- const path11 = resolveOutputCacheFile(directory, entry.cachePath);
1224
- if (!await isRegularFile(path11)) return false;
1225
- if (await measure(
1226
- timingsMs,
1227
- "outputCacheEntryChecksum",
1228
- () => checksumFile(path11)
1229
- ) !== entry.checksum)
1230
- return false;
1231
- }
1232
- return true;
1241
+ };
1242
+ await Promise.all(
1243
+ Array.from(
1244
+ {
1245
+ length: Math.min(
1246
+ outputCacheValidationConcurrency,
1247
+ entriesToValidate.length
1248
+ )
1249
+ },
1250
+ validateEntry
1251
+ )
1252
+ );
1253
+ return valid;
1233
1254
  } catch {
1234
1255
  return false;
1235
1256
  }
@@ -1263,7 +1284,6 @@ async function saveFastPathCache(path11, state, timingsMs) {
1263
1284
  }
1264
1285
  const temporaryPath = `${path11}.${randomUUID2()}.tmp`;
1265
1286
  try {
1266
- await waitForFaultInjection("sidecar-save-start");
1267
1287
  await measure(
1268
1288
  timingsMs,
1269
1289
  "sidecarWrite",
@@ -1275,7 +1295,6 @@ async function saveFastPathCache(path11, state, timingsMs) {
1275
1295
  "sidecarRename",
1276
1296
  () => replaceFile(temporaryPath, path11)
1277
1297
  );
1278
- await waitForFaultInjection("sidecar-committed");
1279
1298
  } catch (error) {
1280
1299
  await rm2(temporaryPath, { force: true }).catch(() => void 0);
1281
1300
  throw error;
@@ -1407,19 +1426,16 @@ async function replaceOutputCacheEntry(source, destination, timingsMs) {
1407
1426
  "outputCacheEntryWrite",
1408
1427
  () => writeFile2(temporaryPath, new Uint8Array())
1409
1428
  );
1410
- await waitForFaultInjection("entry-body-write");
1411
1429
  await measure(
1412
1430
  timingsMs,
1413
1431
  "outputCacheEntryCopy",
1414
1432
  () => copyFile(source, temporaryPath)
1415
1433
  );
1416
- await waitForFaultInjection("entry-rename-before");
1417
1434
  await measure(
1418
1435
  timingsMs,
1419
1436
  "outputCacheEntryRename",
1420
1437
  () => replaceFile(temporaryPath, destination)
1421
1438
  );
1422
- await waitForFaultInjection("entry-committed");
1423
1439
  } catch (error) {
1424
1440
  await rm2(temporaryPath, { force: true }).catch(() => void 0);
1425
1441
  throw error;
@@ -1428,7 +1444,6 @@ async function replaceOutputCacheEntry(source, destination, timingsMs) {
1428
1444
  async function collectUnreferencedOutputCacheEntries(directory, outputs, timingsMs) {
1429
1445
  try {
1430
1446
  if (!await isSafeOutputCacheEntriesDirectory(directory)) return;
1431
- await waitForFaultInjection("gc-start");
1432
1447
  const referenced = new Set(
1433
1448
  Object.values(outputs).map((entry) => entry.cachePath)
1434
1449
  );
@@ -1443,7 +1458,6 @@ async function collectUnreferencedOutputCacheEntries(directory, outputs, timings
1443
1458
  }
1444
1459
  const path11 = join3(directory, entry.name);
1445
1460
  if (await isRegularFile(path11)) {
1446
- await waitForFaultInjection("gc-entry-delete");
1447
1461
  await measure(
1448
1462
  timingsMs,
1449
1463
  "outputCacheGcDelete",
@@ -1455,25 +1469,6 @@ async function collectUnreferencedOutputCacheEntries(directory, outputs, timings
1455
1469
  return;
1456
1470
  }
1457
1471
  }
1458
- async function waitForFaultInjection(point) {
1459
- if (process.env.NODE_ENV !== "test" || process.env.RIEBECKITE_SSG_FAULT_INJECTION !== "1" || process.env.RIEBECKITE_SSG_FAULT_POINT !== point) {
1460
- return;
1461
- }
1462
- const occurrence = Number.parseInt(
1463
- process.env.RIEBECKITE_SSG_FAULT_OCCURRENCE ?? "1",
1464
- 10
1465
- );
1466
- const count = (faultInjectionCounts.get(point) ?? 0) + 1;
1467
- faultInjectionCounts.set(point, count);
1468
- if (!Number.isSafeInteger(occurrence) || occurrence < 1 || count !== occurrence)
1469
- return;
1470
- const signalFile = process.env.RIEBECKITE_SSG_FAULT_SIGNAL_FILE;
1471
- if (!signalFile) return;
1472
- await writeFile2(signalFile, `${point}
1473
- `, "utf8");
1474
- await new Promise(() => {
1475
- });
1476
- }
1477
1472
 
1478
1473
  // src/ssg_plugin.ts
1479
1474
  var defaultEntry = "./src/index.tsx";
@@ -1543,8 +1538,17 @@ function riebeckiteSsg(options = {}) {
1543
1538
  ...config.resolve,
1544
1539
  builtins: [...config.resolve.builtins, /^node:/]
1545
1540
  },
1546
- plugins: [],
1541
+ plugins: [
1542
+ {
1543
+ name: "riebeckite-ssg-package-resolution",
1544
+ enforce: "post",
1545
+ configResolved(runtimeConfig) {
1546
+ runtimeConfig.resolve.alias = [];
1547
+ }
1548
+ }
1549
+ ],
1547
1550
  build: { ssr: true },
1551
+ oxc: { jsx: { runtime: "automatic", importSource: "hono/jsx" } },
1548
1552
  environments: {
1549
1553
  ssr: { resolve: { noExternal: [/^honox(?:\/.*)?$/] } }
1550
1554
  },
@@ -1924,14 +1928,12 @@ function riebeckiteSsg(options = {}) {
1924
1928
  "publicFingerprint",
1925
1929
  () => fingerprintDirectory(resolve2(publicDir))
1926
1930
  ) : void 0;
1927
- await waitForFaultInjection("build-state-before");
1928
1931
  await saveOutputCache(
1929
1932
  outputCache.path,
1930
1933
  outputCache.state,
1931
1934
  outputCache.outDir,
1932
1935
  outputCache.timingsMs
1933
1936
  );
1934
- await waitForFaultInjection("build-state-committed");
1935
1937
  } catch (error) {
1936
1938
  this.warn(
1937
1939
  `SSG output cache could not be saved; the next build regenerates every output. ${error instanceof Error ? error.message : String(error)}`
@@ -2107,15 +2109,24 @@ function createClientModule(config, development) {
2107
2109
  for (const [index, entry] of config.plugins.flatMap((plugin) => plugin.clientEntries ?? []).entries()) {
2108
2110
  const localName = `pluginClientInitializer${index}`;
2109
2111
  const importTarget = entry.exportName ? `{ ${entry.exportName} as ${localName} }` : localName;
2110
- imports.push(
2111
- `import ${importTarget} from ${JSON.stringify(entry.moduleSpecifier)};`
2112
- );
2112
+ if (entry.presenceSelector === void 0) {
2113
+ imports.push(
2114
+ `import ${importTarget} from ${JSON.stringify(entry.moduleSpecifier)};`
2115
+ );
2116
+ }
2113
2117
  const publicConfig = entry.publicConfig;
2114
2118
  const serializedConfig = publicConfig === void 0 ? "undefined" : serializePublicClientConfig(publicConfig);
2115
2119
  const initializer = entry.initializerArguments === "context" ? `${localName}(context)` : entry.initializerArguments === "root" ? `${localName}(context.root)` : `${localName}(context.config)`;
2116
2120
  const initializers = entry.scope === "app" ? appInitializers : pageInitializers;
2121
+ if (entry.presenceSelector === void 0) {
2122
+ initializers.push(
2123
+ `mountPluginClientInitializer(${JSON.stringify(entry.scope ?? "page")}, ${JSON.stringify(entry.moduleSpecifier)}, root, ${serializedConfig}, (context) => ${initializer})`
2124
+ );
2125
+ continue;
2126
+ }
2127
+ const dynamicInitializer = entry.initializerArguments === "context" ? `initializer(context)` : entry.initializerArguments === "root" ? `initializer(context.root)` : `initializer(context.config)`;
2117
2128
  initializers.push(
2118
- `mountPluginClientInitializer(${JSON.stringify(entry.scope ?? "page")}, ${JSON.stringify(entry.moduleSpecifier)}, root, ${serializedConfig}, (context) => ${initializer})`
2129
+ `mountConditionalPluginClientInitializer(${JSON.stringify(entry.scope ?? "page")}, ${JSON.stringify(entry.moduleSpecifier)}, root, ${serializedConfig}, ${JSON.stringify(entry.presenceSelector)}, async () => { const { ${entry.exportName ?? "default"}: initializer } = await import(${JSON.stringify(entry.moduleSpecifier)}); return (context) => ${dynamicInitializer}; })`
2119
2130
  );
2120
2131
  }
2121
2132
  return `${imports.join("\n")}
@@ -2127,6 +2138,14 @@ function mountPluginClientInitializer(scope, name, root, config, initialize) {
2127
2138
  void lifecycle.mount({ scope, name, root, config, initialize });
2128
2139
  }
2129
2140
 
2141
+ function mountConditionalPluginClientInitializer(scope, name, root, config, presenceSelector, load) {
2142
+ if (!root.querySelector(presenceSelector)) return;
2143
+ void load().then((initialize) => {
2144
+ if ("isConnected" in root && !root.isConnected) return;
2145
+ void lifecycle.mount({ scope, name, root, config, initialize });
2146
+ }, (error) => console.error("[riebeckite] Client initializer module failed: " + name, error));
2147
+ }
2148
+
2130
2149
  function mountAppClientInitializers(root) {
2131
2150
  ${appInitializers.map((initializer) => ` ${initializer};`).join("\n")}
2132
2151
  }
@@ -2166,14 +2185,14 @@ export async function disposeRiebeckiteClient() {
2166
2185
  // src/development_inspector.ts
2167
2186
  import {
2168
2187
  buildOutputPlan as buildOutputPlan2,
2169
- ContentManager,
2170
- FileSystemContentSource
2188
+ ContentManager
2171
2189
  } from "@riebeckite/core";
2172
2190
  import {
2173
2191
  collectDevelopmentDiagnostics,
2174
2192
  diagnoseOutputPlan,
2175
2193
  sortDevelopmentDiagnostics
2176
2194
  } from "@riebeckite/diagnostics";
2195
+ import { FileSystemContentSource } from "@riebeckite/node";
2177
2196
  var clientModuleId2 = "virtual:riebeckite/development-inspector";
2178
2197
  var resolvedClientModuleId2 = `\0${clientModuleId2}`;
2179
2198
  var reportPath = "/__riebeckite/inspector/report";
@@ -2919,6 +2938,9 @@ function riebeckite(options = {}, onApplicationResolved) {
2919
2938
  define: {
2920
2939
  "process.env.RIEBECKITE_APP_ROOT": JSON.stringify(appRoot)
2921
2940
  },
2941
+ oxc: {
2942
+ jsx: { runtime: "automatic", importSource: "hono/jsx" }
2943
+ },
2922
2944
  server: {
2923
2945
  watch: {
2924
2946
  ignored: ["**/.riebeckite/**"]
package/dist/server.js CHANGED
@@ -35,6 +35,11 @@ async function resolveRiebeckiteContentRequest(c, content, parameter = "slug") {
35
35
  const requestedSlug = c.req.param(parameter);
36
36
  if (!requestedSlug) return { kind: "response", response: c.notFound() };
37
37
  if (contentExtensionPattern.test(requestedSlug)) {
38
+ const page = await content.resolvePage(c.req.path);
39
+ if (page) {
40
+ applyRiebeckiteRouteContext(c, { kind: "page", page });
41
+ return { kind: "page", page };
42
+ }
38
43
  return { kind: "response", response: c.notFound() };
39
44
  }
40
45
  return resolveRouteRequest(c, content);
@@ -250,6 +255,7 @@ import {
250
255
  } from "@riebeckite/ui";
251
256
 
252
257
  // src/ui/primitives.tsx
258
+ import { escapeScriptJson, uniqueStrings } from "@riebeckite/core";
253
259
  import { ColorModeScript } from "@riebeckite/plugin-color-mode";
254
260
  import { raw } from "hono/html";
255
261
  import { Link, Script } from "honox/server";
@@ -1,7 +1,8 @@
1
1
  import { type ResolvedRiebeckiteConfig } from "@riebeckite/core";
2
- export type GeneratedAssetPaths = {
2
+ type GeneratedAssetPaths = {
3
3
  frameworkStyles: string;
4
4
  pluginStyles: string;
5
5
  themeStyles: string;
6
6
  };
7
7
  export declare function writeRiebeckiteAssetEntries(config: ResolvedRiebeckiteConfig, paths: GeneratedAssetPaths): void;
8
+ export {};
@@ -1,5 +1,5 @@
1
1
  import type { Plugin } from "vite";
2
- export type RiebeckiteContentAssetsOptions = {
2
+ type RiebeckiteContentAssetsOptions = {
3
3
  appRoot: () => string;
4
4
  contentRoot: () => string;
5
5
  };
@@ -1,5 +1,5 @@
1
1
  import type { Plugin, ViteDevServer } from "vite";
2
- export type RiebeckiteContentWatchOptions = {
2
+ type RiebeckiteContentWatchOptions = {
3
3
  appRoot: () => string;
4
4
  contentRoot: () => string;
5
5
  exclude: () => readonly string[];
@@ -9,3 +9,4 @@ export declare function isSiteAssetPath(logicalPath: string): boolean;
9
9
  export declare function isPathInsideDirectory(directory: string, file: string): boolean;
10
10
  export declare function toLogicalPath(directory: string, file: string): string;
11
11
  export declare function invalidateApplicationRuntime(server: Pick<ViteDevServer, "environments">, appRoot: string): number;
12
+ export {};
@@ -1,5 +1,5 @@
1
1
  import type { ContentManager, ResolvedRiebeckiteConfig } from "@riebeckite/core";
2
- export type RiebeckiteEndpointMountOptions = {
2
+ type RiebeckiteEndpointMountOptions = {
3
3
  config: ResolvedRiebeckiteConfig;
4
4
  content: ContentManager;
5
5
  };
@@ -1,5 +1,5 @@
1
1
  import type { NoChangeFastPathOutputMetrics, NoChangeFastPathResult, OutputCacheDecision, OutputCacheRejectionReason } from "./types.js";
2
- export type OutputCacheEntry = {
2
+ type OutputCacheEntry = {
3
3
  cachePath: string;
4
4
  checksum: string;
5
5
  };
@@ -26,4 +26,4 @@ export declare function loadOutputCache(path: string, timingsMs?: Record<string,
26
26
  }) | undefined>;
27
27
  export declare function saveOutputCache(path: string, state: OutputCacheState, outputDirectory: string, timingsMs?: Record<string, number>): Promise<string>;
28
28
  export declare function decodeOutput(outputCachePath: string, entry: OutputCacheEntry): Promise<Uint8Array>;
29
- export declare function waitForFaultInjection(point: string): Promise<void>;
29
+ export {};
@@ -5,13 +5,13 @@ export type GeneratedOutputAsset = {
5
5
  fileName: string;
6
6
  source: string | Uint8Array;
7
7
  };
8
- export type SsgGeneratedHtmlDiagnostic = {
8
+ type SsgGeneratedHtmlDiagnostic = {
9
9
  severity: "info" | "warning" | "error";
10
10
  message: string;
11
11
  code?: string;
12
12
  filePath?: string;
13
13
  };
14
- export type SsgHtmlInspectorPlugin = {
14
+ type SsgHtmlInspectorPlugin = {
15
15
  name?: string;
16
16
  inspectGeneratedHtml?: (page: {
17
17
  path: string;
@@ -125,3 +125,4 @@ export type NoChangeFastPathOutputMetrics = {
125
125
  outputFilesHashed: number;
126
126
  outputBytesHashed: number;
127
127
  };
128
+ export {};
@@ -1,20 +1,20 @@
1
- import type { PluginHeadTag, PostContent, PublicContentEntry, ResolvedRiebeckiteConfig } from "@riebeckite/core";
2
- export type PrimitiveChildren = unknown;
3
- export type PrimitiveProps = {
1
+ import type { PluginHeadTag, PostContent, PublicContentEntry, ResolvedRiebeckiteConfig, SeoMetadata } from "@riebeckite/core";
2
+ type PrimitiveChildren = unknown;
3
+ type PrimitiveProps = {
4
4
  children?: PrimitiveChildren;
5
5
  class?: string;
6
6
  className?: string;
7
7
  };
8
- export type PrimitiveClassProps = {
8
+ type PrimitiveClassProps = {
9
9
  class?: string;
10
10
  className?: string;
11
11
  };
12
- export type ArticleProps = PrimitiveProps & {
12
+ type ArticleProps = PrimitiveProps & {
13
13
  post?: PostContent;
14
14
  entry?: PublicContentEntry;
15
15
  };
16
- export type ArticleLayoutProps = PrimitiveProps;
17
- export type ArticleHeaderProps = (PrimitiveClassProps & {
16
+ type ArticleLayoutProps = PrimitiveProps;
17
+ type ArticleHeaderProps = (PrimitiveClassProps & {
18
18
  dangerouslySetInnerHTML: {
19
19
  __html: string;
20
20
  };
@@ -23,26 +23,26 @@ export type ArticleHeaderProps = (PrimitiveClassProps & {
23
23
  dangerouslySetInnerHTML?: never;
24
24
  children?: PrimitiveChildren;
25
25
  });
26
- export type ArticleContentProps = PrimitiveClassProps & {
26
+ type ArticleContentProps = PrimitiveClassProps & {
27
27
  children?: PrimitiveChildren;
28
28
  };
29
- export type ArticleMetaProps = PrimitiveProps & {
29
+ type ArticleMetaProps = PrimitiveProps & {
30
30
  label?: string;
31
31
  };
32
- export type SidebarProps = PrimitiveProps & {
32
+ type SidebarProps = PrimitiveProps & {
33
33
  label?: string;
34
34
  };
35
- export type ArticleFooterProps = PrimitiveProps;
35
+ type ArticleFooterProps = PrimitiveProps;
36
36
  export declare function Article(props: ArticleProps): import("hono/jsx/jsx-dev-runtime").JSX.Element;
37
37
  export declare function ArticleLayout(props: ArticleLayoutProps): import("hono/jsx/jsx-dev-runtime").JSX.Element;
38
38
  export declare function ArticleHeader(props: ArticleHeaderProps): import("hono/jsx/jsx-dev-runtime").JSX.Element;
39
39
  export declare function ArticleMeta(props: ArticleMetaProps): import("hono/jsx/jsx-dev-runtime").JSX.Element;
40
40
  export declare function ArticleContent(props: ArticleContentProps): import("hono/jsx/jsx-dev-runtime").JSX.Element;
41
- export type ArticleBodyProps = PrimitiveClassProps & {
41
+ type ArticleBodyProps = PrimitiveClassProps & {
42
42
  html?: string;
43
43
  };
44
- export type HtmlFragmentSource = "markdown" | "plugin-page";
45
- export type HtmlFragmentProps = {
44
+ type HtmlFragmentSource = "markdown" | "plugin-page";
45
+ type HtmlFragmentProps = {
46
46
  html: string;
47
47
  source: HtmlFragmentSource;
48
48
  };
@@ -53,7 +53,7 @@ export declare function HtmlFragment(props: HtmlFragmentProps): import("hono/uti
53
53
  * Owns the `.rb-article-content` Stable Hook so Sites never hand-write it.
54
54
  */
55
55
  export declare function ArticleBody(props: ArticleBodyProps): import("hono/jsx/jsx-dev-runtime").JSX.Element;
56
- export type PageBodyProps = PrimitiveClassProps & {
56
+ type PageBodyProps = PrimitiveClassProps & {
57
57
  html: string;
58
58
  };
59
59
  /**
@@ -65,7 +65,7 @@ export type PageBodyProps = PrimitiveClassProps & {
65
65
  export declare function PageBody(props: PageBodyProps): import("hono/jsx/jsx-dev-runtime").JSX.Element;
66
66
  export declare function ArticleFooter(props: ArticleFooterProps): import("hono/jsx/jsx-dev-runtime").JSX.Element;
67
67
  export declare function Sidebar(props: SidebarProps): import("hono/jsx/jsx-dev-runtime").JSX.Element;
68
- export type ThemeRootProps = {
68
+ type ThemeRootProps = {
69
69
  theme?: ResolvedRiebeckiteConfig["theme"];
70
70
  lang?: string;
71
71
  children?: PrimitiveChildren;
@@ -84,7 +84,7 @@ export declare function themeRootAttributes(theme: ResolvedRiebeckiteConfig["the
84
84
  * `<head>` and `<body>` as children.
85
85
  */
86
86
  export declare function ThemeRoot(props: ThemeRootProps): import("hono/jsx/jsx-dev-runtime").JSX.Element;
87
- export type PluginHeadTagsProps = {
87
+ type PluginHeadTagsProps = {
88
88
  tags?: readonly PluginHeadTag[];
89
89
  };
90
90
  /**
@@ -93,12 +93,14 @@ export type PluginHeadTagsProps = {
93
93
  * The Site owns placement; this primitive owns the conversion and ordering.
94
94
  */
95
95
  export declare function PluginHeadTags(props: PluginHeadTagsProps): import("hono/jsx/jsx-dev-runtime").JSX.Element;
96
- export type RiebeckiteHeadProps = {
96
+ type RiebeckiteHeadProps = {
97
97
  title?: string;
98
98
  headTags?: readonly PluginHeadTag[];
99
99
  faviconHref?: string | null;
100
100
  colorModeScript?: boolean;
101
101
  stylesheets?: readonly string[];
102
+ themeStyle?: string;
103
+ themeStylesheets?: readonly string[];
102
104
  clientSrc?: string | null;
103
105
  prod?: boolean;
104
106
  children?: PrimitiveChildren;
@@ -111,3 +113,19 @@ export type RiebeckiteHeadProps = {
111
113
  * surrounding `<head>` so it can add its own elements next to this primitive.
112
114
  */
113
115
  export declare function RiebeckiteHead(props: RiebeckiteHeadProps): import("hono/jsx/jsx-dev-runtime").JSX.Element;
116
+ type SeoHeadProps = {
117
+ seo: SeoMetadata;
118
+ siteTitle: string;
119
+ author?: string;
120
+ ogLocale?: string;
121
+ ogAlternates?: readonly string[];
122
+ twitterSite?: string;
123
+ feedLinks?: readonly {
124
+ href: string;
125
+ type: string;
126
+ }[];
127
+ };
128
+ export declare function SeoHead(props: SeoHeadProps): import("hono/jsx/jsx-dev-runtime").JSX.Element;
129
+ export declare function getThemeStyle(tokens: ResolvedRiebeckiteConfig["theme"]["tokens"]): string;
130
+ export declare function getThemeStylesheets(userCss: readonly string[]): string[];
131
+ export {};
@@ -1,4 +1,5 @@
1
1
  // src/ui/primitives.tsx
2
+ import { escapeScriptJson, uniqueStrings } from "@riebeckite/core";
2
3
  import { ColorModeScript } from "@riebeckite/plugin-color-mode";
3
4
  import { raw } from "hono/html";
4
5
  import { Link, Script } from "honox/server";
@@ -161,11 +162,228 @@ function RiebeckiteHead(props) {
161
162
  faviconHref ? /* @__PURE__ */ jsx("link", { rel: "icon", href: faviconHref }) : null,
162
163
  colorModeScript ? /* @__PURE__ */ jsx(ColorModeScript, {}) : null,
163
164
  stylesheets.map((href) => /* @__PURE__ */ jsx(Link, { href, rel: "stylesheet", prod: props.prod }, href)),
165
+ props.themeStyle ? /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: props.themeStyle } }) : null,
166
+ props.themeStylesheets?.map((href) => /* @__PURE__ */ jsx(Link, { href, rel: "stylesheet", prod: props.prod }, href)),
164
167
  /* @__PURE__ */ jsx(PluginHeadTags, { tags: props.headTags ?? site?.headTags }),
165
168
  clientSrc ? /* @__PURE__ */ jsx(Script, { src: clientSrc, async: true, prod: props.prod }) : null,
166
169
  props.children
167
170
  ] });
168
171
  }
172
+ function SeoHead(props) {
173
+ const { seo } = props;
174
+ const twitterCard = seo.imageUrl ? "summary_large_image" : "summary";
175
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
176
+ /* @__PURE__ */ jsx(
177
+ "meta",
178
+ {
179
+ name: "description",
180
+ content: seo.description,
181
+ "data-riebeckite-head": "page"
182
+ }
183
+ ),
184
+ props.author ? /* @__PURE__ */ jsx(
185
+ "meta",
186
+ {
187
+ name: "author",
188
+ content: props.author,
189
+ "data-riebeckite-head": "page"
190
+ }
191
+ ) : null,
192
+ seo.noindex ? /* @__PURE__ */ jsx(
193
+ "meta",
194
+ {
195
+ name: "robots",
196
+ content: "noindex, nofollow",
197
+ "data-riebeckite-head": "page"
198
+ }
199
+ ) : null,
200
+ /* @__PURE__ */ jsx("title", { children: seo.title }),
201
+ seo.canonicalUrl ? /* @__PURE__ */ jsx(
202
+ "link",
203
+ {
204
+ rel: "canonical",
205
+ href: seo.canonicalUrl,
206
+ "data-riebeckite-head": "page"
207
+ }
208
+ ) : null,
209
+ /* @__PURE__ */ jsx(
210
+ "meta",
211
+ {
212
+ property: "og:title",
213
+ content: seo.title,
214
+ "data-riebeckite-head": "page"
215
+ }
216
+ ),
217
+ /* @__PURE__ */ jsx(
218
+ "meta",
219
+ {
220
+ property: "og:description",
221
+ content: seo.description,
222
+ "data-riebeckite-head": "page"
223
+ }
224
+ ),
225
+ /* @__PURE__ */ jsx("meta", { property: "og:type", content: seo.type, "data-riebeckite-head": "page" }),
226
+ seo.canonicalUrl ? /* @__PURE__ */ jsx(
227
+ "meta",
228
+ {
229
+ property: "og:url",
230
+ content: seo.canonicalUrl,
231
+ "data-riebeckite-head": "page"
232
+ }
233
+ ) : null,
234
+ /* @__PURE__ */ jsx(
235
+ "meta",
236
+ {
237
+ property: "og:site_name",
238
+ content: props.siteTitle,
239
+ "data-riebeckite-head": "page"
240
+ }
241
+ ),
242
+ props.ogLocale ? /* @__PURE__ */ jsx(
243
+ "meta",
244
+ {
245
+ property: "og:locale",
246
+ content: props.ogLocale,
247
+ "data-riebeckite-head": "page"
248
+ }
249
+ ) : null,
250
+ props.ogAlternates?.map((locale) => /* @__PURE__ */ jsx(
251
+ "meta",
252
+ {
253
+ property: "og:locale:alternate",
254
+ content: locale,
255
+ "data-riebeckite-head": "page"
256
+ },
257
+ locale
258
+ )),
259
+ seo.imageUrl ? /* @__PURE__ */ jsx(
260
+ "meta",
261
+ {
262
+ property: "og:image",
263
+ content: seo.imageUrl,
264
+ "data-riebeckite-head": "page"
265
+ }
266
+ ) : null,
267
+ seo.publishedTime ? /* @__PURE__ */ jsx(
268
+ "meta",
269
+ {
270
+ property: "article:published_time",
271
+ content: seo.publishedTime,
272
+ "data-riebeckite-head": "page"
273
+ }
274
+ ) : null,
275
+ seo.modifiedTime ? /* @__PURE__ */ jsx(
276
+ "meta",
277
+ {
278
+ property: "article:modified_time",
279
+ content: seo.modifiedTime,
280
+ "data-riebeckite-head": "page"
281
+ }
282
+ ) : null,
283
+ seo.tags.map((tag) => /* @__PURE__ */ jsx(
284
+ "meta",
285
+ {
286
+ property: "article:tag",
287
+ content: tag,
288
+ "data-riebeckite-head": "page"
289
+ },
290
+ tag
291
+ )),
292
+ /* @__PURE__ */ jsx(
293
+ "meta",
294
+ {
295
+ name: "twitter:card",
296
+ content: twitterCard,
297
+ "data-riebeckite-head": "page"
298
+ }
299
+ ),
300
+ /* @__PURE__ */ jsx(
301
+ "meta",
302
+ {
303
+ name: "twitter:title",
304
+ content: seo.title,
305
+ "data-riebeckite-head": "page"
306
+ }
307
+ ),
308
+ /* @__PURE__ */ jsx(
309
+ "meta",
310
+ {
311
+ name: "twitter:description",
312
+ content: seo.description,
313
+ "data-riebeckite-head": "page"
314
+ }
315
+ ),
316
+ props.twitterSite ? /* @__PURE__ */ jsx(
317
+ "meta",
318
+ {
319
+ name: "twitter:site",
320
+ content: props.twitterSite,
321
+ "data-riebeckite-head": "page"
322
+ }
323
+ ) : null,
324
+ seo.imageUrl ? /* @__PURE__ */ jsx(
325
+ "meta",
326
+ {
327
+ name: "twitter:image",
328
+ content: seo.imageUrl,
329
+ "data-riebeckite-head": "page"
330
+ }
331
+ ) : null,
332
+ props.feedLinks?.map((feed) => /* @__PURE__ */ jsx(
333
+ "link",
334
+ {
335
+ rel: "alternate",
336
+ type: feed.type,
337
+ href: feed.href,
338
+ "data-riebeckite-head": "global"
339
+ },
340
+ feed.href
341
+ )),
342
+ seo.jsonLd ? /* @__PURE__ */ jsx(
343
+ "script",
344
+ {
345
+ type: "application/ld+json",
346
+ "data-riebeckite-head": "page",
347
+ dangerouslySetInnerHTML: {
348
+ __html: escapeScriptJson(JSON.stringify(seo.jsonLd))
349
+ }
350
+ }
351
+ ) : null
352
+ ] });
353
+ }
354
+ function getThemeStyle(tokens) {
355
+ const variables = [
356
+ ["--rb-color-paper", tokens.color?.paper],
357
+ ["--rb-color-ink", tokens.color?.ink],
358
+ ["--rb-color-muted", tokens.color?.muted],
359
+ ["--rb-color-accent", tokens.color?.accent],
360
+ ["--rb-color-link", tokens.color?.link],
361
+ ["--rb-color-focus", tokens.color?.focus],
362
+ ["--rb-color-selected", tokens.color?.selected],
363
+ ["--rb-color-border", tokens.color?.border],
364
+ ["--rb-color-border-strong", tokens.color?.borderStrong],
365
+ ["--rb-color-surface", tokens.color?.surface],
366
+ ["--rb-color-surface-hover", tokens.color?.surfaceHover],
367
+ ["--rb-color-overlay", tokens.color?.overlay],
368
+ ["--rb-color-danger", tokens.color?.danger],
369
+ ["--rb-color-success", tokens.color?.success],
370
+ ["--rb-color-code-background", tokens.color?.codeBackground],
371
+ ["--rb-color-code-ink", tokens.color?.codeInk],
372
+ ["--rb-color-code-inline", tokens.color?.codeInline],
373
+ ["--rb-font-body", tokens.typography?.bodyFont],
374
+ ["--rb-font-heading", tokens.typography?.headingFont],
375
+ ["--rb-font-mono", tokens.typography?.monoFont],
376
+ ["--rb-layout-page-max", tokens.layout?.pageMaxWidth],
377
+ ["--rb-layout-article-max", tokens.layout?.articleMaxWidth],
378
+ ["--rb-layout-sidebar", tokens.layout?.sidebarWidth],
379
+ ["--rb-layout-gap", tokens.layout?.contentGap]
380
+ ];
381
+ const declarations = variables.filter((variable) => Boolean(variable[1])).map(([name, value]) => `${name}: ${value};`);
382
+ return declarations.length === 0 ? "" : `:root { ${declarations.join(" ")} }`;
383
+ }
384
+ function getThemeStylesheets(userCss) {
385
+ return uniqueStrings([...userCss]);
386
+ }
169
387
  function joinClassNames(...classNames) {
170
388
  return classNames.filter(Boolean).join(" ");
171
389
  }
@@ -181,7 +399,10 @@ export {
181
399
  PageBody,
182
400
  PluginHeadTags,
183
401
  RiebeckiteHead,
402
+ SeoHead,
184
403
  Sidebar,
185
404
  ThemeRoot,
405
+ getThemeStyle,
406
+ getThemeStylesheets,
186
407
  themeRootAttributes
187
408
  };
@@ -1,6 +1,6 @@
1
1
  import type { ResolvedRiebeckiteConfig, Tracer } from "@riebeckite/core";
2
2
  import { type RiebeckiteConfigLoaderOptions } from "./config_loader.js";
3
- export type HonoxApplicationOptions = {
3
+ type HonoxApplicationOptions = {
4
4
  root: string;
5
5
  tracer?: Tracer;
6
6
  server?: {
@@ -29,3 +29,4 @@ export declare class HonoxApplicationRootError extends Error {
29
29
  export declare function resolveHonoxApplication(options: ResolveHonoxApplicationOptions): Promise<ResolvedHonoxApplication>;
30
30
  export declare function startHonoxDevServer(options: HonoxApplicationOptions): Promise<void>;
31
31
  export declare function buildHonoxApplication(options: HonoxApplicationOptions): Promise<void>;
32
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riebeckite/honox",
3
- "version": "0.0.32",
3
+ "version": "0.0.33",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -40,19 +40,20 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@babel/parser": "^7.29.3",
43
+ "@riebeckite/core": "0.0.33",
44
+ "@riebeckite/diagnostics": "0.0.33",
45
+ "@riebeckite/node": "0.0.33",
46
+ "@riebeckite/plugin-color-mode": "0.0.33",
47
+ "@riebeckite/spa": "0.0.33",
48
+ "@riebeckite/ui": "0.0.33",
43
49
  "esbuild": "^0.28.2",
44
50
  "hono": "^4.12.25",
45
51
  "honox": "0.1.56",
46
- "vite": "^8.0.9",
47
- "@riebeckite/core": "0.0.32",
48
- "@riebeckite/diagnostics": "0.0.32",
49
- "@riebeckite/plugin-color-mode": "0.0.32",
50
- "@riebeckite/ui": "0.0.32",
51
- "@riebeckite/spa": "0.0.32"
52
+ "vite": "^8.3.5"
52
53
  },
53
54
  "devDependencies": {
54
- "tsx": "^4.22.4",
55
- "@riebeckite/test": "0.0.32"
55
+ "@riebeckite/test": "0.0.33",
56
+ "tsx": "^4.22.4"
56
57
  },
57
58
  "publishConfig": {
58
59
  "access": "public"
package/style.css CHANGED
@@ -22,6 +22,21 @@
22
22
 
23
23
  .rb-article-body {
24
24
  min-width: 0;
25
+ padding-inline: var(--rb-space-2, 1rem);
26
+ }
27
+
28
+ .rb-article {
29
+ width: min(100%, var(--rb-layout-page-max, 80rem));
30
+ max-width: none;
31
+ }
32
+
33
+ :is(:root, .rb-theme-root)[data-article-layout="full-width"] .rb-article {
34
+ width: 100%;
35
+ }
36
+
37
+ :is(:root, .rb-theme-root)[data-article-layout="full-width"]
38
+ .rb-article-body {
39
+ max-width: none;
25
40
  }
26
41
 
27
42
  .rb-page-body {
@@ -80,3 +95,237 @@
80
95
  transform: translateY(0);
81
96
  }
82
97
  }
98
+ @layer components {
99
+ .rb-article-header h1 {
100
+ word-break: auto-phrase;
101
+ }
102
+
103
+ .rb-article-content :where(h1, h2, h3, h4, h5, h6) {
104
+ font-weight: 600;
105
+ line-height: 1.3;
106
+ text-wrap: balance;
107
+ word-break: auto-phrase;
108
+ }
109
+
110
+ .rb-article-content :where(h1) {
111
+ margin-block: 0 1em;
112
+ font-size: 2.25em;
113
+ }
114
+
115
+ .rb-article-content :where(h2) {
116
+ margin-block: 2em 1em;
117
+ font-size: 1.5em;
118
+ }
119
+
120
+ .rb-article-content :where(h3) {
121
+ margin-block: 1.6em 0.6em;
122
+ font-size: 1.25em;
123
+ }
124
+
125
+ .rb-article-content :where(h4) {
126
+ margin-block: 1.5em 0.5em;
127
+ font-size: 1em;
128
+ }
129
+
130
+ .rb-article-content :where(p) {
131
+ margin-block: 1.25em;
132
+ }
133
+
134
+ .rb-article-content :where(a) {
135
+ color: inherit;
136
+ text-decoration: underline;
137
+ text-decoration-thickness: 1px;
138
+ text-underline-offset: 0.2em;
139
+ }
140
+
141
+ .rb-article-content :where(strong) {
142
+ font-weight: 600;
143
+ }
144
+
145
+ .rb-article-content :where(ul) {
146
+ list-style-type: disc;
147
+ }
148
+
149
+ .rb-article-content :where(ol) {
150
+ list-style-type: decimal;
151
+ }
152
+
153
+ .rb-article-content :where(ol[type="A"]) {
154
+ list-style-type: upper-alpha;
155
+ }
156
+
157
+ .rb-article-content :where(ol[type="a"]) {
158
+ list-style-type: lower-alpha;
159
+ }
160
+
161
+ .rb-article-content :where(ol[type="I"]) {
162
+ list-style-type: upper-roman;
163
+ }
164
+
165
+ .rb-article-content :where(ol[type="i"]) {
166
+ list-style-type: lower-roman;
167
+ }
168
+
169
+ .rb-article-content :where(ul, ol) {
170
+ margin-block: 1.25em;
171
+ padding-inline-start: 1.625em;
172
+ }
173
+
174
+ .rb-article-content :where(li) {
175
+ margin-block: 0.5em;
176
+ }
177
+
178
+ .rb-article-content :where(ul ul, ul ol, ol ul, ol ol) {
179
+ margin-block: 0.75em;
180
+ }
181
+
182
+ .rb-article-content :where(.task-list-item) {
183
+ list-style: none;
184
+ }
185
+
186
+ .rb-article-content :where(.task-list-item) > :where(input[type="checkbox"]) {
187
+ margin-inline-end: 0.5em;
188
+ }
189
+
190
+ .rb-article-content :where(blockquote) {
191
+ margin-block: 2em;
192
+ padding-inline-start: 1em;
193
+ border-inline-start: 2px solid var(--rb-color-border);
194
+ }
195
+
196
+ .rb-article-content :where(hr) {
197
+ margin-block: 3em;
198
+ border: 0;
199
+ border-block-start: 1px solid var(--rb-color-border);
200
+ }
201
+
202
+ .rb-article-content :where(:not(pre) > code) {
203
+ font-family: var(--rb-font-mono);
204
+ font-size: 0.875em;
205
+ }
206
+
207
+ .rb-article-content :where(pre):not(:where(.rr-code *)) {
208
+ margin-block: 1.5em;
209
+ padding: 0.75rem 1rem;
210
+ border-radius: 6px;
211
+ background: var(--rb-color-code-background);
212
+ color: var(--rb-color-ink);
213
+ font-family: var(--rb-font-mono);
214
+ font-size: 0.875em;
215
+ line-height: 1.7;
216
+ overflow-x: auto;
217
+ }
218
+
219
+ .rb-article-content :where(pre):not(:where(.rr-code *)) :where(code) {
220
+ background: none;
221
+ border: 0;
222
+ padding: 0;
223
+ font-size: inherit;
224
+ }
225
+
226
+ .rb-article-content :where(table) {
227
+ width: 100%;
228
+ max-width: 100%;
229
+ border-collapse: collapse;
230
+ table-layout: auto;
231
+ }
232
+
233
+ .rb-article-content :where(.rb-table-scroll) {
234
+ max-width: 100%;
235
+ margin-block: 1.5em;
236
+ overflow-x: auto;
237
+ }
238
+
239
+ .rb-article-content :where(figure) {
240
+ margin-block: 2em;
241
+ }
242
+
243
+ .rb-article-content :where(figure > *) {
244
+ margin-block: 0;
245
+ }
246
+
247
+ .rb-article-content :where(figcaption) {
248
+ color: var(--rb-color-muted);
249
+ font-size: 0.875em;
250
+ line-height: 1.43;
251
+ }
252
+
253
+ .rb-source-fallback {
254
+ inline-size: 100%;
255
+ margin-block: 0.75rem 0;
256
+ border-block-start: 1px solid var(--rb-color-border);
257
+ color: var(--rb-color-muted);
258
+ font-size: 0.8125rem;
259
+ text-align: start;
260
+ }
261
+
262
+ figure > .rb-source-fallback {
263
+ margin-block: 0;
264
+ }
265
+
266
+ .rb-source-fallback > summary {
267
+ padding: 0.5rem 0.75rem;
268
+ cursor: pointer;
269
+ }
270
+
271
+ .rb-source-fallback > pre {
272
+ overflow: auto;
273
+ margin: 0 0.75rem 0.75rem;
274
+ padding: 0.75rem;
275
+ border: 1px solid var(--rb-color-border);
276
+ border-radius: 6px;
277
+ background: var(--rb-color-code-background);
278
+ color: var(--rb-color-ink);
279
+ font-size: 0.8125rem;
280
+ }
281
+
282
+ .rb-article-content :where(dl) {
283
+ margin-block: 1.25em;
284
+ }
285
+
286
+ .rb-article-content :where(dt) {
287
+ margin-block-start: 1.25em;
288
+ font-weight: 600;
289
+ }
290
+
291
+ .rb-article-content :where(dd) {
292
+ margin-block-start: 0.5em;
293
+ padding-inline-start: 1.625em;
294
+ }
295
+
296
+ .rb-article-content :where(kbd) {
297
+ padding: 0.15em 0.4em;
298
+ border: 1px solid var(--rb-color-border);
299
+ border-radius: 4px;
300
+ background: var(--rb-color-surface);
301
+ color: inherit;
302
+ font-family: var(--rb-font-mono);
303
+ font-size: 0.875em;
304
+ }
305
+
306
+ .rb-article-content :where(img, picture, video) {
307
+ margin-block: 2em;
308
+ }
309
+
310
+ .rb-article-content :where(picture > img) {
311
+ margin-block: 0;
312
+ }
313
+
314
+ .rb-article-content > :where(:first-child) {
315
+ margin-block-start: 0;
316
+ }
317
+
318
+ .rb-article-content > :where(:last-child) {
319
+ margin-block-end: 0;
320
+ }
321
+
322
+ .rb-article-content :where(hr, h2, h3, h4) + * {
323
+ margin-block-start: 0;
324
+ }
325
+
326
+ @media (max-width: 48rem) {
327
+ .rb-article-content {
328
+ overflow-wrap: break-word;
329
+ }
330
+ }
331
+ }