@webdyne/webdyne-zeroperl-ts
v1.1.4
Published
Perl 5 interpreter powered by WebAssembly for JavaScript runtimes
Maintainers
Readme
ZeroPerl TypeScript bridge — WebDyne integration fork
For maintainers: architecture, verification, and release tagging and npm staging.
Runnable, maintained examples are in examples/, including data exchange, asynchronous callbacks, browser usage and a local Worker.
Working from source
This repository tracks the TypeScript bridge, tests, licenses and runtime
manifest. Generated dist/, zeroperl.wasm and third-party-notices.tar.gz
are local build inputs/outputs and are not tracked by Git. The WebDyne runtime
package built by aspeer/zeroperl includes its own compiled bridge and runtime;
it does not require publication of this standalone npm package.
To test or build the standalone package, install dependencies and import the
matching artifacts produced by aspeer/zeroperl:
npm ci
npm run runtime:prepare -- /path/to/runtime/artifacts
npm test
npm run build
npm run pack:checkThe directory must contain the versioned WASM and notices filenames recorded
in runtime-manifest.json. Alternatively, pass an HTTP(S) base URL containing
those files, or set ZEROPERL_ARTIFACT_BASE_URL. Both size and SHA-256 must
match the tracked manifest. With no source argument or environment variable,
runtime:prepare verifies existing local artifacts. It does not build Perl.
For GitHub Actions, configure the repository Actions variable
ZEROPERL_ARTIFACT_BASE_URL with a publicly readable directory or release-asset
base URL containing the matching artifacts. CI cannot run runtime tests until
those artifacts are hosted and the variable is configured.
The standalone npm package still includes WASM and attribution when built; Git exclusions do not remove them from the published package. Update the manifest only when intentionally adopting a newly qualified runtime build.
This is a fork of 6over3/zeroperl-ts, the JavaScript/TypeScript bridge for the original 6over3/zeroperl project. This fork supports the WebDyne WASM runtime, primarily on Cloudflare Workers today, with the potential to support other WASM providers later. The bridge also remains usable by applications embedding Perl directly.
WebDyne-specific guidance is in WEBDYNE.md. Current generic
installation and API instructions for @webdyne/webdyne-zeroperl-ts are in
USAGE.md. This package is available from npm and maintained on
the main branch. See LICENSE and NOTICE for licensing
and attribution.
Credits
- 6over3/zeroperl-ts and 6over3/zeroperl: the original projects and their contributors.
- lbe/zeroperl-ts: inherited custom-fetch handling and Perl-version helper work, including commit 88b3310. lbe/zeroperl also contributed to the runtime build lineage used by this bridge.
- lilohuang/zeroperl-ts and lilohuang/zeroperl: additional packaging, ExifTool and CI work consulted during consolidation. These are reference/inspiration credits rather than a claim that every patch was merged.
Original upstream README
The text below is from the original 6over3/zeroperl-ts repository's
README at 5e12fb8,
preserved with trailing whitespace removed. Its package names and examples describe that upstream
revision. Use USAGE.md for this fork's current package and API, and
WEBDYNE.md for the integration.
zeroperl-ts
Perl 5 compiled to WebAssembly. Run Perl scripts in the browser or other JavaScript environments without installing Perl.
Built on zeroperl.
Features
- Runs Perl 5 in browser, Node.js, Deno, and Bun
- Virtual filesystem for script and data files
- Bidirectional data exchange between JavaScript and Perl
- Register JavaScript functions callable from Perl
- Call Perl functions from JavaScript
- Environment variable support
- Output capture (stdout/stderr)
- TypeScript type definitions included
Installation
npm install @6over3/zeroperl-ts
# or
bun add @6over3/zeroperl-tsOr, for Browser Usage, copy the zeroperl WASM binary to your local website (to avoid CORS errors).
# The -L option allows the CDN to redirect to the latest version
curl -L -O https://esm.sh/@6over3/zeroperl-ts/zeroperl.wasmQuick Start
import { ZeroPerl } from '@6over3/zeroperl-ts';
const perl = await ZeroPerl.create();
await perl.eval('print "Hello, World!\\n"');
perl.flush(); // Required to see output
perl.dispose();Output Buffering
Perl buffers output by default. Choose one approach:
Option 1: Call flush() after printing
await perl.eval('print "Hello!\\n"');
perl.flush();Option 2: Enable autoflush in Perl
await perl.eval(`
$| = 1; # Enable autoflush
print "Hello!\\n";
`);Basic Usage
Evaluating Perl Code
import { ZeroPerl } from '@6over3/zeroperl-ts';
const perl = await ZeroPerl.create();
const result = await perl.eval(`
$| = 1;
my $x = 42;
print "The answer is $x\\n";
`);
if (!result.success) {
console.error('Error:', result.error);
}
perl.dispose();Capturing Output
let output = '';
const perl = await ZeroPerl.create({
stdout: (data) => {
output += typeof data === 'string' ? data : new TextDecoder().decode(data);
}
});
await perl.eval(`
$| = 1;
print "Line 1\\n";
print "Line 2\\n";
`);
console.log(output);
// Output:
// Line 1
// Line 2
perl.dispose();Exchanging Data
const perl = await ZeroPerl.create();
// JavaScript to Perl
perl.setVariable('name', 'Alice');
perl.setVariable('age', 30);
await perl.eval(`
$| = 1;
print "Name: $name\\n";
print "Age: $age\\n";
`);
// Perl to JavaScript
await perl.eval('$result = 2 + 2');
const result = perl.getVariable('result');
console.log(result.toInt()); // 4
result.dispose();
perl.dispose();Working with Arrays and Hashes
const perl = await ZeroPerl.create();
// Create array
const arr = perl.createArray([1, 2, 3, 'hello']);
perl.setVariable('myarray', arr.toValue());
// Create hash
const hash = perl.createHash({
name: 'Alice',
age: 30,
active: true
});
perl.setVariable('user', hash.toValue());
await perl.eval(`
$| = 1;
print "Array length: ", scalar(@$myarray), "\\n";
print "User: $user->{name}, Age: $user->{age}\\n";
`);
// Convert back to JavaScript
const jsArray = arr.project(); // [1, 2, 3, 'hello']
const jsObject = hash.project(); // { name: 'Alice', age: 30, active: true }
arr.dispose();
hash.dispose();
perl.dispose();Command-Line Arguments
const perl = await ZeroPerl.create();
await perl.eval(`
$| = 1;
print "Arguments: @ARGV\\n";
foreach my $arg (@ARGV) {
print " $arg\\n";
}
`, ['foo', 'bar', 'baz']);
perl.dispose();Working with Files
Creating a Virtual Filesystem
import { ZeroPerl, MemoryFileSystem } from '@6over3/zeroperl-ts';
const fs = new MemoryFileSystem({ "/": "" });
fs.addFile("/data.txt", "Hello from a file!");
fs.addFile("/script.pl", `
$| = 1;
open my $fh, '<', '/data.txt' or die $!;
while (my $line = <$fh>) {
print "Read: $line";
}
close $fh;
`);
const perl = await ZeroPerl.create({ fileSystem: fs });
await perl.runFile('/script.pl');
perl.dispose();Running Scripts with Arguments
const fs = new MemoryFileSystem({ "/": "" });
fs.addFile("/greet.pl", `
$| = 1;
my ($name, $greeting) = @ARGV;
print "$greeting, $name!\\n";
`);
const perl = await ZeroPerl.create({ fileSystem: fs });
await perl.runFile('/greet.pl', ['Alice', 'Hello']);
// Output: Hello, Alice!
perl.dispose();Reading and Writing Files
const fs = new MemoryFileSystem({ "/": "" });
const perl = await ZeroPerl.create({ fileSystem: fs });
// Write from Perl
await perl.eval(`
$| = 1;
open my $fh, '>', '/output.txt' or die $!;
print $fh "Generated content\\n";
close $fh;
print "File written!\\n";
`);
// Read from JavaScript
const content = fs.readFile('/output.txt');
console.log(content); // "Generated content\n"
perl.dispose();Advanced Usage
Registering JavaScript Functions
Register JavaScript functions that can be called from Perl:
const perl = await ZeroPerl.create();
perl.registerFunction('add', (a, b) => {
const x = a.toInt();
const y = b.toInt();
return perl.createInt(x + y);
});
await perl.eval(`
$| = 1;
my $sum = add(10, 32);
print "Sum: $sum\\n";
`);
perl.dispose();Registering JavaScript Methods
const perl = await ZeroPerl.create();
perl.registerMethod('Math', 'square', (x) => {
const num = x.toInt();
return perl.createInt(num * num);
});
await perl.eval(`
$| = 1;
my $result = Math::square(7);
print "Square: $result\\n";
`);
perl.dispose();Calling Perl Functions from JavaScript
const perl = await ZeroPerl.create();
await perl.eval(`
sub greet {
my ($name) = @_;
return "Hello, $name!";
}
sub get_values {
return (1, 2, 3);
}
`);
// Scalar context (single return value)
const arg = perl.createString("Alice");
const greeting = await perl.call("greet", [arg], "scalar");
console.log(greeting?.toString()); // "Hello, Alice!"
// List context (multiple return values)
const values = await perl.call("get_values", [], "list");
console.log(values.map(v => v.toInt())); // [1, 2, 3]
// Void context (no return value)
await perl.call("some_sub", [], "void");
arg.dispose();
greeting?.dispose();
for (const v of values) v.dispose();
perl.dispose();Error Handling
const perl = await ZeroPerl.create();
const result = await perl.eval(`
die "Something went wrong!";
`);
if (!result.success) {
console.log('Exit code:', result.exitCode);
console.log('Error:', result.error);
}
// Get error directly
const error = perl.getLastError();
console.log(error); // "Something went wrong! at ..."
// Clear error
perl.clearError();
perl.dispose();Environment Variables
const perl = await ZeroPerl.create({
env: {
API_KEY: 'secret123',
DEBUG: 'true'
}
});
await perl.eval(`
$| = 1;
print "API Key: $ENV{API_KEY}\\n";
print "Debug: $ENV{DEBUG}\\n";
`);
perl.dispose();Resetting State
const perl = await ZeroPerl.create();
await perl.eval('$counter = 1');
const val1 = perl.getVariable('counter');
console.log(val1?.toInt()); // 1
await perl.reset();
const val2 = perl.getVariable('counter');
console.log(val2); // null
val1?.dispose();
perl.dispose();Progressive Output
const perl = await ZeroPerl.create({
stdout: (data) => process.stdout.write(data)
});
for (let i = 0; i < 5; i++) {
await perl.eval('print "."');
perl.flush();
await new Promise(r => setTimeout(r, 500));
}
perl.dispose();Browser Usage
With bundler (recommended):
import { ZeroPerl } from '@6over3/zeroperl-ts';
import zeroperl from '@6over3/zeroperl-ts/zeroperl.wasm';
const perl = await ZeroPerl.create({
fetch: () => fetch(zeroperl),
stdout: (data) => console.log(data)
});
await perl.eval(`
$| = 1;
print "Hello from Perl!\\n";
print "Running in: $^O\\n";
`);
perl.dispose();Note: Most bundlers should copy the WASM file when imported explicitly. If your bundler doesn't handle this, configure it to copy static assets or use the CDN approach below.
From CDN:
<!DOCTYPE html>
<html>
<body>
<div id="output"></div>
<script type="module">
import { ZeroPerl } from 'https://esm.sh/@6over3/zeroperl-ts';
const output = document.getElementById('output');
const perl = await ZeroPerl.create({
stdout: (data) => {
const text = typeof data === 'string' ? data : new TextDecoder().decode(data);
output.innerHTML += text.replace(/\n/g, '<br>');
}
});
await perl.eval(`
$| = 1;
print "Hello from Perl!\\n";
print "Running in: $^O\\n";
`);
perl.dispose();
</script>
</body>
</html>API Reference
ZeroPerl.create(options?)
Create a new Perl interpreter instance.
Options:
env- Environment variables (Record<string, string>)fileSystem- Virtual filesystem (MemoryFileSystem)stdout- stdout callback ((data: string | Uint8Array) => void)stderr- stderr callback ((data: string | Uint8Array) => void)fetch- Custom fetch for WASM loading
const perl = await ZeroPerl.create({
env: { KEY: 'value' },
fileSystem: fs,
stdout: (data) => console.log(data),
stderr: (data) => console.error(data)
});perl.eval(code, args?)
Evaluate Perl code. Returns { success: boolean, error?: string, exitCode: number }.
const result = await perl.eval('print "Hello\\n"', ['arg1', 'arg2']);perl.runFile(path, args?)
Run a Perl script from the virtual filesystem.
await perl.runFile('/script.pl', ['arg1', 'arg2']);perl.createInt(value), perl.createDouble(value), perl.createString(value), perl.createBool(value), perl.createUndef()
Create Perl values. Returns PerlValue.
const num = perl.createInt(42);
const str = perl.createString("hello");
const bool = perl.createBool(true);perl.createArray(values?), perl.createHash(object?)
Create Perl arrays and hashes. Returns PerlArray or PerlHash.
const arr = perl.createArray([1, 2, 3]);
const hash = perl.createHash({ key: 'value' });perl.toPerlValue(value)
Convert JavaScript value to Perl. Handles primitives, arrays, and objects.
const perlVal = perl.toPerlValue({ name: 'Alice', age: 30 });perl.setVariable(name, value), perl.getVariable(name)
Set and get scalar variables. Variable names should not include the $ prefix.
perl.setVariable('x', 42);
const x = perl.getVariable('x');
console.log(x?.toInt()); // 42perl.getArrayVariable(name), perl.getHashVariable(name)
Get array and hash variables. Returns PerlArray or PerlHash.
const arr = perl.getArrayVariable('myarray');
const hash = perl.getHashVariable('myhash');perl.registerFunction(name, fn)
Register a JavaScript function callable from Perl.
perl.registerFunction('add', (a, b) => {
const x = a.toInt();
const y = b.toInt();
return perl.createInt(x + y);
});perl.registerMethod(packageName, methodName, fn)
Register a JavaScript method callable from Perl.
perl.registerMethod('Math', 'square', (x) => {
const num = x.toInt();
return perl.createInt(num * num);
});perl.call(name, args, context?)
Call a Perl function. Context can be "void", "scalar", or "list".
const result = await perl.call('my_sub', [arg1, arg2], 'scalar');
const results = await perl.call('my_sub', [], 'list');
await perl.call('my_sub', [], 'void');perl.flush()
Flush output buffers. Required if autoflush ($| = 1) is not set.
await perl.eval('print "text"');
perl.flush();perl.reset()
Reset interpreter to clean state. Clears all variables.
await perl.reset();perl.getLastError(), perl.clearError()
Get and clear the Perl error state ($@).
const error = perl.getLastError();
perl.clearError();perl.isInitialized(), perl.canEvaluate()
Check interpreter state.
const ready = perl.isInitialized() && perl.canEvaluate();perl.dispose(), perl.shutdown()
Free resources. Use dispose() for normal cleanup, shutdown() for complete termination.
perl.dispose();
// or
perl.shutdown();PerlValue Methods
toInt()- Convert to 32-bit integertoDouble()- Convert to double-precision floattoString()- Convert to UTF-8 stringtoBoolean()- Convert to boolean (Perl truth test)isUndef()- Check if undefinedisRef()- Check if referencegetType()- Get Perl typeproject()- Convert to JavaScript primitivecreateRef()- Create referencederef()- Dereference valuedispose()- Free memory
PerlArray Methods
push(value)- Add to endpop()- Remove from endget(index)- Get value at indexset(index, value)- Set value at indexgetLength()- Get array lengthclear()- Remove all elementstoValue()- Convert to PerlValue (array reference)project()- Convert to JavaScript array[Symbol.iterator]()- Iterate over valuesdispose()- Free memory
PerlHash Methods
set(key, value)- Set key-value pairget(key)- Get value by keyhas(key)- Check if key existsdelete(key)- Delete keyclear()- Remove all entriestoValue()- Convert to PerlValue (hash reference)project()- Convert to JavaScript objectentries()- Iterate over key-value pairskeys()- Iterate over keysvalues()- Iterate over valuesdispose()- Free memory
Examples
Processing JSON
import { ZeroPerl, MemoryFileSystem } from '@6over3/zeroperl-ts';
const fs = new MemoryFileSystem({ "/": "" });
fs.addFile("/data.json", JSON.stringify({ users: ['Alice', 'Bob'] }));
fs.addFile("/process.pl", `
$| = 1;
use strict;
use warnings;
open my $fh, '<', '/data.json' or die $!;
my $json = do { local $/; <$fh> };
close $fh;
print "Processing: $json\\n";
`);
const perl = await ZeroPerl.create({ fileSystem: fs });
await perl.runFile('/process.pl');
perl.dispose();Interactive REPL
const perl = await ZeroPerl.create({
stdout: (data) => console.log(data)
});
await perl.eval('$| = 1');
await perl.eval('$x = 10');
await perl.eval('print "$x\\n"');
await perl.eval('$x *= 2');
await perl.eval('print "$x\\n"');
perl.dispose();Complex Data Structures
const perl = await ZeroPerl.create();
perl.setVariable('config', {
server: {
host: 'localhost',
port: 8080
},
features: ['auth', 'logging']
});
await perl.eval(`
$| = 1;
print "Host: $config->{server}{host}\\n";
print "Port: $config->{server}{port}\\n";
print "Features: @{$config->{features}}\\n";
`);
perl.dispose();Development
npm i # Install dependencies
bun run build # Build distributions
bun test # Run testsLicense
Apache-2.0
About
ZeroPerl compiles Perl 5 to WebAssembly using a WASI-compliant implementation. This package provides a TypeScript/JavaScript API for the ZeroPerl WASM module.
