From c4c6d189d9c6a06a48a23c1ba47b328aa60c102d Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Fri, 22 Nov 2024 18:21:01 +0700 Subject: [PATCH 1/2] refactor: try to cache computed styles at least within updates Here I added basic cache for computed styles which is busted every time any style or instance is changed. Though it computes each property only once between changes. So property label, style section dots and style control reuse the same computed piece. This can potentially fix performance and memory usages. Though requires testing. --- .../features/style-panel/shared/model.tsx | 21 ++++++++++++--- .../app/shared/style-object-model.test.tsx | 27 +++++++++++++++++++ apps/builder/app/shared/style-object-model.ts | 23 +++++++++++++++- 3 files changed, 66 insertions(+), 5 deletions(-) diff --git a/apps/builder/app/builder/features/style-panel/shared/model.tsx b/apps/builder/app/builder/features/style-panel/shared/model.tsx index 77a75e1f3a79..18163ff3d2f7 100644 --- a/apps/builder/app/builder/features/style-panel/shared/model.tsx +++ b/apps/builder/app/builder/features/style-panel/shared/model.tsx @@ -291,11 +291,26 @@ export const $availableColorVariables = computed( availableVariables.filter((value) => value.fallback?.type !== "unit") ); +// completely bust the cache when model is changed +let prevModel: undefined | StyleObjectModel; +// store cached computed declarations by following key +// instanceSelector + styleSourceId + state + property +let cache = new Map(); + +const validateCache = (model: StyleObjectModel) => { + if (model !== prevModel) { + prevModel = model; + cache.clear(); + } +}; + export const createComputedStyleDeclStore = (property: StyleProperty) => { return computed( [$model, $instanceAndRootSelector, $selectedOrLastStyleSourceSelector], (model, instanceSelector, styleSourceSelector) => { + validateCache(model); return getComputedStyleDecl({ + cache, model, instanceSelector, styleSourceId: styleSourceSelector?.styleSourceId, @@ -306,10 +321,6 @@ export const createComputedStyleDeclStore = (property: StyleProperty) => { ); }; -export const useStyleObjectModel = () => { - return useStore($model); -}; - export const useComputedStyleDecl = (property: StyleProperty) => { const $store = useMemo( () => createComputedStyleDeclStore(property), @@ -324,7 +335,9 @@ export const useParentComputedStyleDecl = (property: StyleProperty) => { computed( [$model, $instanceAndRootSelector], (model, instanceSelector) => { + validateCache(model); return getComputedStyleDecl({ + cache, model, instanceSelector: instanceSelector?.slice(1), property, diff --git a/apps/builder/app/shared/style-object-model.test.tsx b/apps/builder/app/shared/style-object-model.test.tsx index 756460cf75b2..3a84bfb06428 100644 --- a/apps/builder/app/shared/style-object-model.test.tsx +++ b/apps/builder/app/shared/style-object-model.test.tsx @@ -1576,3 +1576,30 @@ describe("style value source", () => { }); }); }); + +describe("cache", () => { + test("reuse computed values", () => { + const model = createModel({ + css: ` + bodyLocal { + color: red; + } + `, + jsx: <$.Body ws:id="body" ws:tag="body" class="bodyLocal">, + }); + const cache = new Map(); + const first = getComputedStyleDecl({ + cache, + model, + instanceSelector: ["body"], + property: "color", + }); + const second = getComputedStyleDecl({ + cache, + model, + instanceSelector: ["body"], + property: "color", + }); + expect(first).toBe(second); + }); +}); diff --git a/apps/builder/app/shared/style-object-model.ts b/apps/builder/app/shared/style-object-model.ts index 73533b44429a..94b1867670cf 100644 --- a/apps/builder/app/shared/style-object-model.ts +++ b/apps/builder/app/shared/style-object-model.ts @@ -285,6 +285,7 @@ export type ComputedStyleDecl = { * https://drafts.csswg.org/css-cascade-5/#value-stages */ export const getComputedStyleDecl = ({ + cache = new Map(), model, instanceSelector = [], styleSourceId, @@ -292,6 +293,7 @@ export const getComputedStyleDecl = ({ property, customPropertiesGraph = new Map(), }: { + cache?: Map; model: StyleObjectModel; instanceSelector?: InstanceSelector; styleSourceId?: StyleDecl["styleSourceId"]; @@ -302,6 +304,17 @@ export const getComputedStyleDecl = ({ */ customPropertiesGraph?: Map>; }): ComputedStyleDecl => { + const cacheKey = JSON.stringify({ + instanceSelector, + styleSourceId, + state, + property, + }); + const cachedValue = cache.get(cacheKey); + if (cachedValue) { + return cachedValue; + } + const isCustomProperty = property.startsWith("--"); const propertyData = isCustomProperty ? customPropertyData @@ -431,5 +444,13 @@ export const getComputedStyleDecl = ({ // fallback to inherited value cascadedValue ??= computedValue; - return { property, source, cascadedValue, computedValue, usedValue }; + const computedStyleDecl: ComputedStyleDecl = { + property, + source, + cascadedValue, + computedValue, + usedValue, + }; + cache.set(cacheKey, computedStyleDecl); + return computedStyleDecl; }; From 010bcb04b74ec7644a937a1202d1369d143cbbee Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Fri, 22 Nov 2024 18:26:58 +0700 Subject: [PATCH 2/2] Fix lint --- apps/builder/app/builder/features/style-panel/shared/model.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/builder/app/builder/features/style-panel/shared/model.tsx b/apps/builder/app/builder/features/style-panel/shared/model.tsx index 18163ff3d2f7..aa8304e515ee 100644 --- a/apps/builder/app/builder/features/style-panel/shared/model.tsx +++ b/apps/builder/app/builder/features/style-panel/shared/model.tsx @@ -295,7 +295,7 @@ export const $availableColorVariables = computed( let prevModel: undefined | StyleObjectModel; // store cached computed declarations by following key // instanceSelector + styleSourceId + state + property -let cache = new Map(); +const cache = new Map(); const validateCache = (model: StyleObjectModel) => { if (model !== prevModel) {