1 Introduction
ClojureScript is a compiler that translates the Clojure programming language into JavaScript, enabling developers to write web applications, mobile apps (via React Native), and other software that runs in JavaScript environments. It preserves Clojure’s core features—immutable data structures, functional programming, and homoiconicity—while offering seamless interoperation with the JavaScript ecosystem. ClojureScript typically compiles to optimized JavaScript and is often used with React wrappers such as Reagent to build interactive user interfaces.
1.1 History and Relationship to Clojure
ClojureScript was publicly released in 2011 by Rich Hickey, the creator of Clojure. It was designed to extend the benefits of Clojure—a dialect of Lisp that runs on the Java Virtual Machine—to the browser and other JavaScript runtimes. The compiler itself is written in Clojure and uses the Google Closure Compiler as one of its backends. ClojureScript is not a separate language but rather a targeted compilation of the same Clojure language; almost all Clojure code can be compiled to JavaScript with minimal changes, though some JVM-specific host facilities are not available.
1.2 Rationale: Why ClojureScript over JavaScript
ClojureScript offers several advantages over vanilla JavaScript: immutable persistent data structures reduce bugs related to state mutation; a consistent functional paradigm encourages composability; homoiconicity (code as data) enables powerful metaprogramming via macros; and the REPL-driven development workflow provides immediate feedback. Its interop capabilities allow access to any JavaScript library, so developers can leverage the vast npm ecosystem while writing in a more expressive and safe dialect.
1.3 Target Environments
ClojureScript can run in any environment that supports JavaScript, including web browsers, Node.js, React Native, and even desktop frameworks like Electron. The compiled output targets ECMAScript 5, 6, or newer depending on configuration, and can be used both client-side and server-side (e.g., with Node.js for server-rendered applications or build scripts). Mobile development is supported through the ClojureScript React Native bridge, allowing code sharing between web and mobile platforms.
2 Language Features
ClojureScript retains nearly all language features of Clojure, with adaptations for the JavaScript runtime.
2.1 Core Differences from Clojure (JVM)
The primary differences stem from the underlying host environment: ClojureScript does not have access to JVM classes, threads, or concurrency primitives like future and pmap (which rely on Java threads). It replaces these with JavaScript equivalents—for example, using JavaScript's setTimeout for asynchrony and atoms for state management. The ClojureScript compiler also handles namespaces differently, using JavaScript module systems. Some Clojure features like definterface are absent, but protocols and records are fully supported.
2.2 Immutable Data Structures and Persistent Collections
ClojureScript implements the same persistent data structures as Clojure: vectors, maps, sets, lists, and queues. These structures are immutable by default and share structural parts between copies for efficiency. The built-in functions for transformation—map, filter, reduce, assoc, dissoc, etc.—are identical to those in Clojure JVM. The only difference is that ClojureScript uses JavaScript numerical types under the hood, so integer arithmetic is limited to 53-bit precision.
2.3 Protocols and Records
Protocols (analogous to interfaces) and records (analogous to typed maps) are fully supported in ClojureScript. Protocols allow polymorphic dispatch based on the type of the first argument, and records provide a form of custom data types with default map semantics. Both compile to efficient JavaScript, using prototype chains for records and simple function maps for protocols.
2.4 Macros and Homoiconicity
ClojureScript supports macros via the same reader and compiler macros used in Clojure. Because macros run at compile time, they are written in Clojure (JVM or self-hosted ClojureScript) and not in JavaScript. Homoiconicity means that ClojureScript code is represented as Clojure data structures (lists, symbols, keywords), enabling powerful code transformations. Macros can be used to extend the language, create domain-specific languages, or eliminate boilerplate.
2.5 Host Interoperability (JavaScript Interop)
ClojureScript provides several mechanisms to interact with JavaScript code and libraries.
2.5.1 Calling JavaScript Libraries
JavaScript libraries are accessed via js/ namespace prefix, require (for npm packages), or the import macro. For example, (js/console.log "hello") calls the browser's console; (-> (new js/Date) .getFullYear) creates a Date object and calls its method. The #js reader literal creates JavaScript arrays and objects.
2.5.2 Using JavaScript Objects and Functions
Functions and methods are called with dot notation: (.method obj arg1 arg2) or (.-property obj). The aget and aset functions handle property access on JavaScript arrays and objects. ClojureScript automatically wraps JavaScript objects to appear as Clojure maps via the js->clj and clj->js functions for conversion.
2.5.3 Type Hints and Type Annotations
Type hints (e.g., ^js) are used to aid the compiler in efficient JavaScript interop, especially when calling methods with .. (double dot) syntax. While ClojureScript is dynamically typed, optional type annotations can be added via the ^int, ^number, ^string metadata to improve performance and avoid JavaScript boxing. These hints are used only by the compiler and disappear at runtime.
3 Tooling and Build Systems
ClojureScript development relies on build tools that manage compilation, REPL connectivity, and project dependencies.
3.1 Compilation Process
ClojureScript source code is compiled by the ClojureScript compiler, which outputs JavaScript files. The compilation can be done eagerly for production or incrementally for development. Multiple build tools orchestrate this process.
3.1.1 Leiningen and ClojureScript build
Leiningen is a build automation tool for Clojure that can also compile ClojureScript via the cljsbuild plugin. It allows configuration of multiple build targets, optimizations, and output options. However, Leiningen’s approach to ClojureScript compilation has largely been superseded by more modern tools.
3.1.2 shadow-cljs
shadow-cljs is the most popular ClojureScript build tool as of the mid-2020s. It integrates deeply with npm, supports hot module replacement, optimizes production builds using the Google Closure Compiler, and provides a powerful development server. It eliminates the need for manual cljsbuild configuration and handles both ClojureScript and JavaScript dependencies seamlessly.
3.1.3 figwheel-main
figwheel-main (formerly figwheel) is another build tool that emphasizes live reloading and incremental compilation. It integrates with Leiningen or shadow-cljs and provides a hot-reloading REPL. While less feature-rich than shadow-cljs for npm integration, it remains popular for lightweight projects.
3.2 REPL and Development Workflow
ClojureScript supports a two-way interactive REPL (Read-Eval-Print Loop) that compiles code on the fly and sends results to the browser or Node.js. This enables dynamic development where functions can be redefined without reloading the page. Tools like figwheel, shadow-cljs, and the ClojureScript Node.js REPL provide the necessary connectivity.
3.3 Debugging and Source Maps
ClojureScript compilers generate source maps linking the generated JavaScript back to the original .cljs files. This allows developers to debug using browser developer tools while viewing the ClojureScript source. The source maps are typically accurate enough for step-through debugging, though some advanced optimization passes may complicate mapping.
4 Development Patterns and Frameworks
ClojureScript is often used with React-based frameworks to build single-page applications (SPAs) and other interactive UIs.
4.1 React Integration
Several ClojureScript libraries provide idiomatic wrappers around React.
4.1.1 Reagent
Reagent is the most widely used React wrapper for ClojureScript. It represents React components as ClojureScript functions (Hiccup-style markup) and manages state with Clojure’s atom, reagent.core/atom, and reactive dereferencing. Reagent components automatically re-render when their tracked state changes, offering a simple and functional approach to UI development.
4.1.2 Rum
Rum is a lightweight, flexible React wrapper that emphasizes composability and offers both functional and class-based component styles. It provides low-level access to React's lifecycle methods and can be used with any state management approach. Rum is preferred when fine-grained control over rendering is required.
4.1.3 Helix
Helix is a more recent React wrapper that directly maps to React hooks (e.g., useState, useEffect). It provides TypeScript-like type validation at compile time (via macros) and integrates well with modern React patterns. Helix is designed for developers who prefer a more literal mapping to the React API.
4.2 State Management
In addition to local component state, ClojureScript applications often use centralized state management.
4.2.1 Re-frame
Re-frame is a reactive state management library built on top of Reagent. It enforces a unidirectional data flow: events trigger handlers that update a single application state (stored in a reagent/atom), and subscriptions derive views from that state. Re-frame encourages a well-structured, testable architecture and is the de facto standard for medium-to-large ClojureScript SPAs.
4.2.2 Datascript
Datascript is an in-memory implementation of the Datomic database model for ClojureScript. It can be used as an alternative to Re-frame’s flat state, providing a queryable entity-attribute-value store with Datalog queries. Datascript is especially useful for applications with complex data relationships, such as collaborative editing or content management systems.
4.3 Single Page Applications (SPAs)
ClojureScript SPAs typically use a client-side router and optional server-side rendering.
4.3.1 Routing and Navigation
Libraries like reitit, bidi, and accountant provide client-side routing. They map URL patterns to Reagent or Rum components, handling history and hash-based navigation. Reitit is particularly well-integrated with the ClojureScript ecosystem and supports both client and server routing.
4.3.2 Server-Side Rendering (SSR)
ClojureScript can be rendered on the server (Node.js) using libraries like fulcro or custom setups with Reagent’s reagent.dom.server/render-to-string. SSR improves initial load times and SEO. The same components and state management can run on both client and server, enabling isomorphic applications.
5 Ecosystem and Libraries
The ClojureScript ecosystem includes a rich set of libraries accessible via Clojars (Java/Clojure repository) and npm.
5.1 Core Libraries
ClojureScript provides a standard library mirroring Clojure’s clojure.core, with additions for the host environment. Key libraries include cljs.core (core functions), clojure.string, clojure.set, clojure.walk, and clojure.math. For async operations, core.async (channels and CSP) is fully supported in ClojureScript.
5.2 Testing and Quality Assurance
Testing is performed with cljs.test (part of the standard library) and runners like doo (for headless execution in browsers or Node.js). For property-based testing, clojure.test.check works in ClojureScript. Integration tests can be run with Cypress or Selenium via JavaScript interop.
5.3 Package Management (Clojars, npm)
ClojureScript packages are distributed via Clojars (Java/Clojure repository) or npm (JavaScript ecosystem). shadow-cljs and leinigen can pull dependencies from both sources. The package.json and node_modules are used for npm packages, while Clojars dependencies are resolved via a project configuration file (e.g., deps.edn or project.clj).
6 Performance and Optimization
ClojureScript performance is generally on par with hand-written JavaScript for equivalent algorithms, thanks to the Google Closure Compiler’s advanced optimizations.
6.1 Advanced Compilation (Google Closure Compiler)
The Google Closure Compiler (GCC) is a JavaScript optimizer used as the final compilation backend. In advanced mode, GCC renames symbols, inlines functions, removes dead code, and performs type-based optimizations. ClojureScript code compiled with GCC’s advanced optimizations can be significantly smaller and faster than unoptimized code.
6.2 Dead Code Elimination
GCC’s dead code elimination (DCE) is particularly effective because ClojureScript’s functional style tends to produce many small, isolated functions that can be removed if unused. Developers can further reduce bundle size by avoiding runtime reflection and using type hints. Custom :export directives ensure that only entry points are retained.
6.3 Profiling and Performance Tuning
ClojureScript applications can be profiled using browser DevTools, Node.js profiler, or the JavaScript console.profile API. Common bottlenecks include excessive allocation of persistent data structures (though they are efficient), excessive re-rendering in React, or slow interop calls. Performance tuning often involves switching to transient data structures for mutation-heavy hot spots, using memoize, or rewriting performance-critical sections in JavaScript via interop.
7 Common Use Cases
ClojureScript is used in a variety of application domains.
7.1 Full-Stack Clojure (Clojure + ClojureScript)
A typical full-stack Clojure application uses Clojure on the JVM for the backend (e.g., Ring, Compojure, or Pedestal) and ClojureScript for the frontend. Shared code—such as validation logic, data schemas, or domain models—can be written once and used in both environments. Libraries like cljc files enable cross-platform code.
7.2 Mobile Development (React Native via ClojureScript)
ClojureScript can target iOS and Android via React Native. Libraries like reagent-react-native or exponent-cljs allow writing mobile apps with the same Reagent/re-frame stack used for web. The developer benefits from code sharing between web and mobile, though native module interop may require extra bridge code.
7.3 Desktop and Node.js Applications
ClojureScript is also used for desktop applications (via Electron) and server-side scripting (Node.js). In Node.js, it can write command-line tools, web servers (e.g., using Express), or build scripts. The same immutable, functional style works uniformly across these environments.
8 Comparison with Alternatives
ClojureScript competes with other compile-to-JS languages and frameworks.
8.1 ClojureScript vs TypeScript
TypeScript adds static typing to JavaScript, catching type errors early. ClojureScript, by contrast, is dynamically typed but offers immutable data structures and a macro system. TypeScript has a larger community and more tooling support, while ClojureScript provides a more expressive functional foundation. Both can be used with React, but ClojureScript’s REPL and state management (Re-frame) may boost productivity for complex UIs.
8.2 ClojureScript vs Elm
Elm is a purely functional language with strict type system and no runtime exceptions. It is simpler and more opinionated than ClojureScript, leading to safer code but less flexibility. ClojureScript’s interop with JavaScript gives it broader applicability, while Elm’s architecture (The Elm Architecture) is similar to Re-frame. ClojureScript generally has a steeper learning curve due to Lisp syntax and macros.
8.3 ClojureScript vs Vanilla JavaScript
Vanilla JavaScript offers universal adoption, direct access to the DOM, and zero build setup. ClojureScript provides immutability by default, a powerful macro system, and better state management patterns. For large codebases, ClojureScript can reduce bug rates and improve maintainability, but requires a build toolchain and familiarity with functional programming.
9 Community and Resources
The ClojureScript community is active and supportive, with numerous projects and learning materials.
9.1 Key Projects and Community Events
Notable projects include Reagent, re-frame, shadow-cljs, and the ClojureScript compiler itself. Community events such as Clojure/conj, ClojureD, and ClojureBridge often feature ClojureScript talks. Online spaces include the ClojureScript Slack channel, Clojurians Zulip, and the /r/ClojureScript subreddit.
9.2 Learning Materials and Documentation
Official documentation is available at clojurescript.org. Books such as *Living Clojure* by Carin Meier and *ClojureScript: Up and Running* by Stuart Sierra cover language basics and tooling. Interactive tutorials like “Try ClojureScript” and the ClojureScript Koans help beginners, while advanced topics are covered in blog posts from the community (e.g., David Nolen’s brand on Google Closure Compiler).