summary refs log tree commit diff stats
path: root/lib/js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/js')
-rw-r--r--lib/js/asyncjs.nim171
-rw-r--r--lib/js/dom.nim284
-rw-r--r--lib/js/dom_extensions.nim5
-rw-r--r--lib/js/jsconsole.nim27
-rw-r--r--lib/js/jscore.nim61
-rw-r--r--lib/js/jsffi.nim321
-rw-r--r--lib/js/jsre.nim115
7 files changed, 656 insertions, 328 deletions
diff --git a/lib/js/asyncjs.nim b/lib/js/asyncjs.nim
index 219b1bed5..9b043f3e5 100644
--- a/lib/js/asyncjs.nim
+++ b/lib/js/asyncjs.nim
@@ -11,43 +11,48 @@
 ## and libraries, writing async procedures in Nim and converting callback-based code
 ## to promises.
 ##
-## A Nim procedure is asynchronous when it includes the ``{.async.}`` pragma. It
-## should always have a ``Future[T]`` return type or not have a return type at all.
-## A ``Future[void]`` return type is assumed by default.
+## A Nim procedure is asynchronous when it includes the `{.async.}` pragma. It
+## should always have a `Future[T]` return type or not have a return type at all.
+## A `Future[void]` return type is assumed by default.
 ##
-## This is roughly equivalent to the ``async`` keyword in JavaScript code.
+## This is roughly equivalent to the `async` keyword in JavaScript code.
 ##
-## .. code-block:: nim
-##  proc loadGame(name: string): Future[Game] {.async.} =
-##    # code
+##   ```nim
+##   proc loadGame(name: string): Future[Game] {.async.} =
+##     # code
+##   ```
 ##
 ## should be equivalent to
 ##
-## .. code-block:: javascript
+##   ```javascript
 ##   async function loadGame(name) {
 ##     // code
 ##   }
+##   ```
 ##
-## A call to an asynchronous procedure usually needs ``await`` to wait for
-## the completion of the ``Future``.
+## A call to an asynchronous procedure usually needs `await` to wait for
+## the completion of the `Future`.
 ##
-## .. code-block:: nim
+##   ```nim
 ##   var game = await loadGame(name)
+##   ```
 ##
 ## Often, you might work with callback-based API-s. You can wrap them with
-## asynchronous procedures using promises and ``newPromise``:
+## asynchronous procedures using promises and `newPromise`:
 ##
-## .. code-block:: nim
+##   ```nim
 ##   proc loadGame(name: string): Future[Game] =
 ##     var promise = newPromise() do (resolve: proc(response: Game)):
 ##       cbBasedLoadGame(name) do (game: Game):
 ##         resolve(game)
 ##     return promise
+##   ```
 ##
-## Forward definitions work properly, you just need to always add the ``{.async.}`` pragma:
+## Forward definitions work properly, you just need to always add the `{.async.}` pragma:
 ##
-## .. code-block:: nim
+##   ```nim
 ##   proc loadGame(name: string): Future[Game] {.async.}
+##   ```
 ##
 ## JavaScript compatibility
 ## ========================
@@ -57,19 +62,22 @@
 ## If you need to use this module with older versions of JavaScript, you can
 ## use a tool that backports the resulting JavaScript code, as babel.
 
-import jsffi
-import macros
+# xxx code: javascript above gives `LanguageXNotSupported` warning.
 
-when not defined(js) and not defined(nimdoc) and not defined(nimsuggest):
+when not defined(js) and not defined(nimsuggest):
   {.fatal: "Module asyncjs is designed to be used with the JavaScript backend.".}
 
+import std/jsffi
+import std/macros
+import std/private/since
+
 type
   Future*[T] = ref object
     future*: T
   ## Wraps the return type of an asynchronous procedure.
 
-  PromiseJs* {.importcpp: "Promise".} = ref object
-  ## A JavaScript Promise
+  PromiseJs* {.importjs: "Promise".} = ref object
+  ## A JavaScript Promise.
 
 
 proc replaceReturn(node: var NimNode) =
@@ -82,6 +90,8 @@ proc replaceReturn(node: var NimNode) =
       node[z] = nnkReturnStmt.newTree(value)
     elif son.kind == nnkAsgn and son[0].kind == nnkIdent and $son[0] == "result":
       node[z] = nnkAsgn.newTree(son[0], nnkCall.newTree(jsResolve, son[1]))
+    elif son.kind in RoutineNodes:
+      discard
     else:
       replaceReturn(son)
     inc z
@@ -92,10 +102,18 @@ proc isFutureVoid(node: NimNode): bool =
            node[1].kind == nnkIdent and $node[1] == "void"
 
 proc generateJsasync(arg: NimNode): NimNode =
-  if arg.kind notin {nnkProcDef, nnkLambda, nnkMethodDef, nnkDo}:
+  if arg.kind notin {nnkProcDef, nnkLambda, nnkMethodDef, nnkDo, nnkProcTy}:
       error("Cannot transform this node kind into an async proc." &
             " proc/method definition or lambda node expected.")
 
+  # Transform type X = proc (): something {.async.}
+  # into      type X = proc (): Future[something]
+  if arg.kind == nnkProcTy:
+    result = arg
+    if arg[0][0].kind == nnkEmpty:
+      result[0][0] = quote do: Future[void]
+    return result
+
   result = arg
   var isVoid = false
   let jsResolve = ident("jsResolve")
@@ -111,16 +129,17 @@ proc generateJsasync(arg: NimNode): NimNode =
 
   if len(code) > 0:
     var awaitFunction = quote:
-      proc await[T](f: Future[T]): T {.importcpp: "(await #)", used.}
+      proc await[T](f: Future[T]): T {.importjs: "(await #)", used.}
     result.body.add(awaitFunction)
 
     var resolve: NimNode
     if isVoid:
       resolve = quote:
-        var `jsResolve` {.importcpp: "undefined".}: Future[void]
+        var `jsResolve` {.importjs: "undefined".}: Future[void]
     else:
       resolve = quote:
-        proc jsResolve[T](a: T): Future[T] {.importcpp: "#", used.}
+        proc jsResolve[T](a: T): Future[T] {.importjs: "#", used.}
+        proc jsResolve[T](a: Future[T]): Future[T] {.importjs: "#", used.}
     result.body.add(resolve)
   else:
     result.body = newEmptyNode()
@@ -139,7 +158,7 @@ proc generateJsasync(arg: NimNode): NimNode =
 
 macro async*(arg: untyped): untyped =
   ## Macro which converts normal procedures into
-  ## javascript-compatible async procedures
+  ## javascript-compatible async procedures.
   if arg.kind == nnkStmtList:
     result = newStmtList()
     for oneProc in arg:
@@ -147,10 +166,104 @@ macro async*(arg: untyped): untyped =
   else:
     result = generateJsasync(arg)
 
-proc newPromise*[T](handler: proc(resolve: proc(response: T))): Future[T] {.importcpp: "(new Promise(#))".}
+proc newPromise*[T](handler: proc(resolve: proc(response: T))): Future[T] {.importjs: "(new Promise(#))".}
   ## A helper for wrapping callback-based functions
-  ## into promises and async procedures
+  ## into promises and async procedures.
 
-proc newPromise*(handler: proc(resolve: proc())): Future[void] {.importcpp: "(new Promise(#))".}
+proc newPromise*(handler: proc(resolve: proc())): Future[void] {.importjs: "(new Promise(#))".}
   ## A helper for wrapping callback-based functions
-  ## into promises and async procedures
+  ## into promises and async procedures.
+
+template maybeFuture(T): untyped =
+  # avoids `Future[Future[T]]`
+  when T is Future: T
+  else: Future[T]
+
+
+since (1, 5, 1):
+  #[
+  TODO:
+  * map `Promise.all()`
+  * proc toString*(a: Error): cstring {.importjs: "#.toString()".}
+
+  Note:
+  We probably can't have a `waitFor` in js in browser (single threaded), but maybe it would be possible
+  in in nodejs, see https://nodejs.org/api/child_process.html#child_process_child_process_execsync_command_options
+  and https://stackoverflow.com/questions/61377358/javascript-wait-for-async-call-to-finish-before-returning-from-function-witho
+  ]#
+
+  type Error*  {.importjs: "Error".} = ref object of JsRoot
+    ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
+    message*: cstring
+    name*: cstring
+
+  type OnReject* = proc(reason: Error)
+
+  proc then*[T](future: Future[T], onSuccess: proc, onReject: OnReject = nil): auto =
+    ## See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
+    ## Returns a `Future` from the return type of `onSuccess(T.default)`.
+    runnableExamples("-r:off"):
+      from std/sugar import `=>`
+
+      proc fn(n: int): Future[int] {.async.} =
+        if n >= 7: raise newException(ValueError, "foobar: " & $n)
+        else: result = n * 2
+
+      proc asyncFact(n: int): Future[int] {.async.} =
+        if n > 0: result = n * await asyncFact(n-1)
+        else: result = 1
+
+      proc main() {.async.} =
+        block: # then
+          assert asyncFact(3).await == 3*2
+          assert asyncFact(3).then(asyncFact).await == 6*5*4*3*2
+          let x1 = await fn(3)
+          assert x1 == 3 * 2
+          let x2 = await fn(4)
+            .then((a: int) => a.float)
+            .then((a: float) => $a)
+          assert x2 == "8.0"
+
+        block: # then with `onReject` callback
+          var witness = 1
+          await fn(6).then((a: int) => (witness = 2), (r: Error) => (witness = 3))
+          assert witness == 2
+          await fn(7).then((a: int) => (witness = 2), (r: Error) => (witness = 3))
+          assert witness == 3
+
+    template impl(call): untyped =
+      # see D20210421T014713
+      when typeof(block: call) is void:
+        var ret: Future[void]
+      else:
+        var ret = default(maybeFuture(typeof(call)))
+      typeof(ret)
+    when T is void:
+      type A = impl(onSuccess())
+    else:
+      type A = impl(onSuccess(default(T)))
+    var ret: A
+    {.emit: "`ret` = `future`.then(`onSuccess`, `onReject`);".}
+    return ret
+
+  proc catch*[T](future: Future[T], onReject: OnReject): Future[void] =
+    ## See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
+    runnableExamples("-r:off"):
+      from std/sugar import `=>`
+      from std/strutils import contains
+
+      proc fn(n: int): Future[int] {.async.} =
+        if n >= 7: raise newException(ValueError, "foobar: " & $n)
+        else: result = n * 2
+
+      proc main() {.async.} =
+        var reason: Error
+        await fn(6).catch((r: Error) => (reason = r)) # note: `()` are needed, `=> reason = r` would not work
+        assert reason == nil
+        await fn(7).catch((r: Error) => (reason = r))
+        assert reason != nil
+        assert  "foobar: 7" in $reason.message
+
+      discard main()
+
+    {.emit: "`result` = `future`.catch(`onReject`);".}
diff --git a/lib/js/dom.nim b/lib/js/dom.nim
index d4e8ce86a..be2a34db1 100644
--- a/lib/js/dom.nim
+++ b/lib/js/dom.nim
@@ -9,16 +9,46 @@
 
 ## Declaration of the Document Object Model for the `JavaScript backend
 ## <backends.html#backends-the-javascript-target>`_.
+##
+##
+## Document Ready
+## --------------
+##
+## * Basic example of a document ready:
+runnableExamples"-b:js -r:off":
+  proc example(e: Event) = echo "Document is ready"
+  document.addEventListener("DOMContentLoaded", example)  # You can also use "load" event.
+## * This example runs 5 seconds after the document ready:
+runnableExamples"-b:js -r:off":
+  proc example() = echo "5 seconds after document ready"
+  proc domReady(e: Event) = discard setTimeout(example, 5_000) # Document is ready.
+  document.addEventListener("DOMContentLoaded", domReady)
+## Document onUnload
+## -----------------
+##
+## * Simple example of how to implement code that runs when the page unloads:
+runnableExamples"-b:js -r:off":
+  proc example(e: Event) = echo "Document is unloaded"
+  document.addEventListener("unload", example)  # You can also use "beforeunload".
+## Document Autorefresh
+## --------------------
+##
+## * Minimal example of a document autorefresh:
+runnableExamples"-b:js -r:off":
+  proc example() = window.location.reload()
+  discard setTimeout(example, 5_000)
+## - For more examples, see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
+
+
 import std/private/since
-when not defined(js) and not defined(Nimdoc):
+when not defined(js):
   {.error: "This module only works on the JavaScript platform".}
 
 const
   DomApiVersion* = 3 ## the version of DOM API we try to follow. No guarantees though.
 
 type
-  EventTarget* = ref EventTargetObj
-  EventTargetObj {.importc.} = object of RootObj
+  EventTarget* {.importc.} = ref object of RootObj
     onabort*: proc (event: Event) {.closure.}
     onblur*: proc (event: Event) {.closure.}
     onchange*: proc (event: Event) {.closure.}
@@ -37,6 +67,7 @@ type
     onmouseup*: proc (event: Event) {.closure.}
     onreset*: proc (event: Event) {.closure.}
     onselect*: proc (event: Event) {.closure.}
+    onstorage*: proc (event: Event) {.closure.}
     onsubmit*: proc (event: Event) {.closure.}
     onunload*: proc (event: Event) {.closure.}
     onloadstart*: proc (event: Event) {.closure.}
@@ -72,6 +103,7 @@ type
     Resize = "resize",
     Scroll = "scroll",
     Select = "select",
+    Storage = "storage",
     Unload = "unload",
     Wheel = "wheel"
 
@@ -125,10 +157,9 @@ type
     rangeCount*: int
     `type`*: cstring
 
-  LocalStorage* {.importc.} = ref object
+  Storage* {.importc.} = ref object
 
-  Window* = ref WindowObj
-  WindowObj {.importc.} = object of EventTargetObj
+  Window* {.importc.} = ref object of EventTarget
     document*: Document
     event*: Event
     history*: History
@@ -153,14 +184,13 @@ type
     screen*: Screen
     performance*: Performance
     onpopstate*: proc (event: Event)
-    localStorage*: LocalStorage
+    localStorage*: Storage
+    sessionStorage*: Storage
     parent*: Window
 
-  Frame* = ref FrameObj
-  FrameObj {.importc.} = object of WindowObj
+  Frame* {.importc.} = ref object of Window
 
-  ClassList* = ref ClassListObj
-  ClassListObj {.importc.} = object of RootObj
+  ClassList* {.importc.} = ref object of RootObj
 
   NodeType* = enum
     ElementNode = 1,
@@ -176,8 +206,7 @@ type
     DocumentFragmentNode,
     NotationNode
 
-  Node* = ref NodeObj
-  NodeObj {.importc.} = object of EventTargetObj
+  Node* {.importc.} = ref object of EventTarget
     attributes*: seq[Node]
     childNodes*: seq[Node]
     children*: seq[Node]
@@ -201,8 +230,7 @@ type
     parentElement*: Element
     isConnected*: bool
 
-  Document* = ref DocumentObj
-  DocumentObj {.importc.} = object of NodeObj
+  Document* {.importc.} = ref object of Node
     activeElement*: Element
     documentElement*: Element
     alinkColor*: cstring
@@ -213,11 +241,13 @@ type
     defaultCharset*: cstring
     fgColor*: cstring
     head*: Element
+    hidden*: bool
     lastModified*: cstring
     linkColor*: cstring
     referrer*: cstring
     title*: cstring
     URL*: cstring
+    visibilityState*: cstring
     vlinkColor*: cstring
     anchors*: seq[AnchorElement]
     forms*: seq[FormElement]
@@ -227,8 +257,7 @@ type
     links*: seq[LinkElement]
     fonts*: FontFaceSet
 
-  Element* = ref ElementObj
-  ElementObj {.importc.} = object of NodeObj
+  Element* {.importc.} = ref object of Node
     className*: cstring
     classList*: ClassList
     checked*: bool
@@ -249,8 +278,7 @@ type
     offsetLeft*: int
     offsetTop*: int
 
-  ValidityState* = ref ValidityStateObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/ValidityState>`_
-  ValidityStateObj {.importc.} = object
+  ValidityState* {.importc.} = ref object ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/ValidityState>`_
     badInput*: bool
     customError*: bool
     patternMismatch*: bool
@@ -263,25 +291,21 @@ type
     valid*: bool
     valueMissing*: bool
 
-  Blob* = ref BlobObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/Blob>`_
-  BlobObj {.importc.} = object of RootObj
+  Blob* {.importc.} = ref object of RootObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/Blob>`_
     size*: int
     `type`*: cstring
 
-  File* = ref FileObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/File>`_
-  FileObj {.importc.} = object of Blob
+  File* {.importc.} = ref object of Blob ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/File>`_
     lastModified*: int
     name*: cstring
 
-  TextAreaElement* = ref TextAreaElementObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement>`_
-  TextAreaElementObj {.importc.} = object of Element
+  TextAreaElement* {.importc.} = ref object of Element ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement>`_
     value*: cstring
     selectionStart*, selectionEnd*: int
     selectionDirection*: cstring
     rows*, cols*: int
 
-  InputElement* = ref InputElementObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement>`_
-  InputElementObj {.importc.} = object of Element
+  InputElement* {.importc.} = ref object of Element ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement>`_
     # Properties related to the parent form
     formAction*: cstring
     formEncType*: cstring
@@ -333,15 +357,13 @@ type
     valueAsDate*: cstring
     valueAsNumber*: float
 
-  LinkElement* = ref LinkObj
-  LinkObj {.importc.} = object of ElementObj
+  LinkElement* {.importc.} = ref object of Element
     target*: cstring
     text*: cstring
     x*: int
     y*: int
 
-  EmbedElement* = ref EmbedObj
-  EmbedObj {.importc.} = object of ElementObj
+  EmbedElement* {.importc.} = ref object of Element
     height*: int
     hspace*: int
     src*: cstring
@@ -349,21 +371,18 @@ type
     `type`*: cstring
     vspace*: int
 
-  AnchorElement* = ref AnchorObj
-  AnchorObj {.importc.} = object of ElementObj
+  AnchorElement* {.importc.} = ref object of Element
     text*: cstring
     x*, y*: int
 
-  OptionElement* = ref OptionObj
-  OptionObj {.importc.} = object of ElementObj
+  OptionElement* {.importc.} = ref object of Element
     defaultSelected*: bool
     selected*: bool
     selectedIndex*: int
     text*: cstring
     value*: cstring
 
-  FormElement* = ref FormObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement>`_
-  FormObj {.importc.} = object of ElementObj
+  FormElement* {.importc.} = ref object of Element ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement>`_
     acceptCharset*: cstring
     action*: cstring
     autocomplete*: cstring
@@ -375,8 +394,7 @@ type
     noValidate*: bool
     target*: cstring
 
-  ImageElement* = ref ImageObj
-  ImageObj {.importc.} = object of ElementObj
+  ImageElement* {.importc.} = ref object of Element
     border*: int
     complete*: bool
     height*: int
@@ -386,8 +404,7 @@ type
     vspace*: int
     width*: int
 
-  Style* = ref StyleObj
-  StyleObj {.importc.} = object of RootObj
+  Style* {.importc.} = ref object of RootObj
     alignContent*: cstring
     alignItems*: cstring
     alignSelf*: cstring
@@ -763,8 +780,7 @@ type
     AtTarget,
     BubblingPhase
 
-  Event* = ref EventObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/Event>`_
-  EventObj {.importc.} = object of RootObj
+  Event* {.importc.} = ref object of RootObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/Event>`_
     bubbles*: bool
     cancelBubble*: bool
     cancelable*: bool
@@ -776,13 +792,11 @@ type
     `type`*: cstring
     isTrusted*: bool
 
-  UIEvent* = ref UIEventObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/UIEvent>`_
-  UIEventObj {.importc.} = object of Event
+  UIEvent* {.importc.} = ref object of Event ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/UIEvent>`_
     detail*: int64
     view*: Window
 
-  KeyboardEvent* = ref KeyboardEventObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent>`_
-  KeyboardEventObj {.importc.} = object of UIEvent
+  KeyboardEvent* {.importc.} = ref object of UIEvent ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent>`_
     altKey*, ctrlKey*, metaKey*, shiftKey*: bool
     code*: cstring
     isComposing*: bool
@@ -1144,8 +1158,7 @@ type
     FourthButton = 8,
     FifthButton = 16
 
-  MouseEvent* = ref MouseEventObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent>`_
-  MouseEventObj {.importc.} = object of UIEvent
+  MouseEvent* {.importc.} = ref object of UIEvent ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent>`_
     altKey*, ctrlKey*, metaKey*, shiftKey*: bool
     button*: int
     buttons*: int
@@ -1162,13 +1175,11 @@ type
     File = "file",
     String = "string"
 
-  DataTransferItem* = ref DataTransferItemObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem>`_
-  DataTransferItemObj {.importc.} = object of RootObj
+  DataTransferItem* {.importc.} = ref object of RootObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem>`_
     kind*: cstring
     `type`*: cstring
 
-  DataTransfer* = ref DataTransferObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer>`_
-  DataTransferObj {.importc.} = object of RootObj
+  DataTransfer* {.importc.} = ref object of RootObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer>`_
     dropEffect*: cstring
     effectAllowed*: cstring
     files*: seq[Element]
@@ -1210,11 +1221,16 @@ type
     ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent>`_
     clipboardData*: DataTransfer
 
+  StorageEvent* {.importc.} = ref object of Event ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/StorageEvent>`_
+    key*: cstring
+    newValue*, oldValue*: cstring
+    storageArea*: Storage
+    url*: cstring
+
   TouchList* {.importc.} = ref object of RootObj
     length*: int
 
-  Touch* = ref TouchObj
-  TouchObj {.importc.} = object of RootObj
+  Touch* {.importc.} = ref object of RootObj
     identifier*: int
     screenX*, screenY*, clientX*, clientY*, pageX*, pageY*: int
     target*: Element
@@ -1222,12 +1238,10 @@ type
     rotationAngle*: int
     force*: float
 
-  TouchEvent* = ref TouchEventObj
-  TouchEventObj {.importc.} = object of UIEvent
+  TouchEvent* {.importc.} = ref object of UIEvent
     changedTouches*, targetTouches*, touches*: seq[Touch]
 
-  Location* = ref LocationObj
-  LocationObj {.importc.} = object of RootObj
+  Location* {.importc.} = ref object of RootObj
     hash*: cstring
     host*: cstring
     hostname*: cstring
@@ -1238,12 +1252,10 @@ type
     search*: cstring
     origin*: cstring
 
-  History* = ref HistoryObj
-  HistoryObj {.importc.} = object of RootObj
+  History* {.importc.} = ref object of RootObj
     length*: int
 
-  Navigator* = ref NavigatorObj
-  NavigatorObj {.importc.} = object of RootObj
+  Navigator* {.importc.} = ref object of RootObj
     appCodeName*: cstring
     appName*: cstring
     appVersion*: cstring
@@ -1281,8 +1293,7 @@ type
   ToolBar* = LocationBar
   StatusBar* = LocationBar
 
-  Screen = ref ScreenObj
-  ScreenObj {.importc.} = object of RootObj
+  Screen* {.importc.} = ref object of RootObj
     availHeight*: int
     availWidth*: int
     colorDepth*: int
@@ -1291,7 +1302,7 @@ type
     width*: int
 
   TimeOut* {.importc.} = ref object of RootObj
-  Interval* {.importc.} = object of RootObj
+  Interval* {.importc.} = ref object of RootObj
 
   AddEventListenerOptions* = object
     capture*: bool
@@ -1307,41 +1318,46 @@ type
     ready*: FontFaceSetReady
     onloadingdone*: proc(event: Event)
 
+  ScrollIntoViewOptions* = object
+    behavior*: cstring
+    `block`*: cstring
+    inline*: cstring
+
+  MediaQueryList* {.importc.} = ref object of EventTarget
+    matches*: bool
+    media*: cstring
+
 since (1, 3):
   type
     DomParser* = ref object
       ## DOM Parser object (defined on browser only, may not be on NodeJS).
       ## * https://developer.mozilla.org/en-US/docs/Web/API/DOMParser
       ##
-      ## .. code-block:: nim
+      ##   ```nim
       ##   let prsr = newDomParser()
       ##   discard prsr.parseFromString("<html><marquee>Hello World</marquee></html>".cstring, "text/html".cstring)
+      ##   ```
 
-    DomException* = ref DOMExceptionObj
+    DomException* {.importc.} = ref object
       ## The DOMException interface represents an abnormal event (called an exception)
       ## which occurs as a result of calling a method or accessing a property of a web API.
       ## Each exception has a name, which is a short "CamelCase" style string identifying
       ## the error or abnormal condition.
       ## https://developer.mozilla.org/en-US/docs/Web/API/DOMException
 
-    DOMExceptionObj {.importc.} = object
-
-    FileReader* = ref FileReaderObj
+    FileReader* {.importc.} = ref object of EventTarget
       ## The FileReader object lets web applications asynchronously read the contents of files
       ## (or raw data buffers) stored on the user's computer, using File or Blob objects to specify
       ## the file or data to read.
       ## https://developer.mozilla.org/en-US/docs/Web/API/FileReader
 
-    FileReaderObj {.importc.} = object of EventTargetObj
-
     FileReaderState* = distinct range[0'u16..2'u16]
     RootNodeOptions* = object of RootObj
       composed*: bool
     DocumentOrShadowRoot* {.importc.} = object of RootObj
       activeElement*: Element
       # styleSheets*: StyleSheetList
-    ShadowRoot* = ref ShadowRootObj
-    ShadowRootObj {.importc.} = object of DocumentOrShadowRoot
+    ShadowRoot* {.importc.} = ref object of DocumentOrShadowRoot
       delegatesFocus*: bool
       host*: Element
       innerHTML*: cstring
@@ -1350,8 +1366,7 @@ since (1, 3):
       mode*: cstring
       delegatesFocus*: bool
 
-    HTMLSlotElement* = ref HTMLSlotElementObj
-    HTMLSlotElementObj {.importc.} = object of RootObj
+    HTMLSlotElement* {.importc.} = ref object of RootObj
       name*: cstring
     SlotOptions* = object of RootObj
       flatten*: bool
@@ -1369,6 +1384,9 @@ proc `class=`*(n: Node; v: cstring) {.importcpp: "#.className = #", nodecl.}
 proc value*(n: Node): cstring {.importcpp: "#.value", nodecl.}
 proc `value=`*(n: Node; v: cstring) {.importcpp: "#.value = #", nodecl.}
 
+proc checked*(n: Node): bool {.importcpp: "#.checked", nodecl.}
+proc `checked=`*(n: Node; v: bool) {.importcpp: "#.checked = #", nodecl.}
+
 proc `disabled=`*(n: Node; v: bool) {.importcpp: "#.disabled = #", nodecl.}
 
 when defined(nodejs):
@@ -1405,7 +1423,7 @@ when defined(nodejs):
         parent.childNodes[i] = newNode
         return
       inc i
-    doAssert false, "old node not in node list"
+    raiseAssert "old node not in node list"
 
   proc removeChild*(parent, child: Node) =
     child.parentNode = nil
@@ -1415,7 +1433,7 @@ when defined(nodejs):
         parent.childNodes.delete(i)
         return
       inc i
-    doAssert false, "old node not in node list"
+    raiseAssert "old node not in node list"
 
   proc insertBefore*(parent, newNode, before: Node) =
     appendChild(parent, newNode)
@@ -1427,7 +1445,7 @@ when defined(nodejs):
         parent.childNodes[i-1] = newNode
         return
       inc i
-    #doAssert false, "before not in node list"
+    #raiseAssert "before not in node list"
 
   proc createElement*(d: Document, identifier: cstring): Element =
     new(result)
@@ -1457,11 +1475,14 @@ else:
   proc insertBefore*(n, newNode, before: Node) {.importcpp.}
   proc getElementById*(d: Document, id: cstring): Element {.importcpp.}
   proc createElement*(d: Document, identifier: cstring): Element {.importcpp.}
+  proc createElementNS*(d: Document, namespaceURI, qualifiedIdentifier: cstring): Element {.importcpp.}
   proc createTextNode*(d: Document, identifier: cstring): Node {.importcpp.}
   proc createComment*(d: Document, data: cstring): Node {.importcpp.}
 
 proc setTimeout*(action: proc(); ms: int): TimeOut {.importc, nodecl.}
 proc clearTimeout*(t: TimeOut) {.importc, nodecl.}
+proc setInterval*(action: proc(); ms: int): Interval {.importc, nodecl.}
+proc clearInterval*(i: Interval) {.importc, nodecl.}
 
 {.push importcpp.}
 
@@ -1475,17 +1496,19 @@ proc removeEventListener*(et: EventTarget; ev: cstring; cb: proc(ev: Event))
 proc alert*(w: Window, msg: cstring)
 proc back*(w: Window)
 proc blur*(w: Window)
-proc clearInterval*(w: Window, interval: ref Interval)
-proc clearTimeout*(w: Window, timeout: ref TimeOut)
+proc clearInterval*(w: Window, interval: Interval)
+proc clearTimeout*(w: Window, timeout: TimeOut)
 proc close*(w: Window)
 proc confirm*(w: Window, msg: cstring): bool
 proc disableExternalCapture*(w: Window)
 proc enableExternalCapture*(w: Window)
 proc find*(w: Window, text: cstring, caseSensitive = false,
-           backwards = false)
+           backwards = false): bool
 proc focus*(w: Window)
 proc forward*(w: Window)
-proc getComputedStyle*(w: Window, e: Node, pe:Node = nil): Style
+proc getComputedStyle*(w: Window, e: Node, pe: Node = nil): Style
+  ## .. warning:: The returned Style may or may not be read-only at run-time in the browser. getComputedStyle is performance costly.
+
 proc handleEvent*(w: Window, e: Event)
 proc home*(w: Window)
 proc moveBy*(w: Window, x, y: int)
@@ -1499,13 +1522,14 @@ proc resizeTo*(w: Window, x, y: int)
 proc routeEvent*(w: Window, event: Event)
 proc scrollBy*(w: Window, x, y: int)
 proc scrollTo*(w: Window, x, y: int)
-proc setInterval*(w: Window, code: cstring, pause: int): ref Interval
-proc setInterval*(w: Window, function: proc (), pause: int): ref Interval
-proc setTimeout*(w: Window, code: cstring, pause: int): ref TimeOut
-proc setTimeout*(w: Window, function: proc (), pause: int): ref Interval
+proc setInterval*(w: Window, code: cstring, pause: int): Interval
+proc setInterval*(w: Window, function: proc (), pause: int): Interval
+proc setTimeout*(w: Window, code: cstring, pause: int): TimeOut
+proc setTimeout*(w: Window, function: proc (), pause: int): Interval
 proc stop*(w: Window)
 proc requestAnimationFrame*(w: Window, function: proc (time: float)): int
 proc cancelAnimationFrame*(w: Window, id: int)
+proc matchMedia*(w: Window, mediaQueryString: cstring): MediaQueryList
 
 # Node "methods"
 proc appendData*(n: Node, data: cstring)
@@ -1522,6 +1546,7 @@ proc removeAttribute*(n: Node, attr: cstring)
 proc removeAttributeNode*(n, attr: Node)
 proc replaceData*(n: Node, start, len: int, text: cstring)
 proc scrollIntoView*(n: Node)
+proc scrollIntoView*(n: Node, options: ScrollIntoViewOptions)
 proc setAttribute*(n: Node, name, value: cstring)
 proc setAttributeNode*(n: Node, attr: Node)
 proc querySelector*(n: Node, selectors: cstring): Element
@@ -1630,7 +1655,7 @@ proc item*(list: TouchList, i: int): Touch
 proc clearData*(dt: DataTransfer, format: cstring)
 proc getData*(dt: DataTransfer, format: cstring): cstring
 proc setData*(dt: DataTransfer, format: cstring, data: cstring)
-proc setDragImage*(dt: DataTransfer, img: Element, xOffset: int64, yOffset: int64)
+proc setDragImage*(dt: DataTransfer, img: Element, xOffset: int, yOffset: int)
 
 # DataTransferItem "methods"
 proc getAsFile*(dti: DataTransferItem): File
@@ -1654,12 +1679,11 @@ proc getRangeAt*(s: Selection, index: int): Range
 converter toString*(s: Selection): cstring
 proc `$`*(s: Selection): string = $(s.toString())
 
-# LocalStorage "methods"
-proc getItem*(ls: LocalStorage, key: cstring): cstring
-proc setItem*(ls: LocalStorage, key, value: cstring)
-proc hasItem*(ls: LocalStorage, key: cstring): bool
-proc clear*(ls: LocalStorage)
-proc removeItem*(ls: LocalStorage, key: cstring)
+# Storage "methods"
+proc getItem*(s: Storage, key: cstring): cstring
+proc setItem*(s: Storage, key, value: cstring)
+proc clear*(s: Storage)
+proc removeItem*(s: Storage, key: cstring)
 
 {.pop.}
 
@@ -1745,5 +1769,73 @@ since (1, 3):
     ## https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsBinaryString
   proc readAsDataURL*(f: FileReader, b: Blob) {.importcpp: "#.readAsDataURL(#)".}
     ## https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL
-  proc readAsText*(f: FileReader, b: Blob, encoding = cstring"UTF-8") {.importcpp: "#.readAsText(#, #)".}
+  proc readAsText*(f: FileReader, b: Blob|File, encoding = cstring"UTF-8") {.importcpp: "#.readAsText(#, #)".}
     ## https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsText
+
+since (1, 5):
+  proc elementsFromPoint*(n: DocumentOrShadowRoot; x, y: float): seq[Element] {.importcpp.}
+
+
+since (1, 7):
+
+  proc insertAdjacentText*(self: Node; position, data: cstring) {.importjs: "#.$1(#, #)".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentText
+
+  proc insertAdjacentElement*(self: Node; position: cstring; element: Node) {.importjs: "#.$1(#, #)".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentElement
+
+  proc insertAdjacentHTML*(self: Node; position, html: cstring) {.importjs: "#.$1(#, #)".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML
+
+  proc after*(self: Node; element: Node): Node {.importjs: "#.$1(@)", varargs.}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/after
+
+  proc before*(self: Node; element: Node): Node {.importjs: "#.$1(@)", varargs.}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/before
+
+  proc append*(self: Node; element: Node): Node {.importjs: "#.$1(@)", varargs.}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/append
+
+  proc closest*(self: Node; cssSelector: cstring): Node {.importjs: "#.$1(#)".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
+
+  proc hasAttributeNS*(self: Node; namespace, localName: cstring): bool {.importjs: "(#.$1(#, #) || false)".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/hasAttributeNS
+
+  proc removeAttributeNS*(self: Node; namespace, attributeName: cstring) {.importjs: "#.$1(#, #)".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttributeNS
+
+  proc hasPointerCapture*(self: Node; pointerId: SomeNumber): bool {.importjs: "(#.$1(#) || false)".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/hasPointerCapture
+
+  proc releasePointerCapture*(self: Node; pointerId: SomeNumber) {.importjs: "#.$1(#)".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/releasePointerCapture
+
+  proc requestPointerLock*(self: Node) {.importjs: "#.$1()".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/requestPointerLock
+
+  proc replaceChildren*(self: Node; replacements: Node) {.importjs: "#.$1(@)", varargs.}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/replaceChildren
+
+  proc replaceWith*(self: Node; replacements: Node) {.importjs: "#.$1(@)", varargs.}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/replaceWith
+
+  proc scrollIntoViewIfNeeded*(self: Node; centerIfNeeded: bool) {.importjs: "#.$1(#)".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoViewIfNeeded
+
+  proc setHTML*(self: Node; html: cstring) {.importjs: "#.$1(#)".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/setHTML
+
+  proc toggleAttribute*(self: Node; name: cstring; force = false): bool {.importjs: "(#.$1(#, #) || false)".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/toggleAttribute
+
+  proc matches*(self: Node; cssSelector: cstring): bool {.importjs: "(#.$1(#) || false)".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/matches
+
+
+since (2, 1):
+  type VisualViewport* {.importc.} = ref object of EventTarget
+    offsetLeft*, offsetTop*, pageLeft*, pageTop*, width*, height*, scale*: float
+    onResize*, onScroll*: proc (event: Event) {.closure.}
+
+  func visualViewport*(self: Window): VisualViewport {.importjs: "#.$1", nodecl.}
diff --git a/lib/js/dom_extensions.nim b/lib/js/dom_extensions.nim
deleted file mode 100644
index 851ec0c5f..000000000
--- a/lib/js/dom_extensions.nim
+++ /dev/null
@@ -1,5 +0,0 @@
-import dom
-
-{.push importcpp.}
-proc elementsFromPoint*(n: DocumentOrShadowRoot; x, y: float): seq[Element]
-{.pop.}
\ No newline at end of file
diff --git a/lib/js/jsconsole.nim b/lib/js/jsconsole.nim
index 5b9893e75..e74127334 100644
--- a/lib/js/jsconsole.nim
+++ b/lib/js/jsconsole.nim
@@ -9,10 +9,22 @@
 
 ## Wrapper for the `console` object for the `JavaScript backend
 ## <backends.html#backends-the-javascript-target>`_.
+##
+## Styled Messages
+## ===============
+##
+## CSS-styled messages in the browser are useful for debugging purposes.
+## To use them, prefix the message with one or more `%c`,
+## and provide the CSS style as the last argument.
+## The amount of `%c`'s must match the amount of CSS-styled strings.
+##
+runnableExamples("-r:off"):
+  console.log "%c My Debug Message", "color: red" # Notice the "%c"
+  console.log "%c My Debug %c Message", "color: red", "font-size: 2em"
 
 import std/private/since, std/private/miscdollars  # toLocation
 
-when not defined(js) and not defined(Nimdoc):
+when not defined(js):
   {.error: "This module only works on the JavaScript platform".}
 
 type Console* = ref object of JsRoot
@@ -75,7 +87,7 @@ since (1, 5):
   func getMsg(info: InstantiationInfo; msg: string): string =
     var temp = ""
     temp.toLocation(info.filename, info.line, info.column + 1)
-    result.addQuoted(temp)
+    result.addQuoted("[jsAssert] " & temp)
     result.add ','
     result.addQuoted(msg)
 
@@ -98,5 +110,16 @@ since (1, 5):
     {.line: loc.}:
       {.emit: ["console.assert(", assertion, ", ", msg, ");"].}
 
+  func dir*(console: Console; obj: auto) {.importcpp.}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Console/dir
+
+  func dirxml*(console: Console; obj: auto) {.importcpp.}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Console/dirxml
+
+  func timeStamp*(console: Console; label: cstring) {.importcpp.}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Console/timeStamp
+    ##
+    ## ..warning:: non-standard
+
 
 var console* {.importc, nodecl.}: Console
diff --git a/lib/js/jscore.nim b/lib/js/jscore.nim
index 2e2bd2402..be353875c 100644
--- a/lib/js/jscore.nim
+++ b/lib/js/jscore.nim
@@ -11,10 +11,11 @@
 ##
 ## Unless your application has very
 ## specific requirements and solely targets JavaScript, you should be using
-## the relevant functions in the ``math``, ``json``, and ``times`` stdlib
+## the relevant functions in the `math`, `json`, and `times` stdlib
 ## modules instead.
+import std/private/[since, jsutils]
 
-when not defined(js) and not defined(Nimdoc):
+when not defined(js):
   {.error: "This module only works on the JavaScript platform".}
 
 type
@@ -73,9 +74,16 @@ proc parse*(d: DateLib, s: cstring): int {.importcpp.}
 proc newDate*(): DateTime {.
   importcpp: "new Date()".}
 
-proc newDate*(date: int|int64|string): DateTime {.
+proc newDate*(date: int|string): DateTime {.
   importcpp: "new Date(#)".}
 
+whenJsNoBigInt64:
+  proc newDate*(date: int64): DateTime {.
+    importcpp: "new Date(#)".}
+do:
+  proc newDate*(date: int64): DateTime {.
+    importcpp: "new Date(Number(#))".}
+
 proc newDate*(year, month, day, hours, minutes,
              seconds, milliseconds: int): DateTime {.
   importcpp: "new Date(#,#,#,#,#,#,#)".}
@@ -87,20 +95,59 @@ proc getMilliseconds*(d: DateTime): int {.importcpp.}
 proc getMinutes*(d: DateTime): int {.importcpp.}
 proc getMonth*(d: DateTime): int {.importcpp.}
 proc getSeconds*(d: DateTime): int {.importcpp.}
-proc getYear*(d: DateTime): int {.importcpp.}
 proc getTime*(d: DateTime): int {.importcpp.}
-proc toString*(d: DateTime): cstring {.importcpp.}
+proc getTimezoneOffset*(d: DateTime): int {.importcpp.}
 proc getUTCDate*(d: DateTime): int {.importcpp.}
+proc getUTCDay*(d: DateTime): int {.importcpp.}
 proc getUTCFullYear*(d: DateTime): int {.importcpp.}
 proc getUTCHours*(d: DateTime): int {.importcpp.}
 proc getUTCMilliseconds*(d: DateTime): int {.importcpp.}
 proc getUTCMinutes*(d: DateTime): int {.importcpp.}
 proc getUTCMonth*(d: DateTime): int {.importcpp.}
 proc getUTCSeconds*(d: DateTime): int {.importcpp.}
-proc getUTCDay*(d: DateTime): int {.importcpp.}
-proc getTimezoneOffset*(d: DateTime): int {.importcpp.}
+proc getYear*(d: DateTime): int {.importcpp.}
+
 proc setFullYear*(d: DateTime, year: int) {.importcpp.}
 
+func toDateString*(d: DateTime): cstring {.importcpp.}
+func toISOString*(d: DateTime): cstring {.importcpp.}
+func toJSON*(d: DateTime): cstring {.importcpp.}
+proc toString*(d: DateTime): cstring {.importcpp.}
+func toTimeString*(d: DateTime): cstring {.importcpp.}
+func toUTCString*(d: DateTime): cstring {.importcpp.}
+
 #JSON library
 proc stringify*(l: JsonLib, s: JsRoot): cstring {.importcpp.}
 proc parse*(l: JsonLib, s: cstring): JsRoot {.importcpp.}
+
+
+since (1, 5):
+  func debugger*() {.importjs: "debugger@".}
+    ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/debugger
+
+  func copyWithin*[T](self: openArray[T]; target: int): seq[T] {.importjs: "#.copyWithin(#)".}
+  func copyWithin*[T](self: openArray[T]; target, start: int): seq[T] {.importjs: "#.copyWithin(#, #)".}
+  func copyWithin*[T](self: openArray[T]; target, start, ends: int): seq[T] {.importjs: "#.copyWithin(#, #, #)".} =
+    ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin
+    ## `copyWithin` uses shallow copy.
+    runnableExamples:
+      assert ['a', 'b', 'c', 'd', 'e'].copyWithin(0, 3, 4) == @['d', 'b', 'c', 'd', 'e']
+      assert ['a', 'b', 'c', 'd', 'e'].copyWithin(1, 3) == @['a', 'd', 'e', 'd', 'e']
+      assert [1, 2, 3, 4, 5].copyWithin(-2) == @[1, 2, 3, 1, 2]
+      assert [1, 2, 3, 4, 5].copyWithin(0, 3) == @[4, 5, 3, 4, 5]
+      assert [1, 2, 3, 4, 5].copyWithin(0, 3, 4) == @[4, 2, 3, 4, 5]
+      assert [1, 2, 3, 4, 5].copyWithin(-2, -3, -1) == @[1, 2, 3, 3, 4]
+
+
+since (1, 7):
+  func shift*[T](self: seq[T]): T {.importjs: "#.$1()".} =
+    ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift
+    runnableExamples:
+      var arrai = @[1, 2, 3]
+      assert arrai.shift() == 1
+      assert arrai == @[2, 3]
+
+  func queueMicrotask*(function: proc) {.importjs: "$1(#)".} =
+    ## * https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask
+    ## * https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide
+    runnableExamples"-r:off": queueMicrotask(proc() = echo "Microtask")
diff --git a/lib/js/jsffi.nim b/lib/js/jsffi.nim
index 9fe934f89..d50d58ae5 100644
--- a/lib/js/jsffi.nim
+++ b/lib/js/jsffi.nim
@@ -8,8 +8,8 @@
 #
 
 ## This Module implements types and macros to facilitate the wrapping of, and
-## interaction with JavaScript libraries. Using the provided types ``JsObject``
-## and ``JsAssoc`` together with the provided macros allows for smoother
+## interaction with JavaScript libraries. Using the provided types `JsObject`
+## and `JsAssoc` together with the provided macros allows for smoother
 ## interfacing with JavaScript, allowing for example quick and easy imports of
 ## JavaScript variables:
 
@@ -21,21 +21,21 @@ runnableExamples:
   var document {.importc, nodecl.}: JsObject
   var console {.importc, nodecl.}: JsObject
   # import the "$" function
-  proc jq(selector: JsObject): JsObject {.importcpp: "$$(#)".}
+  proc jq(selector: JsObject): JsObject {.importjs: "$$(#)".}
 
   # Use jQuery to make the following code run, after the document is ready.
-  # This uses an experimental ``.()`` operator for ``JsObject``, to emit
-  # JavaScript calls, when no corresponding proc exists for ``JsObject``.
+  # This uses an experimental `.()` operator for `JsObject`, to emit
+  # JavaScript calls, when no corresponding proc exists for `JsObject`.
   proc main =
     jq(document).ready(proc() =
       console.log("Hello JavaScript!")
     )
 
 
-when not defined(js) and not defined(nimdoc) and not defined(nimsuggest):
+when not defined(js) and not defined(nimsuggest):
   {.fatal: "Module jsFFI is designed to be used with the JavaScript backend.".}
 
-import macros, tables
+import std/[macros, tables]
 
 const
   setImpl = "#[#] = #"
@@ -64,13 +64,13 @@ proc validJsName(name: string): bool =
     if chr notin {'A'..'Z','a'..'z','_','$','0'..'9'}:
       return false
 
-template mangleJsName(name: cstring): cstring =
+template mangleJsName(name: string): string =
   inc nameCounter
   "mangledName" & $nameCounter
 
 # only values that can be mapped 1 to 1 with cstring should be keys: they have an injective function with cstring
 
-proc toJsKey*[T: SomeInteger](text: cstring, t: type T): T {.importcpp: "parseInt(#)".}
+proc toJsKey*[T: SomeInteger](text: cstring, t: type T): T {.importjs: "parseInt(#)".}
 
 proc toJsKey*[T: enum](text: cstring, t: type T): T =
   T(text.toJsKey(int))
@@ -78,7 +78,7 @@ proc toJsKey*[T: enum](text: cstring, t: type T): T =
 proc toJsKey*(text: cstring, t: type cstring): cstring =
   text
 
-proc toJsKey*[T: SomeFloat](text: cstring, t: type T): T {.importcpp: "parseFloat(#)".}
+proc toJsKey*[T: SomeFloat](text: cstring, t: type T): T {.importjs: "parseFloat(#)".}
 
 type
   JsKey* = concept a, type T
@@ -93,21 +93,21 @@ type
 
 var
   jsArguments* {.importc: "arguments", nodecl}: JsObject
-    ## JavaScript's arguments pseudo-variable
+    ## JavaScript's arguments pseudo-variable.
   jsNull* {.importc: "null", nodecl.}: JsObject
-    ## JavaScript's null literal
+    ## JavaScript's null literal.
   jsUndefined* {.importc: "undefined", nodecl.}: JsObject
-    ## JavaScript's undefined literal
+    ## JavaScript's undefined literal.
   jsDirname* {.importc: "__dirname", nodecl.}: cstring
-    ## JavaScript's __dirname pseudo-variable
+    ## JavaScript's __dirname pseudo-variable.
   jsFilename* {.importc: "__filename", nodecl.}: cstring
-    ## JavaScript's __filename pseudo-variable
+    ## JavaScript's __filename pseudo-variable.
 
-proc isNull*[T](x: T): bool {.noSideEffect, importcpp: "(# === null)".}
-  ## check if a value is exactly null
+proc isNull*[T](x: T): bool {.noSideEffect, importjs: "(# === null)".}
+  ## Checks if a value is exactly null.
 
-proc isUndefined*[T](x: T): bool {.noSideEffect, importcpp: "(# === undefined)".}
-  ## check if a value is exactly undefined
+proc isUndefined*[T](x: T): bool {.noSideEffect, importjs: "(# === undefined)".}
+  ## Checks if a value is exactly undefined.
 
 # Exceptions
 type
@@ -121,36 +121,36 @@ type
   JsURIError* {.importc: "URIError".} = object of JsError
 
 # New
-proc newJsObject*: JsObject {.importcpp: "{@}".}
-  ## Creates a new empty JsObject
+proc newJsObject*: JsObject {.importjs: "{@}".}
+  ## Creates a new empty JsObject.
 
-proc newJsAssoc*[K: JsKey, V]: JsAssoc[K, V] {.importcpp: "{@}".}
+proc newJsAssoc*[K: JsKey, V]: JsAssoc[K, V] {.importjs: "{@}".}
   ## Creates a new empty JsAssoc with key type `K` and value type `V`.
 
 # Checks
 proc hasOwnProperty*(x: JsObject, prop: cstring): bool
-  {.importcpp: "#.hasOwnProperty(#)".}
+  {.importjs: "#.hasOwnProperty(#)".}
   ## Checks, whether `x` has a property of name `prop`.
 
-proc jsTypeOf*(x: JsObject): cstring {.importcpp: "typeof(#)".}
+proc jsTypeOf*(x: JsObject): cstring {.importjs: "typeof(#)".}
   ## Returns the name of the JsObject's JavaScript type as a cstring.
 
-proc jsNew*(x: auto): JsObject {.importcpp: "(new #)".}
+proc jsNew*(x: auto): JsObject {.importjs: "(new #)".}
   ## Turns a regular function call into an invocation of the
-  ## JavaScript's `new` operator
+  ## JavaScript's `new` operator.
 
-proc jsDelete*(x: auto): JsObject {.importcpp: "(delete #)".}
-  ## JavaScript's `delete` operator
+proc jsDelete*(x: auto): JsObject {.importjs: "(delete #)".}
+  ## JavaScript's `delete` operator.
 
 proc require*(module: cstring): JsObject {.importc.}
-  ## JavaScript's `require` function
+  ## JavaScript's `require` function.
 
 # Conversion to and from JsObject
-proc to*(x: JsObject, T: typedesc): T {.importcpp: "(#)".}
+proc to*(x: JsObject, T: typedesc): T {.importjs: "(#)".}
   ## Converts a JsObject `x` to type `T`.
 
-proc toJs*[T](val: T): JsObject {.importcpp: "(#)".}
-  ## Converts a value of any type to type JsObject
+proc toJs*[T](val: T): JsObject {.importjs: "(#)".}
+  ## Converts a value of any type to type JsObject.
 
 template toJs*(s: string): JsObject = cstring(s).toJs
 
@@ -160,50 +160,51 @@ macro jsFromAst*(n: untyped): untyped =
     result = newProc(procType = nnkDo, body = result)
   return quote: toJs(`result`)
 
-proc `&`*(a, b: cstring): cstring {.importcpp: "(# + #)".}
-  ## Concatenation operator for JavaScript strings
-
-proc `+`  *(x, y: JsObject): JsObject {.importcpp: "(# + #)".}
-proc `-`  *(x, y: JsObject): JsObject {.importcpp: "(# - #)".}
-proc `*`  *(x, y: JsObject): JsObject {.importcpp: "(# * #)".}
-proc `/`  *(x, y: JsObject): JsObject {.importcpp: "(# / #)".}
-proc `%`  *(x, y: JsObject): JsObject {.importcpp: "(# % #)".}
-proc `+=` *(x, y: JsObject): JsObject {.importcpp: "(# += #)", discardable.}
-proc `-=` *(x, y: JsObject): JsObject {.importcpp: "(# -= #)", discardable.}
-proc `*=` *(x, y: JsObject): JsObject {.importcpp: "(# *= #)", discardable.}
-proc `/=` *(x, y: JsObject): JsObject {.importcpp: "(# /= #)", discardable.}
-proc `%=` *(x, y: JsObject): JsObject {.importcpp: "(# %= #)", discardable.}
-proc `++` *(x:    JsObject): JsObject {.importcpp: "(++#)".}
-proc `--` *(x:    JsObject): JsObject {.importcpp: "(--#)".}
-proc `>`  *(x, y: JsObject): JsObject {.importcpp: "(# > #)".}
-proc `<`  *(x, y: JsObject): JsObject {.importcpp: "(# < #)".}
-proc `>=` *(x, y: JsObject): JsObject {.importcpp: "(# >= #)".}
-proc `<=` *(x, y: JsObject): JsObject {.importcpp: "(# <= #)".}
-proc `**` *(x, y: JsObject): JsObject {.importcpp: "((#) ** #)".}
-proc `and`*(x, y: JsObject): JsObject {.importcpp: "(# && #)".}
-proc `or` *(x, y: JsObject): JsObject {.importcpp: "(# || #)".}
-proc `not`*(x:    JsObject): JsObject {.importcpp: "(!#)".}
-proc `in` *(x, y: JsObject): JsObject {.importcpp: "(# in #)".}
-
-proc `[]`*(obj: JsObject, field: cstring): JsObject {.importcpp: getImpl.}
-  ## Return the value of a property of name `field` from a JsObject `obj`.
-
-proc `[]`*(obj: JsObject, field: int): JsObject {.importcpp: getImpl.}
-  ## Return the value of a property of name `field` from a JsObject `obj`.
-
-proc `[]=`*[T](obj: JsObject, field: cstring, val: T) {.importcpp: setImpl.}
-  ## Set the value of a property of name `field` in a JsObject `obj` to `v`.
-
-proc `[]=`*[T](obj: JsObject, field: int, val: T) {.importcpp: setImpl.}
-  ## Set the value of a property of name `field` in a JsObject `obj` to `v`.
+proc `&`*(a, b: cstring): cstring {.importjs: "(# + #)".}
+  ## Concatenation operator for JavaScript strings.
+
+proc `+`*(x, y: JsObject): JsObject {.importjs: "(# + #)".}
+proc `-`*(x, y: JsObject): JsObject {.importjs: "(# - #)".}
+proc `*`*(x, y: JsObject): JsObject {.importjs: "(# * #)".}
+proc `/`*(x, y: JsObject): JsObject {.importjs: "(# / #)".}
+proc `%`*(x, y: JsObject): JsObject {.importjs: "(# % #)".}
+proc `+=`*(x, y: JsObject): JsObject {.importjs: "(# += #)", discardable.}
+proc `-=`*(x, y: JsObject): JsObject {.importjs: "(# -= #)", discardable.}
+proc `*=`*(x, y: JsObject): JsObject {.importjs: "(# *= #)", discardable.}
+proc `/=`*(x, y: JsObject): JsObject {.importjs: "(# /= #)", discardable.}
+proc `%=`*(x, y: JsObject): JsObject {.importjs: "(# %= #)", discardable.}
+proc `++`*(x:    JsObject): JsObject {.importjs: "(++#)".}
+proc `--`*(x:    JsObject): JsObject {.importjs: "(--#)".}
+proc `>`*(x, y: JsObject): JsObject {.importjs: "(# > #)".}
+proc `<`*(x, y: JsObject): JsObject {.importjs: "(# < #)".}
+proc `>=`*(x, y: JsObject): JsObject {.importjs: "(# >= #)".}
+proc `<=`*(x, y: JsObject): JsObject {.importjs: "(# <= #)".}
+proc `**`*(x, y: JsObject): JsObject {.importjs: "((#) ** #)".}
+  # (#) needed, refs https://github.com/nim-lang/Nim/pull/16409#issuecomment-760550812
+proc `and`*(x, y: JsObject): JsObject {.importjs: "(# && #)".}
+proc `or`*(x, y: JsObject): JsObject {.importjs: "(# || #)".}
+proc `not`*(x:    JsObject): JsObject {.importjs: "(!#)".}
+proc `in`*(x, y: JsObject): JsObject {.importjs: "(# in #)".}
+
+proc `[]`*(obj: JsObject, field: cstring): JsObject {.importjs: getImpl.}
+  ## Returns the value of a property of name `field` from a JsObject `obj`.
+
+proc `[]`*(obj: JsObject, field: int): JsObject {.importjs: getImpl.}
+  ## Returns the value of a property of name `field` from a JsObject `obj`.
+
+proc `[]=`*[T](obj: JsObject, field: cstring, val: T) {.importjs: setImpl.}
+  ## Sets the value of a property of name `field` in a JsObject `obj` to `v`.
+
+proc `[]=`*[T](obj: JsObject, field: int, val: T) {.importjs: setImpl.}
+  ## Sets the value of a property of name `field` in a JsObject `obj` to `v`.
 
 proc `[]`*[K: JsKey, V](obj: JsAssoc[K, V], field: K): V
-  {.importcpp: getImpl.}
-  ## Return the value of a property of name `field` from a JsAssoc `obj`.
+  {.importjs: getImpl.}
+  ## Returns the value of a property of name `field` from a JsAssoc `obj`.
 
 proc `[]=`*[K: JsKey, V](obj: JsAssoc[K, V], field: K, val: V)
-  {.importcpp: setImpl.}
-  ## Set the value of a property of name `field` in a JsAssoc `obj` to `v`.
+  {.importjs: setImpl.}
+  ## Sets the value of a property of name `field` in a JsAssoc `obj` to `v`.
 
 proc `[]`*[V](obj: JsAssoc[cstring, V], field: string): V =
   obj[cstring(field)]
@@ -211,8 +212,8 @@ proc `[]`*[V](obj: JsAssoc[cstring, V], field: string): V =
 proc `[]=`*[V](obj: JsAssoc[cstring, V], field: string, val: V) =
   obj[cstring(field)] = val
 
-proc `==`*(x, y: JsRoot): bool {.importcpp: "(# === #)".}
-  ## Compare two JsObjects or JsAssocs. Be careful though, as this is comparison
+proc `==`*(x, y: JsRoot): bool {.importjs: "(# === #)".}
+  ## Compares two JsObjects or JsAssocs. Be careful though, as this is comparison
   ## like in JavaScript, so if your JsObjects are in fact JavaScript Objects,
   ## and not strings or numbers, this is a *comparison of references*.
 
@@ -226,36 +227,40 @@ macro `.`*(obj: JsObject, field: untyped): JsObject =
     assert obj.a.to(int) == 20
   if validJsName($field):
     let importString = "#." & $field
+    let helperName = genSym(nskProc, "helper")
     result = quote do:
-      proc helper(o: JsObject): JsObject
-        {.importcpp: `importString`, gensym.}
-      helper(`obj`)
+      proc `helperName`(o: JsObject): JsObject
+        {.importjs: `importString`.}
+      `helperName`(`obj`)
   else:
     if not mangledNames.hasKey($field):
-      mangledNames[$field] = $mangleJsName($field)
+      mangledNames[$field] = mangleJsName($field)
     let importString = "#." & mangledNames[$field]
+    let helperName = genSym(nskProc, "helper")
     result = quote do:
-      proc helper(o: JsObject): JsObject
-        {.importcpp: `importString`, gensym.}
-      helper(`obj`)
+      proc `helperName`(o: JsObject): JsObject
+        {.importjs: `importString`.}
+      `helperName`(`obj`)
 
 macro `.=`*(obj: JsObject, field, value: untyped): untyped =
   ## Experimental dot accessor (set) for type JsObject.
   ## Sets the value of a property of name `field` in a JsObject `x` to `value`.
   if validJsName($field):
     let importString = "#." & $field & " = #"
+    let helperName = genSym(nskProc, "helper")
     result = quote do:
-      proc helper(o: JsObject, v: auto)
-        {.importcpp: `importString`, gensym.}
-      helper(`obj`, `value`)
+      proc `helperName`(o: JsObject, v: auto)
+        {.importjs: `importString`.}
+      `helperName`(`obj`, `value`)
   else:
     if not mangledNames.hasKey($field):
-      mangledNames[$field] = $mangleJsName($field)
+      mangledNames[$field] = mangleJsName($field)
     let importString = "#." & mangledNames[$field] & " = #"
+    let helperName = genSym(nskProc, "helper")
     result = quote do:
-      proc helper(o: JsObject, v: auto)
-        {.importcpp: `importString`, gensym.}
-      helper(`obj`, `value`)
+      proc `helperName`(o: JsObject, v: auto)
+        {.importjs: `importString`.}
+      `helperName`(`obj`, `value`)
 
 macro `.()`*(obj: JsObject,
              field: untyped,
@@ -267,26 +272,26 @@ macro `.()`*(obj: JsObject,
   ## so be careful when using this.)
   ##
   ## Example:
-  ##
-  ## .. code-block:: nim
-  ##
-  ##  # Let's get back to the console example:
-  ##  var console {.importc, nodecl.}: JsObject
-  ##  let res = console.log("I return undefined!")
-  ##  console.log(res) # This prints undefined, as console.log always returns
-  ##                   # undefined. Thus one has to be careful, when using
-  ##                   # JsObject calls.
+  ##   ```nim
+  ##   # Let's get back to the console example:
+  ##   var console {.importc, nodecl.}: JsObject
+  ##   let res = console.log("I return undefined!")
+  ##   console.log(res) # This prints undefined, as console.log always returns
+  ##                    # undefined. Thus one has to be careful, when using
+  ##                    # JsObject calls.
+  ##   ```
   var importString: string
   if validJsName($field):
     importString = "#." & $field & "(@)"
   else:
     if not mangledNames.hasKey($field):
-      mangledNames[$field] = $mangleJsName($field)
+      mangledNames[$field] = mangleJsName($field)
     importString = "#." & mangledNames[$field] & "(@)"
-  result = quote:
-    proc helper(o: JsObject): JsObject
-      {.importcpp: `importString`, gensym, discardable.}
-    helper(`obj`)
+  let helperName = genSym(nskProc, "helper")
+  result = quote do:
+    proc `helperName`(o: JsObject): JsObject
+      {.importjs: `importString`, discardable.}
+    `helperName`(`obj`)
   for idx in 0 ..< args.len:
     let paramName = newIdentNode("param" & $idx)
     result[0][3].add newIdentDefs(paramName, newIdentNode("JsObject"))
@@ -301,12 +306,13 @@ macro `.`*[K: cstring, V](obj: JsAssoc[K, V],
     importString = "#." & $field
   else:
     if not mangledNames.hasKey($field):
-      mangledNames[$field] = $mangleJsName($field)
+      mangledNames[$field] = mangleJsName($field)
     importString = "#." & mangledNames[$field]
+  let helperName = genSym(nskProc, "helper")
   result = quote do:
-    proc helper(o: type(`obj`)): `obj`.V
-      {.importcpp: `importString`, gensym.}
-    helper(`obj`)
+    proc `helperName`(o: type(`obj`)): `obj`.V
+      {.importjs: `importString`.}
+    `helperName`(`obj`)
 
 macro `.=`*[K: cstring, V](obj: JsAssoc[K, V],
                                     field: untyped,
@@ -318,12 +324,13 @@ macro `.=`*[K: cstring, V](obj: JsAssoc[K, V],
     importString = "#." & $field & " = #"
   else:
     if not mangledNames.hasKey($field):
-      mangledNames[$field] = $mangleJsName($field)
+      mangledNames[$field] = mangleJsName($field)
     importString = "#." & mangledNames[$field] & " = #"
+  let helperName = genSym(nskProc, "helper")
   result = quote do:
-    proc helper(o: type(`obj`), v: `obj`.V)
-      {.importcpp: `importString`, gensym.}
-    helper(`obj`, `value`)
+    proc `helperName`(o: type(`obj`), v: `obj`.V)
+      {.importjs: `importString`.}
+    `helperName`(`obj`, `value`)
 
 macro `.()`*[K: cstring, V: proc](obj: JsAssoc[K, V],
                                            field: untyped,
@@ -341,14 +348,14 @@ macro `.()`*[K: cstring, V: proc](obj: JsAssoc[K, V],
 # Iterators:
 
 iterator pairs*(obj: JsObject): (cstring, JsObject) =
-  ## Yields tuples of type ``(cstring, JsObject)``, with the first entry
+  ## Yields tuples of type `(cstring, JsObject)`, with the first entry
   ## being the `name` of a fields in the JsObject and the second being its
   ## value wrapped into a JsObject.
   var k: cstring
   var v: JsObject
   {.emit: "for (var `k` in `obj`) {".}
-  {.emit: "  if (!`obj`.hasOwnProperty(`k`)) continue;".}
-  {.emit: "  `v`=`obj`[`k`];".}
+  {.emit: "  if (!`obj`.hasOwnProperty(`k`)) { continue; }".}
+  {.emit: "  `v` = `obj`[`k`];".}
   yield (k, v)
   {.emit: "}".}
 
@@ -356,8 +363,8 @@ iterator items*(obj: JsObject): JsObject =
   ## Yields the `values` of each field in a JsObject, wrapped into a JsObject.
   var v: JsObject
   {.emit: "for (var k in `obj`) {".}
-  {.emit: "  if (!`obj`.hasOwnProperty(k)) continue;".}
-  {.emit: "  `v`=`obj`[k];".}
+  {.emit: "  if (!`obj`.hasOwnProperty(k)) { continue; }".}
+  {.emit: "  `v` = `obj`[k];".}
   yield v
   {.emit: "}".}
 
@@ -365,18 +372,18 @@ iterator keys*(obj: JsObject): cstring =
   ## Yields the `names` of each field in a JsObject.
   var k: cstring
   {.emit: "for (var `k` in `obj`) {".}
-  {.emit: "  if (!`obj`.hasOwnProperty(`k`)) continue;".}
+  {.emit: "  if (!`obj`.hasOwnProperty(`k`)) { continue; }".}
   yield k
   {.emit: "}".}
 
 iterator pairs*[K: JsKey, V](assoc: JsAssoc[K, V]): (K,V) =
-  ## Yields tuples of type ``(K, V)``, with the first entry
+  ## Yields tuples of type `(K, V)`, with the first entry
   ## being a `key` in the JsAssoc and the second being its corresponding value.
   var k: cstring
   var v: V
   {.emit: "for (var `k` in `assoc`) {".}
-  {.emit: "  if (!`assoc`.hasOwnProperty(`k`)) continue;".}
-  {.emit: "  `v`=`assoc`[`k`];".}
+  {.emit: "  if (!`assoc`.hasOwnProperty(`k`)) { continue; }".}
+  {.emit: "  `v` = `assoc`[`k`];".}
   yield (k.toJsKey(K), v)
   {.emit: "}".}
 
@@ -384,8 +391,8 @@ iterator items*[K, V](assoc: JsAssoc[K, V]): V =
   ## Yields the `values` in a JsAssoc.
   var v: V
   {.emit: "for (var k in `assoc`) {".}
-  {.emit: "  if (!`assoc`.hasOwnProperty(k)) continue;".}
-  {.emit: "  `v`=`assoc`[k];".}
+  {.emit: "  if (!`assoc`.hasOwnProperty(k)) { continue; }".}
+  {.emit: "  `v` = `assoc`[k];".}
   yield v
   {.emit: "}".}
 
@@ -393,34 +400,33 @@ iterator keys*[K: JsKey, V](assoc: JsAssoc[K, V]): K =
   ## Yields the `keys` in a JsAssoc.
   var k: cstring
   {.emit: "for (var `k` in `assoc`) {".}
-  {.emit: "  if (!`assoc`.hasOwnProperty(`k`)) continue;".}
+  {.emit: "  if (!`assoc`.hasOwnProperty(`k`)) { continue; }".}
   yield k.toJsKey(K)
   {.emit: "}".}
 
 # Literal generation
 
 macro `{}`*(typ: typedesc, xs: varargs[untyped]): auto =
-  ## Takes a ``typedesc`` as its first argument, and a series of expressions of
-  ## type ``key: value``, and returns a value of the specified type with each
-  ## field ``key`` set to ``value``, as specified in the arguments of ``{}``.
+  ## Takes a `typedesc` as its first argument, and a series of expressions of
+  ## type `key: value`, and returns a value of the specified type with each
+  ## field `key` set to `value`, as specified in the arguments of `{}`.
   ##
   ## Example:
   ##
-  ## .. code-block:: nim
-  ##
-  ##  # Let's say we have a type with a ton of fields, where some fields do not
-  ##  # need to be set, and we do not want those fields to be set to ``nil``:
-  ##  type
-  ##    ExtremelyHugeType = ref object
-  ##      a, b, c, d, e, f, g: int
-  ##      h, i, j, k, l: cstring
-  ##      # And even more fields ...
+  ##   ```nim
+  ##   # Let's say we have a type with a ton of fields, where some fields do not
+  ##   # need to be set, and we do not want those fields to be set to `nil`:
+  ##   type
+  ##     ExtremelyHugeType = ref object
+  ##       a, b, c, d, e, f, g: int
+  ##       h, i, j, k, l: cstring
+  ##       # And even more fields ...
   ##
-  ##  let obj = ExtremelyHugeType{ a: 1, k: "foo".cstring, d: 42 }
-  ##
-  ##  # This generates roughly the same JavaScript as:
-  ##  {.emit: "var obj = {a: 1, k: "foo", d: 42};".}
+  ##   let obj = ExtremelyHugeType{ a: 1, k: "foo".cstring, d: 42 }
   ##
+  ##   # This generates roughly the same JavaScript as:
+  ##   {.emit: "var obj = {a: 1, k: "foo", d: 42};".}
+  ##   ```
   let a = ident"a"
   var body = quote do:
     var `a` {.noinit.}: `typ`
@@ -455,39 +461,40 @@ macro `{}`*(typ: typedesc, xs: varargs[untyped]): auto =
 # from a proc, `this` being the first argument.
 
 proc replaceSyms(n: NimNode): NimNode =
-  if n.kind == nnkSym: 
+  if n.kind == nnkSym:
     result = newIdentNode($n)
-  else: 
+  else:
     result = n
     for i in 0..<n.len:
       result[i] = replaceSyms(n[i])
 
-macro bindMethod*(procedure: typed): auto =
+macro bindMethod*(procedure: typed): auto {.deprecated: "Don't use it with closures".} =
   ## Takes the name of a procedure and wraps it into a lambda missing the first
-  ## argument, which passes the JavaScript builtin ``this`` as the first
+  ## argument, which passes the JavaScript builtin `this` as the first
   ## argument to the procedure. Returns the resulting lambda.
   ##
   ## Example:
   ##
   ## We want to generate roughly this JavaScript:
+  ##   ```js
+  ##   var obj = {a: 10};
+  ##   obj.someMethod = function() {
+  ##     return this.a + 42;
+  ##   };
+  ##   ```
   ##
-  ## .. code-block:: js
-  ##  var obj = {a: 10};
-  ##  obj.someMethod = function() {
-  ##    return this.a + 42;
-  ##  };
-  ##
-  ## We can achieve this using the ``bindMethod`` macro:
+  ## We can achieve this using the `bindMethod` macro:
   ##
-  ## .. code-block:: nim
-  ##  let obj = JsObject{ a: 10 }
-  ##  proc someMethodImpl(that: JsObject): int =
-  ##    that.a.to(int) + 42
-  ##  obj.someMethod = bindMethod someMethodImpl
+  ##   ```nim
+  ##   let obj = JsObject{ a: 10 }
+  ##   proc someMethodImpl(that: JsObject): int =
+  ##     that.a.to(int) + 42
+  ##   obj.someMethod = bindMethod someMethodImpl
   ##
-  ##  # Alternatively:
-  ##  obj.someMethod = bindMethod
-  ##    proc(that: JsObject): int = that.a.to(int) + 42
+  ##   # Alternatively:
+  ##   obj.someMethod = bindMethod
+  ##     proc(that: JsObject): int = that.a.to(int) + 42
+  ##   ```
   if not (procedure.kind == nnkSym or procedure.kind == nnkLambda):
     error("Argument has to be a proc or a symbol corresponding to a proc.")
   var
diff --git a/lib/js/jsre.nim b/lib/js/jsre.nim
index f5c3cc1ac..2d931eb20 100644
--- a/lib/js/jsre.nim
+++ b/lib/js/jsre.nim
@@ -1,46 +1,97 @@
 ## Regular Expressions for the JavaScript target.
 ## * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
-
-runnableExamples:
-  let jsregex: RegExp = newRegExp(r"\s+", r"i")
-  jsregex.compile(r"\w+", r"i")
-  doAssert jsregex.test(r"nim javascript")
-  doAssert jsregex.exec(r"nim javascript") == @["nim".cstring]
-  doAssert jsregex.toString() == r"/\w+/i"
-  jsregex.compile(r"[0-9]", r"i")
-  doAssert jsregex.test(r"0123456789abcd")
-
-
-when not defined(js) and not defined(Nimdoc):
+when not defined(js):
   {.error: "This module only works on the JavaScript platform".}
 
-type RegExp* {.importjs.} = object    ## Regular Expressions for JavaScript target.
-  flags* {.importjs.}: cstring        ## cstring that contains the flags of the RegExp object.
-  dotAll* {.importjs.}: bool          ## Whether `.` matches newlines or not.
-  global* {.importjs.}: bool          ## Whether to test against all possible matches in a string, or only against the first.
-  ignoreCase* {.importjs.}: bool      ## Whether to ignore case while attempting a match in a string.
-  multiline* {.importjs.}: bool       ## Whether to search in strings across multiple lines.
-  source* {.importjs.}: cstring       ## The text of the pattern.
-  sticky* {.importjs.}: bool          ## Whether the search is sticky.
-  unicode* {.importjs.}: bool         ## Whether Unicode features are enabled.
-  lastIndex* {.importjs.}: cint       ## Index at which to start the next match (read/write property).
-  input* {.importjs.}: cstring        ## Read-only and modified on successful match.
-  lastMatch* {.importjs.}: cstring    ## Ditto.
-  lastParen* {.importjs.}: cstring    ## Ditto.
-  leftContext* {.importjs.}: cstring  ## Ditto.
-  rightContext* {.importjs.}: cstring ## Ditto.
+type RegExp* = ref object of JsRoot
+  ## Regular Expressions for JavaScript target.
+  ## See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
+  flags*: cstring        ## cstring that contains the flags of the RegExp object.
+  dotAll*: bool          ## Whether `.` matches newlines or not.
+  global*: bool          ## Whether to test against all possible matches in a string, or only against the first.
+  ignoreCase*: bool      ## Whether to ignore case while attempting a match in a string.
+  multiline*: bool       ## Whether to search in strings across multiple lines.
+  source*: cstring       ## The text of the pattern.
+  sticky*: bool          ## Whether the search is sticky.
+  unicode*: bool         ## Whether Unicode features are enabled.
+  lastIndex*: cint       ## Index at which to start the next match (read/write property).
+  input*: cstring        ## Read-only and modified on successful match.
+  lastMatch*: cstring    ## Ditto.
+  lastParen*: cstring    ## Ditto.
+  leftContext*: cstring  ## Ditto.
+  rightContext*: cstring ## Ditto.
+  hasIndices*: bool      ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/hasIndices
+
 
 func newRegExp*(pattern: cstring; flags: cstring): RegExp {.importjs: "new RegExp(@)".}
   ## Creates a new RegExp object.
 
+func newRegExp*(pattern: cstring): RegExp {.importjs: "new RegExp(@)".}
+
 func compile*(self: RegExp; pattern: cstring; flags: cstring) {.importjs: "#.compile(@)".}
   ## Recompiles a regular expression during execution of a script.
 
-func exec*(self: RegExp; pattern: cstring): seq[cstring] {.importjs: "#.exec(#)".}
-  ## Executes a search for a match in its string parameter.
+func replace*(pattern: cstring; self: RegExp; replacement: cstring): cstring {.importjs: "#.replace(#, #)".}
+  ## Returns a new string with some or all matches of a pattern replaced by given replacement
+
+func replace*(pattern: cstring, self: RegExp, cb: proc (args: varargs[cstring]): cstring): cstring {.importcpp.}
+  ## Returns a new string with some or all matches of a pattern replaced by given callback function
+
+func split*(pattern: cstring; self: RegExp): seq[cstring] {.importjs: "(#.split(#) || [])".}
+  ## Divides a string into an ordered list of substrings and returns the array
 
-func test*(self: RegExp; pattern: cstring): bool {.importjs: "#.test(#)".}
-  ## Tests for a match in its string parameter.
+func match*(pattern: cstring; self: RegExp): seq[cstring] {.importjs: "(#.match(#) || [])".}
+  ## Returns an array of matches of a RegExp against given string
 
-func toString*(self: RegExp): cstring {.importjs: "#.toString()".}
+func exec*(self: RegExp; pattern: cstring): seq[cstring] {.importjs: "(#.exec(#) || [])".}
+  ## Executes a search for a match in its string parameter.
+
+func toCstring*(self: RegExp): cstring {.importjs: "#.toString()".}
   ## Returns a string representing the RegExp object.
+
+func `$`*(self: RegExp): string = $toCstring(self)
+
+func contains*(pattern: cstring; self: RegExp): bool =
+  ## Tests for a substring match in its string parameter.
+  runnableExamples:
+    let jsregex: RegExp = newRegExp(r"bc$", r"i")
+    assert jsregex in r"abc"
+    assert jsregex notin r"abcd"
+    assert "xabc".contains jsregex
+  {.emit: "`result` = `self`.test(`pattern`);".}
+
+func startsWith*(pattern: cstring; self: RegExp): bool =
+  ## Tests if string starts with given RegExp
+  runnableExamples:
+    let jsregex: RegExp = newRegExp(r"abc", r"i")
+    assert "abcd".startsWith jsregex
+  pattern.contains(newRegExp(("^" & $(self.source)).cstring, self.flags))
+
+func endsWith*(pattern: cstring; self: RegExp): bool =
+  ## Tests if string ends with given RegExp
+  runnableExamples:
+    let jsregex: RegExp = newRegExp(r"bcd", r"i")
+    assert "abcd".endsWith jsregex
+  pattern.contains(newRegExp(($(self.source) & "$").cstring, self.flags))
+
+
+runnableExamples:
+  let jsregex: RegExp = newRegExp(r"\s+", r"i")
+  jsregex.compile(r"\w+", r"i")
+  assert "nim javascript".contains jsregex
+  assert jsregex.exec(r"nim javascript") == @["nim".cstring]
+  assert jsregex.toCstring() == r"/\w+/i"
+  jsregex.compile(r"[0-9]", r"i")
+  assert "0123456789abcd".contains jsregex
+  assert $jsregex == "/[0-9]/i"
+  jsregex.compile(r"abc", r"i")
+  assert "abcd".startsWith jsregex
+  assert "dabc".endsWith jsregex
+  jsregex.compile(r"\d", r"i")
+  assert "do1ne".split(jsregex) == @["do".cstring, "ne".cstring]
+  jsregex.compile(r"[lw]", r"i")
+  assert "hello world".replace(jsregex,"X") == "heXlo world"
+  jsregex.compile(r"([a-z])\1*", r"g")
+  assert "abbcccdddd".replace(jsregex, proc (m: varargs[cstring]): cstring = ($m[0] & $(m.len)).cstring) == "a1b2c3d4"
+  let digitsRegex: RegExp = newRegExp(r"\d")
+  assert "foo".match(digitsRegex) == @[]