A couple of years ago I was learning about Deno. Deno is a JavaScript runtime created by Node’s creator, Ryan Dahl. I remember being fascinated by Deno’s remote import, which essentially allows you to import a module that’s stored somewhere on the internet. The most basic example goes as follows:
/**
* remote.ts
*/
import {
add,
multiply,
} from "https://x.nest.land/[email protected]/source/index.js";
function totalCost(outbound: number, inbound: number, tax: number): number {
return multiply(add(outbound, inbound), tax);
}
console.log(totalCost(19, 31, 1.2));
console.log(totalCost(45, 27, 1.15));
/**
* Output
*
* 60
* 82.8
*/
As you can see, one can simply import a file that’s stored somewhere on the internet and use it freely.
But, could something like this exist in Ruby?
Proof of concept
In Ruby, a similar feature would look like this:
require "https://gist.githubusercontent.com/patriciomacadden/68523656b306e39a4c2fd54f823f320e/raw/f6470e4a0b0352d1051e510b5613315dfdb109ff/remote_require_test.rb"
Person.new.walk
Here, the intent is to require a ruby file that’s stored in a gist. Then, we’re instantiating a Person
object that’s defined in that file and calling its #walk
method.
How would this work?
First, let’s not touch require
just yet. Let’s create a remote_require
method instead. It’d be clearer for now.
An initial implementation would be:
require "net/http"
require "tempfile"
def remote_require(url)
content = Net::HTTP.get URI(url)
file = Tempfile.new [File.basename(url, ".*"), File.extname(url)]
file.write content
file.close
require file.path
end
This, a basic implementation of the remote_require
method, takes a URL as its only argument and it doesn’t consider that URL to be wrong or not containing a Ruby file. Then, it reads the content of the given URL and stores it in the file system. After storing the file, it just require
s it. You can test this implementation by just pasting the code above in an IRB session plus this:
remote_require "https://gist.githubusercontent.com/patriciomacadden/68523656b306e39a4c2fd54f823f320e/raw/f6470e4a0b0352d1051e510b5613315dfdb109ff/remote_require_test.rb"
Person.new.walk
Refining the PoC
Back to the original snippet of the proof of concept, to be able to do this with require
, we’d need to monkey patch Kernel#require
. Doing this is not that difficult:
require "net/http"
require "tempfile"
module Kernel
alias original_require require
def require(path)
if URI.parse(path).scheme&.start_with? "http"
remote_require path
else
original_require path
end
end
private
def remote_require(url)
content = Net::HTTP.get URI(url)
file = Tempfile.new [File.basename(url, ".*"), File.extname(url)]
file.write content
file.close
original_require file.path
end
end
In this case, we’re aliasing the require
method to original_require
and then creating a new require
method which will decide if the given string is a URL or not. If it’s an URL it will call the remote_require
method and if it isn’t it will call the original_require
method. Business as usual.
Is it enough?
Of course, it’s not. Requiring files is much harder than this.
If we think about this for a moment, require
deals not only with files but also with gems. So if we’d want to require a gem, say for example, Sinatra, we’d need to download it, unpack it, etc. Everything the gem
command does for us. Or, if we get greedy, everything Bundler does for us. To download any gem, not only do we need to download, unpack, etc. but also download, unpack, etc. all its dependencies. And the same for all the dependency graph. As you can imagine, a very difficult task already solved by Bundler.
Moreover, to make this usable out of the box (ie not pasting this snippet when we intend to use it), we need to change the Kernel#require
implementation.
Is it worth it?
Enabling the widespread availability of the remote require within the language would mean a huge effort. Not to mention that it’s not something demanded by the community or even something I imagine using myself.
But for sure it was a fun experiment!