Aggressively optimise client library to prevent handlebars enrichment where possible

This commit is contained in:
Andrew Kingston 2021-06-25 15:04:27 +01:00
parent 83a337e20e
commit be2ec9b427
4 changed files with 69 additions and 26 deletions

View File

@ -23,6 +23,11 @@
// props with old ones, depending on how long enrichment takes. // props with old ones, depending on how long enrichment takes.
let latestUpdateTime 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 // Get contexts
const context = getContext("context") const context = getContext("context")
const insideScreenslot = !!getContext("screenslot") const insideScreenslot = !!getContext("screenslot")
@ -42,7 +47,9 @@
definition?.hasChildren && definition?.hasChildren &&
definition?.showEmptyState !== false && definition?.showEmptyState !== false &&
$builderStore.inBuilder $builderStore.inBuilder
$: updateComponentProps(instance, $context) $: rawProps = getRawProps(instance)
$: instanceKey = JSON.stringify(rawProps)
$: updateComponentProps(rawProps, instanceKey, $context)
$: selected = $: selected =
$builderStore.inBuilder && $builderStore.inBuilder &&
$builderStore.selectedComponentId === instance._id $builderStore.selectedComponentId === instance._id
@ -59,6 +66,16 @@
name, 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 // Gets the component constructor for the specified component
const getComponentConstructor = component => { const getComponentConstructor = component => {
const split = component?.split("/") const split = component?.split("/")
@ -76,13 +93,23 @@
} }
// Enriches any string component props using handlebars // 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 // Record the timestamp so we can reference it after enrichment
latestUpdateTime = Date.now() latestUpdateTime = Date.now()
const enrichmentTime = latestUpdateTime const enrichmentTime = latestUpdateTime
// Enrich props with context // Enrich props with context
const enrichedProps = enrichProps(instance, context) const enrichedProps = enrichProps(rawProps, context)
// Abandon this update if a newer update has started // Abandon this update if a newer update has started
if (enrichmentTime !== latestUpdateTime) { if (enrichmentTime !== latestUpdateTime) {

View File

@ -14,18 +14,32 @@
const newContext = createContextStore(context) const newContext = createContextStore(context)
setContext("context", newContext) setContext("context", newContext)
$: providerKey = key || $component.id const providerKey = key || $component.id
// Add data context // Generate a permanent unique ID for this component and use it to register
$: newContext.actions.provideData(providerKey, data) // any datasource actions
const instanceId = generate()
// Instance ID is unique to each instance of a provider // Keep previous state around so we can avoid updating unless necessary
let instanceId let lastDataKey
let lastActionsKey
// Add actions context $: provideData(data)
$: { $: provideActions(actions, instanceId)
if (instanceId) {
actions?.forEach(({ type, callback, metadata }) => { 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) newContext.actions.provideAction(providerKey, type, callback)
// Register any "refresh datasource" actions with a singleton store // Register any "refresh datasource" actions with a singleton store
@ -43,10 +57,6 @@
} }
onMount(() => { 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 // Unregister all datasource instances when unmounting this provider
return () => dataSourceStore.actions.unregisterInstance(instanceId) return () => dataSourceStore.actions.unregisterInstance(instanceId)
}) })

View File

@ -4,7 +4,21 @@ export const createContextStore = oldContext => {
const newContext = writable({}) const newContext = writable({})
const contexts = oldContext ? [oldContext, newContext] : [newContext] const contexts = oldContext ? [oldContext, newContext] : [newContext]
const totalContext = derived(contexts, $contexts => { 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 // Adds a data context layer to the tree

View File

@ -22,14 +22,6 @@ export const propsAreSame = (a, b) => {
* Data bindings are enriched, and button actions are enriched. * Data bindings are enriched, and button actions are enriched.
*/ */
export const enrichProps = (props, context) => { 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 // Create context of all bindings and data contexts
// Duplicate the closest context as "data" which the builder requires // Duplicate the closest context as "data" which the builder requires
const totalContext = { const totalContext = {
@ -41,7 +33,7 @@ export const enrichProps = (props, context) => {
} }
// Enrich all data bindings in top level props // Enrich all data bindings in top level props
let enrichedProps = enrichDataBindings(validProps, totalContext) let enrichedProps = enrichDataBindings(props, totalContext)
// Enrich click actions if they exist // Enrich click actions if they exist
if (enrichedProps.onClick) { if (enrichedProps.onClick) {