Using purs bundle --optimize for PureScript dead‑code elimination
Learn how the PureScript bundle optimizer removes unused PureScript definitions, see a minimal example, and understand its limits and common pitfalls.
01 Jun 2026, 00:56 UTC

Quick answer
Run purs bundle --main Module.Main --output out.js --optimize to produce a JavaScript bundle that excludes PureScript definitions that are not reachable from the entry point. The optimizer analyses the module graph and removes unused functions, types, and values before emitting the final .js file.
Worked example
Consider a tiny project with two modules:
src/Main.purs
module Main where
import Prelude
import Effect (Effect)
import Effect.Console (log)
main :: Effect Unit
main = log "Hello"
src/Helper.purs
module Helper where
unused :: String
unused = "this value is never referenced"
Only Main is used as the bundle entry point; Helper is imported nowhere.
Bundle without optimisation
Run the command in the project root (you need read access to the source files and write access to the output directory):
purs bundle --main Main --output out.jsThe generated
out.jswill contain the compiled code for bothMainandHelper, even thoughHelper.unusedis never used.Bundle with dead‑code elimination
Add the
--optimizeflag:purs bundle --main Main --output out.js --optimizeThe optimizer treats the export list of each module as a root set. Since
Helperis not reachable fromMain, its definitions are considered dead and are omitted from the output. You can verify this by inspecting the generated file: the string "this value is never referenced" and any helper‑specific runtime helpers should not appear.How the optimizer works
When
--optimizeis supplied,purs bundleperforms a static reachability analysis on the PureScript module graph:
- Start from the modules listed in
--main(or themainfield ofspago.dhall/package.dhall).- Follow exported symbols only; if a value is exported but never imported elsewhere, the export itself is considered a root.
- Mark all reachable definitions.
- Emit JavaScript only for marked definitions; everything else is dropped.
This analysis is performed on the PureScript code after type‑checking, so it can eliminate dead code that arises from unused type class instances, unused data constructors, or helper functions that are not exported.
Limits and common mistakes
Foreign Function Interface (FFI)
Definitions that come from foreign imports (
foreign import dataorforeign import) are conservatively kept because the optimizer cannot determine whether the foreign JavaScript might rely on them via dynamic property access or reflection. If you have a PureScript wrapper that simply re‑exports a foreign function, the wrapper may be removed, but the foreign declaration itself stays in the bundle.Lazy evaluation assumptions
The optimizer assumes that PureScript code is strict unless laziness is explicitly expressed through
Lazyor thunks. Using--optimizewithout also enabling--strict(or without ensuring your code does not depend on laziness) can cause runtime errors if the optimizer removes a thunk that is actually needed later.Exported but unused symbols
If a module exports a value that is never imported, the export itself is still a root and the value will be retained. To allow elimination, either stop exporting the symbol or ensure the module is not listed as a dependency of any reachable module.
Large external JavaScript via FFI
When a project bundles big JavaScript libraries through foreign imports, those libraries are not subject to PureScript’s dead‑code analysis, so the relative size reduction from optimisation may appear small.
Verification steps
To confirm that dead code has been removed:
- Build two bundles: one without
--optimize(out-full.js) and one with (out-opt.js).- Compare file sizes:
ls -lh out-full.js out-opt.js. The optimised version should be equal or smaller.- Search for a symbol known to belong to the unused module:
grep -F 'unused' out-opt.js. Expect no match.- Run the bundle in a Node or browser environment to ensure the program still behaves correctly when the unused module is truly unused.
Practical rollback
The bundling operation only creates (or overwrites) the output file. If you need to revert, simply delete the optimised output and regenerate the non‑optimised version, or keep a copy of the previous bundle before running the optimised command.
Summary
The
purs bundle --optimizeflag enables PureScript‑specific dead‑code elimination by performing a reachability analysis on exported symbols. It works well for removing unused PureScript functions, types, and values, but it cannot touch foreign imports, may be unsafe with lazy code if strictness is not assured, and retains exported symbols that are never used. Verify results by comparing bundle sizes and checking for absent symbols, and roll back by removing the generated output file.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.