Compare commits

...

7 Commits

Author SHA1 Message Date
Hauke D 16bc09d89a Added patch for Emscripten 1.39.16 (closes #16)
Thanks @reneeb!
6 years ago
Hauke D d1206e84fb Added more info to an error message
Thanks to Bernhard M. Wiedemann for pointing this out!
6 years ago
Hauke D 00e738560f Updated for Perl 5.30.0 (WebPerl v0.11-beta) 6 years ago
Hauke D 993d24d38f Added gui_sweet example 7 years ago
Hauke D 1c268976bb Added gui_basic example 7 years ago
Hauke D 5306c3b819 Added experiments/use_http.html 7 years ago
Hauke D ac50d81bc2 Tweaked build script for older Emscripten versions
After the update for 1.38.28, the build script didn't recognize 1.38.1*
if the patch had already been applied.
7 years ago

@ -3,6 +3,12 @@ WebPerl Changelog
=================
2019-08-03: v0.11-beta
----------------------
- Updated for Emscripten 1.38.31 / latest Fastcomp (1.38.40) and Perl v5.30.0
2019-03-03: v0.09-beta
----------------------

@ -95,9 +95,11 @@ my $needs_reconfig = !!$opts{reconfig};
# first, we need to take a guess which version of the patch to apply.
my $libraryjs = file($ENV{EMSCRIPTEN}, 'src', 'library.js')->slurp;
my $patchf;
if ( $libraryjs=~/\b\QERRNO_CODES.EAGAIN\E\b/ )
if ( $libraryjs=~/\b\Q___setErrNo(ERRNO_CODES.\E(EAGAIN|ENOTSUP)\b/ )
{ $patchf = 'emscripten_1.38.10_eagain.patch' }
elsif ( $libraryjs=~/\b\QcDefine('EAGAIN')\E/ )
elsif ( $libraryjs=~/no shell available\s+setErrNo\Q({{{ cDefine('EAGAIN') }}})\E/ )
{ $patchf = 'emscripten_1.39.16_eagain.patch' }
elsif ( $libraryjs=~/\b\QcDefine('EAGAIN')\E/ ) # note that this appears in 1.38.1* versions too
{ $patchf = 'emscripten_1.38.28_eagain.patch' }
else { die "Could not figure out which library.js patch to use" }
#TODO Later: we should probably verify the Emscripten version too, and in the future we may need different patches for different versions
@ -161,6 +163,7 @@ GITSTUFF: {
unless $tags=~/^\Q$C{PERLVER}\E$/m;
my $branches = git 'branch', '--list', {show_cmd=>$VERBOSE};
die "could not find branch '$C{PERL_BRANCH}', is this the right repository?"
. " (or the WebPerl author forgot to push tags to the emperl5 repo)"
unless $branches=~/^\*?\s*\b\Q$C{PERL_BRANCH}\E$/m;
say STDERR "# Found tag '$C{PERLVER}' and branch '$C{PERL_BRANCH}' in $C{PERLSRCDIR}";
}

@ -27,7 +27,7 @@ export EMPERL_OUTPUTDIR="$BASEDIR/work/outputperl"
# Don't edit the following options unless you know what you're doing!
# Note to self: In build.pl, we take advantage of the fact that on Perls >=v5.10.0, "$^V" is the same as the tag name.
export EMPERL_PERLVER="v5.28.1"
export EMPERL_PERLVER="v5.30.0"
export EMPERL_PREFIX="/opt/perl"
# Note: strace shows this is how file_packager.py is called: ["/usr/bin/python", "/home/haukex/emsdk/emscripten/1.38.28/tools/file_packager.py", "emperl.data", "--from-emcc", "--export-name=Module", "--preload", "/home/haukex/code/webperl/work/outputperl/opt/perl@/opt/perl", "--no-heap-copy"]
export EMPERL_PRELOAD_FILE="$EMPERL_OUTPUTDIR$EMPERL_PREFIX@$EMPERL_PREFIX"

@ -0,0 +1,19 @@
--- library.js.orig 2020-05-18 17:14:18.682328912 +0200
+++ library.js 2020-05-18 17:14:48.366639562 +0200
@@ -271,7 +271,7 @@
// pid_t fork(void);
// http://pubs.opengroup.org/onlinepubs/000095399/functions/fork.html
// We don't support multiple processes.
- setErrNo({{{ cDefine('EAGAIN') }}});
+ setErrNo({{{ cDefine('ENOTSUP') }}});
return -1;
},
vfork: 'fork',
@@ -696,7 +696,7 @@
// http://pubs.opengroup.org/onlinepubs/000095399/functions/system.html
// Can't call external programs.
if (!command) return 0; // no shell available
- setErrNo({{{ cDefine('EAGAIN') }}});
+ setErrNo({{{ cDefine('ENOTSUP') }}});
return -1;
},

@ -0,0 +1,5 @@
/database.db
/web/webperl.js
/web/emperl.*
/gui_basic
/gui_basic.exe

@ -0,0 +1,50 @@
WebPerl Basic GUI Example
=========================
This is a demo of a very basic GUI using WebPerl. It consists of a
local web server, which includes code to access an SQLite database,
and this web server also serves up WebPerl code to a browser, where
the GUI is implemented as HTML with Perl.
To get this to work, you will need to copy the `webperl.js` and the
three `emperl.*` files from the main `web` directory to the `web`
subdirectory in this project.
Note that this should not be considered production-ready, as there
are several key features missing, such as HTTPS or access control.
Also, a limitation is that the server does not know when the browser
window is closed, so it must be stopped manually.
You can pack this application into a single executable using:
DOING_PAR_PACKER=1 pp -o gui_basic -z 9 -x -a gui_basic_app.psgi -a web gui_basic.pl
Note: I'm not yet sure why, but sometimes this fails with errors such
as *"error extracting info from -c/-x file"*, in that case just try
the above command again.
Author, Copyright, and License
==============================
**WebPerl - <http://webperl.zero-g.net>**
Copyright (c) 2019 Hauke Daempfling (haukex@zero-g.net)
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
Berlin, Germany, <http://www.igb-berlin.de>
This program is free software; you can redistribute it and/or modify
it under the same terms as Perl 5 itself: either the GNU General Public
License as published by the Free Software Foundation (either version 1,
or, at your option, any later version), or the "Artistic License" which
comes with Perl 5.
This program is distributed in the hope that it will be useful, but
**WITHOUT ANY WARRANTY**; without even the implied warranty of
**MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE**.
See the licenses for details.
You should have received a copy of the licenses along with this program.
If not, see <http://perldoc.perl.org/index-licence.html>.

@ -0,0 +1,50 @@
#!/usr/bin/env perl
use warnings;
use 5.018;
use FindBin;
use File::Spec::Functions qw/catdir/;
use Plack::Runner ();
use Starman ();
use Browser::Open qw/open_browser/;
# This just serves up gui_basic_app.psgi in the Starman web server.
# You can also say "plackup gui_basic_app.psgi" instead.
BEGIN {
my $dir = $ENV{PAR_TEMP} ? catdir($ENV{PAR_TEMP},'inc') : $FindBin::Bin;
chdir $dir or die "chdir $dir: $!";
}
my $SERV_PORT = 5000;
my $THE_APP = 'gui_basic_app.psgi';
# AFAICT, both Plack::Runner->new(@args) and ->parse_options(@argv) set
# options, and these options are shared between "Starman::Server"
# (documented in "starman") and "Plack::Runner" (documented in "plackup").
my @args = (
server => 'Starman', loader => 'Delayed', env => 'development',
version_cb => sub { print "Starman $Starman::VERSION\n" } );
my @argv = ( '--listen', "localhost:$SERV_PORT", $THE_APP );
my $runner = Plack::Runner->new(@args);
$runner->parse_options(@argv);
$runner->set_options(argv => \@argv);
die "loader shouldn't be Restarter" if $runner->{loader} eq 'Restarter';
if ($ENV{DOING_PAR_PACKER}) {
require Plack::Util;
Plack::Util::load_psgi($THE_APP); # for dependency resolution
# arrange to have the server shut down in a few moments
my $procpid = $$;
my $pid = fork();
if (!defined $pid) { die "fork failed" }
elsif ($pid==0) { sleep 5; kill 'INT', $procpid; exit; } # child
print "====> Please wait a few seconds...\n";
}
else {
# There's a small chance here that the browser could open before the server
# starts up. In that case, a reload of the browser window is needed.
print "Attempting to open in browser: http://localhost:$SERV_PORT/\n";
open_browser("http://localhost:$SERV_PORT/");
}
$runner->run;

@ -0,0 +1,67 @@
#!/usr/bin/env perl
use warnings;
use 5.018;
use Plack::MIME;
use Plack::Builder qw/builder enable mount/;
use Plack::Request ();
use Plack::Response (); # declare compile-time dependency
use Cpanel::JSON::XS qw/decode_json encode_json/;
use DBI ();
use DBD::SQLite (); # declare compile-time dependency
use HTML::Tiny ();
# This is the server-side code.
# note we rely on gui_basic.pl to set the working directory correctly
my $SERV_ROOT = 'web';
my $DB_FILE = 'database.db';
my $dbh = DBI->connect("DBI:SQLite:dbname=$DB_FILE",
undef, undef, { RaiseError=>1, AutoCommit=>1 });
$dbh->do(q{ CREATE TABLE IF NOT EXISTS FooBar (
foo VARCHAR(255), bar VARCHAR(255) ) });
# This sends HTML to the browser, but we could also send JSON
# and build the HTML table dynamically in the browser.
my $app_select = sub {
state $html = HTML::Tiny->new;
state $sth_select = $dbh->prepare(q{ SELECT rowid,foo,bar FROM FooBar });
$sth_select->execute;
my $data = $sth_select->fetchall_arrayref;
my $out = $html->table(
[ \'tr',
[ \'th', 'rowid', 'foo', 'bar' ],
map { [ \'td', @$_ ] } @$data
] );
return [ 200, [ "Content-Type"=>"text/html" ], [ $out ] ];
};
# This is an example of one way to communicate with JSON.
my $app_insert = sub {
my $req = Plack::Request->new(shift);
state $sth_insert = $dbh->prepare(q{ INSERT INTO FooBar (foo,bar) VALUES (?,?) });
my $rv = eval { # catch errors and return as 500 Server Error
my $content = decode_json( $req->content );
$sth_insert->execute($content->{foo}, $content->{bar});
{ ok=>1 }; # return value from eval, sent to client as JSON
}; my $e = $@||'unknown error';
my $res = $req->new_response($rv ? 200 : 500);
$res->content_type($rv ? 'application/json' : 'text/plain');
$res->body($rv ? encode_json($rv) : 'Server Error: '.$e);
return $res->finalize;
};
Plack::MIME->add_type(".js" => "application/javascript");
Plack::MIME->add_type(".data" => "application/octet-stream");
Plack::MIME->add_type(".mem" => "application/octet-stream");
Plack::MIME->add_type(".wasm" => "application/wasm");
builder {
enable 'SimpleLogger';
enable 'Static',
path => sub { s#\A/\z#/index.html#; /\.(?:html?|js|css|data|mem|wasm|pl)\z/i },
root => $SERV_ROOT;
mount '/select' => $app_select;
mount '/insert' => $app_insert;
}

@ -0,0 +1,32 @@
<!doctype html>
<html lang="en-us">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>WebPerl GUI Demo</title>
<script src="webperl.js"></script>
<script type="text/perl" src="web.pl"></script>
</head>
<body style="font-family:sans-serif;">
<h1>WebPerl GUI Demo</h1>
<div id="datatable"><i>No data loaded yet...</i></div>
<div><button id="reload_data">Reload Data</button></div>
<div style="margin-top:1em">
<div>
<label for="input_foo">foo</label>
<input type="text" id="input_foo">
</div>
<div>
<label for="input_bar">bar</label>
<input type="text" id="input_bar">
</div>
<div>
<button id="do_insert">Insert Data</button>
</div>
</div>
<p>Powered by <a href="http://webperl.zero-g.net" target="_blank">WebPerl</a> (beta)</p>
</body>
</html>

@ -0,0 +1,69 @@
#!perl
use warnings;
use 5.028;
use WebPerl qw/js js_new sub1 encode_json/;
# This is the code that WebPerl runs in the browser. It is loaded by index.html.
sub do_xhr {
my %args = @_;
die "must specify a url" unless $args{url};
$args{fail} ||= sub { js('window')->alert(shift) };
my $xhr = js_new('XMLHttpRequest');
$xhr->addEventListener("error", sub1 {
$args{fail}->("XHR Error on $args{url}: ".(shift->{textContent}||"unknown"));
return;
});
$xhr->addEventListener("load", sub1 {
if ($xhr->{status}==200) {
$args{done}->($xhr->{response}) if $args{done};
}
else {
$args{fail}->("XHR Error on $args{url}: ".$xhr->{status}." ".$xhr->{statusText});
}
return;
});
$xhr->addEventListener("loadend", sub1 {
$args{always}->() if $args{always};
return;
});
# when given data, default to POST (JSON), otherwise GET
if ($args{data}) {
$xhr->open($args{method}||'POST', $args{url});
$xhr->setRequestHeader('Content-Type', 'application/json');
$xhr->send(encode_json($args{data}));
}
else {
$xhr->open($args{method}||'GET', $args{url});
$xhr->send();
}
return;
}
my $document = js('document');
my $btn_reload = $document->getElementById('reload_data');
sub do_reload {
state $dtbl = $document->getElementById('datatable');
$btn_reload->{disabled} = 1;
do_xhr(url => 'select',
done => sub { $dtbl->{innerHTML} = shift; },
always => sub { $btn_reload->{disabled} = 0; } );
return;
}
$btn_reload->addEventListener("click", \&do_reload);
my $btn_insert = $document->getElementById('do_insert');
sub do_insert {
state $txt_foo = $document->getElementById('input_foo');
state $txt_bar = $document->getElementById('input_bar');
$btn_insert->{disabled} = 1;
do_xhr(url => 'insert',
data => { foo=>$txt_foo->{value}, bar=>$txt_bar->{value} },
always => sub { $btn_insert->{disabled} = 0; do_reload; } );
return;
}
$btn_insert->addEventListener("click", \&do_insert);
do_reload; # initial load

@ -0,0 +1,4 @@
/public/webperl.js
/public/emperl.*
/gui_sweet
/gui_sweet.exe

@ -0,0 +1,44 @@
WebPerl Advanced GUI Example
============================
Similar to the "WebPerl Basic GUI Example", this is a demo of a GUI
using WebPerl, but using [Bootstrap](https://getbootstrap.com/)
and [jQuery](https://jquery.com/) instead of plain JavaScript,
and [Mojolicious](https://mojolicious.org/) instead of plain Plack.
To get this to work, you will need to copy the `webperl.js` and the
three `emperl.*` files from the main `web` directory to the `public`
subdirectory in this project.
Also, a limitation is that the server does not know when the browser
window is closed, so it must be stopped manually.
You can pack this application into a single executable using `do_pp.pl`.
Note: I'm not yet sure why, but sometimes this fails with errors such
as *"error extracting info from -c/-x file"*, in that case just try
the command again.
Author, Copyright, and License
==============================
**WebPerl - <http://webperl.zero-g.net>**
Copyright (c) 2019 Hauke Daempfling (haukex@zero-g.net)
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
Berlin, Germany, <http://www.igb-berlin.de>
This program is free software; you can redistribute it and/or modify
it under the same terms as Perl 5 itself: either the GNU General Public
License as published by the Free Software Foundation (either version 1,
or, at your option, any later version), or the "Artistic License" which
comes with Perl 5.
This program is distributed in the hope that it will be useful, but
**WITHOUT ANY WARRANTY**; without even the implied warranty of
**MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE**.
See the licenses for details.
You should have received a copy of the licenses along with this program.
If not, see <http://perldoc.perl.org/index-licence.html>.

@ -0,0 +1,23 @@
#!/usr/bin/env perl
use warnings;
use strict;
use File::Basename qw/fileparse/;
use File::Spec::Functions qw/catfile/;
use File::Temp qw/tempfile/;
# this attempts to locate Mojo's default server.crt/server.key files
chomp( my $dir = `perldoc -l Mojo::IOLoop::Server` );
die "perldoc -l failed, \$?=$?" if $? || !-e $dir;
(undef, $dir) = fileparse($dir);
# set up a file for pp's -A switch
my ($tfh, $tfn) = tempfile(UNLINK=>1);
print {$tfh} catfile($dir,'resources','server.crt'),";server.crt\n";
print {$tfh} catfile($dir,'resources','server.key'),";server.key\n";
close $tfh;
my @args = (qw/ -a public -a templates -A /, $tfn);
local $ENV{DOING_PAR_PACKER}=1;
system(qw/ pp -o gui_sweet -z 9 -x /,@args,'gui_sweet.pl')==0
or die "pp failed, \$?=$?";

@ -0,0 +1,77 @@
#!/usr/bin/env perl
use Mojolicious::Lite;
use Mojo::Util qw/md5_sum/;
use FindBin;
use File::Spec::Functions qw/catdir/;
use Browser::Open qw/open_browser/;
# This is the server-side code.
my $SERV_PORT = 3000;
my ($SSLCERTS,$HOMEDIR);
BEGIN {
$HOMEDIR = $ENV{PAR_TEMP} ? catdir($ENV{PAR_TEMP},'inc') : $FindBin::Bin;
chdir $HOMEDIR or die "chdir $HOMEDIR: $!";
# do_pp.pl pulls the default Mojo SSL certs into the archive for us
$SSLCERTS = $ENV{PAR_TEMP} ? '?cert=./server.crt&key=./server.key' : '';
}
app->static->paths([catdir($HOMEDIR,'public')]);
app->renderer->paths([catdir($HOMEDIR,'templates')]);
app->secrets(['Hello, Perl World!']);
app->types->type(js => "application/javascript");
app->types->type(data => "application/octet-stream");
app->types->type(mem => "application/octet-stream");
app->types->type(wasm => "application/wasm");
# Authentication and browser-launching stuff (optional)
my $TOKEN = md5_sum(rand(1e15).time);
hook before_server_start => sub {
my ($server, $app) = @_;
my @urls = map {Mojo::URL->new($_)->query(token=>$TOKEN)} @{$server->listen};
my $url = shift @urls or die "No urls?";
if ($ENV{DOING_PAR_PACKER}) {
# arrange to have the server shut down in a few moments
my $procpid = $$;
my $pid = fork();
if (!defined $pid) { die "fork failed" }
elsif ($pid==0) { sleep 5; kill 'USR1', $procpid; exit; } # child
print "====> Please wait a few seconds...\n";
$SIG{USR1} = sub { $server->stop; exit };
}
else {
print "Attempting to open in browser: $url\n";
open_browser($url);
}
};
under sub {
my $c = shift;
return 1 if ($c->param('token')//'') eq $TOKEN;
$c->render(text => 'Bad token!', status => 403);
return undef;
};
get '/' => sub { shift->render } => 'index';
post '/example' => sub {
my $c = shift;
my $data = $c->req->json;
# can do anything here, this is just an example
$data->{string} = reverse $data->{string};
$c->render(json => $data);
};
app->start('daemon', '-l', "https://localhost:$SERV_PORT$SSLCERTS");
__DATA__
@@ index.html.ep
% layout 'main', title => 'WebPerl GUI Demo';
<main role="main" class="container">
<div>
<h1>WebPerl Advanced GUI Demo</h1>
<p class="lead">Hello, Perl World!</p>
<div id="buttons"></div>
</div>
</main>

@ -0,0 +1,44 @@
#!perl
use warnings;
use 5.028;
use WebPerl qw/js sub1 encode_json/;
# This is the code that WebPerl runs in the browser. It is loaded by index.html.
my $window = js('window');
my $document = js('document');
my $jq = js('jQuery');
sub do_ajax {
my %args = @_;
die "must specify a url" unless $args{url};
$args{fail} ||= sub { $window->alert(shift) };
$jq->ajax( $args{url}, {
$args{data} # when given data, default to POST (JSON), otherwise GET
? ( method=>$args{method}||'POST',
data=>encode_json($args{data}) )
: ( method=>$args{method}||'GET' ),
} )->done( sub1 {
$args{done}->(shift) if $args{done};
} )->fail( sub1 {
my ($jqXHR, $textStatus, $errorThrown) = @_;
$args{fail}->("AJAX Failed! ($errorThrown)");
} )->always( sub1 {
$args{always}->() if $args{always};
} );
return;
}
# slightly hacky way to get the access token, but it works fine
my ($token) = $window->{location}{search}=~/\btoken=([a-fA-F0-9]+)\b/;
my $btn = $jq->('<button>', { text=>"Click me!" } );
$btn->click(sub {
$btn->prop('disabled',1);
do_ajax( url=>"/example?token=$token",
data => { string=>"rekcaH lreP rehtonA tsuJ" },
done => sub { $window->alert("The server says: ".shift->{string}) },
always => sub { $btn->prop('disabled',0); } );
} );
$btn->appendTo( $jq->('#buttons') );

@ -0,0 +1,50 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<title><%= title %></title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<style>
body { padding-top: 5rem; }
</style>
</head>
<body>
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
<a class="navbar-brand" href="#"><%= title %></a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="dropdown01" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</a>
<div class="dropdown-menu" aria-labelledby="dropdown01">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<a class="dropdown-item" href="#">Something else here</a>
</div>
</li>
</ul>
</div>
</nav>
<%= content %>
<!-- Bootstrap wants its script tags at the end of the body element, so we'll put everything here: -->
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<script src="webperl.js"></script>
<script type="text/perl" src="web.pl"></script>
</body>
</html>

@ -0,0 +1,59 @@
<!doctype html>
<html lang="en-us">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>WebPerl Sync HTTP Demo</title>
<!--
This is a demo of dynamically loading modules via synchronous
XMLHttpRequests.
WARNING: Please note that https://xhr.spec.whatwg.org/ says:
"Synchronous XMLHttpRequest outside of workers is in the process of
being removed from the web platform as it has detrimental effects to
the end users experience. (This is a long process that takes many
years.)"
The method was first described by LanX at
https://www.perlmonks.org/?node_id=1225490
Thank you! :-)
-->
<script src="webperl.js"></script>
<script type="text/perl">
use warnings;
use 5.028;
use WebPerl qw/js js_new/;
BEGIN {
push @INC, sub {
my (undef,$file) = @_;
# sadly, MetaCPAN doesn't send CORS headers (yet)
#my $url = 'https://fastapi.metacpan.org/v1/source/'
# . ( $file =~ s/\//::/r =~ s/\.pm$//ir );
# this requires one to copy Dump.pm into web/Data/:
my $url = $file;
my $xhr = js_new('XMLHttpRequest');
$xhr->open('GET', $url, 0);
$xhr->send();
if ($xhr->{status}==200)
{ return \$xhr->{responseText} }
else { return }
};
}
use Data::Dump 'pp';
js('window')->alert(pp({Hello=>"World!"}));
</script>
</head>
<body>
<p>Output: see JS console</p>
</body>
</html>

@ -28,7 +28,7 @@ var Perl = {
trace: false, // user may enable this
endAfterMain: false, // user may enable this (before Perl.init)
noMountIdbfs: false, // user may enable this (before Perl.start)
WebPerlVersion: 'v0.09-beta', // user may read (only!) this
WebPerlVersion: 'v0.11-beta', // user may read (only!) this
state: "Uninitialized", // user may read (only!) this
exitStatus: undefined, // user may read (only!) this
Util: {},

Loading…
Cancel
Save