diff --git a/client/invoke.js b/client/invoke.js
index 83b6df1a..315a06a6 100644
--- a/client/invoke.js
+++ b/client/invoke.js
@@ -1,5 +1,6 @@
import deserialize from '../shared/deserialize'
import prefix from '../shared/prefix'
+import { symbolHashJoin } from '../shared/symbolHash'
import page from './page'
import worker from './worker'
import client from './client'
@@ -13,7 +14,7 @@ export default function invoke(name, hash) {
} else {
worker.queues[name] = [...worker.queues[name], params]
}
- let finalHash = hash === this.hash ? hash : `${hash}-${this.hash}`
+ let finalHash = hash === this.hash ? hash : symbolHashJoin(hash, this.hash)
let url = `${worker.api}/${prefix}/${finalHash}/${name}.json`
if (module.hot) {
const version = client.klasses[hash].__hashes[name]
diff --git a/client/render.js b/client/render.js
index 7fee3434..e137652f 100644
--- a/client/render.js
+++ b/client/render.js
@@ -5,7 +5,7 @@ import { anchorableElement } from './anchorableNode'
import { generateCallback, generateSubject } from './events'
import { ref } from './ref'
-export default function render(node, options) {
+export default function render(node, isSvg = false) {
if (isFalse(node) || node.type === 'head') {
node.element = document.createComment('')
return node.element
@@ -16,9 +16,8 @@ export default function render(node, options) {
return node.element
}
- const svg = (options && options.svg) || node.type === 'svg'
-
- if (svg) {
+ isSvg = isSvg || node.type === 'svg'
+ if (isSvg) {
node.element = document.createElementNS('http://www.w3.org/2000/svg', node.type)
} else {
node.element = document.createElement(node.type)
@@ -58,7 +57,7 @@ export default function render(node, options) {
if (!node.attributes.html) {
for (let i = 0; i < node.children.length; i++) {
- const child = render(node.children[i], { svg })
+ const child = render(node.children[i], isSvg)
node.element.appendChild(child)
}
diff --git a/client/rerender.js b/client/rerender.js
index cd788f67..512f9d69 100644
--- a/client/rerender.js
+++ b/client/rerender.js
@@ -100,7 +100,7 @@ function updateHeadChildren(currentChildren, nextChildren) {
}
}
-function _rerender(current, next) {
+function _rerender(current, next, isParentSvg = false) {
const selector = current.element
next.element = current.element
@@ -108,8 +108,10 @@ function _rerender(current, next) {
return
}
+ const isSvg = isParentSvg || next.type === 'svg'
+
if (current.type !== next.type) {
- const nextSelector = render(next)
+ const nextSelector = render(next, isSvg)
selector.replaceWith(nextSelector)
return
}
@@ -132,22 +134,22 @@ function _rerender(current, next) {
const limit = Math.max(current.children.length, next.children.length)
if (next.children.length > current.children.length) {
for (let i = 0; i < current.children.length; i++) {
- _rerender(current.children[i], next.children[i])
+ _rerender(current.children[i], next.children[i], isSvg)
}
for (let i = current.children.length; i < next.children.length; i++) {
- const nextSelector = render(next.children[i])
+ const nextSelector = render(next.children[i], isSvg)
selector.appendChild(nextSelector)
}
} else if (current.children.length > next.children.length) {
for (let i = 0; i < next.children.length; i++) {
- _rerender(current.children[i], next.children[i])
+ _rerender(current.children[i], next.children[i], isSvg)
}
for (let i = current.children.length - 1; i >= next.children.length; i--) {
selector.childNodes[i].remove()
}
} else {
for (let i = limit - 1; i > -1; i--) {
- _rerender(current.children[i], next.children[i])
+ _rerender(current.children[i], next.children[i], isSvg)
}
}
}
diff --git a/package.json b/package.json
index 3317b859..970fece1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "nullstack",
- "version": "0.20.0",
+ "version": "0.20.4",
"description": "Feature-Driven Full Stack JavaScript Components",
"main": "./types/index.d.ts",
"author": "Mortaro",
@@ -16,31 +16,27 @@
},
"dependencies": {
"@swc/core": "1.3.35",
- "babel-loader": "9.1.2",
"body-parser": "1.20.1",
"commander": "10.0.0",
- "copy-webpack-plugin": "^11.0.0",
+ "copy-webpack-plugin": "11.0.0",
"css-loader": "6.7.3",
- "css-minimizer-webpack-plugin": "^4.2.2",
+ "css-minimizer-webpack-plugin": "5.0.1",
"dotenv": "16.0.3",
"eslint-plugin-nullstack": "0.0.26",
"express": "4.18.2",
"fs-extra": "11.1.0",
- "lightningcss": "^1.19.0",
+ "lightningcss": "1.21.5",
"mini-css-extract-plugin": "2.7.2",
"node-fetch": "2.6.7",
- "nodemon-webpack-plugin": "^4.8.1",
+ "nodemon-webpack-plugin": "4.8.1",
"sass": "1.58.0",
"sass-loader": "13.2.0",
- "style-loader": "^3.3.1",
+ "style-loader": "3.3.3",
"swc-loader": "0.2.3",
"swc-plugin-nullstack": "0.1.3",
"terser-webpack-plugin": "5.3.6",
- "webpack": "^5.0.0",
- "webpack-dev-server": "4.11.1",
- "webpack-hot-middleware": "^2.25.3"
- },
- "devDependencies": {
- "webpack-dev-middleware": "github:Mortaro/webpack-dev-middleware#fix-write-to-disk-cleanup"
+ "webpack": "5.88.2",
+ "webpack-hot-middleware": "2.25.4",
+ "webpack-dev-middleware": "6.1.1"
}
}
\ No newline at end of file
diff --git a/server/render.js b/server/render.js
index e1b565d2..06e416cf 100644
--- a/server/render.js
+++ b/server/render.js
@@ -1,4 +1,4 @@
-import { isFalse } from '../shared/nodes'
+import { isFalse, isText } from '../shared/nodes'
import { sanitizeHtml } from '../shared/sanitizeString'
import renderAttributes from './renderAttributes'
@@ -25,7 +25,7 @@ function renderBody(node, scope, next) {
if (isFalse(node)) {
return ''
}
- if (node.type === 'text') {
+ if (isText(node)) {
const text = node.text === '' ? ' ' : sanitizeHtml(node.text.toString())
return next && next.type === 'text' ? `${text}` : text
}
diff --git a/server/server.js b/server/server.js
index 45e73617..26721640 100644
--- a/server/server.js
+++ b/server/server.js
@@ -2,6 +2,7 @@ import bodyParser from 'body-parser'
import express from 'express'
import path from 'path'
import deserialize from '../shared/deserialize'
+import { symbolHashSplit } from '../shared/symbolHash'
import prefix from '../shared/prefix'
import context, { getCurrentContext, generateCurrentContext } from './context'
import emulatePrerender from './emulatePrerender'
@@ -123,7 +124,7 @@ server.start = function () {
const payload = request.method === 'GET' ? request.query.payload : request.body
const args = deserialize(payload)
const { hash, methodName } = request.params
- const [invokerHash, boundHash] = hash.split('-')
+ const [invokerHash, boundHash] = symbolHashSplit(hash)
const key = `${invokerHash}.${methodName}`
await load(boundHash || invokerHash)
const invokerKlass = registry[invokerHash]
@@ -154,7 +155,7 @@ server.start = function () {
const payload = request.method === 'GET' ? request.query.payload : request.body
const args = deserialize(payload)
const { version, hash, methodName } = request.params
- const [invokerHash, boundHash] = hash.split('-')
+ const [invokerHash, boundHash] = symbolHashSplit(hash)
let [filePath, klassName] = (invokerHash || boundHash).split("___")
const file = path.resolve('..', filePath.replaceAll('__', '/'))
console.info('\x1b[1;37m%s\x1b[0m', ` [${request.method}] ${request.path}`)
diff --git a/shared/nodes.js b/shared/nodes.js
index f1d3363e..4da25648 100644
--- a/shared/nodes.js
+++ b/shared/nodes.js
@@ -18,5 +18,5 @@ export function isFunction(node) {
}
export function isText(node) {
- return node.type === 'text'
+ return node.type === 'text' && node.attributes === undefined
}
diff --git a/shared/symbolHash.js b/shared/symbolHash.js
new file mode 100644
index 00000000..53d59ad0
--- /dev/null
+++ b/shared/symbolHash.js
@@ -0,0 +1,7 @@
+export function symbolHashSplit(hash) {
+ return hash.split('---')
+}
+
+export function symbolHashJoin(hash, joinHash) {
+ return `${hash}---${joinHash}`
+}
\ No newline at end of file
diff --git a/tests/src/Application.njs b/tests/src/Application.njs
index 58691b9e..5bd8201a 100644
--- a/tests/src/Application.njs
+++ b/tests/src/Application.njs
@@ -63,6 +63,7 @@ import LazyComponentLoader from './LazyComponentLoader'
import NestedFolder from './nested/NestedFolder'
import ChildComponentWithoutServerFunctions from './ChildComponentWithoutServerFunctions'
import ObjectEventScope from './ObjectEventScope'
+import SvgSupport from './SvgSupport.njs'
import './Application.css'
class Application extends Nullstack {
@@ -156,6 +157,7 @@ class Application extends Nullstack {
+