Fix performance of splitCssText (#1615)
- Fix bug where the right split point was not being picked for the 3rd section onwards - Fix that it wasn't able to find a split when both halves were identical - Add test to put splitCssText through it's paces with a large file - Introduce a limit on the iteration which causes the 'efficiently' test to fail - Fix poor 'crawling' performance in the 'matching' algorithm for large css texts - e.g. for a (doubled) benchmark.css, we were running `normalizeCssText` 9480 times before `k` got to the right place - Further algorithm efficiency: need to take larger jumps; use the scaling factor to make better guess at how big a jump to make
This commit is contained in:
@@ -456,8 +456,9 @@ export function normalizeCssString(cssText: string): string {
|
||||
|
||||
/**
|
||||
* Maps the output of stringifyStylesheet to individual text nodes of a <style> element
|
||||
* performance is not considered as this is anticipated to be very much an edge case
|
||||
* (javascript is needed to add extra text nodes to a <style>)
|
||||
* which occurs when javascript is used to append to the style element
|
||||
* and may also occur when browsers opt to break up large text nodes
|
||||
* performance needs to be considered, see e.g. #1603
|
||||
*/
|
||||
export function splitCssText(
|
||||
cssText: string,
|
||||
@@ -465,27 +466,69 @@ export function splitCssText(
|
||||
): string[] {
|
||||
const childNodes = Array.from(style.childNodes);
|
||||
const splits: string[] = [];
|
||||
let iterLimit = 0;
|
||||
if (childNodes.length > 1 && cssText && typeof cssText === 'string') {
|
||||
const cssTextNorm = normalizeCssString(cssText);
|
||||
let cssTextNorm = normalizeCssString(cssText);
|
||||
const normFactor = cssTextNorm.length / cssText.length;
|
||||
for (let i = 1; i < childNodes.length; i++) {
|
||||
if (
|
||||
childNodes[i].textContent &&
|
||||
typeof childNodes[i].textContent === 'string'
|
||||
) {
|
||||
const textContentNorm = normalizeCssString(childNodes[i].textContent!);
|
||||
for (let j = 3; j < textContentNorm.length; j++) {
|
||||
// find a substring that appears only once
|
||||
let j = 3;
|
||||
for (; j < textContentNorm.length; j++) {
|
||||
if (
|
||||
// keep consuming css identifiers (to get a decent chunk more quickly)
|
||||
textContentNorm[j].match(/[a-zA-Z0-9]/) ||
|
||||
// substring needs to be unique to this section
|
||||
textContentNorm.indexOf(textContentNorm.substring(0, j), 1) !== -1
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
for (; j < textContentNorm.length; j++) {
|
||||
const bit = textContentNorm.substring(0, j);
|
||||
if (cssTextNorm.split(bit).length === 2) {
|
||||
const splitNorm = cssTextNorm.indexOf(bit);
|
||||
// this substring should appears only once in overall text too
|
||||
const bits = cssTextNorm.split(bit);
|
||||
let splitNorm = -1;
|
||||
if (bits.length === 2) {
|
||||
splitNorm = cssTextNorm.indexOf(bit);
|
||||
} else if (
|
||||
bits.length > 2 &&
|
||||
bits[0] === '' &&
|
||||
childNodes[i - 1].textContent !== ''
|
||||
) {
|
||||
// this childNode has same starting content as previous
|
||||
splitNorm = cssTextNorm.indexOf(bit, 1);
|
||||
}
|
||||
if (splitNorm !== -1) {
|
||||
// find the split point in the original text
|
||||
for (let k = splitNorm; k < cssText.length; k++) {
|
||||
if (
|
||||
normalizeCssString(cssText.substring(0, k)).length === splitNorm
|
||||
) {
|
||||
let k = Math.floor(splitNorm / normFactor);
|
||||
for (; k > 0 && k < cssText.length; ) {
|
||||
iterLimit += 1;
|
||||
if (iterLimit > 50 * childNodes.length) {
|
||||
// quit for performance purposes
|
||||
splits.push(cssText);
|
||||
return splits;
|
||||
}
|
||||
const normPart = normalizeCssString(cssText.substring(0, k));
|
||||
if (normPart.length === splitNorm) {
|
||||
splits.push(cssText.substring(0, k));
|
||||
cssText = cssText.substring(k);
|
||||
cssTextNorm = cssTextNorm.substring(splitNorm);
|
||||
break;
|
||||
} else if (normPart.length < splitNorm) {
|
||||
k += Math.max(
|
||||
1,
|
||||
Math.floor((splitNorm - normPart.length) / normFactor),
|
||||
);
|
||||
} else {
|
||||
k -= Math.max(
|
||||
1,
|
||||
Math.floor((normPart.length - splitNorm) * normFactor),
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user