writr 5.0.0 → 5.0.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 CHANGED
@@ -68,6 +68,7 @@ plugins and working with the processor directly.
68
68
  - [Methods that Emit Errors](#methods-that-emit-errors)
69
69
  - [Error Event Examples](#error-event-examples)
70
70
  - [Event Emitter Methods](#event-emitter-methods)
71
+ - [Benchmarks](#benchmarks)
71
72
  - [ESM and Node Version Support](#esm-and-node-version-support)
72
73
  - [Code of Conduct and Contributing](#code-of-conduct-and-contributing)
73
74
  - [License](#license)
@@ -727,6 +728,17 @@ Since Writr extends Hookified, you have access to standard event emitter methods
727
728
 
728
729
  For more information about event handling capabilities, see the [Hookified documentation](https://github.com/jaredwray/hookified).
729
730
 
731
+ # Benchmarks
732
+
733
+ The benchmark shows rendering performance via Sync and Async methods with caching enabled and disabled.
734
+
735
+ | name | summary | ops/sec | time/op | margin | samples |
736
+ |---------------------------|:---------:|----------:|----------:|:--------:|----------:|
737
+ | render (Sync) (cached) | 🥇 | 34K | 30µs | ±0.08% | 33K |
738
+ | render (Async) (cached) | -1.5% | 33K | 31µs | ±0.08% | 33K |
739
+ | render (Sync) | -93% | 2K | 474µs | ±0.96% | 10K |
740
+ | render (Async) | -93% | 2K | 481µs | ±0.96% | 10K |
741
+
730
742
  # ESM and Node Version Support
731
743
 
732
744
  This package is ESM only and tested on the current lts version and its previous. Please don't open issues for questions regarding CommonJS / ESM or previous Nodejs versions.
package/dist/writr.d.ts CHANGED
@@ -16,6 +16,13 @@ declare class WritrCache {
16
16
  set(markdown: string, value: string, options?: RenderOptions): void;
17
17
  clear(): void;
18
18
  hash(markdown: string, options?: RenderOptions): string;
19
+ /**
20
+ * Sanitizes render options to only include serializable properties for caching.
21
+ * This prevents issues with structuredClone when options contain Promises, functions, or circular references.
22
+ * @param {RenderOptions} [options] The render options to sanitize
23
+ * @returns {RenderOptions | undefined} A new object with only the known RenderOptions properties
24
+ */
25
+ private sanitizeOptions;
19
26
  }
20
27
 
21
28
  /**
@@ -208,9 +215,9 @@ declare class Writr extends Hookified {
208
215
  * @returns {void}
209
216
  */
210
217
  saveToFileSync(filePath: string): void;
218
+ mergeOptions(current: WritrOptions, options: WritrOptions): WritrOptions;
211
219
  private isCacheEnabled;
212
220
  private createProcessor;
213
- private mergeOptions;
214
221
  private mergeRenderOptions;
215
222
  }
216
223
 
package/dist/writr.js CHANGED
@@ -19,11 +19,12 @@ import remarkToc from "remark-toc";
19
19
  import { unified } from "unified";
20
20
 
21
21
  // src/writr-cache.ts
22
- import { Cacheable, CacheableMemory } from "cacheable";
22
+ import { CacheableMemory } from "cacheable";
23
+ import { Hashery } from "hashery";
23
24
  var WritrCache = class {
24
25
  _store = new CacheableMemory();
25
26
  _hashStore = new CacheableMemory();
26
- _hash = new Cacheable();
27
+ _hash = new Hashery();
27
28
  get store() {
28
29
  return this._store;
29
30
  }
@@ -43,15 +44,53 @@ var WritrCache = class {
43
44
  this._hashStore.clear();
44
45
  }
45
46
  hash(markdown, options) {
46
- const content = { markdown, options };
47
+ const sanitizedOptions = this.sanitizeOptions(options);
48
+ const content = { markdown, options: sanitizedOptions };
47
49
  const key = JSON.stringify(content);
48
- let result = this._hashStore.get(key);
50
+ const result = this._hashStore.get(key);
49
51
  if (result) {
50
52
  return result;
51
53
  }
52
- result = this._hash.hash(content);
53
- this._hashStore.set(key, result);
54
- return result;
54
+ const hash = this._hash.toHashSync(content);
55
+ this._hashStore.set(key, hash);
56
+ return hash;
57
+ }
58
+ /**
59
+ * Sanitizes render options to only include serializable properties for caching.
60
+ * This prevents issues with structuredClone when options contain Promises, functions, or circular references.
61
+ * @param {RenderOptions} [options] The render options to sanitize
62
+ * @returns {RenderOptions | undefined} A new object with only the known RenderOptions properties
63
+ */
64
+ sanitizeOptions(options) {
65
+ if (!options) {
66
+ return void 0;
67
+ }
68
+ const sanitized = {};
69
+ if (options.emoji !== void 0) {
70
+ sanitized.emoji = options.emoji;
71
+ }
72
+ if (options.toc !== void 0) {
73
+ sanitized.toc = options.toc;
74
+ }
75
+ if (options.slug !== void 0) {
76
+ sanitized.slug = options.slug;
77
+ }
78
+ if (options.highlight !== void 0) {
79
+ sanitized.highlight = options.highlight;
80
+ }
81
+ if (options.gfm !== void 0) {
82
+ sanitized.gfm = options.gfm;
83
+ }
84
+ if (options.math !== void 0) {
85
+ sanitized.math = options.math;
86
+ }
87
+ if (options.mdx !== void 0) {
88
+ sanitized.mdx = options.mdx;
89
+ }
90
+ if (options.caching !== void 0) {
91
+ sanitized.caching = options.caching;
92
+ }
93
+ return sanitized;
55
94
  }
56
95
  };
57
96
 
@@ -528,6 +567,16 @@ ${yamlString}---
528
567
  }
529
568
  }
530
569
  }
570
+ mergeOptions(current, options) {
571
+ if (options.throwErrors !== void 0) {
572
+ current.throwErrors = options.throwErrors;
573
+ }
574
+ if (options.renderOptions) {
575
+ current.renderOptions ??= {};
576
+ this.mergeRenderOptions(current.renderOptions, options.renderOptions);
577
+ }
578
+ return current;
579
+ }
531
580
  isCacheEnabled(options) {
532
581
  if (options?.caching !== void 0) {
533
582
  return options.caching;
@@ -563,16 +612,6 @@ ${yamlString}---
563
612
  processor.use(rehypeStringify);
564
613
  return processor;
565
614
  }
566
- mergeOptions(current, options) {
567
- if (options.throwErrors !== void 0) {
568
- current.throwErrors = options.throwErrors;
569
- }
570
- if (options.renderOptions) {
571
- current.renderOptions ??= {};
572
- this.mergeRenderOptions(current.renderOptions, options.renderOptions);
573
- }
574
- return current;
575
- }
576
615
  mergeRenderOptions(current, options) {
577
616
  if (options.emoji !== void 0) {
578
617
  current.emoji = options.emoji;
package/package.json CHANGED
@@ -1,16 +1,20 @@
1
1
  {
2
2
  "name": "writr",
3
- "version": "5.0.0",
3
+ "version": "5.0.2",
4
4
  "description": "Markdown Rendering Simplified",
5
5
  "type": "module",
6
6
  "main": "./dist/writr.js",
7
7
  "types": "./dist/writr.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
+ "types": "./dist/writr.d.ts",
10
11
  "import": "./dist/writr.js"
11
12
  }
12
13
  },
13
- "repository": "https://github.com/jaredwray/writr.git",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/jaredwray/writr.git"
17
+ },
14
18
  "author": "Jared Wray <me@jaredwray.com>",
15
19
  "engines": {
16
20
  "node": ">=20"
@@ -44,29 +48,20 @@
44
48
  "react-markdown",
45
49
  "markdown-to-react"
46
50
  ],
47
- "scripts": {
48
- "clean": "rimraf ./dist ./coverage ./node_modules ./pnpm-lock.yaml ./site/README.md ./site/dist",
49
- "build": "rimraf ./dist && tsup src/writr.ts --format esm --dts --clean",
50
- "prepare": "pnpm build",
51
- "lint": "biome check --write --error-on-warnings",
52
- "test": "pnpm lint && vitest run --coverage",
53
- "test:ci": "biome check --error-on-warnings && vitest run --coverage",
54
- "website:build": "rimraf ./site/README.md ./site/dist && npx docula build -s ./site -o ./site/dist",
55
- "website:serve": "rimraf ./site/README.md ./site/dist && npx docula serve -s ./site -o ./site/dist"
56
- },
57
51
  "dependencies": {
58
- "cacheable": "^2.1.1",
59
- "hookified": "^1.12.2",
60
- "html-react-parser": "^5.2.7",
61
- "js-yaml": "^4.1.0",
62
- "react": "^19.2.0",
52
+ "cacheable": "^2.3.1",
53
+ "hashery": "^1.3.0",
54
+ "hookified": "^1.14.0",
55
+ "html-react-parser": "^5.2.10",
56
+ "js-yaml": "^4.1.1",
57
+ "react": "^19.2.3",
63
58
  "rehype-highlight": "^7.0.2",
64
59
  "rehype-katex": "^7.0.1",
65
60
  "rehype-slug": "^6.0.0",
66
61
  "rehype-stringify": "^10.0.1",
67
62
  "remark-emoji": "^5.0.2",
68
63
  "remark-gfm": "^4.0.1",
69
- "remark-github-blockquote-alert": "^2.0.0",
64
+ "remark-github-blockquote-alert": "^2.0.1",
70
65
  "remark-math": "^6.0.0",
71
66
  "remark-mdx": "^3.1.1",
72
67
  "remark-parse": "^11.0.0",
@@ -75,21 +70,33 @@
75
70
  "unified": "^11.0.5"
76
71
  },
77
72
  "devDependencies": {
78
- "@biomejs/biome": "^2.3.1",
73
+ "@biomejs/biome": "^2.3.10",
74
+ "@monstermann/tinybench-pretty-printer": "^0.3.0",
79
75
  "@types/js-yaml": "^4.0.9",
80
- "@types/node": "^24.9.1",
81
- "@types/react": "^19.2.2",
82
- "@vitest/coverage-v8": "^4.0.4",
83
- "docula": "^0.31.0",
84
- "rimraf": "^6.0.1",
85
- "ts-node": "^10.9.2",
86
- "tsup": "^8.5.0",
76
+ "@types/node": "^25.0.3",
77
+ "@types/react": "^19.2.7",
78
+ "@vitest/coverage-v8": "^4.0.16",
79
+ "docula": "^0.31.1",
80
+ "rimraf": "^6.1.2",
81
+ "tinybench": "^6.0.0",
82
+ "tsup": "^8.5.1",
83
+ "tsx": "^4.21.0",
87
84
  "typescript": "^5.9.3",
88
- "vitest": "^4.0.4"
85
+ "vitest": "^4.0.16"
89
86
  },
90
87
  "files": [
91
88
  "dist",
92
89
  "README.md",
93
90
  "LICENSE"
94
- ]
95
- }
91
+ ],
92
+ "scripts": {
93
+ "clean": "rimraf ./dist ./coverage ./node_modules ./pnpm-lock.yaml ./site/README.md ./site/dist",
94
+ "build": "rimraf ./dist && tsup src/writr.ts --format esm --dts --clean",
95
+ "lint": "biome check --write --error-on-warnings",
96
+ "benchmark": "tsx benchmark/benchmark.ts",
97
+ "test": "pnpm lint && vitest run --coverage",
98
+ "test:ci": "biome check --error-on-warnings && vitest run --coverage",
99
+ "website:build": "rimraf ./site/README.md ./site/dist && npx docula build -s ./site -o ./site/dist",
100
+ "website:serve": "rimraf ./site/README.md ./site/dist && npx docula serve -s ./site -o ./site/dist"
101
+ }
102
+ }