{"id":42220761,"url":"https://github.com/lbguilherme/crystal-js","last_synced_at":"2026-01-27T01:38:59.228Z","repository":{"id":55831783,"uuid":"442929638","full_name":"lbguilherme/crystal-js","owner":"lbguilherme","description":"Bindings to use Crystal compiled to WebAssembly in a JavaScript environment, such as Node.js, Deno or the Web.","archived":false,"fork":false,"pushed_at":"2023-05-03T20:53:31.000Z","size":127,"stargazers_count":58,"open_issues_count":2,"forks_count":4,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-10-10T00:33:28.785Z","etag":null,"topics":["crystal","deno","frontend","javascript","nodejs","wasm","web","webassembly"],"latest_commit_sha":null,"homepage":"","language":"Crystal","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lbguilherme.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-12-30T01:00:55.000Z","updated_at":"2025-08-27T12:29:35.000Z","dependencies_parsed_at":"2023-01-19T17:03:45.088Z","dependency_job_id":null,"html_url":"https://github.com/lbguilherme/crystal-js","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/lbguilherme/crystal-js","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lbguilherme%2Fcrystal-js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lbguilherme%2Fcrystal-js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lbguilherme%2Fcrystal-js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lbguilherme%2Fcrystal-js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lbguilherme","download_url":"https://codeload.github.com/lbguilherme/crystal-js/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lbguilherme%2Fcrystal-js/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28795471,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-27T01:07:07.743Z","status":"ssl_error","status_checked_at":"2026-01-27T01:07:06.974Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["crystal","deno","frontend","javascript","nodejs","wasm","web","webassembly"],"created_at":"2026-01-27T01:38:59.168Z","updated_at":"2026-01-27T01:38:59.221Z","avatar_url":"https://github.com/lbguilherme.png","language":"Crystal","funding_links":[],"categories":[],"sub_categories":[],"readme":"# crystal-js\n\nThis library provides bindings to run a Crystal application in a JavaScript environment, such as Node.js or the Web. Using this it is possible to consume existing JavaScript API's inside Crystal when compiling for the WebAssembly target. It is similar to [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) from Rust.\n\nThis library and WebAssembly with Crystal is highly experimental and still in the early days. Please report bugs.\n\nFor a Web APIs bindings see [`crystal-web`](https://github.com/lbguilherme/crystal-web).\n\n## Getting started\n\n1. Add the dependency to your `shard.yml`:\n\n   ```yaml\n   dependencies:\n     js:\n       github: lbguilherme/crystal-js\n   ```\n\n2. Run `shards install`\n\n3. Write a `src/main.cr` such as:\n\n    ```crystal\n    require \"js\"\n\n    JS.console.log \"Hello from the JavaScript!\"\n    ```\n\n4. Build your project with `lib/js/scripts/build.sh src/main.cr`. This will produce two files: a `main.wasm` and a `main.js`.\n\n    This build command has a few optional flags:\n    - `-o some/other/path.wasm`: Allows you to change the output path. The JavaScript file will be generated on the same path, changing only the extension.\n    - `--esm`: Generates a ECMAScript Module file (`.mjs`) instead of a pure `.js` file.\n    - `--release`: Optimizes performance and shrinks the size as much as possible.\n    - `--error-trace`: Enables verbose error output from Crystal\n\nYou can run your project:\n\n- on the Web: `\u003cscript defer src=\"main.js\"\u003e\u003c/script\u003e`\n- with Node.js: `node main.js`\n- with Deno: `deno run --allow-read main.js`\n\nSee [crystal-web-demo](https://github.com/lbguilherme/crystal-web-demo) for an example project.\n\n## Usage\n\nFor basic usage you can `require \"js\"` and then use common JavaScript methods and classes from the `JS` module.\n\n```crystal\nrequire \"js\"\n\nJS.console.log \"Hello from the Crystal!\"\n```\n\nIf the JavaScript file is simply executed from the command line or included in a `\u003cscript\u003e` tag, then the Crystal program is executed right away.\n\nYou can also export functions to be called from JavaScript. For example:\n\n```crystal\nrequire \"js\"\n\nJS.export def say_hello(name : String)\n  puts \"Hello #{name}!\"\nend\n```\n\nThen from JavaScript (CommonJS):\n\n```js\nconst crystal = require(\"./main.js\");\n\ncrystal().then(() =\u003e {\n  crystal.say_hello(\"World\")\n});\n```\n\nOr with ESM (Recommended):\n\n```js\nimport init, { say_hello } from \"./main.mjs\"\n\nawait init();\n\nsay_hello(\"World\");\n```\n\n### Supported Types\n\n| Crystal Type | JavaScript Type | Notes |\n| - | - | - |\n| `Int` or `Float` | `number` | |\n| `Int64` or `UInt64` | `bigint` | |\n| `Bool` | `boolean` | |\n| `String` | `string` | |\n| `Array(T)` or any `Enumerable(T)` | `Array` | Only from Crystal to JavaScript. `T` must be supported as well. |\n| `Hash(K, V)` | `Array` of pairs | Only from Crystal to JavaScript. It is not translated to an object. |\n| `JS::Reference` | any | Holds a reference to a JavaScript-land value without converting. |\n\n### Exporting Crystal methods to JavaScript\n\nIt isn't yet possible to expose classes and modules, only top-level methods. Use the `JS.export` macro to export a method. The parameter types and the return type must be one of the supported ones.\n\nThe parameter types and the return type (if any) must always be explicitely specified in the method definition.\n\n```crystal\nrequire \"js\"\n\nJS.export def say_hello_and_compute(name : String, a : Int32, b : Int32) : Int32\n  puts \"Hello #{name}!\"\n\n  a + b\nend\n```\n\nTODO: There is no type checking from the JavaScript side. You are expected to pass the right arguments.\n\n### Calling JavaScript from Crystal\n\nThe `JS` module expose some JavaScript types and functions to be used from Crystal. For example, you can print a message to the console with:\n\n```crystal\nrequire \"js\"\n\nJS.console.log \"Hello!\"\n```\n\nYou can also manipulate JavaScript values:\n\n```crystal\nrequire \"js\"\n\njs_string = JS::String.new \"Hello!\"\n\np js_string.length # =\u003e 6\np js_string.code_point_at(1) # =\u003e 101\np js_string.pad_start(10, \"-\") # =\u003e \"----Hello!\"\n```\n\nYou can use these types as method parameters to receive a raw reference to a JavaScript-land value, instead of the converted equivalent Crystal type. These types inherit from `JS::Reference`.\n\n### Defining raw JavaScript methods\n\nYou can define special methods that run JavaScript code from Crystal. They can take parameters but their body must be a single string literal using interpolation to receive arguments:\n\n```crystal\nrequire \"js\"\n\nmodule Test\n  # You need to include this module. It won't add any new methods, all it will do\n  # is expanding JavaScript methods you define.\n  include JS::ExpandMethods\n\n  # Mark JavaScript methods with this annotation. They must be fully typed and\n  # be aware that not all types are supported yet.\n  @[JS::Method]\n  def self.add(first : Int32, second : Int32) : Int32\n    # This is NOT a raw string interpolation. The notation here is used to pass\n    # valid values to JavaScript land.\n    \u003c\u003c-js\n      return #{first} + #{second}; // This is JavaScript!\n    js\n  end\nend\n\nfive = Test.add(2, 3) # Returns 5.\n```\n\nJavaScript methods are always defined in a class or module that includes `JS::ExpandMethods`. Their body must be a single string literal that interpolates values. The string is raw JavaScript code that will be used as is, and the interpolations are placeholders to receive values from Crystal land. The bridging managed by this library handles converting the values to the appropriate type based on the annotations. Splat arguments are supported.\n\nUnions are not supported yet.\n\nIn some cases it may be better to keep the value on JavaScript land instead of converting it, and instead hold a reference to the value that can be used later. The `JS::Reference` class provides this interface. When used as an argument type or a return type, it does no conversion and returns a reference to the value. This class is abstract, you must define a subclass to handle your custom types. Here is a more complex example:\n\n```crystal\nrequire \"js\"\n\nclass MyObj \u003c JS::Reference\n  @[JS::Method]\n  def self.new(data : String) : MyObj\n    \u003c\u003c-js\n      const pieces = #{data}.split(', ')\n\n      return {                    // The value returned here will be held by MyObj\n        pieces,                   // and can later be accessed using `#{self}`.\n        length: pieces.length\n      };\n    js\n  end\n\n  @[JS::Method]\n  def size : Int32\n    \u003c\u003c-js\n      return #{self}.length;\n    js\n  end\n\n  @[JS::Method]\n  def add_piece(piece : String)\n    \u003c\u003c-js\n      #{self}.pieces.push(#{piece.strip.as(String)}); // You can use complex Crystal expressions,\n      #{self}.length += 1;                            // as long as you add `.as(type)` to it.\n    js\n  end\nend\n\nobj = MyObj.new(\"a, b, c\")\np obj.size # =\u003e 3\nobj.add_piece(\"d\")\np obj.size # =\u003e 4\n```\n\nOnly the methods that are actually called will be generated in the output `.js` file, thus it is fine to define lots of unused methods.\n\nAdditionally, there are some helper functions to facilitate creating bindings to existing JavaScript APIs. Use `js_getter`, `js_setter`, `js_property`, and `js_method` to define matching methods. For example:\n\n\n```crystal\nrequire \"js\"\n\nclass XMLHttpRequest \u003c JS::Reference\n  @[JS::Method]\n  def self.new : self\n    \u003c\u003c-js\n      return new XMLHttpRequest();\n    js\n  end\n\n  js_getter readyState : Int32    # defines a `ready_state` method.\n  js_getter responseText : String\n  js_getter status : Int32\n\n  # defines `response_type` and `response_type=` methods.\n  js_property responseType : String\n\n  # methods can be overloaded\n  js_method send\n  js_method send(body : String)\n  js_method open(method : String, url : String)\n\n  # for the return type use a comma, not a colon.\n  js_method getResponseHeader(header : String), String\nend\n\nreq = XMLHttpRequest.new\nreq.open(\"GET\", \"https://example.com\")\nreq.send\np req.response_text\np req.get_response_header \"Content-Type\"\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flbguilherme%2Fcrystal-js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flbguilherme%2Fcrystal-js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flbguilherme%2Fcrystal-js/lists"}