Skip to content

Commit

Permalink
nodeJs: initial commit
Browse files Browse the repository at this point in the history
The nodeJs tool is used to run new ES JavaScript code in Node.js
  • Loading branch information
Matthew Giannini authored and Matthew Giannini committed Aug 14, 2023
1 parent a2051d8 commit 9a151aa
Show file tree
Hide file tree
Showing 10 changed files with 791 additions and 0 deletions.
45 changes: 45 additions & 0 deletions src/nodeJs/build.fan
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#! /usr/bin/env fan
//
// Copyright (c) 2006, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
// 17 Jul 2023 Matthew Giannini creation
//

using build

**
** Build: nodeJs
**
class Build : BuildPod
{
new make()
{
podName = "nodeJs"
summary = "Utilities for running Fantom in Node JS"
meta = ["org.name": "Fantom",
"org.uri": "https://fantom.org/",
"proj.name": "Fantom Core",
"proj.uri": "https://fantom.org/",
"license.name": "Academic Free License 3.0",
"vcs.name": "Git",
"vcs.uri": "https://github.com/fantom-lang/fantom",
]
depends = ["sys 1.0",
"compiler 1.0",
"compilerEs 1.0",
"fandoc 1.0",
"util 1.0",
]
srcDirs = [
`fan/`,
`fan/cmd/`,
// `fan/ts/`,
]
resDirs = [
`res/`,
]
docApi = false
}
}
202 changes: 202 additions & 0 deletions src/nodeJs/fan/EmitUtil.fan
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
//
// Copyright (c) 2023, SkyFoundry LLC
// All Rights Reserved
//
// History:
// 27 Jul 2023 Matthew Giannini Creation
//

using compilerEs
using util

**
** Utility for emitting various JS code
**
internal class EmitUtil
{

//////////////////////////////////////////////////////////////////////////
// Constructor
//////////////////////////////////////////////////////////////////////////

new make(NodeJsCmd cmd)
{
this.cmd = cmd
}

private NodeJsCmd cmd
private Pod[] depends := [Pod.find("sys")]
private File? scriptJs := null
private ModuleSystem ms() { cmd.ms }
private Bool isCjs() { ms.moduleType == "cjs" }

//////////////////////////////////////////////////////////////////////////
// Configure Dependencies
//////////////////////////////////////////////////////////////////////////

** Configure the pod dependencies before emitting any code
This withDepends(Pod[] pods)
{
this.depends = Pod.orderByDepends(Pod.flattenDepends(pods))
return this
}

** Configure the script js for a Fantom script
This withScript(File scriptJs)
{
this.scriptJs = scriptJs
return this
}

//////////////////////////////////////////////////////////////////////////
// Util
//////////////////////////////////////////////////////////////////////////

private File? podJsFile(Pod pod, Str name := pod.name)
{
ext := isCjs ? "js" : "mjs"
script := "${name}.${ext}"
return pod.file(`/js/$script`, false)
}

//////////////////////////////////////////////////////////////////////////
// Emit
//////////////////////////////////////////////////////////////////////////

Void writePackageJson([Str:Obj?] json := [:])
{
if (json["name"] == null) json["name"] = "@fantom/fan"
if (json["version"] == null) json["version"] = Pod.find("sys").version.toStr
ms.writePackageJson(json)
}

** Copy all pod js files into '<dir>/node_modules/'.
// ** Also copies in mime.js, units.js, and indexed-props.js
Void writeNodeModules()
{
writeFanJs
writeNode
writeDepends
writeScriptJs
writeMimeJs
writeUnitsJs
// TODO: indexed-props?
}

** Write 'es6.js' (grab it from sys.js)
Void writeFanJs()
{
out := ms.file("fan").out
podJsFile(Pod.find("sys"), "fan").in.pipe(out)
out.flush.close
}


** Write 'es6.js' (grab it from sys.js)
Void writeEs6()
{
out := ms.file("es6").out
podJsFile(Pod.find("sys"), "es6").in.pipe(out)
out.flush.close
}

** Write 'node.js'
Void writeNode()
{
modules := ["os", "path", "fs", "crypto", "url", "zlib"]
out := ms.file("node").out
ms.writeBeginModule(out)
modules.each |m, i| { ms.writeInclude(out, m) }
ms.writeExports(out, modules)
ms.writeEndModule(out).flush.close
}

** Write js from configured pod dependencies
Void writeDepends()
{
copyOpts := ["overwrite": true]

this.depends.each |pod|
{
file := podJsFile(pod)
target := ms.file(pod.name)
if (file != null)
{
file.copyTo(target, copyOpts)
// if (pod.name == "sys")
// {
// out := target.out
// file.in.pipe(out)
// out.flush.close
// }
// else file.copyTo(target, copyOpts)
}
}
}

** Write the fantom script if one was configured
Void writeScriptJs()
{
if (scriptJs == null) return
out := ms.file(scriptJs.basename).out
try
{
scriptJs.in.pipe(out)
}
finally out.flush.close
}

** Write the code for configuring MIME types to 'fan_mime.js'
Void writeMimeJs()
{
out := ms.file("fan_mime").out
JsExtToMime(ms).write(out)
out.flush.close
}

** Write the unit database to 'fan_units.js'
Void writeUnitsJs()
{
out := ms.file("fan_units").out
JsUnitDatabase(ms).write(out)
out.flush.close
}

//////////////////////////////////////////////////////////////////////////
// Str
//////////////////////////////////////////////////////////////////////////

** Get a Str with all the include statements for the configured
** dependencies that is targetted for the current module system
** This method assumes the script importing the modules is in
** the parent directory.
Str includeStatements()
{
baseDir := "./${ms.moduleDir.name}/"
buf := Buf()
this.depends.each |pod|
{
if ("sys" == pod.name)
{
// need explicit js ext because node has built-in lib named sys
ms.writeInclude(buf.out, "sys.ext", baseDir)
ms.writeInclude(buf.out, "fan_mime.ext", baseDir)
}
else ms.writeInclude(buf.out, "${pod.name}.ext", baseDir)
}
if (scriptJs != null) throw Err("TODO: script js")
// if (scriptJs != null)
// buf.add("import * as ${scriptJs.basename} from './${ms.moduleType}/${scriptJs.name}';\n")
return buf.flip.readAllStr
}

** Get the JS code to configure the Env home, work and temp directories.
Str envDirs()
{
buf := StrBuf()
buf.add(" sys.Env.cur().__homeDir = sys.File.os(${Env.cur.homeDir.pathStr.toCode});\n")
buf.add(" sys.Env.cur().__workDir = sys.File.os(${Env.cur.workDir.pathStr.toCode});\n")
buf.add(" sys.Env.cur().__tempDir = sys.File.os(${Env.cur.tempDir.pathStr.toCode});\n")
return buf.toStr
}
}
31 changes: 31 additions & 0 deletions src/nodeJs/fan/Main.fan
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// Copyright (c) 2023, SkyFoundry LLC
// All Rights Reserved
//
// History:
// 27 Jul 2023 Matthew Giannini Creation
//

using util

**
** Command line main
**
class Main
{
static Int main(Str[] args)
{
// lookup command
if (args.isEmpty) args = ["help"]
name := args.first
cmd := NodeJsCmd.find(name)
if (cmd == null)
{
echo("ERROR: unknown nodeJs command '$name'")
return 1
}

// strip commandname from args and process as util::AbstractMain
return cmd.main(args.dup[1..-1])
}
}
99 changes: 99 additions & 0 deletions src/nodeJs/fan/NodeJsCmd.fan
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//
// Copyright (c) 2023, SkyFoundry LLC
// All Rights Reserved
//
// History:
// 27 Jul 2023 Matthew Giannini Creation
//

using compilerEs
using util

**
** NodeJs command target
**
abstract class NodeJsCmd : AbstractMain
{
** Find a specific target or return null
static NodeJsCmd? find(Str name)
{
list.find |t| { t.name == name || t.aliases.contains(name) }
}

** List installed commands
static NodeJsCmd[] list()
{
NodeJsCmd[] acc := NodeJsCmd#.pod.types.mapNotNull |t->NodeJsCmd?|
{
if (t.isAbstract || !t.fits(NodeJsCmd#)) return null
return t.make
}
acc.sort |a, b| { a.name <=> b.name }
return acc
}

** App name is "nodeJs {name}"
override final Str appName() { "nodeJs ${name}" }

** Log name is "nodeJs"
override Log log() { Log.get("nodeJs") }

** Command name
abstract Str name()

** Command aliases/shortcuts
virtual Str[] aliases() { Str[,] }

** Run the command. Return zero on success
abstract override Int run()

** Single line summary of the command for help
abstract Str summary()

@Opt { help="Verbose debug output"; aliases=["v"] }
Bool verbose

@Opt { help = "Root directory for staging Node.js environment"; aliases = ["d"] }
virtual File dir := Env.cur.tempDir.plus(`nodeJs/`)

@Opt { help = "Emit CommonJs" }
Bool cjs := false

//////////////////////////////////////////////////////////////////////////
// NodeJs
//////////////////////////////////////////////////////////////////////////

protected Bool checkForNode()
{
cmd := ["which", "-s", "node"]
if ("win32" == Env.cur.os) cmd = ["where", "node"]
if (Process(cmd) { it.out = null }.run.join != 0)
{
err("Node not found")
printLine("Please ensure Node.js is installed and available in your PATH")
return false
}
return true
}

** Get the module system environment
once ModuleSystem ms()
{
return this.cjs ? CommonJs(this.dir) : Esm(this.dir)
}

** Get the JS emit utility
internal once EmitUtil emit() { EmitUtil(this) }

//////////////////////////////////////////////////////////////////////////
// Console
//////////////////////////////////////////////////////////////////////////

** Print a line to stdout
Void printLine(Str line := "") { Env.cur.out.printLine(line) }

** Print error message and return 1
Int err(Str msg) { printLine("ERROR: ${msg}"); return 1 }


}
Loading

0 comments on commit 9a151aa

Please sign in to comment.