@moku-labs/web 1.8.1 → 1.9.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/browser.d.mts +40 -1
- package/dist/index.cjs +204 -12
- package/dist/index.d.cts +40 -1
- package/dist/index.d.mts +40 -1
- package/dist/index.mjs +204 -12
- package/package.json +8 -1
package/dist/browser.d.mts
CHANGED
|
@@ -1920,7 +1920,7 @@ type DataProvider = {
|
|
|
1920
1920
|
*/
|
|
1921
1921
|
declare const dataPlugin: import("@moku-labs/core").PluginInstance<"data", DataConfig, DataState, DataProvider, {}> & Record<never, never>;
|
|
1922
1922
|
declare namespace types_d_exports {
|
|
1923
|
-
export { Api, Article, ArticleCard, ComputedFields, Config, ContentApiContext, ContentEvents, ContentProvider, ContentProviderState, FileSystemContentOptions, Frontmatter, LoadAllOptions, State };
|
|
1923
|
+
export { Api, Article, ArticleCard, ComputedFields, Config, ContentApiContext, ContentEvents, ContentProvider, ContentProviderState, FileSystemContentOptions, Frontmatter, LoadAllOptions, MermaidDiagramOptions, State };
|
|
1924
1924
|
}
|
|
1925
1925
|
/**
|
|
1926
1926
|
* YAML frontmatter parsed from each article file.
|
|
@@ -2034,6 +2034,36 @@ interface ContentProvider {
|
|
|
2034
2034
|
*/
|
|
2035
2035
|
invalidate?(paths: readonly string[]): void;
|
|
2036
2036
|
}
|
|
2037
|
+
/**
|
|
2038
|
+
* Options for build-time Mermaid diagram rendering (the `mermaid` key of
|
|
2039
|
+
* {@link FileSystemContentOptions}). Rendering is delegated to the OPTIONAL
|
|
2040
|
+
* peer dependency `mermaid-isomorphic`, so the config stays loosely typed —
|
|
2041
|
+
* its types are never imported here.
|
|
2042
|
+
*
|
|
2043
|
+
* @example
|
|
2044
|
+
* ```ts
|
|
2045
|
+
* fileSystemContent({ contentDir: "./content", trustedContent: true, mermaid: { mermaidConfig: { theme: "dark" } } });
|
|
2046
|
+
* ```
|
|
2047
|
+
*/
|
|
2048
|
+
type MermaidDiagramOptions = {
|
|
2049
|
+
/**
|
|
2050
|
+
* Mermaid configuration passed straight through to mermaid-isomorphic's
|
|
2051
|
+
* render call (e.g. `{ theme: "dark" }`). Loosely typed as a plain record
|
|
2052
|
+
* because the dependency is optional.
|
|
2053
|
+
*/
|
|
2054
|
+
mermaidConfig?: Record<string, unknown>;
|
|
2055
|
+
/**
|
|
2056
|
+
* TEST-ONLY seam: replaces the real mermaid-isomorphic batch renderer so
|
|
2057
|
+
* unit tests stay deterministic with no headless browser. Receives every
|
|
2058
|
+
* mermaid fence source of one document in order and must resolve to exactly
|
|
2059
|
+
* one SVG string per source. Never set this in an app.
|
|
2060
|
+
*
|
|
2061
|
+
* @param sources - Every mermaid fence source of one document, in order.
|
|
2062
|
+
* @param mermaidConfig - The configured mermaid pass-through config, if any.
|
|
2063
|
+
* @returns One SVG string per source, in order.
|
|
2064
|
+
*/
|
|
2065
|
+
renderDiagrams?: (sources: readonly string[], mermaidConfig?: Record<string, unknown>) => Promise<readonly string[]>;
|
|
2066
|
+
};
|
|
2037
2067
|
/**
|
|
2038
2068
|
* Options for the node filesystem provider {@link ContentProvider} `fileSystemContent`.
|
|
2039
2069
|
* These are the markdown-pipeline + source concerns that used to live on the content
|
|
@@ -2059,6 +2089,15 @@ type FileSystemContentOptions = {
|
|
|
2059
2089
|
*/
|
|
2060
2090
|
shikiTheme?: BundledTheme | ThemeRegistrationAny; /** Author applied to articles whose frontmatter omits author. Defaults to undefined. */
|
|
2061
2091
|
defaultAuthor?: string;
|
|
2092
|
+
/**
|
|
2093
|
+
* Build-time Mermaid diagrams: render fenced `mermaid` code blocks to static
|
|
2094
|
+
* inline SVG during the build (zero client-side JS). `true` enables with
|
|
2095
|
+
* defaults; an object passes {@link MermaidDiagramOptions}. Requires
|
|
2096
|
+
* `trustedContent: true` (the raw inline SVG would be stripped by the
|
|
2097
|
+
* sanitize pass) and the OPTIONAL peer dependency `mermaid-isomorphic`
|
|
2098
|
+
* (plus playwright with an installed browser). Defaults to disabled.
|
|
2099
|
+
*/
|
|
2100
|
+
mermaid?: boolean | MermaidDiagramOptions;
|
|
2062
2101
|
};
|
|
2063
2102
|
/**
|
|
2064
2103
|
* Internal mutable state of the filesystem provider: the lazy unified processor and
|
package/dist/index.cjs
CHANGED
|
@@ -1580,6 +1580,23 @@ function createContentState(_ctx) {
|
|
|
1580
1580
|
function validateContentConfig(config) {
|
|
1581
1581
|
if (!Array.isArray(config.providers) || config.providers.length === 0) throw new Error("[web] content: no provider composed.\n Add fileSystemContent(...) to pluginConfigs.content.providers.");
|
|
1582
1582
|
}
|
|
1583
|
+
/**
|
|
1584
|
+
* Validates the `fileSystemContent` provider options (fail-fast at provider
|
|
1585
|
+
* construction). Throws when `mermaid` is enabled without `trustedContent: true`:
|
|
1586
|
+
* mermaid output is raw inline SVG, which the sanitize pass (the untrusted-content
|
|
1587
|
+
* XSS boundary) would strip — so the combination can never work. Errors use the
|
|
1588
|
+
* `[web]` prefix.
|
|
1589
|
+
*
|
|
1590
|
+
* @param options - The provider options to validate.
|
|
1591
|
+
* @throws {Error} If `mermaid` is enabled while `trustedContent` is not `true`.
|
|
1592
|
+
* @example
|
|
1593
|
+
* ```ts
|
|
1594
|
+
* validateFileSystemContentOptions({ contentDir: "./content", trustedContent: true, mermaid: true });
|
|
1595
|
+
* ```
|
|
1596
|
+
*/
|
|
1597
|
+
function validateFileSystemContentOptions(options) {
|
|
1598
|
+
if (Boolean(options.mermaid) && options.trustedContent !== true) throw new Error("[web] content: `mermaid` requires `trustedContent: true`.\n Mermaid diagrams render to raw inline SVG, which the sanitize pass would strip.\n Set trustedContent: true ONLY for fully author-controlled Markdown.");
|
|
1599
|
+
}
|
|
1583
1600
|
//#endregion
|
|
1584
1601
|
//#region src/plugins/content/index.ts
|
|
1585
1602
|
/**
|
|
@@ -3550,6 +3567,23 @@ const FINGERPRINT_NAMING = {
|
|
|
3550
3567
|
asset: "[name]-[hash].[ext]"
|
|
3551
3568
|
};
|
|
3552
3569
|
/**
|
|
3570
|
+
* Font url() references the CSS pass leaves EXTERNAL instead of bundling.
|
|
3571
|
+
* Bun's CSS bundler cannot emit url() assets as files — every resolvable
|
|
3572
|
+
* font reference is inlined as a base64 data URI, ballooning the stylesheet
|
|
3573
|
+
* (a site vendoring a font family ships every weight/subset render-blocking
|
|
3574
|
+
* on every page). Marking font extensions external passes the URL through
|
|
3575
|
+
* verbatim, so apps reference fonts root-relative (e.g. `/fonts/x.woff2`)
|
|
3576
|
+
* and serve the files statically via `publicDir`. CSS-only: a font import
|
|
3577
|
+
* left external in JS would be an unresolvable module at runtime.
|
|
3578
|
+
*/
|
|
3579
|
+
const CSS_EXTERNAL_FONT_GLOBS = [
|
|
3580
|
+
"*.woff2",
|
|
3581
|
+
"*.woff",
|
|
3582
|
+
"*.ttf",
|
|
3583
|
+
"*.otf",
|
|
3584
|
+
"*.eot"
|
|
3585
|
+
];
|
|
3586
|
+
/**
|
|
3553
3587
|
* The default bundler runner — adapts the built-in `Bun.build`.
|
|
3554
3588
|
*
|
|
3555
3589
|
* @param options - Entry/outdir/minify/splitting/target/naming settings forwarded to `Bun.build`.
|
|
@@ -3562,10 +3596,11 @@ const FINGERPRINT_NAMING = {
|
|
|
3562
3596
|
* @param options.naming.entry - Naming template for entry-point outputs.
|
|
3563
3597
|
* @param options.naming.chunk - Naming template for lazy split chunks.
|
|
3564
3598
|
* @param options.naming.asset - Naming template for additional emitted assets.
|
|
3599
|
+
* @param options.external - Import/url() globs left unresolved in the output.
|
|
3565
3600
|
* @returns The structural build result.
|
|
3566
3601
|
* @example
|
|
3567
3602
|
* ```ts
|
|
3568
|
-
* await defaultRunner({ entrypoints: ["a.css"], outdir: "dist", minify: true, splitting: true, target: "browser", naming: FINGERPRINT_NAMING });
|
|
3603
|
+
* await defaultRunner({ entrypoints: ["a.css"], outdir: "dist", minify: true, splitting: true, target: "browser", naming: FINGERPRINT_NAMING, external: [] });
|
|
3569
3604
|
* ```
|
|
3570
3605
|
*/
|
|
3571
3606
|
async function defaultRunner(options) {
|
|
@@ -3658,7 +3693,8 @@ async function runOne(ctx, runner, kind, entrypoints, outDir, outdir, minify) {
|
|
|
3658
3693
|
minify,
|
|
3659
3694
|
splitting: true,
|
|
3660
3695
|
target: "browser",
|
|
3661
|
-
naming: FINGERPRINT_NAMING
|
|
3696
|
+
naming: FINGERPRINT_NAMING,
|
|
3697
|
+
external: kind === "css" ? [...CSS_EXTERNAL_FONT_GLOBS] : []
|
|
3662
3698
|
});
|
|
3663
3699
|
if (!result.success) throw new Error(`[web] build.bundle ${kind} build failed`);
|
|
3664
3700
|
const hashed = {};
|
|
@@ -11550,6 +11586,154 @@ function parseFrontmatter(raw, config) {
|
|
|
11550
11586
|
};
|
|
11551
11587
|
}
|
|
11552
11588
|
//#endregion
|
|
11589
|
+
//#region src/plugins/content/pipeline/mermaid.ts
|
|
11590
|
+
/** CSS class on the `<figure>` wrapper around each rendered diagram. */
|
|
11591
|
+
const MERMAID_FIGURE_CLASS = "mermaid-diagram";
|
|
11592
|
+
/**
|
|
11593
|
+
* Cached renderer promise — `createMermaidRenderer()` is called ONCE per process
|
|
11594
|
+
* and shared by every document (the underlying headless browser is expensive).
|
|
11595
|
+
*/
|
|
11596
|
+
let cachedRendererPromise;
|
|
11597
|
+
/**
|
|
11598
|
+
* Lazily import `mermaid-isomorphic` and create its batched renderer. The import
|
|
11599
|
+
* happens HERE (never at module load) so the optional dependency is only touched
|
|
11600
|
+
* when a document actually contains a mermaid fence. A failed import is wrapped
|
|
11601
|
+
* in an actionable error naming the missing package.
|
|
11602
|
+
*
|
|
11603
|
+
* @param importModule - Import thunk for the package; injectable so tests can
|
|
11604
|
+
* exercise both outcomes without the real dependency. Defaults to the real
|
|
11605
|
+
* dynamic `import("mermaid-isomorphic")`.
|
|
11606
|
+
* @returns The batched mermaid renderer.
|
|
11607
|
+
* @throws {Error} When the optional dependency cannot be loaded.
|
|
11608
|
+
* @example
|
|
11609
|
+
* ```ts
|
|
11610
|
+
* const renderer = await loadMermaidRenderer();
|
|
11611
|
+
* const results = await renderer(["graph TD; A-->B"]);
|
|
11612
|
+
* ```
|
|
11613
|
+
*/
|
|
11614
|
+
async function loadMermaidRenderer(importModule = () => import("mermaid-isomorphic")) {
|
|
11615
|
+
let moduleExports;
|
|
11616
|
+
try {
|
|
11617
|
+
moduleExports = await importModule();
|
|
11618
|
+
} catch (error) {
|
|
11619
|
+
throw new Error("[web] content: `mermaid` is enabled but the optional dependency \"mermaid-isomorphic\" could not be loaded.\n Install it (plus playwright and a browser):\n bun add -d mermaid-isomorphic playwright && bunx playwright install chromium", { cause: error });
|
|
11620
|
+
}
|
|
11621
|
+
return moduleExports.createMermaidRenderer();
|
|
11622
|
+
}
|
|
11623
|
+
/**
|
|
11624
|
+
* Unwrap mermaid-isomorphic's settled results into plain SVG strings, failing
|
|
11625
|
+
* the build on the first rejected diagram. The error quotes the diagram's first
|
|
11626
|
+
* line so the author can locate the broken fence.
|
|
11627
|
+
*
|
|
11628
|
+
* @param sources - The diagram sources, in the order they were rendered.
|
|
11629
|
+
* @param results - The settled render results (one per source).
|
|
11630
|
+
* @returns One SVG string per source, in order.
|
|
11631
|
+
* @throws {Error} When any diagram failed to render.
|
|
11632
|
+
* @example
|
|
11633
|
+
* ```ts
|
|
11634
|
+
* const svgs = unwrapMermaidResults(["graph TD; A-->B"], results);
|
|
11635
|
+
* ```
|
|
11636
|
+
*/
|
|
11637
|
+
function unwrapMermaidResults(sources, results) {
|
|
11638
|
+
return results.map((result, index) => {
|
|
11639
|
+
if (result.status === "rejected") {
|
|
11640
|
+
const firstLine = (sources[index] ?? "").split("\n", 1)[0] ?? "";
|
|
11641
|
+
throw new Error(`[web] content: mermaid diagram failed to render (diagram starts with "${firstLine}"): ${String(result.reason)}`);
|
|
11642
|
+
}
|
|
11643
|
+
return result.value.svg;
|
|
11644
|
+
});
|
|
11645
|
+
}
|
|
11646
|
+
/**
|
|
11647
|
+
* The REAL render path: lazily load mermaid-isomorphic (cached once per
|
|
11648
|
+
* process), render every fence of the document in ONE batched call, and unwrap
|
|
11649
|
+
* the results. Replaced in unit tests by the `renderDiagrams` seam.
|
|
11650
|
+
*
|
|
11651
|
+
* @param sources - Every mermaid fence source of one document, in order.
|
|
11652
|
+
* @param mermaidConfig - Optional mermaid configuration forwarded to the render call.
|
|
11653
|
+
* @returns One SVG string per source, in order.
|
|
11654
|
+
* @example
|
|
11655
|
+
* ```ts
|
|
11656
|
+
* const svgs = await renderWithMermaidIsomorphic(["graph TD; A-->B"]);
|
|
11657
|
+
* ```
|
|
11658
|
+
*/
|
|
11659
|
+
async function renderWithMermaidIsomorphic(sources, mermaidConfig) {
|
|
11660
|
+
cachedRendererPromise ??= loadMermaidRenderer();
|
|
11661
|
+
return unwrapMermaidResults(sources, await (await cachedRendererPromise)([...sources], mermaidConfig ? { mermaidConfig } : void 0));
|
|
11662
|
+
}
|
|
11663
|
+
/**
|
|
11664
|
+
* Collect every fenced `mermaid` code block in the tree (with the parent/index
|
|
11665
|
+
* needed to replace it later), in document order.
|
|
11666
|
+
*
|
|
11667
|
+
* @param tree - The mdast tree to scan.
|
|
11668
|
+
* @returns The fence sites found.
|
|
11669
|
+
* @example
|
|
11670
|
+
* ```ts
|
|
11671
|
+
* const fences = collectMermaidFences(tree);
|
|
11672
|
+
* ```
|
|
11673
|
+
*/
|
|
11674
|
+
function collectMermaidFences(tree) {
|
|
11675
|
+
const fences = [];
|
|
11676
|
+
(0, unist_util_visit.visit)(tree, "code", (node, index, parent) => {
|
|
11677
|
+
if (node.lang !== "mermaid") return;
|
|
11678
|
+
if (parent === void 0 || index === void 0) return;
|
|
11679
|
+
fences.push({
|
|
11680
|
+
node,
|
|
11681
|
+
parent,
|
|
11682
|
+
index
|
|
11683
|
+
});
|
|
11684
|
+
});
|
|
11685
|
+
return fences;
|
|
11686
|
+
}
|
|
11687
|
+
/**
|
|
11688
|
+
* Normalize the provider's `mermaid` config value (`boolean | options`) to a
|
|
11689
|
+
* plain {@link MermaidDiagramOptions} object for the transform factory.
|
|
11690
|
+
*
|
|
11691
|
+
* @param mermaid - The raw `FileSystemContentOptions.mermaid` value (truthy).
|
|
11692
|
+
* @returns The options object (`{}` for the bare `true` form).
|
|
11693
|
+
* @example
|
|
11694
|
+
* ```ts
|
|
11695
|
+
* normalizeMermaidOptions(true); // {}
|
|
11696
|
+
* normalizeMermaidOptions({ mermaidConfig: { theme: "dark" } });
|
|
11697
|
+
* ```
|
|
11698
|
+
*/
|
|
11699
|
+
function normalizeMermaidOptions(mermaid) {
|
|
11700
|
+
return typeof mermaid === "boolean" ? {} : mermaid;
|
|
11701
|
+
}
|
|
11702
|
+
/**
|
|
11703
|
+
* Remark transform factory: replaces every fenced `mermaid` code block with a
|
|
11704
|
+
* `<figure class="mermaid-diagram">` raw-HTML node carrying the diagram as
|
|
11705
|
+
* static inline SVG, rendered at build time (zero client-side JS). Runs at the
|
|
11706
|
+
* mdast stage, BEFORE remark-rehype; the bridge's `allowDangerousHtml` plus the
|
|
11707
|
+
* framework's `rehype-raw` default carry the SVG into the output. Documents
|
|
11708
|
+
* without a mermaid fence return immediately — `mermaid-isomorphic` is never
|
|
11709
|
+
* imported on that path. A diagram that fails to render fails the build with
|
|
11710
|
+
* its first line quoted.
|
|
11711
|
+
*
|
|
11712
|
+
* @param options - Mermaid options: `mermaidConfig` pass-through + the
|
|
11713
|
+
* test-only `renderDiagrams` seam.
|
|
11714
|
+
* @returns An async mdast tree transformer.
|
|
11715
|
+
* @example
|
|
11716
|
+
* ```ts
|
|
11717
|
+
* unified().use(remarkMermaidDiagrams, { mermaidConfig: { theme: "dark" } });
|
|
11718
|
+
* ```
|
|
11719
|
+
*/
|
|
11720
|
+
function remarkMermaidDiagrams(options = {}) {
|
|
11721
|
+
return async (tree) => {
|
|
11722
|
+
const fences = collectMermaidFences(tree);
|
|
11723
|
+
if (fences.length === 0) return;
|
|
11724
|
+
const sources = fences.map((fence) => fence.node.value);
|
|
11725
|
+
const svgs = await (options.renderDiagrams ?? renderWithMermaidIsomorphic)(sources, options.mermaidConfig);
|
|
11726
|
+
if (svgs.length !== sources.length) throw new Error(`[web] content: mermaid renderer returned ${svgs.length} result(s) for ${sources.length} diagram(s).`);
|
|
11727
|
+
for (const [position, fence] of fences.entries()) {
|
|
11728
|
+
const html = {
|
|
11729
|
+
type: "html",
|
|
11730
|
+
value: `<figure class="${MERMAID_FIGURE_CLASS}">${svgs[position] ?? ""}</figure>`
|
|
11731
|
+
};
|
|
11732
|
+
fence.parent.children[fence.index] = html;
|
|
11733
|
+
}
|
|
11734
|
+
};
|
|
11735
|
+
}
|
|
11736
|
+
//#endregion
|
|
11553
11737
|
//#region src/plugins/content/pipeline/plugins.ts
|
|
11554
11738
|
/**
|
|
11555
11739
|
* Type guard for remark-directive nodes (container/leaf/text).
|
|
@@ -11685,25 +11869,31 @@ function sectionDividerPlugin() {
|
|
|
11685
11869
|
}
|
|
11686
11870
|
/**
|
|
11687
11871
|
* The hardcoded framework default remark (Markdown-AST) plugins, in order:
|
|
11688
|
-
* parse, frontmatter, gfm, directive, pull-quote,
|
|
11689
|
-
* (`remark-rehype` with `allowDangerousHtml`).
|
|
11690
|
-
*
|
|
11691
|
-
*
|
|
11872
|
+
* parse, frontmatter, gfm, directive, pull-quote, the OPT-IN mermaid transform,
|
|
11873
|
+
* then the mdast→hast bridge (`remark-rehype` with `allowDangerousHtml`).
|
|
11874
|
+
* Pull-quote and mermaid run on the mdast before the bridge — pull-quote so the
|
|
11875
|
+
* directive carries its `hName`/`hProperties`, mermaid so the fence is replaced
|
|
11876
|
+
* with raw SVG HTML before Shiki could ever claim the code block.
|
|
11877
|
+
*
|
|
11878
|
+
* @param config - Optional provider configuration; only `mermaid` is read here
|
|
11879
|
+
* (truthy enables the mermaid transform at its fixed mdast position).
|
|
11692
11880
|
* @returns The ordered default remark pluggables.
|
|
11693
11881
|
* @example
|
|
11694
11882
|
* ```ts
|
|
11695
11883
|
* const remark = defaultRemarkPlugins();
|
|
11696
11884
|
* ```
|
|
11697
11885
|
*/
|
|
11698
|
-
function defaultRemarkPlugins() {
|
|
11699
|
-
|
|
11886
|
+
function defaultRemarkPlugins(config) {
|
|
11887
|
+
const plugins = [
|
|
11700
11888
|
remark_parse.default,
|
|
11701
11889
|
remark_frontmatter.default,
|
|
11702
11890
|
remark_gfm.default,
|
|
11703
11891
|
remark_directive.default,
|
|
11704
|
-
pullQuotePlugin
|
|
11705
|
-
[remark_rehype.default, { allowDangerousHtml: true }]
|
|
11892
|
+
pullQuotePlugin
|
|
11706
11893
|
];
|
|
11894
|
+
if (config?.mermaid) plugins.push([remarkMermaidDiagrams, normalizeMermaidOptions(config.mermaid)]);
|
|
11895
|
+
plugins.push([remark_rehype.default, { allowDangerousHtml: true }]);
|
|
11896
|
+
return plugins;
|
|
11707
11897
|
}
|
|
11708
11898
|
/**
|
|
11709
11899
|
* The hardcoded framework default rehype (HTML-AST) plugins, in order:
|
|
@@ -11844,14 +12034,15 @@ function applyPluggable(processor, plugin) {
|
|
|
11844
12034
|
* replacing, the defaults).
|
|
11845
12035
|
*
|
|
11846
12036
|
* @param processor - The unified processor under construction (mutated in place).
|
|
11847
|
-
* @param config - Resolved plugin configuration (provides `extraRemarkPlugins
|
|
12037
|
+
* @param config - Resolved plugin configuration (provides `extraRemarkPlugins`,
|
|
12038
|
+
* and `mermaid` which the defaults read to enable the mdast mermaid transform).
|
|
11848
12039
|
* @example
|
|
11849
12040
|
* ```ts
|
|
11850
12041
|
* applyRemarkPlugins(processor, config);
|
|
11851
12042
|
* ```
|
|
11852
12043
|
*/
|
|
11853
12044
|
function applyRemarkPlugins(processor, config) {
|
|
11854
|
-
for (const plugin of defaultRemarkPlugins()) applyPluggable(processor, plugin);
|
|
12045
|
+
for (const plugin of defaultRemarkPlugins(config)) applyPluggable(processor, plugin);
|
|
11855
12046
|
for (const plugin of config.extraRemarkPlugins ?? []) applyPluggable(processor, plugin);
|
|
11856
12047
|
}
|
|
11857
12048
|
/**
|
|
@@ -12003,6 +12194,7 @@ async function discoverSlugs(dir) {
|
|
|
12003
12194
|
* ```
|
|
12004
12195
|
*/
|
|
12005
12196
|
function fileSystemContent(options) {
|
|
12197
|
+
validateFileSystemContentOptions(options);
|
|
12006
12198
|
const state = {
|
|
12007
12199
|
processor: null,
|
|
12008
12200
|
slugs: null,
|
package/dist/index.d.cts
CHANGED
|
@@ -2671,7 +2671,7 @@ type Api$1 = {
|
|
|
2671
2671
|
*/
|
|
2672
2672
|
declare const cliPlugin: import("@moku-labs/core").PluginInstance<"cli", Config$1, State$1, Api$1, {}> & Record<never, never>;
|
|
2673
2673
|
declare namespace types_d_exports$2 {
|
|
2674
|
-
export { Api, Article, ArticleCard, ComputedFields, Config, ContentApiContext, ContentEvents, ContentProvider, ContentProviderState, FileSystemContentOptions, Frontmatter, LoadAllOptions, State };
|
|
2674
|
+
export { Api, Article, ArticleCard, ComputedFields, Config, ContentApiContext, ContentEvents, ContentProvider, ContentProviderState, FileSystemContentOptions, Frontmatter, LoadAllOptions, MermaidDiagramOptions, State };
|
|
2675
2675
|
}
|
|
2676
2676
|
/**
|
|
2677
2677
|
* YAML frontmatter parsed from each article file.
|
|
@@ -2785,6 +2785,36 @@ interface ContentProvider {
|
|
|
2785
2785
|
*/
|
|
2786
2786
|
invalidate?(paths: readonly string[]): void;
|
|
2787
2787
|
}
|
|
2788
|
+
/**
|
|
2789
|
+
* Options for build-time Mermaid diagram rendering (the `mermaid` key of
|
|
2790
|
+
* {@link FileSystemContentOptions}). Rendering is delegated to the OPTIONAL
|
|
2791
|
+
* peer dependency `mermaid-isomorphic`, so the config stays loosely typed —
|
|
2792
|
+
* its types are never imported here.
|
|
2793
|
+
*
|
|
2794
|
+
* @example
|
|
2795
|
+
* ```ts
|
|
2796
|
+
* fileSystemContent({ contentDir: "./content", trustedContent: true, mermaid: { mermaidConfig: { theme: "dark" } } });
|
|
2797
|
+
* ```
|
|
2798
|
+
*/
|
|
2799
|
+
type MermaidDiagramOptions = {
|
|
2800
|
+
/**
|
|
2801
|
+
* Mermaid configuration passed straight through to mermaid-isomorphic's
|
|
2802
|
+
* render call (e.g. `{ theme: "dark" }`). Loosely typed as a plain record
|
|
2803
|
+
* because the dependency is optional.
|
|
2804
|
+
*/
|
|
2805
|
+
mermaidConfig?: Record<string, unknown>;
|
|
2806
|
+
/**
|
|
2807
|
+
* TEST-ONLY seam: replaces the real mermaid-isomorphic batch renderer so
|
|
2808
|
+
* unit tests stay deterministic with no headless browser. Receives every
|
|
2809
|
+
* mermaid fence source of one document in order and must resolve to exactly
|
|
2810
|
+
* one SVG string per source. Never set this in an app.
|
|
2811
|
+
*
|
|
2812
|
+
* @param sources - Every mermaid fence source of one document, in order.
|
|
2813
|
+
* @param mermaidConfig - The configured mermaid pass-through config, if any.
|
|
2814
|
+
* @returns One SVG string per source, in order.
|
|
2815
|
+
*/
|
|
2816
|
+
renderDiagrams?: (sources: readonly string[], mermaidConfig?: Record<string, unknown>) => Promise<readonly string[]>;
|
|
2817
|
+
};
|
|
2788
2818
|
/**
|
|
2789
2819
|
* Options for the node filesystem provider {@link ContentProvider} `fileSystemContent`.
|
|
2790
2820
|
* These are the markdown-pipeline + source concerns that used to live on the content
|
|
@@ -2810,6 +2840,15 @@ type FileSystemContentOptions = {
|
|
|
2810
2840
|
*/
|
|
2811
2841
|
shikiTheme?: BundledTheme | ThemeRegistrationAny; /** Author applied to articles whose frontmatter omits author. Defaults to undefined. */
|
|
2812
2842
|
defaultAuthor?: string;
|
|
2843
|
+
/**
|
|
2844
|
+
* Build-time Mermaid diagrams: render fenced `mermaid` code blocks to static
|
|
2845
|
+
* inline SVG during the build (zero client-side JS). `true` enables with
|
|
2846
|
+
* defaults; an object passes {@link MermaidDiagramOptions}. Requires
|
|
2847
|
+
* `trustedContent: true` (the raw inline SVG would be stripped by the
|
|
2848
|
+
* sanitize pass) and the OPTIONAL peer dependency `mermaid-isomorphic`
|
|
2849
|
+
* (plus playwright with an installed browser). Defaults to disabled.
|
|
2850
|
+
*/
|
|
2851
|
+
mermaid?: boolean | MermaidDiagramOptions;
|
|
2813
2852
|
};
|
|
2814
2853
|
/**
|
|
2815
2854
|
* Internal mutable state of the filesystem provider: the lazy unified processor and
|
package/dist/index.d.mts
CHANGED
|
@@ -2671,7 +2671,7 @@ type Api$1 = {
|
|
|
2671
2671
|
*/
|
|
2672
2672
|
declare const cliPlugin: import("@moku-labs/core").PluginInstance<"cli", Config$1, State$1, Api$1, {}> & Record<never, never>;
|
|
2673
2673
|
declare namespace types_d_exports$2 {
|
|
2674
|
-
export { Api, Article, ArticleCard, ComputedFields, Config, ContentApiContext, ContentEvents, ContentProvider, ContentProviderState, FileSystemContentOptions, Frontmatter, LoadAllOptions, State };
|
|
2674
|
+
export { Api, Article, ArticleCard, ComputedFields, Config, ContentApiContext, ContentEvents, ContentProvider, ContentProviderState, FileSystemContentOptions, Frontmatter, LoadAllOptions, MermaidDiagramOptions, State };
|
|
2675
2675
|
}
|
|
2676
2676
|
/**
|
|
2677
2677
|
* YAML frontmatter parsed from each article file.
|
|
@@ -2785,6 +2785,36 @@ interface ContentProvider {
|
|
|
2785
2785
|
*/
|
|
2786
2786
|
invalidate?(paths: readonly string[]): void;
|
|
2787
2787
|
}
|
|
2788
|
+
/**
|
|
2789
|
+
* Options for build-time Mermaid diagram rendering (the `mermaid` key of
|
|
2790
|
+
* {@link FileSystemContentOptions}). Rendering is delegated to the OPTIONAL
|
|
2791
|
+
* peer dependency `mermaid-isomorphic`, so the config stays loosely typed —
|
|
2792
|
+
* its types are never imported here.
|
|
2793
|
+
*
|
|
2794
|
+
* @example
|
|
2795
|
+
* ```ts
|
|
2796
|
+
* fileSystemContent({ contentDir: "./content", trustedContent: true, mermaid: { mermaidConfig: { theme: "dark" } } });
|
|
2797
|
+
* ```
|
|
2798
|
+
*/
|
|
2799
|
+
type MermaidDiagramOptions = {
|
|
2800
|
+
/**
|
|
2801
|
+
* Mermaid configuration passed straight through to mermaid-isomorphic's
|
|
2802
|
+
* render call (e.g. `{ theme: "dark" }`). Loosely typed as a plain record
|
|
2803
|
+
* because the dependency is optional.
|
|
2804
|
+
*/
|
|
2805
|
+
mermaidConfig?: Record<string, unknown>;
|
|
2806
|
+
/**
|
|
2807
|
+
* TEST-ONLY seam: replaces the real mermaid-isomorphic batch renderer so
|
|
2808
|
+
* unit tests stay deterministic with no headless browser. Receives every
|
|
2809
|
+
* mermaid fence source of one document in order and must resolve to exactly
|
|
2810
|
+
* one SVG string per source. Never set this in an app.
|
|
2811
|
+
*
|
|
2812
|
+
* @param sources - Every mermaid fence source of one document, in order.
|
|
2813
|
+
* @param mermaidConfig - The configured mermaid pass-through config, if any.
|
|
2814
|
+
* @returns One SVG string per source, in order.
|
|
2815
|
+
*/
|
|
2816
|
+
renderDiagrams?: (sources: readonly string[], mermaidConfig?: Record<string, unknown>) => Promise<readonly string[]>;
|
|
2817
|
+
};
|
|
2788
2818
|
/**
|
|
2789
2819
|
* Options for the node filesystem provider {@link ContentProvider} `fileSystemContent`.
|
|
2790
2820
|
* These are the markdown-pipeline + source concerns that used to live on the content
|
|
@@ -2810,6 +2840,15 @@ type FileSystemContentOptions = {
|
|
|
2810
2840
|
*/
|
|
2811
2841
|
shikiTheme?: BundledTheme | ThemeRegistrationAny; /** Author applied to articles whose frontmatter omits author. Defaults to undefined. */
|
|
2812
2842
|
defaultAuthor?: string;
|
|
2843
|
+
/**
|
|
2844
|
+
* Build-time Mermaid diagrams: render fenced `mermaid` code blocks to static
|
|
2845
|
+
* inline SVG during the build (zero client-side JS). `true` enables with
|
|
2846
|
+
* defaults; an object passes {@link MermaidDiagramOptions}. Requires
|
|
2847
|
+
* `trustedContent: true` (the raw inline SVG would be stripped by the
|
|
2848
|
+
* sanitize pass) and the OPTIONAL peer dependency `mermaid-isomorphic`
|
|
2849
|
+
* (plus playwright with an installed browser). Defaults to disabled.
|
|
2850
|
+
*/
|
|
2851
|
+
mermaid?: boolean | MermaidDiagramOptions;
|
|
2813
2852
|
};
|
|
2814
2853
|
/**
|
|
2815
2854
|
* Internal mutable state of the filesystem provider: the lazy unified processor and
|
package/dist/index.mjs
CHANGED
|
@@ -1567,6 +1567,23 @@ function createContentState(_ctx) {
|
|
|
1567
1567
|
function validateContentConfig(config) {
|
|
1568
1568
|
if (!Array.isArray(config.providers) || config.providers.length === 0) throw new Error("[web] content: no provider composed.\n Add fileSystemContent(...) to pluginConfigs.content.providers.");
|
|
1569
1569
|
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Validates the `fileSystemContent` provider options (fail-fast at provider
|
|
1572
|
+
* construction). Throws when `mermaid` is enabled without `trustedContent: true`:
|
|
1573
|
+
* mermaid output is raw inline SVG, which the sanitize pass (the untrusted-content
|
|
1574
|
+
* XSS boundary) would strip — so the combination can never work. Errors use the
|
|
1575
|
+
* `[web]` prefix.
|
|
1576
|
+
*
|
|
1577
|
+
* @param options - The provider options to validate.
|
|
1578
|
+
* @throws {Error} If `mermaid` is enabled while `trustedContent` is not `true`.
|
|
1579
|
+
* @example
|
|
1580
|
+
* ```ts
|
|
1581
|
+
* validateFileSystemContentOptions({ contentDir: "./content", trustedContent: true, mermaid: true });
|
|
1582
|
+
* ```
|
|
1583
|
+
*/
|
|
1584
|
+
function validateFileSystemContentOptions(options) {
|
|
1585
|
+
if (Boolean(options.mermaid) && options.trustedContent !== true) throw new Error("[web] content: `mermaid` requires `trustedContent: true`.\n Mermaid diagrams render to raw inline SVG, which the sanitize pass would strip.\n Set trustedContent: true ONLY for fully author-controlled Markdown.");
|
|
1586
|
+
}
|
|
1570
1587
|
//#endregion
|
|
1571
1588
|
//#region src/plugins/content/index.ts
|
|
1572
1589
|
/**
|
|
@@ -3537,6 +3554,23 @@ const FINGERPRINT_NAMING = {
|
|
|
3537
3554
|
asset: "[name]-[hash].[ext]"
|
|
3538
3555
|
};
|
|
3539
3556
|
/**
|
|
3557
|
+
* Font url() references the CSS pass leaves EXTERNAL instead of bundling.
|
|
3558
|
+
* Bun's CSS bundler cannot emit url() assets as files — every resolvable
|
|
3559
|
+
* font reference is inlined as a base64 data URI, ballooning the stylesheet
|
|
3560
|
+
* (a site vendoring a font family ships every weight/subset render-blocking
|
|
3561
|
+
* on every page). Marking font extensions external passes the URL through
|
|
3562
|
+
* verbatim, so apps reference fonts root-relative (e.g. `/fonts/x.woff2`)
|
|
3563
|
+
* and serve the files statically via `publicDir`. CSS-only: a font import
|
|
3564
|
+
* left external in JS would be an unresolvable module at runtime.
|
|
3565
|
+
*/
|
|
3566
|
+
const CSS_EXTERNAL_FONT_GLOBS = [
|
|
3567
|
+
"*.woff2",
|
|
3568
|
+
"*.woff",
|
|
3569
|
+
"*.ttf",
|
|
3570
|
+
"*.otf",
|
|
3571
|
+
"*.eot"
|
|
3572
|
+
];
|
|
3573
|
+
/**
|
|
3540
3574
|
* The default bundler runner — adapts the built-in `Bun.build`.
|
|
3541
3575
|
*
|
|
3542
3576
|
* @param options - Entry/outdir/minify/splitting/target/naming settings forwarded to `Bun.build`.
|
|
@@ -3549,10 +3583,11 @@ const FINGERPRINT_NAMING = {
|
|
|
3549
3583
|
* @param options.naming.entry - Naming template for entry-point outputs.
|
|
3550
3584
|
* @param options.naming.chunk - Naming template for lazy split chunks.
|
|
3551
3585
|
* @param options.naming.asset - Naming template for additional emitted assets.
|
|
3586
|
+
* @param options.external - Import/url() globs left unresolved in the output.
|
|
3552
3587
|
* @returns The structural build result.
|
|
3553
3588
|
* @example
|
|
3554
3589
|
* ```ts
|
|
3555
|
-
* await defaultRunner({ entrypoints: ["a.css"], outdir: "dist", minify: true, splitting: true, target: "browser", naming: FINGERPRINT_NAMING });
|
|
3590
|
+
* await defaultRunner({ entrypoints: ["a.css"], outdir: "dist", minify: true, splitting: true, target: "browser", naming: FINGERPRINT_NAMING, external: [] });
|
|
3556
3591
|
* ```
|
|
3557
3592
|
*/
|
|
3558
3593
|
async function defaultRunner(options) {
|
|
@@ -3645,7 +3680,8 @@ async function runOne(ctx, runner, kind, entrypoints, outDir, outdir, minify) {
|
|
|
3645
3680
|
minify,
|
|
3646
3681
|
splitting: true,
|
|
3647
3682
|
target: "browser",
|
|
3648
|
-
naming: FINGERPRINT_NAMING
|
|
3683
|
+
naming: FINGERPRINT_NAMING,
|
|
3684
|
+
external: kind === "css" ? [...CSS_EXTERNAL_FONT_GLOBS] : []
|
|
3649
3685
|
});
|
|
3650
3686
|
if (!result.success) throw new Error(`[web] build.bundle ${kind} build failed`);
|
|
3651
3687
|
const hashed = {};
|
|
@@ -11537,6 +11573,154 @@ function parseFrontmatter(raw, config) {
|
|
|
11537
11573
|
};
|
|
11538
11574
|
}
|
|
11539
11575
|
//#endregion
|
|
11576
|
+
//#region src/plugins/content/pipeline/mermaid.ts
|
|
11577
|
+
/** CSS class on the `<figure>` wrapper around each rendered diagram. */
|
|
11578
|
+
const MERMAID_FIGURE_CLASS = "mermaid-diagram";
|
|
11579
|
+
/**
|
|
11580
|
+
* Cached renderer promise — `createMermaidRenderer()` is called ONCE per process
|
|
11581
|
+
* and shared by every document (the underlying headless browser is expensive).
|
|
11582
|
+
*/
|
|
11583
|
+
let cachedRendererPromise;
|
|
11584
|
+
/**
|
|
11585
|
+
* Lazily import `mermaid-isomorphic` and create its batched renderer. The import
|
|
11586
|
+
* happens HERE (never at module load) so the optional dependency is only touched
|
|
11587
|
+
* when a document actually contains a mermaid fence. A failed import is wrapped
|
|
11588
|
+
* in an actionable error naming the missing package.
|
|
11589
|
+
*
|
|
11590
|
+
* @param importModule - Import thunk for the package; injectable so tests can
|
|
11591
|
+
* exercise both outcomes without the real dependency. Defaults to the real
|
|
11592
|
+
* dynamic `import("mermaid-isomorphic")`.
|
|
11593
|
+
* @returns The batched mermaid renderer.
|
|
11594
|
+
* @throws {Error} When the optional dependency cannot be loaded.
|
|
11595
|
+
* @example
|
|
11596
|
+
* ```ts
|
|
11597
|
+
* const renderer = await loadMermaidRenderer();
|
|
11598
|
+
* const results = await renderer(["graph TD; A-->B"]);
|
|
11599
|
+
* ```
|
|
11600
|
+
*/
|
|
11601
|
+
async function loadMermaidRenderer(importModule = () => import("mermaid-isomorphic")) {
|
|
11602
|
+
let moduleExports;
|
|
11603
|
+
try {
|
|
11604
|
+
moduleExports = await importModule();
|
|
11605
|
+
} catch (error) {
|
|
11606
|
+
throw new Error("[web] content: `mermaid` is enabled but the optional dependency \"mermaid-isomorphic\" could not be loaded.\n Install it (plus playwright and a browser):\n bun add -d mermaid-isomorphic playwright && bunx playwright install chromium", { cause: error });
|
|
11607
|
+
}
|
|
11608
|
+
return moduleExports.createMermaidRenderer();
|
|
11609
|
+
}
|
|
11610
|
+
/**
|
|
11611
|
+
* Unwrap mermaid-isomorphic's settled results into plain SVG strings, failing
|
|
11612
|
+
* the build on the first rejected diagram. The error quotes the diagram's first
|
|
11613
|
+
* line so the author can locate the broken fence.
|
|
11614
|
+
*
|
|
11615
|
+
* @param sources - The diagram sources, in the order they were rendered.
|
|
11616
|
+
* @param results - The settled render results (one per source).
|
|
11617
|
+
* @returns One SVG string per source, in order.
|
|
11618
|
+
* @throws {Error} When any diagram failed to render.
|
|
11619
|
+
* @example
|
|
11620
|
+
* ```ts
|
|
11621
|
+
* const svgs = unwrapMermaidResults(["graph TD; A-->B"], results);
|
|
11622
|
+
* ```
|
|
11623
|
+
*/
|
|
11624
|
+
function unwrapMermaidResults(sources, results) {
|
|
11625
|
+
return results.map((result, index) => {
|
|
11626
|
+
if (result.status === "rejected") {
|
|
11627
|
+
const firstLine = (sources[index] ?? "").split("\n", 1)[0] ?? "";
|
|
11628
|
+
throw new Error(`[web] content: mermaid diagram failed to render (diagram starts with "${firstLine}"): ${String(result.reason)}`);
|
|
11629
|
+
}
|
|
11630
|
+
return result.value.svg;
|
|
11631
|
+
});
|
|
11632
|
+
}
|
|
11633
|
+
/**
|
|
11634
|
+
* The REAL render path: lazily load mermaid-isomorphic (cached once per
|
|
11635
|
+
* process), render every fence of the document in ONE batched call, and unwrap
|
|
11636
|
+
* the results. Replaced in unit tests by the `renderDiagrams` seam.
|
|
11637
|
+
*
|
|
11638
|
+
* @param sources - Every mermaid fence source of one document, in order.
|
|
11639
|
+
* @param mermaidConfig - Optional mermaid configuration forwarded to the render call.
|
|
11640
|
+
* @returns One SVG string per source, in order.
|
|
11641
|
+
* @example
|
|
11642
|
+
* ```ts
|
|
11643
|
+
* const svgs = await renderWithMermaidIsomorphic(["graph TD; A-->B"]);
|
|
11644
|
+
* ```
|
|
11645
|
+
*/
|
|
11646
|
+
async function renderWithMermaidIsomorphic(sources, mermaidConfig) {
|
|
11647
|
+
cachedRendererPromise ??= loadMermaidRenderer();
|
|
11648
|
+
return unwrapMermaidResults(sources, await (await cachedRendererPromise)([...sources], mermaidConfig ? { mermaidConfig } : void 0));
|
|
11649
|
+
}
|
|
11650
|
+
/**
|
|
11651
|
+
* Collect every fenced `mermaid` code block in the tree (with the parent/index
|
|
11652
|
+
* needed to replace it later), in document order.
|
|
11653
|
+
*
|
|
11654
|
+
* @param tree - The mdast tree to scan.
|
|
11655
|
+
* @returns The fence sites found.
|
|
11656
|
+
* @example
|
|
11657
|
+
* ```ts
|
|
11658
|
+
* const fences = collectMermaidFences(tree);
|
|
11659
|
+
* ```
|
|
11660
|
+
*/
|
|
11661
|
+
function collectMermaidFences(tree) {
|
|
11662
|
+
const fences = [];
|
|
11663
|
+
visit(tree, "code", (node, index, parent) => {
|
|
11664
|
+
if (node.lang !== "mermaid") return;
|
|
11665
|
+
if (parent === void 0 || index === void 0) return;
|
|
11666
|
+
fences.push({
|
|
11667
|
+
node,
|
|
11668
|
+
parent,
|
|
11669
|
+
index
|
|
11670
|
+
});
|
|
11671
|
+
});
|
|
11672
|
+
return fences;
|
|
11673
|
+
}
|
|
11674
|
+
/**
|
|
11675
|
+
* Normalize the provider's `mermaid` config value (`boolean | options`) to a
|
|
11676
|
+
* plain {@link MermaidDiagramOptions} object for the transform factory.
|
|
11677
|
+
*
|
|
11678
|
+
* @param mermaid - The raw `FileSystemContentOptions.mermaid` value (truthy).
|
|
11679
|
+
* @returns The options object (`{}` for the bare `true` form).
|
|
11680
|
+
* @example
|
|
11681
|
+
* ```ts
|
|
11682
|
+
* normalizeMermaidOptions(true); // {}
|
|
11683
|
+
* normalizeMermaidOptions({ mermaidConfig: { theme: "dark" } });
|
|
11684
|
+
* ```
|
|
11685
|
+
*/
|
|
11686
|
+
function normalizeMermaidOptions(mermaid) {
|
|
11687
|
+
return typeof mermaid === "boolean" ? {} : mermaid;
|
|
11688
|
+
}
|
|
11689
|
+
/**
|
|
11690
|
+
* Remark transform factory: replaces every fenced `mermaid` code block with a
|
|
11691
|
+
* `<figure class="mermaid-diagram">` raw-HTML node carrying the diagram as
|
|
11692
|
+
* static inline SVG, rendered at build time (zero client-side JS). Runs at the
|
|
11693
|
+
* mdast stage, BEFORE remark-rehype; the bridge's `allowDangerousHtml` plus the
|
|
11694
|
+
* framework's `rehype-raw` default carry the SVG into the output. Documents
|
|
11695
|
+
* without a mermaid fence return immediately — `mermaid-isomorphic` is never
|
|
11696
|
+
* imported on that path. A diagram that fails to render fails the build with
|
|
11697
|
+
* its first line quoted.
|
|
11698
|
+
*
|
|
11699
|
+
* @param options - Mermaid options: `mermaidConfig` pass-through + the
|
|
11700
|
+
* test-only `renderDiagrams` seam.
|
|
11701
|
+
* @returns An async mdast tree transformer.
|
|
11702
|
+
* @example
|
|
11703
|
+
* ```ts
|
|
11704
|
+
* unified().use(remarkMermaidDiagrams, { mermaidConfig: { theme: "dark" } });
|
|
11705
|
+
* ```
|
|
11706
|
+
*/
|
|
11707
|
+
function remarkMermaidDiagrams(options = {}) {
|
|
11708
|
+
return async (tree) => {
|
|
11709
|
+
const fences = collectMermaidFences(tree);
|
|
11710
|
+
if (fences.length === 0) return;
|
|
11711
|
+
const sources = fences.map((fence) => fence.node.value);
|
|
11712
|
+
const svgs = await (options.renderDiagrams ?? renderWithMermaidIsomorphic)(sources, options.mermaidConfig);
|
|
11713
|
+
if (svgs.length !== sources.length) throw new Error(`[web] content: mermaid renderer returned ${svgs.length} result(s) for ${sources.length} diagram(s).`);
|
|
11714
|
+
for (const [position, fence] of fences.entries()) {
|
|
11715
|
+
const html = {
|
|
11716
|
+
type: "html",
|
|
11717
|
+
value: `<figure class="${MERMAID_FIGURE_CLASS}">${svgs[position] ?? ""}</figure>`
|
|
11718
|
+
};
|
|
11719
|
+
fence.parent.children[fence.index] = html;
|
|
11720
|
+
}
|
|
11721
|
+
};
|
|
11722
|
+
}
|
|
11723
|
+
//#endregion
|
|
11540
11724
|
//#region src/plugins/content/pipeline/plugins.ts
|
|
11541
11725
|
/**
|
|
11542
11726
|
* Type guard for remark-directive nodes (container/leaf/text).
|
|
@@ -11672,25 +11856,31 @@ function sectionDividerPlugin() {
|
|
|
11672
11856
|
}
|
|
11673
11857
|
/**
|
|
11674
11858
|
* The hardcoded framework default remark (Markdown-AST) plugins, in order:
|
|
11675
|
-
* parse, frontmatter, gfm, directive, pull-quote,
|
|
11676
|
-
* (`remark-rehype` with `allowDangerousHtml`).
|
|
11677
|
-
*
|
|
11678
|
-
*
|
|
11859
|
+
* parse, frontmatter, gfm, directive, pull-quote, the OPT-IN mermaid transform,
|
|
11860
|
+
* then the mdast→hast bridge (`remark-rehype` with `allowDangerousHtml`).
|
|
11861
|
+
* Pull-quote and mermaid run on the mdast before the bridge — pull-quote so the
|
|
11862
|
+
* directive carries its `hName`/`hProperties`, mermaid so the fence is replaced
|
|
11863
|
+
* with raw SVG HTML before Shiki could ever claim the code block.
|
|
11864
|
+
*
|
|
11865
|
+
* @param config - Optional provider configuration; only `mermaid` is read here
|
|
11866
|
+
* (truthy enables the mermaid transform at its fixed mdast position).
|
|
11679
11867
|
* @returns The ordered default remark pluggables.
|
|
11680
11868
|
* @example
|
|
11681
11869
|
* ```ts
|
|
11682
11870
|
* const remark = defaultRemarkPlugins();
|
|
11683
11871
|
* ```
|
|
11684
11872
|
*/
|
|
11685
|
-
function defaultRemarkPlugins() {
|
|
11686
|
-
|
|
11873
|
+
function defaultRemarkPlugins(config) {
|
|
11874
|
+
const plugins = [
|
|
11687
11875
|
remarkParse,
|
|
11688
11876
|
remarkFrontmatter,
|
|
11689
11877
|
remarkGfm,
|
|
11690
11878
|
remarkDirective,
|
|
11691
|
-
pullQuotePlugin
|
|
11692
|
-
[remarkRehype, { allowDangerousHtml: true }]
|
|
11879
|
+
pullQuotePlugin
|
|
11693
11880
|
];
|
|
11881
|
+
if (config?.mermaid) plugins.push([remarkMermaidDiagrams, normalizeMermaidOptions(config.mermaid)]);
|
|
11882
|
+
plugins.push([remarkRehype, { allowDangerousHtml: true }]);
|
|
11883
|
+
return plugins;
|
|
11694
11884
|
}
|
|
11695
11885
|
/**
|
|
11696
11886
|
* The hardcoded framework default rehype (HTML-AST) plugins, in order:
|
|
@@ -11831,14 +12021,15 @@ function applyPluggable(processor, plugin) {
|
|
|
11831
12021
|
* replacing, the defaults).
|
|
11832
12022
|
*
|
|
11833
12023
|
* @param processor - The unified processor under construction (mutated in place).
|
|
11834
|
-
* @param config - Resolved plugin configuration (provides `extraRemarkPlugins
|
|
12024
|
+
* @param config - Resolved plugin configuration (provides `extraRemarkPlugins`,
|
|
12025
|
+
* and `mermaid` which the defaults read to enable the mdast mermaid transform).
|
|
11835
12026
|
* @example
|
|
11836
12027
|
* ```ts
|
|
11837
12028
|
* applyRemarkPlugins(processor, config);
|
|
11838
12029
|
* ```
|
|
11839
12030
|
*/
|
|
11840
12031
|
function applyRemarkPlugins(processor, config) {
|
|
11841
|
-
for (const plugin of defaultRemarkPlugins()) applyPluggable(processor, plugin);
|
|
12032
|
+
for (const plugin of defaultRemarkPlugins(config)) applyPluggable(processor, plugin);
|
|
11842
12033
|
for (const plugin of config.extraRemarkPlugins ?? []) applyPluggable(processor, plugin);
|
|
11843
12034
|
}
|
|
11844
12035
|
/**
|
|
@@ -11990,6 +12181,7 @@ async function discoverSlugs(dir) {
|
|
|
11990
12181
|
* ```
|
|
11991
12182
|
*/
|
|
11992
12183
|
function fileSystemContent(options) {
|
|
12184
|
+
validateFileSystemContentOptions(options);
|
|
11993
12185
|
const state = {
|
|
11994
12186
|
processor: null,
|
|
11995
12187
|
slugs: null,
|
package/package.json
CHANGED
|
@@ -80,9 +80,15 @@
|
|
|
80
80
|
"unist-util-visit": "5.1.0"
|
|
81
81
|
},
|
|
82
82
|
"peerDependencies": {
|
|
83
|
+
"mermaid-isomorphic": "^3.0.0",
|
|
83
84
|
"preact": "^10.29.2",
|
|
84
85
|
"preact-render-to-string": "^6.6.0"
|
|
85
86
|
},
|
|
87
|
+
"peerDependenciesMeta": {
|
|
88
|
+
"mermaid-isomorphic": {
|
|
89
|
+
"optional": true
|
|
90
|
+
}
|
|
91
|
+
},
|
|
86
92
|
"devDependencies": {
|
|
87
93
|
"@biomejs/biome": "2.4.16",
|
|
88
94
|
"@types/bun": "1.3.14",
|
|
@@ -96,6 +102,7 @@
|
|
|
96
102
|
"happy-dom": "20.9.0",
|
|
97
103
|
"jiti": "2.6.1",
|
|
98
104
|
"lefthook": "2.1.1",
|
|
105
|
+
"mermaid-isomorphic": "3.1.0",
|
|
99
106
|
"preact": "10.29.2",
|
|
100
107
|
"preact-render-to-string": "6.6.0",
|
|
101
108
|
"publint": "0.3.21",
|
|
@@ -118,5 +125,5 @@
|
|
|
118
125
|
"test:build-e2e": "bun test src/plugins/build/__tests__/e2e/",
|
|
119
126
|
"test:coverage": "vitest run --project unit --project integration --coverage"
|
|
120
127
|
},
|
|
121
|
-
"version": "1.
|
|
128
|
+
"version": "1.9.0"
|
|
122
129
|
}
|