2021-11-02 07:32:17 -07:00
|
|
|
import { dirname, resolve } from 'path';
|
|
|
|
import { fileURLToPath } from 'url';
|
|
|
|
import { isEqual } from './helpers/html-differ.js';
|
|
|
|
import { loadFiles } from './helpers/load.js';
|
2019-04-25 07:47:23 -05:00
|
|
|
|
2022-08-30 09:36:16 -05:00
|
|
|
import { marked as cjsMarked } from '../lib/marked.cjs';
|
2021-11-02 07:32:17 -07:00
|
|
|
import { marked as esmMarked } from '../lib/marked.esm.js';
|
|
|
|
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
|
|
|
|
|
let marked;
|
2019-04-25 07:47:23 -05:00
|
|
|
|
2019-04-25 13:34:33 -05:00
|
|
|
/**
|
|
|
|
* Load specs
|
|
|
|
*/
|
2021-11-02 07:32:17 -07:00
|
|
|
export function load() {
|
|
|
|
const dir = resolve(__dirname, './specs/commonmark');
|
2019-04-25 13:33:20 -05:00
|
|
|
const sections = loadFiles(dir);
|
|
|
|
let specs = [];
|
|
|
|
|
|
|
|
for (const section in sections) {
|
|
|
|
specs = specs.concat(sections[section].specs);
|
|
|
|
}
|
|
|
|
|
|
|
|
return specs;
|
2019-04-25 07:47:23 -05:00
|
|
|
}
|
|
|
|
|
2019-04-25 13:34:33 -05:00
|
|
|
/**
|
|
|
|
* Run all benchmarks
|
|
|
|
*/
|
2021-11-02 07:32:17 -07:00
|
|
|
export async function runBench(options) {
|
2019-04-25 07:47:23 -05:00
|
|
|
options = options || {};
|
|
|
|
const specs = load();
|
2022-08-30 09:36:16 -05:00
|
|
|
const tests = {};
|
2019-04-25 07:47:23 -05:00
|
|
|
|
|
|
|
// Non-GFM, Non-pedantic
|
2022-08-30 09:36:16 -05:00
|
|
|
cjsMarked.setOptions({
|
2019-04-25 07:47:23 -05:00
|
|
|
gfm: false,
|
|
|
|
breaks: false,
|
|
|
|
pedantic: false,
|
|
|
|
sanitize: false,
|
|
|
|
smartLists: false
|
|
|
|
});
|
|
|
|
if (options.marked) {
|
2022-08-30 09:36:16 -05:00
|
|
|
cjsMarked.setOptions(options.marked);
|
2019-04-25 07:47:23 -05:00
|
|
|
}
|
2022-08-30 09:36:16 -05:00
|
|
|
tests['cjs marked'] = cjsMarked.parse;
|
2019-11-06 11:11:06 -06:00
|
|
|
|
2021-11-02 07:32:17 -07:00
|
|
|
esmMarked.setOptions({
|
2019-11-06 11:11:06 -06:00
|
|
|
gfm: false,
|
|
|
|
breaks: false,
|
|
|
|
pedantic: false,
|
|
|
|
sanitize: false,
|
|
|
|
smartLists: false
|
|
|
|
});
|
|
|
|
if (options.marked) {
|
2021-11-02 07:32:17 -07:00
|
|
|
esmMarked.setOptions(options.marked);
|
2019-11-06 11:11:06 -06:00
|
|
|
}
|
2022-08-30 09:36:16 -05:00
|
|
|
tests['esm marked'] = esmMarked.parse;
|
2019-04-25 07:47:23 -05:00
|
|
|
|
2022-08-30 09:36:16 -05:00
|
|
|
// esmMarked.setOptions({
|
|
|
|
// gfm: true,
|
|
|
|
// breaks: false,
|
|
|
|
// pedantic: false,
|
|
|
|
// sanitize: false,
|
|
|
|
// smartLists: false
|
|
|
|
// });
|
|
|
|
// if (options.marked) {
|
|
|
|
// esmMarked.setOptions(options.marked);
|
|
|
|
// }
|
|
|
|
// tests['esm marked (gfm)'] = esmMarked.parse;
|
2019-04-25 07:47:23 -05:00
|
|
|
|
|
|
|
try {
|
2022-08-30 09:36:16 -05:00
|
|
|
tests.commonmark = (await (async() => {
|
2021-11-02 07:32:17 -07:00
|
|
|
const { Parser, HtmlRenderer } = await import('commonmark');
|
|
|
|
const parser = new Parser();
|
|
|
|
const writer = new HtmlRenderer();
|
2019-04-26 21:00:40 -05:00
|
|
|
return function(text) {
|
2019-04-25 07:47:23 -05:00
|
|
|
return writer.render(parser.parse(text));
|
|
|
|
};
|
2022-08-30 09:36:16 -05:00
|
|
|
})());
|
2019-04-25 07:47:23 -05:00
|
|
|
} catch (e) {
|
|
|
|
console.error('Could not bench commonmark. (Error: %s)', e.message);
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2022-08-30 09:36:16 -05:00
|
|
|
tests['markdown-it'] = (await (async() => {
|
2021-11-02 07:32:17 -07:00
|
|
|
const MarkdownIt = (await import('markdown-it')).default;
|
2019-04-25 07:47:23 -05:00
|
|
|
const md = new MarkdownIt();
|
|
|
|
return md.render.bind(md);
|
2022-08-30 09:36:16 -05:00
|
|
|
})());
|
2019-04-25 07:47:23 -05:00
|
|
|
} catch (e) {
|
|
|
|
console.error('Could not bench markdown-it. (Error: %s)', e.message);
|
|
|
|
}
|
2022-08-30 09:36:16 -05:00
|
|
|
|
|
|
|
await bench(tests, specs);
|
2019-04-25 07:47:23 -05:00
|
|
|
}
|
|
|
|
|
2022-08-30 09:36:16 -05:00
|
|
|
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 isEqual(spec.html, await test(spec.markdown)) ? 1 : 0);
|
2019-04-25 07:47:23 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-30 09:36:16 -05:00
|
|
|
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}%`);
|
2019-04-25 07:47:23 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Argument Parsing
|
|
|
|
*/
|
2019-04-26 21:00:40 -05:00
|
|
|
function parseArg(argv) {
|
2019-04-25 07:47:23 -05:00
|
|
|
argv = argv.slice(2);
|
|
|
|
|
|
|
|
const options = {};
|
|
|
|
const orphans = [];
|
|
|
|
|
2019-04-26 21:00:40 -05:00
|
|
|
function getarg() {
|
2019-04-25 07:47:23 -05:00
|
|
|
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 => {
|
|
|
|
return `-${ch}`;
|
|
|
|
}).concat(argv);
|
|
|
|
arg = argv.shift();
|
|
|
|
} else {
|
|
|
|
// e.g. -a
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// e.g. foo
|
|
|
|
}
|
|
|
|
|
|
|
|
return arg;
|
|
|
|
}
|
|
|
|
|
|
|
|
const defaults = marked.getDefaults();
|
|
|
|
|
|
|
|
while (argv.length) {
|
2019-04-25 13:07:58 -05:00
|
|
|
const arg = getarg();
|
2022-08-30 09:36:16 -05:00
|
|
|
if (arg.indexOf('--') === 0) {
|
|
|
|
const opt = camelize(arg.replace(/^--(no-)?/, ''));
|
|
|
|
if (!defaults.hasOwnProperty(opt)) {
|
|
|
|
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);
|
2019-04-25 07:47:23 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
*/
|
2019-04-26 21:00:40 -05:00
|
|
|
function camelize(text) {
|
2019-04-25 07:47:23 -05:00
|
|
|
return text.replace(/(\w)-(\w)/g, (_, a, b) => a + b.toUpperCase());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Main
|
|
|
|
*/
|
2021-11-02 07:32:17 -07:00
|
|
|
export default async function main(argv) {
|
2022-08-30 09:36:16 -05:00
|
|
|
marked = cjsMarked;
|
2021-11-02 07:32:17 -07:00
|
|
|
|
2019-04-25 07:47:23 -05:00
|
|
|
const opt = parseArg(argv);
|
|
|
|
|
2022-08-30 09:36:16 -05:00
|
|
|
await runBench(opt);
|
2019-04-25 07:47:23 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* returns time to millisecond granularity
|
2022-08-30 09:36:16 -05:00
|
|
|
* @param hrtimeElapsed {bigint}
|
2019-04-25 07:47:23 -05:00
|
|
|
*/
|
2019-04-26 21:00:40 -05:00
|
|
|
function prettyElapsedTime(hrtimeElapsed) {
|
2022-08-30 09:36:16 -05:00
|
|
|
return Number(hrtimeElapsed / 1_000_000n);
|
2019-04-25 07:47:23 -05:00
|
|
|
}
|
|
|
|
|
2021-11-02 07:32:17 -07:00
|
|
|
process.title = 'marked bench';
|
|
|
|
main(process.argv.slice());
|