From calmjs.parse

calmjs.parse latest releasecalmjs.parse supported Pythonscalmjs.parse licensecalmjs.parse monthly downloadscalmjs.parse total downloadscalmjs.parse GitHub starscalmjs.parse last commit

calmjs.parse is a full JavaScript front end in pure Python. It lexes and parses ES5 to a concrete AST (es5(source)), walks and rewrites that tree through its walkers and asttypes modules, and prints it back with either pretty_print (readable, re-indented) or minify_print — whose obfuscate=True renames local identifiers the way a real minifier does. It can also emit source maps for the printed output. Built for the calmjs Node.js integration toolchain, it is the most capable of the PyPI minifiers, but it parses only ES5: modern syntax (arrow functions, let/const, classes, template literals) raises a syntax error, and the pure-Python parse is slow.

turbohtml covers the minification slice of that surface. minify_js() is the same parse-and-rename approach implemented in C, matching or beating calmjs.parse’s output size while running about two orders of magnitude faster and accepting a much larger slice of the language. It does not expose an AST, a pretty-printer, or source maps — it is a minifier, not a general JavaScript toolkit.

turbohtml vs calmjs.parse

Dimension

turbohtml

calmjs.parse

Scope

JavaScript minifier only (plus inline <script> minification in HTML)

Full ES5 front end: lexer, parser, AST, walkers, pretty-printer, minifier, source maps

Feature breadth

Whitespace/comment/number folding, identifier mangling, constant folding, dead-code elimination

AST access and rewriting, beautify and minify printers, identifier obfuscation, source maps

Performance

Native C, single-digit milliseconds on the corpus below

Pure Python, hundreds of milliseconds on the same inputs

Typing

Typed public API (minify_js(), JSMinify)

Untyped

Dependencies

None (ships the C extension)

ply (parser generator)

Maintenance

Actively developed alongside the turbohtml serializer

Maintained, stable, ES5-scoped

Feature overlap

The minification path ports 1:1:

  • Minify a source string to the shortest equivalent program: minify_print(es5(source)) maps to turbohtml.clean.minify_js(source, JSMinify(mangle=False)).

  • Rename local identifiers: calmjs.parse’s minify_print(es5(source), obfuscate=True) maps to turbohtml’s default mangle=True.

  • Both fold whitespace, drop comments, and shorten numeric literals unconditionally.

What turbohtml adds

  • Modern JavaScript: arrow functions, let/const, classes, and template literals minify instead of raising a syntax error.

  • Constant folding and dead-code elimination (JSMinify fold=True), beyond calmjs.parse’s whitespace-and-rename minification.

  • A native-C pipeline that runs about forty to eighty times faster on the corpus below.

  • Inline-<script> minification inside a full HTML document via Minify(minify_js=JSMinify()) on serialize() — no separate JS toolchain step.

  • A typed surface: minify_js() and the frozen JSMinify options object.

What calmjs.parse has that turbohtml does not

  • A parsed AST. es5(source) returns a walkable, mutable concrete syntax tree; turbohtml exposes no JavaScript AST, only the minified string. No equivalent — use calmjs.parse (or a Node-based tool) when you need to inspect or programmatically rewrite JavaScript.

  • A pretty-printer. pretty_print re-indents and beautifies; turbohtml only shrinks. No equivalent.

  • Source maps. calmjs.parse can emit a source map for its printed output; turbohtml’s minifier does not. No equivalent.

  • AST walkers and node types (calmjs.parse.walkers, calmjs.parse.asttypes) for custom analysis or transformation passes. No equivalent.

Performance

On the ES5 library ladder turbohtml reaches the smaller output, and the speed gap is large: on the same machine (python -m bench minify-js) calmjs.parse takes hundreds of milliseconds where turbohtml takes single-digit milliseconds. Each ratio is against turbohtml:

input

turbohtml

calmjs.parse

size

time

size

time

underscore 1.13 (67 kB)

19.3 kB

1.98 ms

20.9 kB (1.08x)

101 ms (50.8x)

backbone 1.6 (79 kB)

24.8 kB

1.13 ms

25.9 kB (1.05x)

116 ms (103x)

jquery 3.7 (279 kB)

87.6 kB

9.96 ms

93.2 kB (1.06x)

471 ms (47.3x)

lodash 4.17 (531 kB)

71.6 kB

9.35 ms

77.0 kB (1.08x)

448 ms (48.0x)

turbohtml beats calmjs.parse on size everywhere, at forty to eighty times less time and on modern JavaScript that calmjs.parse rejects outright, so for any build where minify time or modern syntax is in the loop turbohtml is the practical choice.

How to migrate

Swap the parse-then-print pair for the single minify call:

calmjs.parse

turbohtml

from calmjs.parse import es5

import turbohtml

from calmjs.parse.unparsers.es5 import minify_print

(none needed)

minify_print(es5(src), obfuscate=True)

turbohtml.clean.minify_js(src)

minify_print(es5(src))

turbohtml.clean.minify_js(src, JSMinify(mangle=False))

pretty_print(es5(src))

no equivalent (turbohtml only minifies)

# calmjs.parse
from calmjs.parse import es5
from calmjs.parse.unparsers.es5 import minify_print

minify_print(es5(source), obfuscate=True)  # ES5 only, in Python

# turbohtml
import turbohtml

turbohtml.clean.minify_js(source)  # in C, modern syntax too

To keep readable local names while still folding whitespace and comments, pass a JSMinify with mangle=False:

from turbohtml.clean import minify_js, JSMinify

minify_js(source, JSMinify(mangle=False))

Gotchas and pitfalls

  • Error type. calmjs.parse raises ECMASyntaxError on a script it cannot parse; turbohtml’s standalone minify_js() raises ValueError (with the construct, byte offset, and offending token), and TypeError if source is not a str. Inside HTML the inline-<script> pass instead leaves an unminifiable script verbatim rather than raising.

  • Modern syntax is a hard stop in calmjs.parse (a syntax error), where turbohtml minifies it. A migration that was silently skipping ES6+ files because they failed to parse will start minifying them.

  • No AST after the call. If existing code parses with es5() and then walks or mutates the tree, that path has no turbohtml counterpart; only the minified string is returned.

  • Obfuscation scope. calmjs.parse’s obfuscate=True renames local bindings and leaves globals alone unless obfuscate_globals is set; turbohtml’s mangle likewise renames only locals, so global-name behavior matches by default.