Performance optimization on a large webapp
EXPERIENCE OF DEVELOPING WEWEB EDITOR
WEWEB
PHOTO GATHER
WEWEB
DISCLAIMER
TOOLS
app.config.performance = true
Configure Vue
Browser devtools
Vuejs Devtool
TOOLS
export default {
renderTracked(event) {
debugger
},
renderTriggered(event) {
debugger
}
}
const property = computed(() => getValue(key), {
onTrack(e) {
debugger
},
onTrigger(e) {
debugger
}
})
watch(source, callback, {
onTrack(e) {
debugger
},
onTrigger(e) {
debugger
}
})
Demo time
https://performance.purple-fox.fr
https://github.com/AurelieV/opti-perf
It's not always VUEJS
REFLOW PROBLEM DEMO
calcMenuPosition() {
if (!this.isSelected) return;
const { height, width } = this.$refs.el.getBoundingClientRect();
this.positionStyle = {
height: `${height}px`,
width: `${width}px`,
};
requestAnimationFrame(() => this.calcMenuPosition());
},
REFLOW PROBLEM DEMO
watch: {
isSelected(value) {
if (value) {
if (!this.observer) {
this.observer = new ResizeObserver((entries) => {
for (const entry of entries) {
const rect = entry.contentRect;
this.positionStyle = {
height: `${rect.height}px`,
width: `${rect.width}px`,
};
}
});
}
this.observer.observe(this.$refs.el);
} else {
if (this.observer) {
this.observer.disconnect();
}
}
},
},
OTHER COMMON PROBLEMS
CSS SELECTOR
CSS (height: 100%)
Scroll position
TRACK reactivity
export const collections = computed(() => {
const collections = wwLib.$store.getters['data/getCollections'];
return Object.keys(collections).reduce((obj, key) => {
const item = collections[key]
obj[item.name] = item;
obj[key] = item;
return obj;
}, {})
})
export const collections = computed(() => {
const collections = wwLib.$store.getters['data/getCollections'];
return Object.keys(collections).reduce((obj, key) => {
const item = {
id: collections[key].id,
data: collections[key].data,
isFetched: collections[key].isFetched,
isFetching: collections[key].isFetching
}
obj[item.name] = item;
obj[key] = item;
return obj;
}, {})
})
DEMO
Code 1: Only track the keys of collections object
Code 2: Track all properties of each item
COMPUTED
const isSelected = computed(() => props.id === store.selectedId)
COMPUTED
const isSelected = computed(() => props.id === store.selectedId)
SOLUTIONS
CHILD COMPONENT
v-MEMO
"LAZY" REF
V-MEMO
<ul class="grid grid-cols-4 gap-3">
<li
class="px-2 transition transform"
v-for="i of list"
:key="i"
v-memo="[selectedId === i]"
:class="{ 'rotate-3': selectedId === i }"
@click="selectedId = i"
></li>
</ul>
"lazy" ref
import { ref, watch } from "vue";
import { store } from "./store";
export function useLazyRef(id) {
const isSelected = ref(false);
watch(
() => store.selectedId,
(selectedId) => {
const newValue = selectedId === id;
if (isSelected !== newValue) {
isSelected.value = newValue;
}
},
{ immediate: true }
);
return isSelected;
}
effect scope
Effect?
Effect scope
shared effect
function createSharedRef(createRef) {
let value = null;
let scope = null;
let subscribers = 0;
function dispose() {
subscribers--;
if (scope && subscribers <= 0) {
scope.stop();
value = null;
scope = null;
}
}
return (...args) => {
subscribers++;
if (!value) {
scope = effectScope(true);
value = scope.run(() => createRef(...args));
}
onScopeDispose(dispose);
return value;
};
}
export const useSharedDataProperty = createSharedRef(useDataProperty);
"activable" effect
function useDistance(id, isActive) {
let scope;
const distance = ref(0);
function dispose() {
if (scope) {
scope.stop();
scope = null;
}
}
watch(isActive, isActive => {
if (!isActive) {
dispose();
} else {
scope = effectScope();
scope.run(() => {
watch(
params,
params => distance.value = computeDistance(id, params),
{ immediate: true }
)
})
})
onScopeDispose(dispose);
return distance;
}
stay IN TOUCH
@purple_orwel
Enjoy the pictures? Contact Emmanuelle here
VueJS Asmterdam: Optimize large VueJS Webapp
By Aurélie Violette
VueJS Asmterdam: Optimize large VueJS Webapp
- 382