Skip to content

Commit a2463ca

Browse files
perf: make attribute parser faster (#521)
1 parent 6fa80d5 commit a2463ca

8 files changed

+305
-36
lines changed

src/plugins/sources-plugin.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import {
77
webpackIgnoreCommentRegexp,
88
} from "../utils";
99

10+
const DOUBLE_QUOTE = '"'.charCodeAt(0);
11+
const SINGLE_QUOTE = "'".charCodeAt(0);
12+
1013
export default (options) =>
1114
function process(html) {
1215
const sources = [];
@@ -71,8 +74,10 @@ export default (options) =>
7174
sourceCodeLocation.attrs[name].endOffset,
7275
);
7376
const isValueQuoted =
74-
attributeAndValue[attributeAndValue.length - 1] === '"' ||
75-
attributeAndValue[attributeAndValue.length - 1] === "'";
77+
attributeAndValue.charCodeAt(attributeAndValue.length - 1) ===
78+
DOUBLE_QUOTE ||
79+
attributeAndValue.charCodeAt(attributeAndValue.length - 1) ===
80+
SINGLE_QUOTE;
7681
const valueStartOffset =
7782
sourceCodeLocation.attrs[name].startOffset +
7883
attributeAndValue.indexOf(attribute.value);

src/utils.js

+48-28
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,24 @@ import path from "path";
22

33
import HtmlSourceError from "./HtmlSourceError";
44

5+
const HORIZONTAL_TAB = "\u0009".charCodeAt(0);
6+
const NEWLINE = "\u000A".charCodeAt(0);
7+
const FORM_FEED = "\u000C".charCodeAt(0);
8+
const CARRIAGE_RETURN = "\u000D".charCodeAt(0);
9+
const SPACE = "\u0020".charCodeAt(0);
10+
511
function isASCIIWhitespace(character) {
612
return (
713
// Horizontal tab
8-
character === "\u0009" ||
14+
character === HORIZONTAL_TAB ||
915
// New line
10-
character === "\u000A" ||
16+
character === NEWLINE ||
1117
// Form feed
12-
character === "\u000C" ||
18+
character === FORM_FEED ||
1319
// Carriage return
14-
character === "\u000D" ||
20+
character === CARRIAGE_RETURN ||
1521
// Space
16-
character === "\u0020"
22+
character === SPACE
1723
);
1824
}
1925

@@ -26,6 +32,12 @@ const regexLeadingCommasOrSpaces = /^[, \t\n\r\u000c]+/;
2632
const regexLeadingNotSpaces = /^[^ \t\n\r\u000c]+/;
2733
const regexTrailingCommas = /[,]+$/;
2834
const regexNonNegativeInteger = /^\d+$/;
35+
const COMMA = ",".charCodeAt(0);
36+
const LEFT_PARENTHESIS = "(".charCodeAt(0);
37+
const RIGHT_PARENTHESIS = ")".charCodeAt(0);
38+
const SMALL_LETTER_W = "w".charCodeAt(0);
39+
const SMALL_LETTER_X = "x".charCodeAt(0);
40+
const SMALL_LETTER_H = "h".charCodeAt(0);
2941

3042
// ( Positive or negative or unsigned integers or decimals, without or without exponents.
3143
// Must include at least one digit.
@@ -93,7 +105,7 @@ export function parseSrcset(input) {
93105
// 8. If url ends with a U+002C COMMA character (,), follow these sub steps:
94106
// (1). Remove all trailing U+002C COMMA characters from url. If this removed
95107
// more than one character, that is a parse error.
96-
if (url.slice(-1) === ",") {
108+
if (url.charCodeAt(url.length - 1) === COMMA) {
97109
url = url.replace(regexTrailingCommas, "");
98110

99111
// (Jump ahead to step 9 to skip tokenization and just push the candidate).
@@ -124,7 +136,7 @@ export function parseSrcset(input) {
124136
// eslint-disable-next-line no-constant-condition
125137
while (true) {
126138
// 8.4. Let c be the character at position.
127-
c = input.charAt(position);
139+
c = input.charCodeAt(position);
128140

129141
// Do the following depending on the value of state.
130142
// For the purpose of this step, "EOF" is a special character representing
@@ -149,7 +161,7 @@ export function parseSrcset(input) {
149161
// Advance position to the next character in input. If current descriptor
150162
// is not empty, append current descriptor to descriptors. Jump to the step
151163
// labeled descriptor parser.
152-
else if (c === ",") {
164+
else if (c === COMMA) {
153165
position += 1;
154166

155167
if (currentDescriptor) {
@@ -162,14 +174,14 @@ export function parseSrcset(input) {
162174
}
163175
// U+0028 LEFT PARENTHESIS (()
164176
// Append c to current descriptor. Set state to in parens.
165-
else if (c === "\u0028") {
166-
currentDescriptor += c;
177+
else if (c === LEFT_PARENTHESIS) {
178+
currentDescriptor += input.charAt(position);
167179
state = "in parens";
168180
}
169181
// EOF
170182
// If current descriptor is not empty, append current descriptor to
171183
// descriptors. Jump to the step labeled descriptor parser.
172-
else if (c === "") {
184+
else if (isNaN(c)) {
173185
if (currentDescriptor) {
174186
descriptors.push(currentDescriptor);
175187
}
@@ -181,29 +193,29 @@ export function parseSrcset(input) {
181193
// Anything else
182194
// Append c to current descriptor.
183195
} else {
184-
currentDescriptor += c;
196+
currentDescriptor += input.charAt(position);
185197
}
186198
}
187199
// In parens
188200
else if (state === "in parens") {
189201
// U+0029 RIGHT PARENTHESIS ())
190202
// Append c to current descriptor. Set state to in descriptor.
191-
if (c === ")") {
192-
currentDescriptor += c;
203+
if (c === RIGHT_PARENTHESIS) {
204+
currentDescriptor += input.charAt(position);
193205
state = "in descriptor";
194206
}
195207
// EOF
196208
// Append current descriptor to descriptors. Jump to the step labeled
197209
// descriptor parser.
198-
else if (c === "") {
210+
else if (isNaN(c)) {
199211
descriptors.push(currentDescriptor);
200212
parseDescriptors();
201213
return;
202214
}
203215
// Anything else
204216
// Append c to current descriptor.
205217
else {
206-
currentDescriptor += c;
218+
currentDescriptor += input.charAt(position);
207219
}
208220
}
209221
// After descriptor
@@ -213,7 +225,7 @@ export function parseSrcset(input) {
213225
// Space character: Stay in this state.
214226
}
215227
// EOF: Jump to the step labeled descriptor parser.
216-
else if (c === "") {
228+
else if (isNaN(c)) {
217229
parseDescriptors();
218230
return;
219231
}
@@ -258,14 +270,14 @@ export function parseSrcset(input) {
258270
for (i = 0; i < descriptors.length; i++) {
259271
desc = descriptors[i];
260272

261-
lastChar = desc[desc.length - 1];
273+
lastChar = desc[desc.length - 1].charCodeAt(0);
262274
value = desc.substring(0, desc.length - 1);
263275
intVal = parseInt(value, 10);
264276
floatVal = parseFloat(value);
265277

266278
// If the descriptor consists of a valid non-negative integer followed by
267279
// a U+0077 LATIN SMALL LETTER W character
268-
if (regexNonNegativeInteger.test(value) && lastChar === "w") {
280+
if (regexNonNegativeInteger.test(value) && lastChar === SMALL_LETTER_W) {
269281
// If width and density are not both absent, then let error be yes.
270282
if (w || d) {
271283
pError = true;
@@ -282,7 +294,7 @@ export function parseSrcset(input) {
282294
}
283295
// If the descriptor consists of a valid floating-point number followed by
284296
// a U+0078 LATIN SMALL LETTER X character
285-
else if (regexFloatingPoint.test(value) && lastChar === "x") {
297+
else if (regexFloatingPoint.test(value) && lastChar === SMALL_LETTER_X) {
286298
// If width, density and future-compat-h are not all absent, then let error
287299
// be yes.
288300
if (w || d || h) {
@@ -300,7 +312,10 @@ export function parseSrcset(input) {
300312
}
301313
// If the descriptor consists of a valid non-negative integer followed by
302314
// a U+0068 LATIN SMALL LETTER H character
303-
else if (regexNonNegativeInteger.test(value) && lastChar === "h") {
315+
else if (
316+
regexNonNegativeInteger.test(value) &&
317+
lastChar === SMALL_LETTER_H
318+
) {
304319
// If height and density are not both absent, then let error be yes.
305320
if (h || d) {
306321
pError = true;
@@ -354,14 +369,18 @@ export function parseSrc(input) {
354369
}
355370

356371
let start = 0;
357-
for (; start < input.length && isASCIIWhitespace(input[start]); start++);
372+
for (
373+
;
374+
start < input.length && isASCIIWhitespace(input.charCodeAt(start));
375+
start++
376+
);
358377

359378
if (start === input.length) {
360379
throw new Error("Must be non-empty");
361380
}
362381

363382
let end = input.length - 1;
364-
for (; end > -1 && isASCIIWhitespace(input[end]); end--);
383+
for (; end > -1 && isASCIIWhitespace(input.charCodeAt(end)); end--);
365384
end += 1;
366385

367386
let value = input;
@@ -430,12 +449,13 @@ export function isURLRequestable(url, options = {}) {
430449

431450
const WINDOWS_PATH_SEPARATOR_REGEXP = /\\/g;
432451
const RELATIVE_PATH_REGEXP = /^\.\.?[/\\]/;
452+
const SLASH = "/".charCodeAt(0);
433453

434454
const absoluteToRequest = (context, maybeAbsolutePath) => {
435-
if (maybeAbsolutePath[0] === "/") {
455+
if (maybeAbsolutePath.charCodeAt(0) === SLASH) {
436456
if (
437457
maybeAbsolutePath.length > 1 &&
438-
maybeAbsolutePath[maybeAbsolutePath.length - 1] === "/"
458+
maybeAbsolutePath.charCodeAt(maybeAbsolutePath.length - 1) === SLASH
439459
) {
440460
// this 'path' is actually a regexp generated by dynamic requires.
441461
// Don't treat it as an absolute path.
@@ -505,7 +525,7 @@ export function requestify(context, request) {
505525
.replace(/[\t\n\r]/g, "")
506526
.replace(/\\/g, "/");
507527

508-
if (isWindowsAbsolutePath || newRequest[0] === "/") {
528+
if (isWindowsAbsolutePath || newRequest.charCodeAt(0) === SLASH) {
509529
return newRequest;
510530
}
511531

@@ -1240,7 +1260,7 @@ export function getImportCode(html, loaderContext, imports, options) {
12401260
return `// Imports\n${code}`;
12411261
}
12421262

1243-
const SLASH = "\\".charCodeAt(0);
1263+
const BACKSLASH = "\\".charCodeAt(0);
12441264
const BACKTICK = "`".charCodeAt(0);
12451265
const DOLLAR = "$".charCodeAt(0);
12461266

@@ -1251,7 +1271,7 @@ export function convertToTemplateLiteral(str) {
12511271
const code = str.charCodeAt(i);
12521272

12531273
escapedString +=
1254-
code === SLASH || code === BACKTICK || code === DOLLAR
1274+
code === BACKSLASH || code === BACKTICK || code === DOLLAR
12551275
? `\\${str[i]}`
12561276
: str[i];
12571277
}

test/__snapshots__/esModule-option.test.js.snap

+30
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ var ___HTML_LOADER_IMPORT_23___ = new URL("./video.mp4", import.meta.url);
3232
var ___HTML_LOADER_IMPORT_24___ = new URL("./nested/image3.png", import.meta.url);
3333
var ___HTML_LOADER_IMPORT_25___ = new URL("/nested/image3.png", import.meta.url);
3434
var ___HTML_LOADER_IMPORT_26___ = new URL("./noscript.png", import.meta.url);
35+
var ___HTML_LOADER_IMPORT_27___ = new URL("./😀abc.png", import.meta.url);
3536
// Module
3637
var ___HTML_LOADER_REPLACEMENT_0___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_0___);
3738
var ___HTML_LOADER_REPLACEMENT_1___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_0___, { maybeNeedQuotes: true });
@@ -69,6 +70,7 @@ var ___HTML_LOADER_REPLACEMENT_32___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(
6970
var ___HTML_LOADER_REPLACEMENT_33___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_24___);
7071
var ___HTML_LOADER_REPLACEMENT_34___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_25___);
7172
var ___HTML_LOADER_REPLACEMENT_35___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_26___);
73+
var ___HTML_LOADER_REPLACEMENT_36___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_27___);
7274
var code = \`<!doctype html>
7375

7476
<h1>My First Heading</h1>
@@ -518,6 +520,10 @@ alt" />
518520
</noscript>
519521

520522
<img src="https://raw.githubusercontent.com/webpack-contrib/html-loader/master/test/fixtures/image.png">
523+
<img src="\${___HTML_LOADER_REPLACEMENT_36___}" alt="Smiley face">
524+
<img src="\${___HTML_LOADER_REPLACEMENT_36___}" alt="Smiley face">
525+
<img srcset="\${___HTML_LOADER_REPLACEMENT_36___} 480w, \${___HTML_LOADER_REPLACEMENT_0___} 800w" sizes="(max-width: 600px) 480px, 800px" src="\${___HTML_LOADER_REPLACEMENT_0___}" alt="Elva dressed as a fairy">
526+
<img srcset="\${___HTML_LOADER_REPLACEMENT_0___} 480w, \${___HTML_LOADER_REPLACEMENT_36___} 800w" sizes="(max-width: 600px) 480px, 800px" src="\${___HTML_LOADER_REPLACEMENT_0___}" alt="Elva dressed as a fairy">
521527
\`;
522528
// Exports
523529
export default code;"
@@ -973,6 +979,10 @@ alt" />
973979
</noscript>
974980

975981
<img src="https://raw.githubusercontent.com/webpack-contrib/html-loader/master/test/fixtures/image.png">
982+
<img src="replaced_file_protocol_/webpack/public/path/%F0%9F%98%80abc.png" alt="Smiley face">
983+
<img src="replaced_file_protocol_/webpack/public/path/%F0%9F%98%80abc.png" alt="Smiley face">
984+
<img srcset="replaced_file_protocol_/webpack/public/path/%F0%9F%98%80abc.png 480w, replaced_file_protocol_/webpack/public/path/image.png 800w" sizes="(max-width: 600px) 480px, 800px" src="replaced_file_protocol_/webpack/public/path/image.png" alt="Elva dressed as a fairy">
985+
<img srcset="replaced_file_protocol_/webpack/public/path/image.png 480w, replaced_file_protocol_/webpack/public/path/%F0%9F%98%80abc.png 800w" sizes="(max-width: 600px) 480px, 800px" src="replaced_file_protocol_/webpack/public/path/image.png" alt="Elva dressed as a fairy">
976986
"
977987
`;
978988

@@ -1008,6 +1018,7 @@ var ___HTML_LOADER_IMPORT_21___ = require("./video.mp4");
10081018
var ___HTML_LOADER_IMPORT_22___ = require("./nested/image3.png");
10091019
var ___HTML_LOADER_IMPORT_23___ = require("/nested/image3.png");
10101020
var ___HTML_LOADER_IMPORT_24___ = require("./noscript.png");
1021+
var ___HTML_LOADER_IMPORT_25___ = require("./😀abc.png");
10111022
// Module
10121023
var ___HTML_LOADER_REPLACEMENT_0___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_0___);
10131024
var ___HTML_LOADER_REPLACEMENT_1___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_0___, { maybeNeedQuotes: true });
@@ -1043,6 +1054,7 @@ var ___HTML_LOADER_REPLACEMENT_30___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(
10431054
var ___HTML_LOADER_REPLACEMENT_31___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_22___);
10441055
var ___HTML_LOADER_REPLACEMENT_32___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_23___);
10451056
var ___HTML_LOADER_REPLACEMENT_33___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_24___);
1057+
var ___HTML_LOADER_REPLACEMENT_34___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_25___);
10461058
var code = \`<!doctype html>
10471059

10481060
<h1>My First Heading</h1>
@@ -1495,6 +1507,10 @@ ANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4
14951507
</noscript>
14961508

14971509
<img src="https://raw.githubusercontent.com/webpack-contrib/html-loader/master/test/fixtures/image.png">
1510+
<img src="\${___HTML_LOADER_REPLACEMENT_34___}" alt="Smiley face">
1511+
<img src="\${___HTML_LOADER_REPLACEMENT_34___}" alt="Smiley face">
1512+
<img srcset="\${___HTML_LOADER_REPLACEMENT_34___} 480w, \${___HTML_LOADER_REPLACEMENT_0___} 800w" sizes="(max-width: 600px) 480px, 800px" src="\${___HTML_LOADER_REPLACEMENT_0___}" alt="Elva dressed as a fairy">
1513+
<img srcset="\${___HTML_LOADER_REPLACEMENT_0___} 480w, \${___HTML_LOADER_REPLACEMENT_34___} 800w" sizes="(max-width: 600px) 480px, 800px" src="\${___HTML_LOADER_REPLACEMENT_0___}" alt="Elva dressed as a fairy">
14981514
\`;
14991515
// Exports
15001516
module.exports = code;"
@@ -1953,6 +1969,10 @@ ANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4
19531969
</noscript>
19541970

19551971
<img src="https://raw.githubusercontent.com/webpack-contrib/html-loader/master/test/fixtures/image.png">
1972+
<img src="/webpack/public/path/😀abc.png" alt="Smiley face">
1973+
<img src="/webpack/public/path/😀abc.png" alt="Smiley face">
1974+
<img srcset="/webpack/public/path/😀abc.png 480w, /webpack/public/path/image.png 800w" sizes="(max-width: 600px) 480px, 800px" src="/webpack/public/path/image.png" alt="Elva dressed as a fairy">
1975+
<img srcset="/webpack/public/path/image.png 480w, /webpack/public/path/😀abc.png 800w" sizes="(max-width: 600px) 480px, 800px" src="/webpack/public/path/image.png" alt="Elva dressed as a fairy">
19561976
"
19571977
`;
19581978

@@ -1990,6 +2010,7 @@ var ___HTML_LOADER_IMPORT_23___ = new URL("./video.mp4", import.meta.url);
19902010
var ___HTML_LOADER_IMPORT_24___ = new URL("./nested/image3.png", import.meta.url);
19912011
var ___HTML_LOADER_IMPORT_25___ = new URL("/nested/image3.png", import.meta.url);
19922012
var ___HTML_LOADER_IMPORT_26___ = new URL("./noscript.png", import.meta.url);
2013+
var ___HTML_LOADER_IMPORT_27___ = new URL("./😀abc.png", import.meta.url);
19932014
// Module
19942015
var ___HTML_LOADER_REPLACEMENT_0___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_0___);
19952016
var ___HTML_LOADER_REPLACEMENT_1___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_0___, { maybeNeedQuotes: true });
@@ -2027,6 +2048,7 @@ var ___HTML_LOADER_REPLACEMENT_32___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(
20272048
var ___HTML_LOADER_REPLACEMENT_33___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_24___);
20282049
var ___HTML_LOADER_REPLACEMENT_34___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_25___);
20292050
var ___HTML_LOADER_REPLACEMENT_35___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_26___);
2051+
var ___HTML_LOADER_REPLACEMENT_36___ = ___HTML_LOADER_GET_SOURCE_FROM_IMPORT___(___HTML_LOADER_IMPORT_27___);
20302052
var code = \`<!doctype html>
20312053

20322054
<h1>My First Heading</h1>
@@ -2476,6 +2498,10 @@ alt" />
24762498
</noscript>
24772499

24782500
<img src="https://raw.githubusercontent.com/webpack-contrib/html-loader/master/test/fixtures/image.png">
2501+
<img src="\${___HTML_LOADER_REPLACEMENT_36___}" alt="Smiley face">
2502+
<img src="\${___HTML_LOADER_REPLACEMENT_36___}" alt="Smiley face">
2503+
<img srcset="\${___HTML_LOADER_REPLACEMENT_36___} 480w, \${___HTML_LOADER_REPLACEMENT_0___} 800w" sizes="(max-width: 600px) 480px, 800px" src="\${___HTML_LOADER_REPLACEMENT_0___}" alt="Elva dressed as a fairy">
2504+
<img srcset="\${___HTML_LOADER_REPLACEMENT_0___} 480w, \${___HTML_LOADER_REPLACEMENT_36___} 800w" sizes="(max-width: 600px) 480px, 800px" src="\${___HTML_LOADER_REPLACEMENT_0___}" alt="Elva dressed as a fairy">
24792505
\`;
24802506
// Exports
24812507
export default code;"
@@ -2931,6 +2957,10 @@ alt" />
29312957
</noscript>
29322958

29332959
<img src="https://raw.githubusercontent.com/webpack-contrib/html-loader/master/test/fixtures/image.png">
2960+
<img src="replaced_file_protocol_/webpack/public/path/%F0%9F%98%80abc.png" alt="Smiley face">
2961+
<img src="replaced_file_protocol_/webpack/public/path/%F0%9F%98%80abc.png" alt="Smiley face">
2962+
<img srcset="replaced_file_protocol_/webpack/public/path/%F0%9F%98%80abc.png 480w, replaced_file_protocol_/webpack/public/path/image.png 800w" sizes="(max-width: 600px) 480px, 800px" src="replaced_file_protocol_/webpack/public/path/image.png" alt="Elva dressed as a fairy">
2963+
<img srcset="replaced_file_protocol_/webpack/public/path/image.png 480w, replaced_file_protocol_/webpack/public/path/%F0%9F%98%80abc.png 800w" sizes="(max-width: 600px) 480px, 800px" src="replaced_file_protocol_/webpack/public/path/image.png" alt="Elva dressed as a fairy">
29342964
"
29352965
`;
29362966

0 commit comments

Comments
 (0)