benchmarks: add ng2_ftl and ng2_switch_ftl benchmarks (#11963)
These benchmarks take the output of AoT and manually tweaks it to explore possible future changes to the compiler to produce this output directly.
This commit is contained in:
parent
42b4b6d21b
commit
df1822fc2a
|
@ -44,6 +44,20 @@ describe('tree benchmark perf', () => {
|
|||
runTreeBenchmark({
|
||||
id: `deepTree.ng2.${worker.id}`,
|
||||
url: 'all/benchmarks/src/tree/ng2/index.html',
|
||||
work: worker.work,
|
||||
prepare: worker.prepare,
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
it('should run for ng2 ftl', (done) => {
|
||||
runTreeBenchmark({
|
||||
id: `deepTree.ng2.ftl.${worker.id}`,
|
||||
url: 'all/benchmarks/src/tree/ng2_ftl/index.html',
|
||||
work: worker.work,
|
||||
prepare: worker.prepare,
|
||||
// Can't use bundles as we use AoT generated code
|
||||
// which relies on deep imports
|
||||
extraParams: [{name: 'bundles', value: false}]
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
|
@ -51,6 +65,20 @@ describe('tree benchmark perf', () => {
|
|||
runTreeBenchmark({
|
||||
id: `deepTree.ng2.static.${worker.id}`,
|
||||
url: 'all/benchmarks/src/tree/ng2_static/index.html',
|
||||
work: worker.work,
|
||||
prepare: worker.prepare,
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
it('should run for ng2 static ftl', (done) => {
|
||||
runTreeBenchmark({
|
||||
id: `deepTree.ng2.ftl.${worker.id}`,
|
||||
url: 'all/benchmarks/src/tree/ng2_static_ftl/index.html',
|
||||
work: worker.work,
|
||||
prepare: worker.prepare,
|
||||
// Can't use bundles as we use AoT generated code
|
||||
// which relies on deep imports
|
||||
extraParams: [{name: 'bundles', value: false}]
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
|
@ -58,6 +86,8 @@ describe('tree benchmark perf', () => {
|
|||
runTreeBenchmark({
|
||||
id: `deepTree.ng2_switch.${worker.id}`,
|
||||
url: 'all/benchmarks/src/tree/ng2_switch/index.html',
|
||||
work: worker.work,
|
||||
prepare: worker.prepare,
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
|
@ -66,6 +96,8 @@ describe('tree benchmark perf', () => {
|
|||
id: `deepTree.baseline.${worker.id}`,
|
||||
url: 'all/benchmarks/src/tree/baseline/index.html',
|
||||
ignoreBrowserSynchronization: true,
|
||||
work: worker.work,
|
||||
prepare: worker.prepare,
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
|
@ -74,6 +106,8 @@ describe('tree benchmark perf', () => {
|
|||
id: `deepTree.incremental_dom.${worker.id}`,
|
||||
url: 'all/benchmarks/src/tree/incremental_dom/index.html',
|
||||
ignoreBrowserSynchronization: true,
|
||||
work: worker.work,
|
||||
prepare: worker.prepare,
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
|
@ -82,6 +116,8 @@ describe('tree benchmark perf', () => {
|
|||
id: `deepTree.polymer.${worker.id}`,
|
||||
url: 'all/benchmarks/src/tree/polymer/index.html',
|
||||
ignoreBrowserSynchronization: true,
|
||||
work: worker.work,
|
||||
prepare: worker.prepare,
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
|
@ -90,22 +126,30 @@ describe('tree benchmark perf', () => {
|
|||
id: `deepTree.polymer_leaves.${worker.id}`,
|
||||
url: 'all/benchmarks/src/tree/polymer_leaves/index.html',
|
||||
ignoreBrowserSynchronization: true,
|
||||
work: worker.work,
|
||||
prepare: worker.prepare,
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function runTreeBenchmark(
|
||||
config: {id: string, url: string, ignoreBrowserSynchronization?: boolean}) {
|
||||
function runTreeBenchmark(config: {
|
||||
id: string,
|
||||
url: string, ignoreBrowserSynchronization?: boolean,
|
||||
work: () => any,
|
||||
prepare: () => any, extraParams?: {name: string, value: any}[]
|
||||
}) {
|
||||
let params = [{name: 'depth', value: 11}];
|
||||
if (config.extraParams) {
|
||||
params = params.concat(config.extraParams);
|
||||
}
|
||||
return runBenchmark({
|
||||
id: config.id,
|
||||
url: config.url,
|
||||
ignoreBrowserSynchronization: config.ignoreBrowserSynchronization,
|
||||
params: [{name: 'depth', value: 9}],
|
||||
work: () => {
|
||||
$('#createDom').click();
|
||||
$('#destroyDom').click();
|
||||
}
|
||||
params: params,
|
||||
work: config.work,
|
||||
prepare: config.prepare
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -18,12 +18,30 @@ describe('tree benchmark spec', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should work for ng2 ftl', () => {
|
||||
testTreeBenchmark({
|
||||
url: 'all/benchmarks/src/tree/ng2_ftl/index.html',
|
||||
// Can't use bundles as we use AoT generated code
|
||||
// which relies on deep imports
|
||||
extraParams: [{name: 'bundles', value: false}]
|
||||
});
|
||||
});
|
||||
|
||||
it('should work for ng2 static', () => {
|
||||
testTreeBenchmark({
|
||||
url: 'all/benchmarks/src/tree/ng2_static/index.html',
|
||||
});
|
||||
});
|
||||
|
||||
it('should work for ng2 static ftl', () => {
|
||||
testTreeBenchmark({
|
||||
url: 'all/benchmarks/src/tree/ng2_static_ftl/index.html',
|
||||
// Can't use bundles as we use AoT generated code
|
||||
// which relies on deep imports
|
||||
extraParams: [{name: 'bundles', value: false}]
|
||||
});
|
||||
});
|
||||
|
||||
it('should work for ng2 switch', () => {
|
||||
testTreeBenchmark({
|
||||
url: 'all/benchmarks/src/tree/ng2_switch/index.html',
|
||||
|
@ -58,11 +76,19 @@ describe('tree benchmark spec', () => {
|
|||
});
|
||||
});
|
||||
|
||||
function testTreeBenchmark(openConfig: {url: string, ignoreBrowserSynchronization?: boolean}) {
|
||||
function testTreeBenchmark(openConfig: {
|
||||
url: string,
|
||||
ignoreBrowserSynchronization?: boolean,
|
||||
extraParams?: {name: string, value: any}[]
|
||||
}) {
|
||||
let params = [{name: 'depth', value: 4}];
|
||||
if (openConfig.extraParams) {
|
||||
params = params.concat(openConfig.extraParams);
|
||||
}
|
||||
openBrowser({
|
||||
url: openConfig.url,
|
||||
ignoreBrowserSynchronization: openConfig.ignoreBrowserSynchronization,
|
||||
params: [{name: 'depth', value: 4}],
|
||||
params: params,
|
||||
});
|
||||
$('#createDom').click();
|
||||
expect($('#root').getText()).toContain('0');
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
# Ng2 Faster Than BaseLine (FTL) Benchmark
|
||||
|
||||
This benchmark was produced in a similar way as `ng2_static_ftl`,
|
||||
but starting from the `ng2` benchmark, i.e. the benchmark
|
||||
that only uses 1 component that contains itself via 2 `NgIf`s.
|
||||
|
||||
1. Run AoT over the `TreeComponent` and `AppModule`
|
||||
2. Move the `ComponentFactory` and the corresponding host view class
|
||||
from `tree.ngfactory.ts` into `tree_host.ngfactory.ts`
|
||||
3. Move the `NgModuleFactory` into the `app.ngfactory.ts`
|
||||
4. Optimize the `tree.ngfactory.ts` (see below)
|
||||
|
||||
The goal of this benchmark is to explore what kind of
|
||||
code the Ng2 compiler should generate in order to get faster.
|
||||
I.e. right now, the Ng2 compiler _does not_ produce this kind of code,
|
||||
but hopefully will in the future.
|
||||
|
||||
|
||||
## Optimizations and cleanups
|
||||
|
||||
See `tree.ngfactory.ts` and `tree_leaf.ngfactory.ts`.
|
||||
|
||||
Performance:
|
||||
|
||||
- remove view factory functions, call the constructor of the view class directly
|
||||
- remove `createInternal` and move the logic into the constructor
|
||||
- generate direct calls to `detectChangesInternal` / `destroyInternal` of nested components
|
||||
_and_ view containers, don't use `viewChildren` / `contentChildren`
|
||||
- remove `Renderer` and `SanitizationService` to update
|
||||
the dom directly via `document.createElement` / `element.appendChild` / setting
|
||||
`Text.nodeValue` / `Element.style....`
|
||||
- remove `AppElement` on component host elements
|
||||
- custom implementation of `ViewContainerRef` that uses
|
||||
the views as linked list, instead of an array (see `ftl_util`).
|
||||
- custom implementation of TemplateRef which not use a closure, only a view and an index,
|
||||
and the view has a factory method that switches over the index
|
||||
(see `ftl_util`).
|
||||
- Don't wrap Views into ViewRefs before passing to the user.
|
||||
- View does not use an array to store root nodes,
|
||||
but rather has a generated method `visitRootNodes` (see below)
|
||||
- ViewContainer, TemplateRef and views have minimal amount of fields
|
||||
- use `insertBefore` as primitive.
|
||||
|
||||
Code size:
|
||||
- inputs for `NgIf`, `TreeComponent` are wrapped into view class / directive wrapper method
|
||||
(see `ng_if.ngfactory.ts` and `tree.ngfactory.ts`).
|
||||
- helper methods `createElementAndAppend` / `createTextAndAppend` / `createAnchorAndAppend`
|
||||
- remove `injectorGetInternal` assuming we can detect that nobody is injecting an `Injector`
|
||||
|
||||
## Properties
|
||||
- no allocation of arrays nor closures
|
||||
|
||||
## Background: why `visitRootNodes`?
|
||||
|
||||
* An embedded view has root nodes that need to be attached / detached
|
||||
to the DOM when the view is inserted.
|
||||
* An embedded view can have view containers as root nodes. I.e. the root nodes
|
||||
of a view need to include the root nodes of all views in root view containers
|
||||
* An embedded view can have `<ng-content>` as root nodes. I.e. the root
|
||||
nodes of a view need to include the projects nodes, which can
|
||||
include a view container, in which case they need to include the root nodes
|
||||
of all included views as well.
|
||||
|
||||
Previous implementation:
|
||||
The generated view classes would store a (possible nested) array of root nodes
|
||||
that can contain regular DOM nodes as well as ViewContainers.
|
||||
The helper method `flattenNestedViewRootNodes` is used to convert this array
|
||||
into a flat array of node.
|
||||
However, this generates a new array and is probably hard to optimize for
|
||||
a vm.
|
||||
|
||||
### Solution
|
||||
Generate loop functions / methods.
|
||||
I.e. a method like:
|
||||
```
|
||||
loopRootNodes(cb: (obj: any, ctx: any), ctx: any) {
|
||||
cb(this.node0, ctx);
|
||||
...
|
||||
}
|
||||
```
|
||||
These methods can easily be nested, i.e. a view container
|
||||
can just loop over its views and call this method,
|
||||
forwarding the callback!
|
||||
|
||||
Same is true for projection!
|
||||
==> Tried it, no impact on the benchmark!
|
||||
|
||||
### Discarded Solution 1)
|
||||
Generate `attach` / `detach` method on EmbeddedView,
|
||||
which will `insertBefore` the root nodes. Also
|
||||
generate `project` and `unproject` methods in the caller
|
||||
of a component to add / remove nodes.
|
||||
|
||||
Problems:
|
||||
- generates a lot of code!
|
||||
- projected nodes can only be inserted into the DOM via
|
||||
`insertBefore`, but never via `appendChild` as
|
||||
the generated method `project` does it,
|
||||
and it can do only one of these two, but not both.
|
||||
|
||||
### Discarded Solution 2)
|
||||
Generate a method `rootNodes` on every view that will
|
||||
do produce a flattened array of nodes by generating the
|
||||
right `concat` statements.
|
||||
|
||||
Problems:
|
||||
- allocates arrays and `concat` calls that slows things down
|
||||
(an experiment showed about about 2-5%).
|
||||
|
||||
## Experiments tried
|
||||
- just the fact of having another base class in the prototype chain
|
||||
is not a problem!
|
||||
- Using a linked list over the views vs an array in ViewContainerRef
|
||||
has no impact on performance, given that we use `Array.prototype.push`
|
||||
for adding if possible (only tried with ngIf though...)
|
||||
* Using `Array.prototype.splice` to add made benchmark 2% slower though!
|
||||
- creating an array with root nodes does not make the benchmark much slower.
|
||||
* Using `flattenNestedViewRootNodes` made it 5-10% slower!
|
||||
* Note that this benchmark only includes `NgIf`, i.e. it only
|
||||
inserts 1 entry into the array.
|
||||
- creating an additional comment node for the view container,
|
||||
so it is safe to do `insertBefore`
|
||||
* this slows the benchmark down by 14% (added 2 comment nodes)!
|
||||
==> the number of comment nodes is important!
|
||||
|
||||
## Open experiments:
|
||||
- using ViewRef as a separate wrapper, compared to
|
||||
using the view itself as ref.
|
||||
- using AppElement with an injector, native node, ...,
|
||||
compared to not having all of these fields
|
||||
- using `insertAfter` instead of `insertBefore`?
|
||||
|
||||
## Initial resuls: size
|
||||
|
||||
File size for view class of the component + the 2 embedded view classes (without imports nor host view factory):
|
||||
* before: 140 LOC
|
||||
* after: 104 LOC
|
||||
|
||||
## Initial results: Deep Tree Benchmark
|
||||
|
||||
BENCHMARK deepTree....
|
||||
Description:
|
||||
- bundles: false
|
||||
- depth: 11
|
||||
- forceGc: false
|
||||
- regressionSlopeMetric: scriptTime
|
||||
- runId: f9ae32f0-8580-11e6-914d-9f4f8dbfb5e8
|
||||
- sampleSize: 20
|
||||
- userAgent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36
|
||||
|
||||
...createOnly | gcAmount | gcTime | majorGcTime | pureScriptTime | renderTime | scriptTime
|
||||
--------------- | ------------------ | ------------------ | ------------------ | ------------------ | ------------------ | ------------------
|
||||
ng2 | 5926.64+-11% | 17.46+-5% | 0.02+-44% | 96.74+-9% | 49.72+-5% | 114.19+-8%
|
||||
ng2.ftl | 584.50+-435% | 0.43+-435% | 0.00+-435% | 33.98+-7% | 50.27+-5% | 33.98+-7%
|
||||
baseline | 0.00 | 0.00 | 0.00 | 15.83+-7% | 45.75+-4% | 15.83+-7%
|
||||
incremental_dom | 100.82+-302% | 1.64+-321% | 0.00+-304% | 53.43+-9% | 43.96+-4% | 55.07+-13%
|
||||
|
||||
...update gcAmount | gcTime | majorGcTime | pureScriptTime | renderTime | scriptTime
|
||||
--------------- | ------------------ | ------------------ | ------------------ | ------------------ | ------------------ | ------------------
|
||||
ng2 | 0.00 | 0.00+-435% | 0.00+-435% | 22.82+-9% | 29.52+-6% | 22.82+-9%
|
||||
ng2.ftl | 219.72+-301% | 0.97+-300% | 0.00+-302% | 17.37+-10% | 29.27+-5% | 17.37+-10%
|
||||
baseline | 530.73+-178% | 0.55+-281% | 0.35+-434% | 25.82+-8% | 31.67+-11% | 25.82+-8%
|
||||
incremental_dom | 1057.56+-200% | 0.28+-201% | 0.00+-204% | 22.50+-9% | 28.03+-4% | 22.50+-9%
|
||||
|
||||
Ratios (create, pureScriptTime):
|
||||
- 2.8x faster than current implementation
|
||||
- 1.5x faster than incremental DOM
|
||||
- 2.1x slower than baseline
|
|
@ -0,0 +1,319 @@
|
|||
/**
|
||||
* This file is generated by the Angular 2 template compiler.
|
||||
* Do not edit.
|
||||
*/
|
||||
/* tslint:disable */
|
||||
|
||||
import * as import2 from '@angular/common/src/common_module';
|
||||
import * as import5 from '@angular/common/src/localization';
|
||||
import * as import6 from '@angular/core/src/application_init';
|
||||
import * as import3 from '@angular/core/src/application_module';
|
||||
import * as import8 from '@angular/core/src/application_ref';
|
||||
import * as import19 from '@angular/core/src/application_tokens';
|
||||
import * as import31 from '@angular/core/src/change_detection/differs/iterable_differs';
|
||||
import * as import32 from '@angular/core/src/change_detection/differs/keyvalue_differs';
|
||||
import * as import24 from '@angular/core/src/console';
|
||||
import * as import17 from '@angular/core/src/di/injector';
|
||||
import * as import26 from '@angular/core/src/error_handler';
|
||||
import * as import33 from '@angular/core/src/i18n/tokens';
|
||||
import * as import25 from '@angular/core/src/i18n/tokens';
|
||||
import * as import9 from '@angular/core/src/linker/compiler';
|
||||
import * as import0 from '@angular/core/src/linker/ng_module_factory';
|
||||
import * as import15 from '@angular/core/src/linker/view_utils';
|
||||
import * as import29 from '@angular/core/src/render/api';
|
||||
import * as import30 from '@angular/core/src/security';
|
||||
import * as import7 from '@angular/core/src/testability/testability';
|
||||
import * as import22 from '@angular/core/src/zone/ng_zone';
|
||||
import * as import4 from '@angular/platform-browser/src/browser';
|
||||
import * as import16 from '@angular/platform-browser/src/browser/title';
|
||||
import * as import28 from '@angular/platform-browser/src/dom/animation_driver';
|
||||
import * as import23 from '@angular/platform-browser/src/dom/debug/ng_probe';
|
||||
import * as import13 from '@angular/platform-browser/src/dom/dom_renderer';
|
||||
import * as import27 from '@angular/platform-browser/src/dom/dom_tokens';
|
||||
import * as import20 from '@angular/platform-browser/src/dom/events/dom_events';
|
||||
import * as import11 from '@angular/platform-browser/src/dom/events/event_manager';
|
||||
import * as import10 from '@angular/platform-browser/src/dom/events/hammer_gestures';
|
||||
import * as import21 from '@angular/platform-browser/src/dom/events/key_events';
|
||||
import * as import12 from '@angular/platform-browser/src/dom/shared_styles_host';
|
||||
import * as import14 from '@angular/platform-browser/src/security/dom_sanitization_service';
|
||||
|
||||
import * as import1 from './tree';
|
||||
import * as import18 from './tree_host.ngfactory';
|
||||
|
||||
class AppModuleInjector extends import0.NgModuleInjector<import1.AppModule> {
|
||||
_CommonModule_0: import2.CommonModule;
|
||||
_ApplicationModule_1: import3.ApplicationModule;
|
||||
_BrowserModule_2: import4.BrowserModule;
|
||||
_AppModule_3: import1.AppModule;
|
||||
__LOCALE_ID_4: any;
|
||||
__NgLocalization_5: import5.NgLocaleLocalization;
|
||||
_ErrorHandler_6: any;
|
||||
_ApplicationInitStatus_7: import6.ApplicationInitStatus;
|
||||
_Testability_8: import7.Testability;
|
||||
_ApplicationRef__9: import8.ApplicationRef_;
|
||||
__ApplicationRef_10: any;
|
||||
__Compiler_11: import9.Compiler;
|
||||
__APP_ID_12: any;
|
||||
__DOCUMENT_13: any;
|
||||
__HAMMER_GESTURE_CONFIG_14: import10.HammerGestureConfig;
|
||||
__EVENT_MANAGER_PLUGINS_15: any[];
|
||||
__EventManager_16: import11.EventManager;
|
||||
__DomSharedStylesHost_17: import12.DomSharedStylesHost;
|
||||
__AnimationDriver_18: any;
|
||||
__DomRootRenderer_19: import13.DomRootRenderer_;
|
||||
__RootRenderer_20: any;
|
||||
__DomSanitizer_21: import14.DomSanitizerImpl;
|
||||
__Sanitizer_22: any;
|
||||
__ViewUtils_23: import15.ViewUtils;
|
||||
__IterableDiffers_24: any;
|
||||
__KeyValueDiffers_25: any;
|
||||
__SharedStylesHost_26: any;
|
||||
__Title_27: import16.Title;
|
||||
__TRANSLATIONS_FORMAT_28: any;
|
||||
constructor(parent: import17.Injector) {
|
||||
super(parent, [import18.TreeComponentNgFactory], [import18.TreeComponentNgFactory]);
|
||||
}
|
||||
get _LOCALE_ID_4(): any {
|
||||
if ((this.__LOCALE_ID_4 == (null as any))) {
|
||||
(this.__LOCALE_ID_4 = (null as any));
|
||||
}
|
||||
return this.__LOCALE_ID_4;
|
||||
}
|
||||
get _NgLocalization_5(): import5.NgLocaleLocalization {
|
||||
if ((this.__NgLocalization_5 == (null as any))) {
|
||||
(this.__NgLocalization_5 = new import5.NgLocaleLocalization(this._LOCALE_ID_4));
|
||||
}
|
||||
return this.__NgLocalization_5;
|
||||
}
|
||||
get _ApplicationRef_10(): any {
|
||||
if ((this.__ApplicationRef_10 == (null as any))) {
|
||||
(this.__ApplicationRef_10 = this._ApplicationRef__9);
|
||||
}
|
||||
return this.__ApplicationRef_10;
|
||||
}
|
||||
get _Compiler_11(): import9.Compiler {
|
||||
if ((this.__Compiler_11 == (null as any))) {
|
||||
(this.__Compiler_11 = new import9.Compiler());
|
||||
}
|
||||
return this.__Compiler_11;
|
||||
}
|
||||
get _APP_ID_12(): any {
|
||||
if ((this.__APP_ID_12 == (null as any))) {
|
||||
(this.__APP_ID_12 = import19._appIdRandomProviderFactory());
|
||||
}
|
||||
return this.__APP_ID_12;
|
||||
}
|
||||
get _DOCUMENT_13(): any {
|
||||
if ((this.__DOCUMENT_13 == (null as any))) {
|
||||
(this.__DOCUMENT_13 = import4._document());
|
||||
}
|
||||
return this.__DOCUMENT_13;
|
||||
}
|
||||
get _HAMMER_GESTURE_CONFIG_14(): import10.HammerGestureConfig {
|
||||
if ((this.__HAMMER_GESTURE_CONFIG_14 == (null as any))) {
|
||||
(this.__HAMMER_GESTURE_CONFIG_14 = new import10.HammerGestureConfig());
|
||||
}
|
||||
return this.__HAMMER_GESTURE_CONFIG_14;
|
||||
}
|
||||
get _EVENT_MANAGER_PLUGINS_15(): any[] {
|
||||
if ((this.__EVENT_MANAGER_PLUGINS_15 == (null as any))) {
|
||||
(this.__EVENT_MANAGER_PLUGINS_15 = [
|
||||
new import20.DomEventsPlugin(), new import21.KeyEventsPlugin(),
|
||||
new import10.HammerGesturesPlugin(this._HAMMER_GESTURE_CONFIG_14)
|
||||
]);
|
||||
}
|
||||
return this.__EVENT_MANAGER_PLUGINS_15;
|
||||
}
|
||||
get _EventManager_16(): import11.EventManager {
|
||||
if ((this.__EventManager_16 == (null as any))) {
|
||||
(this.__EventManager_16 = new import11.EventManager(
|
||||
this._EVENT_MANAGER_PLUGINS_15, this.parent.get(import22.NgZone)));
|
||||
}
|
||||
return this.__EventManager_16;
|
||||
}
|
||||
get _DomSharedStylesHost_17(): import12.DomSharedStylesHost {
|
||||
if ((this.__DomSharedStylesHost_17 == (null as any))) {
|
||||
(this.__DomSharedStylesHost_17 = new import12.DomSharedStylesHost(this._DOCUMENT_13));
|
||||
}
|
||||
return this.__DomSharedStylesHost_17;
|
||||
}
|
||||
get _AnimationDriver_18(): any {
|
||||
if ((this.__AnimationDriver_18 == (null as any))) {
|
||||
(this.__AnimationDriver_18 = import4._resolveDefaultAnimationDriver());
|
||||
}
|
||||
return this.__AnimationDriver_18;
|
||||
}
|
||||
get _DomRootRenderer_19(): import13.DomRootRenderer_ {
|
||||
if ((this.__DomRootRenderer_19 == (null as any))) {
|
||||
(this.__DomRootRenderer_19 = new import13.DomRootRenderer_(
|
||||
this._DOCUMENT_13, this._EventManager_16, this._DomSharedStylesHost_17,
|
||||
this._AnimationDriver_18));
|
||||
}
|
||||
return this.__DomRootRenderer_19;
|
||||
}
|
||||
get _RootRenderer_20(): any {
|
||||
if ((this.__RootRenderer_20 == (null as any))) {
|
||||
(this.__RootRenderer_20 = import23._createConditionalRootRenderer(
|
||||
this._DomRootRenderer_19, this.parent.get(import23.NgProbeToken, (null as any))));
|
||||
}
|
||||
return this.__RootRenderer_20;
|
||||
}
|
||||
get _DomSanitizer_21(): import14.DomSanitizerImpl {
|
||||
if ((this.__DomSanitizer_21 == (null as any))) {
|
||||
(this.__DomSanitizer_21 = new import14.DomSanitizerImpl());
|
||||
}
|
||||
return this.__DomSanitizer_21;
|
||||
}
|
||||
get _Sanitizer_22(): any {
|
||||
if ((this.__Sanitizer_22 == (null as any))) {
|
||||
(this.__Sanitizer_22 = this._DomSanitizer_21);
|
||||
}
|
||||
return this.__Sanitizer_22;
|
||||
}
|
||||
get _ViewUtils_23(): import15.ViewUtils {
|
||||
if ((this.__ViewUtils_23 == (null as any))) {
|
||||
(this.__ViewUtils_23 =
|
||||
new import15.ViewUtils(this._RootRenderer_20, this._APP_ID_12, this._Sanitizer_22));
|
||||
}
|
||||
return this.__ViewUtils_23;
|
||||
}
|
||||
get _IterableDiffers_24(): any {
|
||||
if ((this.__IterableDiffers_24 == (null as any))) {
|
||||
(this.__IterableDiffers_24 = import3._iterableDiffersFactory());
|
||||
}
|
||||
return this.__IterableDiffers_24;
|
||||
}
|
||||
get _KeyValueDiffers_25(): any {
|
||||
if ((this.__KeyValueDiffers_25 == (null as any))) {
|
||||
(this.__KeyValueDiffers_25 = import3._keyValueDiffersFactory());
|
||||
}
|
||||
return this.__KeyValueDiffers_25;
|
||||
}
|
||||
get _SharedStylesHost_26(): any {
|
||||
if ((this.__SharedStylesHost_26 == (null as any))) {
|
||||
(this.__SharedStylesHost_26 = this._DomSharedStylesHost_17);
|
||||
}
|
||||
return this.__SharedStylesHost_26;
|
||||
}
|
||||
get _Title_27(): import16.Title {
|
||||
if ((this.__Title_27 == (null as any))) {
|
||||
(this.__Title_27 = new import16.Title());
|
||||
}
|
||||
return this.__Title_27;
|
||||
}
|
||||
get _TRANSLATIONS_FORMAT_28(): any {
|
||||
if ((this.__TRANSLATIONS_FORMAT_28 == (null as any))) {
|
||||
(this.__TRANSLATIONS_FORMAT_28 = (null as any));
|
||||
}
|
||||
return this.__TRANSLATIONS_FORMAT_28;
|
||||
}
|
||||
createInternal(): import1.AppModule {
|
||||
this._CommonModule_0 = new import2.CommonModule();
|
||||
this._ApplicationModule_1 = new import3.ApplicationModule();
|
||||
this._BrowserModule_2 =
|
||||
new import4.BrowserModule(this.parent.get(import4.BrowserModule, (null as any)));
|
||||
this._AppModule_3 = new import1.AppModule();
|
||||
this._ErrorHandler_6 = import4.errorHandler();
|
||||
this._ApplicationInitStatus_7 =
|
||||
new import6.ApplicationInitStatus(this.parent.get(import6.APP_INITIALIZER, (null as any)));
|
||||
this._Testability_8 = new import7.Testability(this.parent.get(import22.NgZone));
|
||||
this._ApplicationRef__9 = new import8.ApplicationRef_(
|
||||
this.parent.get(import22.NgZone), this.parent.get(import24.Console), this,
|
||||
this._ErrorHandler_6, this, this._ApplicationInitStatus_7,
|
||||
this.parent.get(import7.TestabilityRegistry, (null as any)), this._Testability_8);
|
||||
return this._AppModule_3;
|
||||
}
|
||||
getInternal(token: any, notFoundResult: any): any {
|
||||
if ((token === import2.CommonModule)) {
|
||||
return this._CommonModule_0;
|
||||
}
|
||||
if ((token === import3.ApplicationModule)) {
|
||||
return this._ApplicationModule_1;
|
||||
}
|
||||
if ((token === import4.BrowserModule)) {
|
||||
return this._BrowserModule_2;
|
||||
}
|
||||
if ((token === import1.AppModule)) {
|
||||
return this._AppModule_3;
|
||||
}
|
||||
if ((token === import25.LOCALE_ID)) {
|
||||
return this._LOCALE_ID_4;
|
||||
}
|
||||
if ((token === import5.NgLocalization)) {
|
||||
return this._NgLocalization_5;
|
||||
}
|
||||
if ((token === import26.ErrorHandler)) {
|
||||
return this._ErrorHandler_6;
|
||||
}
|
||||
if ((token === import6.ApplicationInitStatus)) {
|
||||
return this._ApplicationInitStatus_7;
|
||||
}
|
||||
if ((token === import7.Testability)) {
|
||||
return this._Testability_8;
|
||||
}
|
||||
if ((token === import8.ApplicationRef_)) {
|
||||
return this._ApplicationRef__9;
|
||||
}
|
||||
if ((token === import8.ApplicationRef)) {
|
||||
return this._ApplicationRef_10;
|
||||
}
|
||||
if ((token === import9.Compiler)) {
|
||||
return this._Compiler_11;
|
||||
}
|
||||
if ((token === import19.APP_ID)) {
|
||||
return this._APP_ID_12;
|
||||
}
|
||||
if ((token === import27.DOCUMENT)) {
|
||||
return this._DOCUMENT_13;
|
||||
}
|
||||
if ((token === import10.HAMMER_GESTURE_CONFIG)) {
|
||||
return this._HAMMER_GESTURE_CONFIG_14;
|
||||
}
|
||||
if ((token === import11.EVENT_MANAGER_PLUGINS)) {
|
||||
return this._EVENT_MANAGER_PLUGINS_15;
|
||||
}
|
||||
if ((token === import11.EventManager)) {
|
||||
return this._EventManager_16;
|
||||
}
|
||||
if ((token === import12.DomSharedStylesHost)) {
|
||||
return this._DomSharedStylesHost_17;
|
||||
}
|
||||
if ((token === import28.AnimationDriver)) {
|
||||
return this._AnimationDriver_18;
|
||||
}
|
||||
if ((token === import13.DomRootRenderer)) {
|
||||
return this._DomRootRenderer_19;
|
||||
}
|
||||
if ((token === import29.RootRenderer)) {
|
||||
return this._RootRenderer_20;
|
||||
}
|
||||
if ((token === import14.DomSanitizer)) {
|
||||
return this._DomSanitizer_21;
|
||||
}
|
||||
if ((token === import30.Sanitizer)) {
|
||||
return this._Sanitizer_22;
|
||||
}
|
||||
if ((token === import15.ViewUtils)) {
|
||||
return this._ViewUtils_23;
|
||||
}
|
||||
if ((token === import31.IterableDiffers)) {
|
||||
return this._IterableDiffers_24;
|
||||
}
|
||||
if ((token === import32.KeyValueDiffers)) {
|
||||
return this._KeyValueDiffers_25;
|
||||
}
|
||||
if ((token === import12.SharedStylesHost)) {
|
||||
return this._SharedStylesHost_26;
|
||||
}
|
||||
if ((token === import16.Title)) {
|
||||
return this._Title_27;
|
||||
}
|
||||
if ((token === import33.TRANSLATIONS_FORMAT)) {
|
||||
return this._TRANSLATIONS_FORMAT_28;
|
||||
}
|
||||
return notFoundResult;
|
||||
}
|
||||
destroyInternal(): void { this._ApplicationRef__9.ngOnDestroy(); }
|
||||
}
|
||||
export const AppModuleNgFactory: import0.NgModuleFactory<import1.AppModule> =
|
||||
new import0.NgModuleFactory(AppModuleInjector, import1.AppModule);
|
|
@ -0,0 +1,199 @@
|
|||
import {ComponentFactory, ComponentRef, ElementRef, Injector, TemplateRef, ViewContainerRef, ViewRef} from '@angular/core';
|
||||
|
||||
export function unimplemented(): any {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
|
||||
export interface FtlView<C> {
|
||||
context: C;
|
||||
detectChangesInternal(throwOnChange: boolean): void;
|
||||
createEmbeddedView?<NC>(context: NC, nodeIndex: number): FtlEmbeddedView<NC>;
|
||||
destroyInternal(): void;
|
||||
}
|
||||
|
||||
export interface FtlEmbeddedView<C> extends FtlView<C> {
|
||||
// Note: if the view has a view container as first node,
|
||||
// create a comment node before it. This makes
|
||||
// inserting a view before this view simpler!
|
||||
_node0: any;
|
||||
prev: FtlEmbeddedView<any>;
|
||||
next: FtlEmbeddedView<any>;
|
||||
// Purpose of the `ctx` argument:
|
||||
// Allows to use top level functions, i.e. no need to create closures!
|
||||
visitRootNodes<CTX>(callback: (node: any, ctx: CTX) => void, ctx: CTX): void;
|
||||
}
|
||||
|
||||
// TODO(tbosch): FTL EmbeddedViewRefs should have no
|
||||
// methods / properties at all. Because of this we can just use the FtlView for it as well!
|
||||
// TODO(tbosch): We can't cast to EmbededViewRef as that also has the `rootNodes` filled
|
||||
// -> would need to generate code for that as well.
|
||||
// Rather: change API for FTL to allow to call View.attachBefore / View.detach
|
||||
// -> faster and we don't need to flatten the root nodes!
|
||||
export type FtlEmbeddedViewRef<C> = FtlView<C>;
|
||||
|
||||
export class FtlTemplateRef<C> implements TemplateRef<C> {
|
||||
constructor(private _index: number, private _view: FtlView<any>) {}
|
||||
get elementRef(): ElementRef { return unimplemented(); }
|
||||
createEmbeddedView(context: C): any {
|
||||
return this._view.createEmbeddedView(context, this._index);
|
||||
}
|
||||
}
|
||||
|
||||
export class FtlViewContainerRef implements ViewContainerRef {
|
||||
private _firstView: FtlEmbeddedView<any> = null;
|
||||
private _lastView: FtlEmbeddedView<any> = null;
|
||||
private _length = 0;
|
||||
|
||||
constructor(private _anchor: any) {}
|
||||
|
||||
detectChangesInternal(throwOnChange: boolean) {
|
||||
let view = this._firstView;
|
||||
while (view) {
|
||||
view.detectChangesInternal(throwOnChange);
|
||||
view = view.next;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(tbosch): don't allow this API in FTL mode!
|
||||
get element(): ElementRef { return <ElementRef>unimplemented(); }
|
||||
|
||||
// TODO(tbosch): don't allow this API in FTL mode!
|
||||
get injector(): Injector { return <Injector>unimplemented(); }
|
||||
|
||||
// TODO(tbosch): don't allow this API in FTL mode!
|
||||
get parentInjector(): Injector { return <Injector>unimplemented(); }
|
||||
|
||||
destroyInternal() {
|
||||
let view = this._firstView;
|
||||
while (view) {
|
||||
view.destroyInternal();
|
||||
view = view.next;
|
||||
}
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
let view = this._firstView;
|
||||
while (view) {
|
||||
detachView(view);
|
||||
view.destroyInternal();
|
||||
view = view.next;
|
||||
}
|
||||
this._firstView = null;
|
||||
this._lastView = null;
|
||||
this._length = 0;
|
||||
}
|
||||
|
||||
get(index: number): any {
|
||||
var result = this._firstView;
|
||||
while (index > 0 && result) {
|
||||
result = result.next;
|
||||
index--;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
get length(): number { return this._length; };
|
||||
|
||||
createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, index?: number): any {
|
||||
const view = templateRef.createEmbeddedView(context);
|
||||
return this.insert(view, index);
|
||||
}
|
||||
|
||||
createComponent<C>(
|
||||
componentFactory: ComponentFactory<C>, index?: number, injector?: Injector,
|
||||
projectableNodes?: any[][]): ComponentRef<C> {
|
||||
// TODO(tbosch): implement this!
|
||||
return unimplemented();
|
||||
}
|
||||
|
||||
insert(viewRef: ViewRef, index?: number): any {
|
||||
const view: FtlEmbeddedView<any> = <any>viewRef;
|
||||
let insertBeforeNode: any;
|
||||
if (this._length === 0) {
|
||||
this._firstView = this._lastView = view;
|
||||
insertBeforeNode = this._anchor;
|
||||
view.prev = null;
|
||||
view.next = null;
|
||||
} else if (index >= this._length) {
|
||||
view.prev = this._lastView;
|
||||
view.next = null;
|
||||
this._lastView.next = view;
|
||||
this._lastView = view;
|
||||
insertBeforeNode = this._anchor;
|
||||
} else {
|
||||
// TODO(tbosch): implement this!
|
||||
unimplemented();
|
||||
}
|
||||
attachViewBefore(view, insertBeforeNode);
|
||||
this._length++;
|
||||
}
|
||||
|
||||
detach(index?: number): any {
|
||||
let view: FtlEmbeddedView<any>;
|
||||
if (this._length === 1) {
|
||||
view = this._firstView;
|
||||
this._firstView = this._lastView = null;
|
||||
} else if (index >= this._length) {
|
||||
view = this._lastView;
|
||||
this._lastView = view.prev;
|
||||
view.prev = null;
|
||||
this._lastView.next = null;
|
||||
} else {
|
||||
// TODO(tbosch): implement this!
|
||||
unimplemented();
|
||||
}
|
||||
this._length--;
|
||||
detachView(view);
|
||||
return view;
|
||||
}
|
||||
|
||||
move(viewRef: ViewRef, currentIndex: number): ViewRef {
|
||||
// TODO(tbosch): implement this!
|
||||
return unimplemented();
|
||||
}
|
||||
|
||||
indexOf(viewRef: ViewRef): number {
|
||||
// TODO(tbosch): implement this!
|
||||
return unimplemented();
|
||||
}
|
||||
|
||||
remove(index?: number): void {
|
||||
var view: FtlView<any> = <any>this.detach(index);
|
||||
view.destroyInternal();
|
||||
}
|
||||
}
|
||||
|
||||
function attachViewBefore(view: FtlEmbeddedView<any>, node: any) {
|
||||
const parent = node.parentNode;
|
||||
view.visitRootNodes(insertBefore, {parent: parent, refNode: node});
|
||||
}
|
||||
|
||||
function insertBefore(node: any, ctx: {parent: any, refNode: any}) {
|
||||
ctx.parent.insertBefore(node, ctx.refNode);
|
||||
}
|
||||
|
||||
function detachView(view: FtlEmbeddedView<any>) {
|
||||
view.visitRootNodes(remove, null);
|
||||
}
|
||||
|
||||
function remove(node: any, ctx: any) {
|
||||
node.remove();
|
||||
}
|
||||
|
||||
export function createElementAndAppend(parent: any, name: string) {
|
||||
const el = document.createElement(name);
|
||||
parent.appendChild(el);
|
||||
return el;
|
||||
}
|
||||
|
||||
export function createTextAndAppend(parent: any) {
|
||||
const txt = document.createTextNode('');
|
||||
parent.appendChild(txt);
|
||||
return txt;
|
||||
}
|
||||
|
||||
export function createAnchorAndAppend(parent: any) {
|
||||
const txt = document.createComment('');
|
||||
parent.appendChild(txt);
|
||||
return txt;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
<h2>Params</h2>
|
||||
<form>
|
||||
Depth:
|
||||
<input type="number" name="depth" placeholder="depth" value="9">
|
||||
<br>
|
||||
<button>Apply</button>
|
||||
</form>
|
||||
|
||||
<h2>Ng2 FTL Tree Benchmark</h2>
|
||||
<p>
|
||||
<button id="destroyDom">destroyDom</button>
|
||||
<button id="createDom">createDom</button>
|
||||
<button id="updateDomProfile">profile updateDom</button>
|
||||
<button id="createDomProfile">profile createDom</button>
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<tree id="root"></tree>
|
||||
</div>
|
||||
|
||||
<script src="../../bootstrap_ng2.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,41 @@
|
|||
import {ApplicationRef, NgModule, enableProdMode} from '@angular/core';
|
||||
import {platformBrowser} from '@angular/platform-browser';
|
||||
|
||||
import {bindAction, profile} from '../../util';
|
||||
import {TreeNode, buildTree, emptyTree} from '../util';
|
||||
|
||||
import {AppModuleNgFactory} from './app.ngfactory';
|
||||
import {TreeComponent} from './tree';
|
||||
|
||||
export function main() {
|
||||
let tree: TreeComponent;
|
||||
let appRef: ApplicationRef;
|
||||
|
||||
function destroyDom() {
|
||||
tree.data = emptyTree;
|
||||
appRef.tick();
|
||||
}
|
||||
|
||||
function createDom() {
|
||||
tree.data = buildTree();
|
||||
appRef.tick();
|
||||
}
|
||||
|
||||
function noop() {}
|
||||
|
||||
function init() {
|
||||
enableProdMode();
|
||||
platformBrowser().bootstrapModuleFactory(AppModuleNgFactory).then((ref) => {
|
||||
const injector = ref.injector;
|
||||
appRef = injector.get(ApplicationRef);
|
||||
|
||||
tree = appRef.components[0].instance;
|
||||
bindAction('#destroyDom', destroyDom);
|
||||
bindAction('#createDom', createDom);
|
||||
bindAction('#updateDomProfile', profile(createDom, noop, 'update'));
|
||||
bindAction('#createDomProfile', profile(createDom, destroyDom, 'create'));
|
||||
});
|
||||
}
|
||||
|
||||
init();
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import {NgIf} from '@angular/common';
|
||||
import {TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
import * as import7 from '@angular/core/src/change_detection/change_detection';
|
||||
import * as import4 from '@angular/core/src/linker/view_utils';
|
||||
|
||||
export class NgIfWrapper {
|
||||
directive: NgIf;
|
||||
_expr_0: any;
|
||||
constructor(viewContainerRef: ViewContainerRef, templateRef: TemplateRef<any>) {
|
||||
this.directive = new NgIf(viewContainerRef, templateRef);
|
||||
this._expr_0 = import7.UNINITIALIZED;
|
||||
}
|
||||
|
||||
updateNgIf(throwOnChange: boolean, currVal: any) {
|
||||
if (import4.checkBinding(throwOnChange, this._expr_0, currVal)) {
|
||||
this.directive.ngIf = currVal;
|
||||
this._expr_0 = currVal;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
/**
|
||||
* This file is hand tweeked based on
|
||||
* the out put of the Angular 2 template compiler
|
||||
* and then hand tweeked to show possible future output.
|
||||
*/
|
||||
/* tslint:disable */
|
||||
|
||||
import * as import10 from '@angular/common/src/directives/ng_if';
|
||||
import * as import7 from '@angular/core/src/change_detection/change_detection';
|
||||
import * as import5 from '@angular/core/src/di/injector';
|
||||
import * as import9 from '@angular/core/src/linker/component_factory';
|
||||
import * as import2 from '@angular/core/src/linker/element';
|
||||
import * as import11 from '@angular/core/src/linker/template_ref';
|
||||
import * as import1 from '@angular/core/src/linker/view';
|
||||
import * as import6 from '@angular/core/src/linker/view_type';
|
||||
import * as import4 from '@angular/core/src/linker/view_utils';
|
||||
import * as import8 from '@angular/core/src/metadata/view';
|
||||
import * as import0 from '@angular/core/src/render/api';
|
||||
import * as import12 from '@angular/core/src/security';
|
||||
|
||||
import {FtlEmbeddedView, FtlTemplateRef, FtlView, FtlViewContainerRef, createAnchorAndAppend, createElementAndAppend, createTextAndAppend} from './ftl_util';
|
||||
import {NgIfWrapper} from './ng_if.ngfactory';
|
||||
import * as import3 from './tree';
|
||||
|
||||
|
||||
export class _View_TreeComponent0 implements FtlView<import3.TreeComponent> {
|
||||
context: import3.TreeComponent;
|
||||
_el_0: any;
|
||||
_text_1: any;
|
||||
_anchor_2: any;
|
||||
_vc_2: FtlViewContainerRef;
|
||||
_TemplateRef_2_5: any;
|
||||
_NgIf_2_6: NgIfWrapper;
|
||||
_anchor_3: any;
|
||||
_vc_3: FtlViewContainerRef;
|
||||
_TemplateRef_3_5: any;
|
||||
_NgIf_3_6: NgIfWrapper;
|
||||
_expr_0: any;
|
||||
_expr_1: any;
|
||||
_expr_2: any;
|
||||
constructor(parentRenderNode: any) {
|
||||
this.context = new import3.TreeComponent();
|
||||
this._el_0 = createElementAndAppend(parentRenderNode, 'span');
|
||||
this._text_1 = createTextAndAppend(this._el_0);
|
||||
this._anchor_2 = createAnchorAndAppend(parentRenderNode);
|
||||
this._TemplateRef_2_5 = new FtlTemplateRef(2, this);
|
||||
this._vc_2 = new FtlViewContainerRef(this._anchor_2);
|
||||
this._NgIf_2_6 = new NgIfWrapper(this._vc_2, this._TemplateRef_2_5);
|
||||
this._anchor_3 = createAnchorAndAppend(parentRenderNode);
|
||||
this._TemplateRef_3_5 = new FtlTemplateRef(3, this);
|
||||
this._vc_3 = new FtlViewContainerRef(this._anchor_3);
|
||||
this._NgIf_3_6 = new NgIfWrapper(this._vc_3, this._TemplateRef_3_5);
|
||||
this._expr_0 = import7.UNINITIALIZED;
|
||||
this._expr_1 = import7.UNINITIALIZED;
|
||||
this._expr_2 = import7.UNINITIALIZED;
|
||||
}
|
||||
detectChangesInternal(throwOnChange: boolean): void {
|
||||
this._NgIf_2_6.updateNgIf(throwOnChange, (this.context.data.right != (null as any)));
|
||||
this._NgIf_3_6.updateNgIf(throwOnChange, (this.context.data.left != (null as any)));
|
||||
this._vc_2.detectChangesInternal(throwOnChange);
|
||||
this._vc_3.detectChangesInternal(throwOnChange);
|
||||
const currVal_0: any = ((this.context.data.depth % 2) ? '' : 'grey');
|
||||
if (import4.checkBinding(throwOnChange, this._expr_0, currVal_0)) {
|
||||
this._el_0.style.backgroundColor = currVal_0;
|
||||
this._expr_0 = currVal_0;
|
||||
}
|
||||
const currVal_1: any = import4.interpolate(1, ' ', this.context.data.value, ' ');
|
||||
if (import4.checkBinding(throwOnChange, this._expr_1, currVal_1)) {
|
||||
this._text_1.nodeValue = currVal_1;
|
||||
this._expr_1 = currVal_1;
|
||||
}
|
||||
}
|
||||
destroyInternal() {
|
||||
this._vc_2.destroyInternal();
|
||||
this._vc_3.destroyInternal();
|
||||
}
|
||||
updateData(throwOnChange: boolean, currVal: any) {
|
||||
if (import4.checkBinding(throwOnChange, this._expr_2, currVal)) {
|
||||
this.context.data = currVal;
|
||||
this._expr_2 = currVal;
|
||||
}
|
||||
}
|
||||
createEmbeddedView(context: any, nodeIndex: number): any {
|
||||
switch (nodeIndex) {
|
||||
case 2:
|
||||
return new _View_TreeComponent1(this, context);
|
||||
case 3:
|
||||
return new _View_TreeComponent2(this, context);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _View_TreeComponent1 implements FtlEmbeddedView<any> {
|
||||
_node0: any;
|
||||
_TreeComponent_0_4: _View_TreeComponent0;
|
||||
prev: FtlEmbeddedView<any>;
|
||||
next: FtlEmbeddedView<any>;
|
||||
constructor(private parent: _View_TreeComponent0, public context: any) {
|
||||
this._node0 = document.createElement('tree');
|
||||
this._TreeComponent_0_4 = new _View_TreeComponent0(this._node0);
|
||||
}
|
||||
detectChangesInternal(throwOnChange: boolean): void {
|
||||
this._TreeComponent_0_4.updateData(throwOnChange, this.parent.context.data.right);
|
||||
this._TreeComponent_0_4.detectChangesInternal(throwOnChange);
|
||||
}
|
||||
visitRootNodes(cb: (...args: any[]) => void, ctx: any) { cb(this._node0, ctx); }
|
||||
destroyInternal() { this._TreeComponent_0_4.destroyInternal(); }
|
||||
}
|
||||
|
||||
class _View_TreeComponent2 implements FtlEmbeddedView<any> {
|
||||
_node0: any;
|
||||
_TreeComponent_0_4: _View_TreeComponent0;
|
||||
prev: FtlEmbeddedView<any>;
|
||||
next: FtlEmbeddedView<any>;
|
||||
constructor(private parent: _View_TreeComponent0, public context: any) {
|
||||
this._node0 = document.createElement('tree');
|
||||
this._TreeComponent_0_4 = new _View_TreeComponent0(this._node0);
|
||||
}
|
||||
detectChangesInternal(throwOnChange: boolean): void {
|
||||
this._TreeComponent_0_4.updateData(throwOnChange, this.parent.context.data.left);
|
||||
this._TreeComponent_0_4.detectChangesInternal(throwOnChange);
|
||||
}
|
||||
visitRootNodes(cb: (...args: any[]) => void, ctx: any) { cb(this._node0, ctx); }
|
||||
destroyInternal() { this._TreeComponent_0_4.destroyInternal(); }
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import {TreeNode, emptyTree} from '../util';
|
||||
|
||||
export class TreeComponent { data: TreeNode = emptyTree; }
|
||||
|
||||
export class AppModule {}
|
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
* This file is generated by the Angular 2 template compiler.
|
||||
* Do not edit.
|
||||
*/
|
||||
/* tslint:disable */
|
||||
|
||||
import * as import10 from '@angular/common/src/directives/ng_if';
|
||||
import * as import7 from '@angular/core/src/change_detection/change_detection';
|
||||
import * as import5 from '@angular/core/src/di/injector';
|
||||
import * as import9 from '@angular/core/src/linker/component_factory';
|
||||
import * as import2 from '@angular/core/src/linker/element';
|
||||
import * as import11 from '@angular/core/src/linker/template_ref';
|
||||
import * as import1 from '@angular/core/src/linker/view';
|
||||
import * as import6 from '@angular/core/src/linker/view_type';
|
||||
import * as import4 from '@angular/core/src/linker/view_utils';
|
||||
import * as import8 from '@angular/core/src/metadata/view';
|
||||
import * as import0 from '@angular/core/src/render/api';
|
||||
import * as import12 from '@angular/core/src/security';
|
||||
|
||||
import * as import3 from './tree';
|
||||
import {_View_TreeComponent0} from './tree.ngfactory';
|
||||
|
||||
var renderType_TreeComponent_Host: import0.RenderComponentType = (null as any);
|
||||
class _View_TreeComponent_Host0 extends import1.AppView<any> {
|
||||
_el_0: any;
|
||||
_vc_0: import2.AppElement;
|
||||
_TreeComponent_0_4: _View_TreeComponent0;
|
||||
constructor(
|
||||
viewUtils: import4.ViewUtils, parentInjector: import5.Injector,
|
||||
declarationEl: import2.AppElement) {
|
||||
super(
|
||||
_View_TreeComponent_Host0, renderType_TreeComponent_Host, import6.ViewType.HOST, viewUtils,
|
||||
parentInjector, declarationEl, import7.ChangeDetectorStatus.CheckAlways);
|
||||
}
|
||||
createInternal(rootSelector: string): import2.AppElement {
|
||||
this._el_0 = this.selectOrCreateHostElement('tree', rootSelector, (null as any));
|
||||
this._vc_0 = new import2.AppElement(0, (null as any), this, this._el_0);
|
||||
this._TreeComponent_0_4 = new _View_TreeComponent0(this._el_0);
|
||||
this._vc_0.initComponent(this._TreeComponent_0_4.context, [], <any>this._TreeComponent_0_4);
|
||||
this.init([].concat([this._el_0]), [this._el_0], [], []);
|
||||
return this._vc_0;
|
||||
}
|
||||
detectChangesInternal(throwOnChange: boolean): void {
|
||||
this._TreeComponent_0_4.detectChangesInternal(throwOnChange);
|
||||
}
|
||||
destroyInternal(): void { this._TreeComponent_0_4.destroyInternal(); }
|
||||
injectorGetInternal(token: any, requestNodeIndex: number, notFoundResult: any): any {
|
||||
if (((token === import3.TreeComponent) && (0 === requestNodeIndex))) {
|
||||
return this._TreeComponent_0_4;
|
||||
}
|
||||
return notFoundResult;
|
||||
}
|
||||
}
|
||||
function viewFactory_TreeComponent_Host0(
|
||||
viewUtils: import4.ViewUtils, parentInjector: import5.Injector,
|
||||
declarationEl: import2.AppElement): import1.AppView<any> {
|
||||
if ((renderType_TreeComponent_Host === (null as any))) {
|
||||
(renderType_TreeComponent_Host =
|
||||
viewUtils.createRenderComponentType('', 0, import8.ViewEncapsulation.None, [], {}));
|
||||
}
|
||||
return new _View_TreeComponent_Host0(viewUtils, parentInjector, declarationEl);
|
||||
}
|
||||
export const TreeComponentNgFactory: import9.ComponentFactory<import3.TreeComponent> =
|
||||
new import9.ComponentFactory<import3.TreeComponent>(
|
||||
'tree', viewFactory_TreeComponent_Host0, import3.TreeComponent);
|
|
@ -0,0 +1,79 @@
|
|||
# Ng2 Static Faster Than BaseLine (FTL) Benchmark
|
||||
|
||||
This benchmark was produced in the following way:
|
||||
|
||||
1. Run AoT over the root component, one branch component
|
||||
and the leaf component of the ng2_static benchmark
|
||||
2. Move all 3 component classes to `tree.ts`
|
||||
3. Add a `depth` property to the view factory constructor of
|
||||
the `TreeBranchComponent` and pass it down to nested view factory calls
|
||||
to be able to use the same view factory for all
|
||||
nesting levels. This is just to make the experiment simpler to not
|
||||
need to change so much code.
|
||||
4. Optimize the `tree_branch.ngfactory.ts` and `tree_leaf.ngfactory.ts`
|
||||
(see below)
|
||||
|
||||
The goal of this benchmark is to explore what kind of
|
||||
code the Ng2 compiler should generate in order to get faster.
|
||||
I.e. right now, the Ng2 compiler _does not_ produce this kind of code,
|
||||
but hopefully will in the future.
|
||||
|
||||
## Optimizations and cleanups
|
||||
|
||||
See `tree_branch.ngfactory.ts` and `tree_leaf.ngfactory.ts`.
|
||||
|
||||
Performance:
|
||||
|
||||
- remove view factory functions, call the constructor of the view class directly
|
||||
- remove `createInternal` and move the logic into the constructor
|
||||
- remove `AppView` parent class
|
||||
- generate direct calls to `detectChangesInternal` / `destroyInternal` of nested components
|
||||
- remove `Renderer` and `SanitizationService` to update
|
||||
the dom directly via `document.createElement` / `element.appendChild` / setting
|
||||
`Text.nodeValue` / `Element.style....`
|
||||
- remove `AppElement`
|
||||
- create component instance in view constructor
|
||||
|
||||
Code size:
|
||||
- helper methods `createElementAndAppend` / `createTextAndAppend` / `createAnchorAndAppend`
|
||||
- wrap dirty check for `TreeComponent.data` into the method `updateData`
|
||||
in the view class
|
||||
- remove `injectorGetInternal` assuming we can detect that nobody is injecting an `Injector`
|
||||
|
||||
|
||||
## Initial results: File size
|
||||
|
||||
File size for view class of the component (without imports nor host view factory):
|
||||
* before: 81 LOC
|
||||
* after: 53 LOC
|
||||
|
||||
## Initial results: Deep Tree Benchmark
|
||||
|
||||
BENCHMARK deepTree....
|
||||
Description:
|
||||
- bundles: false
|
||||
- depth: 11
|
||||
- forceGc: false
|
||||
- regressionSlopeMetric: scriptTime
|
||||
- runId: f9ae32f0-8580-11e6-914d-9f4f8dbfb5e8
|
||||
- sampleSize: 20
|
||||
- userAgent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36
|
||||
|
||||
...createOnly | gcAmount | gcTime | majorGcTime | pureScriptTime | renderTime | scriptTime
|
||||
--------------- | ------------------ | ------------------ | ------------------ | ------------------ | ------------------ | ------------------
|
||||
ng2.static | 10068.17+-33% | 9.50+-43% | 0.01+-84% | 83.01+-18% | 57.81+-19% | 92.33+-16%
|
||||
ng2.ftl | 493.36+-435% | 0.65+-435% | 0.00+-435% | 28.48+-16% | 53.97+-7% | 28.48+-16%
|
||||
baseline | 53.81+-435% | 0.10+-435% | 0.00+-435% | 19.79+-20% | 52.08+-19% | 19.89+-20%
|
||||
incremental_dom | 311.14+-254% | 2.43+-248% | 0.00+-207% | 68.30+-20% | 59.31+-19% | 70.73+-19%
|
||||
|
||||
.....update | gcAmount | gcTime | majorGcTime | pureScriptTime | renderTime | scriptTime
|
||||
--------------- | ------------------ | ------------------ | ------------------ | ------------------ | ------------------ | ------------------
|
||||
ng2.static | 0.00 | 0.00 | 0.00 | 24.65+-12% | 31.43+-22% | 24.65+-12%
|
||||
ng2.ftl | 0.00 | 0.00+-435% | 0.00+-435% | 16.02+-13% | 29.15+-9% | 16.02+-13%
|
||||
baseline | 509.97+-176% | 0.65+-269% | 0.40+-434% | 28.32+-16% | 35.80+-33% | 28.32+-16%
|
||||
incremental_dom | 961.48+-246% | 0.52+-311% | 0.31+-435% | 28.94+-19% | 36.13+-21% | 28.94+-19%
|
||||
|
||||
Ratios (create, pureScriptTime)
|
||||
- 2.9x faster than current implementation
|
||||
- 2.3x faster than incremental dom
|
||||
- 1.4x slower than baseline
|
|
@ -0,0 +1,319 @@
|
|||
/**
|
||||
* This file is generated by the Angular 2 template compiler.
|
||||
* Do not edit.
|
||||
*/
|
||||
/* tslint:disable */
|
||||
|
||||
import * as import2 from '@angular/common/src/common_module';
|
||||
import * as import5 from '@angular/common/src/localization';
|
||||
import * as import6 from '@angular/core/src/application_init';
|
||||
import * as import3 from '@angular/core/src/application_module';
|
||||
import * as import8 from '@angular/core/src/application_ref';
|
||||
import * as import19 from '@angular/core/src/application_tokens';
|
||||
import * as import31 from '@angular/core/src/change_detection/differs/iterable_differs';
|
||||
import * as import32 from '@angular/core/src/change_detection/differs/keyvalue_differs';
|
||||
import * as import24 from '@angular/core/src/console';
|
||||
import * as import17 from '@angular/core/src/di/injector';
|
||||
import * as import26 from '@angular/core/src/error_handler';
|
||||
import * as import33 from '@angular/core/src/i18n/tokens';
|
||||
import * as import25 from '@angular/core/src/i18n/tokens';
|
||||
import * as import9 from '@angular/core/src/linker/compiler';
|
||||
import * as import0 from '@angular/core/src/linker/ng_module_factory';
|
||||
import * as import15 from '@angular/core/src/linker/view_utils';
|
||||
import * as import29 from '@angular/core/src/render/api';
|
||||
import * as import30 from '@angular/core/src/security';
|
||||
import * as import7 from '@angular/core/src/testability/testability';
|
||||
import * as import22 from '@angular/core/src/zone/ng_zone';
|
||||
import * as import4 from '@angular/platform-browser/src/browser';
|
||||
import * as import16 from '@angular/platform-browser/src/browser/title';
|
||||
import * as import28 from '@angular/platform-browser/src/dom/animation_driver';
|
||||
import * as import23 from '@angular/platform-browser/src/dom/debug/ng_probe';
|
||||
import * as import13 from '@angular/platform-browser/src/dom/dom_renderer';
|
||||
import * as import27 from '@angular/platform-browser/src/dom/dom_tokens';
|
||||
import * as import20 from '@angular/platform-browser/src/dom/events/dom_events';
|
||||
import * as import11 from '@angular/platform-browser/src/dom/events/event_manager';
|
||||
import * as import10 from '@angular/platform-browser/src/dom/events/hammer_gestures';
|
||||
import * as import21 from '@angular/platform-browser/src/dom/events/key_events';
|
||||
import * as import12 from '@angular/platform-browser/src/dom/shared_styles_host';
|
||||
import * as import14 from '@angular/platform-browser/src/security/dom_sanitization_service';
|
||||
|
||||
import * as import1 from './app';
|
||||
import * as import18 from './tree_root.ngfactory';
|
||||
|
||||
class AppModuleInjector extends import0.NgModuleInjector<import1.AppModule> {
|
||||
_CommonModule_0: import2.CommonModule;
|
||||
_ApplicationModule_1: import3.ApplicationModule;
|
||||
_BrowserModule_2: import4.BrowserModule;
|
||||
_AppModule_3: import1.AppModule;
|
||||
__LOCALE_ID_4: any;
|
||||
__NgLocalization_5: import5.NgLocaleLocalization;
|
||||
_ErrorHandler_6: any;
|
||||
_ApplicationInitStatus_7: import6.ApplicationInitStatus;
|
||||
_Testability_8: import7.Testability;
|
||||
_ApplicationRef__9: import8.ApplicationRef_;
|
||||
__ApplicationRef_10: any;
|
||||
__Compiler_11: import9.Compiler;
|
||||
__APP_ID_12: any;
|
||||
__DOCUMENT_13: any;
|
||||
__HAMMER_GESTURE_CONFIG_14: import10.HammerGestureConfig;
|
||||
__EVENT_MANAGER_PLUGINS_15: any[];
|
||||
__EventManager_16: import11.EventManager;
|
||||
__DomSharedStylesHost_17: import12.DomSharedStylesHost;
|
||||
__AnimationDriver_18: any;
|
||||
__DomRootRenderer_19: import13.DomRootRenderer_;
|
||||
__RootRenderer_20: any;
|
||||
__DomSanitizer_21: import14.DomSanitizerImpl;
|
||||
__Sanitizer_22: any;
|
||||
__ViewUtils_23: import15.ViewUtils;
|
||||
__IterableDiffers_24: any;
|
||||
__KeyValueDiffers_25: any;
|
||||
__SharedStylesHost_26: any;
|
||||
__Title_27: import16.Title;
|
||||
__TRANSLATIONS_FORMAT_28: any;
|
||||
constructor(parent: import17.Injector) {
|
||||
super(parent, [import18.TreeRootComponentNgFactory], [import18.TreeRootComponentNgFactory]);
|
||||
}
|
||||
get _LOCALE_ID_4(): any {
|
||||
if ((this.__LOCALE_ID_4 == (null as any))) {
|
||||
(this.__LOCALE_ID_4 = (null as any));
|
||||
}
|
||||
return this.__LOCALE_ID_4;
|
||||
}
|
||||
get _NgLocalization_5(): import5.NgLocaleLocalization {
|
||||
if ((this.__NgLocalization_5 == (null as any))) {
|
||||
(this.__NgLocalization_5 = new import5.NgLocaleLocalization(this._LOCALE_ID_4));
|
||||
}
|
||||
return this.__NgLocalization_5;
|
||||
}
|
||||
get _ApplicationRef_10(): any {
|
||||
if ((this.__ApplicationRef_10 == (null as any))) {
|
||||
(this.__ApplicationRef_10 = this._ApplicationRef__9);
|
||||
}
|
||||
return this.__ApplicationRef_10;
|
||||
}
|
||||
get _Compiler_11(): import9.Compiler {
|
||||
if ((this.__Compiler_11 == (null as any))) {
|
||||
(this.__Compiler_11 = new import9.Compiler());
|
||||
}
|
||||
return this.__Compiler_11;
|
||||
}
|
||||
get _APP_ID_12(): any {
|
||||
if ((this.__APP_ID_12 == (null as any))) {
|
||||
(this.__APP_ID_12 = import19._appIdRandomProviderFactory());
|
||||
}
|
||||
return this.__APP_ID_12;
|
||||
}
|
||||
get _DOCUMENT_13(): any {
|
||||
if ((this.__DOCUMENT_13 == (null as any))) {
|
||||
(this.__DOCUMENT_13 = import4._document());
|
||||
}
|
||||
return this.__DOCUMENT_13;
|
||||
}
|
||||
get _HAMMER_GESTURE_CONFIG_14(): import10.HammerGestureConfig {
|
||||
if ((this.__HAMMER_GESTURE_CONFIG_14 == (null as any))) {
|
||||
(this.__HAMMER_GESTURE_CONFIG_14 = new import10.HammerGestureConfig());
|
||||
}
|
||||
return this.__HAMMER_GESTURE_CONFIG_14;
|
||||
}
|
||||
get _EVENT_MANAGER_PLUGINS_15(): any[] {
|
||||
if ((this.__EVENT_MANAGER_PLUGINS_15 == (null as any))) {
|
||||
(this.__EVENT_MANAGER_PLUGINS_15 = [
|
||||
new import20.DomEventsPlugin(), new import21.KeyEventsPlugin(),
|
||||
new import10.HammerGesturesPlugin(this._HAMMER_GESTURE_CONFIG_14)
|
||||
]);
|
||||
}
|
||||
return this.__EVENT_MANAGER_PLUGINS_15;
|
||||
}
|
||||
get _EventManager_16(): import11.EventManager {
|
||||
if ((this.__EventManager_16 == (null as any))) {
|
||||
(this.__EventManager_16 = new import11.EventManager(
|
||||
this._EVENT_MANAGER_PLUGINS_15, this.parent.get(import22.NgZone)));
|
||||
}
|
||||
return this.__EventManager_16;
|
||||
}
|
||||
get _DomSharedStylesHost_17(): import12.DomSharedStylesHost {
|
||||
if ((this.__DomSharedStylesHost_17 == (null as any))) {
|
||||
(this.__DomSharedStylesHost_17 = new import12.DomSharedStylesHost(this._DOCUMENT_13));
|
||||
}
|
||||
return this.__DomSharedStylesHost_17;
|
||||
}
|
||||
get _AnimationDriver_18(): any {
|
||||
if ((this.__AnimationDriver_18 == (null as any))) {
|
||||
(this.__AnimationDriver_18 = import4._resolveDefaultAnimationDriver());
|
||||
}
|
||||
return this.__AnimationDriver_18;
|
||||
}
|
||||
get _DomRootRenderer_19(): import13.DomRootRenderer_ {
|
||||
if ((this.__DomRootRenderer_19 == (null as any))) {
|
||||
(this.__DomRootRenderer_19 = new import13.DomRootRenderer_(
|
||||
this._DOCUMENT_13, this._EventManager_16, this._DomSharedStylesHost_17,
|
||||
this._AnimationDriver_18));
|
||||
}
|
||||
return this.__DomRootRenderer_19;
|
||||
}
|
||||
get _RootRenderer_20(): any {
|
||||
if ((this.__RootRenderer_20 == (null as any))) {
|
||||
(this.__RootRenderer_20 = import23._createConditionalRootRenderer(
|
||||
this._DomRootRenderer_19, this.parent.get(import23.NgProbeToken, (null as any))));
|
||||
}
|
||||
return this.__RootRenderer_20;
|
||||
}
|
||||
get _DomSanitizer_21(): import14.DomSanitizerImpl {
|
||||
if ((this.__DomSanitizer_21 == (null as any))) {
|
||||
(this.__DomSanitizer_21 = new import14.DomSanitizerImpl());
|
||||
}
|
||||
return this.__DomSanitizer_21;
|
||||
}
|
||||
get _Sanitizer_22(): any {
|
||||
if ((this.__Sanitizer_22 == (null as any))) {
|
||||
(this.__Sanitizer_22 = this._DomSanitizer_21);
|
||||
}
|
||||
return this.__Sanitizer_22;
|
||||
}
|
||||
get _ViewUtils_23(): import15.ViewUtils {
|
||||
if ((this.__ViewUtils_23 == (null as any))) {
|
||||
(this.__ViewUtils_23 =
|
||||
new import15.ViewUtils(this._RootRenderer_20, this._APP_ID_12, this._Sanitizer_22));
|
||||
}
|
||||
return this.__ViewUtils_23;
|
||||
}
|
||||
get _IterableDiffers_24(): any {
|
||||
if ((this.__IterableDiffers_24 == (null as any))) {
|
||||
(this.__IterableDiffers_24 = import3._iterableDiffersFactory());
|
||||
}
|
||||
return this.__IterableDiffers_24;
|
||||
}
|
||||
get _KeyValueDiffers_25(): any {
|
||||
if ((this.__KeyValueDiffers_25 == (null as any))) {
|
||||
(this.__KeyValueDiffers_25 = import3._keyValueDiffersFactory());
|
||||
}
|
||||
return this.__KeyValueDiffers_25;
|
||||
}
|
||||
get _SharedStylesHost_26(): any {
|
||||
if ((this.__SharedStylesHost_26 == (null as any))) {
|
||||
(this.__SharedStylesHost_26 = this._DomSharedStylesHost_17);
|
||||
}
|
||||
return this.__SharedStylesHost_26;
|
||||
}
|
||||
get _Title_27(): import16.Title {
|
||||
if ((this.__Title_27 == (null as any))) {
|
||||
(this.__Title_27 = new import16.Title());
|
||||
}
|
||||
return this.__Title_27;
|
||||
}
|
||||
get _TRANSLATIONS_FORMAT_28(): any {
|
||||
if ((this.__TRANSLATIONS_FORMAT_28 == (null as any))) {
|
||||
(this.__TRANSLATIONS_FORMAT_28 = (null as any));
|
||||
}
|
||||
return this.__TRANSLATIONS_FORMAT_28;
|
||||
}
|
||||
createInternal(): import1.AppModule {
|
||||
this._CommonModule_0 = new import2.CommonModule();
|
||||
this._ApplicationModule_1 = new import3.ApplicationModule();
|
||||
this._BrowserModule_2 =
|
||||
new import4.BrowserModule(this.parent.get(import4.BrowserModule, (null as any)));
|
||||
this._AppModule_3 = new import1.AppModule();
|
||||
this._ErrorHandler_6 = import4.errorHandler();
|
||||
this._ApplicationInitStatus_7 =
|
||||
new import6.ApplicationInitStatus(this.parent.get(import6.APP_INITIALIZER, (null as any)));
|
||||
this._Testability_8 = new import7.Testability(this.parent.get(import22.NgZone));
|
||||
this._ApplicationRef__9 = new import8.ApplicationRef_(
|
||||
this.parent.get(import22.NgZone), this.parent.get(import24.Console), this,
|
||||
this._ErrorHandler_6, this, this._ApplicationInitStatus_7,
|
||||
this.parent.get(import7.TestabilityRegistry, (null as any)), this._Testability_8);
|
||||
return this._AppModule_3;
|
||||
}
|
||||
getInternal(token: any, notFoundResult: any): any {
|
||||
if ((token === import2.CommonModule)) {
|
||||
return this._CommonModule_0;
|
||||
}
|
||||
if ((token === import3.ApplicationModule)) {
|
||||
return this._ApplicationModule_1;
|
||||
}
|
||||
if ((token === import4.BrowserModule)) {
|
||||
return this._BrowserModule_2;
|
||||
}
|
||||
if ((token === import1.AppModule)) {
|
||||
return this._AppModule_3;
|
||||
}
|
||||
if ((token === import25.LOCALE_ID)) {
|
||||
return this._LOCALE_ID_4;
|
||||
}
|
||||
if ((token === import5.NgLocalization)) {
|
||||
return this._NgLocalization_5;
|
||||
}
|
||||
if ((token === import26.ErrorHandler)) {
|
||||
return this._ErrorHandler_6;
|
||||
}
|
||||
if ((token === import6.ApplicationInitStatus)) {
|
||||
return this._ApplicationInitStatus_7;
|
||||
}
|
||||
if ((token === import7.Testability)) {
|
||||
return this._Testability_8;
|
||||
}
|
||||
if ((token === import8.ApplicationRef_)) {
|
||||
return this._ApplicationRef__9;
|
||||
}
|
||||
if ((token === import8.ApplicationRef)) {
|
||||
return this._ApplicationRef_10;
|
||||
}
|
||||
if ((token === import9.Compiler)) {
|
||||
return this._Compiler_11;
|
||||
}
|
||||
if ((token === import19.APP_ID)) {
|
||||
return this._APP_ID_12;
|
||||
}
|
||||
if ((token === import27.DOCUMENT)) {
|
||||
return this._DOCUMENT_13;
|
||||
}
|
||||
if ((token === import10.HAMMER_GESTURE_CONFIG)) {
|
||||
return this._HAMMER_GESTURE_CONFIG_14;
|
||||
}
|
||||
if ((token === import11.EVENT_MANAGER_PLUGINS)) {
|
||||
return this._EVENT_MANAGER_PLUGINS_15;
|
||||
}
|
||||
if ((token === import11.EventManager)) {
|
||||
return this._EventManager_16;
|
||||
}
|
||||
if ((token === import12.DomSharedStylesHost)) {
|
||||
return this._DomSharedStylesHost_17;
|
||||
}
|
||||
if ((token === import28.AnimationDriver)) {
|
||||
return this._AnimationDriver_18;
|
||||
}
|
||||
if ((token === import13.DomRootRenderer)) {
|
||||
return this._DomRootRenderer_19;
|
||||
}
|
||||
if ((token === import29.RootRenderer)) {
|
||||
return this._RootRenderer_20;
|
||||
}
|
||||
if ((token === import14.DomSanitizer)) {
|
||||
return this._DomSanitizer_21;
|
||||
}
|
||||
if ((token === import30.Sanitizer)) {
|
||||
return this._Sanitizer_22;
|
||||
}
|
||||
if ((token === import15.ViewUtils)) {
|
||||
return this._ViewUtils_23;
|
||||
}
|
||||
if ((token === import31.IterableDiffers)) {
|
||||
return this._IterableDiffers_24;
|
||||
}
|
||||
if ((token === import32.KeyValueDiffers)) {
|
||||
return this._KeyValueDiffers_25;
|
||||
}
|
||||
if ((token === import12.SharedStylesHost)) {
|
||||
return this._SharedStylesHost_26;
|
||||
}
|
||||
if ((token === import16.Title)) {
|
||||
return this._Title_27;
|
||||
}
|
||||
if ((token === import33.TRANSLATIONS_FORMAT)) {
|
||||
return this._TRANSLATIONS_FORMAT_28;
|
||||
}
|
||||
return notFoundResult;
|
||||
}
|
||||
destroyInternal(): void { this._ApplicationRef__9.ngOnDestroy(); }
|
||||
}
|
||||
export const AppModuleNgFactory: import0.NgModuleFactory<import1.AppModule> =
|
||||
new import0.NgModuleFactory(AppModuleInjector, import1.AppModule);
|
|
@ -0,0 +1 @@
|
|||
export class AppModule {}
|
|
@ -0,0 +1,17 @@
|
|||
export function createElementAndAppend(parent: any, name: string) {
|
||||
const el = document.createElement(name);
|
||||
parent.appendChild(el);
|
||||
return el;
|
||||
}
|
||||
|
||||
export function createTextAndAppend(parent: any) {
|
||||
const txt = document.createTextNode('');
|
||||
parent.appendChild(txt);
|
||||
return txt;
|
||||
}
|
||||
|
||||
export function createAnchorAndAppend(parent: any) {
|
||||
const txt = document.createComment('');
|
||||
parent.appendChild(txt);
|
||||
return txt;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
<h2>Params</h2>
|
||||
<form>
|
||||
Depth:
|
||||
<input type="number" name="depth" placeholder="depth" value="9">
|
||||
<br>
|
||||
<button>Apply</button>
|
||||
</form>
|
||||
|
||||
<h2>Ng2 Static FTL Tree Benchmark</h2>
|
||||
<p>
|
||||
<button id="destroyDom">destroyDom</button>
|
||||
<button id="createDom">createDom</button>
|
||||
<button id="updateDomProfile">profile updateDom</button>
|
||||
<button id="createDomProfile">profile createDom</button>
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<tree id="root"></tree>
|
||||
</div>
|
||||
|
||||
<script src="../../bootstrap_ng2.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,41 @@
|
|||
import {ApplicationRef, NgModule, enableProdMode} from '@angular/core';
|
||||
import {platformBrowser} from '@angular/platform-browser';
|
||||
|
||||
import {bindAction, profile} from '../../util';
|
||||
import {TreeNode, buildTree, emptyTree} from '../util';
|
||||
|
||||
import {AppModuleNgFactory} from './app.ngfactory';
|
||||
import {TreeRootComponent} from './tree';
|
||||
|
||||
export function main() {
|
||||
let tree: TreeRootComponent;
|
||||
let appRef: ApplicationRef;
|
||||
|
||||
function destroyDom() {
|
||||
tree.data = emptyTree;
|
||||
appRef.tick();
|
||||
}
|
||||
|
||||
function createDom() {
|
||||
tree.data = buildTree();
|
||||
appRef.tick();
|
||||
}
|
||||
|
||||
function noop() {}
|
||||
|
||||
function init() {
|
||||
enableProdMode();
|
||||
platformBrowser().bootstrapModuleFactory(AppModuleNgFactory).then((ref) => {
|
||||
const injector = ref.injector;
|
||||
appRef = injector.get(ApplicationRef);
|
||||
|
||||
tree = appRef.components[0].instance;
|
||||
bindAction('#destroyDom', destroyDom);
|
||||
bindAction('#createDom', createDom);
|
||||
bindAction('#updateDomProfile', profile(createDom, noop, 'update'));
|
||||
bindAction('#createDomProfile', profile(createDom, destroyDom, 'create'));
|
||||
});
|
||||
}
|
||||
|
||||
init();
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import {TreeNode, emptyTree} from '../util';
|
||||
|
||||
export class TreeRootComponent { data: TreeNode = emptyTree; }
|
||||
|
||||
export class TreeBranchComponent { data: TreeNode; }
|
||||
|
||||
export class TreeLeafComponent { data: TreeNode; }
|
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* This file is hand tweeked based on
|
||||
* the out put of the Angular 2 template compiler
|
||||
* and then hand tweeked to show possible future output.
|
||||
*/
|
||||
/* tslint:disable */
|
||||
|
||||
import * as import7 from '@angular/core/src/change_detection/change_detection';
|
||||
import * as import5 from '@angular/core/src/di/injector';
|
||||
import * as import9 from '@angular/core/src/linker/component_factory';
|
||||
import * as import2 from '@angular/core/src/linker/element';
|
||||
import * as import1 from '@angular/core/src/linker/view';
|
||||
import * as import6 from '@angular/core/src/linker/view_type';
|
||||
import * as import4 from '@angular/core/src/linker/view_utils';
|
||||
import * as import8 from '@angular/core/src/metadata/view';
|
||||
import * as import0 from '@angular/core/src/render/api';
|
||||
import * as import12 from '@angular/core/src/security';
|
||||
|
||||
import {createAnchorAndAppend, createElementAndAppend, createTextAndAppend} from './ftl_util';
|
||||
import * as import3 from './tree';
|
||||
import * as import11 from './tree_leaf.ngfactory';
|
||||
|
||||
export class View_TreeTreeComponent {
|
||||
context: import3.TreeBranchComponent;
|
||||
ref: any;
|
||||
_el_0: any;
|
||||
_text_1: any;
|
||||
_el_2: any;
|
||||
_TreeComponent20_2_4View: any;
|
||||
_el_3: any;
|
||||
_TreeComponent20_3_4View: any;
|
||||
/*private*/ _expr_0: any;
|
||||
/*private*/ _expr_1: any;
|
||||
/*private*/ _expr_2: any;
|
||||
constructor(depth: number, parentRenderNode: any) {
|
||||
this.context = new import3.TreeBranchComponent();
|
||||
this._el_0 = createElementAndAppend(parentRenderNode, 'span');
|
||||
this._text_1 = createTextAndAppend(this._el_0);
|
||||
this._el_2 = createElementAndAppend(parentRenderNode, 'tree');
|
||||
this._TreeComponent20_2_4View = depth > 0 ? new View_TreeTreeComponent(depth - 1, this._el_2) :
|
||||
new import11.View_TreeLeafComponent(this._el_2);
|
||||
this._el_3 = createElementAndAppend(parentRenderNode, 'tree');
|
||||
this._TreeComponent20_3_4View = depth > 0 ? new View_TreeTreeComponent(depth - 1, this._el_3) :
|
||||
new import11.View_TreeLeafComponent(this._el_3);
|
||||
this._expr_0 = import7.UNINITIALIZED;
|
||||
this._expr_1 = import7.UNINITIALIZED;
|
||||
this._expr_2 = import7.UNINITIALIZED;
|
||||
}
|
||||
destroyInternal() {
|
||||
this._TreeComponent20_2_4View.destroyInternal();
|
||||
this._TreeComponent20_3_4View.destroyInternal();
|
||||
}
|
||||
updateData(currVal_2: any) {
|
||||
if (import4.checkBinding(false, this._expr_2, currVal_2)) {
|
||||
this.context.data = currVal_2;
|
||||
this._expr_2 = currVal_2;
|
||||
}
|
||||
}
|
||||
detectChangesInternal(throwOnChange: boolean): void {
|
||||
this._TreeComponent20_2_4View.updateData(this.context.data.right);
|
||||
this._TreeComponent20_3_4View.updateData(this.context.data.left);
|
||||
|
||||
const currVal_0: any = ((this.context.data.depth % 2) ? '' : 'grey');
|
||||
if (import4.checkBinding(throwOnChange, this._expr_0, currVal_0)) {
|
||||
this._el_0.style.backgroundColor = currVal_0;
|
||||
this._expr_0 = currVal_0;
|
||||
}
|
||||
const currVal_1: any = import4.interpolate(1, ' ', this.context.data.value, ' ');
|
||||
if (import4.checkBinding(throwOnChange, this._expr_1, currVal_1)) {
|
||||
this._text_1.nodeValue = currVal_1;
|
||||
this._expr_1 = currVal_1;
|
||||
}
|
||||
this._TreeComponent20_2_4View.detectChangesInternal(throwOnChange);
|
||||
this._TreeComponent20_3_4View.detectChangesInternal(throwOnChange);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
* This file is hand tweeked based on
|
||||
* the out put of the Angular 2 template compiler
|
||||
* and then hand tweeked to show possible future output.
|
||||
*/
|
||||
/* tslint:disable */
|
||||
|
||||
import * as import7 from '@angular/core/src/change_detection/change_detection';
|
||||
import * as import5 from '@angular/core/src/di/injector';
|
||||
import * as import9 from '@angular/core/src/linker/component_factory';
|
||||
import * as import2 from '@angular/core/src/linker/element';
|
||||
import * as import1 from '@angular/core/src/linker/view';
|
||||
import * as import6 from '@angular/core/src/linker/view_type';
|
||||
import * as import4 from '@angular/core/src/linker/view_utils';
|
||||
import * as import8 from '@angular/core/src/metadata/view';
|
||||
import * as import0 from '@angular/core/src/render/api';
|
||||
import * as import10 from '@angular/core/src/security';
|
||||
|
||||
import * as import3 from './tree';
|
||||
|
||||
export class View_TreeLeafComponent {
|
||||
context: import3.TreeLeafComponent;
|
||||
_el_0: any;
|
||||
_text_1: any;
|
||||
/*private*/ _expr_0: any;
|
||||
/*private*/ _expr_1: any;
|
||||
/*private*/ _expr_2: any;
|
||||
constructor(parentRenderNode: any) {
|
||||
this.context = new import3.TreeLeafComponent();
|
||||
this._el_0 = document.createElement('span');
|
||||
parentRenderNode.appendChild(this._el_0);
|
||||
this._text_1 = document.createTextNode('');
|
||||
this._el_0.appendChild(this._text_1);
|
||||
this._expr_0 = import7.UNINITIALIZED;
|
||||
this._expr_1 = import7.UNINITIALIZED;
|
||||
}
|
||||
updateData(currVal_2: any) {
|
||||
if (import4.checkBinding(false, this._expr_2, currVal_2)) {
|
||||
this.context.data = currVal_2;
|
||||
this._expr_2 = currVal_2;
|
||||
}
|
||||
}
|
||||
destroyInternal() {}
|
||||
detectChangesInternal(throwOnChange: boolean): void {
|
||||
const currVal_0: any = ((this.context.data.depth % 2) ? '' : 'grey');
|
||||
if (import4.checkBinding(throwOnChange, this._expr_0, currVal_0)) {
|
||||
this._el_0.style.backgroundColor = currVal_0;
|
||||
this._expr_0 = currVal_0;
|
||||
}
|
||||
const currVal_1: any = import4.interpolate(1, ' ', this.context.data.value, ' ');
|
||||
if (import4.checkBinding(throwOnChange, this._expr_1, currVal_1)) {
|
||||
this._text_1.nodeValue = currVal_1;
|
||||
this._expr_1 = currVal_1;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
/**
|
||||
* This file is generated by the Angular 2 template compiler.
|
||||
* Do not edit.
|
||||
*/
|
||||
/* tslint:disable */
|
||||
|
||||
import * as import10 from '@angular/common/src/directives/ng_if';
|
||||
import * as import7 from '@angular/core/src/change_detection/change_detection';
|
||||
import * as import5 from '@angular/core/src/di/injector';
|
||||
import * as import9 from '@angular/core/src/linker/component_factory';
|
||||
import * as import2 from '@angular/core/src/linker/element';
|
||||
import * as import11 from '@angular/core/src/linker/template_ref';
|
||||
import * as import1 from '@angular/core/src/linker/view';
|
||||
import * as import6 from '@angular/core/src/linker/view_type';
|
||||
import * as import4 from '@angular/core/src/linker/view_utils';
|
||||
import * as import8 from '@angular/core/src/metadata/view';
|
||||
import * as import0 from '@angular/core/src/render/api';
|
||||
|
||||
import {maxDepth} from '../util';
|
||||
|
||||
import * as import3 from './tree';
|
||||
import * as import12 from './tree';
|
||||
import * as import13 from './tree_branch.ngfactory';
|
||||
|
||||
var renderType_TreeRootComponent_Host: import0.RenderComponentType = (null as any);
|
||||
class _View_TreeRootComponent_Host0 extends import1.AppView<any> {
|
||||
_el_0: any;
|
||||
/*private*/ _appEl_0: import2.AppElement;
|
||||
_TreeRootComponent_0_4: import3.TreeRootComponent;
|
||||
constructor(
|
||||
viewUtils: import4.ViewUtils, parentInjector: import5.Injector,
|
||||
declarationEl: import2.AppElement) {
|
||||
super(
|
||||
_View_TreeRootComponent_Host0, renderType_TreeRootComponent_Host, import6.ViewType.HOST,
|
||||
viewUtils, parentInjector, declarationEl, import7.ChangeDetectorStatus.CheckAlways);
|
||||
}
|
||||
createInternal(rootSelector: string): import2.AppElement {
|
||||
this._el_0 = this.selectOrCreateHostElement('tree', rootSelector, (null as any));
|
||||
this._appEl_0 = new import2.AppElement(0, (null as any), this, this._el_0);
|
||||
var compView_0: any =
|
||||
viewFactory_TreeRootComponent0(this.viewUtils, this.injector(0), this._appEl_0);
|
||||
this._TreeRootComponent_0_4 = new import3.TreeRootComponent();
|
||||
this._appEl_0.initComponent(this._TreeRootComponent_0_4, [], compView_0);
|
||||
compView_0.create(this._TreeRootComponent_0_4, this.projectableNodes, (null as any));
|
||||
this.init([].concat([this._el_0]), [this._el_0], [], []);
|
||||
return this._appEl_0;
|
||||
}
|
||||
injectorGetInternal(token: any, requestNodeIndex: number, notFoundResult: any): any {
|
||||
if (((token === import3.TreeRootComponent) && (0 === requestNodeIndex))) {
|
||||
return this._TreeRootComponent_0_4;
|
||||
}
|
||||
return notFoundResult;
|
||||
}
|
||||
}
|
||||
function viewFactory_TreeRootComponent_Host0(
|
||||
viewUtils: import4.ViewUtils, parentInjector: import5.Injector,
|
||||
declarationEl: import2.AppElement): import1.AppView<any> {
|
||||
if ((renderType_TreeRootComponent_Host === (null as any))) {
|
||||
(renderType_TreeRootComponent_Host =
|
||||
viewUtils.createRenderComponentType('', 0, import8.ViewEncapsulation.None, [], {}));
|
||||
}
|
||||
return new _View_TreeRootComponent_Host0(viewUtils, parentInjector, declarationEl);
|
||||
}
|
||||
export const TreeRootComponentNgFactory: import9.ComponentFactory<import3.TreeRootComponent> =
|
||||
new import9.ComponentFactory<import3.TreeRootComponent>(
|
||||
'tree', viewFactory_TreeRootComponent_Host0, import3.TreeRootComponent);
|
||||
const styles_TreeRootComponent: any[] = [];
|
||||
var renderType_TreeRootComponent: import0.RenderComponentType = (null as any);
|
||||
class _View_TreeRootComponent0 extends import1.AppView<import3.TreeRootComponent> {
|
||||
_anchor_0: any;
|
||||
/*private*/ _appEl_0: import2.AppElement;
|
||||
_TemplateRef_0_5: any;
|
||||
_NgIf_0_6: import10.NgIf;
|
||||
/*private*/ _expr_0: any;
|
||||
constructor(
|
||||
viewUtils: import4.ViewUtils, parentInjector: import5.Injector,
|
||||
declarationEl: import2.AppElement) {
|
||||
super(
|
||||
_View_TreeRootComponent0, renderType_TreeRootComponent, import6.ViewType.COMPONENT,
|
||||
viewUtils, parentInjector, declarationEl, import7.ChangeDetectorStatus.CheckAlways);
|
||||
}
|
||||
createInternal(rootSelector: string): import2.AppElement {
|
||||
const parentRenderNode: any =
|
||||
this.renderer.createViewRoot(this.declarationAppElement.nativeElement);
|
||||
this._anchor_0 = this.renderer.createTemplateAnchor(parentRenderNode, (null as any));
|
||||
this._appEl_0 = new import2.AppElement(0, (null as any), this, this._anchor_0);
|
||||
this._TemplateRef_0_5 =
|
||||
new import11.TemplateRef_(this._appEl_0, viewFactory_TreeRootComponent1);
|
||||
this._NgIf_0_6 = new import10.NgIf(this._appEl_0.vcRef, this._TemplateRef_0_5);
|
||||
this._expr_0 = import7.UNINITIALIZED;
|
||||
this.init([], [this._anchor_0], [], []);
|
||||
return (null as any);
|
||||
}
|
||||
injectorGetInternal(token: any, requestNodeIndex: number, notFoundResult: any): any {
|
||||
if (((token === import11.TemplateRef) && (0 === requestNodeIndex))) {
|
||||
return this._TemplateRef_0_5;
|
||||
}
|
||||
if (((token === import10.NgIf) && (0 === requestNodeIndex))) {
|
||||
return this._NgIf_0_6;
|
||||
}
|
||||
return notFoundResult;
|
||||
}
|
||||
detectChangesInternal(throwOnChange: boolean): void {
|
||||
const currVal_0: any = (this.context.data.left != (null as any));
|
||||
if (import4.checkBinding(throwOnChange, this._expr_0, currVal_0)) {
|
||||
this._NgIf_0_6.ngIf = currVal_0;
|
||||
this._expr_0 = currVal_0;
|
||||
}
|
||||
this.detectContentChildrenChanges(throwOnChange);
|
||||
this.detectViewChildrenChanges(throwOnChange);
|
||||
}
|
||||
}
|
||||
export function viewFactory_TreeRootComponent0(
|
||||
viewUtils: import4.ViewUtils, parentInjector: import5.Injector,
|
||||
declarationEl: import2.AppElement): import1.AppView<import3.TreeRootComponent> {
|
||||
if ((renderType_TreeRootComponent === (null as any))) {
|
||||
(renderType_TreeRootComponent = viewUtils.createRenderComponentType(
|
||||
'/Users/tbosch/projects/conf-demos/ngc-demo/src/ng2_static/root_tree.ts class TreeRootComponent - inline template',
|
||||
0, import8.ViewEncapsulation.None, styles_TreeRootComponent, {}));
|
||||
}
|
||||
return new _View_TreeRootComponent0(viewUtils, parentInjector, declarationEl);
|
||||
}
|
||||
class _View_TreeRootComponent1 extends import1.AppView<any> {
|
||||
_el_0: any;
|
||||
_TreeComponent0_0_4View: any;
|
||||
constructor(
|
||||
viewUtils: import4.ViewUtils, parentInjector: import5.Injector,
|
||||
declarationEl: import2.AppElement) {
|
||||
super(
|
||||
_View_TreeRootComponent1, renderType_TreeRootComponent, import6.ViewType.EMBEDDED,
|
||||
viewUtils, parentInjector, declarationEl, import7.ChangeDetectorStatus.CheckAlways);
|
||||
}
|
||||
createInternal(rootSelector: string): import2.AppElement {
|
||||
this._el_0 = this.renderer.createElement((null as any), 'tree0', (null as any));
|
||||
this._TreeComponent0_0_4View = new import13.View_TreeTreeComponent(maxDepth - 1, this._el_0);
|
||||
this.init([].concat([this._el_0]), [this._el_0], [], []);
|
||||
return (null as any);
|
||||
}
|
||||
destroyInternal() { this._TreeComponent0_0_4View.destroyInternal(); }
|
||||
detectChangesInternal(throwOnChange: boolean): void {
|
||||
this._TreeComponent0_0_4View.updateData(this.parent.context.data);
|
||||
this.detectContentChildrenChanges(throwOnChange);
|
||||
this._TreeComponent0_0_4View.detectChangesInternal(throwOnChange);
|
||||
}
|
||||
}
|
||||
function viewFactory_TreeRootComponent1(
|
||||
viewUtils: import4.ViewUtils, parentInjector: import5.Injector,
|
||||
declarationEl: import2.AppElement): import1.AppView<any> {
|
||||
return new _View_TreeRootComponent1(viewUtils, parentInjector, declarationEl);
|
||||
}
|
|
@ -34,7 +34,9 @@ export function openBrowser(config: {
|
|||
browser.ignoreSynchronization = true;
|
||||
}
|
||||
var params = config.params || [];
|
||||
params = params.concat([{name: 'bundles', value: cmdArgs.bundles}]);
|
||||
if (!params.some((param) => param.name === 'bundles')) {
|
||||
params = params.concat([{name: 'bundles', value: cmdArgs.bundles}]);
|
||||
}
|
||||
|
||||
var urlParams: string[] = [];
|
||||
params.forEach((param) => { urlParams.push(param.name + '=' + param.value); });
|
||||
|
|
Loading…
Reference in New Issue