Repository: HapticX/happyx (language: python)

Issue:
implement proper thunks that construct their part of the vdom after the  parent vdom has been created.
**Is your feature request related to a problem? Please describe.** 🤔
There has been some struggle with loops and deferred TagRef generation.  In #303 I said I'd look back at how Karax handles thunks,. Recall everyone that the general definition of thunk is "a delayed computation", and in Scheme, it means more specifically the functional programming way of doing this by passing around a  `proc (): whatever`
`
Nim takes inspiration from multiple sources, but part of the interest in macros comes from Lisp, of which Scheme is one dialect.

**Describe the solution you'd like** 💡

The i[mplementation in Karax](https://github.com/karaxnim/karax/blob/f4014d134ff48e118eb4810e9ebceb2920f1af1b/karax/karax.nim#L178) seems solid. The core logic is to delay the rendering of a thunk by attaching a placeholder TagRef to the vdom, then when rendering, calling the thunk and modifying the vdom. In proc `toDom`:

```
  elif n.kind == VNodeKind.vthunk:
    let x = callThunk(vcomponents[n.text], n)
    result = toDom(x, useAttachedNode, kxi)
    #n.key = result.key
    attach n
    return result

```

Components are handled in a similar way since that's the normal way of thinking about how html tags themselves are rendered to the dom. First the parent element is placed then the children are attached. The process is recursive and top-down.
```

  elif n.kind == VNodeKind.component:
    let x = VComponent(n)
    if x.onAttachImpl != nil: x.onAttachImpl(x)
    assert x.renderImpl != nil
    if x.expanded == nil:
      x.expanded = x.renderImpl(x)
      #  x.updatedImpl(x, nil)
    assert x.expanded != nil
    result = toDom(x.expanded, useAttachedNode, kxi)
    attach n
    return result
```

Not that the definition of VNode as a[ variant object ](https://nim-lang.org/docs/tut2.html#object-oriented-programming-object-variants)helps with handling these different kinds of rendering behavior. This strategy would also allow diffreent types of TagRef in Happyx to have different data. I would conceptualize a HappyX thunk as a kind of TagRef (so that it can attach to the vdom) that carries a pure function thunk as a field. In other words, something like
```

type TagRef = ref object
   case kind: TagRefKind
     ##....
     of TagRefKind.thunk:
        thunkProc: proc (): TagRef
    ##....
```
    

**Describe alternatives you've considered** 💁‍♀️
I think there are some drawbacks to the approach in the most recent patch #303. Global variables tend to break easily and I think some preexisting functionality is already broken. I'm also somewhat concerned that continually tagging deeply nested components with the ID's of their parents will be non-performant in lengthy html documents.

** Other less-important context**

Karax implements two types of thunk . `vthunks` delay the computation until the vdom is being rendered to the dom. `dthunks `put a placeholder in the DOM that calls a proc, thus delaying computation until the dom is being rendered in the browser. Vthunks are not inmplemented in a particularly friendly way for the front end due to how the vthunk proc is parameterized, but that's a superficial issue and the core logic should be fine.  dthunks work fine but are annoying to code on the front end since they need to render to nodes in the DOM. Of the two I think that` vthunks` are the more important kind of thing to implement. We want  to mark some things in the vdom so that they start rendering after the rest of the vdom has been rendered. This would allow things like loops to make many different branches first in a natural way, then start renderding their bodies to the vdom afterward.
implement proper thunks that construct their part of the vdom after the  parent vdom has been created.
**Is your feature request related to a problem? Please describe.** 🤔
There has been some struggle with loops and deferred TagRef generation.  In #303 I said I'd look back at how Karax handles thunks,. Recall everyone that the general definition of thunk is "a delayed computation", and in Scheme, it means more specifically the functional programming way of doing this by passing around a  `proc (): whatever`
`
Nim takes inspiration from multiple sources, but part of the interest in macros comes from Lisp, of which Scheme is one dialect.

**Describe the solution you'd like** 💡

The i[mplementation in Karax](https://github.com/karaxnim/karax/blob/f4014d134ff48e118eb4810e9ebceb2920f1af1b/karax/karax.nim#L178) seems solid. The core logic is to delay the rendering of a thunk by attaching a placeholder TagRef to the vdom, then when rendering, calling the thunk and modifying the vdom. In proc `toDom`:

```
  elif n.kind == VNodeKind.vthunk:
    let x = callThunk(vcomponents[n.text], n)
    result = toDom(x, useAttachedNode, kxi)
    #n.key = result.key
    attach n
    return result

```

Components are handled in a similar way since that's the normal way of thinking about how html tags themselves are rendered to the dom. First the parent element is placed then the children are attached. The process is recursive and top-down.
```

  elif n.kind == VNodeKind.component:
    let x = VComponent(n)
    if x.onAttachImpl != nil: x.onAttachImpl(x)
    assert x.renderImpl != nil
    if x.expanded == nil:
      x.expanded = x.renderImpl(x)
      #  x.updatedImpl(x, nil)
    assert x.expanded != nil
    result = toDom(x.expanded, useAttachedNode, kxi)
    attach n
    return result
```

Not that the definition of VNode as a[ variant object ](https://nim-lang.org/docs/tut2.html#object-oriented-programming-object-variants)helps with handling these different kinds of rendering behavior. This strategy would also allow diffreent types of TagRef in Happyx to have different data. I would conceptualize a HappyX thunk as a kind of TagRef (so that it can attach to the vdom) that carries a pure function thunk as a field. In other words, something like
```

type TagRef = ref object
   case kind: TagRefKind
     ##....
     of TagRefKind.thunk:
        thunkProc: proc (): TagRef
    ##....
```
    

**Describe alternatives you've considered** 💁‍♀️
I think there are some drawbacks to the approach in the most recent patch #303. Global variables tend to break easily and I think some preexisting functionality is already broken. I'm also somewhat concerned that continually tagging deeply nested components with the ID's of their parents will be non-performant in lengthy html documents.

** Other less-important context**

Karax implements two types of thunk . `vthunks` delay the computation until the vdom is being rendered to the dom. `dthunks `put a placeholder in the DOM that calls a proc, thus delaying computation until the dom is being rendered in the browser. Vthunks are not inmplemented in a particularly friendly way for the front end due to how the vthunk proc is parameterized, but that's a superficial issue and the core logic should be fine.  dthunks work fine but are annoying to code on the front end since they need to render to nodes in the DOM. Of the two I think that` vthunks` are the more important kind of thing to implement. We want  to mark some things in the vdom so that they start rendering after the rest of the vdom has been rendered. This would allow things like loops to make many different branches first in a natural way, then start renderding their bodies to the vdom afterward.


Discussion / hints:
How about this?
```nim
appRoutes "app":
  "/":
    tDiv:
      lazy:  # lazy converts all children into proc(): TagRef
        "Hello, world!"
```
`lazy` statement produces simple comment node.

Before rendering in HTML `prerenderLazyProcs` will called. It calls `lazyFunc` recursively and removes `lazy` from HTML (comment node)

So, if you print `TagRef` with `lazy` statement:
```nim
var html = buildHtml:
  "Hello, world!"
  lazy:
    "Bye, world!"
```
Than output will be:
```html
<div>Hello, world!<!----></div>
```

But after rendering it will be
```html
<div>
Hello, world!
<div>Bye world!</div>
</div>
```
How about this?
```nim
appRoutes "app":
  "/":
    tDiv:
      lazy:  # lazy converts all children into proc(): TagRef
        "Hello, world!"
```
`lazy` statement produces simple comment node.

Before rendering in HTML `prerenderLazyProcs` will called. It calls `lazyFunc` recursively and removes `lazy` from HTML (comment node)

So, if you print `TagRef` with `lazy` statement:
```nim
var html = buildHtml:
  "Hello, world!"
  lazy:
    "Bye, world!"
```
Than output will be:
```html
<div>Hello, world!<!----></div>
```

But after rendering it will be
```html
<div>
Hello, world!
<div>Bye world!</div>
</div>
```

Fix patch:
diff --git a/bindings/python/happyxpy/happyx_win.pyd b/bindings/python/happyxpy/happyx_win.pyd
index 4c404761..c320e2c3 100644
Binary files a/bindings/python/happyxpy/happyx_win.pyd and b/bindings/python/happyxpy/happyx_win.pyd differ
diff --git a/happyx.nimble b/happyx.nimble
index 5d28c3f3..d4eeab53 100755
--- a/happyx.nimble
+++ b/happyx.nimble
@@ -2,7 +2,7 @@
 
 description = "Macro-oriented asynchronous web-framework written with ♥"
 author = "HapticX"
-version = "4.3.1"
+version = "4.4.0"
 license = "MIT"
 srcDir = "src"
 installExt = @["nim"]
@@ -18,7 +18,7 @@ requires "checksums >= 0.1.0"
 requires "cligen >= 1.6.14"
 requires "illwill#2fe96f5c5a6e216e84554d92090ce3d47460667a"
 # Regular expressions (only compile time)
-requires "regex#199e696a1b0e0db72e2e5a657926e5b944e6ae2d"
+requires "regex#head"
 # alternative HTTP servers
 requires "httpx >= 0.3.7"
 requires "microasynchttpserver >= 0.11.0"
diff --git a/src/happyx/cli/build_command.nim b/src/happyx/cli/build_command.nim
index e75dd385..a94d30f7 100644
--- a/src/happyx/cli/build_command.nim
+++ b/src/happyx/cli/build_command.nim
@@ -48,4 +48,4 @@ proc buildCommand*(optSize: bool = false, no_compile: bool = false): int =
     sizeNew.formatSize('.', bpColloquial, true)
   )
   shutdownCli()
-  QuitSuccess
\ No newline at end of file
+  QuitSuccess
diff --git a/src/happyx/cli/dev_command.nim b/src/happyx/cli/dev_command.nim
index 516b4655..506995aa 100644
--- a/src/happyx/cli/dev_command.nim
+++ b/src/happyx/cli/dev_command.nim
@@ -70,7 +70,7 @@ proc devCommand*(host: string = "127.0.0.1", port: int = 5000,
         "<script>" &
         fmt"let socket = new WebSocket('ws://' + window.location.host + '/hcr');" &
         "\nsocket.onmessage = (event) => {\n" &
-        "  if(event.data === 'true'){\n    window.location.reload();\n  }\n" &
+        "  if(event.data === '1'){\n    window.location.reload();\n  }\n" &
         "};\n\n" &
         "function intervalSending(){\n  socket.send('reload')\n}\n\n" &
         "setInterval(intervalSending, 1000);\n" &
@@ -82,9 +82,9 @@ proc devCommand*(host: string = "127.0.0.1", port: int = 5000,
       if wsData == "reload":
         if needReload:
           needReload = false
-          await wsClient.send("true")
+          await wsClient.send("1")
         else:
-          await wsClient.send("false")
+          await wsClient.send("0")
     
     get "/{file:path}":
       var result = ""
@@ -94,4 +94,4 @@ proc devCommand*(host: string = "127.0.0.1", port: int = 5000,
       if fileExists(path):
         await req.answerFile(path, forceResponse = true)
   shutdownCli()
-  QuitSuccess
\ No newline at end of file
+  QuitSuccess
diff --git a/src/happyx/cli/utils.nim b/src/happyx/cli/utils.nim
index 875f37c9..7d36d151 100644
--- a/src/happyx/cli/utils.nim
+++ b/src/happyx/cli/utils.nim
@@ -51,6 +51,11 @@ type
     plPython = "python",
     plJavaScript = "javascript",
     plTypeScript = "typescript"
+  ComponentData* = object
+    name*: string
+    parent*: string
+    file*: string
+    fields*: seq[tuple[name, kind, default: string, optional: bool]]
   ProjectData* = object
     process*: Process
     mainFile*: string  ## Main file without extension
@@ -60,6 +65,7 @@ type
     assetsDir*: string  ## Assets directory
     buildDir*: string  ## Build directory
     language*: ProgrammingLanguage
+    components*: seq[ComponentData]
     name*: string
   GodEyeData* = object
     needReload*: ptr bool
@@ -220,6 +226,48 @@ proc hasComp(tree: XmlNode, usage: var int, comp: string) =
     i.hasComp(usage, comp)
 
 
+proc fetchComponentsNimFrom(source: string): seq[ComponentData] =
+  result = @[]
+  for i in source.findAll(re2"(\n|\A)(component[^:]+:[\s\S]+?)(?=(component|\z))"):
+    let comp = source[i.boundaries]
+    
+    var m: RegexMatch2
+    discard comp.find(re2"component\s*(\w+)\s*(of\s*(\w+))?", m)
+    var component = ComponentData(name: comp[m.group(0)])
+
+    for i in comp.findAll(re2(r"(\A|\n)[\t ]*(\*)?(\w+)[ \t]*:(?!=)[ \t]*(([^\n=]+)=([\t ]*[^\n]+)|([^\n]+))", {regexMultiline})):
+      let optional = i.group(1).len > 0
+      let name = comp[i.group(2)]
+      let kind =
+        if i.group(4).len > 0:
+          comp[i.group(4)]
+        else:
+          comp[i.group(6)]
+      let default =
+        if i.group(5).len > 0:
+          comp[i.group(5)]
+        else:
+          ""
+      component.fields.add((name: name, kind: kind, default: default, optional: optional))
+    
+    if m.group(2).len > 0:
+      component.parent = comp[m.group(2)]
+    result.add(component)
+
+proc findAllComponentsNim(folder: string): seq[ComponentData] =
+  result = @[]
+  var f: File
+  for file in walkDirRec(folder):
+    let (dir, name, ext) = file.splitFile()
+    if ext == ".nim":
+      f = open(file)
+      for i in fetchComponentsNimFrom(f.readAll()):
+        var c = i
+        c.file = file
+        result.add(c)
+      f.close()
+
+
 proc compileProject*(): ProjectData {. discardable .} =
   ## Compiling Project
   result = readConfig()
@@ -232,6 +280,8 @@ proc compileProject*(): ProjectData {. discardable .} =
 
   case result.projectType:
   of ptSPA, ptSPA_PWA:
+    result.components = findAllComponentsNim(getCurrentDir() / result.srcDir)
+    # echo result.components
     result.process = startProcess(
       "nim", getCurrentDir() / result.srcDir,
       [
diff --git a/src/happyx/core/constants.nim b/src/happyx/core/constants.nim
index 334e7a7d..a6e13f6e 100755
--- a/src/happyx/core/constants.nim
+++ b/src/happyx/core/constants.nim
@@ -98,8 +98,8 @@ const
   nim_2_0_0* = (NimMajor, NimMinor, NimPatch) >= (2, 0, 0)
   # Framework version
   HpxMajor* = 4
-  HpxMinor* = 3
-  HpxPatch* = 1
+  HpxMinor* = 4
+  HpxPatch* = 0
   HpxVersion* = $HpxMajor & "." & $HpxMinor & "." & $HpxPatch
 
 
diff --git a/src/happyx/private/macro_utils.nim b/src/happyx/private/macro_utils.nim
index b3adeb45..76319640 100644
--- a/src/happyx/private/macro_utils.nim
+++ b/src/happyx/private/macro_utils.nim
@@ -165,7 +165,7 @@ proc useComponent*(statement: NimNode, inCycle, inComponent: bool,
       else:
         newEmptyNode()
     hasGenerics = generics.kind != nnkEmpty
-    componentName = fmt"comp{uniqueId.value}{uniqueId.value + 2}{uniqueId.value * 2}{uniqueId.value + 7}"
+    componentName = fmt"comp{uniqueId.value}"
     componentNameIdent =
       if cycleTmpVar == "" and compTmpVar.kind == nnkEmpty:
         newLit(componentName)
@@ -678,6 +678,26 @@ proc buildHtmlProcedure*(root, body: NimNode, inComponent: bool = false,
           lineInfoObj(statement[1])
         )
       result.add(newCall("tagFromString", node))
+    
+    elif statement.kind == nnkCall and statement[0] == ident"lazy" and statement.len == 2 and statement[1].kind == nnkStmtList:
+      # result.add(newCall("initTag", newLit"div", newCall("@", newNimNode(nnkBracket).add(newStmtList(
+      #   newLetStmt(ident"_lazy", newCall("initTag", newLit"div")),
+      #   newNimNode(nnkAsgn).add(newDotExpr(ident"_lazy", ident"lazy"), newLit(true)),
+      #   newCall("add", ident"_lazy", newCall("buildHtml", statement[1])),
+      #   ident"_lazy"
+      # )))))
+      result.add(
+        # newCall("initTag", newLit"div", newCall("@", newNimNode(nnkBracket).add(
+          newCall("lazyTag", newLambda(
+            newStmtList(
+              # newCall("buildHtml", statement[1]),
+              buildHtmlProcedure(ident"tDiv", statement[1], inComponent, componentName, inCycle, cycleTmpVar, compTmpVar, cycleVars),
+            ),
+            @[ident"TagRef"])
+          )
+        )# ), newLit(true)
+      # ))
+      # echo result[^1].toStrLit
 
     elif statement.kind == nnkCall and statement[0].kind != nnkPrefix:
       let
diff --git a/src/happyx/spa/renderer.nim b/src/happyx/spa/renderer.nim
index f264537e..337ec7cc 100644
--- a/src/happyx/spa/renderer.nim
+++ b/src/happyx/spa/renderer.nim
@@ -301,6 +301,16 @@ when defined(js):
           dom.childNodes[i].replaceWith(vdom.childNodes[i].cloneNode(true))
         if vdom.childNodes[i].nodeType != NodeType.TextNode:
           diff(vdom.childNodes[i].TagRef, dom.childNodes[i])
+  proc prerenderLazyProcs*(tag: TagRef) {.exportc: "prrndr".} =
+    echo tag.nodeType, ", ", tag.lazy
+    if tag.lazy:
+      let t = tag.lazyFunc()
+      t.prerenderLazyProcs()
+      tag.parentElement.insertBefore(t, tag)
+      tag.remove()
+    else:
+      for t in tag.childNodes:
+        t.TagRef.prerenderLazyProcs()
   proc renderVdom*(app: App, tag: TagRef, force: bool = false) {.exportc: "rndrvd".} =
     ## Rerender DOM with VDOM
     var realDom = document.getElementById(app.appId).Node
@@ -308,6 +318,10 @@ when defined(js):
     for i in rendererHandlers:
       if i.key == "beforeRendered":
         i.p()
+    # let tempTag = tag.cloneNode(true).TagRef
+    # tempTag.prerenderVdomProcs()
+    # tempTag.diff(realDom)
+    tag.prerenderLazyProcs()
     tag.diff(realDom)
     for i in rendererHandlers:
       if i.key == "rendered":
@@ -511,7 +525,7 @@ macro buildHtml*(html: untyped): untyped =
     result.add(newLit(true))
 
 
-template thunkHtml*(body: untyped): proc(): TagRef =
+template lazyHtml*(body: untyped): proc(): TagRef =
   proc(): TagRef =
     result = buildHtml:
       body
@@ -519,7 +533,7 @@ template thunkHtml*(body: untyped): proc(): TagRef =
     result.onlyChildren = true
 
 
-template thunkHtmls*(body: untyped): seq[proc(): TagRef] =
+template lazyHtmls*(body: untyped): seq[proc(): TagRef] =
   block:
     var res: seq[proc(): TagRef]
     template html(b: untyped) =
@@ -764,7 +778,6 @@ when enableAppRouting:
           newNimNode(nnkIfStmt).add(newNimNode(nnkElifBranch).add(
             newCall("hasAttribute", ident"activeElement", newLit"id"),
             newStmtList(
-              newCall("echo", newLit(100)),
               newLetStmt(
                 ident"_activeElement_",
                 newCall("getElementById", ident"document", newCall("id", ident"activeElement"))
diff --git a/src/happyx/spa/tag.nim b/src/happyx/spa/tag.nim
index 710db005..7189c563 100644
--- a/src/happyx/spa/tag.nim
+++ b/src/happyx/spa/tag.nim
@@ -40,6 +40,8 @@ when defined(js):
   type
     TagRef* = ref object of Element
       onlyChildren*: bool  ## Ignore self and shows only children
+      lazyFunc*: proc(): TagRef
+      lazy*: bool
     VmTagRef* = ref object
       name*: string
       parent*: VmTagRef
@@ -48,6 +50,8 @@ when defined(js):
       children*: seq[VmTagRef]
       isText*: bool  ## Ignore attributes and children when true
       onlyChildren*: bool  ## Ignore self and shows only children
+      lazyFunc*: proc(): TagRef
+      lazy*: bool
 else:
   type
     TagRef* = ref object
@@ -58,6 +62,8 @@ else:
       children*: seq[TagRef]
       isText*: bool  ## Ignore attributes and children when true
       onlyChildren*: bool  ## Ignore self and shows only children
+      lazyFunc*: proc(): TagRef
+      lazy*: bool
 
 
 const
@@ -91,11 +97,11 @@ const
 
 when defined(js):
   {.emit: """//js
-  const _originAddEventListener = Element.prototype.addEventListener;
-  const _originRemoveEventListener = Element.prototype.removeEventListener;
-  const _originCloneNode = Element.prototype.cloneNode;
+  const _originAddEventListener = Node.prototype.addEventListener;
+  const _originRemoveEventListener = Node.prototype.removeEventListener;
+  const _originCloneNode = Node.prototype.cloneNode;
 
-  Element.prototype.__getEventIndex = function(target, targetArgs) {
+  Node.prototype.__getEventIndex = function(target, targetArgs) {
     if (!this._eventListeners)
       this._eventListeners = [];
     return this._eventListeners.findIndex(args => {
@@ -105,7 +111,7 @@ when defined(js):
     });
   };
 
-  Element.prototype.getEventListeners = function() {
+  Node.prototype.getEventListeners = function() {
     if (!this._eventListeners)
       this._eventListeners = [];
     return this._eventListeners;
@@ -113,27 +119,32 @@ when defined(js):
 
   const cloneEvents = (source, element, deep) => {
     for (const args of source.getEventListeners())
-      Element.prototype.addEventListener.apply(element, args)
+      Node.prototype.addEventListener.apply(element, args)
+    
+    if (source.lazy && !element.lazy){
+      element.lazy = source.lazy;
+      element.lazyFunc = source.lazyFunc;
+    }
 
     if (deep) {
       for (let i = 0; i < source.childNodes.length; i++) {
         const sourceNode = source.childNodes[i];
         const targetNode = element.childNodes[i];
-        if (sourceNode instanceof Element && targetNode instanceof Element) {
+        if (sourceNode instanceof Node && targetNode instanceof Node) {
           cloneEvents(sourceNode, targetNode, deep);
         }
       }
     }
   };
 
-  Element.prototype.addEventListener = function() {
+  Node.prototype.addEventListener = function() {
     if (!this._eventListeners)
       this._eventListeners = [];
     this._eventListeners.push(arguments);
     return _originAddEventListener.apply(this, arguments);
   };
 
-  Element.prototype.removeEventListener = function() {
+  Node.prototype.removeEventListener = function() {
     if (!this._eventListeners)
       this._eventListeners = [];
     const eventIndex = this.__getEventIndex(arguments);
@@ -142,11 +153,11 @@ when defined(js):
     return _originRemoveEventListener.apply(this, arguments);
   };
 
-  Element.prototype.cloneNode = function(deep) {
+  Node.prototype.cloneNode = function(deep) {
     if (!this._eventListeners)
       this._eventListeners = [];
     const clonedNode = _originCloneNode.apply(this, arguments);
-    if (clonedNode instanceof Element)
+    if (clonedNode instanceof Node)
       cloneEvents(this, clonedNode, deep);
     return clonedNode;
   };
@@ -313,6 +324,21 @@ proc initTag*(name: string, isText: bool, children: seq[TagRef] = @[],
       result.add(child)
 
 
+proc lazyTag*(lazy: proc(): TagRef): TagRef =
+  ## Initializes a new lazy HTML tag
+  when defined(js):
+    result = document.createComment("").TagRef
+    result.onlyChildren = false
+    result.lazy = true
+    result.lazyFunc = lazy
+  else:
+    result = TagRef(
+      name: "div", isText: false, parent: nil,
+      attrs: newStringTable(), children: @[], args: @[],
+      onlyChildren: false, lazyFunc: lazy, lazy: true
+    )
+
+
 proc tag*(name: string): TagRef {.inline.} =
   ## Shortcut for `initTag func<#initTag,string,seq[TagRef]>`_
   runnableExamples:
@@ -565,7 +591,7 @@ func `$`*(self: TagRef): string =
   ## and includes any attributes specified for the tag.
   ## If the tag is a text tag, the function simply returns the tag's name.
   when defined(js):
-    return $self.outerHTML
+    $self.outerHTML
   else:
     let
       level = "  ".repeat(self.lvl)
diff --git a/src/happyx/ssr/server.nim b/src/happyx/ssr/server.nim
index 053eb3c9..4c075334 100755
--- a/src/happyx/ssr/server.nim
+++ b/src/happyx/ssr/server.nim
@@ -237,44 +237,44 @@ template answer*(
       h[key] = val
   # HTTPX
   when enableHttpx:
-    var headersArr: seq[string] = @[]
+    var headersArr = ""
     for key, value in h.pairs():
-      headersArr.add(key & ':' & value)
+      headersArr &= key & ':' & value & "\r\n"
     when declared(outCookies):
       for cookie in outCookies:
-        headersArr.add(cookie)
+        headersArr &= cookie & "\r\n"
     if contentLength.isSome:
       # useful for file answers
       when declared(statusCode):
         when statusCode is int:
-          req.send(statusCode.HttpCode, $message, contentLength, headersArr.join("\r\n"))
+          req.send(statusCode.HttpCode, $message, contentLength, headersArr)
         else:
-          req.send(code, $message, contentLength, headersArr.join("\r\n"))
+          req.send(code, $message, contentLength, headersArr)
       else:
-        req.send(code, $message, contentLength, headersArr.join("\r\n"))
+        req.send(code, $message, contentLength, headersArr)
     else:
       when declared(statusCode):
         when statusCode is int:
-          req.send(statusCode.HttpCode, $message, headersArr.join("\r\n"))
+          req.send(statusCode.HttpCode, $message, headersArr)
         else:
-          req.send(code, $message, headersArr.join("\r\n"))
+          req.send(code, $message, headersArr)
       else:
-        req.send(code, $message, headersArr.join("\r\n"))
+        req.send(code, $message, headersArr)
   # HTTP BEAST
   elif enableHttpBeast:
-    var headersArr: seq[string] = @[]
+    var headersArr = ""
     for key, value in h.pairs():
-      headersArr.add(key & ':' & value)
+      headersArr &= key & ':' & value & "\r\n"
     when declared(outCookies):
       for cookie in outCookies:
-        headersArr.add(cookie)
+        headersArr &= cookie & "\r\n"
     when declared(statusCode):
       when statusCode is int:
-        req.send(statusCode.HttpCode, $message, headersArr.join("\r\n"))
+        req.send(statusCode.HttpCode, $message, headersArr)
       else:
-        req.send(code, $message, headersArr.join("\r\n"))
+        req.send(code, $message, headersArr)
     else:
-      req.send(code, $message, headersArr.join("\r\n"))
+      req.send(code, $message, headersArr)
   # ASYNC HTTP SERVER / MICRO ASYNC HTTP SERVER
   else:
     when declared(outCookies):
@@ -579,6 +579,7 @@ macro routes*(server: Server, body: untyped = newStmtList()): untyped =
         ),
     )
     caseRequestMethodsStmt = newNimNode(nnkCaseStmt)
+    # caseRequestMethodsStmt = newNimNode(nnkIfStmt)
     methodTable = newTable[string, NimNode]()
     finalize = newStmtList()
     setup = newStmtList()
@@ -587,7 +588,7 @@ macro routes*(server: Server, body: untyped = newStmtList()): untyped =
 
   when enableHttpx or enableHttpBeast:
     var path = newCall("decodeUrl", newNimNode(nnkBracketExpr).add(
-      newCall("split", newCall("get", newCall("path", ident"req")), newLit"?"),
+      newCall("split", newCall("get", newCall("path", ident"req")), newLit('?')),
       newLit(0)
     ))
     let
@@ -1085,18 +1086,22 @@ macro routes*(server: Server, body: untyped = newStmtList()): untyped =
       "handleJvmRequest", ident"self", ident"req", ident"urlPath"
     ))
 
-  when not (exportPython or exportJvm or defined(napibuild)):
-    var returnStmt = newStmtList(newNimNode(nnkReturnStmt).add(newLit""))
-    detectReturnStmt(returnStmt)
-    methodTable.mgetOrPut(
-      "OPTIONS", newNimNode(nnkIfStmt)
-    ).add(exportRouteArgs(pathIdent, newLit"/{p:path}", returnStmt))
+  # when not (exportPython or exportJvm or defined(napibuild)):
+  #   var returnStmt = newStmtList(newNimNode(nnkReturnStmt).add(newLit""))
+  #   detectReturnStmt(returnStmt)
+  #   methodTable.mgetOrPut(
+  #     "OPTIONS", newNimNode(nnkIfStmt)
+  #   ).add(exportRouteArgs(pathIdent, newLit"/{p:path}", returnStmt))
 
   for key in methodTable.keys():
     caseRequestMethodsStmt.add(newNimNode(nnkOfBranch).add(
       newLit(parseEnum[HttpMethod](key)),
       methodTable[key]
     ))
+    # caseRequestMethodsStmt.add(newNimNode(nnkElifBranch).add(
+    #   newCall("==", reqMethod, newLit(parseEnum[HttpMethod](key))),
+    #   methodTable[key]
+    # ))
   
   for ifBranch in staticDirs:
     methodTable.mgetOrPut("GET", newNimNode(nnkIfStmt)).add(ifBranch)
@@ -1151,30 +1156,42 @@ macro routes*(server: Server, body: untyped = newStmtList()): untyped =
     else:
       newEmptyNode(),
     setup,
-    newProc(
-      ident"__wsError",
-      [newEmptyNode(), newIdentDefs(wsClientI, wsType)],
-      wsError,
-      nnkTemplateDef
-    ),
-    newProc(
-      ident"__wsClosed",
-      [newEmptyNode(), newIdentDefs(wsClientI, wsType)],
-      wsClosedConnection,
-      nnkTemplateDef
-    ),
-    newProc(
-      ident"__wsConnect",
-      [newEmptyNode(), newIdentDefs(wsClientI, wsType)],
-      wsNewConnection,
-      nnkTemplateDef
-    ),
-    newProc(
-      ident"__wsMismatchProtocol",
-      [newEmptyNode(), newIdentDefs(wsClientI, wsType)],
-      wsMismatchProtocol,
-      nnkTemplateDef
-    ),
+    if stmtList.isIdentUsed(ident"__wsError"):
+      newProc(
+        ident"__wsError",
+        [newEmptyNode(), newIdentDefs(wsClientI, wsType)],
+        wsError,
+        nnkTemplateDef
+      )
+    else:
+      newEmptyNode(),
+    if stmtList.isIdentUsed(ident"__wsClosed"):
+      newProc(
+        ident"__wsClosed",
+        [newEmptyNode(), newIdentDefs(wsClientI, wsType)],
+        wsClosedConnection,
+        nnkTemplateDef
+      )
+    else:
+      newEmptyNode(),
+    if stmtList.isIdentUsed(ident"__wsConnect"):
+      newProc(
+        ident"__wsConnect",
+        [newEmptyNode(), newIdentDefs(wsClientI, wsType)],
+        wsNewConnection,
+        nnkTemplateDef
+      )
+    else:
+      newEmptyNode(),
+    if stmtList.isIdentUsed(ident"__wsMismatchProtocol"):
+      newProc(
+        ident"__wsMismatchProtocol",
+        [newEmptyNode(), newIdentDefs(wsClientI, wsType)],
+        wsMismatchProtocol,
+        nnkTemplateDef
+      )
+    else:
+      newEmptyNode(),
     procStmt,
     newProc(
       ident"finalizeProgram",


Test patch:
diff --git a/bindings/python/test1.py b/bindings/python/test1.py
index 4b1f1cbf..9d82bab4 100644
--- a/bindings/python/test1.py
+++ b/bindings/python/test1.py
@@ -117,9 +117,10 @@ def on_not_found():
 
 @app.middleware
 def on_not_found(req: HttpRequest):
-    print(f"Middleware handled path at [{req.http_method()}]:", req.path())
-    print(f"Middleware detect these headers: {req.headers()}")
-    print(f"Middleware detect this body: {req.body()}")
+    # print(f"Middleware handled path at [{req.http_method()}]:", req.path())
+    # print(f"Middleware detect these headers: {req.headers()}")
+    # print(f"Middleware detect this body: {req.body()}")
+    pass
 
 
 app.start()
diff --git a/tests/testjs18.nim b/tests/testjs18.nim
index f3ceddea..63c40710 100644
--- a/tests/testjs18.nim
+++ b/tests/testjs18.nim
@@ -1,7 +1,7 @@
 import ../src/happyx
 import std/asyncjs
 
-let htmlProcs = thunkHtmls:
+let htmlProcs = lazyHtmls:
   html:
     tSpan: "front"
   html:
@@ -104,10 +104,10 @@ appRoutes("app"):
       tDiv(id="trial2", class="no-print", style="color: red"):
         "hello again"
   "/issues/303":
-    "counts as thunks:"
+    "counts as lazies:"
     for i in 1..5:
       tDiv: countFactory()
-    "counts as components in thunks:"
+    "counts as components in lazies:"
     nim: counter = 0
     for i in 1..5:
       for j in 1..2:
diff --git a/tests/testjs2.nim b/tests/testjs2.nim
index 4853b434..822c9749 100644
--- a/tests/testjs2.nim
+++ b/tests/testjs2.nim
@@ -11,24 +11,35 @@ var d = t.Node
 echo d.TagRef.onlyChildren
 echo cast[TagRef](d).onlyChildren
 
+var lazyTest = buildHtml:
+  lazy:
+    "Hello, world!"
+    lazy:
+      "Other lazy"
+
 app.routes:
   "/":
-    component HelloWorld(counter = 1.0)
-    component HelloWorld(counter = 2.0)
-    component HelloWorld(counter = 4.0)
-    component HelloWorld(counter = 8.0)
-    component HelloWorld(counter = 16.0)
+    HelloWorld(counter = 1.0)
+    HelloWorld(counter = 2.0)
+    HelloWorld(counter = 4.0)
+    HelloWorld(counter = 8.0)
+    HelloWorld(counter = 16.0)
     tagTextarea(style = "font-weight: 500; margin-bottom: 10px;"):
       "Hello, world"
+    lazy:
+      "Hello, world!"
+      lazy:
+        "Other lazy"
+      {$lazyTest}
   
   "/slots":
-    component CompWithSlot:
+    CompWithSlot:
       "hello, world!"
-    component CompWithSlot(counter = 100):
+    CompWithSlot(counter = 100):
       "Hello"
   
   "/nested":
-    component NestedComponent2
+    NestedComponent2
 
   "/visit":
     script(src="https://cdn.tailwindcss.com")  # Tailwind CSS :D
@@ -75,7 +86,7 @@ app.routes:
           {i}
           @click:
             echo i
-      component ComponentFor(counter = 5)
+      ComponentFor(counter = 5)
       {someArg}
   
   "/asd":