From d40ee6a259bc1dcb5070bc6a6523bced738f86cc Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Mon, 21 Oct 2019 22:56:31 -0700 Subject: [PATCH] perf(ivy): improve styling performance (#33326) change the existing implementation from using ``` string.split(/\s+/); ``` to a char scan which performers the same thing. The reason why `split(/\s+/)` is slow is that: - `/\s+/` allocates new `RegExp` every time this code executes. - `RegExp` scans are a lot more expensive because they are more powerful. PR Close #33326 --- .../core/src/render3/util/styling_utils.ts | 32 ++++++++++++++++--- .../test/render3/util/styling_utils_spec.ts | 26 +++++++++++++++ 2 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 packages/core/test/render3/util/styling_utils_spec.ts diff --git a/packages/core/src/render3/util/styling_utils.ts b/packages/core/src/render3/util/styling_utils.ts index 7bac893b7c369..d48019dd7e1ca 100644 --- a/packages/core/src/render3/util/styling_utils.ts +++ b/packages/core/src/render3/util/styling_utils.ts @@ -414,10 +414,8 @@ export function normalizeIntoStylingMap( let map: {[key: string]: any}|undefined|null; let allValuesTrue = false; if (typeof newValues === 'string') { // [class] bindings allow string values - if (newValues.length) { - props = newValues.split(/\s+/); - allValuesTrue = true; - } + props = splitOnWhitespace(newValues); + allValuesTrue = props !== null; } else { props = newValues ? Object.keys(newValues) : null; map = newValues; @@ -435,6 +433,32 @@ export function normalizeIntoStylingMap( return stylingMapArr; } +export function splitOnWhitespace(text: string): string[]|null { + let array: string[]|null = null; + let length = text.length; + let start = 0; + let foundChar = false; + for (let i = 0; i < length; i++) { + const char = text.charCodeAt(i); + if (char <= 32 /*' '*/) { + if (foundChar) { + if (array === null) array = []; + array.push(text.substring(start, i)); + foundChar = false; + } + start = i + 1; + } else { + foundChar = true; + } + } + if (foundChar) { + if (array === null) array = []; + array.push(text.substring(start, length)); + foundChar = false; + } + return array; +} + // TODO (matsko|AndrewKushnir): refactor this once we figure out how to generate separate // `input('class') + classMap()` instructions. export function selectClassBasedInputName(inputs: PropertyAliases): string { diff --git a/packages/core/test/render3/util/styling_utils_spec.ts b/packages/core/test/render3/util/styling_utils_spec.ts new file mode 100644 index 0000000000000..3267e680ce706 --- /dev/null +++ b/packages/core/test/render3/util/styling_utils_spec.ts @@ -0,0 +1,26 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {splitOnWhitespace} from '@angular/core/src/render3/util/styling_utils'; + +describe('styling_utils', () => { + describe('splitOnWhitespace', () => { + it('should treat empty strings as null', () => { + expect(splitOnWhitespace('')).toEqual(null); + expect(splitOnWhitespace(' ')).toEqual(null); + expect(splitOnWhitespace(' \n\r\t ')).toEqual(null); + }); + + it('should split strings into parts', () => { + expect(splitOnWhitespace('a\nb\rc')).toEqual(['a', 'b', 'c']); + expect(splitOnWhitespace('\ta-long\nb-long\rc-long ')).toEqual([ + 'a-long', 'b-long', 'c-long' + ]); + }); + }); +}); \ No newline at end of file