Dim diff option

This commit is contained in:
2020-05-04 19:14:04 +02:00
parent 2b851e7887
commit 7677cbcc7c
11 changed files with 347 additions and 178 deletions

View File

@@ -1,82 +0,0 @@
buildscript {
ext.kotlin_version = '1.3.70'
repositories {
maven {
url "http://nexus.astraeus.nl/nexus/content/groups/public"
}
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
plugins {
id("org.jetbrains.kotlin.js") version "1.3.70"
}
apply plugin: 'idea'
apply plugin: 'maven'
apply plugin: 'maven-publish'
group 'nl.astraeus'
version '0.1.17-SNAPSHOT'
/*
kotlin {
target {
browser {
webpackTask {
output.libraryTarget = "umd"
}
}
}
}
compileKotlinJs.kotlinOptions.moduleKind = "umd"
*/
idea {
module {
name = "komp"
}
}
repositories {
maven {
url "http://nexus.astraeus.nl/nexus/content/groups/public"
}
mavenCentral()
}
ext {
kotlin_version = '1.3.70'
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
compile 'org.jetbrains.kotlinx:kotlinx-html-js:0.7.1'
}
uploadArchives {
//println 'user: ' + nexusUsername
repositories {
mavenDeployer {
repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") {
authentication(userName: nexusUsername, password: nexusPassword)
}
snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") {
authentication(userName: nexusUsername, password: nexusPassword)
}
}
}
}
/*
compileKotlin2Js {
kotlinOptions.sourceMap = true
kotlinOptions.sourceMapEmbedSources = "always"
// remaining configuration options
}
*/

77
build.gradle.kts Normal file
View File

@@ -0,0 +1,77 @@
plugins {
kotlin("multiplatform") version "1.4-M2-eap-68"
`maven-publish`
}
group = "nl.astraeus"
version = "0.1.20-SNAPSHOT"
repositories {
maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") }
mavenCentral()
maven {
url = uri("https://dl.bintray.com/kotlin/kotlin-dev")
}
}
kotlin {
/* Targets configuration omitted.
* To find out how to configure the targets, please follow the link:
* https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#setting-up-targets */
js {
browser {
//produceKotlinLibrary()
}
}
sourceSets {
val commonMain by getting {
dependencies {
implementation(kotlin("stdlib-common"))
//implementation("org.jetbrains.kotlinx:kotlinx-html:0.7.2-build-1711")
}
}
val jsMain by getting {
dependencies {
implementation(kotlin("stdlib-js"))
api("org.jetbrains.kotlinx:kotlinx-html-js:0.7.2-build-1716")
}
}
}
}
publishing {
repositories {
maven {
name = "releases"
// change to point to your repo, e.g. http://my.org/repo
url = uri("http://nexus.astraeus.nl/nexus/content/repositories/releases")
credentials {
val nexusUsername: String by project
val nexusPassword: String by project
username = nexusUsername
password = nexusPassword
}
}
maven {
name = "snapshots"
// change to point to your repo, e.g. http://my.org/repo
url = uri("http://nexus.astraeus.nl/nexus/content/repositories/snapshots")
credentials {
val nexusUsername: String by project
val nexusPassword: String by project
username = nexusUsername
password = nexusPassword
}
}
}
publications {
val kotlinMultiplatform by getting {
//artifactId = "kotlin-css-generator"
}
}
}

View File

@@ -1,41 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="komp" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="nl.astraeus" external.system.module.version="0.1.17-SNAPSHOT" type="JAVA_MODULE" version="4"> <module external.linked.project.id="komp" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="nl.astraeus" external.system.module.version="0.1.20-SNAPSHOT" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="kotlin-language" name="Kotlin">
<configuration version="3" platform="JavaScript " allPlatforms="JS []" useProjectSettings="false">
<testOutputPath>$MODULE_DIR$/build/classes/kotlin/test/komp_test.js</testOutputPath>
<compilerSettings />
<compilerArguments>
<option name="outputFile" value="$MODULE_DIR$/build/classes/kotlin/main/komp.js" />
<option name="noStdlib" value="true" />
<option name="sourceMap" value="true" />
<option name="sourceMapEmbedSources" value="always" />
<option name="metaInfo" value="true" />
<option name="target" value="v5" />
<option name="main" value="call" />
<option name="languageVersion" value="1.3" />
<option name="apiVersion" value="1.3" />
<option name="pluginOptions">
<array />
</option>
<option name="pluginClasspaths">
<array />
</option>
<option name="errors">
<ArgumentParseErrors />
</option>
</compilerArguments>
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true"> <component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output /> <exclude-output />
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" /> <excludeFolder url="file://$MODULE_DIR$/.gradle" />
<excludeFolder url="file://$MODULE_DIR$/build" /> <excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/out" />
</content> </content>
<orderEntry type="jdk" jdkName="Kotlin SDK" jdkType="KotlinSDK" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>

View File

@@ -27,6 +27,9 @@
</map> </map>
</option> </option>
</component> </component>
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8" />
</component>
<component name="Encoding"> <component name="Encoding">
<file url="PROJECT" charset="UTF-8" /> <file url="PROJECT" charset="UTF-8" />
</component> </component>
@@ -218,7 +221,7 @@
<module fileurl="file://$PROJECT_DIR$/komp_test.iml" filepath="$PROJECT_DIR$/komp_test.iml" group="komp" /> <module fileurl="file://$PROJECT_DIR$/komp_test.iml" filepath="$PROJECT_DIR$/komp_test.iml" group="komp" />
</modules> </modules>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/classes" /> <output url="file://$PROJECT_DIR$/classes" />
</component> </component>
<component name="PropertiesComponent"> <component name="PropertiesComponent">

View File

@@ -39,6 +39,17 @@
<content url="file://$MODULE_DIR$/src/test/resources" /> <content url="file://$MODULE_DIR$/src/test/resources" />
<orderEntry type="jdk" jdkName="Kotlin SDK" jdkType="KotlinSDK" /> <orderEntry type="jdk" jdkName="Kotlin SDK" jdkType="KotlinSDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="komp_main" scope="TEST" />
<orderEntry type="module-library" scope="TEST">
<library>
<CLASSES>
<root url="file://$MODULE_DIR$/build/classes/kotlin/main" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-js:1.3.70" level="project" />
<orderEntry type="module-library" scope="RUNTIME"> <orderEntry type="module-library" scope="RUNTIME">
<library> <library>
<CLASSES> <CLASSES>
@@ -48,45 +59,9 @@
<SOURCES /> <SOURCES />
</library> </library>
</orderEntry> </orderEntry>
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlinx:kotlinx-html-common:0.7.1" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.3.70" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlinx:kotlinx-html-js:0.7.1" level="project" /> <orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlinx:kotlinx-html-js:0.7.1" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-js:1.3.70" level="project" /> <orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.3.70" level="project" />
<orderEntry type="module-library" scope="TEST"> <orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlinx:kotlinx-html-common:0.7.1" level="project" />
<library>
<CLASSES>
<root url="file://$MODULE_DIR$/build/classes/kotlin/main" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library" scope="TEST">
<library>
<CLASSES>
<root url="file://$MODULE_DIR$/build/classes/kotlin/main" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-js/1.3.70/41513becdc85a89799b74028a81c39c364ba3465/kotlin-stdlib-js-1.3.70.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-html-js/0.7.1/f3a744916effcbe728d38e4bfd732f9694d18fa9/kotlinx-html-js-0.7.1.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.3.70/3fa8dd6c896d635e78201e5e811545f3846dec04/kotlin-stdlib-common-1.3.70.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-html-common/0.7.1/26651a49fdc0e6abf8a61182b01d6bb0a6bb427e/kotlinx-html-common-0.7.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module" module-name="komp_main" scope="TEST" />
<orderEntry type="module-library" scope="RUNTIME">
<library>
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-html-js/0.7.1/f3a744916effcbe728d38e4bfd732f9694d18fa9/kotlinx-html-js-0.7.1.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-js/1.3.70/41513becdc85a89799b74028a81c39c364ba3465/kotlin-stdlib-js-1.3.70.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.3.70/3fa8dd6c896d635e78201e5e811545f3846dec04/kotlin-stdlib-common-1.3.70.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-html-common/0.7.1/26651a49fdc0e6abf8a61182b01d6bb0a6bb427e/kotlinx-html-common-0.7.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
</component> </component>
<component name="TestModuleProperties" production-module="komp_main" /> <component name="TestModuleProperties" production-module="komp_main" />
</module> </module>

View File

@@ -1,3 +0,0 @@
rootProject.name = 'komp'
enableFeaturePreview('GRADLE_METADATA')

16
settings.gradle.kts Normal file
View File

@@ -0,0 +1,16 @@
pluginManagement {
repositories {
maven { setUrl("https://dl.bintray.com/kotlin/kotlin-dev") }
maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") }
mavenCentral()
maven { setUrl("https://plugins.gradle.org/m2/") }
}
}
rootProject.name = "komp"
enableFeaturePreview("GRADLE_METADATA")

View File

@@ -0,0 +1,195 @@
package nl.astraeus.komp
import org.w3c.dom.HTMLElement
import org.w3c.dom.Node
import org.w3c.dom.events.Event
import org.w3c.dom.get
object DiffPatch {
fun updateNode(oldNode: Node, newNode: Node): Node {
if (oldNode is HTMLElement && newNode is HTMLElement) {
if (oldNode.nodeName == newNode.nodeName) {
if (oldNode.getAttribute("data-komp-hash") != null &&
oldNode.getAttribute("data-komp-hash") == newNode.getAttribute("data-komp-hash")) {
if (Komponent.logReplaceEvent) {
console.log("Skip node, hash equals", oldNode, newNode)
}
return oldNode
} else {
if (Komponent.logReplaceEvent) {
console.log("Update attributes", oldNode.innerHTML, newNode.innerHTML)
}
updateAttributes(oldNode, newNode);
if (Komponent.logReplaceEvent) {
console.log("Update children", oldNode.innerHTML, newNode.innerHTML)
}
updateChildren(oldNode, newNode)
updateEvents(oldNode, newNode)
return oldNode
}
} else {
if (Komponent.logReplaceEvent) {
console.log("Replace node ee", oldNode.innerHTML, newNode.innerHTML)
}
replaceNode(oldNode, newNode)
return newNode
}
} else {
if (oldNode.nodeType == newNode.nodeType && oldNode.nodeType == 3.toShort()) {
if (oldNode.textContent != newNode.textContent) {
if (Komponent.logReplaceEvent) {
console.log("Updating text content", oldNode, newNode)
}
oldNode.textContent = newNode.textContent
return oldNode
}
}
if (Komponent.logReplaceEvent) {
console.log("Replace node", oldNode, newNode)
}
replaceNode(oldNode, newNode)
return newNode
}
}
private fun updateAttributes(oldNode: HTMLElement, newNode: HTMLElement) {
// removed attributes
for (index in 0 until oldNode.attributes.length) {
val attr = oldNode.attributes[index]
if (attr != null && newNode.attributes[attr.name] == null) {
oldNode.removeAttribute(attr.name)
}
}
for (index in 0 until newNode.attributes.length) {
val attr = newNode.attributes[index]
if (attr != null) {
val oldAttr = oldNode.attributes[attr.name]
if (oldAttr == null || oldAttr.value != attr.value) {
oldNode.setAttribute(attr.name, attr.value)
}
}
}
}
private fun updateChildren(oldNode: HTMLElement, newNode: HTMLElement) {
// todo: add 1 look ahead/back
var oldIndex = 0
var newIndex = 0
if (Komponent.logReplaceEvent) {
console.log("updateChildren old/new count", oldNode.childNodes.length, newNode.childNodes.length)
}
while(newIndex < newNode.childNodes.length) {
if (Komponent.logReplaceEvent) {
console.log(">>> updateChildren old/new count", oldNode.childNodes, newNode.childNodes)
console.log("Update Old/new", oldIndex, newIndex)
}
val newChildNode = newNode.childNodes[newIndex]
if (oldIndex < oldNode.childNodes.length) {
val oldChildNode = oldNode.childNodes[oldIndex]
if (oldChildNode != null && newChildNode != null) {
if (Komponent.logReplaceEvent) {
console.log("Update node Old/new", oldChildNode, newChildNode)
}
updateNode(oldChildNode, newChildNode)
if (Komponent.logReplaceEvent) {
console.log("--- Updated Old/new", oldNode.children, newNode.children)
}
} else {
if (Komponent.logReplaceEvent) {
console.log("Null node", oldChildNode, newChildNode)
}
}
} else {
if (Komponent.logReplaceEvent) {
console.log("Append Old/new/node", oldIndex, newIndex, newChildNode)
}
oldNode.append(newChildNode)
}
if (Komponent.logReplaceEvent) {
console.log("<<< Updated Old/new", oldNode.children, newNode.children)
}
oldIndex++
newIndex++
}
while(oldIndex < oldNode.childNodes.length) {
oldNode.childNodes[oldIndex]?.also {
if (Komponent.logReplaceEvent) {
console.log("Remove old node", it)
}
oldNode.removeChild(it)
}
oldIndex++
}
}
private fun updateEvents(oldNode: HTMLElement, newNode: HTMLElement) {
val oldEvents = mutableListOf<String>()
oldEvents.addAll((oldNode.getAttribute("data-komp-events") ?: "").split(","))
val newEvents = (newNode.getAttribute("data-komp-events") ?: "").split(",")
for (event in newEvents) {
if (event.isNotBlank()) {
val oldNodeEvent = oldNode.asDynamic()["event-$event"]
val newNodeEvent = newNode.asDynamic()["event-$event"]
if (oldNodeEvent != null) {
oldNode.removeEventListener(event, oldNodeEvent as ((Event) -> Unit), null)
}
if (newNodeEvent != null) {
oldNode.addEventListener(event, newNodeEvent as ((Event) -> Unit), null)
oldNode.asDynamic()["event-$event"] = newNodeEvent
}
oldEvents.remove(event)
}
}
for (event in oldEvents) {
if (event.isNotBlank()) {
val oldNodeEvent = oldNode.asDynamic()["event-$event"]
if (oldNodeEvent != null) {
oldNode.removeEventListener(event, oldNodeEvent as ((Event) -> Unit), null)
}
}
}
newNode.getAttribute("data-komp-events")?.also {
oldNode.setAttribute("data-komp-events", it)
}
}
private fun replaceNode(oldNode: Node, newNode: Node) {
oldNode.parentNode?.also { parent ->
val clone = newNode.cloneNode(true)
if (newNode is HTMLElement) {
val events = (newNode.getAttribute("data-komp-events") ?: "").split(",")
for (event in events) {
val foundEvent = newNode.asDynamic()["event-$event"]
if (foundEvent != null) {
clone.addEventListener(event, foundEvent as ((Event) -> Unit), null)
}
}
}
parent.replaceChild(clone, oldNode)
}
}
}

View File

@@ -1,21 +1,20 @@
package nl.astraeus.komp package nl.astraeus.komp
import kotlinx.html.DefaultUnsafe import kotlinx.html.*
import kotlinx.html.Entities import org.w3c.dom.*
import kotlinx.html.Tag
import kotlinx.html.TagConsumer
import kotlinx.html.Unsafe
import org.w3c.dom.Document
import org.w3c.dom.HTMLElement
import org.w3c.dom.Node
import org.w3c.dom.asList
import org.w3c.dom.css.CSSStyleDeclaration import org.w3c.dom.css.CSSStyleDeclaration
import org.w3c.dom.events.Event import org.w3c.dom.events.Event
import kotlin.browser.document import kotlin.browser.document
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
private inline fun HTMLElement.setEvent(name: String, noinline callback : (Event) -> Unit) : Unit { private inline fun HTMLElement.setEvent(name: String, noinline callback : (Event) -> Unit) : Unit {
asDynamic()[name] = callback val eventName = if (name.startsWith("on")) { name.substring(2) } else { name }
addEventListener(eventName, callback, null)
//asDynamic()[name] = callback
val events = getAttribute("data-komp-events") ?: ""
setAttribute("data-komp-events", if (events.isBlank()) { eventName } else { "$events,$eventName" })
asDynamic()["event-$eventName"] = callback
} }
interface HtmlConsumer : TagConsumer<HTMLElement> { interface HtmlConsumer : TagConsumer<HTMLElement> {
@@ -73,13 +72,27 @@ class HtmlBuilder(
} }
override fun onTagEnd(tag: Tag) { override fun onTagEnd(tag: Tag) {
var hash = 0
if (path.isEmpty() || path.last().tagName.toLowerCase() != tag.tagName.toLowerCase()) { if (path.isEmpty() || path.last().tagName.toLowerCase() != tag.tagName.toLowerCase()) {
throw IllegalStateException("We haven't entered tag ${tag.tagName} but trying to leave") throw IllegalStateException("We haven't entered tag ${tag.tagName} but trying to leave")
} }
val element = path.last() val element = path.last()
for (index in 0 until element.childNodes.length) {
val child = element.childNodes[index]
if (child is HTMLElement) {
hash = hash * 37 + (child.getAttribute("data-komp-hash")?.toInt() ?: 0)
} else {
hash = hash * 37 + (child?.textContent?.hashCode() ?: 0)
}
}
tag.attributesEntries.forEach { tag.attributesEntries.forEach {
hash = hash * 37 + it.key.hashCode()
hash = hash * 37 + it.value.hashCode()
if (it.key == "class") { if (it.key == "class") {
val classes = it.value.split(Regex("\\s+")) val classes = it.value.split(Regex("\\s+"))
val classNames = StringBuilder() val classNames = StringBuilder()
@@ -121,6 +134,8 @@ class HtmlBuilder(
} }
} }
element.setAttribute("data-komp-hash", hash.toString())
lastLeaved = path.removeAt(path.lastIndex) lastLeaved = path.removeAt(path.lastIndex)
} }

View File

@@ -33,6 +33,11 @@ class DummyKomponent: Komponent() {
} }
} }
enum class UpdateStrategy {
REPLACE,
DOM_DIFF
}
abstract class Komponent { abstract class Komponent {
var element: Node? = null var element: Node? = null
val declaredStyles: MutableMap<String, CSSStyleDeclaration> = HashMap() val declaredStyles: MutableMap<String, CSSStyleDeclaration> = HashMap()
@@ -68,10 +73,18 @@ abstract class Komponent {
val newElement = create() val newElement = create()
if (oldElement != null) { if (oldElement != null) {
if (updateStrategy == UpdateStrategy.REPLACE) {
if (logReplaceEvent) { if (logReplaceEvent) {
console.log("Replacing", oldElement, newElement) console.log("Replacing", oldElement, newElement)
} }
oldElement.parentNode?.replaceChild(newElement, oldElement) oldElement.parentNode?.replaceChild(newElement, oldElement)
element = newElement
} else {
if (logReplaceEvent) {
console.log("DomDiffing", oldElement, newElement)
}
element = DiffPatch.updateNode(oldElement, newElement)
}
} }
} }
@@ -91,6 +104,7 @@ abstract class Komponent {
companion object { companion object {
var logRenderEvent = false var logRenderEvent = false
var logReplaceEvent = false var logReplaceEvent = false
var updateStrategy = UpdateStrategy.DOM_DIFF
fun create(parent: HTMLElement, component: Komponent, insertAsFirst: Boolean = false) { fun create(parent: HTMLElement, component: Komponent, insertAsFirst: Boolean = false) {
val element = component.create() val element = component.create()
@@ -102,4 +116,5 @@ abstract class Komponent {
} }
} }
} }
} }

View File

@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Bla</title>
</head>
<body>
<script src="../../kotlin-js-min/main/kotlin.js"></script>
<script src="../../kotlin-js-min/main/kotlinx-html-js.js"></script>
<script src="../../kotlin-js-min/main/komp.js"></script>
</body>
</html>