├── .formatter.exs ├── .gitignore ├── README.md ├── bench ├── benchmarks.ex └── mix.tasks.benchmark.ex ├── lib ├── yavascript.ex └── yavascript │ ├── bun_engine │ └── bun_engine.ex │ ├── deno_engine │ └── deno_engine.ex │ ├── engine.ex │ ├── mozjs_engine │ └── mozjs_engine.ex │ ├── node_engine │ └── node_engine.ex │ └── spider_monkey_engine │ ├── include │ └── js.h │ ├── js.cpp │ └── spider_monkey_engine.ex ├── mix.exs └── test ├── bun └── bun_test.exs ├── deno └── deno_test.exs ├── node └── node_test.exs ├── spider_monkey └── spider_monkey_test.exs ├── test_helper.exs └── yavascript_test.exs /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | yavascript-*.tar 24 | 25 | # Temporary files, for example, from tests. 26 | /tmp/ 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Yavascript 2 | 3 | **TODO: Add description** 4 | 5 | ## Installation 6 | 7 | If [available in Hex](https://hex.pm/docs/publish), the package can be installed 8 | by adding `yavascript` to your list of dependencies in `mix.exs`: 9 | 10 | ```elixir 11 | def deps do 12 | [ 13 | {:yavascript, "~> 0.1.0"} 14 | ] 15 | end 16 | ``` 17 | 18 | Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) 19 | and published on [HexDocs](https://hexdocs.pm). Once published, the docs can 20 | be found at [https://hexdocs.pm/yavascript](https://hexdocs.pm/yavascript). 21 | 22 | -------------------------------------------------------------------------------- /bench/benchmarks.ex: -------------------------------------------------------------------------------- 1 | defmodule Yavascript.Benchmarks do 2 | def build(benchmarks) do 3 | benchmarks 4 | |> Enum.map( 5 | fn module-> 6 | quote do 7 | benchmark_module = Module.concat(unquote(module), Benchmark) 8 | defmodule benchmark_module do 9 | use Yavascript, 10 | engine: unquote(module), 11 | import: [my_function: 2], 12 | script: """ 13 | const my_function = (a, b) => a + b; 14 | """ 15 | end 16 | end 17 | end) 18 | |> Enum.each(&Code.eval_quoted/1) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /bench/mix.tasks.benchmark.ex: -------------------------------------------------------------------------------- 1 | require Yavascript.Benchmarks 2 | 3 | engines = 4 | [Yavascript.BunEngine, 5 | Yavascript.DenoEngine, 6 | Yavascript.NodeEngine, 7 | Yavascript.SpiderMonkeyEngine, 8 | Yavascript.MozjsEngine] 9 | 10 | Yavascript.Benchmarks.build(engines) 11 | 12 | defmodule Mix.Tasks.Benchmark do 13 | def run(_) do 14 | Yavascript.SpiderMonkeyEngine.init(); 15 | 16 | engines = unquote(engines) 17 | 18 | contexts = Map.new(engines, &{&1, Module.concat(&1, Benchmark).setup()}) 19 | 20 | engines 21 | |> Map.new(fn engine -> 22 | benchmark = Module.concat(engine, Benchmark) 23 | name = "#{inspect engine}" 24 | {name, fn -> 25 | Process.put(:js_context, contexts[engine]) 26 | benchmark.my_function("foo", "bar") 27 | end} 28 | end) 29 | |> Benchee.run 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/yavascript.ex: -------------------------------------------------------------------------------- 1 | defmodule Yavascript do 2 | @type json :: String.t() | number | boolean | nil | [json] | %{optional(String.t()) => json} 3 | defmacro __using__(opts!) do 4 | {opts!, _} = Code.eval_quoted(opts!, [], __CALLER__) 5 | engine = Keyword.fetch!(opts!, :engine) 6 | 7 | functions = 8 | opts! 9 | |> Keyword.fetch!(:import) 10 | |> Enum.map(&engine._build_function(&1)) 11 | 12 | script = Keyword.fetch!(opts!, :script) 13 | 14 | setup = Keyword.get(opts!, :setup, :setup) 15 | 16 | quote do 17 | def unquote(setup)() do 18 | context = unquote(engine).create_context() 19 | 20 | Process.put(:js_context, context) 21 | 22 | unquote(engine).run_script(context, unquote(script), false) 23 | end 24 | 25 | unquote_splicing(functions) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/yavascript/bun_engine/bun_engine.ex: -------------------------------------------------------------------------------- 1 | defmodule Yavascript.BunEngine do 2 | def create_context, do: make_ref() 3 | 4 | def run_script(context, code, needs_result) do 5 | if needs_result do 6 | random_name = 32 |> :crypto.strong_rand_bytes() |> Base.encode16() 7 | 8 | random_filepath = 9 | System.tmp_dir() 10 | |> Path.join("#{random_name}.js") 11 | 12 | full_code = """ 13 | #{context} 14 | 15 | #{code} 16 | """ 17 | 18 | File.write!(random_filepath, full_code) 19 | 20 | {result, 0} = System.cmd("bun", [random_filepath]) 21 | 22 | File.rm!(random_filepath) 23 | 24 | Jason.decode!(result) 25 | else 26 | # this is the base function 27 | Process.put(:js_context, code) 28 | code 29 | end 30 | end 31 | 32 | @spec _build_function({atom, non_neg_integer}) :: Macro.t() 33 | def _build_function({fun, arity}) do 34 | args = 35 | case arity do 36 | 0 -> [] 37 | n -> for i <- 1..n, do: {:"arg#{i}", [], Elixir} 38 | end 39 | 40 | quote do 41 | def unquote(fun)(unquote_splicing(args)) do 42 | context = Process.get(:js_context) || raise "no context!" 43 | 44 | arguments = Jason.encode!(unquote(args)) 45 | code = "console.log(JSON.stringify(#{unquote(fun)}(...JSON.parse('#{arguments}'))))" 46 | 47 | Yavascript.BunEngine.run_script(context, code, true) 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/yavascript/deno_engine/deno_engine.ex: -------------------------------------------------------------------------------- 1 | defmodule Yavascript.DenoEngine do 2 | def create_context, do: make_ref() 3 | 4 | def run_script(context, code, needs_result) do 5 | if needs_result do 6 | full_code = """ 7 | #{context} 8 | 9 | #{code} 10 | """ 11 | 12 | {result, 0} = System.cmd("deno", ["eval", full_code]) 13 | 14 | Jason.decode!(result) 15 | else 16 | # this is the base function 17 | Process.put(:js_context, code) 18 | code 19 | end 20 | end 21 | 22 | @spec _build_function({atom, non_neg_integer}) :: Macro.t() 23 | def _build_function({fun, arity}) do 24 | args = 25 | case arity do 26 | 0 -> [] 27 | n -> for i <- 1..n, do: {:"arg#{i}", [], Elixir} 28 | end 29 | 30 | quote do 31 | def unquote(fun)(unquote_splicing(args)) do 32 | context = Process.get(:js_context) || raise "no context!" 33 | 34 | arguments = Jason.encode!(unquote(args)) 35 | code = "console.log(JSON.stringify(#{unquote(fun)}(...JSON.parse('#{arguments}'))))" 36 | 37 | Yavascript.DenoEngine.run_script(context, code, true) 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/yavascript/engine.ex: -------------------------------------------------------------------------------- 1 | defmodule Yavascript.Engine do 2 | @type context :: term 3 | 4 | @callback init() :: :ok 5 | @callback create_context() :: context 6 | @callback run_script(context, command :: String.t(), needs_result :: boolean) :: 7 | Yavascript.json() | :ok 8 | @callback _build_function({atom, non_neg_integer}) :: Macro.t() 9 | end 10 | -------------------------------------------------------------------------------- /lib/yavascript/mozjs_engine/mozjs_engine.ex: -------------------------------------------------------------------------------- 1 | defmodule Yavascript.MozjsEngine do 2 | def create_context, do: make_ref() 3 | 4 | def run_script(context, code, needs_result) do 5 | if needs_result do 6 | random_name = 32 |> :crypto.strong_rand_bytes() |> Base.encode16() 7 | 8 | random_filepath = 9 | System.tmp_dir() 10 | |> Path.join("#{random_name}.js") 11 | 12 | full_code = """ 13 | #{context} 14 | 15 | #{code} 16 | """ 17 | 18 | File.write!(random_filepath, full_code) 19 | 20 | {result, 0} = System.cmd("js78", [random_filepath]) 21 | 22 | File.rm!(random_filepath) 23 | 24 | Jason.decode!(result) 25 | else 26 | # this is the base function 27 | Process.put(:js_context, code) 28 | code 29 | end 30 | end 31 | 32 | @spec _build_function({atom, non_neg_integer}) :: Macro.t() 33 | def _build_function({fun, arity}) do 34 | args = 35 | case arity do 36 | 0 -> [] 37 | n -> for i <- 1..n, do: {:"arg#{i}", [], Elixir} 38 | end 39 | 40 | quote do 41 | def unquote(fun)(unquote_splicing(args)) do 42 | context = Process.get(:js_context) || raise "no context!" 43 | 44 | arguments = Jason.encode!(unquote(args)) 45 | code = "console.log(JSON.stringify(#{unquote(fun)}(...JSON.parse('#{arguments}'))))" 46 | 47 | Yavascript.MozjsEngine.run_script(context, code, true) 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/yavascript/node_engine/node_engine.ex: -------------------------------------------------------------------------------- 1 | defmodule Yavascript.NodeEngine do 2 | def create_context, do: make_ref() 3 | 4 | def run_script(context, code, needs_result) do 5 | if needs_result do 6 | random_name = 32 |> :crypto.strong_rand_bytes() |> Base.encode16() 7 | 8 | random_filepath = 9 | System.tmp_dir() 10 | |> Path.join("#{random_name}.js") 11 | 12 | full_code = """ 13 | #{context} 14 | 15 | #{code} 16 | """ 17 | 18 | File.write!(random_filepath, full_code) 19 | 20 | {result, 0} = System.cmd("node", [random_filepath]) 21 | 22 | File.rm!(random_filepath) 23 | 24 | Jason.decode!(result) 25 | else 26 | # this is the base function 27 | Process.put(:js_context, code) 28 | 29 | code 30 | end 31 | end 32 | 33 | @spec _build_function({atom, non_neg_integer}) :: Macro.t() 34 | def _build_function({fun, arity}) do 35 | args = 36 | case arity do 37 | 0 -> [] 38 | n -> for i <- 1..n, do: {:"arg#{i}", [], Elixir} 39 | end 40 | 41 | quote do 42 | def unquote(fun)(unquote_splicing(args)) do 43 | context = Process.get(:js_context) || raise "no context!" 44 | 45 | arguments = Jason.encode!(unquote(args)) 46 | code = "console.log(JSON.stringify(#{unquote(fun)}(...JSON.parse('#{arguments}'))))" 47 | 48 | Yavascript.NodeEngine.run_script(context, code, true) 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/yavascript/spider_monkey_engine/include/js.h: -------------------------------------------------------------------------------- 1 | #ifndef JS_H 2 | #define JS_H 3 | 4 | #ifdef __cplusplus 5 | #define JS_EXTERN extern "C" 6 | #else 7 | #define JS_EXTERN 8 | #endif 9 | 10 | #include 11 | 12 | typedef struct JSContext Context; 13 | 14 | typedef struct { 15 | void* global; 16 | void* realm; 17 | } GlobalInfo; 18 | 19 | typedef void (*result_fn_t)(const char *, void *); 20 | 21 | JS_EXTERN void init(); 22 | JS_EXTERN void shutdown(); 23 | 24 | JS_EXTERN void executeCode(Context *, const char*, const void *, void *); 25 | 26 | JS_EXTERN Context* newContext(uint32_t max_bytes); 27 | 28 | // type erasure is necessary here to get it past the C ABI? 29 | // note that the two void* terms here should be the same. 30 | JS_EXTERN GlobalInfo initializeContext(Context *); 31 | JS_EXTERN void cleanupGlobals(Context *, GlobalInfo); 32 | 33 | JS_EXTERN void destroyContext(Context *); 34 | 35 | #endif -------------------------------------------------------------------------------- /lib/yavascript/spider_monkey_engine/js.cpp: -------------------------------------------------------------------------------- 1 | #include "js.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define RootedObject JS::RootedObject 9 | 10 | extern "C" void init() { 11 | JS_Init(); 12 | } 13 | 14 | extern "C" void shutdown() { 15 | JS_ShutDown(); 16 | } 17 | 18 | extern "C" Context* newContext(uint32_t max_bytes) { 19 | if (max_bytes) { 20 | return JS_NewContext(max_bytes); 21 | } else { 22 | return JS_NewContext(JS::DefaultHeapMaxBytes); 23 | } 24 | } 25 | 26 | extern "C" void destroyContext(Context* context) { 27 | JS_DestroyContext(context); 28 | } 29 | 30 | JSObject* createGlobal(Context* cx) { 31 | JS::RealmOptions options; 32 | 33 | static JSClass BoilerplateGlobalClass = { 34 | "BoilerplateGlobal", JSCLASS_GLOBAL_FLAGS, &JS::DefaultGlobalClassOps}; 35 | 36 | return JS_NewGlobalObject(cx, &BoilerplateGlobalClass, nullptr, 37 | JS::FireOnNewGlobalHook, options); 38 | } 39 | 40 | extern "C" GlobalInfo initializeContext(Context *context) { 41 | // note: check for failure 42 | JS::InitSelfHostedCode(context); 43 | 44 | RootedObject *global = new RootedObject(context, createGlobal(context)); 45 | JSAutoRealm *realm = new JSAutoRealm(context, *global); 46 | 47 | // set up the packed struct to return. 48 | GlobalInfo info; 49 | info.global = (void *) global; 50 | info.realm = (void *) realm; 51 | return info; 52 | } 53 | 54 | extern "C" void cleanupGlobals(Context *context, GlobalInfo info) { 55 | delete (RootedObject *) info.global; 56 | delete (JSAutoRealm *) info.realm; 57 | } 58 | 59 | extern "C" void executeCode(Context *context, const char* code, const void * r_fn_ptr, void * r_fn_arg) { 60 | JS::CompileOptions options(context); 61 | options.setFileAndLine("noname", 1); 62 | 63 | JS::SourceText source; 64 | 65 | // NOTE: check failure. 66 | source.init(context, code, strlen(code), JS::SourceOwnership::Borrowed); 67 | 68 | JS::RootedValue rval(context); 69 | 70 | // NOTE: check failure 71 | JS::Evaluate(context, options, source, &rval); 72 | 73 | if (r_fn_ptr != nullptr) { 74 | result_fn_t r_fn = (result_fn_t) r_fn_ptr; 75 | r_fn(JS_EncodeStringToASCII(context, rval.toString()).get(), r_fn_arg); 76 | } 77 | } -------------------------------------------------------------------------------- /lib/yavascript/spider_monkey_engine/spider_monkey_engine.ex: -------------------------------------------------------------------------------- 1 | defmodule Yavascript.SpiderMonkeyEngine do 2 | @behaviour Yavascript.Engine 3 | 4 | use Zig, 5 | link_libc: true, 6 | link_libcpp: true, 7 | include: ["/usr/include/mozjs-91"], 8 | system_libs: ["mozjs-91"], 9 | sources: [{"js.cpp", ["-std=c++17"]}] 10 | 11 | ~Z""" 12 | const js = @cImport({ 13 | @cInclude("js.h"); 14 | }); 15 | 16 | /// nif: init/0 17 | fn init() void { 18 | js.init(); 19 | } 20 | 21 | /// nif: shutdown/0 22 | fn shutdown() void { 23 | js.shutdown(); 24 | } 25 | 26 | /// resource: threadinfo_ptr_t definition 27 | const threadinfo_ptr_t = *ThreadInfo; 28 | const ThreadInfo = struct { 29 | name: []u8, 30 | mutex: *e.ErlNifMutex, 31 | tid: e.ErlNifTid, 32 | called: bool, 33 | pid: beam.pid, 34 | command: []u8, 35 | needs_return: bool, 36 | active: bool, 37 | }; 38 | 39 | /// resource: threadinfo_ptr_t cleanup 40 | fn struct_res_ptr_cleanup(_: beam.env, thread_info: *threadinfo_ptr_t) void { 41 | { 42 | e.enif_mutex_lock(thread_info.*.mutex); 43 | defer e.enif_mutex_unlock(thread_info.*.mutex); 44 | thread_info.*.active = false; 45 | } 46 | 47 | var result:?*anyopaque = undefined; 48 | 49 | _ = e.enif_thread_join(thread_info.*.tid, &result); 50 | } 51 | 52 | var default_thread_opts = e.ErlNifThreadOpts{ .suggested_stack_size = 0}; 53 | const thread_name = "js-executor"; 54 | 55 | /// nif: create_thread/0 dirty_cpu 56 | fn create_thread(env: beam.env) !beam.term { 57 | // we will sort unreachables later. 58 | var thread_name_slice = beam.allocator.alloc(u8, thread_name.len) catch unreachable; 59 | std.mem.copy(u8, thread_name_slice, thread_name); 60 | 61 | var thread_info = beam.allocator.create(ThreadInfo) catch unreachable; 62 | // NOTE: failures are not caught here, need to do this! 63 | thread_info.mutex = e.enif_mutex_create(thread_name_slice.ptr).?; 64 | thread_info.pid = beam.self(env) catch unreachable; 65 | thread_info.called = false; 66 | thread_info.active = true; 67 | 68 | var tcreate = e.enif_thread_create( 69 | thread_name_slice.ptr, 70 | &thread_info.tid, 71 | js_executor, 72 | thread_info, 73 | &default_thread_opts); 74 | 75 | if (tcreate == 0) { 76 | var res = __resource__.create(threadinfo_ptr_t, env, thread_info) 77 | catch unreachable; 78 | 79 | __resource__.release(threadinfo_ptr_t, env, res); 80 | 81 | return res; 82 | } else { 83 | // TODO: actually raise here. 84 | return beam.make_atom(env, "error"); 85 | } 86 | } 87 | 88 | const ResponsePayload = struct{ 89 | env: beam.env, 90 | binary_term: beam.term, 91 | }; 92 | 93 | fn js_executor(thread_info_opaque: ?*anyopaque) callconv(.C) ?*anyopaque { 94 | var context = js.newContext(0).?; 95 | defer js.destroyContext(context); 96 | 97 | var global: js.GlobalInfo = js.initializeContext(context); 98 | defer js.cleanupGlobals(context, global); 99 | 100 | var env = e.enif_alloc_env(); 101 | defer e.enif_free_env(env); 102 | 103 | var thread_info = 104 | @ptrCast( 105 | threadinfo_ptr_t, 106 | @alignCast( 107 | @alignOf(threadinfo_ptr_t), 108 | thread_info_opaque) 109 | ); 110 | 111 | var mutex = thread_info.mutex; 112 | 113 | _ = beam.send_advanced(null, thread_info.pid, env, beam.make_atom(env, "unlock")); 114 | 115 | while (true) { 116 | if (e.enif_mutex_trylock(mutex) == 0) { 117 | defer e.enif_mutex_unlock(mutex); 118 | 119 | if (!thread_info.active) { return null; } 120 | 121 | // check to make sure this_ref is different from the last ref. 122 | if (thread_info.called) { 123 | defer beam.allocator.free(thread_info.command); 124 | 125 | if (thread_info.needs_return) { 126 | var response: ResponsePayload = .{ 127 | .env = env, 128 | .binary_term = undefined, 129 | }; 130 | 131 | js.executeCode(context, thread_info.command.ptr, responseFn, &response); 132 | 133 | var tuple_terms = [_]beam.term{beam.make_atom(env, "js_result"), response.binary_term}; 134 | 135 | _ = beam.send_advanced(null, thread_info.pid, env, beam.make_tuple(env, tuple_terms[0..])); 136 | } else { 137 | js.executeCode(context, thread_info.command.ptr, null, null); 138 | _ = beam.send_advanced(null, thread_info.pid, env, beam.make_atom(env, "js_ok")); 139 | } 140 | thread_info.called = false; 141 | } 142 | } 143 | // 1 ms sleep time till next poll 144 | std.time.sleep(1_000_000); 145 | } 146 | } 147 | 148 | fn responseFn(resp_text: [*c] const u8, response_opaque: ?*anyopaque) void { 149 | var response = @ptrCast(*ResponsePayload, @alignCast(@alignOf(*ResponsePayload), response_opaque.?)); 150 | var response_slice = std.mem.sliceTo(resp_text, 0); 151 | 152 | response.binary_term = beam.make_slice(response.env, response_slice); 153 | } 154 | 155 | /// nif: execute/3 dirty_io 156 | fn execute(env: beam.env, thread_info_term: beam.term, command: []u8, needs_return: bool) beam.term { 157 | var thread_info = __resource__.fetch(threadinfo_ptr_t, env, thread_info_term) 158 | catch unreachable; 159 | var new_ref = e.enif_make_ref(env); 160 | 161 | { 162 | e.enif_mutex_lock(thread_info.mutex); 163 | defer e.enif_mutex_unlock(thread_info.mutex); 164 | 165 | // copy and null-terminate the command 166 | var command_copy = beam.allocator.alloc(u8, command.len + 1) catch unreachable; 167 | std.mem.copy(u8, command_copy, command); 168 | command_copy[command.len] = 0; 169 | 170 | // note that ownership of the command is going to pass to the thread. 171 | thread_info.called = true; 172 | thread_info.command = command_copy; 173 | thread_info.needs_return = needs_return; 174 | thread_info.pid = beam.self(env) catch unreachable; 175 | } 176 | 177 | return new_ref; 178 | } 179 | """ 180 | 181 | def create_context do 182 | result = create_thread() 183 | 184 | receive do 185 | :unlock -> result 186 | end 187 | end 188 | 189 | def run_script(context, code, needs_return) do 190 | Yavascript.SpiderMonkeyEngine.execute(context, code, needs_return) 191 | 192 | receive do 193 | {:js_result, result} when needs_return -> Jason.decode!(result) 194 | :js_ok when not needs_return -> context 195 | end 196 | end 197 | 198 | @spec _build_function({atom, non_neg_integer}) :: Macro.t() 199 | def _build_function({fun, arity}) do 200 | args = 201 | case arity do 202 | 0 -> [] 203 | n -> for i <- 1..n, do: {:"arg#{i}", [], Elixir} 204 | end 205 | 206 | quote do 207 | def unquote(fun)(unquote_splicing(args)) do 208 | context = Process.get(:js_context) || raise "no context!" 209 | 210 | arguments = Jason.encode!(unquote(args)) 211 | code = "JSON.stringify(#{unquote(fun)}(...JSON.parse('#{arguments}')))" 212 | 213 | Yavascript.SpiderMonkeyEngine.run_script(context, code, true) 214 | end 215 | end 216 | end 217 | end 218 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Yavascript.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :yavascript, 7 | version: "0.1.0", 8 | elixir: "~> 1.12", 9 | start_permanent: Mix.env() == :prod, 10 | deps: deps(), 11 | elixirc_paths: elixirc_paths(Mix.env()), 12 | preferred_cli_env: [benchmark: :bench] 13 | ] 14 | end 15 | 16 | def application do 17 | [ 18 | extra_applications: [:logger, :crypto] 19 | ] 20 | end 21 | 22 | defp elixirc_paths(:bench), do: ~w(lib bench) 23 | defp elixirc_paths(_), do: ["lib"] 24 | 25 | defp deps do 26 | [ 27 | {:jason, "~> 1.3.0"}, 28 | {:zigler, "~> 0.9.1"}, 29 | {:benchee, "~> 1.1", only: [:bench]} 30 | ] 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /test/bun/bun_test.exs: -------------------------------------------------------------------------------- 1 | defmodule YavascriptTest.BunTest do 2 | use ExUnit.Case 3 | 4 | use Yavascript, 5 | engine: Yavascript.BunEngine, 6 | setup: :setup_js, 7 | import: [my_function: 2], 8 | script: """ 9 | const my_function = (a, b) => a + b; 10 | """ 11 | 12 | test "you can run javascript" do 13 | setup_js() 14 | assert 3 == my_function(1, 2) 15 | assert "foobar" == my_function("foo", "bar") 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/deno/deno_test.exs: -------------------------------------------------------------------------------- 1 | defmodule YavascriptTest.DenoTest do 2 | use ExUnit.Case 3 | 4 | use Yavascript, 5 | engine: Yavascript.DenoEngine, 6 | setup: :setup_js, 7 | import: [my_function: 2], 8 | script: """ 9 | const my_function = (a, b) => a + b; 10 | """ 11 | 12 | test "you can run javascript" do 13 | setup_js() 14 | assert 3 == my_function(1, 2) 15 | assert "foobar" == my_function("foo", "bar") 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/node/node_test.exs: -------------------------------------------------------------------------------- 1 | defmodule YavascriptTest.NodeTest do 2 | use ExUnit.Case 3 | 4 | use Yavascript, 5 | engine: Yavascript.NodeEngine, 6 | setup: :setup_js, 7 | import: [my_function: 2], 8 | script: """ 9 | const my_function = (a, b) => a + b; 10 | """ 11 | 12 | test "you can run javascript" do 13 | setup_js() 14 | assert 3 == my_function(1, 2) 15 | assert "foobar" == my_function("foo", "bar") 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/spider_monkey/spider_monkey_test.exs: -------------------------------------------------------------------------------- 1 | defmodule YavascriptTest do 2 | use ExUnit.Case 3 | 4 | use Yavascript, 5 | engine: Yavascript.SpiderMonkeyEngine, 6 | setup: :setup_js, 7 | import: [my_function: 2], 8 | script: """ 9 | const my_function = (a, b) => a + b; 10 | """ 11 | 12 | test "you can run javascript" do 13 | setup_js() 14 | assert 3 == my_function(1, 2) 15 | assert "foobar" == my_function("foo", "bar") 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | Yavascript.SpiderMonkeyEngine.init() 2 | 3 | ExUnit.start() 4 | 5 | ExUnit.stop(fn -> 6 | IO.puts("hi mom") 7 | Process.sleep(1000) 8 | Yavascript.SpiderMonkeyEngine.shutdown() 9 | end) 10 | -------------------------------------------------------------------------------- /test/yavascript_test.exs: -------------------------------------------------------------------------------- 1 | 2 | --------------------------------------------------------------------------------