nodejs
Version, currently 0.1.78 versions
github.com/fukaoi/crystal-nodejs
Node.js engine for crystal-lang. JS code and npm module executes on crystal-nodejs
52 stars
0 dependents
License: MIT
Installation
# Add this to your shard.yml
dependencies:
nodejs:
github: fukaoi/crystal-nodejs
version: ~> 0.1.7Then run:
shards installshard.yml
- Crystal
1.0.0- License
- MIT
- Author
- fukaoi
- Target
nodejsfrom src/nodejs.cr
Dependencies
Development Dependencies
- ameba0.14.3github: veelenga/amebadev
README
# crystal-nodejs

Node.js engine for crystal-lang. JS code and npm module executes on crystal-nodejs, And don't need to install Node.js binary.Explain about architecture, Compiled as a shared object Node.js(i.e: libnodejs) and execute as one process on crystal-lang. So call C execvp() system call through Process.run() method, execute in this c function.
Process.run method is low overhead, Compare pure Node.js js code and crystal-nodejs js code, There was no difference in performance(see Benchmark heading about performance detail).
The biggest merit is that **over 11,000,000** npm modules can be used in crystal and Will expand the Crystal Lang ecosystem
Using crystal-nodejs can pass through Node.js result of crystal-lang. Happy Crystaling!! :tada:
<h4>Architecture image</h4>
<img src="docs/architecture_image.png">
In the above architecture image, crystal-nodejs provides Node.js, LibC, lib that was created crystal-lang
## Supported OS
- Linux(Ubuntu18.04, 20.04, Arch Linux...Maybe will work other distributions)
- MacOSX(High Sierra... Maybe will work other os version)
- Windows(Unfortunately yet not supported on crystal-lang...)
## Supported Node.js
* Look at [this](https://github.com/fukaoi/crystal-nodejs/tree/master/ext) directory(e.g 12.20.1, 14.15.4) for supported versions.crystal-nodejs will be supported until latest 2 version in Node.js
* Current Node.js latest version: 14.15.4
## Installation
1. Add the dependency to your `shard.yml`:
```yaml
dependencies:
nodejs:
github: fukaoi/crystal-nodejs
```
2. Run `shards install`
3. libnodejs install, init js directory `make or make all`.So created $HOME/.crystal-nodejs/
```
$HOME/.crystal-nodejs/
├── bin // Use binary in crystal-nodejs
│ ├── node
│ ├── npm
│ └── npx
├── js // Origina JS code, package.json. Enable the subdirectories below js/*/a.js
└── lib // shared object for Node.js
└── libnode.so.64(libnode.64.dylib)
```
## Using Docker
### alpine
```
FROM crystallang/crystal:0.35.1-alpine as builder
RUN apk add --update --no-cache \
curl \
make \
python \
g++ \
gcc \
gcc-doc \
linux-headers \
libc6-compat
RUN ln -s /lib/libc.musl-x86_64.so.1 /lib/ld-linux-x86-64.so.2
RUN git clone https://github.com/fukaoi/crystal-nodejs.git
WORKDIR /crystal-nodejs
RUN shards install
```
### Ubuntu20
```
FROM ubuntu:20.04 as builder
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update
RUN apt-get upgrade -y
RUN apt-get install curl gnupg make g++ libyaml-dev -y
RUN curl -sL "https://keybase.io/crystal/pgp_keys.asc" | apt-key add -
RUN echo "deb https://dist.crystal-lang.org/apt crystal main" | tee /etc/apt/sources.list.d/crystal.list
RUN apt-get update
RUN apt-get install crystal -y
RUN git clone https://github.com/fukaoi/crystal-nodejs.git
WORKDIR /crystal-nodejs
RUN shards install
```
## Usage
### Bacis usage
#### Output Hello crystal-nodejs !! on terminal
* Simple JS code, output 'Hello crystal-nodejs'
```js
require "nodejs"
Nodejs.eval("console.log('Hello crystal-nodejs !!')")
```
<br />
#### Use setTimeout execute lazy code
* below code is output 'Hello crystal' after 2sec
```crystal
require "nodejs"
code = <<-CODE
let user = {
sayHi(firstName) {
console.log(`Hello, ${firstName}!`);
}
};
setTimeout(() => {user.sayHi('crystal')}, 2000);
CODE
Nodejs.eval(code)
```
<br />
#### How to send Node.js result to crystal-lang
* Use special `toCrystal()` method.toCrystal() is only function in crystal-nodejs.Can response various type in JS, and all JS type is converted `JSON::Any` in crystal
```crystal
require "nodejs"
code = <<-CODE
toCrystal({data:"spec"})
CODE
res = Nodejs.eval(code)
puts res # {"data" => "spec"}
puts typeof(res) # JSON::Any
```
<br />
* The below example, all JS type(Number, Boolean, String) is converted `JSON::Any`.
```crystal
require "nodejs"
code = <<-CODE
toCrystal({123456})
CODE
res = Nodejs.eval(code)
puts res # 123456
puts typeof(res) # JSON::Any
```
<br />
### Use NPM(node modules)
Node modules is installed in $HOME/.crystal-nodejs/js/
#### npm install method
* Use Nodejs::Npm.install('package name') method, can install need package.this below example is for use [mathjs](https://www.npmjs.com/package/mathjs) module.
```crystal
require "nodejs"
Nodejs::Npm.install("mathjs")
code = <<-CODE
const math = require("mathjs"); // Note: JS side require
toCrystal(math.log(10000, 10));
CODE
puts Nodejs.eval(code) # 4
```
<br />
#### package.json
* a package.json copy to $HOME/.crystal-nodejs/js/ direcotry, and call NodeJs::Npm.install method
[package.json]
```js
"dependencies": {
"mathjs": "^6.0.2"
}
```
```crystal
require "nodejs"
Nodejs::Npm.install
code = <<-CODE
const math = require("mathjs"); // Note: JS side require
toCrystal(math.log(10000, 10));
CODE
puts Nodejs.eval(code) # 4
```
<br />
### Use existing the JS file
#### loading JS file and execute
* existing js file to, use Node Js.file run method, can run on crystal-lang
[file_run.js]
```js
const fs = require('fs');
fs.readFile('spec/nodejs_spec.cr', 'utf8', (err, text) => {
console.log('text file!');
toCrystal({text: text});
});
```
```crystal
require "nodejs"
puts Nodejs.file_run("./file_run.js")
```
<br />
#### Replace parameters in existing JS file
* Use Nodejs.replace_params() method, can set the customization value to the parameter of the existing js file from crystal-lang. this method merit don't have to modify existing js files and can use on crystal-lang
* replace parameter type can be only `const xxxxx`.
[calc.js]
```js
const a = 10;
const b = 20;
const calc = (left_side, right_side) => {
return left_side + right_side;
}
const res = calc(a, b);
console.log(res);
```
```crystal
require "nodejs"
code = File.read("./calc.js")
hash = {
"a" => 100, # const a = 100
"b" => 200 # const b = 200
}
replaced_code = Nodejs.replace_params(code, hash)
Nodejs.eval(replaced_code) # 300
```
<br />
### Catch JS Exception
#### throw errors directly
* throw Error in JS, it is converted as `JSSideExceptin` in crystal-lang. So can catch JSSIDEExcetpion in crystal-lang.
```crystal
require "nodejs"
code = <<-CODE
throw new Error('Error raise!');
CODE
Nodejs.eval(code)
```
```crystal
Error: Error raise!
at [eval]:10:9
at Script.runInThisContext (vm.js:122:20)
at Object.runInThisContext (vm.js:329:38)
at Object.<anonymous> ([eval]-wrapper:6:22)
at Module._compile (internal/modules/cjs/loader.js:776:30)
at evalScript (internal/bootstrap/node.js:589:27)
at startup (internal/bootstrap/node.js:265:9)
at bootstrapNodeJSCore (internal/bootstrap/node.js:622:3)
(Nodejs::JSSideException)
```
<br />
#### Error object throw after catch the JS Error with `try catch`
* can pass an error object to crystal-lang by using a specific `toCrystalErr` method.
```crystal
require "nodejs"
code = <<-CODE
try {
throw new Error('Error raise!');
} catch(e) {
toCrystalErr(e);
}
CODE
Nodejs.eval(code)
```
```crystal
Error: Error raise!
at [eval]:10:8
at Script.runInThisContext (vm.js:122:20)
at Object.runInThisContext (vm.js:329:38)
at Object.<anonymous> ([eval]-wrapper:6:22)
at Module._compile (internal/modules/cjs/loader.js:776:30)
at evalScript (internal/bootstrap/node.js:589:27)
at startup (internal/bootstrap/node.js:265:9)
at bootstrapNodeJSCore (internal/bootstrap/node.js:622:3)
(Nodejs::JSSideException)
```
#### More usages look at [spec/nodejs/npm/](https://github.com/fukaoi/crystal-nodejs/tree/master/spec/nodejs/npm)
## Benchmark
This benchmark is fibonacci and binary-search results, As can see from the result, crystal-nodejs is high performance.below at bar chart is response average time that executes 5 times fibonacci and binary-search. benchmark source code detail is can see from URL link
* Machine spec:
* cpu: Intel(R) Core(TM) i7-7820HK CPU @ 2.90GHz 4core
* memory: 16Gbyte
<br />
* Benchmark code:
* fibonacci source: [Node.js](https://github.com/fukaoi/crystal-nodejs/blob/benchmark/bench/fibonacci.js) [crystal-lang](https://github.com/fukaoi/crystal-nodejs/blob/benchmark/bench/binarysearch.cr)
* binary-search source: [Node.js](https://github.com/fukaoi/crystal-nodejs/blob/benchmark/bench/binarysearch.js) [crystal-lang](https://github.com/fukaoi/crystal-nodejs/blob/benchmark/bench/binarysearch.cr)
<img src="docs/barchart.png">
#### Raw data(Benchmark result)
* fibonacci
| - | Node.js | crystal-nodejs |
| ---- | ---- | ---- |
| 1 | real 2m26.968s | real 2m27.337s |
| 2 | real 2m27.230s | real 2m27.263s |
| 3 | real 2m26.987s | real 2m28.058s |
| 4 | real 2m27.582s | real 2m26.672s |
| 5 | real 2m27.262s | real 2m26.831s |
* binary-search
| - | Node.js | crystal-nodejs |
| ---- | ---- | ---- |
| 1 | real 1m35.699s | real 1m35.962s |
| 2 | real 1m35.082s | real 1m32.018s |
| 3 | real 1m33.309s | real 1m35.212s |
| 4 | real 1m34.423s | real 1m33.830s |
| 5 | real 1m32.050s | real 1m33.530s |
## Development
#### Extension directory tree
```
crystal-nodejs/
ext/
├── libnode.cc // main function for libnode.so
├── node_path.cr // script for rewrite node path of npm
├── obj // shared object of Linux, Mac OSX
│ └── 10.16.0
└── 10.16.0 // shared object dependency file and directory
├── bin
├── include
└── lib
```
#### Create json class
May want to convert crystal-lang json mapper type because response parameter from Nodejs.eval() method is all JSON::Any type. So the recommended tool is [jsontocr](https://github.com/molnarmark/jsontocr), very simple and easy.
#### Exception
* CrystalSideException ... Be thrown error in crystal-lang code
* JSSideException ... Be thrown error in JS code
#### Make tasks
* make build ... build libnode and deploy $HOME/.crystal-nodejs/
* make install ... using package.json, npm install of dependency modules
* make nodejs ... git clone Node.js and build
* make (all) ... Same task make build && make install
* make clean ... delete $HOME/.crystal-nodejs/ and temporaty directory
* make audit ... do static analysis for JS
#### Raw JS
If will do mixing crystal-lang code and JS code, Debug is hard. But crystal-nodejs can export Raw JS, Can easily see the JS code that is actually executed.
this function is enable below
```crystal
RAW_JS=true crystal run(or spec)
```
`RAW_JS=true` best timing is when execute `spec`, because must call Nodejs.eval() method.Export of most Raw JS if coverage of spec code is high.
To see exported the code, **/tmp/raw_js/**
```bash
/tmp/raw_js/
├── 0cc6107a79be7b828321e57a81a5f828.js
├── 124d1032b2bc7c02cd59430dd8d1ba65.js
├── 170fa95978969d76eca70d2947da4e2f.js
├── 31df5239bef6b4510b8b6e3840627a58.js
├── eaaa61190908871f1571bc5eca89b87a.js
├── ff3c96e301d7d34c34c4a86fef92e09b.js
└── ff4d0b01de3d93d612bb5a09ed381711.js
```
#### Safety
Scan for vulnerabilities when build project. If be found vulnerabilities, build is supposed to fail.this logic execute `npm audit` command in Makefile.execute `npm audit --fix` command and build continue
use `make audit`, export raw JS to /tmp/raw_js/ and do static analysis with eslint-plugin-security
## Case study
Now writting...
## Contributing
1. Fork it (<https://github.com/fukaoi/crystal-nodejs/fork>)
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request
## Contributors
- [fukaoi](https://github.com/fukaoi) - creator and maintainer
Documentation
Built from the current release. The first visit to a release nobody has asked for starts its build.
Links
This release
- Version
0.1.7- Tagged
- May 11, 2021
- Commit
1336f6d9736d- Crystal
1.0.0- Indexed
- yes
Dependents
No indexed shard depends on this one yet.
Repository
github.com/fukaoi/crystal-nodejs
Metadata
- Created
- Aug 12, 2026
- Updated
- Aug 13, 2026
- Synced
- Aug 13, 2026
- Versions
- 8