class Test
def initialize
end
def crash
print x
end
end
Test.new.crash
Clearly this snippet will crash at line 8. If you parse this with Opal, you will get this compiled code:
/* Generated by Opal 0.8.0.beta1 */
(function(Opal) {
Opal.dynamic_require_severity = "error";
var self = Opal.top, $scope = Opal, nil = Opal.nil, $breaker = Opal.breaker, $slice = Opal.slice, $klass = Opal.klass;
Opal.add_stubs(['$print', '$x', '$crash', '$new']);
(function($base, $super) {
function $Test(){};
var self = $Test = $klass($base, $super, 'Test', $Test);
var def = self.$$proto, $scope = self.$$scope;
def.$initialize = function() {
var self = this;
return nil;
};
return (def.$crash = function() {
var self = this;
return self.$print(self.$x());
}, nil) && 'crash';
})(self, null);
return $scope.get('Test').$new().$crash();
})(Opal);
And of course it will throw the same error.
However, is there a way to determine the Ruby line where this error comes from?
I can see this question: Is there a way to show the Ruby line numbers in javascript generated by Opal, but I don't understand the answer: it leads me to https://github.com/opal/opal/tree/0-6-stable/examples/rack and I'm not sure what am I supposed to be looking at or doing.
When I run my javascript, I have an index.html
file that loads opal.min.js
and opal-parser.min.js
, then finally I have my compiled Ruby-Javascript code in a <script>
tag.
Opal has source map support, to facilitate this kind of source level of debugging. I will not go into details about sourcemaps, but HTML5Rocks has a great article that covers the topic in depth.
Here is the minimal boilerplate to set that up with Opal:
Let index.rb
be our source file:
class Test
def initialize
end
def crash
print x
end
end
Test.new.crash
Since you would rather not use a lot of extraneous utilties, let us directly use the Opal API. Create a file builder.rb
which will compile the file above:
require 'opal'
Opal::Processor.source_map_enabled = true
Opal.append_path "."
builder = Opal::Builder.new.build('index')
# Write the output file containing a referece to sourcemap
# which we generate below : this will help the browser locate the
# sourcemap. Note that we are generating sourcemap for only code and not
# the entire Opal corelib.
#
File.binwrite "build.js", "#{builder.to_s}\n//# sourceMappingURL=build.js.map"
File.binwrite "build.js.map", builder.source_map.to_s
File.binwrite "opal_lib.js", Opal::Builder.build('opal_lib')
Also create an opal_lib.rb
file containing only:
require 'opal'
Finally create an index.html
which will allow us to run the script in browser.
<!DOCTYPE html>
<html>
<head>
<script src="opal_lib.js"></script>
<script src="build.js"></script>
</head>
<body>
</body>
</html>
Now to actually compile your file, run:
ruby builder.rb
This will generate compiled javascript files opal_lib.js
and build.js
which are referenced by our index.html
file. Now just open index.html
in your browser. You will get a full call-stack and source view:
The line numbers of the source file are available:
As an alternative to using the browser, you can also use Node.js for the same purpose. This requires you have Node.js
and npm
installed. You will also need to install npm module source-map-support
npm install source-map-support
Now you can open the node repl and enter the following:
require('source-map-support').install();
require('./opal_lib');
require('./build');
You will get a stack trace with correct source line numbers :
/home/gaurav/Workspace/opal-playground/opal_lib.js:4436
Error.captureStackTrace(err);
^
NoMethodError: undefined method `x' for #<Test:0x102>
at OpalClass.$new (/home/gaurav/Workspace/opal-playground/opal_lib.js:4436:15)
at OpalClass.$exception (/home/gaurav/Workspace/opal-playground/opal_lib.js:4454:31)
at $Test.$raise (/home/gaurav/Workspace/opal-playground/opal_lib.js:4204:31)
at $Test.Opal.defn.TMP_1 (/home/gaurav/Workspace/opal-playground/opal_lib.js:3032:19)
at $Test.method_missing_stub [as $x] (/home/gaurav/Workspace/opal-playground/opal_lib.js:886:35)
at $Test.$crash (/home/gaurav/Workspace/opal-playground/index.rb:8:11)
at /home/gaurav/Workspace/opal-playground/index.rb:13:10
at Object.<anonymous> (/home/gaurav/Workspace/opal-playground/index.rb:13:10)
at Module._compile (module.js:435:26)
at Object.Module._extensions..js (module.js:442:10)
I recommend that you use bundler for gem management. Here is the Gemfile
for fetching Opal master:
source 'http://production.cf.rubygems.org'
gem 'opal', github: 'opal/opal'
To compile you will have to run:
bundle install
bundle exec ruby builder.rb
Sprockets integration / rack integration that others have mentioned use the same API underneath, abstracting away the plumbing.
Update:
Since we have the correct line numbers in stack, it is fairly to programatically parse the stack and extract this line number into a variable:
require('./opal_lib');
require('source-map-support').install();
var $e = null;
try {
require('./build');
} catch (e) {
$e = e;
}
var lines = e.split('\n').map(function(line){ return line.match(/^.*\((\S+):(\d+):(\d+)\)/) })
var first_source_line;
for (var i = 0; i < lines.length ; i++) {
var match = lines[i];
if (match == null) continue;
if (match[1].match(/index.rb$/) {
first_source_line = match;
break;
}
}
var line_number;
if (first_source_line) line_number = first_source_line[2] // ==> 8
And of course you can do it ruby as well (but if you are running this in browser you will have to include source-map-support here as well):
class Test
def initialize
end
def crash
print x
end
end
line_num = nil
begin
Test.new.crash
rescue => e
if line = e.backtrace.map{|line| line.match(/^.*\((\S+):(\d+):(\d+)\)/) }.compact.find{|match| match[1] =~ /index.rb$/ }
line_num = line[2]
end
end
puts "line_num => #{line_num}" # ==> 8
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With