12

I have a Rails application that needs to run a node script. I imagine that using the ExecJS gem is the cleanest way to run JavaScript from a Rails app. However, so far, ExecJS has proved to be very frustrating to use.

Here is the script I need to run:

// Generated by CoffeeScript 1.7.1
(function() {
  var PDFDocument, doc, fs;
  fs = require("fs");
  PDFDocument = require('pdfkit');
  doc = new PDFDocument;
  doc.pipe(fs.createWriteStream('output.pdf'));
  doc.addPage().fontSize(25).text('Here is some vector graphics...', 100, 100);
  doc.save().moveTo(100, 150).lineTo(100, 250).lineTo(200, 250).fill("#FF3300");
  doc.scale(0.6).translate(470, -380).path('M 250,75 L 323,301 131,161 369,161 177,301 z').fill('red', 'even-odd').restore();
  doc.addPage().fillColor("blue").text('Here is a link!', 100, 100).underline(100, 100, 160, 27, {
    color: "#0000FF"
  }).link(100, 100, 160, 27, 'http://google.com/');
  doc.end();
}).call(this)

From my Rails console, I try this:

[2] pry(main)> file = File.open('test.js').read
[3] pry(main)> ExecJS.eval(file)
ExecJS::ProgramError: TypeError: undefined is not a function
from /Users/matt/.rvm/gems/ruby-2.1.0/gems/execjs-2.0.2/lib/execjs/external_runtime.rb:68:in `extract_result'

Note that I can run this script successfully using 'node test.js' and I am also able to run run the script using the backtick syntax Ruby offers:

`node test.js`

But that feels like a hack...

3
  • 2
    Sounds like a great use case for microservices. Have a Node.js server attending requests made from the Rails server! Commented Dec 14, 2015 at 16:07
  • This is unrelated to the question, but relevant to the code. If you're trying to create a pdf from a rails app, why not use ruby libraries like 'prawn'? Commented Dec 15, 2015 at 16:13
  • @vemv prawn is terrible when it comes to render runtime charts in PDF. Here is some analysis. aarvy.me/blog/2019/09/11/… Commented Dec 28, 2019 at 4:29

4 Answers 4

2

It's erroring out because require() is not supported by EvalJS. 'require' is undefined, and undefined is not a function. ;)

Sign up to request clarification or add additional context in comments.

1 Comment

This doesn't work. The only real solution is to remove ExecJS.
1

ExecJS people say use commonjs.rb https://github.com/cowboyd/commonjs.rb

Why can't I use CommonJS require() inside ExecJS?

ExecJS provides a lowest common denominator interface to any JavaScript runtime. Use ExecJS when it doesn't matter which JavaScript interpreter your code runs in. If you want to access the Node API, you should check another library like commonjs.rb designed to provide a consistent interface.

But this doesn't work basically. The require acts completely erratically - I had to execute npm -g install pdfkit fs between env = and env.require in

require 'v8'
require 'commonjs'
env = CommonJS::Environment.new(V8::Context.new, path: ::Rails.root )
env.require 'script'

for the module lookup to work O.o and if I tried pointing path to the node_modules folder then it would be impossible for the gem to find script (not to mention that the #new and require are basically the only documented methods - only methods afaik - and #new is misdocumented :P)

Your options as far as I can tell:

  1. system(node ...) - you can use Cocaine to escape some gotcha's (piping output, error handling, performance tweaks, ...) and run a cleaner syntax - this is not as bad as it looks - this is how paperclip does image postprocessing (imagemagick system package + cocaine) so I guess it's very stable and very doable
  2. expose to web api and run a separate worker on a free heroku dyno for example to do this and similar stuff you want to do with node libs
  3. use prawn :)

1 Comment

This doesn't work. The only real solution is to remove ExecJS.
0

I'm not sure of the answer but maybe you need to precise the exec_js_runtime environment variable to be node.

Something like ENV['EXECJS_RUNTIME'] = 'Node' You can try to put it in the config/boot.rb or just to define the EXECJS_RUNTIME in your environment, something like export EXECJS_RUNTIME=Node

Hope it helps

6 Comments

I don't think the is the problem. I definitely have a working node installation and I am able to run 'ExecJS.eval("1 + 1")' without any problems. Thanks anyway.
Yeah but are you sure that exec js is using node and not another runtime ?
What is the result of ExecJS.runtime ?
[18] pry(main)> ExecJS.runtime => #<ExecJS::ExternalRuntime:0x0000010644fe60 @binary="node", @command=["nodejs", "node"], @deprecated=false, @encoding="UTF-8", @name="Node.js (V8)", @runner_path= "/Users/matt/.rvm/gems/ruby-2.1.0/gems/execjs-2.0.2/lib/execjs/support/node_runner.js",
The example doesn't work according to ExecJS people github.com/sstephenson/execjs#faq they say you should use commonjs gem, but it feels unfinished github.com/cowboyd/commonjs.rb - the OP's example script executes without error but there's no output
|
0

Get rid of ExecJS and anything that depends on ExecJS. I tried all the other suggestions, but this actually fixed it.

ES6 has been around since 2015. Any tool worth using supports it by now. MICROSOFT EDGE supports it by now. Seriously.

PLEASE NOTE: No other answer on here actually solves the problem. This might not be the ideal answer, but it's the only one that allows you to move past the error and go about your day. This is the worst answer, except for every other answer.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.