diff --git a/build.gradle.kts b/build.gradle.kts index ae510cd..366bd0a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,11 +1,11 @@ plugins { - kotlin("multiplatform") version "1.5.20" + kotlin("multiplatform") version "1.5.21" `maven-publish` } group = "nl.astraeus" -version = "0.5.2" +version = "0.5.5" repositories { mavenCentral() @@ -16,7 +16,8 @@ kotlin { browser { testTask { useKarma { - useChromeHeadless() + useChromiumHeadless() + //useChromeHeadless() } } } diff --git a/komp.commonMain.iml b/komp.commonMain.iml index 095f3b3..72b6087 100644 --- a/komp.commonMain.iml +++ b/komp.commonMain.iml @@ -1,5 +1,5 @@ - + @@ -13,13 +13,13 @@ \ No newline at end of file diff --git a/komp.commonTest.iml b/komp.commonTest.iml index 4e4ed82..a1fa3f2 100644 --- a/komp.commonTest.iml +++ b/komp.commonTest.iml @@ -1,5 +1,5 @@ - + @@ -16,13 +16,13 @@ diff --git a/komp.iml b/komp.iml index 1751387..964f4ea 100644 --- a/komp.iml +++ b/komp.iml @@ -1,5 +1,5 @@ - + diff --git a/komp.ipr b/komp.ipr index 8bdd234..c6bc0cf 100644 --- a/komp.ipr +++ b/komp.ipr @@ -79,6 +79,24 @@ + + $PROJECT_DIR$/build/libs + + + + + + $PROJECT_DIR$/build/libs + + + + + + $PROJECT_DIR$/build/libs + + + + + + + - + - + - + + + - + - + - + - + - + - + - + diff --git a/komp.jsMain.iml b/komp.jsMain.iml index 99ab995..092474f 100644 --- a/komp.jsMain.iml +++ b/komp.jsMain.iml @@ -1,5 +1,5 @@ - + @@ -24,11 +24,11 @@ \ No newline at end of file diff --git a/komp.jsTest.iml b/komp.jsTest.iml index 568df0e..8c1368d 100644 --- a/komp.jsTest.iml +++ b/komp.jsTest.iml @@ -1,5 +1,5 @@ - + @@ -29,11 +29,11 @@ - + - - + + \ No newline at end of file diff --git a/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt b/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt index 8820f92..78cad1f 100644 --- a/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt +++ b/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt @@ -233,10 +233,20 @@ class HtmlBuilder( } override fun include(komponent: Komponent) { - komponent.create( - currentPosition.last().parent as Element, - currentPosition.last().childIndex - ) + if ( + komponent.element != null && + !komponent.memoizeChanged() + ) { + currentPosition.replace(komponent.element!!) + if (Komponent.logRenderEvent) { + console.log("Skipped include $komponent, memoize hasn't changed") + } + } else { + komponent.create( + currentPosition.last().parent as Element, + currentPosition.last().childIndex + ) + } currentPosition.nextElement() } diff --git a/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt b/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt index c5b5bb6..18c6cb5 100644 --- a/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt +++ b/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt @@ -37,6 +37,7 @@ enum class UnsafeMode { abstract class Komponent { val createIndex = getNextCreateIndex() private var dirty: Boolean = true + private var lastMemoizeHash: Int? = null var element: Node? = null val declaredStyles: MutableMap = HashMap() @@ -49,9 +50,12 @@ abstract class Komponent { builder.render() element = builder.root + lastMemoizeHash = generateMemoizeHash() onAfterUpdate() } + fun memoizeChanged() = lastMemoizeHash != null && lastMemoizeHash != generateMemoizeHash() + abstract fun HtmlBuilder.render() /** @@ -87,25 +91,36 @@ abstract class Komponent { refresh() } + /** + * If this function returns a value it will be stored and on the next render it will be compared. + * + * The render will only happen if the hash is not null and has changed + */ + open fun generateMemoizeHash(): Int? = null + internal fun refresh() { val currentElement = element - if (currentElement == null) { + check(currentElement != null) { error("element is null") - } else { - val parent = currentElement.parentElement as? HTMLElement ?: error("parent is null!?") - var childIndex = 0 - for (index in 0 until parent.children.length) { - if (parent.children[index] == currentElement) { - childIndex = index - } - } - val consumer = HtmlBuilder(parent, childIndex) - consumer.root = null - consumer.render() - element = consumer.root - dirty = false } + + val parent = currentElement.parentElement as? HTMLElement ?: error("parent is null!?") + var childIndex = 0 + for (index in 0 until parent.children.length) { + if (parent.children[index] == currentElement) { + childIndex = index + } + } + val consumer = HtmlBuilder(parent, childIndex) + consumer.root = null + consumer.render() + element = consumer.root + dirty = false + } + + override fun toString(): String { + return "${this::class.simpleName}" } companion object { @@ -159,8 +174,15 @@ abstract class Komponent { if (logRenderEvent) { console.log("Update dirty ${next.createIndex}") } - next.update() - next.onAfterUpdate() + val memoizeHash = next.generateMemoizeHash() + + if (memoizeHash == null || next.lastMemoizeHash != memoizeHash) { + next.update() + next.lastMemoizeHash = memoizeHash + next.onAfterUpdate() + } else if (logRenderEvent) { + console.log("Skipped render, memoizeHash is equal $next-[$memoizeHash]") + } } else { if (logRenderEvent) { console.log("Skip ${next.createIndex}") diff --git a/src/jsTest/kotlin/nl/astraeus/komp/TestUpdate.kt b/src/jsTest/kotlin/nl/astraeus/komp/TestUpdate.kt index efde8c5..0d9612d 100644 --- a/src/jsTest/kotlin/nl/astraeus/komp/TestUpdate.kt +++ b/src/jsTest/kotlin/nl/astraeus/komp/TestUpdate.kt @@ -48,7 +48,6 @@ class SimpleKomponent : Komponent() { } } - override fun HtmlBuilder.render() { div("div_class") { span { @@ -104,7 +103,21 @@ class SimpleKomponent : Komponent() { } +class IncludeKomponent( + var text: String = "My Text" +) : Komponent() { + + override fun generateMemoizeHash(): Int = text.hashCode() + + override fun HtmlBuilder.render() { + span { + +text + } + } +} + class ReplaceKomponent : Komponent() { + val includeKomponent = IncludeKomponent() var includeSpan = true override fun HtmlBuilder.render() { @@ -129,6 +142,8 @@ class ReplaceKomponent : Komponent() { } } } + + include(includeKomponent) } } } @@ -147,10 +162,20 @@ class TestUpdate { println("ReplaceKomponent: ${div.printTree()}") + rk.requestImmediateUpdate() + + println("ReplaceKomponent: ${div.printTree()}") + rk.includeSpan = false rk.requestImmediateUpdate() println("ReplaceKomponent: ${div.printTree()}") + + rk.includeSpan = true + rk.includeKomponent.text = "New Text" + rk.requestImmediateUpdate() + + println("ReplaceKomponent: ${div.printTree()}") } @Test