Skip to content

Commit

Permalink
test(ivy): support className in micro benchmarks (#33392)
Browse files Browse the repository at this point in the history
The styling algorithm requires that the `RNode` has a `className`
property in order to execute the fast-path. This changes adds the
emulation of this property.

PR Close #33392
  • Loading branch information
mhevery authored and AndrewKushnir committed Oct 25, 2019
1 parent b381497 commit 6323a35
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 13 deletions.
1 change: 1 addition & 0 deletions karma-js.conf.js
Expand Up @@ -79,6 +79,7 @@ module.exports = function(config) {
'dist/all/@angular/compiler/test/render3/**',
'dist/all/@angular/core/test/bundling/**',
'dist/all/@angular/core/test/render3/ivy/**',
'dist/all/@angular/core/test/render3/perf/**',
'dist/all/@angular/elements/schematics/**',
'dist/all/@angular/examples/**/e2e_test/*',
'dist/all/@angular/language-service/**',
Expand Down
10 changes: 9 additions & 1 deletion packages/core/test/render3/perf/BUILD.bazel
@@ -1,6 +1,6 @@
package(default_visibility = ["//visibility:private"])

load("//tools:defaults.bzl", "ng_rollup_bundle", "ts_library")
load("//tools:defaults.bzl", "jasmine_node_test", "ng_rollup_bundle", "ts_library")

ts_library(
name = "perf_lib",
Expand All @@ -9,10 +9,18 @@ ts_library(
),
deps = [
"//packages/core",
"@npm//@types/jasmine",
"@npm//@types/node",
],
)

jasmine_node_test(
name = "perf",
deps = [
":perf_lib",
],
)

ng_rollup_bundle(
name = "class_binding",
entry_point = ":class_binding/index.ts",
Expand Down
60 changes: 50 additions & 10 deletions packages/core/test/render3/perf/noop_renderer.ts
Expand Up @@ -7,23 +7,24 @@
*/
import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, RendererFactory3, RendererStyleFlags3} from '../../../src/render3/interfaces/renderer';

export class WebWorkerRenderNode implements RNode, RComment, RText {
export class MicroBenchmarkRenderNode implements RNode, RComment, RText {
textContent: string|null = null;
parentNode: RNode|null = null;
parentElement: RElement|null = null;
nextSibling: RNode|null = null;
removeChild(oldChild: RNode): RNode { return oldChild; }
insertBefore(newChild: RNode, refChild: RNode|null, isViewRoot: boolean): void {}
appendChild(newChild: RNode): RNode { return newChild; }
className: string = '';
}

export class NoopRenderer implements ProceduralRenderer3 {
export class MicroBenchmarkRenderer implements ProceduralRenderer3 {
destroy(): void { throw new Error('Method not implemented.'); }
createComment(value: string): RComment { return new WebWorkerRenderNode(); }
createComment(value: string): RComment { return new MicroBenchmarkRenderNode(); }
createElement(name: string, namespace?: string|null|undefined): RElement {
return new WebWorkerRenderNode() as any as RElement;
return new MicroBenchmarkRenderNode() as any as RElement;
}
createText(value: string): RText { return new WebWorkerRenderNode(); }
createText(value: string): RText { return new MicroBenchmarkRenderNode(); }
destroyNode?: ((node: RNode) => void)|null|undefined;
appendChild(parent: RElement, newChild: RNode): void {}
insertBefore(parent: RNode, newChild: RNode, refChild: RNode|null): void {}
Expand All @@ -32,10 +33,21 @@ export class NoopRenderer implements ProceduralRenderer3 {
parentNode(node: RNode): RElement|null { throw new Error('Method not implemented.'); }
nextSibling(node: RNode): RNode|null { throw new Error('Method not implemented.'); }
setAttribute(el: RElement, name: string, value: string, namespace?: string|null|undefined): void {
if (name === 'class' && isOurNode(el)) {
el.className = value;
}
}
removeAttribute(el: RElement, name: string, namespace?: string|null|undefined): void {}
addClass(el: RElement, name: string): void {}
removeClass(el: RElement, name: string): void {}
addClass(el: RElement, name: string): void {
if (isOurNode(el)) {
el.className = el.className === '' ? name : remove(el.className, name) + ' ' + name;
}
}
removeClass(el: RElement, name: string): void {
if (isOurNode(el)) {
el.className = remove(el.className, name);
}
}
setStyle(el: RElement, style: string, value: any, flags?: RendererStyleFlags3|undefined): void {}
removeStyle(el: RElement, style: string, flags?: RendererStyleFlags3|undefined): void {}
setProperty(el: RElement, name: string, value: any): void {}
Expand All @@ -47,11 +59,39 @@ export class NoopRenderer implements ProceduralRenderer3 {
}
}

export class NoopRendererFactory implements RendererFactory3 {
export class MicroBenchmarkRendererFactory implements RendererFactory3 {
createRenderer(hostElement: RElement|null, rendererType: null): Renderer3 {
if (typeof global !== 'undefined') {
(global as any).Node = WebWorkerRenderNode;
(global as any).Node = MicroBenchmarkRenderNode;
}
return new NoopRenderer();
return new MicroBenchmarkRenderer();
}
}

function isOurNode(node: any): node is MicroBenchmarkRenderNode {
return node instanceof MicroBenchmarkRenderNode;
}

const enum Code {
SPACE = 32,
}

function remove(text: string, key: string): string {
let wasLastWhitespace = true;
for (let i = 0; i < text.length; i++) {
if (wasLastWhitespace) {
const start = i;
let same = true;
let k = 0;
while (k < key.length && (same = text.charCodeAt(i) === key.charCodeAt(k))) {
k++;
i++;
}
if (same && text.charCodeAt(i) == Code.SPACE) {
return text.substring(0, start) + text.substring(i + 1);
}
}
wasLastWhitespace = text.charCodeAt(i) <= Code.SPACE;
}
return text;
}
38 changes: 38 additions & 0 deletions packages/core/test/render3/perf/noop_renderer_spec.ts
@@ -0,0 +1,38 @@
/**
* @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 {ProceduralRenderer3} from '@angular/core/src/render3/interfaces/renderer';

import {MicroBenchmarkRenderNode, MicroBenchmarkRendererFactory} from './noop_renderer';

describe('MicroBenchmarkRenderNode', () => {
const renderer =
new MicroBenchmarkRendererFactory().createRenderer(null, null) as ProceduralRenderer3;
describe('className', () => {
it('should be available in global space', () => {
expect(Node).toBeDefined();
const node: any = new MicroBenchmarkRenderNode();
expect(node instanceof Node).toBeTruthy();
});

it('should emulate className', () => {
const node: any = new MicroBenchmarkRenderNode();
expect(node.className).toBe('');
renderer.setAttribute(node, 'foo', 'A AA BBB');
expect(node.className).toBe('');
renderer.setAttribute(node, 'class', 'A AA BBB');
expect(node.className).toBe('A AA BBB');
renderer.addClass(node, 'A');
expect(node.className).toBe('AA BBB A');
renderer.addClass(node, 'C');
expect(node.className).toBe('AA BBB A C');
renderer.removeClass(node, 'A');
expect(node.className).toBe('AA BBB C');
});
});
});
5 changes: 3 additions & 2 deletions packages/core/test/render3/perf/setup.ts
Expand Up @@ -12,10 +12,11 @@ import {RendererFactory3, domRendererFactory3} from '../../../src/render3/interf
import {LView, LViewFlags, TView} from '../../../src/render3/interfaces/view';
import {insertView} from '../../../src/render3/node_manipulation';

import {NoopRendererFactory} from './noop_renderer';
import {MicroBenchmarkRendererFactory} from './noop_renderer';

const isBrowser = typeof process === 'undefined';
const rendererFactory: RendererFactory3 = isBrowser ? domRendererFactory3 : new NoopRendererFactory;
const rendererFactory: RendererFactory3 =
isBrowser ? domRendererFactory3 : new MicroBenchmarkRendererFactory;
const renderer = rendererFactory.createRenderer(null, null);

export function createAndRenderLView(
Expand Down

0 comments on commit 6323a35

Please sign in to comment.