Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update README #228

Merged
merged 2 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@ jobs:
- run: zig build -Doptimize=ReleaseSafe -Dengine=v8
- name: run benchmark
run: |
./zig-out/bin/jsruntime-bench > benchmark.txt
./zig-out/bin/zig-js-runtime-bench > benchmark.txt
cat benchmark.txt

- name: json output
run: ./zig-out/bin/jsruntime-bench --json > benchmark.json
run: ./zig-out/bin/zig-js-runtime-bench --json > benchmark.json

- name: write commit
run: |
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ run: build-release

## Run a JS shell in release-safe mode
shell:
@printf "\e[36mBuilding shell (release safe)...\e[0m\n"
@printf "\e[36mBuilding shell (debug)...\e[0m\n"
@$(ZIG) build shell -Dengine=v8 || (printf "\e[33mBuild ERROR\e[0m\n"; exit 1;)

## Test
Expand Down
146 changes: 108 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,49 @@
# JS Runtime lib
# zig-js-runtime

A fast and easy library to integrate a Javascript engine into your Zig project.
A fast and easy library to add a Javascript runtime into your Zig project.

With this library you can:

- add Javascript as a scripting language for your native project (eg. plugin system, game scripting)
- build a basic web browser (this library as been developed to build Browsercore headless)
- build a Javascript runtime (ie. a Node/Deno/Bun competitor)
- add Javascript as a scripting language for your Zig project (eg. plugin system, game scripting)
- build a web browser (this library as been developed for the [Ligthpanda headless browser](https://lightpanda.io))
- build a Javascript runtime (ie. a Node/Bun like)

Features:

- [x] Setup and configure the Javascript engine (v8)
- [x] Generate Zig structs into Javascript functions and objects (at compile time)
- [x] Setup and configure the Javascript engine
- [x] Expose Zig structs as Javascript functions and objects (at compile time)
- [x] Bi-directional "link" between Zig structs and Javascript objects
- [x] Support for inheritance (on Zig structs) and prototype chain (on Javascript objects)
- [x] Support for Javascript asynchronous code (IO event loop)
- [x] Support for Javascript asynchronous code (I/O event loop)

Currently only _v8_ is supported as a Javascript engine, but other engines might be added in the future.
Currently only v8 is supported as a Javascript engine, but other engines might be added in the future.

This library is fully single-threaded to matches the nature of Javascript and avoid any cost of context switching for the Javascript engine.

## Rationale

The _v8_ Javascript engine is quite easy to embed for a basic usage, but it's more difficult to integrate closely in a native project. You need to handle:
Integrate a Javascript engine into a Zig project is not just embeding an external library and making language bindings.
You need to handle other stuffs:

- the creation of your native structs in Javascript (_ObjectTemplate_ and _FunctionTemplate_ in _v8_)
- the callbacks of Javascript actions into your native functions (constructors, getters, setters, methods)
- the memory management between the Javascript Garbage Collector and your native code
- the IO event loop to support asynchronous Javascript code
- the generation of your Zig structs as Javascript functions and objects (_ObjectTemplate_ and _FunctionTemplate_ in v8)
- the callbacks of Javascript actions into your Zig functions (constructors, getters, setters, methods)
- the memory management between the Javascript engine and your Zig code
- the I/O event loop to support asynchronous Javascript code

This library takes care of all this, with no overhead thanks to Zig awesome compile time capabilities.

## Getting started

In your Zig project, let's say you have this basic struct.
In your Zig project, let's say you have this basic struct that you want to expose in Javascript:

```zig
const Person = struct {
first_name: []u8,
last_name: []u8,
age: u32,

// Constructor
// if there is no 'constructor' method defined
// 'new Person()' will be prohibited from JS
// Constructor
// if there is no 'constructor' defined 'new Person()' will raise a TypeError in JS
pub fn constructor(first_name: []u8, last_name: []u8, age: u32) Person {
return .{
.first_name = first_name,
Expand All @@ -52,54 +52,114 @@ const Person = struct {
};
}

// Getter
// must be 'get_<field_name>'
// Getter, 'get_<field_name>'
pub fn get_age(self: Person) u32 {
return self.age;
}

// Setter
// must be 'set_<field_name>'
// Setter, 'set_<field_name>'
pub fn set_age(self: *Person, age: u32) void {
self.age = age;
}

// Method
// must be with '_<method_name>'
pub fn _name(self: Person) []u8 {
// Method, '_<method_name>'
pub fn _lastName(self: Person) []u8 {
return self.last_name;
}
};
```

You can use it in a Javascript script.
You can generate the corresponding Javascript functions at comptime with:

```zig
const jsruntime = @import("jsruntime");
pub const Types = jsruntime.reflect(.{Person});
```

And then use it in a Javascript script:

```javascript
// Creating a new instance of the object
// Creating a new instance of Person
let p = new Person('John', 'Doe', 40);

// Getter
console.log(p.age); // => 40
p.age; // => 40

// Setter
p.age = 41;
console.log(p.age); // => 41
p.age; // => 41

// Method
console.log(p.name()); // 'Doe'
p.lastName(); // => 'Doe'
```

Let's add some inheritance (ie. prototype chain):

```zig
const User = struct {
proto: Person,
role: u8,

pub const prototype = *Person;

pub fn constructor(first_name: []u8, last_name: []u8, age: u32, role: u8) User {
const proto = Person.constructor(first_name, last_name, age);
return .{ .proto = proto, .role = role };
}

pub fn get_role(self: User) u8 {
return self.role;
}
};

pub const Types = jsruntime.reflect(.{Person, User});
```

And use it in a Javascript script:

```javascript
// Creating a new instance of User
let u = new User('Jane', 'Smith', 35, 1); // eg. 1 for admin

// we can use the User getters/setters/methods
u.role; // => 1

// but also the Person getters/setters/methods
u.age; // => 35
u.age = 36;
u.age; // => 36
u.lastName(); // => 'Smith'

// checking the prototype chain
u instanceof User == true;
u instanceof Person == true;
User.prototype.__proto__ === Person.prototype;
```

### Javascript shell

A Javascript shell is provided as an example in `src/main_shell.zig`.

```sh
$ make shell

zig-js-runtime - Javascript Shell
exit with Ctrl+D or "exit"

>
```

## Build

### Prerequisites

jsruntime-lib is written with [Zig](https://ziglang.org/) `0.12`. You have to
zig-js-runtime is written with [Zig](https://ziglang.org/) `0.12`. You have to
install it with the right version in order to build the project.

To be able to build the v8 engine, you have to install some libs:

For Debian/Ubuntu based Linux:
```
```sh
sudo apt install xz-utils \
python3 ca-certificates git \
pkg-config libglib2.0-dev clang
Expand All @@ -113,7 +173,7 @@ The project uses git submodule for dependencies.
The `make install-submodule` will init and update the submodules in the `vendor/`
directory.

```
```sh
make install-submodule
```

Expand All @@ -125,23 +185,33 @@ Be aware the build task is very long and cpu consuming.
Build v8 engine for debug/dev version, it creates
`vendor/v8/$ARCH/debug/libc_v8.a` file.

```
```sh
make install-v8-dev
```

You should also build a release vesion of v8 with:

```
```sh
make install-v8
```

### All in one build

You can run `make intall` and `make install-dev` to install deps all in one.
You can run `make install` and `make install-dev` to install deps all in one.

## Development

Some Javascript features are not supported yet:

- [ ] [Promises](https://github.com/lightpanda-io/zig-js-runtime/issues/73) and [micro-tasks](https://github.com/lightpanda-io/zig-js-runtime/issues/56)
- [ ] Some Javascript types, including [Arrays](https://github.com/lightpanda-io/zig-js-runtime/issues/52)
- [ ] [Function overloading](https://github.com/lightpanda-io/zig-js-runtime/issues/54)
- [ ] [Types static methods](https://github.com/lightpanda-io/zig-js-runtime/issues/127)
- [ ] [Non-optional nullable types](https://github.com/lightpanda-io/zig-js-runtime/issues/72)

## Test
### Test

You can test the jsruntime-lib by running `make test`.
You can test the zig-js-runtime library by running `make test`.

## Credits

Expand Down
6 changes: 3 additions & 3 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pub fn build(b: *std.Build) !void {

// compile and install
const bench = b.addExecutable(.{
.name = "jsruntime-bench",
.name = "zig-js-runtime-bench",
.root_source_file = .{ .path = "src/main_bench.zig" },
.single_threaded = true,
.target = target,
Expand Down Expand Up @@ -77,7 +77,7 @@ pub fn build(b: *std.Build) !void {

// compile and install
const shell = b.addExecutable(.{
.name = "jsruntime-shell",
.name = "zig-js-runtime-shell",
.root_source_file = .{ .path = "src/main_shell.zig" },
.target = target,
.optimize = mode,
Expand All @@ -90,7 +90,7 @@ pub fn build(b: *std.Build) !void {
shell.strip = true;
}
// do not install shell binary
// shell.install();
// b.installArtifact(shell);

// run
const shell_cmd = b.addRunArtifact(shell);
Expand Down
2 changes: 1 addition & 1 deletion src/main_shell.zig
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@ pub fn main() !void {
defer arena.deinit();

// launch shell
try public.shell(&arena, null, .{ .app_name = "jsruntime-shell" });
try public.shell(&arena, null, .{ .app_name = "zig-js-runtime-shell" });
}
4 changes: 3 additions & 1 deletion src/shell.zig
Original file line number Diff line number Diff line change
Expand Up @@ -284,9 +284,11 @@ fn repl() !void {

// greetings
printStdout(
\\JS Repl
\\
\\zig-js-runtime - Javascript Shell
\\exit with Ctrl+D or "exit"
\\
\\
, .{});

// create a socket client connected to the internal server
Expand Down
Loading