0

Hello railites and jsheads,

I'm a ruby guy and pretty new to the node thing, but I'm learning and so far I really like it. I've been developing a rails app on heroku for a week or so, and (of course) the requirements have evolved.

I need to be able to use pdfkit, a node library for creating pdfs, from my rails app.

Ultimately, I am after the simplest way to accomplish just that. This is what I've tried so far:

From the heroku docs here, I know that heroku rails apps come with a node js runtime, but it seems that npm is not included, and I could not find an obvious way to require pdfkit.

So I followed the the lead on this blog, using ddollar's multi buildpack:

heroku config:add BUILDPACK_URL=https://github.com/ddollar/heroku-buildpack-multi.git

Here are snippets form the relevant files (let me know if I am missing something ;) )

.buildpack

https://github.com/heroku/heroku-buildpack-nodejs
https://github.com/heroku/heroku-buildpack-ruby

package.json

{
  "name": "MoneyMaker",
  "version": "0.0.1",
  "dependencies": {
    "pdfkit": "0.5.2"
  },
  "repository": {
    "type" : "git",
    "url" : "https://github.com/mattwalters/example.git"
  }
}

Gemfile:

...
gem 'execjs'
...

test.js

// 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)

Note I did check node_modules into source control. I pushed all of this to heroku, and tried to manually call test.js with execjs:

matt$ heroku run rails c
heroku-irb> ExecJS.eval(File.open('test.js').read)

I get the folloring error:

ExecJS::ProgramError: TypeError: undefined is not a function
    from /app/vendor/bundle/ruby/2.1.0/gems/execjs-2.0.2/lib/execjs/external_runtime.rb:68:in `extract_result'
    from /app/vendor/bundle/ruby/2.1.0/gems/execjs-2.0.2/lib/execjs/external_runtime.rb:28:in `block in exec'
    from /app/vendor/bundle/ruby/2.1.0/gems/execjs-2.0.2/lib/execjs/external_runtime.rb:41:in `compile_to_tempfile'
    from /app/vendor/bundle/ruby/2.1.0/gems/execjs-2.0.2/lib/execjs/external_runtime.rb:27:in `exec'
    from /app/vendor/bundle/ruby/2.1.0/gems/execjs-2.0.2/lib/execjs/external_runtime.rb:19:in `eval'
    from /app/vendor/bundle/ruby/2.1.0/gems/execjs-2.0.2/lib/execjs/runtime.rb:40:in `eval'
    from /app/vendor/bundle/ruby/2.1.0/gems/execjs-2.0.2/lib/execjs/module.rb:23:in `eval'
    from (irb):3
    from /app/vendor/bundle/ruby/2.1.0/gems/railties-4.0.2/lib/rails/commands/console.rb:90:in `start'
    from /app/vendor/bundle/ruby/2.1.0/gems/railties-4.0.2/lib/rails/commands/console.rb:9:in `start'
    from /app/vendor/bundle/ruby/2.1.0/gems/railties-4.0.2/lib/rails/commands.rb:62:in `<top (required)>'
    from /app/bin/rails:4:in `require'
    from /app/bin/rails:4:in `<main>'

I tried to simplify and just run:

heroku - irb> ExecJS.eval("require('pdfkit')")

But I get the same error. I think I am missing something pretty major when it comes to execjs. Can anyone enlighten me?

Then I wanted to see if I could call test.js directly from node. Note this works in my local dev environment.

matt$ heroku run bash
heroku$ node test.js

But I get:

node.js:134
        throw e; // process.nextTick error, or 'error' event on first tick
        ^
Error: Cannot find module 'zlib'
    at Function._resolveFilename (module.js:320:11)
    at Function._load (module.js:266:25)
    at require (module.js:348:19)
    at Object.<anonymous> (/app/node_modules/pdfkit/js/reference.js:12:10)
    at Object.<anonymous> (/app/node_modules/pdfkit/js/reference.js:101:4)
    at Module._compile (module.js:404:26)
    at Object..js (module.js:410:10)
    at Module.load (module.js:336:31)
    at Function._load (module.js:297:12)
    at require (module.js:348:19)

So now I'm throwing my hands up and hoping the wisdom of the community can help me. Please let me know if you'd like additional information.

Thanks very much, Matt

Cymen
  • 14,079
  • 4
  • 52
  • 72
matt walters
  • 591
  • 6
  • 14

1 Answers1

1

So the problem was that the default buildpack installs a version of node automatically if your application depends on execjs. Unfortunately this is a very old version of node -- 0.4.7 -- that is incompatible with the node libraries I need to use.

My solution was to fork heroku's buildpack and patch the add_node_js_binary method to never install node binaries. This works as long as your multi buildpack configuration includes a node distro. Check it out here:

https://github.com/mattwalters/heroku-buildpack-ruby/blob/master/lib/language_pack/ruby.rb

matt walters
  • 591
  • 6
  • 14
  • Awesome, I'm having this exact problem. As you say, /app/bin is in the environmental PATH variable first and it has node 0.4.7 while another directory later in the PATH has the version of node specified in package.json. Super annoying -- going to go fork your repo and be happy. Thanks! – Cymen Apr 04 '14 at 19:03
  • BTW, I am still having trouble making ExecJS behave.... See my SO question here: http://stackoverflow.com/q/22852086/1396827 Any idea there? – matt walters Apr 04 '14 at 20:18
  • I'm not sure how to fix that -- I'm actually trying to get a gem to work (browserify-rails) that uses a node module on Heroku during asset compilation. – Cymen Apr 04 '14 at 21:16