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