js.cr v1.3.0
js.cr
An experimental tool to generate JavaScript code from Crystal code.
Contents
Goals
The most important idea behind this project is to create the ability to reference JavaScript functions, classes and modules in Cystal code using typechecked entities instead of strings.
Initially I wanted to leverage Crystal's type system to have my JavaScript code typechecked at compile time, but that turned out to be too much to start with. It remains a goal to reintroduce that feature at some point, but for now you can generate invalid JavaScript with this library.
Please note that this is still very experimental and probably lacks essential features to be genuinely used. So far I am employing it to generate stimulus controllers which toggle some classes of HTML elements.
Also, I skipped any whitespacing for now as I wanted to have my proof of concept as fast as possible. Not sure if I will ever deem it necessary to add it. The specs are still quite readable because I include whitespace in the examples and only remove it before comparing to the results.
Examples
JavaScript Code
Just want to output some loose snippet of JavaScript code? Use the JS::Code
base class. You can print your code directly into a <script>
tag of your favorite template engine via .to_js
if you want.
require "js"
class MyCode < JS::Code
def_to_js do
console.log("Hello World!")
end
end
puts MyCode.to_js
_call
In Crystal, there are no object properties but just method calls. Without any type information about the JS code you are calling, js.cr
cannot know whether you want to reference a property or call a function without any arguments on an object. It therefore always assumes a property reference. If you need a function call instead, just append ._call
to it. Note that this isn't necessary if your call has arguments.
require "js"
class MyCallCode < JS::Code
def_to_js do
a = SomeUnknownJSConstant.somePropertyOfIt
b = SomeUnknownJSConstant.someFunctionOfIt._call
c = SomeUnknownJSConstant.someOtherFunctionOfIt("foo")
end
end
# => var a = SomeUnknownJSConstant.somePropertyOfIt;
# => var b = SomeUnknownJSConstant.someFunctionOfIt();
# => var c = SomeUnknownJSConstant.someOtherFunctionOfIt("foo");
puts MyCallCode.to_js
JavaScript Functions
If you were wondering how to define a function within JS::Code.def_to_js
- that's not possible. Well, technically it is via a _literal_js
call but that's dirty and there is a better way:
require "js"
class MyFunction < JS::Function
def_to_js do |foo|
console.log(foo)
end
end
puts MyFunction.to_js
You could have that printed into a <script>
tag again via .to_js
and reference it in an onclick
attribute by calling .to_js_call
. Not sure how hip that is anymore.
<html>
<head>
<title>JavaScript!</title>
<script><%= MyFunction.to_js %></script>
</head>
<body>
<div onclick="<%= MyFunction.to_js_call("test") %>">Print "test" to the console!</div>
</body>
</html>
JavaScript Classes
Generate modern JavaScript Classes with the JS::Class
base class. Classes have methods
instead of functions
.
require "js"
class MyClass < JS::Class
js_method :do_something do
console.log("test")
end
end
puts MyClass.to_js
Javascript Files
Want to put some functions and classes together? A JS::File
encapsulates an arbitrary number of these, along with one code snippet to use them. This might change in the future, but it's enough for now.
require "js"
class MyFile < JS::File
js_function :say_hello do
console.log("Hello!")
end
js_class ImportantData do
js_method :constructor do |name|
this.name = name
end
js_method :tell_name do
console.log(this.name)
end
end
def_to_js do
say_hello.to_js_call
my_data = ImportantData.new("Joe")
my_data.tell_name._call
end
end
puts MyFile.to_js
Aliases
Sometimes you want to leverage JS libraries like jQuery or underscore.js that define entry points not expressable in Crystal. For these cases you can define aliases.
require "js"
class MyAliasingFile < JS::File
js_alias "jQ", "$"
def_to_js do
jQ("div").addClass("foo")
end
end
puts MyAliasingFile.to_js
JavaScript Modules
All hail JavaScript Modules! Here these are just JS::File
s that can additionally import
things.
Please note that for now there will be no typechecks whatsoever by the Crystal compiler for this. You only need to include import
statements for them to be present in the JavaScript code.
require "js"
class MyImportingModule < JS::Module
js_import Application, Controller, from: "/assets/stimulus.js"
js_class MyStimulusController do
js_method :connect do
console.log("connected!")
end
end
def_to_js do
window.Stimulus = Application.start._call
Stimulus.register("my-stimulus", MyStimulusController)
end
end
puts MyImportingModule.to_js
Loops
forEach
loops are supported.
require "js"
class MyLoop < JS::Code
def_to_js do
values = ["This", "Is", "Sparta"]
values.forEach do |value|
console.log(value)
end
end
end
puts MyLoop.to_js
js.cr
- 2
- 0
- 1
- 1
- 0
- 2 days ago
- October 1, 2023
Mon, 18 Nov 2024 08:27:49 GMT