Skip to content
This repository has been archived by the owner on Aug 25, 2020. It is now read-only.

Commit

Permalink
refactor: improve styles rendering mechanism, optimize compiler
Browse files Browse the repository at this point in the history
  • Loading branch information
navix committed Jul 30, 2017
1 parent effab99 commit 22219f2
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 163 deletions.
10 changes: 0 additions & 10 deletions package/src/compiler/compiler-unit.ts

This file was deleted.

171 changes: 58 additions & 113 deletions package/src/compiler/compiler.service.ts
@@ -1,73 +1,92 @@
import { DOCUMENT } from '@angular/common';
import { Inject, Injectable } from '@angular/core';
import { Inject, Injectable, NgZone } from '@angular/core';
import { ɵSharedStylesHost as SharedStylesHost } from '@angular/platform-browser';
import { processAutoPx } from '../helpers/process-auto-px';
import { autoPx } from '../meta/compiler';
import { StyleDef } from '../meta/def';
import { StylerHashService } from '../meta/hash';
import { Style } from '../meta/style';
import { stylerHash } from '../meta/tokens';
import { isString } from '../utils/is-string';
import { objectFilter } from '../utils/object-filter';
import { StylerCompilerUnit } from './compiler-unit';
import { compileBorder } from './props/border';
import { compileMargin } from './props/margin';
import { compilePadding } from './props/padding';

// @todo use Set instead []
@Injectable()
export class StylerCompilerService {
private readonly attrPrefix = 'sid-';

private debug = true;

private debugId = 0;

private hashes: string[] = [];

private rendered: {hash: string, css: string}[] = [];
private hashes = new Set<string>();

private units: StylerCompilerUnit[] = [];
private stylesBuffer = new Set<string>();

constructor(@Inject(DOCUMENT) private doc: any,
private sharedStylesHost: SharedStylesHost,
@Inject(stylerHash) private hash: StylerHashService) {
}

create(): StylerCompilerUnit {
const unit = new StylerCompilerUnit();
this.units.push(unit);
return unit;
@Inject(stylerHash) private hash: StylerHashService,
private zone: NgZone) {
// add css to head on zone stable
this.zone.onStable.subscribe(() => {
this.sharedStylesHost.addStyles([Array.from(this.stylesBuffer).join('')]);
this.stylesBuffer.clear();
});
}

destroyUnit(unit: StylerCompilerUnit) {
const index = this.units.indexOf(unit);
if (index !== -1) {
this.units.splice(index, 1);
renderElement(def: StyleDef): string {
// root
const compiled = [{
selector: '',
props: this.compileProps(objectFilter(def, ['$nest'])),
}];
// nested
if (def.$nest) {
for (const selector in def.$nest) {
const styles = def.$nest[selector];
if (styles) {
compiled.push({
selector: selector.replace(/&/g, ''),
props: this.compileProps(styles),
});
}
}
}
// gen hash
const hash = this.hash.hash(compiled.map(c => c.selector + c.props).join());
// check if added
if (!this.hashes.has(hash)) {
const attrSelector = `[${this.attrPrefix}${hash}]`;
const css = compiled.reduce((prev, curr) => {
return `${prev}${attrSelector}${curr.selector}{${curr.props}}`;
}, '');
this.addStyles(css);
console.log('add styles', css);
this.hashes.add(hash);
}
return `${this.attrPrefix}${hash}`;
}

render(unit?: StylerCompilerUnit, source?: string): void {
if (unit) {
// update passed unit
this.updateUnit(unit);
} else {
// update all units
this.updateAllUnits();
renderKeyframe(def: any): string {
let css = '';
for (const key in def) {
css += `${key}{${this.compileProps(def[key])}}`
}
// gather hashes
this.hashes = [];
this.units.forEach(unit => {
if (this.hashes.includes(unit.hash.value) === false) {
this.hashes.push(unit.hash.value);
}
});
// render
this.renderCss();
// debug
if (this.debug) {
this.log(`render(source:${source})`, {
units: this.units,
hashes: this.hashes,
});
const hash = `kf-${this.hash.hash(css)}`;
if (!this.hashes.has(hash)) {
// @todo impr performace by hash-caching
this.addStyles(`@keyframes ${hash}{${css}}`);
console.log('add keyframe styles', css);
this.hashes.add(hash);
}
return hash;
}

private addStyles(style: string) {
this.stylesBuffer.add(style);
}

// @todo it should be optimized
Expand Down Expand Up @@ -122,78 +141,4 @@ export class StylerCompilerService {
this.debugId++;
console.log(`[${this.debugId}] Styler >> `, ...params);
}

private renderCss(): void {
const styles = this.hashes.map(hash => {
const localCss = this.rendered.find(r => r.hash === hash);
if (localCss && localCss.css) {
return localCss.css;
} else {
if (this.debug) {
this.log('rendered', this.rendered);
}
throw new Error(`Styler: local css for hash "${hash}" not found!`);
}
});
this.sharedStylesHost.addStyles(styles);
}

private updateAllUnits(): void {
this.units.forEach(unit => this.updateUnit(unit));
if (this.debug) {
this.log('updateAllUnits', {
units: this.units,
hashes: this.hashes,
});
}
}

private updateUnit(unit: StylerCompilerUnit): void {
// root
const compiled = [{
selector: '',
props: this.compileProps(objectFilter(unit.style, ['$nest'])),
}];
// nested
if (unit.style.$nest) {
for (const selector in unit.style.$nest) {
const styles = unit.style.$nest[selector];
if (styles) {
compiled.push({
selector: selector.replace(/&/g, ''),
props: this.compileProps(styles),
});
}
}
}
// gen hash
const newHash = this.hash.hash(compiled.map(c => c.selector + c.props).join());
// render css or get from cache
const rendered = this.rendered.find(r => r.hash === newHash);
if (!rendered) {
const attrSelector = `[sid-${newHash}]`;
const hostAttrSelector = `[host-sid-${newHash}]`;
const attrValueSelector = `[sid="${newHash}"]`;
unit.css = compiled.reduce((prev, curr) => {
return `${prev}${attrSelector}${curr.selector},${attrValueSelector}${curr.selector}{${curr.props}}`;
}, '');
// save to cache
this.rendered.push({hash: newHash, css: unit.css});
} else {
unit.css = rendered.css;
}
// update hash in unit
unit.hash.next(newHash);
}

addKeyframes(def: any): string {
let css = '';
for (const key in def) {
css += `${key}{${this.compileProps(def[key])}}`
}
const selector = `kf-${this.hash.hash(css)}`;
// @todo impr performace by hash-caching
this.sharedStylesHost.addStyles([`@keyframes ${selector}{${css}}`]);
return selector;
}
}
24 changes: 8 additions & 16 deletions package/src/styler-component.ts
@@ -1,10 +1,10 @@
import { ElementRef, Inject, Injectable, OnDestroy, Optional, Renderer2, Self } from '@angular/core';
import { StylerCompilerUnit } from './compiler/compiler-unit';
import { StylerCompilerService } from './compiler/compiler.service';
import { ComponentStyle } from './meta/component';
import { componentStyle } from './meta/tokens';
import { StylerElement } from './styler-element';
import { StylerService } from './styler.service';
import { StyleDef } from './meta/def';

/**
* @todo optimize & add cache
Expand Down Expand Up @@ -43,29 +43,25 @@ export class StylerComponent implements OnDestroy {
}

ngOnDestroy() {
this.elements.forEach(element => {
element.destroy();
});
this.stylerService.unregisterComponent(this);
}

createElement(elementName: string): StylerElement {
if (!this.style[elementName]) {
throw new Error(`Styler: element with name "${elementName}" is not defined!`);
}
const compilerUnit = this.compiler.create();
// bind style to def function if needed
const def = typeof this.style[elementName] === 'function'
? this.style[elementName].bind(this.style)
: this.style[elementName];
// create element
const element = new StylerElement(this, def, compilerUnit, elementName);
const element = new StylerElement(this, def, elementName);
this.elements.push(element);
return element;
}

destroyUnit(unit: StylerCompilerUnit): void {
this.compiler.destroyUnit(unit);
renderElement(def: StyleDef): string {
return this.compiler.renderElement(def);
}

register(style: ComponentStyle): void {
Expand All @@ -83,13 +79,9 @@ export class StylerComponent implements OnDestroy {
});
}

render(unit: StylerCompilerUnit, source?: string): void {
this.compiler.render(unit, source);
}

update(withRender = true) {
update() {
this.elements.forEach(element => {
element.update(withRender);
element.update();
});
}

Expand All @@ -105,10 +97,10 @@ export class StylerComponent implements OnDestroy {
// @todo check prev hostSid
if (this.hostSid !== sid) {
// remove prev
this.renderer.removeAttribute(this.el.nativeElement, `sid-${this.hostSid}`);
this.renderer.removeAttribute(this.el.nativeElement, this.hostSid);
// add new
this.hostSid = sid;
this.renderer.setAttribute(this.el.nativeElement, `sid-${this.hostSid}`, '');
this.renderer.setAttribute(this.el.nativeElement, this.hostSid, '');
}
}
}
24 changes: 7 additions & 17 deletions package/src/styler-element.ts
@@ -1,19 +1,20 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import { StylerCompilerUnit } from './compiler/compiler-unit';
import { StyleDef, StyleReactiveDef } from './meta/def';
import { StateSetter } from './meta/state';
import { StylerComponent } from './styler-component';

@Injectable()
export class StylerElement {
private _sid$ = new BehaviorSubject<string>('');

private _state: StateSetter = {};

private stateSize = 0;

constructor(private component: StylerComponent,
private def: StyleDef | StyleReactiveDef,
private unit: StylerCompilerUnit,
private elementName: string) {
this.update();
}
Expand All @@ -23,7 +24,7 @@ export class StylerElement {
}

get sid$(): Observable<string> {
return this.unit.hash.asObservable();
return this._sid$.asObservable();
}

set state(setterRaw: StateSetter) {
Expand All @@ -42,22 +43,11 @@ export class StylerElement {
}
}

destroy(): void {
this.component.destroyUnit(this.unit);
}

render() {
this.component.render(this.unit, `element:${this.elementName}.render`);
}

update(withRender = true) {
update() {
// Util
this.stateSize = Object.keys(this._state).length;
// Update style
this.unit.style = this.compile();
if (withRender) {
this.render();
}
// Update sid
this._sid$.next(this.component.renderElement(this.compile()));
}

private compile(): StyleDef {
Expand Down
6 changes: 3 additions & 3 deletions package/src/styler.directive.ts
Expand Up @@ -62,7 +62,7 @@ export class StylerDirective implements OnChanges, OnInit, OnDestroy, AfterViewI
}

ngOnDestroy() {
this.element.destroy();
// @todo destroy element
}

ngOnInit() {
Expand Down Expand Up @@ -100,10 +100,10 @@ export class StylerDirective implements OnChanges, OnInit, OnDestroy, AfterViewI
// check if changed
if (this.sid !== sid) {
// remove prev
this.renderer.removeAttribute(this.el.nativeElement, `sid-${this.sid}`);
this.renderer.removeAttribute(this.el.nativeElement, this.sid);
// add new
this.sid = sid;
this.renderer.setAttribute(this.el.nativeElement, `sid-${this.sid}`, '');
this.renderer.setAttribute(this.el.nativeElement, this.sid, '');
}
}
}
5 changes: 2 additions & 3 deletions package/src/styler.service.ts
Expand Up @@ -20,12 +20,11 @@ export class StylerService {
updateComponents() {
// @todo render only once
this.components.forEach(component => {
component.update(false);
component.update();
});
this.compiler.render();
}

keyframes(def: any): string {
return this.compiler.addKeyframes(def);
return this.compiler.renderKeyframe(def);
}
}
1 change: 0 additions & 1 deletion package/src/styler.ts
Expand Up @@ -6,7 +6,6 @@ export * from './styler-component';
export * from './styler-def.service';
export * from './styler-element';
export * from './compiler/compiler.service';
export * from './compiler/compiler-unit';
export * from './compiler/default-hash.service';
export * from './meta/color';
export * from './meta/compiler';
Expand Down

0 comments on commit 22219f2

Please sign in to comment.