@@ -14,6 +14,12 @@ const { SourceProcessor } = htmlSyntax;
1414// children's text and read back whole. Both paths are covered here, and so are
1515// the tree shapes they cost differently on — the piecemeal path carries one
1616// frame per open element, so fan-out and depth are separate axes.
17+ //
18+ // Every fixture runs twice: `walk` is what a build that is not minimizing pays
19+ // — parse and walk, no output — and `minify` is the same pass with the printer
20+ // on. No visitors are registered, so `walk` is the floor both share and the
21+ // difference is what printing costs on that shape, which neither number shows
22+ // alone. Visitor cost itself is `html-parser-document-unit`'s.
1723
1824// No large real-world HTML ships in the repo, so generate a deterministic
1925// document mixing tree construction, entity references, comments, lists/tables
@@ -39,125 +45,114 @@ function makeHtml(blocks) {
3945 }
4046 return `${ out } </body></html>` ;
4147}
42- const HTML = makeHtml ( 2000 ) ;
4348
44- // Big table-body fragment (tr context seeds the tokenizer content mode).
45- const FRAGMENT = '<td class="c">cell <b>one</b> & two</td>' . repeat ( 6000 ) ;
46-
47- // One axis each. `TABLE` and `LISTS` are wide with a shallow repeating nest,
48- // `FLAT` is one long sibling run at depth 1, `DEEP` is a narrow 20-deep spine —
49- // together they separate the per-sibling cost from the per-frame one.
50- const TABLE = ( ( ) => {
51- let s = "<table>" ;
52- for ( let i = 0 ; i < 8000 ; i ++ ) {
53- s += `<tr><td class=c${ i } >cell ${ i } </td><td>${ i } </td></tr>` ;
54- }
55- return `${ s } </table>` ;
56- } ) ( ) ;
57- const FLAT = ( ( ) => {
58- let s = "" ;
59- for ( let i = 0 ; i < 8000 ; i ++ ) s += `<p class=c${ i } >text ${ i } </p>` ;
60- return s ;
61- } ) ( ) ;
62- const LISTS = ( ( ) => {
63- let s = "" ;
64- for ( let i = 0 ; i < 800 ; i ++ ) {
65- s += `<ul id=u${ i } >` ;
66- for ( let j = 0 ; j < 10 ; j ++ ) s += `<li><a href=/p${ j } >item ${ j } </a></li>` ;
67- s += "</ul>" ;
68- }
69- return s ;
70- } ) ( ) ;
71- const DEEP = ( ( ) => {
72- let s = "" ;
73- for ( let i = 0 ; i < 800 ; i ++ ) {
74- for ( let j = 0 ; j < 20 ; j ++ ) s += `<div class=l${ j } >` ;
75- s += `<span>leaf ${ i } </span>` ;
76- for ( let j = 0 ; j < 20 ; j ++ ) s += "</div>" ;
77- }
78- return s ;
79- } ) ( ) ;
80-
81- // Elements that cannot print in pieces, so their subtrees are composed and read
82- // back: a `<pre>` / `<textarea>` leading newline only survives re-parsing once
83- // the children's text is in, and a `<template>` holds its children in a content
84- // fragment rather than the child chain.
85- const WHOLE = ( ( ) => {
86- let s = "" ;
87- for ( let i = 0 ; i < 2000 ; i ++ ) {
88- s += `<div><pre>\n\nkeep ${ i } </pre><textarea>\n\nt ${ i } </textarea><template><p>x${ i } </p></template></div>` ;
89- }
90- return s ;
91- } ) ( ) ;
92-
93- // An omitted `<html>` / `<body>` start tag, held back until the first thing
94- // inside it decides whether it materializes (it does here — the text is
95- // whitespace the insertion modes would otherwise drop).
96- const IMPLIED = ( ( ) => {
97- let s = "" ;
98- for ( let i = 0 ; i < 4000 ; i ++ ) s += `<p>t${ i } <b>b${ i } </b>` ;
99- return ` ${ s } ` ;
100- } ) ( ) ;
49+ // One axis each. `wide table` and `nested lists` are wide with a shallow
50+ // repeating nest, `flat siblings` is one long sibling run at depth 1, `deep
51+ // nesting` is a narrow 20-deep spine — together they separate the per-sibling
52+ // cost from the per-frame one.
53+ /** @type {[string, string, { fragmentContext?: string }][] } name, source, extra process options */
54+ const FIXTURES = [
55+ [ "document" , makeHtml ( 2000 ) , { } ] ,
56+ // The context element seeds the tokenizer content mode.
57+ [
58+ "fragment, tr context" ,
59+ '<td class="c">cell <b>one</b> & two</td>' . repeat ( 6000 ) ,
60+ { fragmentContext : "tr" }
61+ ] ,
62+ [
63+ "wide table" ,
64+ ( ( ) => {
65+ let s = "<table>" ;
66+ for ( let i = 0 ; i < 8000 ; i ++ ) {
67+ s += `<tr><td class=c${ i } >cell ${ i } </td><td>${ i } </td></tr>` ;
68+ }
69+ return `${ s } </table>` ;
70+ } ) ( ) ,
71+ { }
72+ ] ,
73+ [
74+ "flat siblings" ,
75+ ( ( ) => {
76+ let s = "" ;
77+ for ( let i = 0 ; i < 8000 ; i ++ ) s += `<p class=c${ i } >text ${ i } </p>` ;
78+ return s ;
79+ } ) ( ) ,
80+ { }
81+ ] ,
82+ [
83+ "nested lists" ,
84+ ( ( ) => {
85+ let s = "" ;
86+ for ( let i = 0 ; i < 800 ; i ++ ) {
87+ s += `<ul id=u${ i } >` ;
88+ for ( let j = 0 ; j < 10 ; j ++ ) {
89+ s += `<li><a href=/p${ j } >item ${ j } </a></li>` ;
90+ }
91+ s += "</ul>" ;
92+ }
93+ return s ;
94+ } ) ( ) ,
95+ { }
96+ ] ,
97+ [
98+ "deep nesting" ,
99+ ( ( ) => {
100+ let s = "" ;
101+ for ( let i = 0 ; i < 800 ; i ++ ) {
102+ for ( let j = 0 ; j < 20 ; j ++ ) s += `<div class=l${ j } >` ;
103+ s += `<span>leaf ${ i } </span>` ;
104+ for ( let j = 0 ; j < 20 ; j ++ ) s += "</div>" ;
105+ }
106+ return s ;
107+ } ) ( ) ,
108+ { }
109+ ] ,
110+ // Elements that cannot print in pieces, so their subtrees are composed and
111+ // read back: a `<pre>` / `<textarea>` leading newline only survives
112+ // re-parsing once the children's text is in, and a `<template>` holds its
113+ // children in a content fragment rather than the child chain.
114+ [
115+ "composed subtrees" ,
116+ ( ( ) => {
117+ let s = "" ;
118+ for ( let i = 0 ; i < 2000 ; i ++ ) {
119+ s += `<div><pre>\n\nkeep ${ i } </pre><textarea>\n\nt ${ i } </textarea><template><p>x${ i } </p></template></div>` ;
120+ }
121+ return s ;
122+ } ) ( ) ,
123+ { }
124+ ] ,
125+ // An omitted `<html>` / `<body>` start tag, held back until the first thing
126+ // inside it decides whether it materializes (it does here — the text is
127+ // whitespace the insertion modes would otherwise drop).
128+ [
129+ "implied start tags" ,
130+ ( ( ) => {
131+ let s = " " ;
132+ for ( let i = 0 ; i < 4000 ; i ++ ) s += `<p>t${ i } <b>b${ i } </b>` ;
133+ return s ;
134+ } ) ( ) ,
135+ { }
136+ ]
137+ ] ;
101138
102139/**
103140 * @param {import("tinybench").Bench } bench bench
104141 * @returns {void }
105142 */
106143export default ( bench ) => {
107- // Whole-document minify — the entry `htmlMinify` drives.
108- bench . add (
109- 'unit benchmark "html-printer-document-unit", minify (document)' ,
110- ( ) => {
111- new SourceProcessor ( ) . process ( HTML , { minimize : true } ) ;
112- }
113- ) ;
114- bench . add (
115- 'unit benchmark "html-printer-document-unit", minify (fragment, tr context)' ,
116- ( ) => {
117- new SourceProcessor ( ) . process ( FRAGMENT , {
118- minimize : true ,
119- fragmentContext : "tr"
120- } ) ;
121- }
122- ) ;
123-
124- // Tree shapes: wide, flat, nested-wide, deep.
125- bench . add (
126- 'unit benchmark "html-printer-document-unit", minify (wide table)' ,
127- ( ) => {
128- new SourceProcessor ( ) . process ( TABLE , { minimize : true } ) ;
129- }
130- ) ;
131- bench . add (
132- 'unit benchmark "html-printer-document-unit", minify (flat siblings)' ,
133- ( ) => {
134- new SourceProcessor ( ) . process ( FLAT , { minimize : true } ) ;
135- }
136- ) ;
137- bench . add (
138- 'unit benchmark "html-printer-document-unit", minify (nested lists)' ,
139- ( ) => {
140- new SourceProcessor ( ) . process ( LISTS , { minimize : true } ) ;
141- }
142- ) ;
143- bench . add (
144- 'unit benchmark "html-printer-document-unit", minify (deep nesting)' ,
145- ( ) => {
146- new SourceProcessor ( ) . process ( DEEP , { minimize : true } ) ;
147- }
148- ) ;
149-
150- // The two paths a node can take, isolated.
151- bench . add (
152- 'unit benchmark "html-printer-document-unit", minify (composed subtrees)' ,
153- ( ) => {
154- new SourceProcessor ( ) . process ( WHOLE , { minimize : true } ) ;
155- }
156- ) ;
157- bench . add (
158- 'unit benchmark "html-printer-document-unit", minify (implied start tags)' ,
159- ( ) => {
160- new SourceProcessor ( ) . process ( IMPLIED , { minimize : true } ) ;
161- }
162- ) ;
144+ for ( const [ name , source , options ] of FIXTURES ) {
145+ bench . add (
146+ `unit benchmark "html-printer-document-unit", walk (${ name } )` ,
147+ ( ) => {
148+ new SourceProcessor ( ) . process ( source , options ) ;
149+ }
150+ ) ;
151+ bench . add (
152+ `unit benchmark "html-printer-document-unit", minify (${ name } )` ,
153+ ( ) => {
154+ new SourceProcessor ( ) . process ( source , { ...options , minimize : true } ) ;
155+ }
156+ ) ;
157+ }
163158} ;
0 commit comments