Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #39 from ConradIrwin/source-maps

Source maps
  • Loading branch information...
commit 2f6c31a67d6dbd04c3153603339fab49a06ec4ec 2 parents e32bb87 + f90bc56
@lautis authored
View
5 .gitmodules
@@ -1,3 +1,6 @@
[submodule "vendor/uglifyjs"]
path = vendor/uglifyjs
- url = https://github.com/mishoo/UglifyJS.git
+ url = https://github.com/mishoo/UglifyJS2.git
+[submodule "vendor/source-map"]
+ path = vendor/source-map
+ url = https://github.com/mozilla/source-map.git
View
1  Gemfile
@@ -22,4 +22,5 @@ group :development do
gem "bundler", "~> 1.0"
gem "jeweler", "~> 1.8.3"
gem "rdoc", "~> 3.11"
+ gem "source_map"
end
View
4 README.md
@@ -54,9 +54,7 @@ Available options and their defaults are
## Development
-Uglifier uses [stitch](https://github.com/sstephenson/stitch) to compile UglifyJs for non-node JS runtimes. If you need to update or patch UglifyJS, you can stitch UglifyJS using
-
- node build.js
+Uglifier bundles its javascript dependencies using git submodules. If you want to rebuild the javascript you will first need to get the latest version of the code with `git submodule update --init`. After you have the git submodules at the desired versions, run `rake js` to recreate `lib/uglify.js`.
See [CONTRIBUTING](https://github.com/lautis/uglifier/blob/master/CONTRIBUTING.md) for details about contributing to Uglifier.
View
22 Rakefile
@@ -37,3 +37,25 @@ Rake::RDocTask.new do |rdoc|
rdoc.rdoc_files.include('README*')
rdoc.rdoc_files.include('lib/**/*.rb')
end
+
+desc "Rebuild lib/uglify.js"
+task :js do
+
+ cd 'vendor/source-map/' do
+ `npm install`
+ `node Makefile.dryice.js`
+ end
+
+ cd 'vendor/uglifyjs/' do
+ # required to run ./uglifyjs2 --self; not bundled.
+ `npm install`
+ end
+
+ source = ""
+ source << "window = this;"
+ source << File.read("vendor/source-map/dist/source-map.js")
+ source << "MOZ_SourceMap = sourceMap;"
+ source << `./vendor/uglifyjs/bin/uglifyjs2 --self`
+
+ File.write("lib/uglify.js", source)
+end
View
23 build.js
@@ -1,23 +0,0 @@
-#!/usr/bin/env node
-
-var fs = require("fs");
-var stitch = require("stitch");
-
-var package = stitch.createPackage({
- paths: [__dirname + "/vendor/uglifyjs/lib"]
-});
-
-package.compile(function(err, source) {
- if (err) throw err;
-
- source = "(function(global) {" +
- source + ";\n" +
- "global.UglifyJS = {};\n" +
- "global.UglifyJS.parser = this.require('parse-js');\n" +
- "global.UglifyJS.uglify = this.require('process');\n" +
- "}).call({}, this);\n";
-
- fs.writeFile(__dirname + "/lib/uglify.js", source, function(err) {
- if (err) throw err;
- });
-});
View
167 lib/uglifier.rb
@@ -10,7 +10,6 @@ class Uglifier
# Default options for compilation
DEFAULTS = {
:mangle => true, # Mangle variable and function names, use :vars to skip function mangling
- :toplevel => false, # Mangle top-level variable names
:except => ["$super"], # Variable names to be excluded from mangling
:max_line_length => 32 * 1024, # Maximum line length
:squeeze => true, # Squeeze code resulting in smaller, but less-readable code
@@ -28,7 +27,11 @@ class Uglifier
:indent_level => 4,
:indent_start => 0,
:space_colon => false
- }
+ },
+ :source_filename => nil, # The filename of the input
+ :source_root => nil, # The URL of the directory which contains :source_filename
+ :output_filename => nil, # The filename or URL where the minified output can be found
+ :input_source_map => nil, # The contents of the source map describing the input
}
SourcePath = File.expand_path("../uglify.js", __FILE__)
@@ -44,6 +47,16 @@ def self.compile(source, options = {})
self.new(options).compile(source)
end
+ # Minifies JavaScript code and generates a source map using implicit context.
+ #
+ # source should be a String or IO object containing valid JavaScript.
+ # options contain optional overrides to Uglifier::DEFAULTS
+ #
+ # Returns a pair of [minified code as String, source map as a String]
+ def self.compile_with_map(source, options = {})
+ self.new(options).compile_with_map(source)
+ end
+
# Initialize new context for Uglifier with given options
#
# options - Hash of options to override Uglifier::DEFAULTS
@@ -58,89 +71,115 @@ def initialize(options = {})
#
# Returns minified code as String
def compile(source)
+ really_compile(source, false)
+ end
+ alias_method :compress, :compile
+
+ # Minifies JavaScript code and generates a source map
+ #
+ # source should be a String or IO object containing valid JavaScript.
+ #
+ # Returns a pair of [minified code as String, source map as a String]
+ def compile_with_map(source)
+ really_compile(source, true)
+ end
+
+ private
+
+ # Minifies JavaScript code
+ #
+ # source should be a String or IO object containing valid JavaScript.
+ def really_compile(source, generate_map)
source = source.respond_to?(:read) ? source.read : source.to_s
- js = []
- js << "var result = '';"
- js << "var source = #{json_encode(source)};"
- js << "var ast = UglifyJS.parser.parse(source);"
+ js = <<-JS
+ var options = %s;
+ var source = options.source;
+ var ast = UglifyJS.parse(source, options.parse_options);
+ ast.figure_out_scope();
- if @options[:lift_vars]
- js << "ast = UglifyJS.uglify.ast_lift_variables(ast);"
- end
+ if (options.squeeze) {
+ var compressor = UglifyJS.Compressor(options.compressor_options);
+ ast = ast.transform(compressor);
+ ast.figure_out_scope();
+ }
- if @options[:copyright]
- js << <<-JS
- var comments = UglifyJS.parser.tokenizer(source)().comments_before;
- for (var i = 0; i < comments.length; i++) {
- var c = comments[i];
- result += (c.type == "comment1") ? "//"+c.value+"\\n" : "/*"+c.value+"*/\\n";
+ if (options.mangle) {
+ ast.compute_char_frequency();
+ ast.mangle_names(options.mangle_options);
}
- JS
- end
- js << "ast = UglifyJS.uglify.ast_mangle(ast, #{json_encode(mangle_options)});"
+ var gen_code_options = options.gen_code_options;
- if @options[:squeeze]
- js << "ast = UglifyJS.uglify.ast_squeeze(ast, #{json_encode(squeeze_options)});"
- end
+ if (options.generate_map) {
+ var source_map = UglifyJS.SourceMap(options.source_map_options);
+ gen_code_options.source_map = source_map;
+ }
- if @options[:unsafe]
- js << "ast = UglifyJS.uglify.ast_squeeze_more(ast);"
- end
+ var stream = UglifyJS.OutputStream(gen_code_options);
- js << "result += UglifyJS.uglify.gen_code(ast, #{json_encode(gen_code_options)});"
+ if (options.copyright) {
+ var comments = ast.start.comments_before;
+ for (var i = 0; i < comments.length; i++) {
+ var c = comments[i];
+ stream.print((c.type == "comment1") ? "//"+c.value+"\\n" : "/*"+c.value+"*/\\n");
+ }
+ }
- if !@options[:beautify] && @options[:max_line_length]
- js << "result = UglifyJS.uglify.split_lines(result, #{@options[:max_line_length].to_i})"
- end
+ ast.print(stream);
+ if (options.generate_map) {
+ return [stream.toString() + ";", source_map.toString()];
+ } else {
+ return stream.toString() + ";";
+ }
+ JS
+
+ @context.exec(js % json_encode(
+ :source => source,
+ :compressor_options => compressor_options,
+ :gen_code_options => gen_code_options,
+ :mangle_options => mangle_options,
+ :parse_options => parse_options,
+ :source_map_options => source_map_options,
+ :squeeze => squeeze?,
+ :mangle => mangle?,
+ :copyright => copyright?,
+ :generate_map => (!!generate_map)
+ ))
+ end
- js << "return result + ';';"
+ def mangle?
+ !!@options[:mangle]
+ end
- @context.exec js.join("\n")
+ def squeeze?
+ !!@options[:squeeze]
end
- alias_method :compress, :compile
- private
+ def copyright?
+ !!@options[:copyright]
+ end
def mangle_options
- {
- "mangle" => @options[:mangle],
- "toplevel" => @options[:toplevel],
- "defines" => defines,
- "except" => @options[:except],
- "no_functions" => @options[:mangle] == :vars
- }
+ {"except" => @options[:except]}
end
- def squeeze_options
+ def compressor_options
{
- "make_seqs" => @options[:seqs],
+ "sequences" => @options[:seqs],
"dead_code" => @options[:dead_code],
- "keep_comps" => !@options[:unsafe]
+ "unsafe" => !@options[:unsafe],
+ "hoist_vars" => @options[:lift_vars],
+ "global_defs" => @options[:define] || {}
}
end
- def defines
- Hash[(@options[:define] || {}).map do |k, v|
- token = if v.is_a? Numeric
- ['num', v]
- elsif [true, false].include?(v)
- ['name', v.to_s]
- elsif v == nil
- ['name', 'null']
- else
- ['string', v.to_s]
- end
- [k, token]
- end]
- end
-
def gen_code_options
options = {
:ascii_only => @options[:ascii_only],
:inline_script => @options[:inline_script],
- :quote_keys => @options[:quote_keys]
+ :quote_keys => @options[:quote_keys],
+ :max_line_len => @options[:max_line_length]
}
if @options[:beautify]
@@ -150,6 +189,18 @@ def gen_code_options
end
end
+ def source_map_options
+ {
+ :file => @options[:output_filename],
+ :root => @options[:source_root],
+ :orig => @options[:input_source_map]
+ }
+ end
+
+ def parse_options
+ {:filename => @options[:source_filename]}
+ end
+
# MultiJson API detection
if MultiJson.respond_to? :dump
def json_encode(obj)
View
6,005 lib/uglify.js
1,231 additions, 4,774 deletions not shown
View
86 spec/source_map_spec.rb
@@ -0,0 +1,86 @@
+# encoding: UTF-8
+require 'stringio'
+require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
+
+describe "Uglifier" do
+ it "generates source maps" do
+ source = File.open("lib/uglify.js", "r:UTF-8").read
+ minified, map = Uglifier.new.compile_with_map(source)
+ minified.length.should < source.length
+ map.length.should > 0
+ lambda {
+ JSON.parse(map)
+ }.should_not raise_error
+ end
+
+ it "generates source maps with the correct meta-data" do
+ source = <<-JS
+ function hello () {
+ function world () {
+ return 2;
+ };
+
+ return world() + world();
+ };
+ JS
+
+ minified, map = Uglifier.compile_with_map(source,
+ :source_filename => "ahoy.js",
+ :output_filename => "ahoy.min.js",
+ :source_root => "http://localhost/")
+
+ map = SourceMap.from_s(map)
+ map.file.should == "ahoy.min.js"
+ map.sources.should == ["ahoy.js"]
+ map.names.should == ["hello", "world"]
+ map.source_root.should == "http://localhost/"
+ map.mappings.first[:generated_line].should == 1
+ end
+
+ it "should skip copyright lines in source maps" do
+ source = <<-JS
+ /* @copyright Conrad Irwin */
+ function hello () {
+ function world () {
+ return 2;
+ };
+
+ return world() + world();
+ };
+ JS
+
+ minified, map = Uglifier.compile_with_map(source,
+ :source_filename => "ahoy.js",
+ :source_root => "http://localhost/")
+ map = SourceMap.from_s(map)
+ map.mappings.first[:generated_line].should == 2
+ end
+
+ it "should be able to handle an input source map" do
+ source = <<-JS
+ function hello () {
+ function world () {
+ return 2;
+ };
+
+ return world() + world();
+ };
+ JS
+
+ minified1, map1 = Uglifier.compile_with_map(source,
+ :source_filename => "ahoy.js",
+ :source_root => "http://localhost/",
+ :mangle => false)
+
+ minified2, map2 = Uglifier.compile_with_map(source,
+ :input_source_map => map1,
+ :mangle => true)
+
+ minified1.lines.to_a.length.should == 1
+
+ map = SourceMap.from_s(map2)
+ map.sources.should == ["http://localhost/ahoy.js"]
+ map.mappings.first[:source_line].should == 1
+ map.mappings.last[:source_line].should == 6
+ end
+end
View
1  spec/spec_helper.rb
@@ -1,6 +1,7 @@
# encoding: UTF-8
require 'uglifier'
require 'rspec'
+require 'source_map'
# Requires supporting files with custom matchers and macros, etc,
# in ./support/ and its subdirectories.
View
19 spec/uglifier_spec.rb
@@ -63,11 +63,6 @@
end
end
- it "does additional squeezing when unsafe options is true" do
- unsafe_input = "function a(b){b.toString();}"
- Uglifier.new(:unsafe => true).compile(unsafe_input).length.should < Uglifier.new(:unsafe => false).compile(unsafe_input).length
- end
-
it "mangles variables only if mangle is set to true" do
code = "function longFunctionName(){};"
Uglifier.new(:mangle => false).compile(code).length.should == code.length
@@ -78,26 +73,14 @@
Uglifier.compile(code, :squeeze => false).length.should > Uglifier.compile(code, :squeeze => true).length
end
- it "should allow top level variables to be mangled" do
- code = "var foo = 123"
- Uglifier.compile(code, :toplevel => true).should_not include("var foo")
- end
-
it "allows variables to be excluded from mangling" do
code = "var foo = {bar: 123};"
Uglifier.compile(code, :except => ["foo"], :toplevel => true).should include("var foo")
end
- it "allows disabling of function name mangling" do
- code = "function sample() {var bar = 1; function foo() { return bar; }}"
- mangled = Uglifier.compile(code, :mangle => :vars)
- mangled.should include("foo()")
- mangled.should_not include("bar")
- end
-
it "honors max line length" do
code = "var foo = 123;var bar = 123456"
- Uglifier.compile(code, :max_line_length => 8).split("\n").length.should == 2
+ Uglifier.compile(code, :max_line_length => 8, :squeeze => false).split("\n").length.should == 2
end
it "lifts vars to top of the scope" do
View
1  uglifier.gemspec
@@ -25,7 +25,6 @@ Gem::Specification.new do |s|
"README.md",
"Rakefile",
"VERSION",
- "build.js",
"lib/es5.js",
"lib/uglifier.rb",
"lib/uglify.js",
1  vendor/source-map
@@ -0,0 +1 @@
+Subproject commit 39422eff01ca2363979e52de14e31dc78abd3971
2  vendor/uglifyjs
@@ -1 +1 @@
-Subproject commit 09511bd5fe71afdcfa8d2a24bed1ab7a591fe91e
+Subproject commit cf409800be18c76efc91a65051bea5f4cb9c44e4
Please sign in to comment.
Something went wrong with that request. Please try again.