@vxrn/mdx-rust 1.20.3

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.
Files changed (59) hide show
  1. package/README.md +52 -0
  2. package/dist/client.mjs +11 -0
  3. package/dist/client.mjs.map +1 -0
  4. package/dist/getAllFrontmatter.mjs +26 -0
  5. package/dist/getAllFrontmatter.mjs.map +1 -0
  6. package/dist/getHeadings.mjs +12 -0
  7. package/dist/getHeadings.mjs.map +1 -0
  8. package/dist/getMDX.mjs +66 -0
  9. package/dist/getMDX.mjs.map +1 -0
  10. package/dist/getMDXBySlug.mjs +22 -0
  11. package/dist/getMDXBySlug.mjs.map +1 -0
  12. package/dist/index.js +10 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/index.mjs +10 -0
  15. package/dist/index.mjs.map +1 -0
  16. package/dist/processImageMeta.mjs +37 -0
  17. package/dist/processImageMeta.mjs.map +1 -0
  18. package/dist/slugPlugin.mjs +29 -0
  19. package/dist/slugPlugin.mjs.map +1 -0
  20. package/dist/tableWhitespacePlugin.mjs +19 -0
  21. package/dist/tableWhitespacePlugin.mjs.map +1 -0
  22. package/dist/types.mjs +2 -0
  23. package/dist/types.mjs.map +1 -0
  24. package/dist/watchFile.mjs +7 -0
  25. package/dist/watchFile.mjs.map +1 -0
  26. package/package.json +57 -0
  27. package/src/client.ts +20 -0
  28. package/src/getAllFrontmatter.ts +33 -0
  29. package/src/getHeadings.ts +18 -0
  30. package/src/getMDX.ts +90 -0
  31. package/src/getMDXBySlug.ts +34 -0
  32. package/src/index.ts +9 -0
  33. package/src/processImageMeta.ts +38 -0
  34. package/src/slugPlugin.ts +28 -0
  35. package/src/tableWhitespacePlugin.ts +23 -0
  36. package/src/types.ts +30 -0
  37. package/src/watchFile.ts +12 -0
  38. package/types/client.d.ts +13 -0
  39. package/types/client.d.ts.map +1 -0
  40. package/types/getAllFrontmatter.d.ts +3 -0
  41. package/types/getAllFrontmatter.d.ts.map +1 -0
  42. package/types/getHeadings.d.ts +3 -0
  43. package/types/getHeadings.d.ts.map +1 -0
  44. package/types/getMDX.d.ts +27 -0
  45. package/types/getMDX.d.ts.map +1 -0
  46. package/types/getMDXBySlug.d.ts +8 -0
  47. package/types/getMDXBySlug.d.ts.map +1 -0
  48. package/types/index.d.ts +10 -0
  49. package/types/index.d.ts.map +1 -0
  50. package/types/processImageMeta.d.ts +7 -0
  51. package/types/processImageMeta.d.ts.map +1 -0
  52. package/types/slugPlugin.d.ts +8 -0
  53. package/types/slugPlugin.d.ts.map +1 -0
  54. package/types/tableWhitespacePlugin.d.ts +8 -0
  55. package/types/tableWhitespacePlugin.d.ts.map +1 -0
  56. package/types/types.d.ts +38 -0
  57. package/types/types.d.ts.map +1 -0
  58. package/types/watchFile.d.ts +6 -0
  59. package/types/watchFile.d.ts.map +1 -0
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # @vxrn/mdx-rust
2
+
3
+ A small, fast MDX pipeline for One, built on [satteri](https://satteri.bruits.org)
4
+ (Markdown/MDX parsed and compiled in Rust) with syntax highlighting from
5
+ [Expressive Code](https://expressive-code.com) (Shiki).
6
+
7
+ It replaces the `mdx-bundler` + remark/rehype stack in `@vxrn/mdx` with far less
8
+ code and a much faster compile. GFM, SmartyPants, frontmatter, and `:::`
9
+ directives are built into the parser; heading slugs and code highlighting are the
10
+ only bundled plugins.
11
+
12
+ ## Usage
13
+
14
+ ```ts
15
+ // server (build / loader)
16
+ import { getMDXBySlug } from '@vxrn/mdx-rust'
17
+
18
+ const { frontmatter, code } = await getMDXBySlug('data/docs', slug)
19
+ ```
20
+
21
+ ```tsx
22
+ // client
23
+ import { getMDXComponent } from '@vxrn/mdx-rust/client'
24
+
25
+ const Component = useMemo(() => getMDXComponent(code), [code])
26
+ return <Component components={components} />
27
+ ```
28
+
29
+ ## API
30
+
31
+ - `getMDX(source, options?)` → `{ frontmatter, code }`
32
+ - `getMDXBySlug(basePath, slug, options?)` → `{ frontmatter, code }`
33
+ - `getAllFrontmatter(fromPath)` → `Frontmatter[]`
34
+ - `getAllVersionsFromPath(fromPath)` → `string[]`
35
+ - `getHeadings(source)` → table-of-contents entries
36
+ - `getMDXComponent(code)` (from `@vxrn/mdx-rust/client`)
37
+ - `slugPlugin` — the bundled heading-id hast plugin, exported for reuse
38
+
39
+ ### `GetMDXOptions`
40
+
41
+ - `expressiveCode` — Expressive Code options (themes, frames, styleOverrides,
42
+ plugins), or `false` to skip highlighting. Defaults to `{ themes: ['github-dark'] }`.
43
+ - `mdastPlugins` / `hastPlugins` — extra satteri plugins (e.g. a hero/demo
44
+ source injector). These use satteri's `defineMdastPlugin` / `defineHastPlugin`,
45
+ not remark/rehype.
46
+ - `publicDir` — for resolving `image:` frontmatter paths (default `./public`).
47
+
48
+ ## Code fence meta
49
+
50
+ Highlighting, file titles, and line markers use Expressive Code's meta syntax:
51
+
52
+ ```tsx title="app/routes/index.tsx" {2-4}
@@ -0,0 +1,11 @@
1
+ import * as runtime from "react/jsx-runtime";
2
+ function getMDXComponent(code, globals = {}) {
3
+ const fn = new Function(String(code));
4
+ const mod = fn({
5
+ ...runtime,
6
+ ...globals
7
+ });
8
+ return mod.default;
9
+ }
10
+ export { getMDXComponent };
11
+ //# sourceMappingURL=client.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["runtime","getMDXComponent","code","globals","fn","Function","String","mod","default"],"sources":["../src/client.ts"],"sourcesContent":[null],"mappings":"AAAA,YAAYA,OAAA,MAAa;AAWlB,SAASC,gBACdC,IAAA,EACAC,OAAA,GAAmC,CAAC,GACsB;EAE1D,MAAMC,EAAA,GAAK,IAAIC,QAAA,CAASC,MAAA,CAAOJ,IAAI,CAAC;EACpC,MAAMK,GAAA,GAAMH,EAAA,CAAG;IAAE,GAAGJ,OAAA;IAAS,GAAGG;EAAQ,CAAC;EACzC,OAAOI,GAAA,CAAIC,OAAA;AACb","ignoreList":[]}
@@ -0,0 +1,26 @@
1
+ import fs from "node:fs";
2
+ import glob from "fast-glob";
3
+ import matter from "gray-matter";
4
+ import readingTime from "reading-time";
5
+ import { getHeadings } from "./getHeadings.mjs";
6
+ import { notifyFileRead } from "./watchFile.mjs";
7
+ const getAllFrontmatter = fromPath => {
8
+ const paths = glob.sync(`${fromPath}/**/*.mdx`);
9
+ return paths.map(filePath => {
10
+ notifyFileRead(filePath);
11
+ const source = fs.readFileSync(filePath, "utf8");
12
+ const {
13
+ data,
14
+ content
15
+ } = matter(source);
16
+ const slug = filePath.replace(`${fromPath.replaceAll("\\", "/")}/`, "").replace(".mdx", "");
17
+ return {
18
+ ...data,
19
+ slug,
20
+ headings: getHeadings(source),
21
+ readingTime: readingTime(content)
22
+ };
23
+ }).sort((a, b) => Number(new Date(b.publishedAt || "")) - Number(new Date(a.publishedAt || "")));
24
+ };
25
+ export { getAllFrontmatter };
26
+ //# sourceMappingURL=getAllFrontmatter.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["fs","glob","matter","readingTime","getHeadings","notifyFileRead","getAllFrontmatter","fromPath","paths","sync","map","filePath","source","readFileSync","data","content","slug","replace","replaceAll","headings","sort","a","b","Number","Date","publishedAt"],"sources":["../src/getAllFrontmatter.ts"],"sourcesContent":[null],"mappings":"AAAA,OAAOA,EAAA,MAAQ;AAEf,OAAOC,IAAA,MAAU;AACjB,OAAOC,MAAA,MAAY;AACnB,OAAOC,WAAA,MAAiB;AACxB,SAASC,WAAA,QAAmB;AAE5B,SAASC,cAAA,QAAsB;AAIxB,MAAMC,iBAAA,GAAqBC,QAAA,IAAoC;EACpE,MAAMC,KAAA,GAAQP,IAAA,CAAKQ,IAAA,CAAK,GAAGF,QAAQ,WAAW;EAC9C,OAAOC,KAAA,CACJE,GAAA,CAAKC,QAAA,IAAa;IACjBN,cAAA,CAAeM,QAAQ;IACvB,MAAMC,MAAA,GAASZ,EAAA,CAAGa,YAAA,CAAaF,QAAA,EAAU,MAAM;IAC/C,MAAM;MAAEG,IAAA;MAAMC;IAAQ,IAAIb,MAAA,CAAOU,MAAM;IACvC,MAAMI,IAAA,GAAOL,QAAA,CACVM,OAAA,CAAQ,GAAGV,QAAA,CAASW,UAAA,CAAW,MAAM,GAAG,CAAC,KAAK,EAAE,EAChDD,OAAA,CAAQ,QAAQ,EAAE;IACrB,OAAO;MACL,GAAGH,IAAA;MACHE,IAAA;MACAG,QAAA,EAAUf,WAAA,CAAYQ,MAAM;MAC5BT,WAAA,EAAaA,WAAA,CAAYY,OAAO;IAClC;EACF,CAAC,EACAK,IAAA,CACC,CAACC,CAAA,EAAGC,CAAA,KACFC,MAAA,CAAO,IAAIC,IAAA,CAAKF,CAAA,CAAEG,WAAA,IAAe,EAAE,CAAC,IAAIF,MAAA,CAAO,IAAIC,IAAA,CAAKH,CAAA,CAAEI,WAAA,IAAe,EAAE,CAAC,CAChF;AACJ","ignoreList":[]}
@@ -0,0 +1,12 @@
1
+ import GithubSlugger from "github-slugger";
2
+ const getTitle = source => source.replace(/^#+\s+/, "").replace(/<.*>/, " ");
3
+ const getHeadings = source => {
4
+ const slugger = new GithubSlugger();
5
+ return source.split("\n").filter(x => x.startsWith("#")).map(x => ({
6
+ title: getTitle(x),
7
+ priority: x.trim().split(" ")[0].length,
8
+ id: slugger.slug(getTitle(x))
9
+ }));
10
+ };
11
+ export { getHeadings };
12
+ //# sourceMappingURL=getHeadings.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["GithubSlugger","getTitle","source","replace","getHeadings","slugger","split","filter","x","startsWith","map","title","priority","trim","length","id","slug"],"sources":["../src/getHeadings.ts"],"sourcesContent":[null],"mappings":"AAAA,OAAOA,aAAA,MAAmB;AAG1B,MAAMC,QAAA,GAAYC,MAAA,IAAmBA,MAAA,CAAOC,OAAA,CAAQ,UAAU,EAAE,EAAEA,OAAA,CAAQ,QAAQ,GAAG;AAI9E,MAAMC,WAAA,GAAeF,MAAA,IAA8B;EACxD,MAAMG,OAAA,GAAU,IAAIL,aAAA,CAAc;EAClC,OAAOE,MAAA,CACJI,KAAA,CAAM,IAAI,EACVC,MAAA,CAAQC,CAAA,IAAMA,CAAA,CAAEC,UAAA,CAAW,GAAG,CAAC,EAC/BC,GAAA,CAAKF,CAAA,KAAO;IACXG,KAAA,EAAOV,QAAA,CAASO,CAAC;IACjBI,QAAA,EAAUJ,CAAA,CAAEK,IAAA,CAAK,EAAEP,KAAA,CAAM,GAAG,EAAE,CAAC,EAAEQ,MAAA;IACjCC,EAAA,EAAIV,OAAA,CAAQW,IAAA,CAAKf,QAAA,CAASO,CAAC,CAAC;EAC9B,EAAE;AACN","ignoreList":[]}
@@ -0,0 +1,66 @@
1
+ import matter from "gray-matter";
2
+ import readingTime from "reading-time";
3
+ import { mdxToJs } from "satteri";
4
+ import expressiveCode from "satteri-expressive-code";
5
+ import { getHeadings } from "./getHeadings.mjs";
6
+ import { processImageMeta } from "./processImageMeta.mjs";
7
+ import { slugPlugin } from "./slugPlugin.mjs";
8
+ import { tableWhitespacePlugin } from "./tableWhitespacePlugin.mjs";
9
+ const DEFAULT_EC = {
10
+ themes: ["github-dark"]
11
+ };
12
+ const ecByKey = /* @__PURE__ */new Map();
13
+ function getExpressiveCode(opts) {
14
+ const key = (() => {
15
+ try {
16
+ return JSON.stringify(opts);
17
+ } catch {
18
+ return null;
19
+ }
20
+ })();
21
+ if (key == null) return expressiveCode(opts);
22
+ let plugin = ecByKey.get(key);
23
+ if (!plugin) {
24
+ plugin = expressiveCode(opts);
25
+ ecByKey.set(key, plugin);
26
+ }
27
+ return plugin;
28
+ }
29
+ async function getMDX(source, options = {}) {
30
+ const {
31
+ data,
32
+ content
33
+ } = matter(source);
34
+ const ec = options.expressiveCode === false ? [] : [getExpressiveCode(options.expressiveCode ?? DEFAULT_EC)];
35
+ const {
36
+ code
37
+ } = await mdxToJs(source, {
38
+ outputFormat: "function-body",
39
+ jsxImportSource: "react",
40
+ features: {
41
+ gfm: true,
42
+ smartPunctuation: true,
43
+ frontmatter: true
44
+ },
45
+ mdastPlugins: [...(options.mdastPlugins ?? [])],
46
+ hastPlugins: [slugPlugin, tableWhitespacePlugin, ...ec, ...(options.hastPlugins ?? [])]
47
+ });
48
+ let imageMeta;
49
+ if (data.image && typeof data.image === "string") {
50
+ const meta = await processImageMeta(data.image, {
51
+ publicDir: options.publicDir
52
+ });
53
+ if (meta) imageMeta = meta;
54
+ }
55
+ return {
56
+ frontmatter: {
57
+ ...data,
58
+ headings: getHeadings(source),
59
+ readingTime: readingTime(content),
60
+ imageMeta
61
+ },
62
+ code
63
+ };
64
+ }
65
+ export { getMDX };
66
+ //# sourceMappingURL=getMDX.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["matter","readingTime","mdxToJs","expressiveCode","getHeadings","processImageMeta","slugPlugin","tableWhitespacePlugin","DEFAULT_EC","themes","ecByKey","Map","getExpressiveCode","opts","key","JSON","stringify","plugin","get","set","getMDX","source","options","data","content","ec","code","outputFormat","jsxImportSource","features","gfm","smartPunctuation","frontmatter","mdastPlugins","hastPlugins","imageMeta","image","meta","publicDir","headings"],"sources":["../src/getMDX.ts"],"sourcesContent":[null],"mappings":"AAAA,OAAOA,MAAA,MAAY;AACnB,OAAOC,WAAA,MAAiB;AACxB,SAAsDC,OAAA,QAAe;AACrE,OAAOC,cAAA,MAEA;AACP,SAASC,WAAA,QAAmB;AAC5B,SAASC,gBAAA,QAAwB;AACjC,SAASC,UAAA,QAAkB;AAC3B,SAASC,qBAAA,QAA6B;AAkBtC,MAAMC,UAAA,GAA2C;EAAEC,MAAA,EAAQ,CAAC,aAAa;AAAE;AAK3E,MAAMC,OAAA,GAAU,mBAAIC,GAAA,CAA+C;AACnE,SAASC,kBAAkBC,IAAA,EAAoC;EAC7D,MAAMC,GAAA,IAAO,MAAM;IACjB,IAAI;MACF,OAAOC,IAAA,CAAKC,SAAA,CAAUH,IAAI;IAC5B,QAAQ;MACN,OAAO;IACT;EACF,GAAG;EACH,IAAIC,GAAA,IAAO,MAAM,OAAOX,cAAA,CAAeU,IAAI;EAC3C,IAAII,MAAA,GAASP,OAAA,CAAQQ,GAAA,CAAIJ,GAAG;EAC5B,IAAI,CAACG,MAAA,EAAQ;IACXA,MAAA,GAASd,cAAA,CAAeU,IAAI;IAC5BH,OAAA,CAAQS,GAAA,CAAIL,GAAA,EAAKG,MAAM;EACzB;EACA,OAAOA,MAAA;AACT;AAOA,eAAsBG,OACpBC,MAAA,EACAC,OAAA,GAAyB,CAAC,GAC2B;EACrD,MAAM;IAAEC,IAAA;IAAMC;EAAQ,IAAIxB,MAAA,CAAOqB,MAAM;EAEvC,MAAMI,EAAA,GACJH,OAAA,CAAQnB,cAAA,KAAmB,QACvB,EAAC,GACD,CAACS,iBAAA,CAAkBU,OAAA,CAAQnB,cAAA,IAAkBK,UAAU,CAAC;EAE9D,MAAM;IAAEkB;EAAK,IAAI,MAAMxB,OAAA,CAAQmB,MAAA,EAAQ;IACrCM,YAAA,EAAc;IACdC,eAAA,EAAiB;IACjBC,QAAA,EAAU;MAAEC,GAAA,EAAK;MAAMC,gBAAA,EAAkB;MAAMC,WAAA,EAAa;IAAK;IACjEC,YAAA,EAAc,CAAC,IAAIX,OAAA,CAAQW,YAAA,IAAgB,EAAG;IAC9CC,WAAA,EAAa,CAAC5B,UAAA,EAAYC,qBAAA,EAAuB,GAAGkB,EAAA,EAAI,IAAIH,OAAA,CAAQY,WAAA,IAAe,EAAG;EACxF,CAAC;EAED,IAAIC,SAAA;EACJ,IAAIZ,IAAA,CAAKa,KAAA,IAAS,OAAOb,IAAA,CAAKa,KAAA,KAAU,UAAU;IAChD,MAAMC,IAAA,GAAO,MAAMhC,gBAAA,CAAiBkB,IAAA,CAAKa,KAAA,EAAO;MAAEE,SAAA,EAAWhB,OAAA,CAAQgB;IAAU,CAAC;IAChF,IAAID,IAAA,EAAMF,SAAA,GAAYE,IAAA;EACxB;EAEA,OAAO;IACLL,WAAA,EAAa;MACX,GAAGT,IAAA;MACHgB,QAAA,EAAUnC,WAAA,CAAYiB,MAAM;MAC5BpB,WAAA,EAAaA,WAAA,CAAYuB,OAAO;MAChCW;IACF;IACAT;EACF;AACF","ignoreList":[]}
@@ -0,0 +1,22 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import compareVersions from "compare-versions";
4
+ import { getMDX } from "./getMDX.mjs";
5
+ import { notifyFileRead } from "./watchFile.mjs";
6
+ const getMDXBySlug = async (basePath, slug, options) => {
7
+ let mdxPath = slug;
8
+ if (!slug.includes(".") && basePath.includes("components")) {
9
+ const versions = getAllVersionsFromPath(path.join(basePath, slug));
10
+ mdxPath += `/${versions[0]}`;
11
+ }
12
+ const filePath = path.join(basePath, `${mdxPath}.mdx`);
13
+ notifyFileRead(filePath);
14
+ const source = fs.readFileSync(filePath, "utf8");
15
+ return getMDX(source, options);
16
+ };
17
+ function getAllVersionsFromPath(fromPath) {
18
+ if (!fs.existsSync(fromPath)) return [];
19
+ return fs.readdirSync(fromPath).map(fileName => fileName.replace(".mdx", "")).sort(compareVersions).reverse();
20
+ }
21
+ export { getAllVersionsFromPath, getMDXBySlug };
22
+ //# sourceMappingURL=getMDXBySlug.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["fs","path","compareVersions","getMDX","notifyFileRead","getMDXBySlug","basePath","slug","options","mdxPath","includes","versions","getAllVersionsFromPath","join","filePath","source","readFileSync","fromPath","existsSync","readdirSync","map","fileName","replace","sort","reverse"],"sources":["../src/getMDXBySlug.ts"],"sourcesContent":[null],"mappings":"AAAA,OAAOA,EAAA,MAAQ;AACf,OAAOC,IAAA,MAAU;AACjB,OAAOC,eAAA,MAAqB;AAC5B,SAA6BC,MAAA,QAAc;AAE3C,SAASC,cAAA,QAAsB;AAExB,MAAMC,YAAA,GAAe,MAAAA,CAC1BC,QAAA,EACAC,IAAA,EACAC,OAAA,KACwD;EACxD,IAAIC,OAAA,GAAUF,IAAA;EAGd,IAAI,CAACA,IAAA,CAAKG,QAAA,CAAS,GAAG,KAAKJ,QAAA,CAASI,QAAA,CAAS,YAAY,GAAG;IAC1D,MAAMC,QAAA,GAAWC,sBAAA,CAAuBX,IAAA,CAAKY,IAAA,CAAKP,QAAA,EAAUC,IAAI,CAAC;IACjEE,OAAA,IAAW,IAAIE,QAAA,CAAS,CAAC,CAAC;EAC5B;EAEA,MAAMG,QAAA,GAAWb,IAAA,CAAKY,IAAA,CAAKP,QAAA,EAAU,GAAGG,OAAO,MAAM;EACrDL,cAAA,CAAeU,QAAQ;EACvB,MAAMC,MAAA,GAASf,EAAA,CAAGgB,YAAA,CAAaF,QAAA,EAAU,MAAM;EAC/C,OAAOX,MAAA,CAAOY,MAAA,EAAQP,OAAO;AAC/B;AAEO,SAASI,uBAAuBK,QAAA,EAA4B;EACjE,IAAI,CAACjB,EAAA,CAAGkB,UAAA,CAAWD,QAAQ,GAAG,OAAO,EAAC;EACtC,OAAOjB,EAAA,CACJmB,WAAA,CAAYF,QAAQ,EACpBG,GAAA,CAAKC,QAAA,IAAaA,QAAA,CAASC,OAAA,CAAQ,QAAQ,EAAE,CAAC,EAC9CC,IAAA,CAAKrB,eAAe,EACpBsB,OAAA,CAAQ;AACb","ignoreList":[]}
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ import { getMDX } from "./getMDX.mjs";
2
+ import { getAllVersionsFromPath, getMDXBySlug } from "./getMDXBySlug.mjs";
3
+ import { getAllFrontmatter } from "./getAllFrontmatter.mjs";
4
+ import { getHeadings } from "./getHeadings.mjs";
5
+ import { slugPlugin } from "./slugPlugin.mjs";
6
+ import { tableWhitespacePlugin } from "./tableWhitespacePlugin.mjs";
7
+ import { processImageMeta } from "./processImageMeta.mjs";
8
+ import { notifyFileRead } from "./watchFile.mjs";
9
+ export { getAllFrontmatter, getAllVersionsFromPath, getHeadings, getMDX, getMDXBySlug, notifyFileRead, processImageMeta, slugPlugin, tableWhitespacePlugin };
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["getMDX","getAllVersionsFromPath","getMDXBySlug","getAllFrontmatter","getHeadings","slugPlugin","tableWhitespacePlugin","processImageMeta","notifyFileRead"],"sources":["../src/index.ts"],"sourcesContent":[null],"mappings":"AAAA,SAASA,MAAA,QAAkC;AAC3C,SAASC,sBAAA,EAAwBC,YAAA,QAAoB;AACrD,SAASC,iBAAA,QAAyB;AAClC,SAASC,WAAA,QAAmB;AAC5B,SAASC,UAAA,QAAkB;AAC3B,SAASC,qBAAA,QAA6B;AACtC,SAASC,gBAAA,QAAwB;AACjC,SAASC,cAAA,QAAsB","ignoreList":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,10 @@
1
+ import { getMDX } from "./getMDX.mjs";
2
+ import { getAllVersionsFromPath, getMDXBySlug } from "./getMDXBySlug.mjs";
3
+ import { getAllFrontmatter } from "./getAllFrontmatter.mjs";
4
+ import { getHeadings } from "./getHeadings.mjs";
5
+ import { slugPlugin } from "./slugPlugin.mjs";
6
+ import { tableWhitespacePlugin } from "./tableWhitespacePlugin.mjs";
7
+ import { processImageMeta } from "./processImageMeta.mjs";
8
+ import { notifyFileRead } from "./watchFile.mjs";
9
+ export { getAllFrontmatter, getAllVersionsFromPath, getHeadings, getMDX, getMDXBySlug, notifyFileRead, processImageMeta, slugPlugin, tableWhitespacePlugin };
10
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["getMDX","getAllVersionsFromPath","getMDXBySlug","getAllFrontmatter","getHeadings","slugPlugin","tableWhitespacePlugin","processImageMeta","notifyFileRead"],"sources":["../src/index.ts"],"sourcesContent":[null],"mappings":"AAAA,SAASA,MAAA,QAAkC;AAC3C,SAASC,sBAAA,EAAwBC,YAAA,QAAoB;AACrD,SAASC,iBAAA,QAAyB;AAClC,SAASC,WAAA,QAAmB;AAC5B,SAASC,UAAA,QAAkB;AAC3B,SAASC,qBAAA,QAA6B;AACtC,SAASC,gBAAA,QAAwB;AACjC,SAASC,cAAA,QAAsB","ignoreList":[]}
@@ -0,0 +1,37 @@
1
+ import { existsSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ async function getSharp() {
4
+ try {
5
+ const sharpModule = await import("sharp");
6
+ return sharpModule.default || sharpModule;
7
+ } catch {
8
+ return null;
9
+ }
10
+ }
11
+ async function processImageMeta(imagePath, opts) {
12
+ const publicDir = opts?.publicDir ?? "./public";
13
+ const filePath = imagePath.startsWith("/") ? resolve(publicDir, imagePath.slice(1)) : resolve(imagePath);
14
+ if (!existsSync(filePath)) return null;
15
+ const sharp = await getSharp();
16
+ if (!sharp) return null;
17
+ try {
18
+ const image = sharp(filePath);
19
+ const {
20
+ width = 0,
21
+ height = 0
22
+ } = await image.metadata();
23
+ const blurBuffer = await image.resize(10).blur().jpeg({
24
+ quality: 40
25
+ }).toBuffer();
26
+ const blurDataURL = `data:image/jpeg;base64,${blurBuffer.toString("base64")}`;
27
+ return {
28
+ width,
29
+ height,
30
+ blurDataURL
31
+ };
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+ export { processImageMeta };
37
+ //# sourceMappingURL=processImageMeta.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["existsSync","resolve","getSharp","sharpModule","default","processImageMeta","imagePath","opts","publicDir","filePath","startsWith","slice","sharp","image","width","height","metadata","blurBuffer","resize","blur","jpeg","quality","toBuffer","blurDataURL","toString"],"sources":["../src/processImageMeta.ts"],"sourcesContent":[null],"mappings":"AAAA,SAASA,UAAA,QAAkB;AAC3B,SAASC,OAAA,QAAe;AAGxB,eAAeC,SAAA,EAAmD;EAChE,IAAI;IACF,MAAMC,WAAA,GAAc,MAAM,OAAO,OAAO;IACxC,OAAQA,WAAA,CAAoBC,OAAA,IAAWD,WAAA;EACzC,QAAQ;IACN,OAAO;EACT;AACF;AAIA,eAAsBE,iBACpBC,SAAA,EACAC,IAAA,EAC2B;EAC3B,MAAMC,SAAA,GAAYD,IAAA,EAAMC,SAAA,IAAa;EACrC,MAAMC,QAAA,GAAWH,SAAA,CAAUI,UAAA,CAAW,GAAG,IACrCT,OAAA,CAAQO,SAAA,EAAWF,SAAA,CAAUK,KAAA,CAAM,CAAC,CAAC,IACrCV,OAAA,CAAQK,SAAS;EAErB,IAAI,CAACN,UAAA,CAAWS,QAAQ,GAAG,OAAO;EAClC,MAAMG,KAAA,GAAQ,MAAMV,QAAA,CAAS;EAC7B,IAAI,CAACU,KAAA,EAAO,OAAO;EAEnB,IAAI;IACF,MAAMC,KAAA,GAAQD,KAAA,CAAMH,QAAQ;IAC5B,MAAM;MAAEK,KAAA,GAAQ;MAAGC,MAAA,GAAS;IAAE,IAAI,MAAMF,KAAA,CAAMG,QAAA,CAAS;IACvD,MAAMC,UAAA,GAAa,MAAMJ,KAAA,CAAMK,MAAA,CAAO,EAAE,EAAEC,IAAA,CAAK,EAAEC,IAAA,CAAK;MAAEC,OAAA,EAAS;IAAG,CAAC,EAAEC,QAAA,CAAS;IAChF,MAAMC,WAAA,GAAc,0BAA0BN,UAAA,CAAWO,QAAA,CAAS,QAAQ,CAAC;IAC3E,OAAO;MAAEV,KAAA;MAAOC,MAAA;MAAQQ;IAAY;EACtC,QAAQ;IACN,OAAO;EACT;AACF","ignoreList":[]}
@@ -0,0 +1,29 @@
1
+ import GithubSlugger from "github-slugger";
2
+ import { defineHastPlugin } from "satteri";
3
+ function textOf(node) {
4
+ if (node.type === "text") return node.value || "";
5
+ if (node.children) return node.children.map(textOf).join("");
6
+ return "";
7
+ }
8
+ const slugPlugin = () => {
9
+ const slugger = new GithubSlugger();
10
+ return defineHastPlugin({
11
+ name: "vxrn-heading-slugs",
12
+ element: {
13
+ filter: ["h1", "h2", "h3", "h4", "h5", "h6"],
14
+ visit(node, ctx) {
15
+ if (node.properties?.id) return;
16
+ const id = slugger.slug(textOf(node));
17
+ ctx.replaceNode(node, {
18
+ ...node,
19
+ properties: {
20
+ ...node.properties,
21
+ id
22
+ }
23
+ });
24
+ }
25
+ }
26
+ });
27
+ };
28
+ export { slugPlugin };
29
+ //# sourceMappingURL=slugPlugin.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["GithubSlugger","defineHastPlugin","textOf","node","type","value","children","map","join","slugPlugin","slugger","name","element","filter","visit","ctx","properties","id","slug","replaceNode"],"sources":["../src/slugPlugin.ts"],"sourcesContent":[null],"mappings":"AAAA,OAAOA,aAAA,MAAmB;AAC1B,SAASC,gBAAA,QAAwB;AAEjC,SAASC,OAAOC,IAAA,EAAmB;EACjC,IAAIA,IAAA,CAAKC,IAAA,KAAS,QAAQ,OAAOD,IAAA,CAAKE,KAAA,IAAS;EAC/C,IAAIF,IAAA,CAAKG,QAAA,EAAU,OAAOH,IAAA,CAAKG,QAAA,CAASC,GAAA,CAAIL,MAAM,EAAEM,IAAA,CAAK,EAAE;EAC3D,OAAO;AACT;AAIO,MAAMC,UAAA,GAAaA,CAAA,KAAM;EAC9B,MAAMC,OAAA,GAAU,IAAIV,aAAA,CAAc;EAClC,OAAOC,gBAAA,CAAiB;IACtBU,IAAA,EAAM;IACNC,OAAA,EAAS;MACPC,MAAA,EAAQ,CAAC,MAAM,MAAM,MAAM,MAAM,MAAM,IAAI;MAC3CC,MAAMX,IAAA,EAAWY,GAAA,EAAU;QACzB,IAAIZ,IAAA,CAAKa,UAAA,EAAYC,EAAA,EAAI;QACzB,MAAMA,EAAA,GAAKP,OAAA,CAAQQ,IAAA,CAAKhB,MAAA,CAAOC,IAAI,CAAC;QACpCY,GAAA,CAAII,WAAA,CAAYhB,IAAA,EAAM;UACpB,GAAGA,IAAA;UACHa,UAAA,EAAY;YAAE,GAAGb,IAAA,CAAKa,UAAA;YAAYC;UAAG;QACvC,CAAC;MACH;IACF;EACF,CAAC;AACH","ignoreList":[]}
@@ -0,0 +1,19 @@
1
+ import { defineHastPlugin } from "satteri";
2
+ const tableWhitespacePlugin = defineHastPlugin({
3
+ name: "vxrn-strip-table-whitespace",
4
+ element: {
5
+ filter: ["table", "thead", "tbody", "tr"],
6
+ visit(node, ctx) {
7
+ const children = node.children || [];
8
+ const cleaned = children.filter(c => !(c.type === "text" && typeof c.value === "string" && c.value.trim() === ""));
9
+ if (cleaned.length !== children.length) {
10
+ ctx.replaceNode(node, {
11
+ ...node,
12
+ children: cleaned
13
+ });
14
+ }
15
+ }
16
+ }
17
+ });
18
+ export { tableWhitespacePlugin };
19
+ //# sourceMappingURL=tableWhitespacePlugin.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["defineHastPlugin","tableWhitespacePlugin","name","element","filter","visit","node","ctx","children","cleaned","c","type","value","trim","length","replaceNode"],"sources":["../src/tableWhitespacePlugin.ts"],"sourcesContent":[null],"mappings":"AAAA,SAASA,gBAAA,QAAwB;AAO1B,MAAMC,qBAAA,GAAwBD,gBAAA,CAAiB;EACpDE,IAAA,EAAM;EACNC,OAAA,EAAS;IACPC,MAAA,EAAQ,CAAC,SAAS,SAAS,SAAS,IAAI;IACxCC,MAAMC,IAAA,EAAWC,GAAA,EAAU;MACzB,MAAMC,QAAA,GAAWF,IAAA,CAAKE,QAAA,IAAY,EAAC;MACnC,MAAMC,OAAA,GAAUD,QAAA,CAASJ,MAAA,CACtBM,CAAA,IACC,EAAEA,CAAA,CAAEC,IAAA,KAAS,UAAU,OAAOD,CAAA,CAAEE,KAAA,KAAU,YAAYF,CAAA,CAAEE,KAAA,CAAMC,IAAA,CAAK,MAAM,GAC7E;MACA,IAAIJ,OAAA,CAAQK,MAAA,KAAWN,QAAA,CAASM,MAAA,EAAQ;QACtCP,GAAA,CAAIQ,WAAA,CAAYT,IAAA,EAAM;UAAE,GAAGA,IAAA;UAAME,QAAA,EAAUC;QAAQ,CAAC;MACtD;IACF;EACF;AACF,CAAC","ignoreList":[]}
package/dist/types.mjs ADDED
@@ -0,0 +1,2 @@
1
+
2
+ //# sourceMappingURL=types.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sources":[],"sourcesContent":[],"mappings":"","ignoreList":[]}
@@ -0,0 +1,7 @@
1
+ const WATCH_FILE_KEY = "__oneWatchFile";
2
+ function notifyFileRead(filePath) {
3
+ const watchFile = globalThis[WATCH_FILE_KEY];
4
+ if (watchFile) watchFile(filePath);
5
+ }
6
+ export { notifyFileRead };
7
+ //# sourceMappingURL=watchFile.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["WATCH_FILE_KEY","notifyFileRead","filePath","watchFile","globalThis"],"sources":["../src/watchFile.ts"],"sourcesContent":[null],"mappings":"AAAA,MAAMA,cAAA,GAAiB;AAMhB,SAASC,eAAeC,QAAA,EAAwB;EACrD,MAAMC,SAAA,GAAaC,UAAA,CAAmBJ,cAAc;EAGpD,IAAIG,SAAA,EAAWA,SAAA,CAAUD,QAAQ;AACnC","ignoreList":[]}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@vxrn/mdx-rust",
3
+ "version": "1.20.3",
4
+ "type": "module",
5
+ "sideEffects": false,
6
+ "module": "dist",
7
+ "types": "./types/index.d.ts",
8
+ "exports": {
9
+ "./package.json": "./package.json",
10
+ ".": {
11
+ "types": "./types/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "default": "./dist/index.mjs"
14
+ },
15
+ "./client": {
16
+ "types": "./types/client.d.ts",
17
+ "import": "./dist/client.mjs",
18
+ "default": "./dist/client.mjs"
19
+ }
20
+ },
21
+ "files": [
22
+ "src",
23
+ "types",
24
+ "dist"
25
+ ],
26
+ "scripts": {
27
+ "build": "tamagui-build --skip-native",
28
+ "clean": "tamagui-build clean",
29
+ "clean:build": "tamagui-build clean:build",
30
+ "lint": "biome check src",
31
+ "lint:fix": "biome check --write src",
32
+ "typecheck": "tsc --noEmit",
33
+ "watch": "bun run build --watch"
34
+ },
35
+ "optionalDependencies": {
36
+ "sharp": "^0.34.5"
37
+ },
38
+ "dependencies": {
39
+ "compare-versions": "^4.1.3",
40
+ "fast-glob": "^3.3.3",
41
+ "github-slugger": "^2.0.0",
42
+ "gray-matter": "^4.0.3",
43
+ "reading-time": "1.3.0",
44
+ "satteri": "^0.9.4",
45
+ "satteri-expressive-code": "^0.1.17"
46
+ },
47
+ "peerDependencies": {
48
+ "react": "*"
49
+ },
50
+ "devDependencies": {
51
+ "@tamagui/build": "2.3.0",
52
+ "react": "^19.0.0"
53
+ },
54
+ "publishConfig": {
55
+ "access": "public"
56
+ }
57
+ }
package/src/client.ts ADDED
@@ -0,0 +1,20 @@
1
+ import * as runtime from 'react/jsx-runtime'
2
+
3
+ /**
4
+ * Evaluate the `code` returned by `getMDX` into a React component.
5
+ *
6
+ * satteri compiles MDX to a `function-body` module that reads its JSX runtime
7
+ * from `arguments[0]` and returns `{ default, ...exports }`. Render the result
8
+ * with a `components` prop: `<Component components={components} />`.
9
+ *
10
+ * Browser-safe: imports only React, never satteri's native binding.
11
+ */
12
+ export function getMDXComponent(
13
+ code: string,
14
+ globals: Record<string, unknown> = {}
15
+ ): (props: { components?: Record<string, unknown> }) => any {
16
+ // eslint-disable-next-line no-new-func
17
+ const fn = new Function(String(code))
18
+ const mod = fn({ ...runtime, ...globals })
19
+ return mod.default
20
+ }
@@ -0,0 +1,33 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import glob from 'fast-glob'
4
+ import matter from 'gray-matter'
5
+ import readingTime from 'reading-time'
6
+ import { getHeadings } from './getHeadings'
7
+ import type { Frontmatter } from './types'
8
+ import { notifyFileRead } from './watchFile'
9
+
10
+ // frontmatter for every mdx file under `fromPath`, newest first. reads only the
11
+ // frontmatter, so it stays cheap over hundreds of files.
12
+ export const getAllFrontmatter = (fromPath: string): Frontmatter[] => {
13
+ const paths = glob.sync(`${fromPath}/**/*.mdx`)
14
+ return paths
15
+ .map((filePath) => {
16
+ notifyFileRead(filePath)
17
+ const source = fs.readFileSync(filePath, 'utf8')
18
+ const { data, content } = matter(source)
19
+ const slug = filePath
20
+ .replace(`${fromPath.replaceAll('\\', '/')}/`, '')
21
+ .replace('.mdx', '')
22
+ return {
23
+ ...data,
24
+ slug,
25
+ headings: getHeadings(source),
26
+ readingTime: readingTime(content),
27
+ } as Frontmatter
28
+ })
29
+ .sort(
30
+ (a, b) =>
31
+ Number(new Date(b.publishedAt || '')) - Number(new Date(a.publishedAt || ''))
32
+ )
33
+ }
@@ -0,0 +1,18 @@
1
+ import GithubSlugger from 'github-slugger'
2
+ import type { Heading } from './types'
3
+
4
+ const getTitle = (source: string) => source.replace(/^#+\s+/, '').replace(/<.*>/, ' ')
5
+
6
+ // extract headings for a table of contents. ids match the slugs added by
7
+ // slugPlugin during compilation, so anchor links resolve.
8
+ export const getHeadings = (source: string): Heading[] => {
9
+ const slugger = new GithubSlugger()
10
+ return source
11
+ .split('\n')
12
+ .filter((x) => x.startsWith('#'))
13
+ .map((x) => ({
14
+ title: getTitle(x),
15
+ priority: x.trim().split(' ')[0].length,
16
+ id: slugger.slug(getTitle(x)),
17
+ }))
18
+ }
package/src/getMDX.ts ADDED
@@ -0,0 +1,90 @@
1
+ import matter from 'gray-matter'
2
+ import readingTime from 'reading-time'
3
+ import { type HastPluginInput, type MdastPluginInput, mdxToJs } from 'satteri'
4
+ import expressiveCode, {
5
+ type SatteriExpressiveCodeOptions,
6
+ } from 'satteri-expressive-code'
7
+ import { getHeadings } from './getHeadings'
8
+ import { processImageMeta } from './processImageMeta'
9
+ import { slugPlugin } from './slugPlugin'
10
+ import { tableWhitespacePlugin } from './tableWhitespacePlugin'
11
+ import type { Frontmatter, ImageMeta } from './types'
12
+
13
+ export type GetMDXOptions = {
14
+ /** public directory for resolving image paths that start with `/` (default: ./public) */
15
+ publicDir?: string
16
+ /**
17
+ * Expressive Code (Shiki) options: themes, frames, styleOverrides, plugins.
18
+ * Pass `false` to skip syntax highlighting entirely. Defaults to the
19
+ * `github-dark` theme.
20
+ */
21
+ expressiveCode?: SatteriExpressiveCodeOptions | false
22
+ /** extra satteri mdast plugins, e.g. a hero/demo source injector */
23
+ mdastPlugins?: MdastPluginInput[]
24
+ /** extra satteri hast plugins */
25
+ hastPlugins?: HastPluginInput[]
26
+ }
27
+
28
+ const DEFAULT_EC: SatteriExpressiveCodeOptions = { themes: ['github-dark'] }
29
+
30
+ // the Expressive Code plugin caches its Shiki renderer (themes + grammars) for
31
+ // its lifetime, so we reuse one instance per config across every getMDX call
32
+ // instead of paying the warmup on every file.
33
+ const ecByKey = new Map<string, ReturnType<typeof expressiveCode>>()
34
+ function getExpressiveCode(opts: SatteriExpressiveCodeOptions) {
35
+ const key = (() => {
36
+ try {
37
+ return JSON.stringify(opts)
38
+ } catch {
39
+ return null
40
+ }
41
+ })()
42
+ if (key == null) return expressiveCode(opts)
43
+ let plugin = ecByKey.get(key)
44
+ if (!plugin) {
45
+ plugin = expressiveCode(opts)
46
+ ecByKey.set(key, plugin)
47
+ }
48
+ return plugin
49
+ }
50
+
51
+ /**
52
+ * Compile an MDX string to an evaluatable component module using satteri (Rust)
53
+ * for parsing/compilation and Expressive Code (Shiki) for code highlighting.
54
+ * Evaluate the returned `code` with `getMDXComponent` from `@vxrn/mdx-rust/client`.
55
+ */
56
+ export async function getMDX(
57
+ source: string,
58
+ options: GetMDXOptions = {}
59
+ ): Promise<{ frontmatter: Frontmatter; code: string }> {
60
+ const { data, content } = matter(source)
61
+
62
+ const ec =
63
+ options.expressiveCode === false
64
+ ? []
65
+ : [getExpressiveCode(options.expressiveCode ?? DEFAULT_EC)]
66
+
67
+ const { code } = await mdxToJs(source, {
68
+ outputFormat: 'function-body',
69
+ jsxImportSource: 'react',
70
+ features: { gfm: true, smartPunctuation: true, frontmatter: true },
71
+ mdastPlugins: [...(options.mdastPlugins ?? [])],
72
+ hastPlugins: [slugPlugin, tableWhitespacePlugin, ...ec, ...(options.hastPlugins ?? [])],
73
+ })
74
+
75
+ let imageMeta: ImageMeta | undefined
76
+ if (data.image && typeof data.image === 'string') {
77
+ const meta = await processImageMeta(data.image, { publicDir: options.publicDir })
78
+ if (meta) imageMeta = meta
79
+ }
80
+
81
+ return {
82
+ frontmatter: {
83
+ ...data,
84
+ headings: getHeadings(source),
85
+ readingTime: readingTime(content),
86
+ imageMeta,
87
+ } as Frontmatter,
88
+ code,
89
+ }
90
+ }
@@ -0,0 +1,34 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import compareVersions from 'compare-versions'
4
+ import { type GetMDXOptions, getMDX } from './getMDX'
5
+ import type { Frontmatter } from './types'
6
+ import { notifyFileRead } from './watchFile'
7
+
8
+ export const getMDXBySlug = async (
9
+ basePath: string,
10
+ slug: string,
11
+ options?: GetMDXOptions
12
+ ): Promise<{ frontmatter: Frontmatter; code: string }> => {
13
+ let mdxPath = slug
14
+
15
+ // if no version given, resolve to the latest
16
+ if (!slug.includes('.') && basePath.includes('components')) {
17
+ const versions = getAllVersionsFromPath(path.join(basePath, slug))
18
+ mdxPath += `/${versions[0]}`
19
+ }
20
+
21
+ const filePath = path.join(basePath, `${mdxPath}.mdx`)
22
+ notifyFileRead(filePath)
23
+ const source = fs.readFileSync(filePath, 'utf8')
24
+ return getMDX(source, options)
25
+ }
26
+
27
+ export function getAllVersionsFromPath(fromPath: string): string[] {
28
+ if (!fs.existsSync(fromPath)) return []
29
+ return fs
30
+ .readdirSync(fromPath)
31
+ .map((fileName) => fileName.replace('.mdx', ''))
32
+ .sort(compareVersions)
33
+ .reverse()
34
+ }
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ export { getMDX, type GetMDXOptions } from './getMDX'
2
+ export { getAllVersionsFromPath, getMDXBySlug } from './getMDXBySlug'
3
+ export { getAllFrontmatter } from './getAllFrontmatter'
4
+ export { getHeadings } from './getHeadings'
5
+ export { slugPlugin } from './slugPlugin'
6
+ export { tableWhitespacePlugin } from './tableWhitespacePlugin'
7
+ export { processImageMeta } from './processImageMeta'
8
+ export { notifyFileRead } from './watchFile'
9
+ export type { Frontmatter, Heading, ImageMeta } from './types'
@@ -0,0 +1,38 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { resolve } from 'node:path'
3
+ import type { ImageMeta } from './types'
4
+
5
+ async function getSharp(): Promise<typeof import('sharp') | null> {
6
+ try {
7
+ const sharpModule = await import('sharp')
8
+ return (sharpModule as any).default || sharpModule
9
+ } catch {
10
+ return null
11
+ }
12
+ }
13
+
14
+ /** Read an image's dimensions and a tiny blur placeholder. Null if sharp is
15
+ * not installed or the file is missing. */
16
+ export async function processImageMeta(
17
+ imagePath: string,
18
+ opts?: { publicDir?: string }
19
+ ): Promise<ImageMeta | null> {
20
+ const publicDir = opts?.publicDir ?? './public'
21
+ const filePath = imagePath.startsWith('/')
22
+ ? resolve(publicDir, imagePath.slice(1))
23
+ : resolve(imagePath)
24
+
25
+ if (!existsSync(filePath)) return null
26
+ const sharp = await getSharp()
27
+ if (!sharp) return null
28
+
29
+ try {
30
+ const image = sharp(filePath)
31
+ const { width = 0, height = 0 } = await image.metadata()
32
+ const blurBuffer = await image.resize(10).blur().jpeg({ quality: 40 }).toBuffer()
33
+ const blurDataURL = `data:image/jpeg;base64,${blurBuffer.toString('base64')}`
34
+ return { width, height, blurDataURL }
35
+ } catch {
36
+ return null
37
+ }
38
+ }
@@ -0,0 +1,28 @@
1
+ import GithubSlugger from 'github-slugger'
2
+ import { defineHastPlugin } from 'satteri'
3
+
4
+ function textOf(node: any): string {
5
+ if (node.type === 'text') return node.value || ''
6
+ if (node.children) return node.children.map(textOf).join('')
7
+ return ''
8
+ }
9
+
10
+ // adds github-style id slugs to headings so table-of-contents anchors resolve.
11
+ // used as a factory so the slugger's dedupe counter resets per document.
12
+ export const slugPlugin = () => {
13
+ const slugger = new GithubSlugger()
14
+ return defineHastPlugin({
15
+ name: 'vxrn-heading-slugs',
16
+ element: {
17
+ filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
18
+ visit(node: any, ctx: any) {
19
+ if (node.properties?.id) return
20
+ const id = slugger.slug(textOf(node))
21
+ ctx.replaceNode(node, {
22
+ ...node,
23
+ properties: { ...node.properties, id },
24
+ })
25
+ },
26
+ },
27
+ })
28
+ }
@@ -0,0 +1,23 @@
1
+ import { defineHastPlugin } from 'satteri'
2
+
3
+ // satteri (via pulldown-cmark) keeps the newline text nodes between table
4
+ // elements; they become stray children of the table/row nodes, which trips
5
+ // React's "whitespace text nodes cannot be a child of <table>" hydration
6
+ // warning and can spawn anonymous table cells. strip whitespace-only children
7
+ // from table structural elements, matching remark-rehype's clean output.
8
+ export const tableWhitespacePlugin = defineHastPlugin({
9
+ name: 'vxrn-strip-table-whitespace',
10
+ element: {
11
+ filter: ['table', 'thead', 'tbody', 'tr'],
12
+ visit(node: any, ctx: any) {
13
+ const children = node.children || []
14
+ const cleaned = children.filter(
15
+ (c: any) =>
16
+ !(c.type === 'text' && typeof c.value === 'string' && c.value.trim() === '')
17
+ )
18
+ if (cleaned.length !== children.length) {
19
+ ctx.replaceNode(node, { ...node, children: cleaned })
20
+ }
21
+ },
22
+ },
23
+ })
package/src/types.ts ADDED
@@ -0,0 +1,30 @@
1
+ export type ImageMeta = {
2
+ width: number
3
+ height: number
4
+ blurDataURL: string
5
+ }
6
+
7
+ export type Heading = { title: string; priority: number; id: string }
8
+
9
+ export type Frontmatter = {
10
+ title: string
11
+ headings?: Heading[]
12
+ description?: string
13
+ name?: string
14
+ versions?: string[]
15
+ version?: string
16
+ by?: string
17
+ publishedAt?: string
18
+ draft?: boolean
19
+ relatedIds?: string[]
20
+ type?: 'changelog' | string
21
+ readingTime?: { text: string; minutes: number; time: number; words: number }
22
+ poster?: string
23
+ slug: string
24
+ image?: string
25
+ /** image dimensions and blur placeholder, populated if `image` exists and sharp is installed */
26
+ imageMeta?: ImageMeta
27
+ component?: string
28
+ package?: string
29
+ demoName?: string
30
+ }
@@ -0,0 +1,12 @@
1
+ const WATCH_FILE_KEY = '__oneWatchFile'
2
+
3
+ /**
4
+ * Notifies One's HMR system about a file dependency so the page hot-reloads
5
+ * when the file changes. No-op when One is not present or in production.
6
+ */
7
+ export function notifyFileRead(filePath: string): void {
8
+ const watchFile = (globalThis as any)[WATCH_FILE_KEY] as
9
+ | ((path: string) => void)
10
+ | undefined
11
+ if (watchFile) watchFile(filePath)
12
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Evaluate the `code` returned by `getMDX` into a React component.
3
+ *
4
+ * satteri compiles MDX to a `function-body` module that reads its JSX runtime
5
+ * from `arguments[0]` and returns `{ default, ...exports }`. Render the result
6
+ * with a `components` prop: `<Component components={components} />`.
7
+ *
8
+ * Browser-safe: imports only React, never satteri's native binding.
9
+ */
10
+ export declare function getMDXComponent(code: string, globals?: Record<string, unknown>): (props: {
11
+ components?: Record<string, unknown>;
12
+ }) => any;
13
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACpC,CAAC,KAAK,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,KAAK,GAAG,CAK1D"}
@@ -0,0 +1,3 @@
1
+ import type { Frontmatter } from './types';
2
+ export declare const getAllFrontmatter: (fromPath: string) => Frontmatter[];
3
+ //# sourceMappingURL=getAllFrontmatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getAllFrontmatter.d.ts","sourceRoot":"","sources":["../src/getAllFrontmatter.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAK1C,eAAO,MAAM,iBAAiB,GAAI,UAAU,MAAM,KAAG,WAAW,EAqB/D,CAAA"}
@@ -0,0 +1,3 @@
1
+ import type { Heading } from './types';
2
+ export declare const getHeadings: (source: string) => Heading[];
3
+ //# sourceMappingURL=getHeadings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getHeadings.d.ts","sourceRoot":"","sources":["../src/getHeadings.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAMtC,eAAO,MAAM,WAAW,GAAI,QAAQ,MAAM,KAAG,OAAO,EAUnD,CAAA"}
@@ -0,0 +1,27 @@
1
+ import { type HastPluginInput, type MdastPluginInput } from 'satteri';
2
+ import { type SatteriExpressiveCodeOptions } from 'satteri-expressive-code';
3
+ import type { Frontmatter } from './types';
4
+ export type GetMDXOptions = {
5
+ /** public directory for resolving image paths that start with `/` (default: ./public) */
6
+ publicDir?: string;
7
+ /**
8
+ * Expressive Code (Shiki) options: themes, frames, styleOverrides, plugins.
9
+ * Pass `false` to skip syntax highlighting entirely. Defaults to the
10
+ * `github-dark` theme.
11
+ */
12
+ expressiveCode?: SatteriExpressiveCodeOptions | false;
13
+ /** extra satteri mdast plugins, e.g. a hero/demo source injector */
14
+ mdastPlugins?: MdastPluginInput[];
15
+ /** extra satteri hast plugins */
16
+ hastPlugins?: HastPluginInput[];
17
+ };
18
+ /**
19
+ * Compile an MDX string to an evaluatable component module using satteri (Rust)
20
+ * for parsing/compilation and Expressive Code (Shiki) for code highlighting.
21
+ * Evaluate the returned `code` with `getMDXComponent` from `@vxrn/mdx-rust/client`.
22
+ */
23
+ export declare function getMDX(source: string, options?: GetMDXOptions): Promise<{
24
+ frontmatter: Frontmatter;
25
+ code: string;
26
+ }>;
27
+ //# sourceMappingURL=getMDX.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getMDX.d.ts","sourceRoot":"","sources":["../src/getMDX.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,gBAAgB,EAAW,MAAM,SAAS,CAAA;AAC9E,OAAuB,EACrB,KAAK,4BAA4B,EAClC,MAAM,yBAAyB,CAAA;AAKhC,OAAO,KAAK,EAAE,WAAW,EAAa,MAAM,SAAS,CAAA;AAErD,MAAM,MAAM,aAAa,GAAG;IAC1B,yFAAyF;IACzF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,cAAc,CAAC,EAAE,4BAA4B,GAAG,KAAK,CAAA;IACrD,oEAAoE;IACpE,YAAY,CAAC,EAAE,gBAAgB,EAAE,CAAA;IACjC,iCAAiC;IACjC,WAAW,CAAC,EAAE,eAAe,EAAE,CAAA;CAChC,CAAA;AAyBD;;;;GAIG;AACH,wBAAsB,MAAM,CAC1B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC;IAAE,WAAW,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CA+BrD"}
@@ -0,0 +1,8 @@
1
+ import { type GetMDXOptions } from './getMDX';
2
+ import type { Frontmatter } from './types';
3
+ export declare const getMDXBySlug: (basePath: string, slug: string, options?: GetMDXOptions) => Promise<{
4
+ frontmatter: Frontmatter;
5
+ code: string;
6
+ }>;
7
+ export declare function getAllVersionsFromPath(fromPath: string): string[];
8
+ //# sourceMappingURL=getMDXBySlug.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getMDXBySlug.d.ts","sourceRoot":"","sources":["../src/getMDXBySlug.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,aAAa,EAAU,MAAM,UAAU,CAAA;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAG1C,eAAO,MAAM,YAAY,GACvB,UAAU,MAAM,EAChB,MAAM,MAAM,EACZ,UAAU,aAAa,KACtB,OAAO,CAAC;IAAE,WAAW,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAapD,CAAA;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAOjE"}
@@ -0,0 +1,10 @@
1
+ export { getMDX, type GetMDXOptions } from './getMDX';
2
+ export { getAllVersionsFromPath, getMDXBySlug } from './getMDXBySlug';
3
+ export { getAllFrontmatter } from './getAllFrontmatter';
4
+ export { getHeadings } from './getHeadings';
5
+ export { slugPlugin } from './slugPlugin';
6
+ export { tableWhitespacePlugin } from './tableWhitespacePlugin';
7
+ export { processImageMeta } from './processImageMeta';
8
+ export { notifyFileRead } from './watchFile';
9
+ export type { Frontmatter, Heading, ImageMeta } from './types';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAA;AACrD,OAAO,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAA;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA"}
@@ -0,0 +1,7 @@
1
+ import type { ImageMeta } from './types';
2
+ /** Read an image's dimensions and a tiny blur placeholder. Null if sharp is
3
+ * not installed or the file is missing. */
4
+ export declare function processImageMeta(imagePath: string, opts?: {
5
+ publicDir?: string;
6
+ }): Promise<ImageMeta | null>;
7
+ //# sourceMappingURL=processImageMeta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"processImageMeta.d.ts","sourceRoot":"","sources":["../src/processImageMeta.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAWxC;4CAC4C;AAC5C,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5B,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAmB3B"}
@@ -0,0 +1,8 @@
1
+ export declare const slugPlugin: () => {
2
+ name: string;
3
+ element: {
4
+ filter: string[];
5
+ visit(node: any, ctx: any): void;
6
+ };
7
+ };
8
+ //# sourceMappingURL=slugPlugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slugPlugin.d.ts","sourceRoot":"","sources":["../src/slugPlugin.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,UAAU;;;;oBAML,GAAG,OAAO,GAAG;;CAU9B,CAAA"}
@@ -0,0 +1,8 @@
1
+ export declare const tableWhitespacePlugin: {
2
+ name: string;
3
+ element: {
4
+ filter: string[];
5
+ visit(node: any, ctx: any): void;
6
+ };
7
+ };
8
+ //# sourceMappingURL=tableWhitespacePlugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tableWhitespacePlugin.d.ts","sourceRoot":"","sources":["../src/tableWhitespacePlugin.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,qBAAqB;;;;oBAIlB,GAAG,OAAO,GAAG;;CAW3B,CAAA"}
@@ -0,0 +1,38 @@
1
+ export type ImageMeta = {
2
+ width: number;
3
+ height: number;
4
+ blurDataURL: string;
5
+ };
6
+ export type Heading = {
7
+ title: string;
8
+ priority: number;
9
+ id: string;
10
+ };
11
+ export type Frontmatter = {
12
+ title: string;
13
+ headings?: Heading[];
14
+ description?: string;
15
+ name?: string;
16
+ versions?: string[];
17
+ version?: string;
18
+ by?: string;
19
+ publishedAt?: string;
20
+ draft?: boolean;
21
+ relatedIds?: string[];
22
+ type?: 'changelog' | string;
23
+ readingTime?: {
24
+ text: string;
25
+ minutes: number;
26
+ time: number;
27
+ words: number;
28
+ };
29
+ poster?: string;
30
+ slug: string;
31
+ image?: string;
32
+ /** image dimensions and blur placeholder, populated if `image` exists and sharp is installed */
33
+ imageMeta?: ImageMeta;
34
+ component?: string;
35
+ package?: string;
36
+ demoName?: string;
37
+ };
38
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,OAAO,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAA;AAErE,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IACrB,IAAI,CAAC,EAAE,WAAW,GAAG,MAAM,CAAA;IAC3B,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5E,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gGAAgG;IAChG,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Notifies One's HMR system about a file dependency so the page hot-reloads
3
+ * when the file changes. No-op when One is not present or in production.
4
+ */
5
+ export declare function notifyFileRead(filePath: string): void;
6
+ //# sourceMappingURL=watchFile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watchFile.d.ts","sourceRoot":"","sources":["../src/watchFile.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAKrD"}