Skip to content

Instantly share code, notes, and snippets.

@myguidingstar
Last active July 10, 2021 09:28
Show Gist options
  • Save myguidingstar/71686f9c07b753c8c7fee1079b6ba437 to your computer and use it in GitHub Desktop.
Save myguidingstar/71686f9c07b753c8c7fee1079b6ba437 to your computer and use it in GitHub Desktop.
Clojurescript REPL for quickjs via weasel

This project code provides a Clojurescript REPL for Quickjs via weasel.

Quickjs is a lightweight javascript engine with low memory footprint. Clojurescript code compiled in advanced mode can be turned into standalone executables via Quickjs's qjsc. Your app can use awesome cljs libraries like core.async, clojure.spec, pathom and datascript without any problem. React should be possible with a custom renderer, like: https://github.com/doodlewind/react-ssd1306

A REPL is handly to develop such apps. The easiest way to create a REPL env for Clojurescript is to leverage weasel. However, Quickjs doesn't support Websocket out of the box. Wiring up a websocket client via C api is undesired. Fortunately, there's a python wrapper for Quickjs.

This project makes use of that python wrapper together with websockets library to implement the weasel client and edn_format for edn transport. Python's functionality can be easily exposed to the embeded Quickjs therefore we have straightforward Python interop right in Cljs.

The Python ecosystem is huge. Have fun!

Note: you must choose with either with standalone executable or python access. Not both.

Installation

  • start weasel server, usually from your editor or manually with: (cider.piggieback/cljs-repl (weasel.repl.websocket/repl-env :ip "0.0.0.0" :port 9001))
  • compile your cljs project with :target :none, can be as simple as this: clj -M -m cljs.main --target none -c your-project.core
  • take note of the compiled js files' base path which is the directory containing base.js (usually target/out/goog/. You need absolute path for this. Update the base_path var in the provided file quickjs_cljs.py)
  • DO NOT start weasel client in your clojurescript code (as seen in weasel's documentation) - that is for websocket-powered javascript environments
  • once you see that weasel is waiting for client (<< started Weasel server on ws://127.0.0.1:9001 >><< waiting for client to connect ..., run the provided python: python quickjs_cljs.py
import quickjs
import asyncio
import websockets
import random
import edn_format
from pathlib import Path
base_path = "/path/to/out/goog/"
context = quickjs.Context()
def qjs_import_script(path, src=None):
print('importing ' + path+'...')
src = src or Path(base_path + path).read_text()
print('found content, evaluating...')
context.eval(src)
return True
context.eval("CLOSURE_NO_DEPS=true")
context.add_callable("CLOSURE_IMPORT_SCRIPT", qjs_import_script)
repl_print_queue = asyncio.Queue()
def print_console_and_repl(s):
repl_print_queue.put_nowait(s)
print(s)
context.add_callable("py__", exec)
context.add_callable("py_", eval)
context.eval('console={}')
context.add_callable("_console_log", print_console_and_repl)
context.eval('console.log=_console_log')
print('bootstraping...')
qjs_import_script('base.js')
qjs_import_script('../main.js')
context.eval("goog.require('cljs.core')")
print('bootstraped!')
# edn keywords
_code = edn_format.Keyword('code')
_error = edn_format.Keyword('error')
_exception = edn_format.Keyword('exception')
_eval_js = edn_format.Keyword('eval-js')
_op = edn_format.Keyword('op')
_print = edn_format.Keyword('print')
_result = edn_format.Keyword('result')
_stacktrace = edn_format.Keyword('stacktrace')
_status = edn_format.Keyword('status')
_success = edn_format.Keyword('success')
_type = edn_format.Keyword('type')
_value = edn_format.Keyword('value')
def wrap_edn_error(e):
return edn_format.dumps({_op: _result, _value: {_status: _exception, _value: e, _stacktrace: "No stacktrace."}})
def wrap_edn_result(result):
return edn_format.dumps({_op: _result, _value: {_status: _success, _value: result}})
async def handle_ws():
uri = "ws://localhost:9001"
async with websockets.connect(uri) as websocket:
async def process(msg):
data = edn_format.loads(msg)
if data[_op] == _eval_js:
code = data[_code]
try:
result = context.eval(code)
out = wrap_edn_result(result)
await websocket.send(out)
except quickjs.JSException as e:
out = wrap_edn_error(e.args[0])
await websocket.send(out)
elif data[_op] == _error:
print('Websocket REPL error ' + data[_type])
async def cljs_eval_loop():
while True:
message = await websocket.recv()
await process(message)
async def repl_print_loop():
while True:
message = await repl_print_queue.get()
if message:
msg = edn_format.dumps({_op: _print, _value: edn_format.dumps(message + '\n')})
await websocket.send(msg)
await asyncio.gather(cljs_eval_loop(), repl_print_loop())
asyncio.get_event_loop().run_until_complete(handle_ws())
quickjs
edn_format
@myguidingstar
Copy link
Author

myguidingstar commented May 17, 2021

todo:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment