123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- import { dirname, resolve } from 'node:path';
- import { fileURLToPath } from 'node:url';
- import { htmlIsEqual, getTests } from '@markedjs/testutils';
- import { marked } from '../lib/marked.esm.js';
- const __dirname = dirname(fileURLToPath(import.meta.url));
- /**
- * Load specs
- */
- export async function load() {
- const dir = resolve(__dirname, './specs/commonmark');
- const sections = await getTests(dir);
- let specs = [];
- for (const section in sections) {
- specs = specs.concat(sections[section].specs);
- }
- return specs;
- }
- /**
- * Run all benchmarks
- */
- export async function runBench(options) {
- options = options || {};
- const specs = await load();
- const tests = {};
- marked.setOptions({
- gfm: false,
- breaks: false,
- pedantic: false,
- });
- if (options.marked) {
- marked.setOptions(options.marked);
- }
- tests.marked = marked.parse;
- try {
- tests.commonmark = await (async() => {
- const { Parser, HtmlRenderer } = await import('commonmark');
- const parser = new Parser();
- const writer = new HtmlRenderer();
- return function(text) {
- return writer.render(parser.parse(text));
- };
- })();
- } catch(e) {
- console.error('Could not bench commonmark. (Error: %s)', e.message);
- }
- try {
- tests['markdown-it'] = await (async() => {
- const MarkdownIt = (await import('markdown-it')).default;
- const md = new MarkdownIt();
- return md.render.bind(md);
- })();
- } catch(e) {
- console.error('Could not bench markdown-it. (Error: %s)', e.message);
- }
- await bench(tests, specs);
- }
- export async function bench(tests, specs) {
- const stats = {};
- for (const name in tests) {
- stats[name] = {
- elapsed: 0n,
- correct: 0,
- };
- }
- console.log();
- for (let i = 0; i < specs.length; i++) {
- const spec = specs[i];
- process.stdout.write(
- `${((i * 100) / specs.length).toFixed(1).padStart(5)}% ${i
- .toString()
- .padStart(specs.length.toString().length)} of ${specs.length}\r`,
- );
- for (const name in tests) {
- const test = tests[name];
- const before = process.hrtime.bigint();
- for (let n = 0; n < 1e3; n++) {
- await test(spec.markdown);
- }
- const after = process.hrtime.bigint();
- stats[name].elapsed += after - before;
- stats[name].correct += (await htmlIsEqual(
- spec.html,
- await test(spec.markdown),
- ))
- ? 1
- : 0;
- }
- }
- for (const name in tests) {
- const ms = prettyElapsedTime(stats[name].elapsed);
- const percent = ((stats[name].correct / specs.length) * 100).toFixed(2);
- console.log(`${name} completed in ${ms}ms and passed ${percent}%`);
- }
- const percentSlower = ((
- prettyElapsedTime(stats.marked.elapsed)
- / prettyElapsedTime(stats.commonmark.elapsed)
- ) - 1) * 100;
- console.log(`${Math.round(percentSlower)}% slower than commonmark`);
- }
- /**
- * Argument Parsing
- */
- function parseArg(argv) {
- argv = argv.slice(2);
- const options = {};
- const orphans = [];
- function getArg() {
- let arg = argv.shift();
- if (arg.indexOf('--') === 0) {
- // e.g. --opt
- arg = arg.split('=');
- if (arg.length > 1) {
- // e.g. --opt=val
- argv.unshift(arg.slice(1).join('='));
- }
- arg = arg[0];
- } else if (arg[0] === '-') {
- if (arg.length > 2) {
- // e.g. -abc
- argv = arg
- .substring(1)
- .split('')
- .map((ch) => `-${ch}`)
- .concat(argv);
- arg = argv.shift();
- } else {
- // e.g. -a
- }
- } else {
- // e.g. foo
- }
- return arg;
- }
- const defaults = marked.getDefaults();
- while (argv.length) {
- const arg = getArg();
- if (arg.indexOf('--') === 0) {
- const opt = camelize(arg.replace(/^--(no-)?/, ''));
- if (!(opt in defaults)) {
- continue;
- }
- options.marked = options.marked || {};
- if (arg.indexOf('--no-') === 0) {
- options.marked[opt] = typeof defaults[opt] !== 'boolean' ? null : false;
- } else {
- options.marked[opt] =
- typeof defaults[opt] !== 'boolean' ? argv.shift() : true;
- }
- } else {
- orphans.push(arg);
- }
- }
- if (orphans.length > 0) {
- console.error();
- console.error('The following arguments are not used:');
- orphans.forEach((arg) => console.error(` ${arg}`));
- console.error();
- }
- return options;
- }
- /**
- * Helpers
- */
- function camelize(text) {
- return text.replace(/(\w)-(\w)/g, (_, a, b) => a + b.toUpperCase());
- }
- /**
- * Main
- */
- export default async function main(argv) {
- const opt = parseArg(argv);
- await runBench(opt);
- }
- /**
- * returns time to millisecond granularity
- * @param hrtimeElapsed {bigint}
- */
- function prettyElapsedTime(hrtimeElapsed) {
- return Number(hrtimeElapsed / 1_000_000n);
- }
- process.title = 'marked bench';
- main(process.argv.slice());
|