Skip to content

musictheory.net Changes

iccir edited this page Oct 2, 2012 · 10 revisions

musictheory.net changes to UglifyJS

  • Added ast_maul, which implements global name mangling (see description below). Use the --maul command line options to turn on mauling.
  • Pulled https://github.com/mishoo/UglifyJS/pull/316 from laverdet
  • Incremented stack size in uglifyjs to 8MB, due to errors when build the ignore file. (Can also use --no-seqs, See mishoo/UglifyJS/issues/362.)
  • Added --input-state-file and --output-state-file, used to persist the options.state object between invocations of uglifyjs. While only ast_maul uses options.state at the moment, it may be useful for additional transformations in the future.
  • Added --reserved-names-from-file, similar to --reserved-names, but the name list is read from a file rather than stdin.

Background

musictheory.net has three main JS modules: a library of core functions (similar to Prototype.js or underscore.js), a library for UI widgets, and a library for shared exercise code. Consolidation into one JS file is not possible due to bandwidth concerns.

Exercises on the site require all three libraries. Lessons and tools need only the Core and UI modules. The rest of the site just needs Core.

In visual form, the dependency tree looks like this:

dependency tree

Google's Closure Compiler requires that manual exports and imports be set up between each module. This was not acceptable for our needs.

Mauler Design

Enter the Mauler addition to UglifyJS. When --maul is specified as a command-line option to uglifyjs, ast_maul is ran on the AST prior to ast_mangle. Maul works as follows:

  1. Names are transformed to a (hopefully shorter) mauled name form consisting of $ followed by [A-Za-z0-9]+. This affects function names, variable names, dot-syntax references, and object literal keys.
  2. The mapping of name to mauled name is global and persistent. Once the name Foo is mapped to the mauled $Aa, all references of Foo, regardless of scope, become $Aa. $Aa will never be reused for another name.
  3. Mappings may be shared among modules using --input-state-file and --output-state-file.
  4. Like Closure, string literals are never changed. Refer to Inconsistent Property Names for more information. Use the --maul-print-conflicts command-line option to show detected inconsistent property names.
  5. Like ast_mangle, ast_maul ignores all reserved names (--reserved-names).

Building a reserved names list

For my reserved names list, I use the contents of the externs directory from Closure Compiler.

svn checkout http://closure-compiler.googlecode.com/svn/trunk/externs
cat externs/* >> externs.js
uglifyjs --no-seqs --maul-make-reserved-names externs.js >> reserved_names

Example for the above dependency tree

# Build core.js first
#
uglifyjs --maul --reserved-names-from-file reserved_names \
         --output-state-file core.state \
         --overwrite core.js

# Now process ui.js, which depends on core.js
#
uglifyjs --maul --reserved-names-from-file reserved_names \
         --input-state-file core.state \
         --output-state-file ui.state \
         --overwrite ui.js

# Now process exercises.js, which depends on ui.js + core.js.
# ui.sym contains the symbols for both core.js and ui.js
#
uglifyjs --maul --reserved-names-from-file reserved_names \
         --input-state-file ui.state \
         --output-state-file exercises.state \
         --overwrite exercises.js

# Process a_site_page.js, which depends on just core.js.  Hence, we
# only need the mauled names from core.state.  Nothing depends on
# a_site_page.js, so we don't need to specify an output symbol file
#
uglifyjs --maul --reserved-names-from-file reserved_names \
         --input-state-file core.state \
         --overwrite a_site_page.js


# Process an_exercise_page.js, which depends on core.js, ui.js, and
# exercises.js.  exercises.state contains the mauled names for all
# three files.
#
# As before, nothing depends on an_exercise_page.js, so we don't need to
# specify an output symbol file
#
uglifyjs --maul --maul-ignore-list ignore \
         --input-state-file exercises.state \
         --overwrite an_exercise_page.js