Using ClojureScript’s :target :es6 for Native ES‑Module Integration
Learn how to compile ClojureScript to native ES6 modules, expose only the needed vars, and bundle with Webpack or Rollup. A step‑by‑step example shows the compiler flags, metadata, and a quick sanity check.
25 Jul 2026, 07:56 UTC

Problem: Mixing ClojureScript with modern JavaScript tooling
When a ClojureScript project grows, developers often need to share code with plain JavaScript modules, use Webpack or Rollup for tree‑shaking, or run the code in Node.js that expects native ES‑modules. The default ClojureScript compiler output is a single goog.require‑based bundle, which does not play nicely with these tools.
How :target :es6 works
- Compiler flag:
:target :es6tells the compiler to emit each namespace as an independent ES‑module. - Exporting vars: Only vars marked with
^:exportbecome public exports. All other vars stay private to the module. - Dependencies: The compiler generates
importstatements forgoog.requiredependencies, so the module can use other ClojureScript namespaces or JavaScript modules. - Compatibility: The output can be consumed by browsers with
type="module", Node.js (v14+ withtype=moduleor--experimental-modules), or any bundler that understands ES‑modules.
Concrete Example
- Create a simple namespace –
src/myapp/core.cljs:(ns myapp.core (:require [clojure.string :as str])) ;; Exported function (defn ^:export greet [name] (str "Hello, " name "!")) ;; Internal helper – not exported (defn internal-hello [name] (str "Hi, " name)) - Compile with :target :es6 – in
project.cljorshadow-cljs.edn::compiler { :target :es6 :optimizations :simple }Run
lein cljsbuild onceorshadow-cljs release coreto producecore.js. - Check the generated file – the first few lines look like:
import * as str from "clojure.string.js"; export function greet(name) { return str.concat("Hello, ", name, "!"); } // internal-hello is not exportedNotice the
export function greetand theimportofclojure.string.js. - Test in Node.js – create
test.js:import { greet } from "./core.js"; console.log(greet("World")); // → Hello, World!Run with
node --experimental-modules test.jsornode test.jsif your package.json has"type": "module". The output should be exactlyHello, World!. - Bundle with Rollup –
rollup.config.js:export default { input: "core.js", output: { file: "bundle.js", format: "esm" }, plugins: [ // Rollup will tree‑shake unused exports ] };Running Rollup will produce
bundle.jsthat contains only the exportedgreetfunction.
Trade‑offs and Limitations
- Advanced optimizations: When
:optimizations :advancedis used, exported names are mangled unless you provide externs. Add an extern file that lists the exported vars to preserve their names. - Bundle size: Using
:target :es6with:optimizations :simplecan lead to larger bundles because dead‑code elimination is less aggressive than with:advanced. - Interop with CommonJS: Native ES‑modules cannot be required via
require()in a CommonJS context without a loader or transpilation step. - Metadata requirement: Every var you want to expose must be annotated with
^:export. Forgetting this will result in the var being private and potentially removed during optimization.
Actionable Closing
- Set
:target :es6in your compiler config and add^:exportmetadata to public vars. - Compile and inspect the output for
exportstatements. - If you plan to use
:advanced, create an extern file listing the exported names. - Integrate the generated JS with Webpack, Rollup, or Node.js by importing the module. Verify by running a small test script.
- Use tree‑shaking in your bundler to keep the bundle lean – only the exported functions you actually import will survive.
By emitting native ES6 modules, ClojureScript projects can seamlessly share code with the broader JavaScript ecosystem, leverage modern bundlers’ optimizations, and keep encapsulation intact.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.