From 171b4c9f9fd6e1732e1a12eeb8c46e101cf8195a Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 25 Jun 2021 15:04:27 +0100 Subject: [PATCH] Aggressively optimise client library to prevent handlebars enrichment where possible --- .../client/src/components/Component.svelte | 33 +++++++++++++++-- .../client/src/components/Provider.svelte | 36 ++++++++++++------- packages/client/src/store/context.js | 16 ++++++++- packages/client/src/utils/componentProps.js | 10 +----- 4 files changed, 69 insertions(+), 26 deletions(-) diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index 0ee079a0bc..4440e83845 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -23,6 +23,11 @@ // props with old ones, depending on how long enrichment takes. let latestUpdateTime + // Keep track of stringified representations of context and instance + // to avoid enriching bindings as much as possible + let lastContextKey + let lastInstanceKey + // Get contexts const context = getContext("context") const insideScreenslot = !!getContext("screenslot") @@ -42,7 +47,9 @@ definition?.hasChildren && definition?.showEmptyState !== false && $builderStore.inBuilder - $: updateComponentProps(instance, $context) + $: rawProps = getRawProps(instance) + $: instanceKey = JSON.stringify(rawProps) + $: updateComponentProps(rawProps, instanceKey, $context) $: selected = $builderStore.inBuilder && $builderStore.selectedComponentId === instance._id @@ -59,6 +66,16 @@ name, }) + const getRawProps = instance => { + let validProps = {} + Object.entries(instance) + .filter(([name]) => !name.startsWith("_")) + .forEach(([key, value]) => { + validProps[key] = value + }) + return validProps + } + // Gets the component constructor for the specified component const getComponentConstructor = component => { const split = component?.split("/") @@ -76,13 +93,23 @@ } // Enriches any string component props using handlebars - const updateComponentProps = (instance, context) => { + const updateComponentProps = (rawProps, instanceKey, context) => { + const instanceSame = instanceKey === lastInstanceKey + const contextSame = context.key === lastContextKey + + if (instanceSame && contextSame) { + return + } else { + lastInstanceKey = instanceKey + lastContextKey = context.key + } + // Record the timestamp so we can reference it after enrichment latestUpdateTime = Date.now() const enrichmentTime = latestUpdateTime // Enrich props with context - const enrichedProps = enrichProps(instance, context) + const enrichedProps = enrichProps(rawProps, context) // Abandon this update if a newer update has started if (enrichmentTime !== latestUpdateTime) { diff --git a/packages/client/src/components/Provider.svelte b/packages/client/src/components/Provider.svelte index d2679957c0..1079779e9a 100644 --- a/packages/client/src/components/Provider.svelte +++ b/packages/client/src/components/Provider.svelte @@ -14,18 +14,32 @@ const newContext = createContextStore(context) setContext("context", newContext) - $: providerKey = key || $component.id + const providerKey = key || $component.id - // Add data context - $: newContext.actions.provideData(providerKey, data) + // Generate a permanent unique ID for this component and use it to register + // any datasource actions + const instanceId = generate() - // Instance ID is unique to each instance of a provider - let instanceId + // Keep previous state around so we can avoid updating unless necessary + let lastDataKey + let lastActionsKey - // Add actions context - $: { - if (instanceId) { - actions?.forEach(({ type, callback, metadata }) => { + $: provideData(data) + $: provideActions(actions, instanceId) + + const provideData = newData => { + const dataKey = JSON.stringify(newData) + if (dataKey !== lastDataKey) { + newContext.actions.provideData(providerKey, newData) + lastDataKey = dataKey + } + } + + const provideActions = newActions => { + const actionsKey = JSON.stringify(newActions) + if (actionsKey !== lastActionsKey) { + lastActionsKey = actionsKey + newActions?.forEach(({ type, callback, metadata }) => { newContext.actions.provideAction(providerKey, type, callback) // Register any "refresh datasource" actions with a singleton store @@ -43,10 +57,6 @@ } onMount(() => { - // Generate a permanent unique ID for this component and use it to register - // any datasource actions - instanceId = generate() - // Unregister all datasource instances when unmounting this provider return () => dataSourceStore.actions.unregisterInstance(instanceId) }) diff --git a/packages/client/src/store/context.js b/packages/client/src/store/context.js index e9d307d4f3..e372b837bd 100644 --- a/packages/client/src/store/context.js +++ b/packages/client/src/store/context.js @@ -4,7 +4,21 @@ export const createContextStore = oldContext => { const newContext = writable({}) const contexts = oldContext ? [oldContext, newContext] : [newContext] const totalContext = derived(contexts, $contexts => { - return $contexts.reduce((total, context) => ({ ...total, ...context }), {}) + // The key is the serialized representation of context + let key = "" + for (let i = 0; i < $contexts.length - 1; i++) { + key += $contexts[i].key + } + key += JSON.stringify($contexts[$contexts.length - 1]) + + // Reduce global state + const reducer = (total, context) => ({ ...total, ...context }) + const context = $contexts.reduce(reducer, {}) + + return { + ...context, + key, + } }) // Adds a data context layer to the tree diff --git a/packages/client/src/utils/componentProps.js b/packages/client/src/utils/componentProps.js index 14516fdb4c..65e86c28d6 100644 --- a/packages/client/src/utils/componentProps.js +++ b/packages/client/src/utils/componentProps.js @@ -22,14 +22,6 @@ export const propsAreSame = (a, b) => { * Data bindings are enriched, and button actions are enriched. */ export const enrichProps = (props, context) => { - // Exclude all private props that start with an underscore - let validProps = {} - Object.entries(props) - .filter(([name]) => !name.startsWith("_")) - .forEach(([key, value]) => { - validProps[key] = value - }) - // Create context of all bindings and data contexts // Duplicate the closest context as "data" which the builder requires const totalContext = { @@ -41,7 +33,7 @@ export const enrichProps = (props, context) => { } // Enrich all data bindings in top level props - let enrichedProps = enrichDataBindings(validProps, totalContext) + let enrichedProps = enrichDataBindings(props, totalContext) // Enrich click actions if they exist if (enrichedProps.onClick) {