From ddb3ee61e74c52bda6eef1ec5e6a8f0e2cf88f65 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 30 Dec 2020 17:13:01 +0200 Subject: [PATCH] UI: Optimizations - AOT + JIT --- ui-ngx/angular.json | 2 + ui-ngx/extra-webpack.config.js | 29 +++++-- ui-ngx/package.json | 1 + ui-ngx/src/app/core/auth/auth.service.ts | 8 +- .../dynamic-component-factory.service.ts | 59 +++++++------ .../app/core/services/resources.service.ts | 87 ++++++++++--------- .../complex-filter-predicate.component.ts | 2 +- .../filter/filter-text.component.ts | 2 +- .../alarm-rule-condition-dialog.component.ts | 2 +- .../widget/dialog/custom-dialog.component.ts | 3 +- .../lib/alarms-table-widget.component.ts | 2 +- .../components/widget/lib/maps/map-models.ts | 5 ++ .../lib/multiple-input-widget.component.ts | 4 +- .../lib/timeseries-table-widget.component.ts | 2 +- .../json-form/json-form.component.ts | 36 ++++++-- .../shared/components/led-light.component.ts | 14 +-- 16 files changed, 165 insertions(+), 93 deletions(-) diff --git a/ui-ngx/angular.json b/ui-ngx/angular.json index 1960aa0103..a9a2cac52f 100644 --- a/ui-ngx/angular.json +++ b/ui-ngx/angular.json @@ -22,6 +22,7 @@ "main": "src/main.ts", "polyfills": "src/polyfills.ts", "tsConfig": "src/tsconfig.app.json", + "aot": true, "assets": [ "src/thingsboard.ico", "src/assets", @@ -161,6 +162,7 @@ "serve": { "builder": "@angular-builders/custom-webpack:dev-server", "options": { + "aot": true, "browserTarget": "thingsboard:build", "proxyConfig": "proxy.conf.js" }, diff --git a/ui-ngx/extra-webpack.config.js b/ui-ngx/extra-webpack.config.js index 9adc0e5852..f834952fb3 100644 --- a/ui-ngx/extra-webpack.config.js +++ b/ui-ngx/extra-webpack.config.js @@ -14,8 +14,10 @@ * limitations under the License. */ const CompressionPlugin = require("compression-webpack-plugin"); +const TerserPlugin = require("terser-webpack-plugin"); const webpack = require("webpack"); const dirTree = require("directory-tree"); +const AngularCompilerPlugin = require('@ngtools/webpack'); var langs = []; @@ -25,12 +27,14 @@ dirTree("./src/assets/locale/", {extensions: /\.json$/}, (item) => { langs.push(item.name.slice(item.name.lastIndexOf("-") + 1, -5)); }); -module.exports = { - plugins: [ +module.exports = (config, options) => { + config.plugins.push( new webpack.DefinePlugin({ TB_VERSION: JSON.stringify(require("./package.json").version), SUPPORTED_LANGS: JSON.stringify(langs), - }), + }) + ); + config.plugins.push( new CompressionPlugin({ filename: "[path][base].gz[query]", algorithm: "gzip", @@ -38,6 +42,21 @@ module.exports = { threshold: 10240, minRatio: 0.8, deleteOriginalAssets: false, - }), - ], + }) + ); + + if (config.mode === 'production') { + const index = config.plugins.findIndex(p => p instanceof AngularCompilerPlugin.AngularCompilerPlugin); + const angularCompilerOptions = config.plugins[index]._options; + angularCompilerOptions.emitClassMetadata = true; + angularCompilerOptions.emitNgModuleScope = true; + config.plugins.splice(index, 1); + config.plugins.push(new AngularCompilerPlugin.AngularCompilerPlugin(angularCompilerOptions)); + const terserPluginOptions = config.optimization.minimizer[1].options; + delete terserPluginOptions.terserOptions.compress.global_defs.ngJitMode; + terserPluginOptions.terserOptions.compress.side_effects = false; + config.optimization.minimizer.splice(1, 1); + config.optimization.minimizer.push(new TerserPlugin(terserPluginOptions)); + } + return config; }; diff --git a/ui-ngx/package.json b/ui-ngx/package.json index bf05109bb6..a2449a2a7f 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -100,6 +100,7 @@ "@angular/cli": "^10.1.5", "@angular/compiler-cli": "^10.1.5", "@angular/language-service": "^10.1.5", + "@ngtools/webpack": "10.1.5", "@types/canvas-gauges": "^2.1.2", "@types/flot": "^0.0.31", "@types/jasmine": "^3.5.12", diff --git a/ui-ngx/src/app/core/auth/auth.service.ts b/ui-ngx/src/app/core/auth/auth.service.ts index 3d4a203252..ff83b84167 100644 --- a/ui-ngx/src/app/core/auth/auth.service.ts +++ b/ui-ngx/src/app/core/auth/auth.service.ts @@ -446,8 +446,12 @@ export class AuthService { const refreshTokenValid = AuthService.isTokenValid('refresh_token'); this.setUserFromJwtToken(null, null, false); if (!refreshTokenValid) { - this.refreshTokenSubject.error(new Error(this.translate.instant('access.refresh-token-expired'))); - this.refreshTokenSubject = null; + this.translate.get('access.refresh-token-expired').subscribe( + (translation) => { + this.refreshTokenSubject.error(new Error(translation)); + this.refreshTokenSubject = null; + } + ); } else { const refreshTokenRequest = { refreshToken diff --git a/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts b/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts index 53a72ad2cc..2fe11cccb5 100644 --- a/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts +++ b/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts @@ -59,37 +59,40 @@ export class DynamicComponentFactoryService { template: string, modules?: Type[]): Observable> { const dymamicComponentFactorySubject = new ReplaySubject>(); - const comp = this.createDynamicComponent(componentType, template); - let moduleImports: Type[] = [CommonModule]; - if (modules) { - moduleImports = [...moduleImports, ...modules]; - } - // noinspection AngularInvalidImportedOrDeclaredSymbol - @NgModule({ - declarations: [comp], - imports: moduleImports - }) - class DynamicComponentInstanceModule extends DynamicComponentModule {} - try { - this.compiler.compileModuleAsync(DynamicComponentInstanceModule).then( - (module) => { - const moduleRef = module.create(this.injector); - const factory = moduleRef.componentFactoryResolver.resolveComponentFactory(comp); - this.dynamicComponentModulesMap.set(factory, { - moduleRef, - moduleType: module.moduleType - }); - dymamicComponentFactorySubject.next(factory); - dymamicComponentFactorySubject.complete(); + import('@angular/compiler').then( + () => { + const comp = this.createDynamicComponent(componentType, template); + let moduleImports: Type[] = [CommonModule]; + if (modules) { + moduleImports = [...moduleImports, ...modules]; } - ).catch( - (e) => { + // noinspection AngularInvalidImportedOrDeclaredSymbol + const dynamicComponentInstanceModule = NgModule({ + declarations: [comp], + imports: moduleImports + })(class DynamicComponentInstanceModule extends DynamicComponentModule {}); + try { + this.compiler.compileModuleAsync(dynamicComponentInstanceModule).then( + (module) => { + const moduleRef = module.create(this.injector); + const factory = moduleRef.componentFactoryResolver.resolveComponentFactory(comp); + this.dynamicComponentModulesMap.set(factory, { + moduleRef, + moduleType: module.moduleType + }); + dymamicComponentFactorySubject.next(factory); + dymamicComponentFactorySubject.complete(); + } + ).catch( + (e) => { + dymamicComponentFactorySubject.error(e); + } + ); + } catch (e) { dymamicComponentFactorySubject.error(e); } - ); - } catch (e) { - dymamicComponentFactorySubject.error(e); - } + } + ); return dymamicComponentFactorySubject.asObservable(); } diff --git a/ui-ngx/src/app/core/services/resources.service.ts b/ui-ngx/src/app/core/services/resources.service.ts index 97dd480e2e..f27f8b2dee 100644 --- a/ui-ngx/src/app/core/services/resources.service.ts +++ b/ui-ngx/src/app/core/services/resources.service.ts @@ -78,28 +78,31 @@ export class ResourcesService { (module) => { const modules = this.extractNgModules(module); if (modules.length) { - const tasks: Promise>[] = []; - for (const m of modules) { - tasks.push(this.compiler.compileModuleAndAllComponentsAsync(m)); - } - forkJoin(tasks).subscribe((compiled) => { - try { - const componentFactories: ComponentFactory[] = []; - for (const c of compiled) { - c.ngModuleFactory.create(this.injector); - componentFactories.push(...c.componentFactories); + import('@angular/compiler').then( + () => { + const tasks: Promise>[] = []; + for (const m of modules) { + tasks.push(this.compiler.compileModuleAndAllComponentsAsync(m)); } - this.loadedFactories[url].next(componentFactories); - this.loadedFactories[url].complete(); - } catch (e) { - this.loadedFactories[url].error(new Error(`Unable to init module from url: ${url}`)); - delete this.loadedFactories[url]; - } - }, - (e) => { - this.loadedFactories[url].error(new Error(`Unable to compile module from url: ${url}`)); - delete this.loadedFactories[url]; - }); + forkJoin(tasks).subscribe((compiled) => { + try { + const componentFactories: ComponentFactory[] = []; + for (const c of compiled) { + c.ngModuleFactory.create(this.injector); + componentFactories.push(...c.componentFactories); + } + this.loadedFactories[url].next(componentFactories); + this.loadedFactories[url].complete(); + } catch (e) { + this.loadedFactories[url].error(new Error(`Unable to init module from url: ${url}`)); + delete this.loadedFactories[url]; + } + }, + (e) => { + this.loadedFactories[url].error(new Error(`Unable to compile module from url: ${url}`)); + delete this.loadedFactories[url]; + }); } + ); } else { this.loadedFactories[url].error(new Error(`Module '${url}' doesn't have default export!`)); delete this.loadedFactories[url]; @@ -133,26 +136,30 @@ export class ResourcesService { } catch (e) { } if (modules && modules.length) { - const tasks: Promise>[] = []; - for (const m of modules) { - tasks.push(this.compiler.compileModuleAndAllComponentsAsync(m)); - } - forkJoin(tasks).subscribe((compiled) => { - try { - for (const c of compiled) { - c.ngModuleFactory.create(this.injector); - } - this.loadedModules[url].next(modules); - this.loadedModules[url].complete(); - } catch (e) { - this.loadedModules[url].error(new Error(`Unable to init module from url: ${url}`)); - delete this.loadedModules[url]; + import('@angular/compiler').then( + () => { + const tasks: Promise>[] = []; + for (const m of modules) { + tasks.push(this.compiler.compileModuleAndAllComponentsAsync(m)); } - }, - (e) => { - this.loadedModules[url].error(new Error(`Unable to compile module from url: ${url}`)); - delete this.loadedModules[url]; - }); + forkJoin(tasks).subscribe((compiled) => { + try { + for (const c of compiled) { + c.ngModuleFactory.create(this.injector); + } + this.loadedModules[url].next(modules); + this.loadedModules[url].complete(); + } catch (e) { + this.loadedModules[url].error(new Error(`Unable to init module from url: ${url}`)); + delete this.loadedModules[url]; + } + }, + (e) => { + this.loadedModules[url].error(new Error(`Unable to compile module from url: ${url}`)); + delete this.loadedModules[url]; + }); + } + ); } else { this.loadedModules[url].error(new Error(`Module '${url}' doesn't have default export or not NgModule!`)); delete this.loadedModules[url]; diff --git a/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.ts b/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.ts index 87bad30d5a..5c6d46ed8b 100644 --- a/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.ts +++ b/ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.ts @@ -77,7 +77,7 @@ export class ComplexFilterPredicateComponent implements ControlValueAccessor, On this.complexFilterPredicate = predicate; } - private openComplexFilterDialog() { + public openComplexFilterDialog() { this.dialog.open(ComplexFilterPredicateDialogComponent, { disableClose: true, diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-text.component.ts b/ui-ngx/src/app/modules/home/components/filter/filter-text.component.ts index 5499cdc765..d98e627ee8 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-text.component.ts +++ b/ui-ngx/src/app/modules/home/components/filter/filter-text.component.ts @@ -59,7 +59,7 @@ export class FilterTextComponent implements ControlValueAccessor, OnInit { requiredClass = false; - private filterText: string; + public filterText: string; private propagateChange = (v: any) => { }; diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts index 0c81ebf397..6a6830e70c 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts @@ -38,7 +38,7 @@ export interface AlarmRuleConditionDialogData { selector: 'tb-alarm-rule-condition-dialog', templateUrl: './alarm-rule-condition-dialog.component.html', providers: [{provide: ErrorStateMatcher, useExisting: AlarmRuleConditionDialogComponent}], - styleUrls: ['/alarm-rule-condition-dialog.component.scss'] + styleUrls: ['./alarm-rule-condition-dialog.component.scss'] }) export class AlarmRuleConditionDialogComponent extends DialogComponent implements OnInit, ErrorStateMatcher { diff --git a/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.component.ts index 2e609eb617..b66a9cea6e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.component.ts @@ -15,7 +15,7 @@ /// import { MatDialogRef } from '@angular/material/dialog'; -import { Inject, InjectionToken } from '@angular/core'; +import { Directive, Inject, InjectionToken } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { Router } from '@angular/router'; @@ -30,6 +30,7 @@ export interface CustomDialogData { [key: string]: any; } +@Directive() export class CustomDialogComponent extends PageComponent { [key: string]: any; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts index 75d0b24485..6082e3716a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts @@ -154,7 +154,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, private alarmsTitlePattern: string; private displayDetails = true; - private allowAcknowledgment = true; + public allowAcknowledgment = true; private allowClear = true; private defaultPageSize = 10; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts index 6f0e629c54..af11dfd607 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts @@ -186,11 +186,16 @@ export type TripAnimationSettings = { usePointAsAnchor: boolean; normalizationStep: number; showPolygon: boolean; + showLabel: boolean; + showTooltip: boolean; latKeyName: string; lngKeyName: string; rotationAngle: number; label: string; tooltipPattern: string; + tooltipColor: string; + tooltipOpacity: number; + tooltipFontColor: string; useTooltipFunction: boolean; useLabelFunction: boolean; pointAsAnchorFunction: GenericFunction; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts index 0403f2844c..1707b5495d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts @@ -102,11 +102,11 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni ctx: WidgetContext; private formResize$: ResizeObserver; - private settings: MultipleInputWidgetSettings; + public settings: MultipleInputWidgetSettings; private widgetConfig: WidgetConfig; private subscription: IWidgetSubscription; private datasources: Array; - private sources: Array = []; + public sources: Array = []; isVerticalAlignment: boolean; inputWidthSettings: string; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts index f9393a9e79..307efc2733 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts @@ -126,7 +126,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI private defaultPageSize = 10; private defaultSortOrder = '-0'; private hideEmptyLines = false; - private showTimestamp = true; + public showTimestamp = true; private dateFormatFilter: string; private searchAction: WidgetAction = { diff --git a/ui-ngx/src/app/shared/components/json-form/json-form.component.ts b/ui-ngx/src/app/shared/components/json-form/json-form.component.ts index ecd249af60..51e32c18f9 100644 --- a/ui-ngx/src/app/shared/components/json-form/json-form.component.ts +++ b/ui-ngx/src/app/shared/components/json-form/json-form.component.ts @@ -36,12 +36,14 @@ import { JsonFormProps } from './react/json-form.models'; import inspector from 'schema-inspector'; import * as tinycolor_ from 'tinycolor2'; import { DialogService } from '@app/core/services/dialog.service'; -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import ReactSchemaForm from './react/json-form-react'; +// import * as React from 'react'; +// import * as ReactDOM from 'react-dom'; +// import ReactSchemaForm from './react/json-form-react'; import JsonFormUtils from './react/json-form-utils'; import { JsonFormComponentData } from './json-form-component.models'; import { GroupInfo } from '@shared/models/widget.models'; +import { Observable } from 'rxjs/internal/Observable'; +import { forkJoin, from } from 'rxjs'; const tinycolor = tinycolor_; @@ -252,11 +254,35 @@ export class JsonFormComponent implements OnInit, ControlValueAccessor, Validato if (destroy) { this.destroyReactSchemaForm(); } - ReactDOM.render(React.createElement(ReactSchemaForm, this.formProps), this.reactRootElmRef.nativeElement); + + // import ReactSchemaForm from './react/json-form-react'; + const reactSchemaFormObservables: Observable[] = []; + reactSchemaFormObservables.push(from(import('react'))); + reactSchemaFormObservables.push(from(import('react-dom'))); + reactSchemaFormObservables.push(from(import('./react/json-form-react'))); + forkJoin(reactSchemaFormObservables).subscribe( + (modules) => { + const react = modules[0]; + const reactDom = modules[1]; + const jsonFormReact = modules[2].default; + reactDom.render(react.createElement(jsonFormReact, this.formProps), this.reactRootElmRef.nativeElement); + } + ); + /* import('./react/json-form-react').then( + (mod) => { + ReactDOM.render(React.createElement(mod.default, this.formProps), this.reactRootElmRef.nativeElement); + } + );*/ + // ReactDOM.render(React.createElement(ReactSchemaForm, this.formProps), this.reactRootElmRef.nativeElement); } private destroyReactSchemaForm() { - ReactDOM.unmountComponentAtNode(this.reactRootElmRef.nativeElement); + import('react-dom').then( + (reactDom) => { + reactDom.unmountComponentAtNode(this.reactRootElmRef.nativeElement); + } + ); + // ReactDOM.unmountComponentAtNode(this.reactRootElmRef.nativeElement); } private validateModel(): boolean { diff --git a/ui-ngx/src/app/shared/components/led-light.component.ts b/ui-ngx/src/app/shared/components/led-light.component.ts index aa2c373dfa..9ede198215 100644 --- a/ui-ngx/src/app/shared/components/led-light.component.ts +++ b/ui-ngx/src/app/shared/components/led-light.component.ts @@ -16,7 +16,7 @@ import { AfterViewInit, Component, ElementRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; -import Raphael, { RaphaelElement, RaphaelPaper, RaphaelSet } from 'raphael'; +import { RaphaelElement, RaphaelPaper, RaphaelSet } from 'raphael'; import * as tinycolor_ from 'tinycolor2'; const tinycolor = tinycolor_; @@ -90,10 +90,14 @@ export class LedLightComponent implements OnInit, AfterViewInit, OnChanges { if (this.paper) { this.paper.remove(); } - this.paper = Raphael($('#canvas_container', this.elementRef.nativeElement)[0], this.canvasSize, this.canvasSize); - const center = this.canvasSize / 2; - this.circleElement = this.paper.circle(center, center, this.radius); - this.draw(); + import('raphael').then( + (raphael) => { + this.paper = raphael.default($('#canvas_container', this.elementRef.nativeElement)[0], this.canvasSize, this.canvasSize); + const center = this.canvasSize / 2; + this.circleElement = this.paper.circle(center, center, this.radius); + this.draw(); + } + ); } private draw() {