Browse Source

Performance improvements (#6621)

* add cache for css composer

* make animation for cssRuleView

* Refactor component class

* improve helper functions

* add cache for selectors

* make property view render in virtual dom first

* Refactor html parser

* Revert component.createId change

* Add CssRule.getAtRule

* Revert cssRuleView change

* Add css rule caching

* Fix parse style tests

* Revert component changes

* Revert model data resolver changes

* revert html parser changes

* revert property view changes

* revert cach dom changes

* revert dom changes

* fix test

* Move cache to ItemModule

* Refactor and cleanup

* Fix type

* Adjust selectors get

* Cleanup

* Cleanup selectors

---------

Co-authored-by: Artur Arseniev <artur.catch@hotmail.it>
release-v0.22.14-rc.0
mohamed yahia 3 months ago
committed by GitHub
parent
commit
c9667e09b4
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 46
      packages/core/src/abstract/Module.ts
  2. 134
      packages/core/src/css_composer/index.ts
  3. 11
      packages/core/src/css_composer/model/CssRule.ts
  4. 4
      packages/core/src/dom_components/model/Component.ts
  5. 17
      packages/core/src/dom_components/model/Components.ts
  6. 1
      packages/core/src/editor/model/Editor.ts
  7. 22
      packages/core/src/selector_manager/index.ts

46
packages/core/src/abstract/Module.ts

@ -130,6 +130,7 @@ export abstract class ItemManagerModule<
all: TCollection; all: TCollection;
view?: View; view?: View;
events!: Record<string, string>; events!: Record<string, string>;
protected _itemCache = new Map<string, Model>();
constructor( constructor(
em: EditorModel, em: EditorModel,
@ -207,6 +208,51 @@ export abstract class ItemManagerModule<
}, {} as any); }, {} as any);
} }
protected _makeCacheKey(m: Model) {
return '';
}
protected _cacheItem(item: Model) {
const key = this._makeCacheKey(item);
key && this._itemCache.set(key, item);
}
protected _uncacheItem(item: Model) {
const key = this._makeCacheKey(item);
key && this._itemCache.delete(key);
}
protected _clearItemCache() {
this._itemCache.clear();
}
protected _onItemsResetCache(collection: Collection) {
this._clearItemCache();
collection.each((item: Model) => this._cacheItem(item));
}
protected _onItemKeyChange(item: Model) {
let oldKey: string | undefined;
for (const [key, cachedItem] of (this._itemCache as any).entries()) {
if (cachedItem === item) {
oldKey = key;
break;
}
}
if (oldKey) {
this._itemCache.delete(oldKey);
}
this._cacheItem(item);
}
protected _setupCacheListeners() {
this.em.listenTo(this.all, 'add', this._cacheItem.bind(this));
this.em.listenTo(this.all, 'remove', this._uncacheItem.bind(this));
this.em.listenTo(this.all, 'reset', this._onItemsResetCache.bind(this));
}
__initListen(opts: any = {}) { __initListen(opts: any = {}) {
const { all, em, events } = this; const { all, em, events } = this;
all && all &&

134
packages/core/src/css_composer/index.ts

@ -87,7 +87,7 @@ export default class CssComposer extends ItemManagerModule<CssComposerConfig & {
Selectors = Selectors; Selectors = Selectors;
storageKey = 'styles'; storageKey = 'styles';
protected _itemCache = new Map<string, CssRule>();
/** /**
* Initializes module. Automatically called with a new instance of the editor * Initializes module. Automatically called with a new instance of the editor
* @param {Object} config Configurations * @param {Object} config Configurations
@ -104,6 +104,34 @@ export default class CssComposer extends ItemManagerModule<CssComposerConfig & {
config.rules = this.em.config.style || config.rules || ''; config.rules = this.em.config.style || config.rules || '';
this.rules = new CssRules([], config); this.rules = new CssRules([], config);
this._setupCacheListeners();
}
protected override _setupCacheListeners() {
super._setupCacheListeners();
this.em.listenTo(this.rules, 'change:selectors change:state change:mediaText', this._onItemKeyChange.bind(this));
}
protected _makeCacheKey(rule: CssRule) {
const atRuleKey = rule.getAtRule();
const selectorsKey = rule.selectorsToString();
return `${atRuleKey}__${selectorsKey}`;
}
_makeCacheKeyFromProps(ruleProps: CssRuleProperties) {
const { atRuleType = '', mediaText = '', state = '', selectorsAdd = '', selectors = [] } = ruleProps;
const selectorsStr = selectors.map((selector) => (isString(selector) ? selector : selector.toString())).join('');
const selectorsRes = [];
selectorsStr && selectorsRes.push(`${selectorsStr}${state ? `:${state}` : ''}`);
selectorsAdd && selectorsRes.push(selectorsAdd);
const selectorsKey = selectorsRes.join(', ');
const typeStr = atRuleType ? `@${atRuleType}` : mediaText ? '@media' : '';
const atRuleKey = typeStr + (mediaText && typeStr ? ` ${mediaText}` : '');
return `${atRuleKey}__${selectorsKey}`;
} }
/** /**
@ -112,6 +140,7 @@ export default class CssComposer extends ItemManagerModule<CssComposerConfig & {
*/ */
onLoad() { onLoad() {
this.rules.add(this.config.rules, { silent: true }); this.rules.add(this.config.rules, { silent: true });
this._onItemsResetCache(this.rules as any);
} }
/** /**
@ -134,6 +163,28 @@ export default class CssComposer extends ItemManagerModule<CssComposerConfig & {
}); });
} }
/**
* Find a rule in the collection by its properties.
* @private
*/
_findRule(
selectors: any,
state?: string,
width?: string,
ruleProps?: Omit<CssRuleProperties, 'selectors'>,
): CssRule | null {
let slc = selectors;
if (isString(selectors)) {
const sm = this.em.Selectors;
const singleSel = selectors.split(',')[0].trim();
const node = this.em.Parser.parserCss.checkNode({ selectors: singleSel } as any)[0];
slc = sm.get(node.selectors as string[]);
}
const rule = this.rules.find((r) => r.compare(slc, state, width, ruleProps)) || null;
return rule;
}
/** /**
* Add new rule to the collection, if not yet exists with the same selectors * Add new rule to the collection, if not yet exists with the same selectors
* @param {Array<Selector>} selectors Array of selectors * @param {Array<Selector>} selectors Array of selectors
@ -157,27 +208,38 @@ export default class CssComposer extends ItemManagerModule<CssComposerConfig & {
const s = state || ''; const s = state || '';
const w = width || ''; const w = width || '';
const opt = { ...opts } as CssRuleProperties; const opt = { ...opts } as CssRuleProperties;
let rule = this.get(selectors, s, w, opt); const key = this._makeCacheKeyFromProps({
state: s,
mediaText: w,
...opt,
selectors: Array.isArray(selectors) ? selectors : [selectors],
});
const cached = this._itemCache.get(key);
if (cached && cached.config && !cached.config.singleAtRule) {
return cached;
}
let rule = this._findRule(selectors, s, w, opt);
// do not create rules that were found before
// unless this is a single at-rule, for which multiple declarations
// make sense (e.g. multiple `@font-type`s)
if (rule && rule.config && !rule.config.singleAtRule) { if (rule && rule.config && !rule.config.singleAtRule) {
return rule; this._cacheItem(rule);
} else {
opt.state = s;
opt.mediaText = w;
opt.selectors = [];
// #4727: Prevent updating atRuleType if already defined
if (w && !opt.atRuleType) {
opt.atRuleType = 'media';
}
rule = new CssRule(opt, this.config);
// @ts-ignore
rule.get('selectors').add(selectors, addOpts);
this.rules.add(rule, addOpts);
return rule; return rule;
} }
opt.state = s;
opt.mediaText = w;
opt.selectors = [];
if (w && !opt.atRuleType) opt.atRuleType = 'media';
rule = new CssRule(opt, this.config);
// @ts-ignore
rule.get('selectors').add(selectors, addOpts);
this.rules.add(rule, addOpts);
this._cacheItem(rule);
return rule;
} }
/** /**
@ -199,20 +261,24 @@ export default class CssComposer extends ItemManagerModule<CssComposerConfig & {
* color: '#000', * color: '#000',
* }); * });
* */ * */
get( get(selectors: any, state?: string, width?: string, ruleProps?: Omit<CssRuleProperties, 'selectors'>) {
selectors: any, const key = this._makeCacheKeyFromProps({
state?: string, ...ruleProps,
width?: string, selectors: Array.isArray(selectors) ? selectors : [selectors],
ruleProps?: Omit<CssRuleProperties, 'selectors'>, state,
): CssRule | undefined { width,
let slc = selectors; mediaText: width,
if (isString(selectors)) { });
const sm = this.em.Selectors; const cached = this._itemCache.get(key);
const singleSel = selectors.split(',')[0].trim(); if (cached) return cached;
const node = this.em.Parser.parserCss.checkNode({ selectors: singleSel } as any)[0];
slc = sm.get(node.selectors as string[]); const rule = this._findRule(selectors, state, width, ruleProps);
if (rule) {
this._cacheItem(rule);
} }
return this.rules.find((rule) => rule.compare(slc, state, width, ruleProps)) || null;
return rule;
} }
getAll() { getAll() {
@ -485,8 +551,9 @@ export default class CssComposer extends ItemManagerModule<CssComposerConfig & {
*/ */
remove(rule: string | CssRule, opts?: any) { remove(rule: string | CssRule, opts?: any) {
const toRemove = isString(rule) ? this.getRules(rule) : rule; const toRemove = isString(rule) ? this.getRules(rule) : rule;
const result = this.getAll().remove(toRemove, opts); const arr = Array.isArray(toRemove) ? toRemove : [toRemove];
return isArray(result) ? result : [result]; const result = this.getAll().remove(arr, opts);
return Array.isArray(result) ? result : [result];
} }
/** /**
@ -494,6 +561,7 @@ export default class CssComposer extends ItemManagerModule<CssComposerConfig & {
* @return {this} * @return {this}
*/ */
clear(opts = {}) { clear(opts = {}) {
this._clearItemCache();
this.getAll().reset([], opts); this.getAll().reset([], opts);
return this; return this;
} }

11
packages/core/src/css_composer/model/CssRule.ts

@ -125,8 +125,8 @@ export default class CssRule extends StyleableModel<CssRuleProperties> {
this.opt = opt; this.opt = opt;
this.em = opt.em; this.em = opt.em;
this.ensureSelectors(null, null, {}); this.ensureSelectors(null, null, {});
this.on('change', this.__onChange);
this.setStyle(this.get('style'), { skipWatcherUpdates: true }); this.setStyle(this.get('style'), { skipWatcherUpdates: true });
this.on('change', this.__onChange);
} }
__onChange(m: CssRule, opts: any) { __onChange(m: CssRule, opts: any) {
@ -175,9 +175,12 @@ export default class CssRule extends StyleableModel<CssRuleProperties> {
* cssRule.getAtRule(); // "@media (min-width: 500px)" * cssRule.getAtRule(); // "@media (min-width: 500px)"
*/ */
getAtRule() { getAtRule() {
const type = this.get('atRuleType'); return CssRule.getAtRuleFromProps(this.attributes);
const condition = this.get('mediaText'); }
// Avoid breaks with the last condition
static getAtRuleFromProps(cssRuleProps: Partial<CssRuleProperties>) {
const type = cssRuleProps.atRuleType;
const condition = cssRuleProps.mediaText;
const typeStr = type ? `@${type}` : condition ? '@media' : ''; const typeStr = type ? `@${type}` : condition ? '@media' : '';
return typeStr + (condition && typeStr ? ` ${condition}` : ''); return typeStr + (condition && typeStr ? ` ${condition}` : '');

4
packages/core/src/dom_components/model/Component.ts

@ -272,7 +272,7 @@ export default class Component extends StyleableModel<ComponentProperties> {
views!: ComponentView[]; views!: ComponentView[];
view?: ComponentView; view?: ComponentView;
viewLayer?: ItemView; viewLayer?: ItemView;
rule?: CssRule; rule?: CssRule | null;
prevColl?: Components; prevColl?: Components;
__hasUm?: boolean; __hasUm?: boolean;
__symbReady?: boolean; __symbReady?: boolean;
@ -1123,7 +1123,7 @@ export default class Component extends StyleableModel<ComponentProperties> {
return coll as any; return coll as any;
} else { } else {
coll.reset(undefined, opts); coll.reset(undefined, opts);
return components ? this.append(components, opts) : ([] as any); return (components ? this.append(components, opts) : []) as any;
} }
} }

17
packages/core/src/dom_components/model/Components.ts

@ -418,15 +418,18 @@ Component> {
onAdd(model: Component, c?: any, opts: { temporary?: boolean } = {}) { onAdd(model: Component, c?: any, opts: { temporary?: boolean } = {}) {
const { domc, em } = this; const { domc, em } = this;
const style = model.getStyle(); const avoidInline = em.config.avoidInlineStyle;
const avoidInline = em && em.getConfig().avoidInlineStyle;
domc && domc.Component.ensureInList(model); domc && domc.Component.ensureInList(model);
if (!isEmpty(style) && !avoidInline && em && em.getConfig().forceClass && !opts.temporary) { if (!avoidInline && em.config.forceClass && !opts.temporary) {
const name = model.cid; const style = model.getStyle();
em.Css.setClassRule(name, style);
model.setStyle({}); if (!isEmpty(style)) {
model.addClass(name); const name = model.cid;
em.Css.setClassRule(name, style);
model.setStyle({});
model.addClass(name);
}
} }
model.__postAdd({ recursive: true }); model.__postAdd({ recursive: true });

1
packages/core/src/editor/model/Editor.ts

@ -76,6 +76,7 @@ const storableDeps: (new (em: EditorModel) => IModule & IStorableModule)[] = [
CssComposer, CssComposer,
PageManager, PageManager,
ComponentManager, ComponentManager,
SelectorManager,
]; ];
Extender({ $ }); Extender({ $ });

22
packages/core/src/selector_manager/index.ts

@ -98,7 +98,6 @@ export default class SelectorManager extends ItemManagerModule<SelectorManagerCo
storageKey = ''; storageKey = '';
__update: Debounced; __update: Debounced;
__ctn?: HTMLElement; __ctn?: HTMLElement;
/** /**
* Get configuration object * Get configuration object
* @name getConfig * @name getConfig
@ -113,7 +112,6 @@ export default class SelectorManager extends ItemManagerModule<SelectorManagerCo
const ppfx = config.pStylePrefix; const ppfx = config.pStylePrefix;
if (ppfx) config.stylePrefix = ppfx + config.stylePrefix; if (ppfx) config.stylePrefix = ppfx + config.stylePrefix;
// Global selectors container
this.all = new Selectors(config.selectors); this.all = new Selectors(config.selectors);
this.selected = new Selectors([], { em, config }); this.selected = new Selectors([], { em, config });
this.states = new Collection<State>( this.states = new Collection<State>(
@ -153,14 +151,6 @@ export default class SelectorManager extends ItemManagerModule<SelectorManagerCo
}; };
} }
// postLoad() {
// this.__postLoad();
// const { em, model } = this;
// const um = em.get('UndoManager');
// um && um.add(model);
// um && um.add(this.pages);
// },
postRender() { postRender() {
this.__appendTo(); this.__appendTo();
this.__trgCustom(); this.__trgCustom();
@ -200,10 +190,8 @@ export default class SelectorManager extends ItemManagerModule<SelectorManagerCo
props.name = this.escapeName(props.label); props.name = this.escapeName(props.label);
} }
const cname = props.name; const { all, em, config } = this;
const config = this.getConfig(); const selector = all.get(props);
const { all, em } = this;
const selector = cname ? (this.get(cname, props.type) as Selector) : all.where(props)[0];
if (!selector) { if (!selector) {
const selModel = props instanceof Selector ? props : new Selector(props, { ...cOpts, config, em }); const selModel = props instanceof Selector ? props : new Selector(props, { ...cOpts, config, em });
@ -221,7 +209,7 @@ export default class SelectorManager extends ItemManagerModule<SelectorManagerCo
name = name.substr(1); name = name.substr(1);
} }
return this.all.where({ name, type })[0]; return this.all.get({ name, type } as any);
} }
/** /**
@ -522,6 +510,10 @@ export default class SelectorManager extends ItemManagerModule<SelectorManagerCo
this.selectorTags = undefined; this.selectorTags = undefined;
} }
// Need for the IStorableModule to run the clenup
load() {}
store() {}
/** /**
* Get common selectors from the current selection. * Get common selectors from the current selection.
* @return {Array<Selector>} * @return {Array<Selector>}

Loading…
Cancel
Save