1import { readFileSync, createReadStream, readdirSync } from 'node:fs'; 2import Benchmark from 'benchmark'; 3import { loadTreeConstructionTestData } from 'parse5-test-utils/dist/generate-parsing-tests.js'; 4import { loadSAXParserTestData } from 'parse5-test-utils/dist/load-sax-parser-test-data.js'; 5import { treeAdapters, WritableStreamStub, finished } from 'parse5-test-utils/dist/common.js'; 6import * as parse5 from '../../packages/parse5/dist/index.js'; 7import { ParserStream as parse5Stream } from '../../packages/parse5-parser-stream/dist/index.js'; 8import * as parse5Upstream from 'parse5'; 9 10const hugePagePath = new URL('../../test/data/huge-page/huge-page.html', import.meta.url); 11const treeConstructionPath = new URL('../../test/data/html5lib-tests/tree-construction', import.meta.url); 12const saxPath = new URL('../../test/data/sax/', import.meta.url); 13 14//HACK: https://github.com/bestiejs/benchmark.js/issues/51 15/* global workingCopy, WorkingCopyParserStream, upstreamParser, hugePage, microTests, runMicro, runPages, files */ 16global.workingCopy = parse5; 17global.WorkingCopyParserStream = parse5Stream; 18global.upstreamParser = parse5Upstream; 19 20// Huge page data 21global.hugePage = readFileSync(hugePagePath).toString(); 22 23// Micro data 24global.microTests = loadTreeConstructionTestData(treeConstructionPath, treeAdapters.default) 25 .filter( 26 (test) => 27 //NOTE: this test caused a stack overflow in parse5 v1.x 28 test.input !== '<button><p><button>' 29 ) 30 .map((test) => ({ 31 html: test.input, 32 fragmentContext: test.fragmentContext, 33 })); 34 35global.runMicro = function (parser) { 36 for (const test of microTests) { 37 if (test.fragmentContext) { 38 parser.parseFragment(test.fragmentContext, test.html); 39 } else { 40 parser.parse(test.html); 41 } 42 } 43}; 44 45// Pages data 46const pages = loadSAXParserTestData().map((test) => test.src); 47 48global.runPages = function (parser) { 49 for (const page of pages) { 50 parser.parse(page); 51 } 52}; 53 54// Stream data 55global.files = readdirSync(saxPath).map((dirName) => new URL(`${dirName}/src.html`, saxPath).pathname); 56 57// Utils 58function getHz(suite, testName) { 59 for (let i = 0; i < suite.length; i++) { 60 if (suite[i].name === testName) { 61 return suite[i].hz; 62 } 63 } 64} 65 66function runBench({ name, workingCopyFn, upstreamFn, defer = false }) { 67 const suite = new Benchmark.Suite(name); 68 69 suite 70 .add('Working copy', workingCopyFn, { defer }) 71 .add('Upstream', upstreamFn, { defer }) 72 .on('start', () => console.log(name)) 73 .on('cycle', (event) => console.log(String(event.target))) 74 .on('complete', () => { 75 const workingCopyHz = getHz(suite, 'Working copy'); 76 const upstreamHz = getHz(suite, 'Upstream'); 77 78 if (workingCopyHz > upstreamHz) { 79 console.log(`Working copy is ${(workingCopyHz / upstreamHz).toFixed(2)}x faster.\n`); 80 } else { 81 console.log(`Working copy is ${(upstreamHz / workingCopyHz).toFixed(2)}x slower.\n`); 82 } 83 }) 84 .run(); 85} 86 87// Benchmarks 88runBench({ 89 name: 'parse5 regression benchmark - MICRO', 90 workingCopyFn: () => runMicro(workingCopy), 91 upstreamFn: () => runMicro(upstreamParser), 92}); 93 94runBench({ 95 name: 'parse5 regression benchmark - HUGE', 96 workingCopyFn: () => workingCopy.parse(hugePage), 97 upstreamFn: () => upstreamParser.parse(hugePage), 98}); 99 100runBench({ 101 name: 'parse5 regression benchmark - PAGES', 102 workingCopyFn: () => runPages(workingCopy), 103 upstreamFn: () => runPages(upstreamParser), 104}); 105 106runBench({ 107 name: 'parse5 regression benchmark - STREAM', 108 defer: true, 109 workingCopyFn: async (deferred) => { 110 const parsePromises = files.map((fileName) => { 111 const stream = createReadStream(fileName, 'utf8'); 112 const parserStream = new WorkingCopyParserStream(); 113 114 stream.pipe(parserStream); 115 return finished(parserStream); 116 }); 117 118 await Promise.all(parsePromises); 119 deferred.resolve(); 120 }, 121 upstreamFn: async (deferred) => { 122 const parsePromises = files.map(async (fileName) => { 123 const stream = createReadStream(fileName, 'utf8'); 124 const writable = new WritableStreamStub(); 125 126 stream.pipe(writable); 127 128 await finished(writable); 129 130 upstreamParser.parse(writable.writtenData); 131 }); 132 133 await Promise.all(parsePromises); 134 deferred.resolve(); 135 }, 136}); 137