@changerawr/markdown 1.1.5 → 1.1.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.
@@ -6,9 +6,11 @@ import { useState, useCallback, useMemo, useRef, useEffect } from "react";
6
6
 
7
7
  // src/parser.ts
8
8
  var MarkdownParser = class {
9
+ // Cache compiled regexes
9
10
  constructor(config) {
10
11
  this.rules = [];
11
12
  this.warnings = [];
13
+ this.compiledPatterns = /* @__PURE__ */ new Map();
12
14
  this.config = {
13
15
  debugMode: false,
14
16
  maxIterations: 1e4,
@@ -18,6 +20,10 @@ var MarkdownParser = class {
18
20
  }
19
21
  addRule(rule) {
20
22
  this.rules.push(rule);
23
+ this.compiledPatterns.set(
24
+ rule,
25
+ new RegExp(rule.pattern.source, rule.pattern.flags.replace("g", ""))
26
+ );
21
27
  this.rules.sort((a, b) => {
22
28
  const aFeatureExtension = ["alert", "button", "embed"].includes(a.name);
23
29
  const bFeatureExtension = ["alert", "button", "embed"].includes(b.name);
@@ -63,12 +69,20 @@ var MarkdownParser = class {
63
69
  iterationCount++;
64
70
  let matched = false;
65
71
  let bestMatch = null;
72
+ let nextBestMatchIndex = null;
66
73
  for (const rule of this.rules) {
67
74
  try {
68
- const pattern = new RegExp(rule.pattern.source, rule.pattern.flags.replace("g", ""));
75
+ const pattern = this.compiledPatterns.get(rule);
69
76
  const match = remaining.match(pattern);
70
77
  if (match && match.index !== void 0) {
71
- const priority = match.index === 0 ? 1e3 : 1e3 - match.index;
78
+ if (match.index === 0) {
79
+ bestMatch = { rule, match, priority: 1e3 };
80
+ break;
81
+ }
82
+ if (nextBestMatchIndex === null || match.index < nextBestMatchIndex) {
83
+ nextBestMatchIndex = match.index;
84
+ }
85
+ const priority = 1e3 - match.index;
72
86
  if (!bestMatch || priority > bestMatch.priority || priority === bestMatch.priority && match.index < (bestMatch.match.index || 0)) {
73
87
  bestMatch = { rule, match, priority };
74
88
  }
@@ -115,15 +129,14 @@ var MarkdownParser = class {
115
129
  }
116
130
  }
117
131
  if (!matched) {
118
- const char = remaining[0];
119
- if (char) {
120
- tokens.push({
121
- type: "text",
122
- content: char,
123
- raw: char
124
- });
125
- remaining = remaining.slice(1);
126
- }
132
+ const chunkSize = nextBestMatchIndex !== null ? nextBestMatchIndex : Math.min(remaining.length, 1e3);
133
+ const textChunk = remaining.slice(0, chunkSize);
134
+ tokens.push({
135
+ type: "text",
136
+ content: textChunk,
137
+ raw: textChunk
138
+ });
139
+ remaining = remaining.slice(chunkSize);
127
140
  }
128
141
  }
129
142
  if (iterationCount >= maxIterations) {
@@ -541,6 +554,154 @@ var MarkdownRenderer = class {
541
554
  }
542
555
  };
543
556
 
557
+ // src/cache.ts
558
+ var LRUCache = class {
559
+ constructor(capacity = 100) {
560
+ this.cache = /* @__PURE__ */ new Map();
561
+ this.hits = 0;
562
+ this.misses = 0;
563
+ this.evictions = 0;
564
+ if (capacity <= 0) {
565
+ throw new Error("Cache capacity must be greater than 0");
566
+ }
567
+ this.capacity = capacity;
568
+ }
569
+ /**
570
+ * Get a value from the cache
571
+ */
572
+ get(key) {
573
+ const entry = this.cache.get(key);
574
+ if (entry) {
575
+ entry.timestamp = Date.now();
576
+ entry.accessCount++;
577
+ this.hits++;
578
+ this.cache.delete(key);
579
+ this.cache.set(key, entry);
580
+ return entry.value;
581
+ }
582
+ this.misses++;
583
+ return void 0;
584
+ }
585
+ /**
586
+ * Set a value in the cache
587
+ */
588
+ set(key, value) {
589
+ if (this.cache.has(key)) {
590
+ this.cache.delete(key);
591
+ } else if (this.cache.size >= this.capacity) {
592
+ this.evictLRU();
593
+ }
594
+ this.cache.set(key, {
595
+ value,
596
+ timestamp: Date.now(),
597
+ accessCount: 0
598
+ });
599
+ }
600
+ /**
601
+ * Check if a key exists in the cache
602
+ */
603
+ has(key) {
604
+ return this.cache.has(key);
605
+ }
606
+ /**
607
+ * Delete a specific key from the cache
608
+ */
609
+ delete(key) {
610
+ return this.cache.delete(key);
611
+ }
612
+ /**
613
+ * Clear all entries from the cache
614
+ */
615
+ clear() {
616
+ this.cache.clear();
617
+ this.hits = 0;
618
+ this.misses = 0;
619
+ this.evictions = 0;
620
+ }
621
+ /**
622
+ * Get the current size of the cache
623
+ */
624
+ get size() {
625
+ return this.cache.size;
626
+ }
627
+ /**
628
+ * Get cache statistics
629
+ */
630
+ getStats() {
631
+ const totalRequests = this.hits + this.misses;
632
+ return {
633
+ size: this.cache.size,
634
+ capacity: this.capacity,
635
+ hits: this.hits,
636
+ misses: this.misses,
637
+ hitRate: totalRequests > 0 ? this.hits / totalRequests : 0,
638
+ evictions: this.evictions
639
+ };
640
+ }
641
+ /**
642
+ * Reset cache statistics
643
+ */
644
+ resetStats() {
645
+ this.hits = 0;
646
+ this.misses = 0;
647
+ this.evictions = 0;
648
+ }
649
+ /**
650
+ * Get all keys in the cache
651
+ */
652
+ keys() {
653
+ return Array.from(this.cache.keys());
654
+ }
655
+ /**
656
+ * Get all values in the cache
657
+ */
658
+ values() {
659
+ return Array.from(this.cache.values()).map((entry) => entry.value);
660
+ }
661
+ /**
662
+ * Update cache capacity and evict if necessary
663
+ */
664
+ setCapacity(newCapacity) {
665
+ if (newCapacity <= 0) {
666
+ throw new Error("Cache capacity must be greater than 0");
667
+ }
668
+ this.capacity = newCapacity;
669
+ while (this.cache.size > this.capacity) {
670
+ this.evictLRU();
671
+ }
672
+ }
673
+ /**
674
+ * Evict the least recently used entry
675
+ */
676
+ evictLRU() {
677
+ const firstKey = this.cache.keys().next().value;
678
+ if (firstKey !== void 0) {
679
+ this.cache.delete(firstKey);
680
+ this.evictions++;
681
+ }
682
+ }
683
+ };
684
+ function hashContent(content) {
685
+ if (content.length > 1e4) {
686
+ const start = content.slice(0, 1e3);
687
+ const middle = content.slice(Math.floor(content.length / 2) - 500, Math.floor(content.length / 2) + 500);
688
+ const end = content.slice(-1e3);
689
+ const sample = content.length + "|" + start + middle + end;
690
+ let hash2 = 2166136261;
691
+ for (let i = 0; i < sample.length; i++) {
692
+ hash2 ^= sample.charCodeAt(i);
693
+ hash2 += (hash2 << 1) + (hash2 << 4) + (hash2 << 7) + (hash2 << 8) + (hash2 << 24);
694
+ }
695
+ return (hash2 >>> 0).toString(36);
696
+ }
697
+ let hash = 2166136261;
698
+ for (let i = 0; i < content.length; i++) {
699
+ hash ^= content.charCodeAt(i);
700
+ hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
701
+ }
702
+ return (hash >>> 0).toString(36);
703
+ }
704
+
544
705
  // src/extensions/core/blockquote.ts
545
706
  var BlockquoteExtension = {
546
707
  name: "blockquote",
@@ -1513,8 +1674,13 @@ function extractVimeoId(url) {
1513
1674
  var ChangerawrMarkdown = class {
1514
1675
  constructor(config) {
1515
1676
  this.extensions = /* @__PURE__ */ new Map();
1677
+ this.parseTime = 0;
1678
+ this.renderTime = 0;
1679
+ this.lastTokenCount = 0;
1516
1680
  this.parser = new MarkdownParser(config?.parser);
1517
1681
  this.renderer = new MarkdownRenderer(config?.renderer);
1682
+ this.parseCache = new LRUCache(100);
1683
+ this.renderCache = new LRUCache(100);
1518
1684
  this.registerCoreExtensions();
1519
1685
  this.registerFeatureExtensions();
1520
1686
  if (config?.extensions) {
@@ -1597,14 +1763,83 @@ var ChangerawrMarkdown = class {
1597
1763
  }
1598
1764
  }
1599
1765
  parse(markdown2) {
1600
- return this.parser.parse(markdown2);
1766
+ const cacheKey = hashContent(markdown2);
1767
+ const cached = this.parseCache.get(cacheKey);
1768
+ if (cached) {
1769
+ this.lastTokenCount = cached.length;
1770
+ return cached;
1771
+ }
1772
+ const startTime = performance.now();
1773
+ const tokens = this.parser.parse(markdown2);
1774
+ this.parseTime = performance.now() - startTime;
1775
+ this.lastTokenCount = tokens.length;
1776
+ this.parseCache.set(cacheKey, tokens);
1777
+ return tokens;
1601
1778
  }
1602
- render(tokens) {
1603
- return this.renderer.render(tokens);
1779
+ render(tokens, cacheKey) {
1780
+ if (cacheKey) {
1781
+ const cached = this.renderCache.get(cacheKey);
1782
+ if (cached) {
1783
+ return cached;
1784
+ }
1785
+ }
1786
+ const startTime = performance.now();
1787
+ const html = this.renderer.render(tokens);
1788
+ this.renderTime = performance.now() - startTime;
1789
+ if (cacheKey) {
1790
+ this.renderCache.set(cacheKey, html);
1791
+ }
1792
+ return html;
1604
1793
  }
1605
1794
  toHtml(markdown2) {
1795
+ const cacheKey = hashContent(markdown2);
1796
+ const cachedHtml = this.renderCache.get(cacheKey);
1797
+ if (cachedHtml) {
1798
+ return cachedHtml;
1799
+ }
1606
1800
  const tokens = this.parse(markdown2);
1607
- return this.render(tokens);
1801
+ return this.render(tokens, cacheKey);
1802
+ }
1803
+ /**
1804
+ * Render markdown with performance metrics
1805
+ */
1806
+ toHtmlWithMetrics(markdown2) {
1807
+ const startTotal = performance.now();
1808
+ const parseCacheKey = hashContent(markdown2);
1809
+ const parseCacheHit = this.parseCache.has(parseCacheKey);
1810
+ const html = this.toHtml(markdown2);
1811
+ const totalTime = performance.now() - startTotal;
1812
+ const metrics = {
1813
+ inputSize: markdown2.length,
1814
+ parseTime: this.parseTime,
1815
+ renderTime: this.renderTime,
1816
+ totalTime,
1817
+ tokenCount: this.lastTokenCount,
1818
+ cacheHit: parseCacheHit
1819
+ };
1820
+ return { html, metrics };
1821
+ }
1822
+ /**
1823
+ * Stream-render large documents in chunks for better performance
1824
+ */
1825
+ async toHtmlStreamed(markdown2, options = {}) {
1826
+ const chunkSize = options.chunkSize || 50;
1827
+ const tokens = this.parse(markdown2);
1828
+ const totalTokens = tokens.length;
1829
+ const chunks = [];
1830
+ for (let i = 0; i < tokens.length; i += chunkSize) {
1831
+ const chunkTokens = tokens.slice(i, Math.min(i + chunkSize, tokens.length));
1832
+ const chunkHtml = this.render(chunkTokens);
1833
+ chunks.push(chunkHtml);
1834
+ if (options.onChunk) {
1835
+ options.onChunk({
1836
+ html: chunkHtml,
1837
+ progress: Math.min(i + chunkSize, tokens.length) / totalTokens
1838
+ });
1839
+ }
1840
+ await new Promise((resolve) => setTimeout(resolve, 0));
1841
+ }
1842
+ return chunks.join("");
1608
1843
  }
1609
1844
  getExtensions() {
1610
1845
  return Array.from(this.extensions.keys());
@@ -1618,25 +1853,50 @@ var ChangerawrMarkdown = class {
1618
1853
  getDebugInfo() {
1619
1854
  return {
1620
1855
  warnings: this.getWarnings(),
1621
- parseTime: 0,
1622
- renderTime: 0,
1623
- tokenCount: 0,
1856
+ parseTime: this.parseTime,
1857
+ renderTime: this.renderTime,
1858
+ tokenCount: this.lastTokenCount,
1624
1859
  iterationCount: 0
1625
1860
  };
1626
1861
  }
1627
1862
  getPerformanceMetrics() {
1628
1863
  return {
1629
- parseTime: 0,
1630
- renderTime: 0,
1631
- totalTime: 0,
1632
- tokenCount: 0
1864
+ parseTime: this.parseTime,
1865
+ renderTime: this.renderTime,
1866
+ totalTime: this.parseTime + this.renderTime,
1867
+ tokenCount: this.lastTokenCount
1868
+ };
1869
+ }
1870
+ /**
1871
+ * Get cache statistics
1872
+ */
1873
+ getCacheStats() {
1874
+ return {
1875
+ parse: this.parseCache.getStats(),
1876
+ render: this.renderCache.getStats()
1633
1877
  };
1634
1878
  }
1879
+ /**
1880
+ * Clear all caches
1881
+ */
1882
+ clearCaches() {
1883
+ this.parseCache.clear();
1884
+ this.renderCache.clear();
1885
+ }
1886
+ /**
1887
+ * Update cache capacity
1888
+ */
1889
+ setCacheSize(size) {
1890
+ this.parseCache.setCapacity(size);
1891
+ this.renderCache.setCapacity(size);
1892
+ }
1635
1893
  rebuildParserAndRenderer() {
1636
1894
  const parserConfig = this.parser.getConfig();
1637
1895
  const rendererConfig = this.renderer.getConfig();
1638
1896
  this.parser = new MarkdownParser(parserConfig);
1639
1897
  this.renderer = new MarkdownRenderer(rendererConfig);
1898
+ this.parseCache.clear();
1899
+ this.renderCache.clear();
1640
1900
  const extensionsToRegister = Array.from(this.extensions.values());
1641
1901
  const featureExtensions = extensionsToRegister.filter(
1642
1902
  (ext) => ["alert", "button", "embed"].includes(ext.name)