hono-decks 1.1.0 → 1.2.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/vite.js CHANGED
@@ -7,10 +7,10 @@ import { unified } from "unified";
7
7
  import remarkGfm from "remark-gfm";
8
8
  import remarkMdx from "remark-mdx";
9
9
  import remarkParse from "remark-parse";
10
- import { fileURLToPath, pathToFileURL } from "node:url";
11
10
  import { compile } from "@mdx-js/mdx";
12
11
  import remarkDirective from "remark-directive";
13
12
  import { codeToHtml } from "shiki";
13
+ import { fileURLToPath, pathToFileURL } from "node:url";
14
14
  import { existsSync } from "node:fs";
15
15
  import { lookup } from "node:dns/promises";
16
16
  import { isIP } from "node:net";
@@ -1505,29 +1505,20 @@ function extractDeckPrelude(source) {
1505
1505
  };
1506
1506
  }
1507
1507
  //#endregion
1508
- //#region src/node/void-markdown.ts
1509
- /** Load only for an explicit Void Markdown build; ordinary MDX has no new peers. */
1510
- async function compileVoidMarkdownDecks(input) {
1508
+ //#region src/node/markdown-decks.ts
1509
+ async function compileMarkdownDecks(input, adapter) {
1511
1510
  const markdownEntries = input.decks.filter((entry) => entry.sourcePath.endsWith(".md"));
1512
1511
  const mdx = await compileMdxModuleDecks({
1513
1512
  ...input,
1514
1513
  decks: input.decks.filter((entry) => !entry.sourcePath.endsWith(".md"))
1515
1514
  });
1516
1515
  if (!markdownEntries.length) return mdx;
1517
- let compiler;
1518
- try {
1519
- compiler = await import(pathToFileURL(createRequire(resolve(input.cwd ?? process.cwd(), "package.json")).resolve("void-plugin-hono-jsx/markdown/compiler")).href);
1520
- if (typeof (compiler.compileMarkdownModule ?? compiler.compileStaticMarkdown) !== "function" || typeof (compiler.readMarkdownModule ?? compiler.readStaticMarkdown) !== "function") throw new Error("Missing standalone compiler API");
1521
- } catch (error) {
1522
- throw new Error("Void Markdown requires void-plugin-hono-jsx with the public /markdown/compiler API and its Markdown peers installed in the app.", { cause: error });
1523
- }
1524
- const readModule = compiler.readMarkdownModule ?? compiler.readStaticMarkdown;
1525
- const compileModule = compiler.compileMarkdownModule ?? compiler.compileStaticMarkdown;
1526
1516
  const decks = [...mdx.decks];
1527
1517
  const clients = /* @__PURE__ */ new Set();
1528
1518
  const markdownIslands = /* @__PURE__ */ new Map();
1529
1519
  for (const entry of markdownEntries) try {
1530
- const parsed = readModule(await input.readText(entry.sourcePath));
1520
+ const source = await input.readText(entry.sourcePath);
1521
+ const parsed = adapter.read(source);
1531
1522
  if (input.clientComponentIds?.[entry.slug] && Object.keys(input.clientComponentIds[entry.slug]).length) throw new Error("Void Markdown slides support static components only; use MDX for hono-decks client components.");
1532
1523
  const warnings = [];
1533
1524
  const meta = toDeckFrontmatter(parsed.frontmatter, warnings);
@@ -1548,12 +1539,13 @@ async function compileVoidMarkdownDecks(input) {
1548
1539
  const slides = [];
1549
1540
  for (const [index, slideSource] of splitMarkdownSlides(parsed.content).entries()) {
1550
1541
  const prelude = parsed.script ? `<script>\n${parsed.script}\n<\/script>\n` : "";
1551
- const slide = readModule(prelude + slideSource);
1542
+ const slide = adapter.read(prelude + slideSource);
1552
1543
  const path = `${input.outDir}/decks/${entry.slug}/slide-${index}.ts`;
1553
- const result = await compileModule(rewriteAssetUrls(`${prelude}---\n${JSON.stringify({
1544
+ const rewritten = rewriteAssetUrls(`${prelude}---\n${JSON.stringify({
1554
1545
  ...parsed.frontmatter,
1555
1546
  ...slide.frontmatter
1556
- })}\n---\n${slide.content}`, assets), { sourcePath: resolve(input.cwd ?? process.cwd(), entry.sourcePath) });
1547
+ })}\n---\n${slide.content}`, assets);
1548
+ const result = await adapter.compile(rewritten, { sourcePath: resolve(input.cwd ?? process.cwd(), entry.sourcePath) });
1557
1549
  clientEntryRequired ||= Boolean(result.clientCode || result.islands?.length);
1558
1550
  if (result.clientCode) clients.add(result.clientCode);
1559
1551
  for (const island of result.islands ?? []) markdownIslands.set(island.id, island);
@@ -1603,6 +1595,7 @@ async function compileVoidMarkdownDecks(input) {
1603
1595
  decks,
1604
1596
  routerModule: emitModuleDecksRouter({ decks }),
1605
1597
  clientCode: [...clients].join("\n"),
1598
+ clientModules: [adapter.client([...markdownIslands.values()])].filter(Boolean),
1606
1599
  markdownIslands: [...markdownIslands.values()]
1607
1600
  };
1608
1601
  }
@@ -1639,6 +1632,64 @@ function splitMarkdownSlides(source) {
1639
1632
  return splitSlideSources(protectedLines.join("\n")).map((slide) => slide.replace(new RegExp(`${prefix}(\\d+)_END`, "g"), (_, id) => fences[Number(id)]));
1640
1633
  }
1641
1634
  //#endregion
1635
+ //#region src/adapters/hono-markdown.ts
1636
+ /** Node-only bridge. Existing Void configs retain their original compiler/runtime pair. */
1637
+ async function createHonoMarkdownAdapter(cwd, legacy = false, options) {
1638
+ const require = createRequire(resolve(cwd, "package.json"));
1639
+ const entry = legacy ? "void-plugin-hono-jsx/markdown/compiler" : "@ts-76/hono-markdown/compiler";
1640
+ let compiler;
1641
+ try {
1642
+ compiler = await import(pathToFileURL(require.resolve(entry)).href);
1643
+ } catch (cause) {
1644
+ throw new Error(`Hono Markdown requires ${legacy ? "its public /markdown/compiler API" : "@ts-76/hono-markdown/compiler"} and Markdown peers installed in the app.`, { cause });
1645
+ }
1646
+ const read = compiler.readMarkdownModule ?? compiler.readStaticMarkdown;
1647
+ const compile = compiler.compileMarkdownModule ?? compiler.compileStaticMarkdown;
1648
+ if (typeof read !== "function" || typeof compile !== "function") throw new Error("Missing standalone compiler API");
1649
+ const runtime = entry.startsWith("void-plugin") ? "void-plugin-hono-jsx/markdown/client" : "@ts-76/hono-markdown/client";
1650
+ return {
1651
+ read,
1652
+ compile: (source, input) => compile(source, {
1653
+ ...input,
1654
+ options
1655
+ }),
1656
+ client: (islands) => emitMarkdownClient(islands, runtime)
1657
+ };
1658
+ }
1659
+ function emitMarkdownClient(islands, runtime) {
1660
+ if (!islands.length) return "";
1661
+ const literal = (value) => JSON.stringify(value).replace(/</g, "\\u003c");
1662
+ return `import { hydrateMarkdownIslands } from ${literal(runtime)};
1663
+ hydrateMarkdownIslands({ ${islands.map((island) => `${literal(island.id)}: () => import(${literal(island.modulePath)})`).join(",")} }, {
1664
+ ready(element, start) {
1665
+ const slide = element.closest('section[data-slide-state]');
1666
+ if (!slide || slide.dataset.slideState === 'active') { start(); return; }
1667
+ const observer = new MutationObserver(() => {
1668
+ if (slide.dataset.slideState === 'active') { observer.disconnect(); start(); }
1669
+ });
1670
+ observer.observe(slide, { attributes: true, attributeFilter: ['data-slide-state'] });
1671
+ }
1672
+ });`;
1673
+ }
1674
+ //#endregion
1675
+ //#region src/node/markdown-adapter.ts
1676
+ async function loadMarkdownAdapter(selection, cwd) {
1677
+ if (selection === "void") return createHonoMarkdownAdapter(cwd, true);
1678
+ if (selection.adapter === "hono-decks/adapters/hono-markdown") return createHonoMarkdownAdapter(cwd, false, selection.options);
1679
+ const module = await import(pathToFileURL(createRequire(resolve(cwd, "package.json")).resolve(selection.adapter)).href);
1680
+ if (typeof module.createMarkdownAdapter !== "function") throw new Error("Markdown adapter must export createMarkdownAdapter.");
1681
+ const adapter = await module.createMarkdownAdapter({
1682
+ cwd,
1683
+ options: selection.options
1684
+ });
1685
+ if (![
1686
+ adapter?.read,
1687
+ adapter?.compile,
1688
+ adapter?.client
1689
+ ].every((value) => typeof value === "function")) throw new Error("Markdown adapter must provide read, compile and client functions.");
1690
+ return adapter;
1691
+ }
1692
+ //#endregion
1642
1693
  //#region src/node/client-entry.ts
1643
1694
  async function discoverClientComponentIds(input) {
1644
1695
  const result = {};
@@ -1659,12 +1710,11 @@ async function emitClientEntryModule(input) {
1659
1710
  registrations.push(`${JSON.stringify(clientId)}: ${localName}`);
1660
1711
  }
1661
1712
  }
1662
- const markdown = input.markdownIslands ?? [];
1663
- if (registrations.length === 0 && markdown.length === 0) return `export const decksClientEntry = ${JSON.stringify(input.extraCode ?? "")};\n`;
1713
+ const clientModules = input.clientModules ?? [];
1714
+ if (registrations.length === 0 && clientModules.length === 0) return `export const decksClientEntry = ${JSON.stringify(input.extraCode ?? "")};\n`;
1664
1715
  const result = await build({
1665
1716
  stdin: {
1666
- contents: `${markdown.length ? `import { hydrateMarkdownIslands } from "void-plugin-hono-jsx/markdown/client";
1667
- hydrateMarkdownIslands({ ${markdown.map((entry) => `${javascriptString(entry.id)}: () => import(${javascriptString(entry.modulePath)})`).join(",")} });` : ""}
1717
+ contents: `${clientModules.join("\n")}
1668
1718
  import { hydrateSlideIslands } from "hono-decks/client";
1669
1719
  ${imports.join("\n")}
1670
1720
 
@@ -1741,9 +1791,6 @@ function hashString(value) {
1741
1791
  function safeIdentifier(value) {
1742
1792
  return value.replace(/[^A-Za-z0-9_$]+/g, "_").replace(/^[^A-Za-z_$]+/, "_") || "_";
1743
1793
  }
1744
- function javascriptString(value) {
1745
- return JSON.stringify(value).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
1746
- }
1747
1794
  //#endregion
1748
1795
  //#region src/node/path-utils.ts
1749
1796
  function dirname(path) {
@@ -2023,7 +2070,7 @@ async function compileDeckModules(input) {
2023
2070
  const out = normalizeRelativePath(input.out, "Output directory");
2024
2071
  const root = normalizeDeckRoot(input.root);
2025
2072
  const paths = await listFiles(input.cwd, join(input.cwd, root));
2026
- const resolved = resolveDeckFiles(paths, root, { markdown: input.markdown === "void" });
2073
+ const resolved = resolveDeckFiles(paths, root, { markdown: Boolean(input.markdown) });
2027
2074
  const componentModulePaths = Object.fromEntries(resolved.flatMap((deck) => {
2028
2075
  const path = deck.kind === "directory" ? findComponentModule(paths, root, deck.slug) : void 0;
2029
2076
  return path ? [[deck.slug, path]] : [];
@@ -2051,7 +2098,8 @@ async function compileDeckModules(input) {
2051
2098
  refresh: input.refreshOgp,
2052
2099
  resolveOgp: input.resolveOgp ?? resolveOgpMetadata
2053
2100
  });
2054
- const generated = await (input.markdown === "void" ? compileVoidMarkdownDecks : compileMdxModuleDecks)({
2101
+ const adapter = input.markdown && resolved.some((entry) => entry.sourcePath.endsWith(".md")) ? await loadMarkdownAdapter(input.markdown, input.cwd) : void 0;
2102
+ const generated = await (adapter ? (value) => compileMarkdownDecks(value, adapter) : compileMdxModuleDecks)({
2055
2103
  cwd: input.cwd,
2056
2104
  root,
2057
2105
  outDir: out,
@@ -2070,7 +2118,7 @@ async function compileDeckModules(input) {
2070
2118
  clientEntries: clientEntryPaths,
2071
2119
  clientComponentIds,
2072
2120
  extraCode: generated.clientCode,
2073
- markdownIslands: generated.markdownIslands,
2121
+ clientModules: generated.clientModules,
2074
2122
  onCss: (css) => {
2075
2123
  clientCss = css;
2076
2124
  }
@@ -2220,7 +2268,7 @@ async function loadDecksConfig(input) {
2220
2268
  const config = (await import(`data:text/javascript;charset=utf-8,${encodeURIComponent(source)}#${Date.now()}`)).default;
2221
2269
  if (!config || typeof config !== "object") throw new Error(`Config must have a default export: ${path}`);
2222
2270
  if (typeof config.mountPath !== "string") throw new Error(`Config mountPath must be a string: ${path}`);
2223
- if (config.build?.markdown !== void 0 && config.build.markdown !== "void") throw new Error("Config build.markdown must be \"void\".");
2271
+ if (config.build?.markdown !== void 0 && config.build.markdown !== "void" && (typeof config.build.markdown !== "object" || !config.build.markdown || typeof config.build.markdown.adapter !== "string" || !config.build.markdown.adapter.trim())) throw new Error("Config build.markdown must be \"void\" or an adapter reference.");
2224
2272
  return {
2225
2273
  path,
2226
2274
  config,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hono-decks",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Slide decks for Hono and Cloudflare Workers",
5
5
  "keywords": [
6
6
  "cloudflare-workers",
@@ -63,6 +63,10 @@
63
63
  },
64
64
  "./vite-env": {
65
65
  "types": "./vite-env.d.ts"
66
+ },
67
+ "./adapters/hono-markdown": {
68
+ "types": "./dist/adapters/hono-markdown.d.ts",
69
+ "import": "./dist/adapters/hono-markdown.js"
66
70
  }
67
71
  },
68
72
  "publishConfig": {