cljs.user=> (require '[clojure.set :refer :all])
clojure.lang.ExceptionInfo: Don't know how to create ISeq from: clojure.lang.Keyword at line 1 ...
26 March 2018
ClojureScript Team
ClojureScript 1.10 is finally out!
Many enhancements have been made, including support for Java 9 and Java 10. Below, we’ll take you through a quick tour of some of the major new features.
ClojureScript now includes cljs.main
, which brings many of the capabilities of clojure.main
(and popularized via the new Clojure CLI tools) to ClojureScript, along with special support for the additional considerations and capabilities of the ClojureScript environment, allowing you to compile or run ClojureScript code directly from the command line.
The cljs.main
capability has allowed us to drastically simplify the experience for new users. Much of the complexity and ceremony in the previous version of the Quick Start guide has simply evaporated.
And for experienced ClojureScript users, cljs.main
makes what should be easy operations, well… easy. Many things can now be done with just a command line flag or two, without any need for complicated setup.
You can read more about cljs.main
at ClojureScript Command Line.
The shared AOT cache feature saves artifacts compiled from JARs so that they can be reused across your projects, or reused in the event that you do a clean build in a project. This can save time by avoiding recompiling code that does not change.
You can read more about this feature at Shared AOT Cache.
ClojureScript now uses the latest Closure Compiler release (v20180204), which includes many improvements for consuming Node modules. In addition to the Closure Compiler update, several changes were implemented on the ClojureScript side. Some of the improvements include:
CommonJS and ES6 modules can now require each other
Closure now detects and is able to remove more UMD wrappers
Node module support can be disabled by setting the :npm-deps
option to false
for cases where the node_modules
directory exists but should not be used
A new compiler option, :stable-names
has been introcuced. This reduces name churn between advanced builds and facilitates proper vendorization if you’re using :modules
.
This release adds several cljs.server.*
namespaces for integration with -Dclojure.server.repl
, allowing for much richer ClojureScript Socket REPL capabilities.
In addition, support for pREPL has been added. This is similar to Socket REPL, but is instead EDN-based so that tooling can programatically consume REPL output.
The new Socket REPL and pREPL features require Clojure 1.10.0-alpha4
to be on the classpath.
The core.specs.alpha library has been ported to ClojureScript, and is available in this release as an opt-in feature. This library contains specs that describe core macros and functions. Support for the ns
special form is additionally included.
To use this library, simply require the new cljs.core.specs.alpha
namespace. By doing this, specs for defn
, let
, and other macros will be registered, and subsequent compilation of these macros will be subject to spec validation.
The following illustrates its use at the REPL. Let’s say you accidentally attempt to refer all symbols of a library, using a Clojure-specific feature that does not exist in ClojureScript:
cljs.user=> (require '[clojure.set :refer :all])
clojure.lang.ExceptionInfo: Don't know how to create ISeq from: clojure.lang.Keyword at line 1 ...
This error is a bit cryptic. Now, let’s try again, but using core.specs.alpha
:
cljs.user=> (require 'cljs.core.specs.alpha)
nil
cljs.user=> (require '[clojure.set :refer :all])
clojure.lang.ExceptionInfo: Call to cljs.core/require did not conform to spec:
In: [0 1 :refer] val: :all fails spec: :cljs.core.specs.alpha/refer at:
[:args :spec :libspec :lib+opts :options :refer] predicate: coll?
...
The resulting error is essentially indicating that :all
is the problem and that :refer
takes a collection as its argument.
This feature is still alpha, but we encourage you to give it a try and report any defects you might find!
With this ClojureScript release, the results of iterate
, repeat
and cycle
are now directly reducible. This brings some great work that Alex Miller did for Clojure a few years ago to ClojureScript. This means that you will get much better performance when reducing over the output of these functions.
Take, for example, a benchmark involving running (transduce (take 64) + (iterate inc 0))
a total of 10,000 times when compiled with :advanced
optimizations. You can try this benchmark on your machine, but we are seeing this run 4.5 times faster under V8 and SpiderMonkey, and 3.3 times faster on JavaScriptCore.
In addition, this provides a way to process large output without involving intermediate sequence generation, thus bypassing the lack of locals-clearing and inevitable head-holding that occurs in ClojureScript. This means you can now run programs like
(transduce (comp (map inc) (filter odd?) (take 1e8)) + (iterate inc 0))
and they will consume very little memory. This example completes in around 10 seconds in the Node REPL, using just a few megabytes of RAM, whereas previously it would essentially never terminate, consuming gigabytes of RAM.
As an expediency, ClojureScript has been returning 2-element vectors for non-sorted persistent map entries. For many use cases, this is OK because map entries can be used as vectors. But, the opposite is not the case, and to pull this off, ClojureScript needed to add artificial support to persistent vectors for the key
and val
map entry functions.
In order to align with Clojure, ClojureScript now returns a dedicated map entry type for this case and eliminates the artifical vector support. One example illustrating higher fidelity with Clojure is that this allows ClojureScript to properly return nil
when empty
is applied to a map entry. (Since map entries have exactly two elements, it is impossible to have an empty map entry.)
While this certainly cleans things up, be on the lookout for code that incorrectly treats vectors as map entries. For example, while (key (first {:a 1}))
and (val (first {:a 1}))
are perfectly valid, (key [:a 1])
and (val [:a 1])
are incorrect and will result in a runtime exception.
Finally, using a dedicated map entry type can lead to performance improvements in some code that works with map entries. For example, in :advanced
mode, this code
(simple-benchmark [m (zipmap (range 100) (range))]
(reduce (fn [a [k v]] (if (even? v) (+ a k) a)) 0 m) 100000)
runs 11% faster in JavaScriptCore, 18% faster in V8, and a whopping 105% faster in SpiderMonkey. And if you use the dedicated key
and val
functions instead of destructuring, the V8 performance goes to 44% faster and SpiderMonkey 112%.
For a complete list of updates in ClojureScript 1.10.238 see Changes.
Thanks to all of the community members who contributed to ClojureScript 1.10.238:
Andrea Richiardi
Bruce Hauman
Dieter Komendera
Enzzo Cavallo
Erik Assum
Hendrik Poernama
Jannis Pohlmann
Jinseop Kim
John Newman
Juho Teperi
Levi Tan Ong
Mark Hepburn
Martin Klepsch
Mike Fikes
Oliver George
Paulus Esterhazy
Roman Scherer
Thomas Heller
Tim Pote
Tom Mulvaney