Skip to content

Commit bb0e05a

Browse files
authored
fix: Synchronous HtmlDiff (#527)
1 parent 783452a commit bb0e05a

File tree

5 files changed

+118
-126
lines changed

5 files changed

+118
-126
lines changed

lib/cli.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export function run(argv) {
9797
const options = defaults(config);
9898
const htmlDiffer = new HtmlDiffer(options);
9999

100-
const diff = await htmlDiffer.diffHtml(html1, html2);
100+
const diff = htmlDiffer.diffHtml(html1, html2);
101101

102102
const loggerOptions = {
103103
charsAroundDiff: opts.charsAroundDiff,

lib/index.js

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import modifyHtmlAccordingToOptions from './utils/modify.js';
33
import { defaults } from './defaults.js';
44
import handleMasks from './utils/mask.js';
55

6-
async function getModifiedTokens(html, options) {
7-
const tokens = await modifyHtmlAccordingToOptions(html, options);
6+
function getModifiedTokens(html, options) {
7+
const tokens = modifyHtmlAccordingToOptions(html, options);
88
return tokens.split(/({{.+?}}(?!})|[{}\(\)\[\]#\*`=:;,.<>"'\/]|\s+)/).filter(i => i);
99
}
1010

@@ -32,12 +32,11 @@ export class HtmlDiffer {
3232
* @param {String} html2
3333
* @returns {Diff}
3434
*/
35-
async diffHtml(html1, html2) {
36-
// precompute the tokenized html here, since it can't do it in `Diff.tokenize()` because
37-
// the sax parser is async
35+
diffHtml(html1, html2) {
36+
// precompute the tokenized html
3837
const tokens = {
39-
html1: await getModifiedTokens(html1, this.options),
40-
html2: await getModifiedTokens(html2, this.options),
38+
html1: getModifiedTokens(html1, this.options),
39+
html2: getModifiedTokens(html2, this.options),
4140
};
4241

4342
const htmlDiffer = new HtmlDiff(this.options, tokens);
@@ -57,12 +56,12 @@ export class HtmlDiffer {
5756
* @param {String} html2
5857
* @returns {Boolean}
5958
*/
60-
async isEqual(html1, html2) {
59+
isEqual(html1, html2) {
6160
if (html1 === html2) {
6261
return true;
6362
}
6463

65-
const diff = await this.diffHtml(html1, html2);
64+
const diff = this.diffHtml(html1, html2);
6665

6766
return (diff.length === 1 && !diff[0].added && !diff[0].removed);
6867
}

lib/utils/modify.js

Lines changed: 88 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -12,102 +12,95 @@ import * as utils from './utils.js';
1212
* @param {Boolean} [options.ignoreComments=true]
1313
* @param {Boolean} [options.ignoreEndTags=false]
1414
* @param {Boolean} [options.ignoreSelfClosingSlash=false]
15-
* returns {String}
15+
* @returns {String}
1616
*/
1717
export default function modify(value, options) {
18-
return new Promise((resolve, reject) => {
19-
const modifiedValues = [];
20-
const parser = new SAXParser();
21-
22-
/**
23-
* @param {object} doctypeToken
24-
* @param {String} doctypeToken.name
25-
* @param {String} doctypeToken.publicId
26-
* @param {String} doctypeToken.systemId
27-
*/
28-
parser.on('doctype', function(doctypeToken) {
29-
const name = doctypeToken.name;
30-
const publicId = doctypeToken.publicId;
31-
const systemId = doctypeToken.systemId;
32-
33-
modifiedValues.push(serialize.doctype(name, publicId, systemId));
34-
});
35-
36-
/**
37-
* @param {object} startTagToken
38-
* @param {String} startTagToken.tagName
39-
* @param {Array} startTagToken.attrs
40-
* @param {Boolean} startTagToken.selfClosing
41-
*/
42-
parser.on('startTag', function(startTagToken) {
43-
let attrs = startTagToken.attrs;
44-
let selfClosing = startTagToken.selfClosing;
45-
const tagName = startTagToken.tagName;
46-
47-
if (options.ignoreSelfClosingSlash) {
48-
selfClosing = false;
49-
}
50-
51-
attrs = utils.sortAttrs(attrs)
52-
&& utils.sortCssClasses(attrs)
53-
&& utils.sortAttrsValues(attrs, options.compareAttributesAsJSON)
54-
&& utils.removeAttrsValues(attrs, options.ignoreAttributes);
55-
56-
modifiedValues.push(serialize.startTag(tagName, attrs, selfClosing));
57-
});
58-
59-
/**
60-
* @param {object} endTagToken
61-
* @param {String} endTagToken.tagName
62-
*/
63-
parser.on('endTag', function(endTagToken) {
64-
const tagName = endTagToken.tagName;
65-
66-
if (!options.ignoreEndTags) {
67-
modifiedValues.push(serialize.endTag(tagName));
68-
}
69-
});
70-
71-
/**
72-
* @param {object} textToken
73-
* @param {String} textToken.text
74-
*/
75-
parser.on('text', function(textToken) {
76-
let text = textToken.text;
77-
78-
if (options.ignoreWhitespaces) {
79-
text = utils.removeWhitespaces(text);
80-
}
81-
82-
modifiedValues.push(serialize.text(text));
83-
});
84-
85-
/**
86-
* @param {object} commentToken
87-
* @param {String} commentToken.text
88-
*/
89-
parser.on('comment', function(commentToken) {
90-
modifiedValues.push(new Promise(resolve => {
91-
const comment = commentToken.text;
92-
resolve(utils.getConditionalComment(comment, modify, options));
93-
}).then(conditionalComment => {
94-
if (conditionalComment) {
95-
return serialize.comment(conditionalComment);
96-
} else if (!options.ignoreComments) {
97-
return serialize.comment(commentToken.text);
98-
}
99-
}));
100-
});
101-
102-
parser.on('end', async function() {
103-
const values = await Promise.all(modifiedValues);
104-
resolve(values.join(''));
105-
});
106-
107-
parser.on('error', function(err) {
108-
reject(err);
109-
});
110-
111-
parser.end(value);
18+
const modifiedValues = [];
19+
const parser = new SAXParser();
20+
21+
/**
22+
* @param {object} doctypeToken
23+
* @param {String} doctypeToken.name
24+
* @param {String} doctypeToken.publicId
25+
* @param {String} doctypeToken.systemId
26+
*/
27+
parser.on('doctype', function(doctypeToken) {
28+
const name = doctypeToken.name;
29+
const publicId = doctypeToken.publicId;
30+
const systemId = doctypeToken.systemId;
31+
32+
modifiedValues.push(serialize.doctype(name, publicId, systemId));
11233
});
34+
35+
/**
36+
* @param {object} startTagToken
37+
* @param {String} startTagToken.tagName
38+
* @param {Array} startTagToken.attrs
39+
* @param {Boolean} startTagToken.selfClosing
40+
*/
41+
parser.on('startTag', function(startTagToken) {
42+
let attrs = startTagToken.attrs;
43+
let selfClosing = startTagToken.selfClosing;
44+
const tagName = startTagToken.tagName;
45+
46+
if (options.ignoreSelfClosingSlash) {
47+
selfClosing = false;
48+
}
49+
50+
attrs = utils.sortAttrs(attrs)
51+
&& utils.sortCssClasses(attrs)
52+
&& utils.sortAttrsValues(attrs, options.compareAttributesAsJSON)
53+
&& utils.removeAttrsValues(attrs, options.ignoreAttributes);
54+
55+
modifiedValues.push(serialize.startTag(tagName, attrs, selfClosing));
56+
});
57+
58+
/**
59+
* @param {object} endTagToken
60+
* @param {String} endTagToken.tagName
61+
*/
62+
parser.on('endTag', function(endTagToken) {
63+
const tagName = endTagToken.tagName;
64+
65+
if (!options.ignoreEndTags) {
66+
modifiedValues.push(serialize.endTag(tagName));
67+
}
68+
});
69+
70+
/**
71+
* @param {object} textToken
72+
* @param {String} textToken.text
73+
*/
74+
parser.on('text', function(textToken) {
75+
let text = textToken.text;
76+
77+
if (options.ignoreWhitespaces) {
78+
text = utils.removeWhitespaces(text);
79+
}
80+
81+
modifiedValues.push(serialize.text(text));
82+
});
83+
84+
/**
85+
* @param {object} commentToken
86+
* @param {String} commentToken.text
87+
*/
88+
parser.on('comment', function(commentToken) {
89+
const comment = commentToken.text;
90+
const conditionalComment = utils.getConditionalComment(comment, modify, options);
91+
if (conditionalComment) {
92+
modifiedValues.push(serialize.comment(conditionalComment));
93+
} else if (!options.ignoreComments) {
94+
modifiedValues.push(serialize.comment(comment));
95+
}
96+
});
97+
98+
parser.on('error', function(err) {
99+
throw err;
100+
});
101+
102+
parser.end(value);
103+
104+
// Since the parser is used synchronously, it's possible to get the final value after `end` call.
105+
return modifiedValues.join('');
113106
}

lib/utils/utils.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ export function removeWhitespaces(text) {
173173
* @param {Object} options
174174
* @returns {String|undefined}
175175
*/
176-
export async function getConditionalComment(comment, modify, options) {
176+
export function getConditionalComment(comment, modify, options) {
177177
const START_IF = '^\\s*\\[if .*\\]>';
178178
const END_IF = '<!\\[endif\\]\\s*$';
179179
const matchedStartIF = comment.match(new RegExp(START_IF));
@@ -186,7 +186,7 @@ export async function getConditionalComment(comment, modify, options) {
186186
if (matchedStartIF && matchedEndIF) {
187187
const start = matchedStartIF[0];
188188
const end = matchedEndIF[0];
189-
const modified = await modify(comment.substring(start.length, matchedEndIF.index), options);
189+
const modified = modify(comment.substring(start.length, matchedEndIF.index), options);
190190

191191
return (start + modified + end).trim();
192192
}

0 commit comments

Comments
 (0)