Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Merge the site's CPU: code highlighted once per data centre by content, markdown parsed once per text, and the request handler built once per isolate | 1 | import type { Root } from "hast"; |
| 2 | import { type Components as JsxComponents, toJsxRuntime } from "hast-util-to-jsx-runtime"; | |
| 3 | import { urlAttributes } from "html-url-attributes"; | |
| 4 | import type { ReactElement } from "react"; | |
| 5 | import { Fragment, jsx, jsxs } from "react/jsx-runtime"; | |
| 6 | import { type Components, defaultUrlTransform } from "react-markdown"; | |
| 7 | import remarkParse from "remark-parse"; | |
| 8 | import remarkRehype from "remark-rehype"; | |
| 9 | import { type PluggableList, unified } from "unified"; | |
| 10 | import { visit } from "unist-util-visit"; | |
| 11 | import { VFile } from "vfile"; | |
| 12 | ||
| 13 | /** | |
| 14 | * Markdown in two steps, the same two `react-markdown`'s `<Markdown>` takes | |
| 15 | * on every render, split so the first can be kept: | |
| 16 | * | |
| 17 | * 1. `markdownTree`: parse, run the plugins, make URLs safe. Most of the | |
| 18 | * work (nine tenths of a large README's), and it depends only on the | |
| 19 | * text and the plugins, so components/markdown.tsx keeps its trees. | |
| 20 | * 2. `renderMarkdownTree`: the tree as React elements, with the page's | |
| 21 | * components. | |
| 22 | * | |
| 23 | * Together they make exactly what `<Markdown>` makes for the same options | |
| 24 | * (lib/markdown-tree.test.ts renders both and compares). Options this file | |
| 25 | * does not take (`allowedElements`, `skipHtml`, `urlTransform`, …) are not | |
| 26 | * used by g1t. | |
| 27 | */ | |
| 28 | export type MarkdownTreeOptions = { | |
| 29 | remarkPlugins?: PluggableList; | |
| 30 | rehypePlugins?: PluggableList; | |
| 31 | }; | |
| 32 | ||
| 33 | /** The hast tree `<Markdown>` would render for `source`. Not changed by rendering, so it can be kept and rendered again. */ | |
| 34 | export function markdownTree(source: string, options: MarkdownTreeOptions = {}): Root { | |
| 35 | const processor = unified() | |
| 36 | .use(remarkParse) | |
| 37 | .use(options.remarkPlugins ?? []) | |
| 38 | .use(remarkRehype, { allowDangerousHtml: true }) | |
| 39 | .use(options.rehypePlugins ?? []); | |
| 40 | const file = new VFile(); | |
| 41 | file.value = source; | |
| 42 | const tree = processor.runSync(processor.parse(file), file) as Root; | |
| 43 | // What react-markdown does before rendering: raw HTML left over becomes | |
| 44 | // text, and every URL attribute goes through the default transform. | |
| 45 | visit(tree, (node, index, parent) => { | |
| 46 | if (node.type === "raw" && parent && typeof index === "number") { | |
| 47 | parent.children[index] = { type: "text", value: node.value }; | |
| 48 | return index; | |
| 49 | } | |
| 50 | if (node.type === "element") { | |
| 51 | for (const key in urlAttributes) { | |
| 52 | if (Object.hasOwn(urlAttributes, key) && Object.hasOwn(node.properties, key)) { | |
| 53 | const value = node.properties[key]; | |
| 54 | const test = urlAttributes[key]; | |
| 55 | if (test === null || test.includes(node.tagName)) { | |
| 56 | node.properties[key] = defaultUrlTransform(String(value || "")); | |
| 57 | } | |
| 58 | } | |
| 59 | } | |
| 60 | } | |
| 61 | return undefined; | |
| 62 | }); | |
| 63 | return tree; | |
| 64 | } | |
| 65 | ||
| 66 | /** `tree` as React elements, as `<Markdown>` renders it with `components`. */ | |
| 67 | export function renderMarkdownTree(tree: Root, components?: Components): ReactElement { | |
| 68 | return toJsxRuntime(tree, { | |
| 69 | Fragment, | |
| 70 | // react-markdown's components, which it hands to this same function. | |
| 71 | components: components as JsxComponents | undefined, | |
| 72 | ignoreInvalidStyle: true, | |
| 73 | jsx, | |
| 74 | jsxs, | |
| 75 | passKeys: true, | |
| 76 | passNode: true, | |
| 77 | }); | |
| 78 | } | |
| 79 | ||
| 80 | /** How much a kept tree weighs: its text's length, a stand-in for the tree's size. */ | |
| 81 | export function markdownWeight(source: string): number { | |
| 82 | return source.length + 64; | |
| 83 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.