diff --git a/fastn-js/BENCHMARK_README.md b/fastn-js/BENCHMARK_README.md new file mode 100644 index 0000000000..11eda78d8e --- /dev/null +++ b/fastn-js/BENCHMARK_README.md @@ -0,0 +1,219 @@ +# Fastn JavaScript Benchmarking + +This document explains how to use the new benchmarking infrastructure added to +fastn's JavaScript runtime. + +## Overview + +The benchmarking system provides: +- **Performance instrumentation** for core operations +- **Memory profiling** capabilities +- **CSS caching** with hit/miss tracking +- **Event system** performance monitoring + +## Quick Start + +### 1. Enable Benchmark Mode + +Add `?benchmark=true` to your URL or set `window.FASTN_BENCHMARK = true` in console. + +### 2. Run Individual Tests + +```javascript +// Create reactive objects +const mut = fastn_benchmark_api.createMutable(42); +const closure = fastn_benchmark_api.createClosure(() => mut.get() * 2); + +// Run CSS tests +const css = fastn_benchmark_api.createStyle('test-class', { + color: 'red', + 'font-size': '14px' +}); + +// Check performance results +console.log(fastn_benchmark_api.getPerformanceEntries()); +``` + +### 3. Monitor Performance + +```javascript +// Clear previous measurements +fastn_benchmark_api.clearPerformanceMarks(); + +// Perform operations to test +for (let i = 0; i < 1000; i++) { + const mut = fastn_benchmark_api.createMutable(i); + mut.set(i * 2); +} + +// View results +const results = fastn_benchmark_api.getPerformanceEntries(); +console.log('Timing measurements:', results.measurements); +console.log('Operation counters:', results.counters); +``` + +## Performance Monitoring + +The system automatically tracks performance for these core operations: + +### Timing Metrics +- `closure-update` - Time spent updating closures +- `mutable-set` - Time spent setting mutable values +- `css-creation` - Time spent generating CSS +- `resize-handler` - Time spent processing resize events + +### Counters +- `closure-updates` - Number of closure updates +- `mutable-created` - Number of mutable variable created +- `mutable-sets` - Number of mutable variable updates +- `css-creations` - Number of CSS classes generated +- `css-cache-hits/misses` - CSS cache effectiveness + +## API Reference + +### `fastn_benchmark_api` + +Core benchmarking functions: + +```javascript +// Reactive system +createClosure(func, execute) // Create new closure +createMutable(val) // Create new mutable variable +createMutableList(list) // Create new mutable list + +// DOM operations +createNode(kind) // Create DOM node +createStyle(cssClass, obj) // Generate CSS with caching + +// Event testing +simulateResize() // Trigger resize handler +clearEventHandlers(type) // Clear event handlers + +// Utilities +getStaticValue(obj) // Convert to static value +getMemoryUsage() // Get current memory stats +clearPerformanceMarks() // Reset performance data + +// Test data generators +generateTestData.largeArray(size) // Large array for testing +generateTestData.deepObject(depth) // Nested object for testing +generateTestData.complexCSSProps() // Complex CSS properties +``` + +### `fastn_perf` + +Performance monitoring system: + +```javascript +// Manual timing +fastn_perf.mark('my-operation'); +// ... perform operation ... +fastn_perf.measure('my-operation'); + +// Counters +fastn_perf.count('my-counter'); +console.log(fastn_perf.getCounter('my-counter')); + +// Get all results +const results = fastn_perf.getResults(); +console.log(results.measurements, results.counters); + +// Clear data +fastn_perf.clear(); +``` + +### `fastn_css` + +CSS system with caching: + +```javascript +// Usage (automatically used by fastn_utils.createStyle) +fastn_css.createStyle(className, properties) +fastn_css.clearCache() // Clear CSS cache +fastn_css.getCacheSize() // Get cache size +fastn_css.getStats() // Get hit/miss stats +fastn_css.getCacheHitRatio() // Get cache efficiency +``` + +### `fastn_events` + +Event system for testing: + +```javascript +// Register/trigger events +fastn_events.register(type, handler, metadata) +fastn_events.trigger(type, event, targetHandlers) + +// Testing utilities +fastn_events.clear(type) // Clear handlers +fastn_events.getHandlerCount(type) // Get handler count +fastn_events.getStats() // Get event statistics +``` + +## Best Practices + +### 1. Baseline Measurements +Always establish baseline performance before making changes: + +```javascript +// Clear previous measurements +fastn_benchmark_api.clearPerformanceMarks(); + +// Record baseline +const baselineCounters = fastn_perf.getCounter('mutable-sets'); +// Make changes... +const updatedCounters = fastn_perf.getCounter('mutable-sets'); +console.log('Change in operations:', updatedCounters - baselineCounters); +``` + +### 2. Statistical Significance +Run tests multiple times to get stable measurements: + +```javascript +const measurements = []; +for (let i = 0; i < 5; i++) { + fastn_benchmark_api.clearPerformanceMarks(); + // Perform test operations... + const results = fastn_benchmark_api.getPerformanceEntries(); + measurements.push(results); +} +// Analyze variance in measurements +``` + +### 3. Realistic Test Data +Use representative data sizes and structures: + +```javascript +// Test with realistic list sizes +const largeList = fastn_benchmark_api.generateTestData.largeArray(10000); +const mut = fastn_benchmark_api.createMutableList(largeList); +``` + +## Troubleshooting + +### Benchmarks Not Running +- Check `window.FASTN_BENCHMARK` is true +- Verify performance API is available (`typeof performance !== 'undefined'`) +- Check browser console for errors + +### Inaccurate Results +- Disable browser extensions that might interfere +- Close other tabs to reduce resource contention +- Run tests in private/incognito mode +- Use dedicated benchmark browser profile + +### Memory Issues +- Clear caches between test runs +- Force garbage collection if available (`gc()` in dev tools) +- Monitor for memory leaks in long-running tests + +## Contributing + +When adding performance instrumentation to new code: + +1. Add `fastn_perf.mark()` and `fastn_perf.measure()` calls around critical operations +2. Use `fastn_perf.count()` to track operation frequency +3. Add new functions to `fastn_benchmark_api` for isolated testing +4. Update this documentation + +The benchmarking system provides performance insights to help optimize fastn's JavaScript runtime effectively. diff --git a/fastn-js/js/benchmark-utils.js b/fastn-js/js/benchmark-utils.js new file mode 100644 index 0000000000..8fd72eade9 --- /dev/null +++ b/fastn-js/js/benchmark-utils.js @@ -0,0 +1,228 @@ +// Benchmark utilities for fastn JavaScript performance testing +// This file provides instrumentation and isolated testing capabilities + +// Performance monitoring wrapper +const fastn_perf = { + enabled: + typeof window !== "undefined" && + (window.FASTN_BENCHMARK || + window.location.search.includes("benchmark=true")), + timers: new Map(), + counters: new Map(), + + mark(name) { + if (this.enabled && typeof performance !== "undefined") { + performance.mark(`fastn-${name}-start`); + } + }, + + measure(name) { + if (this.enabled && typeof performance !== "undefined") { + performance.mark(`fastn-${name}-end`); + try { + performance.measure( + `fastn-${name}`, + `fastn-${name}-start`, + `fastn-${name}-end`, + ); + } catch (e) { + // Ignore timing errors in benchmarks + } + } + }, + + count(name) { + if (this.enabled) { + this.counters.set(name, (this.counters.get(name) || 0) + 1); + } + }, + + getCounter(name) { + return this.counters.get(name) || 0; + }, + + clearCounters() { + this.counters.clear(); + }, + + getResults() { + if (!this.enabled || typeof performance === "undefined") return {}; + + const measures = performance + .getEntriesByType("measure") + .filter((entry) => entry.name.startsWith("fastn-")) + .map((entry) => ({ + name: entry.name.replace("fastn-", ""), + duration: entry.duration, + startTime: entry.startTime, + })); + + return { + measurements: measures, + counters: Object.fromEntries(this.counters), + }; + }, + + clear() { + if (typeof performance !== "undefined") { + performance.clearMarks(); + performance.clearMeasures(); + } + this.counters.clear(); + this.timers.clear(); + }, +}; + +const fastn_benchmark_api = { + // Performance monitoring + perf: fastn_perf, + + // Module isolation helpers + modules: { + reactive: null, + dom: null, + utils: null, + events: null, + virtual: null, + }, + + // Reactive system testing + createClosure: function (func, execute = true) { + if (typeof fastn !== "undefined" && fastn.closure) { + return new fastn.closure(func, execute); + } + throw new Error("fastn.closure not available"); + }, + + createMutable: function (val) { + if (typeof fastn !== "undefined" && fastn.mutable) { + return new fastn.mutable(val); + } + throw new Error("fastn.mutable not available"); + }, + + createMutableList: function (list = []) { + if (typeof fastn !== "undefined" && fastn.mutableList) { + return new fastn.mutableList(list); + } + throw new Error("fastn.mutableList not available"); + }, + + // DOM operations testing + createNode: function (kind) { + if (typeof fastn_utils !== "undefined") { + return fastn_utils.htmlNode(kind); + } + throw new Error("fastn_utils not available"); + }, + + createStyle: function (cssClass, obj) { + if (typeof fastn_utils !== "undefined") { + return fastn_utils.createStyle(cssClass, obj); + } + throw new Error("fastn_utils.createStyle not available"); + }, + + // Event handling testing + simulateResize: function () { + if (typeof window !== "undefined" && window.onresize) { + return window.onresize(); + } + return null; + }, + + triggerClosureUpdate: function (closure) { + if (closure && typeof closure.update === "function") { + return closure.update(); + } + throw new Error("Invalid closure object"); + }, + + // Utility testing + getStaticValue: function (obj) { + if (typeof fastn_utils !== "undefined") { + return fastn_utils.getStaticValue(obj); + } + throw new Error("fastn_utils not available"); + }, + + // CSS system testing + getCSSCacheSize: function () { + if (typeof fastn_css !== "undefined") { + return fastn_css.getCacheSize(); + } + return 0; + }, + + clearCSSCache: function () { + if (typeof fastn_css !== "undefined") { + fastn_css.clearCache(); + } + }, + + // Event system testing + clearEventHandlers: function (type) { + if (typeof fastn_events !== "undefined") { + fastn_events.clear(type); + } + }, + + getEventHandlerCount: function (type) { + if (typeof fastn_events !== "undefined") { + return fastn_events.handlers[type] + ? fastn_events.handlers[type].length + : 0; + } + return 0; + }, + + // Performance helpers + clearPerformanceMarks: function () { + fastn_perf.clear(); + }, + + getPerformanceEntries: function () { + return fastn_perf.getResults(); + }, + + // Test data generators + generateTestData: { + largeArray: function (size = 1000) { + return Array.from({ length: size }, (_, i) => ({ + id: i, + value: `item-${i}`, + data: Math.random(), + })); + }, + + deepObject: function (depth = 5) { + let obj = { value: "leaf" }; + for (let i = 0; i < depth; i++) { + obj = { nested: obj, level: i }; + } + return obj; + }, + + complexCSSProps: function () { + return { + "background-color": "#ff0000", + "border-radius": "5px", + "box-shadow": "0 2px 4px rgba(0,0,0,0.1)", + margin: "10px", + padding: "20px", + "font-size": "14px", + "line-height": "1.5", + }; + }, + }, +}; + +// Enable benchmark mode detection +if (typeof window !== "undefined") { + window.FASTN_BENCHMARK = + window.FASTN_BENCHMARK || + window.location.search.includes("benchmark=true") || + window.location.hostname === "localhost"; + + window.fastn_benchmark_api = fastn_benchmark_api; +} diff --git a/fastn-js/js/dom.js b/fastn-js/js/dom.js index 8ad874dc17..8e04c0ec7d 100644 --- a/fastn-js/js/dom.js +++ b/fastn-js/js/dom.js @@ -137,6 +137,60 @@ fastn_dom.getClassesAsStringWithoutStyleTag = function () { return classes.join("\n\t"); }; +// Benchmarkable CSS system with caching and performance monitoring +const fastn_css = { + cache: new Map(), + stats: { + hits: 0, + misses: 0, + creations: 0, + }, + + createStyle(cssClass, obj) { + if (typeof fastn_perf !== "undefined") fastn_perf.mark("css-creation"); + if (typeof fastn_perf !== "undefined") fastn_perf.count("css-creations"); + + const cacheKey = `${cssClass}-${JSON.stringify(obj)}`; + + if (this.cache.has(cacheKey)) { + this.stats.hits++; + if (typeof fastn_perf !== "undefined") fastn_perf.count("css-cache-hits"); + const result = this.cache.get(cacheKey); + if (typeof fastn_perf !== "undefined") fastn_perf.measure("css-creation"); + return result; + } + + this.stats.misses++; + if (typeof fastn_perf !== "undefined") fastn_perf.count("css-cache-misses"); + + const result = getClassAsString(cssClass, obj); + this.cache.set(cacheKey, result); + this.stats.creations++; + + if (typeof fastn_perf !== "undefined") fastn_perf.measure("css-creation"); + return result; + }, + + // For benchmarking + clearCache() { + this.cache.clear(); + this.stats = { hits: 0, misses: 0, creations: 0 }; + }, + + getCacheSize() { + return this.cache.size; + }, + + getStats() { + return { ...this.stats }; + }, + + getCacheHitRatio() { + const total = this.stats.hits + this.stats.misses; + return total > 0 ? this.stats.hits / total : 0; + }, +}; + function getClassAsString(className, obj) { if (typeof obj.value === "object" && obj.value !== null) { let value = ""; @@ -847,6 +901,7 @@ class Node2 { #extraData; #children; constructor(parentOrSibiling, kind) { + if (typeof fastn_perf !== "undefined") fastn_perf.count("node2_constructor"); this.#kind = kind; this.#parent = parentOrSibiling; this.#children = []; @@ -1943,6 +1998,7 @@ class Node2 { this.#mutables.push(ftd.dark_mode); } setStaticProperty(kind, value, inherited) { + if (typeof fastn_perf !== "undefined") fastn_perf.count("node2_setStaticProperty"); // value can be either static or mutable let staticValue = fastn_utils.getStaticValue(value); if (kind === fastn_dom.PropertyKind.Children) { @@ -2740,6 +2796,7 @@ class Node2 { } } setProperty(kind, value, inherited) { + if (typeof fastn_perf !== "undefined") fastn_perf.count("node2_setProperty"); if (value instanceof fastn.mutableClass) { this.setDynamicProperty( kind, @@ -2761,6 +2818,7 @@ class Node2 { } } setDynamicProperty(kind, deps, func, inherited) { + if (typeof fastn_perf !== "undefined") fastn_perf.count("node2_setDynamicProperty"); let closure = fastn .closure(func) .addNodeProperty(this, kind, inherited); @@ -2829,6 +2887,7 @@ class Node2 { } } destroy() { + if (typeof fastn_perf !== "undefined") fastn_perf.count("node2_destroy"); for (let i = 0; i < this.#mutables.length; i++) { this.#mutables[i].unlinkNode(this); } diff --git a/fastn-js/js/fastn.js b/fastn-js/js/fastn.js index 09378a517f..03046240b6 100644 --- a/fastn-js/js/fastn.js +++ b/fastn-js/js/fastn.js @@ -31,8 +31,16 @@ const fastn = (function (fastn) { } update() { + if (typeof fastn_perf !== "undefined") + fastn_perf.mark("closure-update"); + if (typeof fastn_perf !== "undefined") + fastn_perf.count("closure-updates"); + this.#cached_value = this.#formula(); this.updateUi(); + + if (typeof fastn_perf !== "undefined") + fastn_perf.measure("closure-update"); } getNode() { @@ -64,6 +72,9 @@ const fastn = (function (fastn) { #closureInstance; constructor(val) { + if (typeof fastn_perf !== "undefined") + fastn_perf.count("mutable-created"); + this.#value = null; this.#old_closure = null; this.#closures = []; @@ -131,9 +142,16 @@ const fastn = (function (fastn) { } set(value) { - this.setWithoutUpdate(value); + if (typeof fastn_perf !== "undefined") + fastn_perf.mark("mutable-set"); + if (typeof fastn_perf !== "undefined") + fastn_perf.count("mutable-sets"); + this.setWithoutUpdate(value); this.#closureInstance.update(); + + if (typeof fastn_perf !== "undefined") + fastn_perf.measure("mutable-set"); } // we have to unlink all nodes, else they will be kept in memory after the node is removed from DOM diff --git a/fastn-js/js/postInit.js b/fastn-js/js/postInit.js index cb4072b665..dc51bda4af 100644 --- a/fastn-js/js/postInit.js +++ b/fastn-js/js/postInit.js @@ -1,3 +1,85 @@ +// Benchmarkable event system with isolated testing capabilities +const fastn_events = { + handlers: { + resize: [], + click: [], + clickOutside: [], + keydown: [], + keyup: [], + globalKey: [], + globalKeySeq: [], + }, + + stats: { + triggeredEvents: {}, + handlerExecutions: 0, + }, + + register(type, handler, metadata = {}) { + if (typeof fastn_perf !== "undefined") + fastn_perf.count(`event-register-${type}`); + + if (!this.handlers[type]) { + this.handlers[type] = []; + } + + this.handlers[type].push({ + handler, + metadata, + registeredAt: Date.now(), + }); + }, + + trigger(type, event, targetHandlers = null) { + if (typeof fastn_perf !== "undefined") + fastn_perf.mark(`events-${type}`); + if (typeof fastn_perf !== "undefined") + fastn_perf.count(`event-trigger-${type}`); + + const handlers = targetHandlers || this.handlers[type] || []; + + handlers.forEach((handlerObj) => { + try { + if (typeof handlerObj === "function") { + handlerObj(event); + } else if (handlerObj.handler) { + handlerObj.handler(event); + } + this.stats.handlerExecutions++; + } catch (error) { + console.error(`Error in ${type} event handler:`, error); + } + }); + + this.stats.triggeredEvents[type] = + (this.stats.triggeredEvents[type] || 0) + 1; + + if (typeof fastn_perf !== "undefined") + fastn_perf.measure(`events-${type}`); + }, + + // For benchmarking + clear(type) { + if (type) { + this.handlers[type] = []; + } else { + Object.keys(this.handlers).forEach((key) => { + this.handlers[key] = []; + }); + } + this.stats = { triggeredEvents: {}, handlerExecutions: 0 }; + }, + + getHandlerCount(type) { + return this.handlers[type] ? this.handlers[type].length : 0; + }, + + getStats() { + return { ...this.stats }; + }, +}; + +// Maintain backward compatibility ftd.clickOutsideEvents = []; ftd.globalKeyEvents = []; ftd.globalKeySeqEvents = []; @@ -40,9 +122,21 @@ ftd.post_init = function () { const DARK_MODE_CLASS = "dark"; let last_device = ftd.device.get(); - window.onresize = function () { + // Benchmarkable resize handler + const optimizedResizeHandler = function () { + if (typeof fastn_perf !== "undefined") + fastn_perf.mark("resize-handler"); + if (typeof fastn_perf !== "undefined") + fastn_perf.count("resize-events"); + initialise_device(); + fastn_events.trigger("resize", { timestamp: Date.now() }); + + if (typeof fastn_perf !== "undefined") + fastn_perf.measure("resize-handler"); }; + + window.onresize = optimizedResizeHandler; function initialise_click_outside_events() { document.addEventListener("click", function (event) { ftd.clickOutsideEvents.forEach(([ftdNode, func]) => { diff --git a/fastn-js/js/utils.js b/fastn-js/js/utils.js index f4f358e2e2..61bc9325d0 100644 --- a/fastn-js/js/utils.js +++ b/fastn-js/js/utils.js @@ -51,6 +51,25 @@ let fastn_utils = { return [node, css, attributes]; }, createStyle(cssClass, obj) { + // Use the benchmarkable CSS system if available + if (typeof fastn_css !== "undefined") { + const cssString = fastn_css.createStyle(cssClass, obj); + + if (doubleBuffering) { + fastn_dom.styleClasses = `${fastn_dom.styleClasses}${cssString}\n`; + } else { + let styles = document.getElementById("styles"); + let textNode = document.createTextNode(cssString); + if (styles.styleSheet) { + styles.styleSheet.cssText = cssString; + } else { + styles.appendChild(textNode); + } + } + return cssString; + } + + // Fallback to original implementation if (doubleBuffering) { fastn_dom.styleClasses = `${ fastn_dom.styleClasses diff --git a/fastn-js/marked.js b/fastn-js/js/vendor/marked.js similarity index 100% rename from fastn-js/marked.js rename to fastn-js/js/vendor/marked.js index eaa08fabf4..edd0bd83d7 100644 --- a/fastn-js/marked.js +++ b/fastn-js/js/vendor/marked.js @@ -1,7 +1,7 @@ +// Content taken from https://cdn.jsdelivr.net/npm/marked/marked.min.js /** * marked v9.1.4 - a markdown parser * Copyright (c) 2011-2023, Christopher Jeffrey. (MIT Licensed) * https://github.com/markedjs/marked */ -// Content taken from https://cdn.jsdelivr.net/npm/marked/marked.min.js !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).marked={})}(this,(function(e){"use strict";function t(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}function n(t){e.defaults=t}e.defaults={async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null};const s=/[&<>"']/,r=new RegExp(s.source,"g"),i=/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,l=new RegExp(i.source,"g"),o={"&":"&","<":"<",">":">",'"':""","'":"'"},a=e=>o[e];function c(e,t){if(t){if(s.test(e))return e.replace(r,a)}else if(i.test(e))return e.replace(l,a);return e}const h=/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi;const p=/(^|[^\[])\^/g;function u(e,t){e="string"==typeof e?e:e.source,t=t||"";const n={replace:(t,s)=>(s=(s="object"==typeof s&&"source"in s?s.source:s).replace(p,"$1"),e=e.replace(t,s),n),getRegex:()=>new RegExp(e,t)};return n}function g(e){try{e=encodeURI(e).replace(/%25/g,"%")}catch(e){return null}return e}const k={exec:()=>null};function f(e,t){const n=e.replace(/\|/g,((e,t,n)=>{let s=!1,r=t;for(;--r>=0&&"\\"===n[r];)s=!s;return s?"|":" |"})).split(/ \|/);let s=0;if(n[0].trim()||n.shift(),n.length>0&&!n[n.length-1].trim()&&n.pop(),t)if(n.length>t)n.splice(t);else for(;n.length0)return{type:"space",raw:t[0]}}code(e){const t=this.rules.block.code.exec(e);if(t){const e=t[0].replace(/^ {1,4}/gm,"");return{type:"code",raw:t[0],codeBlockStyle:"indented",text:this.options.pedantic?e:d(e,"\n")}}}fences(e){const t=this.rules.block.fences.exec(e);if(t){const e=t[0],n=function(e,t){const n=e.match(/^(\s+)(?:```)/);if(null===n)return t;const s=n[1];return t.split("\n").map((e=>{const t=e.match(/^\s+/);if(null===t)return e;const[n]=t;return n.length>=s.length?e.slice(s.length):e})).join("\n")}(e,t[3]||"");return{type:"code",raw:e,lang:t[2]?t[2].trim().replace(this.rules.inline._escapes,"$1"):t[2],text:n}}}heading(e){const t=this.rules.block.heading.exec(e);if(t){let e=t[2].trim();if(/#$/.test(e)){const t=d(e,"#");this.options.pedantic?e=t.trim():t&&!/ $/.test(t)||(e=t.trim())}return{type:"heading",raw:t[0],depth:t[1].length,text:e,tokens:this.lexer.inline(e)}}}hr(e){const t=this.rules.block.hr.exec(e);if(t)return{type:"hr",raw:t[0]}}blockquote(e){const t=this.rules.block.blockquote.exec(e);if(t){const e=d(t[0].replace(/^ *>[ \t]?/gm,""),"\n"),n=this.lexer.state.top;this.lexer.state.top=!0;const s=this.lexer.blockTokens(e);return this.lexer.state.top=n,{type:"blockquote",raw:t[0],tokens:s,text:e}}}list(e){let t=this.rules.block.list.exec(e);if(t){let n=t[1].trim();const s=n.length>1,r={type:"list",raw:"",ordered:s,start:s?+n.slice(0,-1):"",loose:!1,items:[]};n=s?`\\d{1,9}\\${n.slice(-1)}`:`\\${n}`,this.options.pedantic&&(n=s?n:"[*+-]");const i=new RegExp(`^( {0,3}${n})((?:[\t ][^\\n]*)?(?:\\n|$))`);let l="",o="",a=!1;for(;e;){let n=!1;if(!(t=i.exec(e)))break;if(this.rules.block.hr.test(e))break;l=t[0],e=e.substring(l.length);let s=t[2].split("\n",1)[0].replace(/^\t+/,(e=>" ".repeat(3*e.length))),c=e.split("\n",1)[0],h=0;this.options.pedantic?(h=2,o=s.trimStart()):(h=t[2].search(/[^ ]/),h=h>4?1:h,o=s.slice(h),h+=t[1].length);let p=!1;if(!s&&/^ *$/.test(c)&&(l+=c+"\n",e=e.substring(c.length+1),n=!0),!n){const t=new RegExp(`^ {0,${Math.min(3,h-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ \t][^\\n]*)?(?:\\n|$))`),n=new RegExp(`^ {0,${Math.min(3,h-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),r=new RegExp(`^ {0,${Math.min(3,h-1)}}(?:\`\`\`|~~~)`),i=new RegExp(`^ {0,${Math.min(3,h-1)}}#`);for(;e;){const a=e.split("\n",1)[0];if(c=a,this.options.pedantic&&(c=c.replace(/^ {1,4}(?=( {4})*[^ ])/g," ")),r.test(c))break;if(i.test(c))break;if(t.test(c))break;if(n.test(e))break;if(c.search(/[^ ]/)>=h||!c.trim())o+="\n"+c.slice(h);else{if(p)break;if(s.search(/[^ ]/)>=4)break;if(r.test(s))break;if(i.test(s))break;if(n.test(s))break;o+="\n"+c}p||c.trim()||(p=!0),l+=a+"\n",e=e.substring(a.length+1),s=c.slice(h)}}r.loose||(a?r.loose=!0:/\n *\n *$/.test(l)&&(a=!0));let u,g=null;this.options.gfm&&(g=/^\[[ xX]\] /.exec(o),g&&(u="[ ] "!==g[0],o=o.replace(/^\[[ xX]\] +/,""))),r.items.push({type:"list_item",raw:l,task:!!g,checked:u,loose:!1,text:o,tokens:[]}),r.raw+=l}r.items[r.items.length-1].raw=l.trimEnd(),r.items[r.items.length-1].text=o.trimEnd(),r.raw=r.raw.trimEnd();for(let e=0;e"space"===e.type)),n=t.length>0&&t.some((e=>/\n.*\n/.test(e.raw)));r.loose=n}if(r.loose)for(let e=0;e$/,"$1").replace(this.rules.inline._escapes,"$1"):"",s=t[3]?t[3].substring(1,t[3].length-1).replace(this.rules.inline._escapes,"$1"):t[3];return{type:"def",tag:e,raw:t[0],href:n,title:s}}}table(e){const t=this.rules.block.table.exec(e);if(t){if(!/[:|]/.test(t[2]))return;const e={type:"table",raw:t[0],header:f(t[1]).map((e=>({text:e,tokens:[]}))),align:t[2].replace(/^\||\| *$/g,"").split("|"),rows:t[3]&&t[3].trim()?t[3].replace(/\n[ \t]*$/,"").split("\n"):[]};if(e.header.length===e.align.length){let t,n,s,r,i=e.align.length;for(t=0;t({text:e,tokens:[]})));for(i=e.header.length,n=0;n/i.test(t[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&/^<(pre|code|kbd|script)(\s|>)/i.test(t[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&/^<\/(pre|code|kbd|script)(\s|>)/i.test(t[0])&&(this.lexer.state.inRawBlock=!1),{type:"html",raw:t[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:t[0]}}link(e){const t=this.rules.inline.link.exec(e);if(t){const e=t[2].trim();if(!this.options.pedantic&&/^$/.test(e))return;const t=d(e.slice(0,-1),"\\");if((e.length-t.length)%2==0)return}else{const e=function(e,t){if(-1===e.indexOf(t[1]))return-1;let n=0;for(let s=0;s-1){const n=(0===t[0].indexOf("!")?5:4)+t[1].length+e;t[2]=t[2].substring(0,e),t[0]=t[0].substring(0,n).trim(),t[3]=""}}let n=t[2],s="";if(this.options.pedantic){const e=/^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(n);e&&(n=e[1],s=e[3])}else s=t[3]?t[3].slice(1,-1):"";return n=n.trim(),/^$/.test(e)?n.slice(1):n.slice(1,-1)),x(t,{href:n?n.replace(this.rules.inline._escapes,"$1"):n,title:s?s.replace(this.rules.inline._escapes,"$1"):s},t[0],this.lexer)}}reflink(e,t){let n;if((n=this.rules.inline.reflink.exec(e))||(n=this.rules.inline.nolink.exec(e))){let e=(n[2]||n[1]).replace(/\s+/g," ");if(e=t[e.toLowerCase()],!e){const e=n[0].charAt(0);return{type:"text",raw:e,text:e}}return x(n,e,n[0],this.lexer)}}emStrong(e,t,n=""){let s=this.rules.inline.emStrong.lDelim.exec(e);if(!s)return;if(s[3]&&n.match(/[\p{L}\p{N}]/u))return;if(!(s[1]||s[2]||"")||!n||this.rules.inline.punctuation.exec(n)){const n=[...s[0]].length-1;let r,i,l=n,o=0;const a="*"===s[0][0]?this.rules.inline.emStrong.rDelimAst:this.rules.inline.emStrong.rDelimUnd;for(a.lastIndex=0,t=t.slice(-1*e.length+s[0].length-1);null!=(s=a.exec(t));){if(r=s[1]||s[2]||s[3]||s[4]||s[5]||s[6],!r)continue;if(i=[...r].length,s[3]||s[4]){l+=i;continue}if((s[5]||s[6])&&n%3&&!((n+i)%3)){o+=i;continue}if(l-=i,l>0)continue;i=Math.min(i,i+l+o);const t=[...e].slice(0,n+s.index+i+1).join("");if(Math.min(n,i)%2){const e=t.slice(1,-1);return{type:"em",raw:t,text:e,tokens:this.lexer.inlineTokens(e)}}const a=t.slice(2,-2);return{type:"strong",raw:t,text:a,tokens:this.lexer.inlineTokens(a)}}}}codespan(e){const t=this.rules.inline.code.exec(e);if(t){let e=t[2].replace(/\n/g," ");const n=/[^ ]/.test(e),s=/^ /.test(e)&&/ $/.test(e);return n&&s&&(e=e.substring(1,e.length-1)),e=c(e,!0),{type:"codespan",raw:t[0],text:e}}}br(e){const t=this.rules.inline.br.exec(e);if(t)return{type:"br",raw:t[0]}}del(e){const t=this.rules.inline.del.exec(e);if(t)return{type:"del",raw:t[0],text:t[2],tokens:this.lexer.inlineTokens(t[2])}}autolink(e){const t=this.rules.inline.autolink.exec(e);if(t){let e,n;return"@"===t[2]?(e=c(t[1]),n="mailto:"+e):(e=c(t[1]),n=e),{type:"link",raw:t[0],text:e,href:n,tokens:[{type:"text",raw:e,text:e}]}}}url(e){let t;if(t=this.rules.inline.url.exec(e)){let e,n;if("@"===t[2])e=c(t[0]),n="mailto:"+e;else{let s;do{s=t[0],t[0]=this.rules.inline._backpedal.exec(t[0])[0]}while(s!==t[0]);e=c(t[0]),n="www."===t[1]?"http://"+t[0]:t[0]}return{type:"link",raw:t[0],text:e,href:n,tokens:[{type:"text",raw:e,text:e}]}}}inlineText(e){const t=this.rules.inline.text.exec(e);if(t){let e;return e=this.lexer.state.inRawBlock?t[0]:c(t[0]),{type:"text",raw:t[0],text:e}}}}const m={newline:/^(?: *(?:\n|$))+/,code:/^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/,fences:/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,hr:/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,heading:/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,blockquote:/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,list:/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/,html:"^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|)[\\s\\S]*?(?:(?:\\n *)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$))",def:/^ {0,3}\[(label)\]: *(?:\n *)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n *)?| *\n *)(title))? *(?:\n+|$)/,table:k,lheading:/^(?!bull )((?:.|\n(?!\s*?\n|bull ))+?)\n {0,3}(=+|-+) *(?:\n+|$)/,_paragraph:/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,text:/^[^\n]+/,_label:/(?!\s*\])(?:\\.|[^\[\]\\])+/,_title:/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/};m.def=u(m.def).replace("label",m._label).replace("title",m._title).getRegex(),m.bullet=/(?:[*+-]|\d{1,9}[.)])/,m.listItemStart=u(/^( *)(bull) */).replace("bull",m.bullet).getRegex(),m.list=u(m.list).replace(/bull/g,m.bullet).replace("hr","\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))").replace("def","\\n+(?="+m.def.source+")").getRegex(),m._tag="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",m._comment=/|$)/,m.html=u(m.html,"i").replace("comment",m._comment).replace("tag",m._tag).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),m.lheading=u(m.lheading).replace(/bull/g,m.bullet).getRegex(),m.paragraph=u(m._paragraph).replace("hr",m.hr).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("|table","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",m._tag).getRegex(),m.blockquote=u(m.blockquote).replace("paragraph",m.paragraph).getRegex(),m.normal={...m},m.gfm={...m.normal,table:"^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)"},m.gfm.table=u(m.gfm.table).replace("hr",m.hr).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("blockquote"," {0,3}>").replace("code"," {4}[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",m._tag).getRegex(),m.gfm.paragraph=u(m._paragraph).replace("hr",m.hr).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("table",m.gfm.table).replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",m._tag).getRegex(),m.pedantic={...m.normal,html:u("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",m._comment).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:k,lheading:/^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,paragraph:u(m.normal._paragraph).replace("hr",m.hr).replace("heading"," *#{1,6} *[^\n]").replace("lheading",m.lheading).replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").getRegex()};const w={escape:/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,autolink:/^<(scheme:[^\s\x00-\x1f<>]*|email)>/,url:k,tag:"^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^",link:/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/,reflink:/^!?\[(label)\]\[(ref)\]/,nolink:/^!?\[(ref)\](?:\[\])?/,reflinkSearch:"reflink|nolink(?!\\()",emStrong:{lDelim:/^(?:\*+(?:((?!\*)[punct])|[^\s*]))|^_+(?:((?!_)[punct])|([^\s_]))/,rDelimAst:/^[^_*]*?__[^_*]*?\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\*)[punct](\*+)(?=[\s]|$)|[^punct\s](\*+)(?!\*)(?=[punct\s]|$)|(?!\*)[punct\s](\*+)(?=[^punct\s])|[\s](\*+)(?!\*)(?=[punct])|(?!\*)[punct](\*+)(?!\*)(?=[punct])|[^punct\s](\*+)(?=[^punct\s])/,rDelimUnd:/^[^_*]*?\*\*[^_*]*?_[^_*]*?(?=\*\*)|[^_]+(?=[^_])|(?!_)[punct](_+)(?=[\s]|$)|[^punct\s](_+)(?!_)(?=[punct\s]|$)|(?!_)[punct\s](_+)(?=[^punct\s])|[\s](_+)(?!_)(?=[punct])|(?!_)[punct](_+)(?!_)(?=[punct])/},code:/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,br:/^( {2,}|\\)\n(?!\s*$)/,del:k,text:/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\`^|~"};w.punctuation=u(w.punctuation,"u").replace(/punctuation/g,w._punctuation).getRegex(),w.blockSkip=/\[[^[\]]*?\]\([^\(\)]*?\)|`[^`]*?`|<[^<>]*?>/g,w.anyPunctuation=/\\[punct]/g,w._escapes=/\\([punct])/g,w._comment=u(m._comment).replace("(?:--\x3e|$)","--\x3e").getRegex(),w.emStrong.lDelim=u(w.emStrong.lDelim,"u").replace(/punct/g,w._punctuation).getRegex(),w.emStrong.rDelimAst=u(w.emStrong.rDelimAst,"gu").replace(/punct/g,w._punctuation).getRegex(),w.emStrong.rDelimUnd=u(w.emStrong.rDelimUnd,"gu").replace(/punct/g,w._punctuation).getRegex(),w.anyPunctuation=u(w.anyPunctuation,"gu").replace(/punct/g,w._punctuation).getRegex(),w._escapes=u(w._escapes,"gu").replace(/punct/g,w._punctuation).getRegex(),w._scheme=/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/,w._email=/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/,w.autolink=u(w.autolink).replace("scheme",w._scheme).replace("email",w._email).getRegex(),w._attribute=/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/,w.tag=u(w.tag).replace("comment",w._comment).replace("attribute",w._attribute).getRegex(),w._label=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,w._href=/<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/,w._title=/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/,w.link=u(w.link).replace("label",w._label).replace("href",w._href).replace("title",w._title).getRegex(),w.reflink=u(w.reflink).replace("label",w._label).replace("ref",m._label).getRegex(),w.nolink=u(w.nolink).replace("ref",m._label).getRegex(),w.reflinkSearch=u(w.reflinkSearch,"g").replace("reflink",w.reflink).replace("nolink",w.nolink).getRegex(),w.normal={...w},w.pedantic={...w.normal,strong:{start:/^__|\*\*/,middle:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,endAst:/\*\*(?!\*)/g,endUnd:/__(?!_)/g},em:{start:/^_|\*/,middle:/^()\*(?=\S)([\s\S]*?\S)\*(?!\*)|^_(?=\S)([\s\S]*?\S)_(?!_)/,endAst:/\*(?!\*)/g,endUnd:/_(?!_)/g},link:u(/^!?\[(label)\]\((.*?)\)/).replace("label",w._label).getRegex(),reflink:u(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",w._label).getRegex()},w.gfm={...w.normal,escape:u(w.escape).replace("])","~|])").getRegex(),_extended_email:/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/,url:/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,_backpedal:/(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/,text:/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\t+" ".repeat(n.length)));e;)if(!(this.options.extensions&&this.options.extensions.block&&this.options.extensions.block.some((s=>!!(n=s.call({lexer:this},e,t))&&(e=e.substring(n.raw.length),t.push(n),!0)))))if(n=this.tokenizer.space(e))e=e.substring(n.raw.length),1===n.raw.length&&t.length>0?t[t.length-1].raw+="\n":t.push(n);else if(n=this.tokenizer.code(e))e=e.substring(n.raw.length),s=t[t.length-1],!s||"paragraph"!==s.type&&"text"!==s.type?t.push(n):(s.raw+="\n"+n.raw,s.text+="\n"+n.text,this.inlineQueue[this.inlineQueue.length-1].src=s.text);else if(n=this.tokenizer.fences(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.heading(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.hr(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.blockquote(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.list(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.html(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.def(e))e=e.substring(n.raw.length),s=t[t.length-1],!s||"paragraph"!==s.type&&"text"!==s.type?this.tokens.links[n.tag]||(this.tokens.links[n.tag]={href:n.href,title:n.title}):(s.raw+="\n"+n.raw,s.text+="\n"+n.raw,this.inlineQueue[this.inlineQueue.length-1].src=s.text);else if(n=this.tokenizer.table(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.lheading(e))e=e.substring(n.raw.length),t.push(n);else{if(r=e,this.options.extensions&&this.options.extensions.startBlock){let t=1/0;const n=e.slice(1);let s;this.options.extensions.startBlock.forEach((e=>{s=e.call({lexer:this},n),"number"==typeof s&&s>=0&&(t=Math.min(t,s))})),t<1/0&&t>=0&&(r=e.substring(0,t+1))}if(this.state.top&&(n=this.tokenizer.paragraph(r)))s=t[t.length-1],i&&"paragraph"===s.type?(s.raw+="\n"+n.raw,s.text+="\n"+n.text,this.inlineQueue.pop(),this.inlineQueue[this.inlineQueue.length-1].src=s.text):t.push(n),i=r.length!==e.length,e=e.substring(n.raw.length);else if(n=this.tokenizer.text(e))e=e.substring(n.raw.length),s=t[t.length-1],s&&"text"===s.type?(s.raw+="\n"+n.raw,s.text+="\n"+n.text,this.inlineQueue.pop(),this.inlineQueue[this.inlineQueue.length-1].src=s.text):t.push(n);else if(e){const t="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(t);break}throw new Error(t)}}return this.state.top=!0,t}inline(e,t=[]){return this.inlineQueue.push({src:e,tokens:t}),t}inlineTokens(e,t=[]){let n,s,r,i,l,o,a=e;if(this.tokens.links){const e=Object.keys(this.tokens.links);if(e.length>0)for(;null!=(i=this.tokenizer.rules.inline.reflinkSearch.exec(a));)e.includes(i[0].slice(i[0].lastIndexOf("[")+1,-1))&&(a=a.slice(0,i.index)+"["+"a".repeat(i[0].length-2)+"]"+a.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;null!=(i=this.tokenizer.rules.inline.blockSkip.exec(a));)a=a.slice(0,i.index)+"["+"a".repeat(i[0].length-2)+"]"+a.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);for(;null!=(i=this.tokenizer.rules.inline.anyPunctuation.exec(a));)a=a.slice(0,i.index)+"++"+a.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);for(;e;)if(l||(o=""),l=!1,!(this.options.extensions&&this.options.extensions.inline&&this.options.extensions.inline.some((s=>!!(n=s.call({lexer:this},e,t))&&(e=e.substring(n.raw.length),t.push(n),!0)))))if(n=this.tokenizer.escape(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.tag(e))e=e.substring(n.raw.length),s=t[t.length-1],s&&"text"===n.type&&"text"===s.type?(s.raw+=n.raw,s.text+=n.text):t.push(n);else if(n=this.tokenizer.link(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.reflink(e,this.tokens.links))e=e.substring(n.raw.length),s=t[t.length-1],s&&"text"===n.type&&"text"===s.type?(s.raw+=n.raw,s.text+=n.text):t.push(n);else if(n=this.tokenizer.emStrong(e,a,o))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.codespan(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.br(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.del(e))e=e.substring(n.raw.length),t.push(n);else if(n=this.tokenizer.autolink(e))e=e.substring(n.raw.length),t.push(n);else if(this.state.inLink||!(n=this.tokenizer.url(e))){if(r=e,this.options.extensions&&this.options.extensions.startInline){let t=1/0;const n=e.slice(1);let s;this.options.extensions.startInline.forEach((e=>{s=e.call({lexer:this},n),"number"==typeof s&&s>=0&&(t=Math.min(t,s))})),t<1/0&&t>=0&&(r=e.substring(0,t+1))}if(n=this.tokenizer.inlineText(r))e=e.substring(n.raw.length),"_"!==n.raw.slice(-1)&&(o=n.raw.slice(-1)),l=!0,s=t[t.length-1],s&&"text"===s.type?(s.raw+=n.raw,s.text+=n.text):t.push(n);else if(e){const t="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(t);break}throw new Error(t)}}else e=e.substring(n.raw.length),t.push(n);return t}}class y{options;constructor(t){this.options=t||e.defaults}code(e,t,n){const s=(t||"").match(/^\S*/)?.[0];return e=e.replace(/\n$/,"")+"\n",s?'
'+(n?e:c(e,!0))+"
\n":"
"+(n?e:c(e,!0))+"
\n"}blockquote(e){return`
\n${e}
\n`}html(e,t){return e}heading(e,t,n){return`${e}\n`}hr(){return"
\n"}list(e,t,n){const s=t?"ol":"ul";return"<"+s+(t&&1!==n?' start="'+n+'"':"")+">\n"+e+"\n"}listitem(e,t,n){return`
  • ${e}
  • \n`}checkbox(e){return"'}paragraph(e){return`

    ${e}

    \n`}table(e,t){return t&&(t=`${t}`),"\n\n"+e+"\n"+t+"
    \n"}tablerow(e){return`\n${e}\n`}tablecell(e,t){const n=t.header?"th":"td";return(t.align?`<${n} align="${t.align}">`:`<${n}>`)+e+`\n`}strong(e){return`${e}`}em(e){return`${e}`}codespan(e){return`${e}`}br(){return"
    "}del(e){return`${e}`}link(e,t,n){const s=g(e);if(null===s)return n;let r='",r}image(e,t,n){const s=g(e);if(null===s)return n;let r=`${n}"colon"===(t=t.toLowerCase())?":":"#"===t.charAt(0)?"x"===t.charAt(1)?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):"")));continue}case"code":{const e=r;n+=this.renderer.code(e.text,e.lang,!!e.escaped);continue}case"table":{const e=r;let t="",s="";for(let t=0;t0&&"paragraph"===n.tokens[0].type?(n.tokens[0].text=e+" "+n.tokens[0].text,n.tokens[0].tokens&&n.tokens[0].tokens.length>0&&"text"===n.tokens[0].tokens[0].type&&(n.tokens[0].tokens[0].text=e+" "+n.tokens[0].tokens[0].text)):n.tokens.unshift({type:"text",text:e+" "}):o+=e+" "}o+=this.parse(n.tokens,i),l+=this.renderer.listitem(o,r,!!s)}n+=this.renderer.list(l,t,s);continue}case"html":{const e=r;n+=this.renderer.html(e.text,e.block);continue}case"paragraph":{const e=r;n+=this.renderer.paragraph(this.parseInline(e.tokens));continue}case"text":{let i=r,l=i.tokens?this.parseInline(i.tokens):i.text;for(;s+1{n=n.concat(this.walkTokens(e[s],t))})):e.tokens&&(n=n.concat(this.walkTokens(e.tokens,t)))}}return n}use(...e){const t=this.defaults.extensions||{renderers:{},childTokens:{}};return e.forEach((e=>{const n={...e};if(n.async=this.defaults.async||n.async||!1,e.extensions&&(e.extensions.forEach((e=>{if(!e.name)throw new Error("extension name required");if("renderer"in e){const n=t.renderers[e.name];t.renderers[e.name]=n?function(...t){let s=e.renderer.apply(this,t);return!1===s&&(s=n.apply(this,t)),s}:e.renderer}if("tokenizer"in e){if(!e.level||"block"!==e.level&&"inline"!==e.level)throw new Error("extension level must be 'block' or 'inline'");const n=t[e.level];n?n.unshift(e.tokenizer):t[e.level]=[e.tokenizer],e.start&&("block"===e.level?t.startBlock?t.startBlock.push(e.start):t.startBlock=[e.start]:"inline"===e.level&&(t.startInline?t.startInline.push(e.start):t.startInline=[e.start]))}"childTokens"in e&&e.childTokens&&(t.childTokens[e.name]=e.childTokens)})),n.extensions=t),e.renderer){const t=this.defaults.renderer||new y(this.defaults);for(const n in e.renderer){const s=e.renderer[n],r=n,i=t[r];t[r]=(...e)=>{let n=s.apply(t,e);return!1===n&&(n=i.apply(t,e)),n||""}}n.renderer=t}if(e.tokenizer){const t=this.defaults.tokenizer||new b(this.defaults);for(const n in e.tokenizer){const s=e.tokenizer[n],r=n,i=t[r];t[r]=(...e)=>{let n=s.apply(t,e);return!1===n&&(n=i.apply(t,e)),n}}n.tokenizer=t}if(e.hooks){const t=this.defaults.hooks||new T;for(const n in e.hooks){const s=e.hooks[n],r=n,i=t[r];T.passThroughHooks.has(n)?t[r]=e=>{if(this.defaults.async)return Promise.resolve(s.call(t,e)).then((e=>i.call(t,e)));const n=s.call(t,e);return i.call(t,n)}:t[r]=(...e)=>{let n=s.apply(t,e);return!1===n&&(n=i.apply(t,e)),n}}n.hooks=t}if(e.walkTokens){const t=this.defaults.walkTokens,s=e.walkTokens;n.walkTokens=function(e){let n=[];return n.push(s.call(this,e)),t&&(n=n.concat(t.call(this,e))),n}}this.defaults={...this.defaults,...n}})),this}setOptions(e){return this.defaults={...this.defaults,...e},this}#e(e,t){return(n,s)=>{const r={...s},i={...this.defaults,...r};!0===this.defaults.async&&!1===r.async&&(i.silent||console.warn("marked(): The async option was set to true by an extension. The async: false option sent to parse will be ignored."),i.async=!0);const l=this.#t(!!i.silent,!!i.async);if(null==n)return l(new Error("marked(): input parameter is undefined or null"));if("string"!=typeof n)return l(new Error("marked(): input parameter is of type "+Object.prototype.toString.call(n)+", string expected"));if(i.hooks&&(i.hooks.options=i),i.async)return Promise.resolve(i.hooks?i.hooks.preprocess(n):n).then((t=>e(t,i))).then((e=>i.walkTokens?Promise.all(this.walkTokens(e,i.walkTokens)).then((()=>e)):e)).then((e=>t(e,i))).then((e=>i.hooks?i.hooks.postprocess(e):e)).catch(l);try{i.hooks&&(n=i.hooks.preprocess(n));const s=e(n,i);i.walkTokens&&this.walkTokens(s,i.walkTokens);let r=t(s,i);return i.hooks&&(r=i.hooks.postprocess(r)),r}catch(e){return l(e)}}}#t(e,t){return n=>{if(n.message+="\nPlease report this to https://github.com/markedjs/marked.",e){const e="

    An error occurred:

    "+c(n.message+"",!0)+"
    ";return t?Promise.resolve(e):e}if(t)return Promise.reject(n);throw n}}}const S=new R;function A(e,t){return S.parse(e,t)}A.options=A.setOptions=function(e){return S.setOptions(e),A.defaults=S.defaults,n(A.defaults),A},A.getDefaults=t,A.defaults=e.defaults,A.use=function(...e){return S.use(...e),A.defaults=S.defaults,n(A.defaults),A},A.walkTokens=function(e,t){return S.walkTokens(e,t)},A.parseInline=S.parseInline,A.Parser=z,A.parser=z.parse,A.Renderer=y,A.TextRenderer=$,A.Lexer=_,A.lexer=_.lex,A.Tokenizer=b,A.Hooks=T,A.parse=A;const I=A.options,E=A.setOptions,Z=A.use,q=A.walkTokens,L=A.parseInline,D=A,P=z.parse,v=_.lex;e.Hooks=T,e.Lexer=_,e.Marked=R,e.Parser=z,e.Renderer=y,e.TextRenderer=$,e.Tokenizer=b,e.getDefaults=t,e.lexer=v,e.marked=A,e.options=I,e.parse=D,e.parseInline=L,e.parser=P,e.setOptions=E,e.use=Z,e.walkTokens=q})); diff --git a/fastn-js/prism/prism-bash.js b/fastn-js/js/vendor/prism/prism-bash.js similarity index 100% rename from fastn-js/prism/prism-bash.js rename to fastn-js/js/vendor/prism/prism-bash.js diff --git a/fastn-js/prism/prism-diff.js b/fastn-js/js/vendor/prism/prism-diff.js similarity index 100% rename from fastn-js/prism/prism-diff.js rename to fastn-js/js/vendor/prism/prism-diff.js diff --git a/fastn-js/prism/prism-javascript.js b/fastn-js/js/vendor/prism/prism-javascript.js similarity index 100% rename from fastn-js/prism/prism-javascript.js rename to fastn-js/js/vendor/prism/prism-javascript.js diff --git a/fastn-js/prism/prism-json.js b/fastn-js/js/vendor/prism/prism-json.js similarity index 100% rename from fastn-js/prism/prism-json.js rename to fastn-js/js/vendor/prism/prism-json.js diff --git a/fastn-js/prism/prism-line-highlight.css b/fastn-js/js/vendor/prism/prism-line-highlight.css similarity index 100% rename from fastn-js/prism/prism-line-highlight.css rename to fastn-js/js/vendor/prism/prism-line-highlight.css diff --git a/fastn-js/prism/prism-line-highlight.js b/fastn-js/js/vendor/prism/prism-line-highlight.js similarity index 100% rename from fastn-js/prism/prism-line-highlight.js rename to fastn-js/js/vendor/prism/prism-line-highlight.js diff --git a/fastn-js/prism/prism-line-numbers.css b/fastn-js/js/vendor/prism/prism-line-numbers.css similarity index 100% rename from fastn-js/prism/prism-line-numbers.css rename to fastn-js/js/vendor/prism/prism-line-numbers.css diff --git a/fastn-js/prism/prism-line-numbers.js b/fastn-js/js/vendor/prism/prism-line-numbers.js similarity index 100% rename from fastn-js/prism/prism-line-numbers.js rename to fastn-js/js/vendor/prism/prism-line-numbers.js diff --git a/fastn-js/prism/prism-markdown.js b/fastn-js/js/vendor/prism/prism-markdown.js similarity index 100% rename from fastn-js/prism/prism-markdown.js rename to fastn-js/js/vendor/prism/prism-markdown.js diff --git a/fastn-js/prism/prism-python.js b/fastn-js/js/vendor/prism/prism-python.js similarity index 100% rename from fastn-js/prism/prism-python.js rename to fastn-js/js/vendor/prism/prism-python.js diff --git a/fastn-js/prism/prism-rust.js b/fastn-js/js/vendor/prism/prism-rust.js similarity index 100% rename from fastn-js/prism/prism-rust.js rename to fastn-js/js/vendor/prism/prism-rust.js diff --git a/fastn-js/prism/prism-sql.js b/fastn-js/js/vendor/prism/prism-sql.js similarity index 100% rename from fastn-js/prism/prism-sql.js rename to fastn-js/js/vendor/prism/prism-sql.js diff --git a/fastn-js/prism/prism.js b/fastn-js/js/vendor/prism/prism.js similarity index 100% rename from fastn-js/prism/prism.js rename to fastn-js/js/vendor/prism/prism.js diff --git a/fastn-js/js/virtual.js b/fastn-js/js/virtual.js index 16cd349371..a359bdb17b 100644 --- a/fastn-js/js/virtual.js +++ b/fastn-js/js/virtual.js @@ -28,6 +28,7 @@ class Node { #children; #attributes; constructor(id, tagName) { + if (typeof fastn_perf !== "undefined") fastn_perf.count("node_constructor"); this.#tagName = tagName; this.#dataId = id; this.classList = new ClassList(); diff --git a/fastn-js/src/lib.rs b/fastn-js/src/lib.rs index 1b165b397f..fa5a463ebf 100644 --- a/fastn-js/src/lib.rs +++ b/fastn-js/src/lib.rs @@ -62,6 +62,8 @@ pub fn fastn_test_js() -> &'static str { pub fn all_js_without_test_and_ftd_langugage_js() -> String { let markdown_js = fastn_js::markdown_js(); + // Core JS files - order is important for dependencies + let benchmark_utils_js = include_str_with_debug!("../js/benchmark-utils.js"); let fastn_js = include_str_with_debug!("../js/fastn.js"); let dom_js = include_str_with_debug!("../js/dom.js"); let utils_js = include_str_with_debug!("../js/utils.js"); @@ -71,9 +73,10 @@ pub fn all_js_without_test_and_ftd_langugage_js() -> String { let post_init_js = include_str_with_debug!("../js/postInit.js"); // the order is important + // benchmark-utils must come first to define fastn_perf // global variable defined in dom_js might be read in virtual_js format!( - "{markdown_js}{fastn_js}{dom_js}{utils_js}{virtual_js}{web_component_js}{ftd_js}{post_init_js}" + "{benchmark_utils_js}{markdown_js}{fastn_js}{dom_js}{utils_js}{virtual_js}{web_component_js}{ftd_js}{post_init_js}" ) } @@ -102,30 +105,30 @@ pub fn all_js_with_test() -> String { } pub fn markdown_js() -> &'static str { - include_str!("../marked.js") + include_str!("../js/vendor/marked.js") } pub fn prism_css() -> String { - let prism_line_highlight = include_str!("../prism/prism-line-highlight.css"); - let prism_line_numbers = include_str!("../prism/prism-line-numbers.css"); + let prism_line_highlight = include_str!("../js/vendor/prism/prism-line-highlight.css"); + let prism_line_numbers = include_str!("../js/vendor/prism/prism-line-numbers.css"); format!("{prism_line_highlight}{prism_line_numbers}") } pub fn prism_js() -> String { - let prism = include_str!("../prism/prism.js"); - let prism_line_highlight = include_str!("../prism/prism-line-highlight.js"); - let prism_line_numbers = include_str!("../prism/prism-line-numbers.js"); + let prism = include_str!("../js/vendor/prism/prism.js"); + let prism_line_highlight = include_str!("../js/vendor/prism/prism-line-highlight.js"); + let prism_line_numbers = include_str!("../js/vendor/prism/prism-line-numbers.js"); // Languages supported // Rust, Json, Python, Markdown, SQL, Bash, JavaScript - let prism_rust = include_str!("../prism/prism-rust.js"); - let prism_json = include_str!("../prism/prism-json.js"); - let prism_python = include_str!("../prism/prism-python.js"); - let prism_markdown = include_str!("../prism/prism-markdown.js"); - let prism_sql = include_str!("../prism/prism-sql.js"); - let prism_bash = include_str!("../prism/prism-bash.js"); - let prism_javascript = include_str!("../prism/prism-javascript.js"); - let prism_diff = include_str!("../prism/prism-diff.js"); + let prism_rust = include_str!("../js/vendor/prism/prism-rust.js"); + let prism_json = include_str!("../js/vendor/prism/prism-json.js"); + let prism_python = include_str!("../js/vendor/prism/prism-python.js"); + let prism_markdown = include_str!("../js/vendor/prism/prism-markdown.js"); + let prism_sql = include_str!("../js/vendor/prism/prism-sql.js"); + let prism_bash = include_str!("../js/vendor/prism/prism-bash.js"); + let prism_javascript = include_str!("../js/vendor/prism/prism-javascript.js"); + let prism_diff = include_str!("../js/vendor/prism/prism-diff.js"); format!( "{prism}{prism_line_highlight}{prism_line_numbers}{prism_rust}{prism_json}{prism_python\