@toaq-oss/omni-mdx 1.1.1 โ 1.1.2
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/README.md +89 -48
- package/dist/client.cjs +69 -13
- package/dist/client.cjs.map +1 -1
- package/dist/client.js +70 -14
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/omni_mdx_core_bg.wasm +0 -0
- package/dist/server.d.ts +12 -1
- package/dist/server.js +76 -41
- package/dist/server.js.map +1 -1
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -1,41 +1,69 @@
|
|
|
1
1
|
# @toaq-oss/omni-mdx
|
|
2
2
|
|
|
3
|
+
[![Build][build-badge]][build]
|
|
4
|
+
[![Coverage][coverage-badge]][coverage]
|
|
5
|
+
[![Downloads][downloads-badge]][downloads]
|
|
6
|
+
[![Size][size-badge]][size]
|
|
7
|
+
[![Chat][chat-badge]][chat]
|
|
8
|
+
|
|
3
9
|
**The high-performance MDX engine.** A unified React & Next.js rendering layer powered by a dual Rust backend (Native Node.js + WebAssembly).
|
|
4
10
|
|
|
5
|
-
|
|
6
|
-
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Contents
|
|
14
|
+
|
|
15
|
+
* [What is this?](#what-is-this)
|
|
16
|
+
* [Why Omni-MDX?](#why-omni-mdx)
|
|
17
|
+
* [Install](#install)
|
|
18
|
+
* [Use](#use)
|
|
19
|
+
* [Server-Side Rendering (RSC)](#server-side-rendering-rsc)
|
|
20
|
+
* [Live Client Editor (WASM)](#live-client-editor-wasm)
|
|
21
|
+
* [Monorepo Structure](#monorepo-structure)
|
|
22
|
+
* [Next.js Configuration](#nextjs-configuration)
|
|
23
|
+
* [Contributing](#contributing)
|
|
24
|
+
* [License](#license)
|
|
7
25
|
|
|
8
26
|
---
|
|
9
27
|
|
|
10
|
-
##
|
|
28
|
+
## What is this?
|
|
11
29
|
|
|
12
|
-
|
|
30
|
+
`omni-mdx` is an ecosystem designed to overcome the performance bottlenecks of traditional MDX pipelines. Instead of relying on heavy JavaScript regular expressions, it utilizes a core written in **Rust** to transform MDX into a structured Abstract Syntax Tree (AST) nearly instantaneously.
|
|
13
31
|
|
|
14
|
-
|
|
32
|
+
Whether you are building for the Web (Next.js, React), or the Server (Node.js), Omni-MDX provides a unified interface for smooth and precise rendering.
|
|
15
33
|
|
|
16
|
-
|
|
34
|
+
---
|
|
17
35
|
|
|
18
|
-
|
|
36
|
+
## Why Omni-MDX?
|
|
19
37
|
|
|
20
|
-
*
|
|
38
|
+
* ๐ **Extreme Performance:** Parsing is offloaded to Rust, performing up to 10x faster than pure JS alternatives.
|
|
39
|
+
* ๐ **Unified/Rehype Bridge:** Native Rust core with full support for standard **Rehype plugins** (Highlighting, Slugs, Autolink, etc.).
|
|
40
|
+
* ๐ **Built-in Features:** GFM Tables, KaTeX math, and JSX components handled nativelyโno extra configuration required.
|
|
41
|
+
* โ๏ธ **RSC Optimized:** Built for Next.js App Router and Server Components with zero-hydration math rendering.
|
|
21
42
|
* ๐งต **Non-Blocking:** Offloads parsing from the Node.js main thread, keeping your server responsive.
|
|
22
43
|
|
|
23
44
|
---
|
|
24
45
|
|
|
25
|
-
##
|
|
26
|
-
|
|
27
|
-
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
This package is [ESM only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c). In Node.js, install with [npm]:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install @toaq-oss/omni-mdx
|
|
52
|
+
# Required for math styles
|
|
53
|
+
npm install katex
|
|
54
|
+
```
|
|
28
55
|
|
|
29
|
-
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Use
|
|
30
59
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
60
|
+
### Server-Side Rendering (RSC)
|
|
61
|
+
|
|
62
|
+
Recommended for documentation, blogs, and research papers.
|
|
34
63
|
|
|
35
64
|
```tsx
|
|
36
65
|
import { parseMdx, MDXServerRenderer } from "@toaq-oss/omni-mdx/server";
|
|
37
66
|
import rehypeHighlight from "rehype-highlight";
|
|
38
|
-
import { MyComponent } from "@/components/mdx";
|
|
39
67
|
|
|
40
68
|
export default async function Page({ content }) {
|
|
41
69
|
// 1. Parse via Native Rust Addon (.node)
|
|
@@ -52,10 +80,9 @@ export default async function Page({ content }) {
|
|
|
52
80
|
}
|
|
53
81
|
```
|
|
54
82
|
|
|
55
|
-
###
|
|
56
|
-
Perfect for real-time previews or CMS interfaces.
|
|
83
|
+
### Live Client Editor (WASM)
|
|
57
84
|
|
|
58
|
-
|
|
85
|
+
Perfect for real-time previews or CMS interfaces.
|
|
59
86
|
|
|
60
87
|
```tsx
|
|
61
88
|
"use client";
|
|
@@ -80,16 +107,23 @@ export default function Editor() {
|
|
|
80
107
|
}
|
|
81
108
|
```
|
|
82
109
|
|
|
83
|
-
|
|
110
|
+
---
|
|
84
111
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
112
|
+
## Monorepo Structure
|
|
113
|
+
|
|
114
|
+
This repository houses the various components of the ecosystem:
|
|
115
|
+
|
|
116
|
+
| Package | Language | Description |
|
|
117
|
+
| :--- | :---: | :--- |
|
|
118
|
+
| [**`core-parser`**](./packages/core-parser) | ![Rust] | The high-performance parsing core. |
|
|
119
|
+
| [**`mdx-next`**](./packages/mdx-next) | ![TypeScript] | React/Next.js layer (Native + WASM). |
|
|
120
|
+
|
|
121
|
+
---
|
|
90
122
|
|
|
91
123
|
## Next.js Configuration
|
|
124
|
+
|
|
92
125
|
To enable the WebAssembly engine for client-side rendering, update your `next.config.js`:
|
|
126
|
+
|
|
93
127
|
```typescript
|
|
94
128
|
/** @type {import('next').NextConfig} */
|
|
95
129
|
const nextConfig = {
|
|
@@ -108,34 +142,41 @@ export default nextConfig;
|
|
|
108
142
|
|
|
109
143
|
---
|
|
110
144
|
|
|
111
|
-
##
|
|
112
|
-
### ๐ Extensibility (Rehype Support)
|
|
113
|
-
Omni-MDX uses a unique "Bridge" architecture. It parses Markdown to a high-performance AST in Rust, then optionally passes it through the Unified/Rehype pipeline in JS for transformations.
|
|
114
|
-
* **Support:** `rehype-slug`, `rehype-highlight`, `rehype-autolink-headings`, etc.
|
|
115
|
-
* **Performance:** Plugins only run on the final tree, keeping the heavy parsing phase in Rust.
|
|
145
|
+
## Contributing
|
|
116
146
|
|
|
117
|
-
|
|
118
|
-
Math is handled via KaTeX. Include the CSS in your `layout.tsx`:
|
|
119
|
-
`import "katex/dist/katex.min.css";`
|
|
120
|
-
* **Inline:** `$E=mc^2$`
|
|
121
|
-
* **Block:** `$$\zeta(s) = \sum_{1}^{\infty} n^{-s}$$`
|
|
147
|
+
We enthusiastically welcome contributions! Omni-MDX is a sophisticated project blending Rust, C++, TypeScript, and Python.
|
|
122
148
|
|
|
123
|
-
|
|
124
|
-
|
|
149
|
+
1. **Clone the repository:**
|
|
150
|
+
```bash
|
|
151
|
+
git clone https://github.com/TOAQ-oss/omni-mdx-core.git
|
|
152
|
+
```
|
|
153
|
+
2. **Check the contribution guide:** [CONTRIBUTING.md](./CONTRIBUTING.md) to learn how to set up your local development environment.
|
|
125
154
|
|
|
126
155
|
---
|
|
127
|
-
## ๐ Documentation & Support
|
|
128
156
|
|
|
129
|
-
|
|
157
|
+
## License
|
|
130
158
|
|
|
131
|
-
|
|
132
|
-
|:---|:---|:---|
|
|
133
|
-
|**Server** (Node.js)|Native Addon (`.node`)|`@toaq-oss/omni-mdx/server`|
|
|
134
|
-
|**Client** (Browser)|WebAssembly (`.wasm`)|`@toaq-oss/omni-mdx/client`|
|
|
135
|
-
|**Edge** (Vercel)|Native Addon (`.wasm`)|`@toaq-oss/omni-mdx/client`|
|
|
159
|
+
[MIT][license] ยฉ [TOAQ-oss][author-url]
|
|
136
160
|
|
|
137
161
|
---
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
162
|
+
|
|
163
|
+
[build-badge]: https://github.com/TOAQ-oss/omni-mdx-core/actions/workflows/publish-next.yml/badge.svg
|
|
164
|
+
[build]: https://github.com/TOAQ-oss/omni-mdx-core/actions
|
|
165
|
+
|
|
166
|
+
[coverage-badge]: https://img.shields.io/codecov/c/github/TOAQ-oss/omni-mdx-core/main.svg?flag=npm&logo=codecov
|
|
167
|
+
[coverage]: https://codecov.io/github/TOAQ-oss/omni-mdx-core
|
|
168
|
+
|
|
169
|
+
[downloads-badge]: https://img.shields.io/npm/dm/@toaq-oss/omni-mdx.svg?style=flat-square&color=blue
|
|
170
|
+
[downloads]: https://www.npmjs.com/package/@toaq-oss/omni-mdx
|
|
171
|
+
|
|
172
|
+
[size-badge]: https://img.shields.io/bundlejs/size/@toaq-oss/omni-mdx?color=brightgreen
|
|
173
|
+
[size]: https://bundlejs.com/?q=@toaq-oss/omni-mdx
|
|
174
|
+
|
|
175
|
+
[chat-badge]: https://img.shields.io/badge/chat-discussions-success.svg?logo=github
|
|
176
|
+
[chat]: https://github.com/TOAQ-oss/omni-mdx-core/discussions
|
|
177
|
+
|
|
178
|
+
[license]: https://github.com/TOAQ-oss/omni-mdx-core/blob/main/LICENSE
|
|
179
|
+
[author-url]: https://github.com/toaq-oss
|
|
180
|
+
|
|
181
|
+
[Rust]: https://img.shields.io/badge/rust-000000?logo=rust
|
|
182
|
+
[TypeScript]: https://img.shields.io/badge/typescript-3178C6?logo=typescript
|
package/dist/client.cjs
CHANGED
|
@@ -109,7 +109,7 @@ function parse_to_binary(mdx_input) {
|
|
|
109
109
|
function __wbg_get_imports() {
|
|
110
110
|
const import0 = {
|
|
111
111
|
__proto__: null,
|
|
112
|
-
|
|
112
|
+
__wbg_Error_960c155d3d49e4c2: function(arg0, arg1) {
|
|
113
113
|
const ret = Error(getStringFromWasm0(arg0, arg1));
|
|
114
114
|
return ret;
|
|
115
115
|
},
|
|
@@ -310,6 +310,7 @@ module.exports = __toCommonJS(client_exports);
|
|
|
310
310
|
// src/MDXClientRenderer.tsx
|
|
311
311
|
var import_react2 = __toESM(require("react"), 1);
|
|
312
312
|
var import_katex = __toESM(require("katex"), 1);
|
|
313
|
+
var import_json5 = __toESM(require("json5"), 1);
|
|
313
314
|
|
|
314
315
|
// src/MDXErrorBoundary.tsx
|
|
315
316
|
var import_react = require("react");
|
|
@@ -384,7 +385,7 @@ var BASIC_STYLES = {
|
|
|
384
385
|
table: (props) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "overflow-x-auto mb-6", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("table", { className: "w-full text-sm text-left border-collapse", ...props }) }),
|
|
385
386
|
th: (props) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { className: "border-b border-white/10 p-2 font-semibold text-neutral-200", ...props }),
|
|
386
387
|
td: (props) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { className: "border-b border-white/5 p-2 text-neutral-400", ...props }),
|
|
387
|
-
img: (props) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("img", { className: "rounded-xl border border-white/10 my-8 mx-auto max-w-full h-auto", ...props }),
|
|
388
|
+
img: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("img", { className: "rounded-xl border border-white/10 my-8 mx-auto max-w-full h-auto", ...props }),
|
|
388
389
|
a: (props) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("a", { className: "text-blue-400 hover:text-blue-300 underline underline-offset-4 decoration-blue-500/30 transition-colors", ...props })
|
|
389
390
|
};
|
|
390
391
|
|
|
@@ -403,7 +404,7 @@ function resolveAttr(attr, components) {
|
|
|
403
404
|
} catch {
|
|
404
405
|
}
|
|
405
406
|
try {
|
|
406
|
-
return
|
|
407
|
+
return import_json5.default.parse(raw);
|
|
407
408
|
} catch {
|
|
408
409
|
}
|
|
409
410
|
return raw;
|
|
@@ -488,27 +489,77 @@ function renderNode(node, index, components) {
|
|
|
488
489
|
if (node.node_type === "fragment") {
|
|
489
490
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react2.default.Fragment, { children: node.children?.map((c, i) => renderNode(c, i, components)) }, key);
|
|
490
491
|
}
|
|
492
|
+
const getMathFormula = (node2) => {
|
|
493
|
+
let formula = "";
|
|
494
|
+
if (node2.attributes) {
|
|
495
|
+
const attrs = typeof node2.attributes === "string" ? JSON.parse(node2.attributes) : node2.attributes;
|
|
496
|
+
const mathData = attrs?.["data-math"];
|
|
497
|
+
if (mathData && mathData.value) {
|
|
498
|
+
formula = String(mathData.value);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
if (!formula) {
|
|
502
|
+
formula = extractText(node2);
|
|
503
|
+
}
|
|
504
|
+
return formula;
|
|
505
|
+
};
|
|
491
506
|
if (node.node_type === "InlineMath") {
|
|
492
|
-
const formula =
|
|
507
|
+
const formula = getMathFormula(node);
|
|
493
508
|
try {
|
|
494
|
-
const html = import_katex.default.renderToString(formula, {
|
|
495
|
-
|
|
509
|
+
const html = import_katex.default.renderToString(formula, {
|
|
510
|
+
displayMode: false,
|
|
511
|
+
throwOnError: false,
|
|
512
|
+
output: "html"
|
|
513
|
+
});
|
|
514
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
515
|
+
"span",
|
|
516
|
+
{
|
|
517
|
+
className: "math math-inline",
|
|
518
|
+
dangerouslySetInnerHTML: { __html: html }
|
|
519
|
+
},
|
|
520
|
+
key
|
|
521
|
+
);
|
|
496
522
|
} catch {
|
|
497
523
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "math math-inline", children: formula }, key);
|
|
498
524
|
}
|
|
499
525
|
}
|
|
500
|
-
if (node.node_type === "BlockMath") {
|
|
501
|
-
const formula =
|
|
526
|
+
if (node.node_type === "BlockMath" || node.node_type === "math") {
|
|
527
|
+
const formula = getMathFormula(node);
|
|
502
528
|
try {
|
|
503
|
-
const html = import_katex.default.renderToString(formula, {
|
|
504
|
-
|
|
529
|
+
const html = import_katex.default.renderToString(formula, {
|
|
530
|
+
displayMode: true,
|
|
531
|
+
throwOnError: false,
|
|
532
|
+
output: "html"
|
|
533
|
+
});
|
|
534
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
535
|
+
"div",
|
|
536
|
+
{
|
|
537
|
+
className: "math math-display",
|
|
538
|
+
dangerouslySetInnerHTML: { __html: html }
|
|
539
|
+
},
|
|
540
|
+
key
|
|
541
|
+
);
|
|
505
542
|
} catch {
|
|
506
543
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "math math-display", children: formula }, key);
|
|
507
544
|
}
|
|
508
545
|
}
|
|
509
546
|
const resolvedProps = {};
|
|
510
547
|
if (node.attributes) {
|
|
511
|
-
|
|
548
|
+
let attrs = {};
|
|
549
|
+
if (typeof node.attributes === "string") {
|
|
550
|
+
try {
|
|
551
|
+
attrs = JSON.parse(node.attributes);
|
|
552
|
+
} catch (error) {
|
|
553
|
+
if (true) {
|
|
554
|
+
console.warn(
|
|
555
|
+
`[toaq-oss/omni-mdx] Failed to parse attributes JSON for <${node.node_type}>`,
|
|
556
|
+
error
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
} else {
|
|
561
|
+
attrs = node.attributes;
|
|
562
|
+
}
|
|
512
563
|
for (const [k, v] of Object.entries(attrs)) {
|
|
513
564
|
resolvedProps[k] = resolveAttr(v, components);
|
|
514
565
|
}
|
|
@@ -537,7 +588,6 @@ function MDXClientRenderer({
|
|
|
537
588
|
ast,
|
|
538
589
|
components = {}
|
|
539
590
|
}) {
|
|
540
|
-
const rootRef = (0, import_react2.useRef)(null);
|
|
541
591
|
if (!ast || !Array.isArray(ast)) return null;
|
|
542
592
|
const finalComponents = {
|
|
543
593
|
...BASIC_STYLES,
|
|
@@ -670,6 +720,9 @@ var MdxBinaryDecoder = class {
|
|
|
670
720
|
*/
|
|
671
721
|
readStringU16() {
|
|
672
722
|
const len = this.readU16();
|
|
723
|
+
if (this.offset + len > this.buffer.byteLength) {
|
|
724
|
+
throw new Error(`Binary corruption: string (u16) out of bounds at offset ${this.offset}`);
|
|
725
|
+
}
|
|
673
726
|
const str = this.decoder.decode(this.buffer.subarray(this.offset, this.offset + len));
|
|
674
727
|
this.offset += len;
|
|
675
728
|
let cached = this.stringCache.get(str);
|
|
@@ -684,6 +737,9 @@ var MdxBinaryDecoder = class {
|
|
|
684
737
|
*/
|
|
685
738
|
readStringU32() {
|
|
686
739
|
const len = this.readU32();
|
|
740
|
+
if (this.offset + len > this.buffer.byteLength) {
|
|
741
|
+
throw new Error(`Binary corruption: string (u32) out of bounds at offset ${this.offset}`);
|
|
742
|
+
}
|
|
687
743
|
const str = this.decoder.decode(this.buffer.subarray(this.offset, this.offset + len));
|
|
688
744
|
this.offset += len;
|
|
689
745
|
return str;
|
|
@@ -818,7 +874,7 @@ function getClientParser() {
|
|
|
818
874
|
const wasm2 = await Promise.resolve().then(() => (init_omni_mdx_core(), omni_mdx_core_exports));
|
|
819
875
|
if (typeof wasm2.default === "function") {
|
|
820
876
|
const wasmUrl = new URL("./omni_mdx_core_bg.wasm", import_meta2.url);
|
|
821
|
-
await wasm2.default(wasmUrl);
|
|
877
|
+
await wasm2.default({ module_or_path: wasmUrl });
|
|
822
878
|
}
|
|
823
879
|
return (mdx) => wasm2.parse_to_binary(mdx);
|
|
824
880
|
})();
|