summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorzah <zahary@gmail.com>2018-04-13 20:08:43 +0300
committerAndreas Rumpf <rumpf_a@web.de>2018-04-13 19:08:43 +0200
commite3037a2f33124edaaba92fec19b951c767eb74f5 (patch)
treedf7b45b4dd68e3adae8aff7af23d632d4a4f11c6
parent1d1d6f39a360e3f65051868306f67121a4eeae12 (diff)
downloadNim-e3037a2f33124edaaba92fec19b951c767eb74f5.tar.gz
Support code hot reloading for JavaScript projects (#7362)
* Support code hot reloading for JavaScript projects

* Add some missing JavaScript symbols and APIs

* fix the Travis build

* (review changes) remove the js type from the standard library as it doesn't follow NEP-1

* more additions to the DOM module

* Follow NEP-1 in jsffi; spell 'hot code reloading' correctly

* introduce a jscore module

* Document jscore module.

* readded js type

* Remove the '$' operator that doesn't behave
-rw-r--r--changelog.md15
-rw-r--r--compiler/ccgtypes.nim29
-rw-r--r--compiler/commands.nim4
-rw-r--r--compiler/jsgen.nim76
-rw-r--r--compiler/jstypes.nim4
-rw-r--r--compiler/options.nim3
-rw-r--r--compiler/sighashes.nim31
-rw-r--r--doc/advopt.txt1
-rw-r--r--doc/lib.rst4
-rw-r--r--doc/nimc.rst46
-rw-r--r--lib/js/jscore.nim91
-rw-r--r--lib/js/jsffi.nim24
-rw-r--r--lib/system.nim24
-rw-r--r--web/website.ini1
14 files changed, 285 insertions, 68 deletions
diff --git a/changelog.md b/changelog.md
index 364047d05..89eb999d1 100644
--- a/changelog.md
+++ b/changelog.md
@@ -61,6 +61,15 @@
 
 ### Compiler changes
 
-- The VM's instruction count limit was raised to 1 billion instructions in order
-  to support more complex computations at compile-time.
-- Added ``macros.getProjectPath`` and ``ospaths.putEnv`` procs to VM.
+- The VM's instruction count limit was raised to 1 billion instructions in
+  order to support more complex computations at compile-time.
+
+- Support for hot code reloading has been implemented for the JavaScript
+  target. To use it, compile your code with `--hotReloading:on` and use a
+  helper library such as LiveReload or BrowserSync.
+
+- Added ``macros.getProjectPath`` and ``ospaths.putEnv`` procs to Nim's virtual 
+  machine.
+
+### Bugfixes
+
diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim
index cc27bc4e0..56fb379bd 100644
--- a/compiler/ccgtypes.nim
+++ b/compiler/ccgtypes.nim
@@ -44,38 +44,11 @@ when false:
     assert p.kind == skPackage
     result = gDebugInfo.register(p.name.s, m.name.s)
 
-proc idOrSig(m: BModule; s: PSym): Rope =
-  if s.kind in routineKinds and s.typ != nil:
-    # signatures for exported routines are reliable enough to
-    # produce a unique name and this means produced C++ is more stable wrt
-    # Nim changes:
-    let sig = hashProc(s)
-    result = rope($sig)
-    #let m = if s.typ.callConv != ccInline: findPendingModule(m, s) else: m
-    let counter = m.sigConflicts.getOrDefault(sig)
-    #if sigs == "_jckmNePK3i2MFnWwZlp6Lg" and s.name.s == "contains":
-    #  echo "counter ", counter, " ", s.id
-    if counter != 0:
-      result.add "_" & rope(counter+1)
-    # this minor hack is necessary to make tests/collections/thashes compile.
-    # The inlined hash function's original module is ambiguous so we end up
-    # generating duplicate names otherwise:
-    if s.typ.callConv == ccInline:
-      result.add rope(m.module.name.s)
-    m.sigConflicts.inc(sig)
-  else:
-    let sig = hashNonProc(s)
-    result = rope($sig)
-    let counter = m.sigConflicts.getOrDefault(sig)
-    if counter != 0:
-      result.add "_" & rope(counter+1)
-    m.sigConflicts.inc(sig)
-
 proc mangleName(m: BModule; s: PSym): Rope =
   result = s.loc.r
   if result == nil:
     result = s.name.s.mangle.rope
-    add(result, m.idOrSig(s))
+    add(result, idOrSig(s, m.module.name.s, m.sigConflicts))
     s.loc.r = result
     writeMangledName(m.ndi, s)
 
diff --git a/compiler/commands.nim b/compiler/commands.nim
index 2e9b6f8db..5b8f569ac 100644
--- a/compiler/commands.nim
+++ b/compiler/commands.nim
@@ -473,6 +473,10 @@ proc processSwitch(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
     processOnOffSwitch({optMemTracker}, arg, pass, info)
     if optMemTracker in gOptions: defineSymbol("memtracker")
     else: undefSymbol("memtracker")
+  of "hotreloading":
+    processOnOffSwitch({optHotReloading}, arg, pass, info)
+    if optHotReloading in gOptions: defineSymbol("hotreloading")
+    else: undefSymbol("hotreloading")
   of "oldnewlines":
     case arg.normalize
     of "on":
diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim
index 357708bb9..727e9209d 100644
--- a/compiler/jsgen.nim
+++ b/compiler/jsgen.nim
@@ -31,9 +31,9 @@ implements the required case distinction.
 
 import
   ast, astalgo, strutils, hashes, trees, platform, magicsys, extccomp, options,
-  nversion, nimsets, msgs, std / sha1, bitsets, idents, types, os,
+  nversion, nimsets, msgs, std / sha1, bitsets, idents, types, os, tables,
   times, ropes, math, passes, ccgutils, wordrecg, renderer, rodread, rodutils,
-  intsets, cgmeth, lowerings
+  intsets, cgmeth, lowerings, sighashes
 
 from modulegraphs import ModuleGraph
 
@@ -43,6 +43,7 @@ type
   TJSGen = object of TPassContext
     module: PSym
     target: TTarget
+    sigConflicts: CountTable[SigHash]
 
   BModule = ref TJSGen
   TJSTypeKind = enum       # necessary JS "types"
@@ -210,7 +211,7 @@ proc mapType(p: PProc; typ: PType): TJSTypeKind =
   if p.target == targetPHP: result = etyObject
   else: result = mapType(typ)
 
-proc mangleName(s: PSym; target: TTarget): Rope =
+proc mangleName(m: BModule, s: PSym): Rope =
   proc validJsName(name: string): bool =
     result = true
     const reservedWords = ["abstract", "await", "boolean", "break", "byte",
@@ -235,7 +236,7 @@ proc mangleName(s: PSym; target: TTarget): Rope =
   if result == nil:
     if s.kind == skField and s.name.s.validJsName:
       result = rope(s.name.s)
-    elif target == targetJS or s.kind == skTemp:
+    elif m.target == targetJS or s.kind == skTemp:
       result = rope(mangle(s.name.s))
     else:
       var x = newStringOfCap(s.name.s.len)
@@ -254,8 +255,13 @@ proc mangleName(s: PSym; target: TTarget): Rope =
         inc i
       result = rope(x)
     if s.name.s != "this" and s.kind != skField:
-      add(result, "_")
-      add(result, rope(s.id))
+      if optHotReloading in gOptions:
+        # When hot reloading is enabled, we must ensure that the names
+        # of functions and types will be preserved across rebuilds:
+        add(result, idOrSig(s, m.module.name.s, m.sigConflicts))
+      else:
+        add(result, "_")
+        add(result, rope(s.id))
     s.loc.r = result
 
 proc escapeJSString(s: string): string =
@@ -821,7 +827,7 @@ proc genAsmOrEmitStmt(p: PProc, n: PNode) =
       # for backwards compatibility we don't deref syms here :-(
       if v.kind in {skVar, skLet, skTemp, skConst, skResult, skParam, skForVar}:
         if p.target == targetPHP: p.body.add "$"
-        p.body.add mangleName(v, p.target)
+        p.body.add mangleName(p.module, v)
       else:
         var r: TCompRes
         gen(p, it, r)
@@ -862,7 +868,7 @@ proc generateHeader(p: PProc, typ: PType): Rope =
     var param = typ.n.sons[i].sym
     if isCompileTimeOnly(param.typ): continue
     if result != nil: add(result, ", ")
-    var name = mangleName(param, p.target)
+    var name = mangleName(p.module, param)
     if p.target == targetJS:
       add(result, name)
       if mapType(param.typ) == etyBaseIndex:
@@ -1012,7 +1018,7 @@ proc genFieldAddr(p: PProc, n: PNode, r: var TCompRes) =
   else:
     if b.sons[1].kind != nkSym: internalError(b.sons[1].info, "genFieldAddr")
     var f = b.sons[1].sym
-    if f.loc.r == nil: f.loc.r = mangleName(f, p.target)
+    if f.loc.r == nil: f.loc.r = mangleName(p.module, f)
     r.res = makeJSString($f.loc.r)
   internalAssert a.typ != etyBaseIndex
   r.address = a.res
@@ -1028,7 +1034,7 @@ proc genFieldAccess(p: PProc, n: PNode, r: var TCompRes) =
   else:
     if n.sons[1].kind != nkSym: internalError(n.sons[1].info, "genFieldAccess")
     var f = n.sons[1].sym
-    if f.loc.r == nil: f.loc.r = mangleName(f, p.target)
+    if f.loc.r == nil: f.loc.r = mangleName(p.module, f)
     if p.target == targetJS:
       r.res = "$1.$2" % [r.res, f.loc.r]
     else:
@@ -1240,7 +1246,7 @@ proc genSym(p: PProc, n: PNode, r: var TCompRes) =
       r.res = "$" & s.loc.r
       p.declareGlobal(s.id, r.res)
   of skProc, skFunc, skConverter, skMethod:
-    discard mangleName(s, p.target)
+    discard mangleName(p.module, s)
     if p.target == targetPHP and r.kind != resCallee:
       r.res = makeJsString($s.loc.r)
     else:
@@ -1396,7 +1402,7 @@ proc genPatternCall(p: PProc; n: PNode; pat: string; typ: PType;
 proc genInfixCall(p: PProc, n: PNode, r: var TCompRes) =
   # don't call '$' here for efficiency:
   let f = n[0].sym
-  if f.loc.r == nil: f.loc.r = mangleName(f, p.target)
+  if f.loc.r == nil: f.loc.r = mangleName(p.module, f)
   if sfInfixCall in f.flags:
     let pat = n.sons[0].sym.loc.r.data
     internalAssert pat != nil
@@ -1467,9 +1473,9 @@ proc createRecordVarAux(p: PProc, rec: PNode, excludedFieldIDs: IntSet, output:
     if rec.sym.id notin excludedFieldIDs:
       if output.len > 0: output.add(", ")
       if p.target == targetJS:
-        output.addf("$#: ", [mangleName(rec.sym, p.target)])
+        output.addf("$#: ", [mangleName(p.module, rec.sym)])
       else:
-        output.addf("'$#' => ", [mangleName(rec.sym, p.target)])
+        output.addf("'$#' => ", [mangleName(p.module, rec.sym)])
       output.add(createVar(p, rec.sym.typ, false))
   else: internalError(rec.info, "createRecordVarAux")
 
@@ -1575,18 +1581,25 @@ proc genVarInit(p: PProc, v: PSym, n: PNode) =
     a: TCompRes
     s: Rope
     varCode: string
+    varName = mangleName(p.module, v)
+    useReloadingGuard = sfGlobal in v.flags and optHotReloading in gOptions
+
   if v.constraint.isNil:
-    varCode = "var $2"
+    if useReloadingGuard:
+      lineF(p, "var $1;$n", varName)
+      lineF(p, "if ($1 === undefined) {$n", varName)
+      varCode = $varName
+    else:
+      varCode = "var $2"
   else:
     varCode = v.constraint.strVal
+
   if n.kind == nkEmpty:
-    let mname = mangleName(v, p.target)
     lineF(p, varCode & " = $3;$n" | "$$$2 = $3;$n",
-               [returnType, mname, createVar(p, v.typ, isIndirect(v))])
+               [returnType, varName, createVar(p, v.typ, isIndirect(v))])
     if v.typ.kind in {tyVar, tyPtr, tyLent, tyRef} and mapType(p, v.typ) == etyBaseIndex:
-      lineF(p, "var $1_Idx = 0;$n", [ mname ])
+      lineF(p, "var $1_Idx = 0;$n", [varName])
   else:
-    discard mangleName(v, p.target)
     gen(p, n, a)
     case mapType(p, v.typ)
     of etyObject, etySeq:
@@ -1619,6 +1632,9 @@ proc genVarInit(p: PProc, v: PSym, n: PNode) =
     else:
       lineF(p, varCode & " = $3;$n" | "$$$2 = $3;$n", [returnType, v.loc.r, s])
 
+  if useReloadingGuard:
+    lineF(p, "}$n")
+
 proc genVarStmt(p: PProc, n: PNode) =
   for i in countup(0, sonsLen(n) - 1):
     var a = n.sons[i]
@@ -2031,7 +2047,7 @@ proc genObjConstr(p: PProc, n: PNode, r: var TCompRes) =
     let val = it.sons[1]
     gen(p, val, a)
     var f = it.sons[0].sym
-    if f.loc.r == nil: f.loc.r = mangleName(f, p.target)
+    if f.loc.r == nil: f.loc.r = mangleName(p.module, f)
     fieldIDs.incl(f.id)
 
     let typ = val.typ.skipTypes(abstractInst)
@@ -2158,11 +2174,11 @@ proc genProc(oldProc: PProc, prc: PSym): Rope =
   p.up = oldProc
   var returnStmt: Rope = nil
   var resultAsgn: Rope = nil
-  let name = mangleName(prc, p.target)
+  var name = mangleName(p.module, prc)
   let header = generateHeader(p, prc.typ)
   if prc.typ.sons[0] != nil and sfPure notin prc.flags:
     resultSym = prc.ast.sons[resultPos].sym
-    let mname = mangleName(resultSym, p.target)
+    let mname = mangleName(p.module, resultSym)
     let resVar = createVar(p, resultSym.typ, isIndirect(resultSym))
     resultAsgn = p.indentLine(("var $# = $#;$n" | "$$$# = $#;$n") % [mname, resVar])
     if resultSym.typ.kind in {tyVar, tyPtr, tyLent, tyRef} and
@@ -2188,6 +2204,18 @@ proc genProc(oldProc: PProc, prc: PSym): Rope =
               optionaLine(genProcBody(p, prc)),
               optionaLine(p.indentLine(returnStmt))]
   else:
+    result = ~tnl
+
+    if optHotReloading in gOptions:
+      # Here, we introduce thunks that create the equivalent of a jump table
+      # for all global functions, because references to them may be stored
+      # in JavaScript variables. The added indirection ensures that such
+      # references will end up calling the reloaded code.
+      var thunkName = name
+      name = name & "IMLP"
+      result.add("function $#() { return $#.apply(this, arguments); }$n" %
+                 [thunkName, name])
+
     def = "function $#($#) {$n$#$#$#$#$#" %
             [ name,
               header,
@@ -2198,7 +2226,6 @@ proc genProc(oldProc: PProc, prc: PSym): Rope =
               optionaLine(p.indentLine(returnStmt))]
 
   dec p.extraIndent
-  result = ~tnl
   result.add p.indentLine(def)
   result.add p.indentLine(~"}$n")
 
@@ -2332,7 +2359,7 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) =
   of nkEmpty: discard
   of nkLambdaKinds:
     let s = n.sons[namePos].sym
-    discard mangleName(s, p.target)
+    discard mangleName(p.module, s)
     r.res = s.loc.r
     if lfNoDecl in s.loc.flags or s.magic != mNone: discard
     elif not p.g.generatedSyms.containsOrIncl(s.id):
@@ -2389,6 +2416,7 @@ var globals: PGlobals
 proc newModule(module: PSym): BModule =
   new(result)
   result.module = module
+  result.sigConflicts = initCountTable[SigHash]()
   if globals == nil:
     globals = newGlobals()
 
diff --git a/compiler/jstypes.nim b/compiler/jstypes.nim
index 3768acf27..8bd963a65 100644
--- a/compiler/jstypes.nim
+++ b/compiler/jstypes.nim
@@ -34,7 +34,7 @@ proc genObjectFields(p: PProc, typ: PType, n: PNode): Rope =
     s = genTypeInfo(p, field.typ)
     result = ("{kind: 1, offset: \"$1\", len: 0, " &
         "typ: $2, name: $3, sons: null}") %
-                   [mangleName(field, p.target), s,
+                   [mangleName(p.module, field), s,
                     makeJSString(field.name.s)]
   of nkRecCase:
     length = sonsLen(n)
@@ -63,7 +63,7 @@ proc genObjectFields(p: PProc, typ: PType, n: PNode): Rope =
            [u, genObjectFields(p, typ, lastSon(b))])
     result = ("{kind: 3, offset: \"$1\", len: $3, " &
         "typ: $2, name: $4, sons: [$5]}") % [
-        mangleName(field, p.target), s,
+        mangleName(p.module, field), s,
         rope(lengthOrd(field.typ)), makeJSString(field.name.s), result]
   else: internalError(n.info, "genObjectFields")
 
diff --git a/compiler/options.nim b/compiler/options.nim
index be13e15d7..d1428312b 100644
--- a/compiler/options.nim
+++ b/compiler/options.nim
@@ -36,7 +36,8 @@ type                          # please make sure we have under 32 options
     optImplicitStatic,        # optimization: implicit at compile time
                               # evaluation
     optPatterns,              # en/disable pattern matching
-    optMemTracker
+    optMemTracker,
+    optHotReloading
 
   TOptions* = set[TOption]
   TGlobalOption* = enum       # **keep binary compatible**
diff --git a/compiler/sighashes.nim b/compiler/sighashes.nim
index 0fea3bbe3..46b83c386 100644
--- a/compiler/sighashes.nim
+++ b/compiler/sighashes.nim
@@ -9,7 +9,7 @@
 
 ## Computes hash values for routine (proc, method etc) signatures.
 
-import ast, md5
+import ast, md5, tables, ropes
 from hashes import Hash
 from astalgo import debug
 from types import typeToString, preferDesc
@@ -326,3 +326,32 @@ proc hashOwner*(s: PSym): SigHash =
   c &= m.name.s
 
   md5Final c, result.Md5Digest
+
+proc idOrSig*(s: PSym, currentModule: string,
+              sigCollisions: var CountTable[SigHash]): Rope =
+  if s.kind in routineKinds and s.typ != nil:
+    # signatures for exported routines are reliable enough to
+    # produce a unique name and this means produced C++ is more stable wrt
+    # Nim changes:
+    let sig = hashProc(s)
+    result = rope($sig)
+    #let m = if s.typ.callConv != ccInline: findPendingModule(m, s) else: m
+    let counter = sigCollisions.getOrDefault(sig)
+    #if sigs == "_jckmNePK3i2MFnWwZlp6Lg" and s.name.s == "contains":
+    #  echo "counter ", counter, " ", s.id
+    if counter != 0:
+      result.add "_" & rope(counter+1)
+    # this minor hack is necessary to make tests/collections/thashes compile.
+    # The inlined hash function's original module is ambiguous so we end up
+    # generating duplicate names otherwise:
+    if s.typ.callConv == ccInline:
+      result.add rope(currentModule)
+    sigCollisions.inc(sig)
+  else:
+    let sig = hashNonProc(s)
+    result = rope($sig)
+    let counter = sigCollisions.getOrDefault(sig)
+    if counter != 0:
+      result.add "_" & rope(counter+1)
+    sigCollisions.inc(sig)
+
diff --git a/doc/advopt.txt b/doc/advopt.txt
index ac309c222..7d8d81c4f 100644
--- a/doc/advopt.txt
+++ b/doc/advopt.txt
@@ -60,6 +60,7 @@ Advanced options:
   --implicitStatic:on|off   turn implicit compile time evaluation on|off
   --patterns:on|off         turn pattern matching on|off
   --memTracker:on|off       turn memory tracker on|off
+  --hotReloading:on|off     turn support for hot code reloading on|off
   --excessiveStackTrace:on|off
                             stack traces use full file paths
   --oldNewlines:on|off      turn on|off the old behaviour of "\n"
diff --git a/doc/lib.rst b/doc/lib.rst
index 5b3b47f65..f0569070d 100644
--- a/doc/lib.rst
+++ b/doc/lib.rst
@@ -436,6 +436,10 @@ Modules for JS backend
 * `asyncjs <asyncjs.html>`_
   Types and macros for writing asynchronous procedures in JavaScript.
 
+* `jscore <jscore.html>`_
+  Wrapper of core JavaScript functions. For most purposes you should be using
+  the ``math``, ``json``, and ``times`` stdlib modules instead of this module.
+
 Deprecated modules
 ------------------
 
diff --git a/doc/nimc.rst b/doc/nimc.rst
index 151510df2..fe414b643 100644
--- a/doc/nimc.rst
+++ b/doc/nimc.rst
@@ -278,12 +278,12 @@ Define               Effect
                      what's in the Nim file with what's in the C header
                      (requires a C compiler with _Static_assert support, like
                      any C11 compiler)
-``tempDir``          This symbol takes a string as its value, like 
+``tempDir``          This symbol takes a string as its value, like
                      ``--define:tempDir:/some/temp/path`` to override the
                      temporary directory returned by ``os.getTempDir()``.
-                     The value **should** end with a directory separator 
+                     The value **should** end with a directory separator
                      character. (Relevant for the Android platform)
-``useShPath``        This symbol takes a string as its value, like 
+``useShPath``        This symbol takes a string as its value, like
                      ``--define:useShPath:/opt/sh/bin/sh`` to override the
                      path for the ``sh`` binary, in cases where it is not
                      located in the default location ``/bin/sh``
@@ -325,6 +325,46 @@ Debugger option
 The ``debugger`` option enables or disables the *Embedded Nim Debugger*.
 See the documentation of endb_ for further information.
 
+Hot code reloading
+------------------
+**Note:** At the moment hot code reloading is supported only in
+JavaScript projects.
+
+The `hotReloading` option enables special compilation mode where changes in
+the code can be applied automatically to a running program. The code reloading
+happens at the granularity of an individual module. When a module is reloaded,
+Nim will preserve the state of all global variables which are initialized with
+a standard variable declaration in the code. All other top level code will be
+executed repeatedly on each reload. If you want to prevent this behavior, you
+can guard a block of code with the `once` construct:
+
+.. code-block:: Nim
+  var settings = initTable[string, string]()
+
+  once:
+    myInit()
+
+    for k, v in loadSettings():
+      settings[k] = v
+
+If you want to reset the state of a global variable on each reload, just
+re-assign a value anywhere within the top-level code:
+
+.. code-block:: Nim
+  var lastReload: Time
+
+  lastReload = now()
+  resetProgramState()
+
+**Known limitations:** In the JavaScript target, global variables using the
+`codegenDecl` pragma will be re-initialized on each reload. Please guard the
+initialization with a `once` block to work-around this.
+
+**Usage in JavaScript projects:**
+
+Once your code is compiled for hot reloading, you can use a framework such
+as `LiveReload <http://livereload.com/>` or `BrowserSync <https://browsersync.io/>`
+to implement the actual reloading behavior in your project.
 
 Breakpoint pragma
 -----------------
diff --git a/lib/js/jscore.nim b/lib/js/jscore.nim
new file mode 100644
index 000000000..94aadf87a
--- /dev/null
+++ b/lib/js/jscore.nim
@@ -0,0 +1,91 @@
+## This module wraps core JavaScript functions.
+##
+## 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
+## modules instead.
+
+when not defined(js) and not defined(Nimdoc):
+  {.error: "This module only works on the JavaScript platform".}
+
+type
+  MathLib* = ref object
+  JsonLib* = ref object
+  DateLib* = ref object
+  DateTime* = ref object
+
+var
+  Math* {.importc, nodecl.}: MathLib
+  Date* {.importc, nodecl.}: DateLib
+  JSON* {.importc, nodecl.}: JsonLib
+
+{.push importcpp.}
+
+# Math library
+proc abs*(m: MathLib, a: SomeNumber): SomeNumber
+proc acos*(m: MathLib, a: SomeNumber): float
+proc acosh*(m: MathLib, a: SomeNumber): float
+proc asin*(m: MathLib, a: SomeNumber): float
+proc asinh*(m: MathLib, a: SomeNumber): float
+proc atan*(m: MathLib, a: SomeNumber): float
+proc atan2*(m: MathLib, a: SomeNumber): float
+proc atanh*(m: MathLib, a: SomeNumber): float
+proc cbrt*(m: MathLib, f: SomeReal): SomeReal
+proc ceil*(m: MathLib, f: SomeReal): SomeReal
+proc clz32*(m: MathLib, f: SomeInteger): int
+proc cos*(m: MathLib, a: SomeNumber): float
+proc cosh*(m: MathLib, a: SomeNumber): float
+proc exp*(m: MathLib, a: SomeNumber): float
+proc expm1*(m: MathLib, a: SomeNumber): float
+proc floor*(m: MathLib, f: SomeReal): int
+proc fround*(m: MathLib, f: SomeReal): float32
+proc hypot*(m: MathLib, args: varargs[distinct SomeNumber]): float
+proc imul*(m: MathLib, a, b: int32): int32
+proc log*(m: MathLib, a: SomeNumber): float
+proc log10*(m: MathLib, a: SomeNumber): float
+proc log1p*(m: MathLib, a: SomeNumber): float
+proc log2*(m: MathLib, a: SomeNumber): float
+proc max*(m: MathLib, a, b: SomeNumber): SomeNumber
+proc min*[T: SomeNumber | JsRoot](m: MathLib, a, b: T): T
+proc pow*(m: MathLib, a, b: distinct SomeNumber): float
+proc random*(m: MathLib): float
+proc round*(m: MathLib, f: SomeReal): int
+proc sign*(m: MathLib, f: SomeNumber): int
+proc sin*(m: MathLib, a: SomeNumber): float
+proc sinh*(m: MathLib, a: SomeNumber): float
+proc sqrt*(m: MathLib, f: SomeReal): SomeReal
+proc tan*(m: MathLib, a: SomeNumber): float
+proc tanh*(m: MathLib, a: SomeNumber): float
+proc trunc*(m: MathLib, f: SomeReal): int
+
+# Date library
+proc now*(d: DateLib): int
+proc UTC*(d: DateLib): int
+proc parse*(d: DateLib, s: cstring): int
+
+proc newDate*(): DateTime {.
+  importcpp: "new Date()".}
+
+proc newDate*(date: int|string): DateTime {.
+  importcpp: "new Date(#)".}
+
+proc newDate*(year, month, day, hours, minutes,
+             seconds, milliseconds: int): DateTime {.
+  importcpp: "new Date(#,#,#,#,#,#,#)".}
+
+proc getDay*(d: DateTime): int
+proc getFullYear*(d: DateTime): int
+proc getHours*(d: DateTime): int
+proc getMilliseconds*(d: DateTime): int
+proc getMinutes*(d: DateTime): int
+proc getMonth*(d: DateTime): int
+proc getSeconds*(d: DateTime): int
+proc getYear*(d: DateTime): int
+proc getTime*(d: DateTime): int
+proc toString*(d: DateTime): cstring
+
+#JSON library
+proc stringify*(l: JsonLib, s: JsRoot): cstring
+proc parse*(l: JsonLib, s: cstring): JsRoot
+
+{.pop.}
diff --git a/lib/js/jsffi.nim b/lib/js/jsffi.nim
index f34efe9a2..6e48db6c7 100644
--- a/lib/js/jsffi.nim
+++ b/lib/js/jsffi.nim
@@ -70,22 +70,31 @@ template mangleJsName(name: cstring): cstring =
   "mangledName" & $nameCounter
 
 type
-  JsRoot* = ref object of RootObj
-    ## Root type of both JsObject and JsAssoc
   JsObject* = ref object of JsRoot
     ## Dynamically typed wrapper around a JavaScript object.
   JsAssoc*[K, V] = ref object of JsRoot
     ## Statically typed wrapper around a JavaScript object.
+
   NotString = concept c
     c isnot string
   js* = JsObject
 
-var jsarguments* {.importc: "arguments", nodecl}: JsObject
-  ## JavaScript's arguments pseudo-variable
+var
+  jsArguments* {.importc: "arguments", nodecl}: JsObject
+    ## JavaScript's arguments pseudo-variable
+  jsNull* {.importc: "null", nodecl.}: JsObject
+    ## JavaScript's null literal
+  jsUndefined* {.importc: "undefined", nodecl.}: JsObject
+    ## JavaScript's undefined literal
+  jsDirname* {.importc: "__dirname", nodecl.}: cstring
+    ## JavaScript's __dirname pseudo-variable
+  jsFilename* {.importc: "__filename", nodecl.}: cstring
+    ## JavaScript's __filename pseudo-variable
 
 # New
 proc newJsObject*: JsObject {. importcpp: "{@}" .}
   ## Creates a new empty JsObject
+
 proc newJsAssoc*[K, V]: JsAssoc[K, V] {. importcpp: "{@}" .}
   ## Creates a new empty JsAssoc with key type `K` and value type `V`.
 
@@ -97,13 +106,16 @@ proc hasOwnProperty*(x: JsObject, prop: cstring): bool
 proc jsTypeOf*(x: JsObject): cstring {. importcpp: "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 {.importcpp: "(new #)".}
   ## Turns a regular function call into an invocation of the
   ## JavaScript's `new` operator
 
-proc jsdelete*(x: auto): JsObject {.importcpp: "(delete #)".}
+proc jsDelete*(x: auto): JsObject {.importcpp: "(delete #)".}
   ## JavaScript's `delete` operator
 
+proc require*(module: cstring): JsObject {.importc.}
+  ## JavaScript's `require` function
+
 # Conversion to and from JsObject
 proc to*(x: JsObject, T: typedesc): T {. importcpp: "(#)" .}
   ## Converts a JsObject `x` to type `T`.
diff --git a/lib/system.nim b/lib/system.nim
index 5aeddaf39..1d1b2d2d2 100644
--- a/lib/system.nim
+++ b/lib/system.nim
@@ -649,6 +649,11 @@ when defined(nimNewRuntime):
   ESynch: Exception
 ].}
 
+when defined(js) or defined(nimdoc):
+  type
+    JsRoot* = ref object of RootObj
+      ## Root type of the JavaScript object hierarchy
+
 proc unsafeNew*[T](a: var ref T, size: Natural) {.magic: "New", noSideEffect.}
   ## creates a new object of type ``T`` and returns a safe (traced)
   ## reference to it in ``a``. This is **unsafe** as it allocates an object
@@ -4080,6 +4085,25 @@ template closureScope*(body: untyped): untyped =
   ##   myClosure() # outputs 3
   (proc() = body)()
 
+template once*(body: untyped): untyped =
+  ## Executes a block of code only once (the first time the block is reached).
+  ## When hot code reloading is enabled, protects top-level code from being
+  ## re-executed on each module reload.
+  ##
+  ## .. code-block:: nim
+  ## proc draw(t: Triangle) =
+  ##   once:
+  ##     graphicsInit()
+  ##
+  ##   line(t.p1, t.p2)
+  ##   line(t.p2, t.p3)
+  ##   line(t.p3, t.p1)
+  ##
+  var alreadyExecuted {.global.} = false
+  if not alreadyExecuted:
+    alreadyExecuted = true
+    body
+
 {.pop.} #{.push warning[GcMem]: off, warning[Uninit]: off.}
 
 when defined(nimconfig):
diff --git a/web/website.ini b/web/website.ini
index be3fbb6e3..56b7c436f 100644
--- a/web/website.ini
+++ b/web/website.ini
@@ -77,5 +77,6 @@ webdoc: "wrappers/mysql;wrappers/iup"
 webdoc: "wrappers/sqlite3;wrappers/postgres;wrappers/tinyc;wrappers/odbcsql"
 webdoc: "wrappers/pcre"
 webdoc: "wrappers/openssl"
+webdoc: "js/jscore"
 
 webdoc: "posix/posix;wrappers/odbcsql"