@scalar/code-highlight 0.4.3 → 0.4.6
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/CHANGELOG.md +26 -0
- package/README.md +1 -1
- package/dist/chunks/code.js +1 -1
- package/dist/chunks/code.js.map +1 -1
- package/dist/chunks/languages.js +611 -272
- package/dist/chunks/languages.js.map +1 -1
- package/dist/chunks/lib.js +99 -64
- package/dist/chunks/lib.js.map +1 -1
- package/dist/chunks/lib2.js +6 -1
- package/dist/chunks/lib2.js.map +1 -1
- package/dist/chunks/markdown.js +99 -32
- package/dist/chunks/markdown.js.map +1 -1
- package/dist/chunks/rehype-alert.js.map +1 -1
- package/dist/chunks/rehype-highlight.js +33 -8
- package/dist/chunks/rehype-highlight.js.map +1 -1
- package/dist/code/index.js +0 -1
- package/dist/index.js +1 -2
- package/dist/languages/standard.d.ts +1 -0
- package/dist/languages/standard.d.ts.map +1 -1
- package/dist/markdown/index.js +0 -1
- package/dist/markdown/markdown.d.ts +6 -5
- package/dist/markdown/markdown.d.ts.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @scalar/code-highlight
|
|
2
2
|
|
|
3
|
+
## 0.4.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#10074](https://github.com/scalar/scalar/pull/10074): perf(code-highlight): reuse the markdown processor and skip the pipeline for plain paragraphs
|
|
8
|
+
|
|
9
|
+
`htmlFromMarkdown` rebuilt its twelve-plugin unified chain on every call, and each of those rebuilds ran `createLowlight(standardLanguages)`, re-registering all 57 syntax grammars before rendering. Rendering a single API description paid for a whole highlighter. Profiling an API reference schema put 34-44% of the time in markdown, roughly 70% of it in construction rather than parsing.
|
|
10
|
+
|
|
11
|
+
Two changes. One lazily created lowlight instance is now shared through `rehypeHighlight`'s `lowlight` option, and the processor is frozen and cached per option set (sorted `removeTags`, sorted `allowTags`, transform type). Calls that pass a `transform` callback still build per call, because the callback receives the AST. Separately, a description that is a single plain line with no markdown, HTML or collapsible whitespace now returns the paragraph the pipeline would have produced, without running it.
|
|
12
|
+
|
|
13
|
+
Output is unchanged. Every unique description in the Stripe OpenAPI document (5,167 of them), plus targeted probes across seven option sets and 40,000 random strings drawn from an alphabet of the excluded metacharacters, render byte-identically before and after.
|
|
14
|
+
|
|
15
|
+
Two caches now live for the process lifetime: the grammar registry and the processor map. Both are immutable once built, and the registry is only mutated through an `aliases` option this package never passes.
|
|
16
|
+
|
|
17
|
+
## 0.4.5
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- [#9941](https://github.com/scalar/scalar/pull/9941): Republish every package through npm trusted publishing. No functional changes.
|
|
22
|
+
|
|
23
|
+
## 0.4.4
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- [#9913](https://github.com/scalar/scalar/pull/9913): Add Julia (HTTP.jl) as a code example target. The new `julia/http` client generates HTTP.jl snippets, including headers, query parameters, cookies, basic auth, JSON bodies (as `Dict`s serialized with `JSON.json`), url-encoded bodies and `HTTP.Form` multipart uploads. Julia syntax highlighting and a Julia icon are included, so the client shows up in the code example picker like any other language.
|
|
28
|
+
|
|
3
29
|
## 0.4.3
|
|
4
30
|
|
|
5
31
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Scalar is an open-source API platform for teams who want beautiful developer int
|
|
|
8
8
|
|
|
9
9
|
- **[API References](https://scalar.com/products/api-references/getting-started)** — Interactive API documentation from OpenAPI and AsyncAPI specs.
|
|
10
10
|
- **[Developer Docs](https://scalar.com/products/docs/getting-started)** — Write in Markdown/MDX, generate API references, sync with two-way Git.
|
|
11
|
-
- **[SDK Generator](https://scalar.com/products/
|
|
11
|
+
- **[SDK Generator](https://scalar.com/products/sdk-generator/getting-started)** — Type-safe SDKs and CLIs in TypeScript, Python, Go, PHP, Java, and Ruby.
|
|
12
12
|
- **[API Client](https://scalar.com/products/api-client/getting-started)** — Open-source, offline-first Postman alternative built on OpenAPI.
|
|
13
13
|
|
|
14
14
|
20M+ monthly npm installs · 15,500+ GitHub stars · MIT licensed · [scalar.com](https://scalar.com)
|
package/dist/chunks/code.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as lowlightLanguageMappings, t as rehypeHighlight } from "./rehype-highlight.js";
|
|
2
2
|
import { n as rehypeStringify, s as rehypeParse, t as unified } from "./lib.js";
|
|
3
3
|
import { t as visit } from "./lib2.js";
|
|
4
4
|
//#region src/code/line-numbers.ts
|
package/dist/chunks/code.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"code.js","names":[],"sources":["../../src/code/line-numbers.ts","../../src/code/highlight.ts"],"sourcesContent":["import type { Element, ElementContent, Root, Text } from 'hast'\nimport { visit } from 'unist-util-visit'\n\n// ---------------------------------------------------------------------------\n// Line Numbering plugin\n\nfunction isText(element?: ElementContent): element is Text {\n return element?.type === 'text'\n}\n\nfunction isElement(node?: ElementContent): node is Element {\n return node?.type === 'element'\n}\n\nfunction textElement(value: string): Text {\n return { type: 'text', value }\n}\n\nfunction lineBreak(): Text {\n return { type: 'text', value: '\\n' }\n}\n\n/**\n * Adds lines to code blocks\n */\nexport function codeBlockLinesPlugin() {\n return (tree: Root) => {\n visit(tree, 'element', (node: Element, _i, parent: Root | Element | null) => {\n if (parent?.type === 'element' && parent.tagName === 'pre' && node.tagName === 'code') {\n let numLines = 0\n\n // Wraps each line in a span\n node.children = addLines(node)\n\n // Adds a line break to the end of each line\n node.children.forEach((child: ElementContent) => {\n if (child.type === 'element' && child.tagName === 'span') {\n const lastChild: ElementContent | undefined = child.children[child.children.length - 1]\n\n if (lastChild && (!isText(lastChild) || (isText(lastChild) && !hasLineBreak(lastChild)))) {\n child.children.push(lineBreak())\n numLines++\n }\n }\n })\n\n // We need to maintain a count of the total lines to allow space for the labels\n node.properties.style = [`--line-count: ${numLines};`, `--line-digits: ${numLines.toString().length};`]\n }\n })\n\n // console.log('NUMBER OF LINES IS: ', numLines)\n }\n}\n\n/**\n * Adds lines to a node recursively and returns them\n *\n * @param node - The node to add lines to\n * @param lines - The current lines\n * @param copyParent - Whether to copy the parent node to save the original node styles\n */\nfunction addLines(node: Element, lines: Element[] = [], copyParent?: boolean): Element[] {\n const line = () => lines[lines.length - 1] ?? ((lines.push(createLine()) && lines[lines.length - 1]) || undefined)\n\n node.children.forEach((child: ElementContent) => {\n if (isText(child) && hasLineBreak(child)) {\n const split: string[] = child.value.split(/\\n/)\n\n split.forEach((content: string, i: number) => {\n if (copyParent) {\n line()?.children.push({ ...node, children: [textElement(content)] })\n } else {\n line()?.children.push(textElement(content))\n }\n\n i !== split.length - 1 && lines.push(createLine())\n })\n } else if (isElement(child) && child.children.some(hasLineBreak)) {\n addLines(child, lines, true)\n } else {\n line()?.children.push(child)\n }\n })\n\n return lines\n}\n\n/**\n * Creates a new line element\n *\n * @param children - The children the line should have initially\n */\nfunction createLine(...children: ElementContent[]): Element {\n return {\n type: 'element',\n tagName: 'span',\n properties: { class: ['line'] },\n children,\n }\n}\n\n/**\n * Checks if a node has a line break\n *\n * @param node - The node to check\n */\nfunction hasLineBreak(node: ElementContent): boolean {\n return (isText(node) && /\\r?\\n/.test(node.value)) || (isElement(node) && node.children.some(hasLineBreak))\n}\n","import type { Element, Root } from 'hast'\nimport type { LanguageFn } from 'highlight.js'\nimport rehypeParse from 'rehype-parse'\nimport rehypeStringify from 'rehype-stringify'\nimport { type Plugin, unified } from 'unified'\nimport { visit } from 'unist-util-visit'\n\nimport { lowlightLanguageMappings } from '@/constants'\nimport { rehypeHighlight } from '@/rehype-highlight'\n\nimport { codeBlockLinesPlugin } from './line-numbers'\n\n/**\n * Syntax highlights a code string using the `rehype-highlight` library.\n */\nexport function syntaxHighlight(\n codeString: string,\n options: {\n lang: string\n languages: Record<string, LanguageFn>\n lineNumbers?: boolean\n maskCredentials?: string | string[]\n },\n) {\n // Simple restriction on credentials to prevent unexpected behavior\n const credentials = (\n typeof options?.maskCredentials === 'string' ? [options.maskCredentials] : (options?.maskCredentials ?? [])\n ).filter((c) => {\n // Credentials must be at least 3 characters to mask.\n if (c.length < 3) {\n return false\n }\n\n return true\n })\n\n // Classname is used by lowlight to select the language model\n const className = `language-${lowlightLanguageMappings[options.lang] ?? options.lang}`\n\n // biome-ignore lint/suspicious/noEmptyBlockStatements: empty plugin\n const nullPlugin = (() => {}) satisfies Plugin\n\n const html = unified()\n // Parses markdown\n .use(rehypeParse, { fragment: true })\n // Raw code string must be injected after initial hast parsing\n // so that HTML code is not parsed into the hast tree\n .use(injectRawCodeStringPlugin(codeString))\n // Syntax highlighting\n .use(rehypeHighlight, {\n languages: options.languages,\n })\n .use(options?.lineNumbers ? codeBlockLinesPlugin : nullPlugin)\n // Converts the HTML AST to a string\n .use(rehypeStringify)\n // Run the pipeline\n .processSync(`<pre><code class=\"${className}\"></code></pre>`)\n\n const htmlString = html.toString()\n\n // Replace any credentials with a wrapper element\n return credentials.length\n ? credentials.reduce(\n (acc, credential) =>\n acc\n .split(credential)\n .join(`<span class=\"credential\"><span class=\"credential-value\">${credential}</span></span>`),\n htmlString,\n )\n : htmlString\n}\n\n/**\n * To prevent unified from parsing any content of the code string we inject\n * it as a raw text node into the AST tree as a child of the code element\n */\nfunction injectRawCodeStringPlugin(rawCodeString: string) {\n return () => (tree: Root) => {\n visit(tree, 'element', (node: Element) => {\n if (node.tagName === 'code') {\n node.children.push({\n type: 'text',\n value: rawCodeString,\n })\n }\n })\n }\n}\n"],"mappings":";;;;AAMA,SAAS,OAAO,SAA2C;
|
|
1
|
+
{"version":3,"file":"code.js","names":[],"sources":["../../src/code/line-numbers.ts","../../src/code/highlight.ts"],"sourcesContent":["import type { Element, ElementContent, Root, Text } from 'hast'\nimport { visit } from 'unist-util-visit'\n\n// ---------------------------------------------------------------------------\n// Line Numbering plugin\n\nfunction isText(element?: ElementContent): element is Text {\n return element?.type === 'text'\n}\n\nfunction isElement(node?: ElementContent): node is Element {\n return node?.type === 'element'\n}\n\nfunction textElement(value: string): Text {\n return { type: 'text', value }\n}\n\nfunction lineBreak(): Text {\n return { type: 'text', value: '\\n' }\n}\n\n/**\n * Adds lines to code blocks\n */\nexport function codeBlockLinesPlugin() {\n return (tree: Root) => {\n visit(tree, 'element', (node: Element, _i, parent: Root | Element | null) => {\n if (parent?.type === 'element' && parent.tagName === 'pre' && node.tagName === 'code') {\n let numLines = 0\n\n // Wraps each line in a span\n node.children = addLines(node)\n\n // Adds a line break to the end of each line\n node.children.forEach((child: ElementContent) => {\n if (child.type === 'element' && child.tagName === 'span') {\n const lastChild: ElementContent | undefined = child.children[child.children.length - 1]\n\n if (lastChild && (!isText(lastChild) || (isText(lastChild) && !hasLineBreak(lastChild)))) {\n child.children.push(lineBreak())\n numLines++\n }\n }\n })\n\n // We need to maintain a count of the total lines to allow space for the labels\n node.properties.style = [`--line-count: ${numLines};`, `--line-digits: ${numLines.toString().length};`]\n }\n })\n\n // console.log('NUMBER OF LINES IS: ', numLines)\n }\n}\n\n/**\n * Adds lines to a node recursively and returns them\n *\n * @param node - The node to add lines to\n * @param lines - The current lines\n * @param copyParent - Whether to copy the parent node to save the original node styles\n */\nfunction addLines(node: Element, lines: Element[] = [], copyParent?: boolean): Element[] {\n const line = () => lines[lines.length - 1] ?? ((lines.push(createLine()) && lines[lines.length - 1]) || undefined)\n\n node.children.forEach((child: ElementContent) => {\n if (isText(child) && hasLineBreak(child)) {\n const split: string[] = child.value.split(/\\n/)\n\n split.forEach((content: string, i: number) => {\n if (copyParent) {\n line()?.children.push({ ...node, children: [textElement(content)] })\n } else {\n line()?.children.push(textElement(content))\n }\n\n i !== split.length - 1 && lines.push(createLine())\n })\n } else if (isElement(child) && child.children.some(hasLineBreak)) {\n addLines(child, lines, true)\n } else {\n line()?.children.push(child)\n }\n })\n\n return lines\n}\n\n/**\n * Creates a new line element\n *\n * @param children - The children the line should have initially\n */\nfunction createLine(...children: ElementContent[]): Element {\n return {\n type: 'element',\n tagName: 'span',\n properties: { class: ['line'] },\n children,\n }\n}\n\n/**\n * Checks if a node has a line break\n *\n * @param node - The node to check\n */\nfunction hasLineBreak(node: ElementContent): boolean {\n return (isText(node) && /\\r?\\n/.test(node.value)) || (isElement(node) && node.children.some(hasLineBreak))\n}\n","import type { Element, Root } from 'hast'\nimport type { LanguageFn } from 'highlight.js'\nimport rehypeParse from 'rehype-parse'\nimport rehypeStringify from 'rehype-stringify'\nimport { type Plugin, unified } from 'unified'\nimport { visit } from 'unist-util-visit'\n\nimport { lowlightLanguageMappings } from '@/constants'\nimport { rehypeHighlight } from '@/rehype-highlight'\n\nimport { codeBlockLinesPlugin } from './line-numbers'\n\n/**\n * Syntax highlights a code string using the `rehype-highlight` library.\n */\nexport function syntaxHighlight(\n codeString: string,\n options: {\n lang: string\n languages: Record<string, LanguageFn>\n lineNumbers?: boolean\n maskCredentials?: string | string[]\n },\n) {\n // Simple restriction on credentials to prevent unexpected behavior\n const credentials = (\n typeof options?.maskCredentials === 'string' ? [options.maskCredentials] : (options?.maskCredentials ?? [])\n ).filter((c) => {\n // Credentials must be at least 3 characters to mask.\n if (c.length < 3) {\n return false\n }\n\n return true\n })\n\n // Classname is used by lowlight to select the language model\n const className = `language-${lowlightLanguageMappings[options.lang] ?? options.lang}`\n\n // biome-ignore lint/suspicious/noEmptyBlockStatements: empty plugin\n const nullPlugin = (() => {}) satisfies Plugin\n\n const html = unified()\n // Parses markdown\n .use(rehypeParse, { fragment: true })\n // Raw code string must be injected after initial hast parsing\n // so that HTML code is not parsed into the hast tree\n .use(injectRawCodeStringPlugin(codeString))\n // Syntax highlighting\n .use(rehypeHighlight, {\n languages: options.languages,\n })\n .use(options?.lineNumbers ? codeBlockLinesPlugin : nullPlugin)\n // Converts the HTML AST to a string\n .use(rehypeStringify)\n // Run the pipeline\n .processSync(`<pre><code class=\"${className}\"></code></pre>`)\n\n const htmlString = html.toString()\n\n // Replace any credentials with a wrapper element\n return credentials.length\n ? credentials.reduce(\n (acc, credential) =>\n acc\n .split(credential)\n .join(`<span class=\"credential\"><span class=\"credential-value\">${credential}</span></span>`),\n htmlString,\n )\n : htmlString\n}\n\n/**\n * To prevent unified from parsing any content of the code string we inject\n * it as a raw text node into the AST tree as a child of the code element\n */\nfunction injectRawCodeStringPlugin(rawCodeString: string) {\n return () => (tree: Root) => {\n visit(tree, 'element', (node: Element) => {\n if (node.tagName === 'code') {\n node.children.push({\n type: 'text',\n value: rawCodeString,\n })\n }\n })\n }\n}\n"],"mappings":"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"}
|