Giter Club home page Giter Club logo

componentizejs's People

Contributors

bakkot avatar dicej avatar divya-mohan0209 avatar guybedford avatar kyleconroy avatar pvlugter avatar saulecabrera avatar vados-cosmonic avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

componentizejs's Issues

"use" unsupported

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.

Wizer-time bindings checks

  • In the recent bindgen updates, import checks at Wizer time were removed, these should be added back.
  • In addition we should extend these checks to the exports shape as well.

Relatively simple work, and should improve the user experience.

Support WIT versions

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:

  1. The version(s) in the WIT file(s) need to be included in the component output.
  2. A guest should be allowed to import and/or export multiple versions of the same interface without a conflict.

To start with, we should at least support (1). (2) is arguably a lower priority and could be addressed separately at a later time.

Auto-drop borrows

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.

Document `witPath` option

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.

Boilerplate generation

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.

How to import types or records from imported interfaces?

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. ๐Ÿ™

wasm `unreachable` instruction executed

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 wrote
  • scala2 with code generated for ScalaJS from a Scala 2 implementation
  • scala3 with code generated for ScalaJS from a Scala 3 implementation

All 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.

Logging

console logging should be going through wasi-logging, either by default or as an option.

Dynamic Linking of SpiderMonkey Interpreter

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

Upgrade to Latest WIT

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.

Import packages from npm

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)}`;
}

Document how imports work & avoid printing invalid JS in the error message

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 `fetch` traps

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

Environment

I'm using ComponentizeJS 0.8.3 and wasmtime 19.

% wasmtime --version
wasmtime-cli 19.0.0 (6e0abd754 2024-03-20)

"Cannot find module" error

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';

Rooting array buffers

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.

Example extensions

The example should be extended to external bindings linkage and a browser example as well.

Getting `TypeError: this is undefined`

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

Why focus or depend on V8 and Node.js?

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)?

TypeScript + Dependency support

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.

Use of `wasi-http` fails to fully finish writing the body

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

Documentation disagrees with itself

In the Usage section, componentize is a 3-argument function.

In the API section, componentize is a 2-argument function.

In the example, componentize is a 2-argument function but the types are different from the previous case.

Also it looks like enableStdout might no longer exist?

Create a new release?

0.7.0 has already been published to npm and tagged, but no GitHub release has been created.

Bundler step

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.

Not able to run the example with Rust

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!

Support for Resources

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)

Resource destructors via Symbol.dispose

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.

Unknown failure with latest version with no useful error output

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.

The output after `npm run build`:
> 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

`setTimeout` support

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?

Debugger Integration

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!

enums not exported?

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.

variant errors on top-level errors mask real error

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.

lowering floating point values is not (always) working

I've extended the floats test to include a case that's currently failing, apparently due to incorrect lowering code:

dicej@351b476

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

Repeated calls to an instance start failing

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)

Document how imports work

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.

Is it possible to expose existing JavaScript libraries as components?

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/ .

Support for targeting multiple worlds

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).

Option to generate "plain" wasm

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...

Producer metadata

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.

Create a componentize-js CLI experience

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).

Verify resource borrow drop behaviours

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.

Add a fetch example?

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?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.