How to implement "JS require" function in Perl JE?
JE is a good JavaScript engine written in pure pearl. Executes generic JS code nicely, and its ability to bind perl routines to JS functions is remarkable.
What is missing is a feature require
that is implemented in "node.js" and performs a similar task as perl requires.
It would be nice to know how to implement such a function in perl and bind it to JE for example. in a simple skeleton:
use 5.016;
use warnings;
use Path::Tiny;
use JE::Destroyer;
use JE;
my $jslib = path("./jslib");
my $j = new JE;
$j->new_function("say", sub { say @_ }); # the "say" in the JS
$j->new_function("require", sub { # the "require"
my $source = $jslib->child($_[0])->slurp_utf8; #read the source from some file
#how to implement the require ?
#e.g. what JE object should be created and what to bind to it?
});
Any idea how the function is node.js
require
implemented in C and how to implement it in perl?
EDIT
For a more precise example, adding a more extended skeleton:
- file
./reqtest.js
(main program)
console.log("start");
p = require('adder');
console.log(p.add(2,3));
- file
./node_modules/adder.js
(in node_modules "- for testnode.js
)
exports.add = function() {
var sum = 0, i = 0, args = arguments, l = args.length;
while (i < l) {
sum += args[i++];
}
return sum;
};
do the above as
node reqtest.js
prints
start
5
so it correctly requested adder.js
and returned the object, but running it with the following perl / JE script:
use 5.016;
use warnings;
use JE::Destroyer;
use JE;
use Scalar::Util qw( weaken );
use Path::Tiny;
use PIR;
my $script = path("reqtest.js")->slurp_utf8;
my $j = new JE;
$j->{console} = {};
$j->{console}->new_function("log", sub { say @_ });
{
weaken(my $j = $j);
$j->new_function(require => sub {
my $source = get_source($_[0]);
$j->eval($source) if $source;
});
}
$j->eval($script);#eval the main script
JE::Destroyer::destroy($j);
undef $j;
#extremelly simplyfied
sub get_source {
my $name = shift;
return path("node_modules")->child("$name.js")->slurp_utf8;
}
prints only:
start
therefore "a simple implementation $j->eval($source)
require
does not follow the module specification CommonJS
- it needs something else.
source to share
After a bit of research, I created the following (extremely simplified) solution:
use 5.016;
use warnings;
use JE::Destroyer;
use JE;
use Scalar::Util qw( weaken );
use Path::Tiny;
my $script = path("reqtest.js")->slurp_utf8;
my $j = new JE;
$j->{console} = {};
$j->{console}->new_function("log", sub { say @_ });
{
weaken(my $j = $j); #based on ikegami previous answer
$j->new_function(require => sub {
my $source = get_source($_[0]);
my $code = new JE::Object::Function $j, qw(exports), $source;
my $exports = $j->{Object};
$code->($exports);
return $exports;
});
}
$j->eval($script);#eval the main script
JE::Destroyer::destroy($j);
undef $j;
# extremelly simplyfied code search
# the real code-search for the requide("some") should try the following
# some.js some/index.js some.json some/index.json
# for the defined search-paths.
sub get_source {
my $name = shift;
return path("node_modules")->child("$name.js")->slurp_utf8;
}
The above shortest possible CommonJS implementation requires and prints the same as the node.js
example in the question
start
5
Of course a lot of extra work is required (like caching code), but the minimal skeleton works.
source to share