Improved diffs
This commit is contained in:
parent
a6390e5eaf
commit
1931d681f7
@ -1,3 +1,5 @@
|
||||
import { MutationQueue, CustomMutationQueue } from "./MutationQueue"
|
||||
|
||||
export class Diff {
|
||||
static persistentClasses = new Set<string>()
|
||||
static persistentAttributes = new Set<string>()
|
||||
@ -5,6 +7,7 @@ export class Diff {
|
||||
// Reuse container for diffs to avoid memory allocation
|
||||
static container: HTMLElement
|
||||
static rootContainer: HTMLElement
|
||||
static mutations: CustomMutationQueue = new CustomMutationQueue()
|
||||
|
||||
// innerHTML will diff the element with the given HTML string and apply DOM mutations.
|
||||
static innerHTML(aRoot: HTMLElement, html: string): Promise<void> {
|
||||
@ -15,10 +18,8 @@ export class Diff {
|
||||
Diff.container.innerHTML = html
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
window.requestAnimationFrame(() => {
|
||||
Diff.childNodes(aRoot, Diff.container)
|
||||
resolve()
|
||||
})
|
||||
Diff.childNodes(aRoot, Diff.container)
|
||||
this.mutations.wait(resolve)
|
||||
})
|
||||
}
|
||||
|
||||
@ -31,10 +32,8 @@ export class Diff {
|
||||
|
||||
Diff.rootContainer.innerHTML = html.replace("<!DOCTYPE html>", "")
|
||||
|
||||
window.requestAnimationFrame(() => {
|
||||
Diff.childNodes(aRoot.getElementsByTagName("body")[0], Diff.rootContainer.getElementsByTagName("body")[0])
|
||||
resolve()
|
||||
})
|
||||
Diff.childNodes(aRoot.getElementsByTagName("body")[0], Diff.rootContainer.getElementsByTagName("body")[0])
|
||||
this.mutations.wait(resolve)
|
||||
})
|
||||
}
|
||||
|
||||
@ -49,7 +48,7 @@ export class Diff {
|
||||
|
||||
// Remove nodes at the end of a that do not exist in b
|
||||
if(i >= bChild.length) {
|
||||
aRoot.removeChild(a)
|
||||
this.mutations.queue(() => aRoot.removeChild(a))
|
||||
continue
|
||||
}
|
||||
|
||||
@ -57,13 +56,13 @@ export class Diff {
|
||||
|
||||
// If a doesn't have that many nodes, simply append at the end of a
|
||||
if(i >= aChild.length) {
|
||||
aRoot.appendChild(b)
|
||||
this.mutations.queue(() => aRoot.appendChild(b))
|
||||
continue
|
||||
}
|
||||
|
||||
// If it's a completely different HTML tag or node type, replace it
|
||||
if(a.nodeName !== b.nodeName || a.nodeType !== b.nodeType) {
|
||||
aRoot.replaceChild(b, a)
|
||||
this.mutations.queue(() => aRoot.replaceChild(b, a))
|
||||
continue
|
||||
}
|
||||
|
||||
@ -71,7 +70,7 @@ export class Diff {
|
||||
// We don't need to check for b to be a text node as well because
|
||||
// we eliminated different node types in the previous condition.
|
||||
if(a.nodeType === Node.TEXT_NODE) {
|
||||
a.textContent = b.textContent
|
||||
this.mutations.queue(() => a.textContent = b.textContent)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -92,9 +91,11 @@ export class Diff {
|
||||
}
|
||||
}
|
||||
|
||||
for(let attr of removeAttributes) {
|
||||
elemA.removeAttributeNode(attr)
|
||||
}
|
||||
this.mutations.queue(() => {
|
||||
for(let attr of removeAttributes) {
|
||||
elemA.removeAttributeNode(attr)
|
||||
}
|
||||
})
|
||||
|
||||
for(let x = 0; x < elemB.attributes.length; x++) {
|
||||
let attrib = elemB.attributes[x]
|
||||
@ -119,25 +120,29 @@ export class Diff {
|
||||
}
|
||||
}
|
||||
|
||||
for(let className of removeClasses) {
|
||||
classesA.remove(className)
|
||||
}
|
||||
|
||||
for(let className of classesB) {
|
||||
if(!classesA.contains(className)) {
|
||||
classesA.add(className)
|
||||
this.mutations.queue(() => {
|
||||
for(let className of removeClasses) {
|
||||
classesA.remove(className)
|
||||
}
|
||||
}
|
||||
|
||||
for(let className of classesB) {
|
||||
if(!classesA.contains(className)) {
|
||||
classesA.add(className)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
elemA.setAttribute(attrib.name, attrib.value)
|
||||
this.mutations.queue(() => elemA.setAttribute(attrib.name, attrib.value))
|
||||
}
|
||||
|
||||
// Special case: Apply state of input elements
|
||||
if(elemA !== document.activeElement && elemA instanceof HTMLInputElement && elemB instanceof HTMLInputElement) {
|
||||
elemA.value = elemB.value
|
||||
this.mutations.queue(() => {
|
||||
(elemA as HTMLInputElement).value = (elemB as HTMLInputElement).value
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,3 +27,52 @@ export class MutationQueue {
|
||||
this.elements.length = 0
|
||||
}
|
||||
}
|
||||
|
||||
export class CustomMutationQueue {
|
||||
mutations: Array<() => void>
|
||||
onClearCallBack: () => void
|
||||
timeCapacity = 6.5
|
||||
|
||||
constructor() {
|
||||
this.mutations = []
|
||||
}
|
||||
|
||||
queue(mutation: () => void) {
|
||||
this.mutations.push(mutation)
|
||||
|
||||
if(this.mutations.length === 1) {
|
||||
window.requestAnimationFrame(() => this.mutateAll())
|
||||
}
|
||||
}
|
||||
|
||||
mutateAll() {
|
||||
let start = performance.now()
|
||||
|
||||
for(let i = 0; i < this.mutations.length; i++) {
|
||||
if(performance.now() - start > this.timeCapacity) {
|
||||
let end = performance.now()
|
||||
// console.log(i, "mutations in", performance.now() - start, "ms")
|
||||
this.mutations = this.mutations.slice(i)
|
||||
window.requestAnimationFrame(() => this.mutateAll())
|
||||
return
|
||||
}
|
||||
|
||||
this.mutations[i]()
|
||||
}
|
||||
|
||||
this.clear()
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.mutations.length = 0
|
||||
|
||||
if(this.onClearCallBack) {
|
||||
this.onClearCallBack()
|
||||
this.onClearCallBack = null
|
||||
}
|
||||
}
|
||||
|
||||
wait(callBack: () => void) {
|
||||
this.onClearCallBack = callBack
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user