Skip to content
This repository has been archived by the owner on Nov 3, 2021. It is now read-only.
/ hello-cljs Public archive

Hello, world! Native Clojure with ClojureScript and Node.js.

License

Notifications You must be signed in to change notification settings

rduplain/hello-cljs

Repository files navigation

Hello ClojureScript

Build Status

Summary

Write code with ClojureScript and npm. Target Node.js. Ship as binaries which run natively without dependencies on GNU/Linux, Mac OS X, FreeBSD, and Windows. The binaries ship with a snapshot filesystem and an embedded Node runtime loaded via ELF/Mach-O/PE32+, respective to the target operating system. Code can include npm modules and ClojureScript (Clojure) libraries.

Project Overview

This project demonstrates tooling for ClojureScript with workflows appropriate to Unix tradition with a build oriented toward general-purpose programming (with Node.js, and not the browser). Build redistributable native binaries with ClojureScript and npm libraries.

While Java is a dependency of Clojure, this project focuses exclusively on building ClojureScript for Node.js. The same workflow applies to other JavaScript targets, namely the browser, if the Clojure deps and npm packages support the runtime. The redistributable binary only applies with a Node.js target, but this workflow can produce a .js file suitable for other JavaScript targets.

Of particular note, both Clojure and JavaScript have many options available to build a project. This demonstration project arrives at many decisions aligned with Unix expectations:

  • Use Clojure command line tools directly, with deps.edn.
  • Use Node.js and npm directly, with package.json (node_modules).
  • Use shadow-cljs to build .js from deps.edn and node_modules.
  • Use pkg to build binaries from node_modules and the built .js file.
  • Use a Makefile (GNU Make) to provide a simple developer workflow.

Dependencies

Development:

Production has no dependencies. The redistributable binaries run natively on GNU/Linux, Mac OS X, and Windows, respectively.

Workflow

Development:

  • make -- Test, build binaries, and test platform binary (make release).
  • make bin -- Create redistributable binaries.
  • make build -- Build the ClojureScript project into a single .js file.
  • make install -- Install packages; this is run automatically.
  • make outdated -- Check for outdated packages.
  • make release -- Test, build binaries, and test platform binary.
  • make repl -- Run a ClojureScript repl. (Use an IDE instead, below.)
  • make test-refresh -- Run project tests and watch for changes.
  • make test -- Run project tests.

To build a binary for FreeBSD, set up a development environment and run make release-for-os on a FreeBSD system, with resulting binary at ./target/hello.

Production:

  • After running make, find redistributable binaries in ./target/bin-*/.
  • The redistributable binaries run natively without dependencies on GNU/Linux, Mac OS X, and Windows, respectively.
  • The binaries include their own Node.js runtime and therefore are relatively large (tens of megabytes) compared to other system executables.
  • If needed, these binaries compress by a factor of 3 with bzip2. A release step could publish compressed binaries with an install step which decompresses and puts the binary on the PATH.

Integrated Development Environment

Run M-x cider-jack-in from Emacs with CIDER, using shadow-cljs as the command. Once loaded, in the CIDER clj repl, start a CLJS repl with:

(require '[shadow.cljs.devtools.api :as shadow])
(shadow/node-repl :app)

Note that shadow-cljs has a fake piggieback; use the shadow-cljs snippet above in a clj repl to create the cljs repl instead of using M-x cider-jack-in-cljs. Alternatively, load the snippet above into a custom cljs repl type in CIDER with its cider-register-cljs-repl-type function and use M-x cider-jack-in-cljs on that newly registered type.

While cider-connect is available, the available tools are best run separately as to allow repl interactions separately from the build process. Start this CIDER repl before starting any other shadow-cljs process.

Using npm Packages

shadow-cljs loads any module found in the node_modules directory. See this guide for example require forms.

Using Macros

Keep in mind that ClojureScript has differences from Clojure:

ClojureScript’s macros must be defined in a different compilation stage than the one from where they are consumed. One way to achieve this is to define them in one namespace and use them from another.

To use a macro in a ClojureScript project, define the macro in a .clj or .cljc file. In a cljs (ns ...) form, reference macros with the (:require-macros ...) form, which has the same format as (:require ...). Note that ClojureScript compiler :optimizations set to :none in development will result in Node.js loading separate files. In effect, this means that any namespace references in a macro will not be automatically brought forward when requiring the macro during a repl session or when tests are running (unless using shadow-cljs release subcommand to compile the tests into a single file), and names will not resolve and will be undefined in the resulting macro-expanded code.

In development:

  • In the repl, load the requisite macro definition and its require calls.
  • In the cljs test matching the macro definition, require the namespaces which will allow the expanded macro to fully resolve its references.

These steps are only strictly required in development; the production build compiles a single .js and Node.js and may find the relevant references. Test accordingly.

Wishlist


Copyright (c) 2018-2020, Ron DuPlain. EPL-1.0 licensed. Use freely on projects with any license, including proprietary work.