bytecodealliance / componentizejs Goto Github PK
View Code? Open in Web Editor NEWJS -> WebAssembly Component
License: Apache License 2.0
JS -> WebAssembly Component
License: Apache License 2.0
Currently even without external wit file support, use against self-interfaces is also not working, eg for the world:
interface types {
record app-props {
name: string,
count: u32
}
}
default world app {
use self.types.{app-props}
export render: func(props: app-props) -> string
}
Fixing this will involve upstreaming the bindings inversion into the transpiler bindgen in jco, now that the technique is proven.
Relatively simple work, and should improve the user experience.
When creating a wasm file with c++ and wit, it is possible to export a "main" function, which is invoked by default - is there equivilant with JS?
Per https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md#package-declaration, WIT packages may contain versions, and these should be included in the resulting component type.
There are actually two levels of support to consider:
To start with, we should at least support (1). (2) is arguably a lower priority and could be addressed separately at a later time.
In bytecodealliance/jco#262, we now validate all borrows properly.
The need to call resource[Symbol.dispose]()
explicitly for all functions that take resources seems unnecessary though.
Rather, the implementation should always auto-drop borrows at the bindgen level without requiring the JS user to have to do that explicitly. Explicitly drop calls should instead act as early drops if desired.
The witPath
option to the componentize
function can either be a path to a single wit file or to a directory. If the path is a wit file then the wit package must not have any dependencies. Otherwise, if the path is a directory, the package's dependencies can be located in a deps
directory.
This should be documented as it is not obvious that this is the behavior.
One of the trickiest parts of a ComponentizeJS workflow is figuring out the write structure to author the code to.
We should generate the boilerplate outline for the world being targetted as a new generateBoilerplate
operation, which could become something like jco componentize-gen world.wit
for example.
I've posted bytecodealliance/jco#152 in jco for the bindgen discussion on this.
a.wit
package foo:a
interface foo {
record bar {
baz: string
}
}
b.wit
Does not work with componentize.
package foo:b
world b {
use foo:a/foo.{bar}
export test: func() -> bar
}
Does not work with componentize.
package foo:b
world b {
import foo:a/foo.{bar}
export test: func() -> bar
}
Would work with componentize but then the type will not be recognized in wit
package foo:b
world b {
import foo:a/foo
export test: func() -> bar
}
On the js
side I'd like to use it as
import { bar } from "foo:a/foo"
If bar
was a func
then I can use it as above.
I couldn't yet find a way to import types from interfaces.
Basically the ideal form for me would be,
package foo:b
world b {
use foo:a/foo.{bar}
export test: func() -> bar
}
which works with no issues when writing the component in Rust with wit-bindgen
.
I'd be grateful if you could point me to the right direction. ๐
Hello, I'm encountering the error in the title when running a WASM file generated by Componentize and I'd need some help.
I created a small reproducer for you on my fork. You can find 3 branches:
main
with minimal JS code that I wrotescala2
with code generated for ScalaJS from a Scala 2 implementationscala3
with code generated for ScalaJS from a Scala 3 implementationAll branches are building successfully with npm run build
. The first two branches have ./test.sh
pass successfully, while the third errors this way:
> test
> ./target/release/wasmtime-test
Error: error while executing at wasm backtrace:
0: 0x57fc47 - <unknown>!<wasm function 6688>
1: 0x5826bc - <unknown>!<wasm function 7282>
2: 0x3306e9 - <unknown>!<wasm function 587>
3: 0x57f97a - <unknown>!local:hello/hello#hello
Caused by:
wasm trap: wasm `unreachable` instruction executed
Please note that the generated code between scala2
and scala3
branches might differ, but when imported manually it always works correctly:
> const { hello } = await import('./hello.js');
undefined
> hello.hello("Jane")
'Hello, Jane!'
This makes me wonder that something is wrong in the WASM file itself and that's why I'm reporting this issue. Please let me know if I can help you further.
console
logging should be going through wasi-logging, either by default or as an option.
Problem
The size of wasm module produced is quite high, ~ 8 MB for a function adding two numbers. My understanding is that the interpreter (SpiderMonkey) is the majority of it.
Query
Is there a way to dynamically link the interpreter and wasm module to reduce the module size? The primary benefit is when there are multiple wasm modules & instances
For reference: Javy has -d flag for enabling dynamic linking
Would you be open to remove process.exit for a throw ?
I'm using componentizejs to build a CLI that wraps bundling, auto wit world creation, wasi imports etc but i need to control the exits on my side.
https://github.com/fission-codes/stack/blob/main/packages/homestar/src/wasmify/index.js
I would do a PR, just asking if you would be open to it
Thanks.
Wastime 16 now supports rc-2023-12-05 for wasi-http and wasi-cli.
https://github.com/WebAssembly/wasi-http/releases/tag/v0.2.0-rc-2023-12-05
Tracking issue to upgrade ComponentizeJS to the latest WIT syntax.
For kebab names, the integration remains identical to the current implementation, ie:
package local:app
world app {
import fn: func () -> string;
import iface: interface {
test: func () -> string
}
export fn: func () -> string;
export iface: interface {
test: func () -> string
}
}
can be represented by the JS ESM source:
import fnImport from 'fn';
import { test } from 'iface';
export function fn () {
return fnImport();
}
export const iface = {
test () {
return test();
}
};
In the recent [email protected] release, the WIT wasn't quite represented correctly for namespaces which require a conversion, resulting in a need for some further refinements described in bytecodealliance/jco#91.
When handling namespaces, we effectively perform a "conversion" from the namespace semantics to the ESM semantics, exactly as described in the JCO issue there.
The convention with aliasing semantics becomes for componentizing a world:
package my:package
interface test {
hello: func () -> string
}
world test {
import wasi:filesystem/types
import test
export wasi:filesystem/types
export test
}
becomes the corresponding ESM source written by the user:
import * as types from 'wasi:filesystem/types';
import { hello } from 'test';
// export aliases
export const types = {
fn () {], // ...
};
export const test = {
hello () {}
};
If there were an aliasing conflict, say there already existed interface name exports for test
and types
, then we would need the code author to instead write:
import * as types from 'wasi:filesystem/types';
import { hello } from 'test';
// when the world has these namespace interface exports already,
// eg from another interface via:
// export another:package/types
// export another:package/test
// then the base alias can only be the first or neither
export const types = /*...*/;
export const test = /*...*/;
// now we must then export the full canonical names for the duplicates,
// by defining local variables,
const wasiFilesystemTypes = {
hello () {}
};
const myPackageTest = {
fn () {], // ...
};
// then we export them using the "string export" syntax for ES modules
export { wasiFilesystemTypes as 'wasi:filesystem/types', myPackageTest as 'my:package/test' }
The major concern with this Componentize convention for implementing external namespaces, is that when there is a conflict this new authoring style wouldn't be known to the user, so they would need to be informed that they must switch authoring modes into this full convention.
The alternative would be to always require the full verbose export mode without aliasing in componentize workflows to avoid such a cliff.
I am looking to build on the provided examples and support the ability to add npm
packages that my users might want to import.
I tried to just add an import
to my hello.js
, but when I try and build the wasm module I get the following error. Is there a way to import that I'm overlooking?
$ node componentize.mjs
Import 'randomstring' in source.js is not defined as a world import. Only component-defined imports can be used.
Unable to link the source code. Imports should be:
Code:
import randomstring from "randomstring";
export function hello(name) {
return `Hello ${name} - ${randomstring.generate(10)}`;
}
Suppose you have the following WIT:
package local:hello;
world hello {
import foo: func() -> string;
export hello: func(name: string) -> string;
}
and you want to create and componentize a JS module which provides that interface.
The first thing that trips you up is that nothing tells you want module specifier to use for the import. If you pick one at random:
import { foo } from '??';
export function hello(name) {
return `hello ${name} here is your value: ${foo()}`;
}
That produces
Import '??' in source.js is not defined as a world import. Only component-defined imports can be used.
Unable to link the source code. Imports should be:
import { default } from "foo";
which at least suggests that the module specifier should match the import name. (Though it's confusing that it says the error is in source.js
given that my file is in fact named hello.js
.) Great! Ideally that would be documented outside of the error message.
Unfortunately, the code it suggests is a syntax error (since default
is not a valid identifier name in JS). You actually want a default import, as in:
import nameOfYourChoice from "foo";
export function hello(name) {
return `hello ${name} here is your value: ${nameOfYourChoice()}`;
}
which does in fact work once you provide the right bindings, for example with JCO:
jco componentize hello.js --wit hello.wit -n hello -o hello.wasm
jco transpile hello.wasm --instantiation sync --out-dir out
can be run with
import fs from 'node:fs';
import { instantiate } from './out/hello.js';
let x = instantiate(
n => new WebAssembly.Module(fs.readFileSync('out/' + n)),
{ foo: { default: () => 'xyz' } },
);
console.log(x.hello('alice'));
Using the command-extended world, I'm trying to use fetch
. The following code compiles to WASM but traps at runtime.
// All of the same imports and exports available in the wasi:cli/command world
// with addition of HTTP proxy related imports:
world command-extended {
include wasi:cli/command@0.2.0;
import wasi:http/outgoing-handler@0.2.0;
}
// app.js
export const run = {
async run() {
const resp = await fetch("http://example.com");
console.log(`status: ${resp.status}`)
console.log('Hello, world!');
}
}
import { componentize } from '@bytecodealliance/componentize-js';
import { readFile, writeFile } from 'node:fs/promises';
import { resolve } from 'node:path';
const jsSource = await readFile("app.js", 'utf8');
const { component } = await componentize(jsSource, {
witPath: resolve("../../wit"),
worldName: "command-extended",
enableFeatures: ['http'],
});
await writeFile('cli.wasm', component);
% wasmtime run -Shttp cli.wasm
Error: failed to run main module `cli.wasm`
Caused by:
0: failed to invoke `run` function
1: error while executing at wasm backtrace:
0: 0x2978c5 - <unknown>!<wasm function 332>
1: 0x3d4992 - <unknown>!<wasm function 810>
2: 0x33649f - <unknown>!<wasm function 523>
3: 0xc7b6b - <unknown>!<wasm function 112>
4: 0x333c63 - <unknown>!<wasm function 519>
5: 0x3362fa - <unknown>!<wasm function 523>
6: 0x67f172 - <unknown>!<wasm function 4654>
7: 0x4de98a - <unknown>!<wasm function 1628>
8: 0x3c1a15 - <unknown>!<wasm function 769>
9: 0x7749be - <unknown>!wasi:cli/[email protected]#run
2: wasm trap: wasm `unreachable` instruction executed
I'm using ComponentizeJS 0.8.3 and wasmtime 19.
% wasmtime --version
wasmtime-cli 19.0.0 (6e0abd754 2024-03-20)
I tried running the example https://github.com/bytecodealliance/componentize-js/blob/main/EXAMPLE.md
but got the error:
$ node -e "import('./hello/hello.component.js').then(m => console.log(m.hello('ComponentizeJS')))"
node:internal/errors:465
ErrorCaptureStackTrace(err);
^
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/home/mjs/tmp/comp/node_modules/@bytecodealliance/preview2-shim/lib/nodejs/wall-clock' imported from /home/mjs/tmp/comp/hello/hello.component.js
at new NodeError (node:internal/errors:372:5)
at finalizeResolution (node:internal/modules/esm/resolve:437:11)
at moduleResolve (node:internal/modules/esm/resolve:1009:10)
at defaultResolve (node:internal/modules/esm/resolve:1218:11)
at ESMLoader.resolve (node:internal/modules/esm/loader:580:30)
at ESMLoader.getModuleJob (node:internal/modules/esm/loader:294:18)
at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:80:40)
at link (node:internal/modules/esm/module_job:78:36) {
code: 'ERR_MODULE_NOT_FOUND'
}
I then manually changed the start of the file hello/hello.component.js
to directly refer to the libraries, and it worked:
import { now as lowering12Callee, resolution as lowering13Callee } from '../node_modules/@bytecodealliance/preview2-shim/lib/nodejs/wall-clock.js';
import { getRandomBytes as lowering21Callee } from '../node_modules/@bytecodealliance/preview2-shim/lib/nodejs/random.js';
import { getStdio as lowering10Callee, getDirectories as lowering11Callee } from '../node_modules/@bytecodealliance/preview2-shim/lib/nodejs/preopens.js';
import { resolution as lowering0Callee, now as lowering1Callee } from '../node_modules/@bytecodealliance/preview2-shim/lib/nodejs/monotonic-clock.js';
import { exit as lowering7Callee } from '../node_modules/@bytecodealliance/preview2-shim/lib/nodejs/exit.js';
import { dropInputStream as lowering8Callee, dropOutputStream as lowering9Callee, read as lowering23Callee, write as lowering24Callee } from '../node_modules/@bytecodealliance/preview2-shim/lib/nodejs/streams.js';
import { getEnvironment as lowering22Callee } from '../node_modules/@bytecodealliance/preview2-shim/lib/nodejs/environment.js';
import { dropDirectoryEntryStream as lowering2Callee, readViaStream as lowering3Callee, writeViaStream as lowering4Callee, appendViaStream as lowering5Callee, dropDescriptor as lowering6Callee, getFlags as lowering14Callee, getType as lowering15Callee, setFlags as lowering16Callee, stat as lowering17Callee, openAt as lowering18Callee, removeDirectoryAt as lowering19Callee, unlinkFileAt as lowering20Callee } from '../node_modules/@bytecodealliance/preview2-shim/lib/nodejs/filesystem.js';
The current GC strategy is to just clear all allocations at the end of each call, but we need to allow the JS ArrayBuffers to hold on to the memory they expose. This likely involves integration into the JS GC to ensure it can become a managed pointer in the engine.
The example should be extended to external bindings linkage and a browser example as well.
Say I have a js library that is already using an emscripten generated wasm file and I try to componentize that, given spider monkey supports wasm, can I "just" bundle the existing wasm as if it were any other resource (like a jpeg)?
I'm trying to componentize the following JS file generated by the Scala.js compiler (Scala 3.3.1):
https://gist.github.com/jorge-vasquez-2301/76ba147e39fda32aab07beae552a9dbb
The WIT file is like:
package golem:template
interface api {
hello: func(name: string) -> ()
}
world hello {
export api
}
When I try to run the component after transpiling it with jco
, I get the following error:
source.js:1225:3 TypeError: this is undefined
Redirecting call to abort() to mozalloc_abort
wasm://wasm/01bbaa36:1
RuntimeError: unreachable
at wasm://wasm/01bbaa36:wasm-function[6442]:0x4ad9bb
at wasm://wasm/01bbaa36:wasm-function[7049]:0x4b0604
at wasm://wasm/01bbaa36:wasm-function[516]:0x279137
at golem:template/api#hello (wasm://wasm/01bbaa36:wasm-function[6384]:0x4ad19e)
at Object.hello (file:///Users/jorgevasquez/dev/projects/scala-shopping-cart/cart/component.js:881:39)
at [eval]:1:59
By the way, when I run the JS file without componentizing, everything works as expected
SpiderMonkey is chosen here as a JS engine with first-class WASI build support. The total embedding size is around 5MB.
Since you are embedding SpiderMonkey JavaScript engine why is V8 and Node.js involved in the process?
Embedding size is noted as 5MB.
node
nightly (v21.0.0-nightly202305318aa02e81d0
) executable alone is 91.2MB.
Node.js does not provide a download of node
executable alone. Node.js nightly download include node_modules
, npm
, etc. That's why I wrote this https://github.com/guest271314/download-node-nightly-executable.
That last time I checked d8
(10.9.60) alone was 26.1MB.
txiki.js supports wasm3 WebAssembly engine is 7.6MB.
QuickJS qjs
executable is 5MB, less than 1MB after strip
.
I think the JavaScript examples should be as generic as standard, considering QuickJS, txiki.js, Bun, Deno, and Node.js, favoring neither.
Can't you just use SpiderMonkey for the entire process without depending on V8 (node
) and npm
and without tailoring the example in the README for Node.js (that will cost at least 91.2MB)?
We should automatically support a TypeScript input and also having dependencies being bundled in as well, down to imports not colliding with the world imports.
I've been trying to replicate a few examples using ComponentizeJS with TypeScript and I've been trouble using a component that uses wasi-http. Here's the component code:
import {
IncomingRequest,
ResponseOutparam,
OutgoingResponse,
OutgoingBody,
OutputStream,
Fields,
} from "wasi:http/[email protected]";
// Implementation of wasi-http incoming-handler
function handle(req: IncomingRequest, resp: ResponseOutparam) {
// Start building an outgoing response
const outgoingResponse = new OutgoingResponse(new Fields());
// Set the status code for the response
outgoingResponse.setStatusCode(200);
// Access the outgoing response body
const outgoingBody = outgoingResponse.body();
// Create a stream for the response body
let outputStream: OutputStream | null = outgoingBody.write();
// Write hello world to the response stream
outputStream.blockingWriteAndFlush(
new Uint8Array(new TextEncoder().encode("Hello from Typescript!\n"))
);
// Set the created response
ResponseOutparam.set(resp, { tag: "ok", val: outgoingResponse });
}
export const incomingHandler = {
handle,
};
Now when I use this and curl
the endpoint, I do get "Hello from Typescript", but the component hangs indefinitely. After taking a look at a couple other examples I noticed that I have one line missing here:
outputStream.blockingWriteAndFlush(
new Uint8Array(new TextEncoder().encode("Hello from Typescript!\n"))
);
ResponseOutparam.set(resp, { tag: "ok", val: outgoingResponse });
OutgoingBody.finish(outgoingBody, undefined); // This line, to "finish" the outgoing body
However trying to run this gets me an issue:
โ wasmtime serve -Scommon ./build/http_hello_world_s.wasm
Serving HTTP on http://0.0.0.0:8080/
2024-04-10T16:15:42.362242Z ERROR wasmtime_cli::commands::serve: [0] :: Error {
context: "error while executing at wasm backtrace:\n 0: 0xa71792 - wit-component:shim!indirect-wasi:http/[email protected][static]outgoing-body.finish\n 1: 0x73cd95 - <unknown>!<wasm function 8453>\n 2: 0x3360a9 - <unknown>!<wasm function 515>\n 3: 0xc777a - <unknown>!<wasm function 104>\n 4: 0x33386d - <unknown>!<wasm function 511>\n 5: 0x335f04 - <unknown>!<wasm function 515>\n 6: 0x67ed71 - <unknown>!<wasm function 4646>\n 7: 0x4de593 - <unknown>!<wasm function 1620>\n 8: 0x3c161e - <unknown>!<wasm function 761>\n 9: 0x76e374 - <unknown>!wasi:http/[email protected]#handle",
source: HasChildren,
}
Do you know how to compensate for this error? In Rust and with componentize-py this code works ๐ค In tinygo, I did end up needing to explicitly drop
the outputStream equivalent but I don't see a way to do this in JS/TS
0.7.0 has already been published to npm and tagged, but no GitHub release has been created.
When componentizing a JS application, we should including a JS bundler to build the JS file first before componentization.
This could support both TypeScript as well as node_modules and local imports being bundled in, down to the WIT dependencies being treated as externals.
We do need to be careful here with error messages to be sure that WIT / componentization issues can be correctly debugged.
Hi,
I've successfully executed the example with node, now I'm trying to run it with Rust, however I'm getting the following
error: failed to select a version for `wasmtime-fiber`.
... required by package `wasmtime v6.0.0`
... which satisfies dependency `wasmtime = "^6.0.0"` of package `host v0.0.0 (https://github.com/bytecodealliance/preview2-prototyping#75f8fc61)`
... which satisfies git dependency `host` of package `runner v0.1.0 (/Users/r/Experimentos/componentize/runner)`
versions that meet the requirements `=6.0.0` are: 6.0.0
the package `wasmtime-fiber` links to the native library `wasmtime-fiber-shims`, but it conflicts with a previous package which links to `wasmtime-fiber-shims` as well:
package `wasmtime-fiber v6.0.0 (https://github.com/bytecodealliance/wasmtime?branch=release-6.0.0#c00d3f05)`
... which satisfies git dependency `wasmtime-fiber` of package `wasmtime v6.0.0 (https://github.com/bytecodealliance/wasmtime?branch=release-6.0.0#c00d3f05)`
... which satisfies git dependency `wasmtime` of package `runner v0.1.0 (/Users/r/Experimentos/componentize/runner)`
Only one package in the dependency graph may specify the same links value. This helps ensure that only one copy of a native library is linked in the final binary. Try to adjust yo
ur dependencies so that only one package uses the links ='wasmtime-fiber' value.
failed to select a version for `wasmtime-fiber` which could resolve this conflict
Where runner
is my local project.
I've been checking the repos and seen that preview2-prototyping has only one tag, how would you suggest to resolve this?
Thank you!
Hey all
I'm trying to build a small javascript component which imports a single external resource. I can't seem to figure out what's causing the error here. But, my best guess is resources aren't yet supported.
// Build Script
import { componentize } from '@bytecodealliance/componentize-js';
import { readFile, writeFile, mkdir } from 'node:fs/promises';
import { resolve } from 'node:path';
const jsSource = await readFile('main.js', 'utf8');
const { component } = await componentize(jsSource, {
witPath: resolve('wit'),
enableStdout: true,
preview2Adapter: '../wasi_snapshot_preview1.reactor.wasm.dev',
worldName: "function-world",
debug: true,
});
await mkdir('out', { recursive: true });
await writeFile('cool_function.wasm', component);
// Function
export function handleRequest(req) {
return {
status : 200,
headers : [],
body : req.body
}
}
// Function World
world function-world {
use mycelia:http/interfaces.{client} // importing the client throws
export handle-request: func(req: http-request) -> http-response
}
// Dep snippet
interface interfaces {
use types.{client-request, client-result}
resource client {
constructor()
send: func(req: client-request) -> client-result
}
}
thread '<unnamed>' panicked at /Users/gbedford/.cargo/git/checkouts/jco-c2d1523c7c4f4f8a/be4ab7c/crates/js-component-bindgen/src/function_bindgen.rs:1121:71:
no entry found for key
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
wasm://wasm/015bfc9a:1
RuntimeError: unreachable
at __rust_start_panic (wasm://wasm/015bfc9a:wasm-function[4802]:0x291bdc)
at rust_panic (wasm://wasm/015bfc9a:wasm-function[4789]:0x2915cb)
at _ZN3std9panicking20rust_panic_with_hook17h53a16cc107f82a01E (wasm://wasm/015bfc9a:wasm-function[4788]:0x2915a8)
at _ZN3std9panicking19begin_panic_handler28_$u7b$$u7b$closure$u7d$$u7d$17h785108f94bf7bebdE (wasm://wasm/015bfc9a:wasm-function[4776]:0x2907fb)
at _ZN3std10sys_common9backtrace26__rust_end_short_backtrace17he50a360aef66315aE (wasm://wasm/015bfc9a:wasm-function[4775]:0x29071e)
at rust_begin_unwind (wasm://wasm/015bfc9a:wasm-function[4783]:0x290eb1)
at _ZN4core9panicking9panic_fmt17h5e1c79a8a40da992E (wasm://wasm/015bfc9a:wasm-function[4949]:0x29f4e5)
at _ZN4core6option13expect_failed17h9b74a8942db97e9aE (wasm://wasm/015bfc9a:wasm-function[5012]:0x2a944b)
at _ZN106_$LT$js_component_bindgen..function_bindgen..FunctionBindgen$u20$as$u20$wit_bindgen_core..abi..Bindgen$GT$4emit17hdee68ca0c39977e9E (wasm://wasm/015bfc9a:wasm-function[1454]:0xe311e)
at _ZN16wit_bindgen_core3abi18Generator$LT$B$GT$4emit17h4215c8ba60679758E (wasm://wasm/015bfc9a:wasm-function[34]:0x5bf4)
We should support resource destructors for the ComponentizeJS resources implementation.
Specifically - Symbol.dispose
allows resources authored in JS to define a destructor. And Symbol.dispose
also provides a mechanism for early destruction of own resources, instead of having to wait for the GC.
In addition, the finalization registry implemented for resources doesn't currently cater to dtor
calls, which can be looked up with a custom parse of the component Wasm.
I had this simple example of a "reactor style" counter component which was working well with a previous version
(specifically
"@bytecodealliance/jco": "0.11.1",
"@bytecodealliance/componentize-js": "0.2.0"
with a preview2 adapter built from wasmtime
's 134dddc
commit
)
Here is the example code:
https://github.com/vigoo/componentize-js-bug-1
With the latest version (0.5.0
) it looks like the wizer
step does not produce any output, but does not print any error either.
Providing a proper preview2Adapter
wasm to the componentize
call does not make any difference.
> build
> node componentize.js
--- JS Source ---
var state = 0;
export const api = {
"add": function (value) {
console.log(`Adding ${value} to the counter`);
state += Number(value);
},
"get": function() {
console.log(`Returning the current counter value: ${state}`);
return BigInt(state);
}
}
--- JS Bindings ---
import * as $source_mod from 'source.js';
let repCnt = 0;
let repTable = new Map();
let $memory, $realloc;
export function $initBindings (_memory, _realloc) {
$memory = _memory;
$realloc = _realloc;
}
class BindingsError extends Error {
constructor (path, type, helpContext, help) {
super(`"source.js" source does not export a "${path}" ${type} as expected by the world.${
help ? `\n\n Try defining it${helpContext}:\n\n${' ' + help.split('\n').map(ln => ` ${ln}`).join('\n')}
` : ''
}`);
}
}
function getInterfaceExport (mod, exportNameOrAlias, exportId) {
if (typeof mod[exportId] === 'object')
return mod[exportId];
if (exportNameOrAlias && typeof mod[exportNameOrAlias] === 'object')
return mod[exportNameOrAlias];
if (!exportNameOrAlias)
throw new BindingsError(exportId, 'interface', ' by its qualified interface name', `const obj = {};
export { obj as '${exportId}' }
`);
else
throw new BindingsError(exportNameOrAlias, 'interface', exportId && exportNameOrAlias ? ' by its alias' : ' by name', `export const ${exportNameOrAlias} = {};`);
}
function verifyInterfaceFn (fn, exportName, ifaceProp, interfaceExportAlias) {
if (typeof fn !== 'function') {
if (!interfaceExportAlias)
throw new BindingsError(exportName, `${ifaceProp} function`, ' on the exported interface object', `const obj = {
${ifaceProp} () {
}
};
export { obj as '${exportName}' }
`);
else
throw new BindingsError(exportName, `${ifaceProp} function`, ` on the interface alias "${interfaceExportAlias}"`, `export const ${interfaceExportAlias} = {
${ifaceProp} () {
}
};`);
}
}
function verifyInterfaceResource (fn, exportName, ifaceProp, interfaceExportAlias) {
if (typeof fn !== 'function') {
if (!interfaceExportAlias)
throw new BindingsError(exportName, `${ifaceProp} resource`, ' on the exported interface object', `const obj = {
${ifaceProp} () {
}
};
export { obj as '${exportName}' }
`);
else
throw new BindingsError(exportName, `${ifaceProp} resource`, ` on the interface alias "${interfaceExportAlias}"`, `export const ${interfaceExportAlias} = {
${ifaceProp} () {
}
};`);
}
}
const { add: apiAdd, get: apiGet } = getInterfaceExport($source_mod, 'api', 'golem:it/api');
verifyInterfaceFn(apiAdd, 'golem:it/api', 'add', 'api');
verifyInterfaceFn(apiGet, 'golem:it/api', 'get', 'api');
const toUint64 = val => BigInt.asUintN(64, BigInt(val));
export function export_api$add(arg0) {
apiAdd(BigInt.asUintN(64, arg0));
}
export function export_api$get() {
const ret = apiGet();
return toUint64(ret);
}
--- JS Imports ---
[]
[]
--- JS Exports ---
[
[
'export_api$add',
{
params: [Array],
ret: undefined,
retptr: false,
retsize: 0,
paramptr: false
}
],
[
'export_api$get',
{
params: [],
ret: 'i64',
retptr: false,
retsize: 0,
paramptr: false
}
]
]
--- Wizer Env ---
{
DEBUG: '1',
SOURCE_NAME: 'source.js',
SOURCE_LEN: '293',
BINDINGS_LEN: '2718',
IMPORT_WRAPPER_CNT: '0',
EXPORT_CNT: '2',
EXPORT0_NAME: 'export_api$add',
EXPORT0_ARGS: 'i64',
EXPORT0_RET: '',
EXPORT0_RETSIZE: '0',
EXPORT1_NAME: 'export_api$get',
EXPORT1_ARGS: '',
EXPORT1_RET: 'i64',
EXPORT1_RETSIZE: '0',
IMPORT_CNT: 0
}
node:internal/fs/promises:633
return new FileHandle(await PromisePrototypeThen(
^
Error: ENOENT: no such file or directory, open 'out/component.wasm'
at async open (node:internal/fs/promises:633:25)
at async writeFile (node:internal/fs/promises:1212:14)
at async file:///Users/vigoo/projects/test/componentize-js-bug-1/componentize.js:12:1 {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: 'out/component.wasm'
}
Node.js v21.4.0
I'm trying to run componentize for a simple API that uses setTimeout
:
export const api = {
"checkout": function () {
setTimeout(() => {
console.log("Checking out.");
}, 1000);
}
}
And I'm getting this error when running the component: ReferenceError: setTimeout is not defined
, is there a way to solve that problem?
Brought up at CTW - Spidermonkey has a debugger API that we should figure out the right integration for.
Unfortunately it does not include a debugging protocol itself, so one would need to be implemented on top of WASI sockets or otherwise. Apparently VSCode solved this for Python using a character device on preview1 to handle the V8 debugging protocol.
The debugging that needs to be handled is stepping through the JS code, when yielding through to external component model import calls, I suppose this would suspend the entire debugging interface while the external call is made, then reinitiate the debugging interface after that. This seems fine as far as I can tell.
We could possibly even have a first-class world import for debugging support, by exposing the world for debug builds only, and then relying on hosts that support the debugger protocol imports and exports, as an alternative to implementing it on top of wasi sockets.
Should be a really interesting project!
I'm getting the error source.js:2:43 SyntaxError: ambiguous indirect export: MyEnum
when I try to import my enum like: import { MyEnum } from 'my_package:my_world/my_module'
-- I see in debug mode that none of the enums appear to be exported.
When using say a restricted error variant, throwing an unsupported error will be masked by the "invalid variant" error in the bindings.
In componentize bindings, we should specially handle invalid error variants at the top-level to be able to provide better debugging output.
I've extended the floats
test to include a case that's currently failing, apparently due to incorrect lowering code:
With that patch, the result of running npm run test -- --grep floats
is:
0 passing (2s)
1 failing
1) Bindings
floats:
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
NaN !== 3
+ expected - actual
-NaN
+3
We're testing long-lived component instances created with ComponentizeJS. After some number of calls to an instance we start getting failures, wasm traps such as unreachable or uninitialized value. The number of calls before a failure seems to vary based on the functions called and the data returned, but it consistently fails given the same pattern of calls.
I've created a test case using the ComponentizeJS testing here: main...pvlugter:ComponentizeJS:repeated-calls
Which fails after 1805 calls with:
RuntimeError: failed on attempt [1806]: null function or function signature mismatch
at js::InternalCallOrConstruct(JSContext*, JS::CallArgs const&, js::MaybeConstruct, js::CallReason) (wasm://wasm/029a3d3a:wasm-function[337]:0x193642)
at js::Call(JSContext*, JS::Handle<JS::Value>, JS::Handle<JS::Value>, js::AnyInvokeArgs const&, JS::MutableHandle<JS::Value>, js::CallReason) (wasm://wasm/029a3d3a:wasm-function[6458]:0x58cc39)
at JS_CallFunctionValue(JSContext*, JS::Handle<JSObject*>, JS::Handle<JS::Value>, JS::HandleValueArray const&, JS::MutableHandle<JS::Value>) (wasm://wasm/029a3d3a:wasm-function[1448]:0x31b2b4)
at call(unsigned int, void*) (wasm://wasm/029a3d3a:wasm-function[500]:0x1e76dc)
at exports#hello (wasm://wasm/029a3d3a:wasm-function[14509]:0x67e160)
at Object.hello (file:///.../ComponentizeJS/test/output/repeated-calls/repeated-calls.js:1430:40)
at Module.test (file:///.../ComponentizeJS/test/cases/repeated-calls/test.js:6:36)
at Context.<anonymous> (file:///.../ComponentizeJS/test/test.js:138:18)
As noted in #88, it is not obvious how imports work. While I understand the hesitation to document this extensively as things are likely to change in the future, it makes it very hard for new comers to ComponentizeJS to use the tool if there is absolutely no documentation about how to call imports.
I'm just getting started with components. I thought it would be possible to expose existing code and libraries as components. Is this not the case?
If I add an import to the example/hello.js
:
import process from 'node:process';
export function hello (name) {
return `Hello ${name} ${process.version}`;
}
I get this error:
cameron@CameronsMacBook example % node componentize.js
Import 'node:process' in source.js is not defined as a world import. Only component-defined imports can be used.
Unable to link the source code. Imports should be:
I read through https://component-model.bytecodealliance.org/ .
It would be great to support the ability to create a component that can target the union of multiple worlds in the cases where the user cannot edit the wit file to create a third world that includes the target worlds. An example of this would be an SDK built on top of a wit that bundles the wit in an npm package and the user application wanting to target their world where there would be 2 distinct wit
folders.
This request is motivated by the ability to do that in componentize-py
(the commit that added the support).
Not sure if this issue should target JCO - but I would like access to the "hello.wasm" as well as the "hello.component.wasm".
Side question - I was expecting to be able to extract the "hello.wasm" from "hello.component.wasm" directly, but didn't find anyway with wasm-tools or wit-bindgen - ended up having to call jco transpile...
When building the component we need to add support for producer metadata - noting the toolchain that created the component as this JS toolchain and version.
Instead of requiring componentize-js
to be used as a library, it would be nice if there was also a CLI experience. To begin with the CLI could take two mandatory arguments (the path to the wit package and the path to write the encoded component) and one optional argument (the world to be selected).
Currently resource borrows should be strictly dropped before call completion.
ComponentizeJS may not yet currently be implementing this correctly as discussed in #69 (comment).
Creating a dedicated issue to track this further.
I saw that fetch
support was recently added, but I'm not sure how to use it. Would it be possible to add an example using the proxy
world?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.