Create web apps programmatically
Thanks to the new version of the Wasmer Javascript SDK: `@wasmer/sdk`, you can now create websites from Node.js or your browser easily
Edoardo Marangoni
Software Engineer
August 13, 2024
Today we're excited to announce significant updates to the Wasmer JS SDK @wasmer/sdk
. With these latest enhancements, users can now programmatically create packages, publish them to the Wasmer registry, and deploy apps to Wasmer Edge… and more, all from Javascript, using your browser or Node!
We have created an awesome website to demonstrate the capabilities of the new SDK:
Lets get started!
Note: you can reproduce all the examples in the browser: sdk-demo-wasmer.wasmer.app, or using Node.js: github.com/wasmerio/wasmer-js/tree/main/examples/deploy-node
Initializing the library
Installing the library
$ npm install @wasmer/sdk # 0.7.1
The first step to programmatically create web apps is obtaining a token from the Wasmer registry. To generate a token, visit this page: Wasmer Access Tokens.
Once you have your token, you can import and initialize the Wasmer JS SDK in your code as follows:
import { init, Wasmer } from "@wasmer/sdk";
await init({ token: "<YOUR_TOKEN>" });
Note: If you have a Node version prior to 22 , you should instead import
@wasmer/sdk/node
Deploying a static site programmatically
Let’s do a cool example: let’s create a simple website - a copy of the static-website
template - just by specifying the same structure the usual manifest for Wasmer packages have, but as a plain JS value.
Assuming the library is initialized, we start by declaring the manifest of our package:
const manifest = {
command: [
{
module: "wasmer/static-web-server:webserver",
name: "script",
runner: "https://webc.org/runner/wasi",
annotations: {
wasi: {
"main-args": [
"--directory-listing=true", // This shows an index view of directories
],
},
},
},
],
dependencies: {
"wasmer/static-web-server": "^1",
},
fs: {
"/public": {
"myfile.txt": "The contents",
other: {
"index.html": "Feels good being embedded here ᕕ(ᐛ)ᕗ",
},
},
},
};
let pkg = await Wasmer.createPackage(manifest);
Now that we have the package set, we can proceed and just deploy it
const appConfig = {
name: "my-static-website",
owner: "<YOUR_USERNAME>",
domains: ["my-awesome-site.wasmer.app"],
package: pkg,
};
let result = await Wasmer.deployApp(appConfig);
console.log("\n== Deploying user-created app ==");
console.log("Deployed app id: ", result.id);
console.log("Deployed app unique url: ", result.url);
Et voilá! my-awesome-site.wasmer.app is now fully set up and serving your contents.
Deploying a self-contained PHP app
In this section we show a self contained example that users can run to deploy a simple PHP application running on Wasmer Edge.
import { init, Wasmer } from "@wasmer/sdk";
await init({token: "<YOUR_TOKEN>");
const manifest = {
"command": [
{
"module": "php/php:php",
"name": "run",
"runner": "wasi",
"annotations": {
"wasi": {
"main-args": [
"-t",
"/app",
"-S",
"localhost:8080"
]
}
}
}
],
"dependencies": {
"php/php": "=8.3.401"
},
"fs": {
"/app": {
"index.php": "<?php phpinfo(); ?>"
}
}
};
let pkg = await Wasmer.createPackage(manifest);
const appConfig = {
name: "my-php-app",
owner: "<YOUR-USERNAME>",
package: pkg,
default: true
};
let result = await Wasmer.deployApp(appConfig);
console.log("\n== Deploying user-created app ==");
console.log("Deployed app id: ", result.id);
console.log("Deployed app url: ", result.url);
And there we go, now we have a PHP app deployed!
Deploying packages is not all that you can do in the new @wasmer/sdk
, you can also create packages and run them directly in your browser, as shown in the following sections.
Creating and running packages
const manifest = {
command: [
{
module: "wasmer/python:python",
name: "myCommand",
runner: "wasi",
annotations: {
wasi: {
"main-args": ["/app/myapp.py"],
},
},
},
],
dependencies: {
"wasmer/python": "3.12.5+build.7",
},
fs: {
app: {
"myapp.py": `print("Hello from Python!");`,
},
},
};
let pkg = await Wasmer.createPackage(manifest);
Attentive users will have already noticed an important peculiarity: when creating packages, the files we want to use in it, listed under fs
, are embedded directly in the manifest itself.
This approach differs from the “usual” definition of package manifests used by the CLI, where you would specify the path to directories or files to include in the package. On the web, we can’t access filesystem nodes directly, so we need an alternative way to specify the contents of the package.
Web users can run their packages using the familiar mechanism available in previous versions of the SDK. However, Node users currently cannot run packages this way due to the lack of specific worker-related API calls.
let instance = await pkg.commands.myCommand.run();
const output = await instance.wait();
Publishing packages
The JS SDK allows users to publish packages created with a name and a version:
const manifest = {
"package": {
"name": "<OWNER>/<PACKAGE_NAME>",
"version": "0.1.0",
}
...
};
let pkg = await Wasmer.createPackage(manifest);
Once the package is created, publishing packages is just one call away!
await Wasmer.publishPackage(pkg);
Your package will then be available at the URL with the following structure: https://wasmer.io/<OWNER>/<PACKAGE_NAME>
.
One last thing…
Big files
In the two examples we saw, files to be embedded in the package were represented using simple strings but this, of course, is not always handy. Another possibility is that of using Uint8Array
to store the bytes of the file to be embedded:
...
"fs" : {
"public": {
"my_large_blob": new Uint8Array(...)
}
}
File metadata
Users can specify metadata for each file in the following fashion:
...
"fs" : {
"public": {
"index_date.html": {data: "Hello, js!", modified: new Date()},
"index_timestamp.html": { data: "Hello, js!", modified: 987656789 },
}
}
Package metadata
Provide package metadata such as README.md
and License
as follows:
const manifest = {
"package": {
"readme": "This is my readme!",
"license": { data: "This is my license!", modified: new Date() }
},
...
}
Technical challenges
To deliver these updates to the SDK, we faced two major challenges. The first was enabling calls to a Wasmer-like registry on the web. The second was adapting libraries to build packages without relying on a filesystem. Both tasks have been successfully completed.
As a result, you can now utilize the wasmer-api
crate on the web. More interestingly, we have been working on a new spec for containerizing apps at the Edge that we call webc
. Stay tuned for more updates on this front!
Conclusions
This post exemplified how users can now programmatically create web applications with a minimal set of function calls. We can’t wait to see what you come up with! Show us your projects and ask us questions on our dedicated discord server.
About the Author
Edo is the resident compiler guy, and part of the CLI magic happens thanks to him as well
Edoardo Marangoni
Software Engineer
Initializing the library
Deploying a static site programmatically
Deploying a self-contained PHP app
Creating and running packages
Publishing packages
One last thing…
Big files
File metadata
Package metadata
Technical challenges
Conclusions
Read more
wasmer edgejavascriptWinterJS
How fast is WinterJS vs alternatives? Blazing-fast
Syrus AkbaryNovember 1, 2023
wasmer runtimejavascriptwasmer-jsbrowser
Introducing the new Wasmer JS SDK
Michael BryanDecember 13, 2023