lbguilherme / crystal-web

Crystal bindings to the Web APIs for building front-end applications with WebAssembly
MIT License
27 stars 2 forks source link

More Examples? #2

Open carcinocron opened 2 years ago

carcinocron commented 2 years ago
require "./web"

window = Web.window
console = window.console
document = window.document

# I had to do this to compile:
console.log JS::String.new("Width: #{window.inner_width}")
console.log JS::String.new("Height: #{window.inner_height}")

canvas = document.create_element("canvas")
document.body.append_child(canvas)
ctx = canvas.get_context("2d")
ctx.font = "30px Arial"
ctx.fill_text("Hello World!", 10, 30)

# events are important
canvas.addEventListener "onmouseover", do |event: JS::MouseEvent|
  console.log event
end

how to make events work?

carcinocron commented 2 years ago

unrelated information on crystal wasm hello world build size:

+ lib/js/scripts/build.sh src/main.cr --error-trace
+ ls -lh main.js main.wasm
-rw-r--r-- 1 forge forge 6.6K Jul 31 10:30 main.js
-rwxr-xr-x 1 forge forge 502K Jul 31 10:30 main.wasm
+ lib/js/scripts/build.sh src/main.cr --release
+ ls -lh main.js main.wasm
-rw-r--r-- 1 forge forge 3.2K Jul 31 10:30 main.js
-rwxr-xr-x 1 forge forge  53K Jul 31 10:30 main.wasm
carcinocron commented 2 years ago

Let's say if we had a seperate js file that needed to call a function from crystal, what would that look like?

main.cr

def foo
  return "bar"
end

main2.ts

console.log(foo())
carcinocron commented 2 years ago
pp "A"

begin
  raise "B"
rescue ex
  pp ex
end

pp "C"

only outputs "A" and EXITING: Attempting to raise: B (Exception) . I think this is WebAssembly's fault though: https://github.com/WebAssembly/exception-handling

carcinocron commented 2 years ago

I tried this and it compiled but it didn't crash or output "fired".

class ::Web::EventTarget
  def addEventListener(name : ::String, listener : Proc(Nil)) : Nil
    pp "::Web::EventTarget.addEventListener before #{__LINE__}"
    <<-js
      "\#{self}".id.addEventListener(#{name}, #{listener})
    js
    pp "::Web::EventTarget.addEventListener after #{__LINE__}"
  end
end

cb = -> : Nil { pp "fired" }
canvas.addEventListener("onmouseover", cb)
lbguilherme commented 2 years ago

Hi there.

how to make events work?

For events to work we need to pass a callback from Crystal to JavaScript. Currently Procs are not supported by crystal-js, only primitive types, strings and array-like types. It needs to support both "sending" a Proc from Crystal to a JavaScript function, and the way back, before we can support DOM events here.


Let's say if we had a seperate js file that needed to call a function from crystal, what would that look like?

main.cr

def foo
  return "bar"
end

main2.ts

console.log(foo())

This repo assumes that Crystal calls JavaScript, but not the other way around. You can export a Crystal function (using the fun syntax instead of def), but if you do that, you will only be able to take numbers as arguments and to return a single number as result, nothing more complicated than that. These exported functions will show up in the wasm module.

It would be, of course, a nice feature to facilitate exporting Crystal functions to JavaScript with full support for complex types. I'll keep it in the backlog.


pp "A"

begin
  raise "B"
rescue ex
  pp ex
end

pp "C"

only outputs "A" and EXITING: Attempting to raise: B (Exception) . I think this is WebAssembly's fault though: WebAssembly/exception-handling

Exceptions are not implemented yet for the WASM target of Crystal. See https://github.com/crystal-lang/crystal/issues/12002 for more details. Currently the behavior is that whenever a exception is thrown, the application will just crash and exit right there, with no way of catching and recovering.


I tried this and it compiled but it didn't crash or output "fired".

class ::Web::EventTarget
  def addEventListener(name : ::String, listener : Proc(Nil)) : Nil
    pp "::Web::EventTarget.addEventListener before #{__LINE__}"
    <<-js
      "\#{self}".id.addEventListener(#{name}, #{listener})
    js
    pp "::Web::EventTarget.addEventListener after #{__LINE__}"
  end
end

cb = -> : Nil { pp "fired" }
canvas.addEventListener("onmouseover", cb)

Please check the README at https://github.com/lbguilherme/crystal-js. It explains more about how new methods can be created. But in short: you need to annotate them with @[JS::Method] and their body can't anything other than one single string literal with special interpolations for bridge values. Also, Proc isn't supported yet. You will receive a compile-time error about that.


All in all, this repository is a proof of concept and a experiment. It shows that creating Web bindings for Crystal is possible, but there is still a long way before it gets to a usable point. Please tell me a little about your intended use case so I can better understand how to evolve this shard in the future.

Thanks!

carcinocron commented 2 years ago

I wanted to make an interactive canvas thing with most of the core logic in crystal, but I need at least mouse events for it to be feasible. Proc specifically aren't important, but that's that only guess I can think of to get events in general listened to by crystal code.