Giter Club home page Giter Club logo

bootsharp's Introduction

bootsharp's People

Contributors

aragas avatar cadbimdeveloper avatar elringus avatar promontis avatar skleni avatar thomasrockhu-codecov 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  avatar  avatar

bootsharp's Issues

Add an option to side-load binary boot data

While packing everything to a single .js library is convenient and even required in some cases (eg, for VS Code web extensions), it adds about 30% of extra size due to binary->base64 conversion of the WASM and DLLs.

We should add a build option to not pack the binary boot data with the emitted .js and instead allow the data to be side-loaded by the consumer; this way the binary files can be streamed directly from the server to optimize the traffic/initial load time.

Skipping boot function override should be enough to achieve that (bootData would be provided by the consumer): https://github.com/Elringus/DotNetJS/blob/a126393aed63581ec3078db0a382dffc84adfad2/DotNet/Packer/LibraryGenerator.cs#L17-L25

Malformed bindings.g.js due to improper namespaces names handling

Hi @elringus ,

Bootsharp makes malformed bindings.g.js file in some cases when the first namespace name is a part of the second namespace name and parseAst.js fails with a message like Expected ',' got 'export'.

I manged to reproduce this issue with the following toy example:

namespace BootSharpBackendCore.Input;

public enum Foo
{
    Value1 = 0,

    Value2 = 1
}
using BootSharpBackendCore.Input;

namespace BootSharpBackendCore.Output;

public record Bar(Foo Foo);
namespace BootSharpBackendCore;

public static class Facade
{
    public static IReadOnlyList<Bar> Test(Foo foo) => new[] { new Bar(foo) };
}

and a project with JSInvokable:

using Bootsharp;
using BootSharpBackendCore;
using BootSharpBackendCore.Input;
using BootSharpBackendCore.Output;

namespace BootSharpBackend;

public static partial class Program
{
    public static void Main()
    {

    }
    [JSInvokable]
    public static IReadOnlyList<Bar> Test(Foo foo) => Facade.Test(foo);
}

dotnet publish command will fail, but some of artifacts will remain, so, the bin\Release\net8.0\browser-wasm\AppBundle\_framework\bindings.g.js file will have something like that:

import { exports } from "./exports";
import { Event } from "./event";
function getExports () { if (exports == null) throw Error("Boot the runtime before invoking C# APIs."); return exports; }
function serialize(obj) { return JSON.stringify(obj); }
function deserialize(json) { const result = JSON.parse(json); if (result === null) return undefined; return result; }

export const BootSharpBackend = {
    test: (foo) => deserialize(getExports().BootSharpBackend_Program.Test(serialize(foo)))
export const BootSharpBackendCore = {
    Input: {
        Foo: { "0": "Value1", "1": "Value2", "Value1": 0, "Value2": 1 }
    }
};

Thank you!

Throw meaningful error when JavaScript function is not implemented

Currently, when a [JSFunction]-attributed method is invoked while the associated function is not implemented in JavaScript, a JSException is thrown, but without any message to identify reason of the error: https://github.com/Elringus/DotNetJS/blob/f44665a9b7b4259d13469ef5ea928c7cdb6caff1/JavaScript/dotnet-runtime/test/packed.js#L26

We've tried initializing the bindings with function (...args) { throw Error("..."); } instead of undefined: https://github.com/Elringus/DotNetJS/blob/f44665a9b7b4259d13469ef5ea928c7cdb6caff1/DotNet/Packer/LibraryGenerator.cs#L71

— hoping the message will be propagated back to JavaScript, but .NET runtime throws back just the exception name with stacktrace and, more importantly, the runtime fails to invoke the function after such throw, even if it's assigned.

If anyone have any idea on how this can be solved, please let us know here.

Opt out of monkey patching .NET internals

We are currently patching .NET's and Emscripten's JavaScript modules to make them compatible with JavaScript runtimes and bundlers:

File.WriteAllText(dotnet, File.ReadAllText(dotnet, Encoding.UTF8)
.Replace("import.meta.url", url)
.Replace("import(", "import(/*@vite-ignore*//*webpackIgnore:true*/"), Encoding.UTF8);
File.WriteAllText(runtime, File.ReadAllText(runtime, Encoding.UTF8)
.Replace("pt('WebAssembly resource does not have the expected content type \"application/wasm\", so falling back to slower ArrayBuffer instantiation.')", "true")
.Replace("import(", "import(/*@vite-ignore*//*webpackIgnore:true*/"), Encoding.UTF8);
File.WriteAllText(native, File.ReadAllText(native, Encoding.UTF8)
.Replace("var _scriptDir = import.meta.url", "var _scriptDir = \"file:/\"")
.Replace("require('url').fileURLToPath(new URL('./', import.meta.url))", "\"./\"")
.Replace("require(\"url\").fileURLToPath(new URL(\"./\",import.meta.url))", "\"./\"") // when aggressive trimming enabled
.Replace("new URL('dotnet.native.wasm', import.meta.url).href", "\"file:/\"")
.Replace("new URL(\"dotnet.native.wasm\",import.meta.url).href", "\"file:/\"") // when aggressive trimming enabled
.Replace("import.meta.url", url)
.Replace("import(", "import(/*@vite-ignore*//*webpackIgnore:true*/"), Encoding.UTF8);

— this is fragile and will most likely break with each new .NET release and/or WASM workload update.

Hopefully, .NET will at some point ensure the modules are compatible out of the box, so we won't have to do this:

Don't serialize `Task<[]>` of supported colletion items

.NET's JS interop currently is only able to marshal types with single-level nesting:

— hence we're serializing Task<byte[]> into JSON string, which is very ineffective for binary files content.

public static bool ShouldSerialize (Type type)
{
if (IsVoid(type)) return false;
if (IsTaskWithResult(type, out var result))
// TODO: Remove IsList (eg, serialization of Task<byte[]>) when https://github.com/dotnet/runtime/issues/81348 is resolved.
return IsList(result) || ShouldSerialize(result);

Either wait for .NET to support deeper nesting or figure alternative path.

Workaround until this is solved

Wrap the call into two: first to wait for the async operation, second to get the array in blocking manner, eg:

[JSFunction] Task OpenFile (string uri);
[JSFunction] byte[] GetOpenFileContent (string uri);

Task<byte[]> ReadFile (string uri)
{
    await OpenFile(uri);
    return GetOpenFileContent(uri);
}

Implement strong entropy shim for non-browser environments

Starting with .NET 6 the runtime uses native platform API to generate random numbers: https://docs.microsoft.com/en-us/dotnet/api/system.guid.newguid?view=net-6.0#remarks

In browser crypto.getRandomValues is utilized by the runtime and for other environments we are using a naive Math.random shim at the moment: https://github.com/Elringus/DotNetJS/blob/97647de97a9ffe0aafdc1941a947f79f9816b9d8/JavaScript/dotnet-runtime/src/mono.ts#L39

As mentioned in the documentation, the function does not provide cryptographically secure random numbers.

While it's probably not possible (?) to implement proper strong entropy generation without access to the native APIs, we have to at least improve current naive solution.

Related: #2

Access local file system from .Net

I'm working on a VS Code extension and writing the base implementation in .Net and calling from the extension in Typescript. I'm having trouble accessing local files from the .Net code using System.IO.* API. E.g. calling File.Exists() on a local Windows file path returns false for a valid path. I'm guessing that I don't have permission to access the file from the running process or the path needs to be modified. Any ideas?

Support typedefs for nullable collection elements

Currently, we can detect when a property, method argument and method return type is explicitly marked as nullable (either Nullable<> or has ? in nullable context) and generate corresponding TypeScript declaration with either ? or undefined modifier:

https://github.com/Elringus/DotNetJS/blob/456c1b5af62e88dddcc1215767ce2922529d9f52/DotNet/Packer/Utilities/TypeUtilities.cs#L43-L63

However, I'm not sure if it's possible to detect when a collection element type has nullable modifier, eg string?[] or List<string?>. NullabilityInfoContext doesn't seem to have a way to detect that.

If anyone have any idea how to infer nullability of collection and array element types, please let us know here.

Emit documentation

Explore an option to include C# XML docs (when present) to the generated TS type declarations.

Guid.NewGuid() throws Exception

Hey, thank you for this awesome project! I'm currently trying to get some existing .NET code to run in a VS Code web extension, so it came just at the right time for me, too.

I got the basics to work, however a call to Guid.NewGuid() results in an exception:

System.AggregateException: AggregateException_ctor_DefaultMessage (Arg_CryptographyException)
 ---> System.Security.Cryptography.CryptographicException: Arg_CryptographyException
   at Interop.GetCryptographicallySecureRandomBytes(Byte* , Int32 )
   at System.Guid.NewGuid()
   at Program.Main(String[] args)
   Exception_EndOfInnerExceptionStack

Do you have any idea why this happens and how it can be prevented? It works in a normal Blazor app.
It might be related to this change:

On non-Windows platforms, starting with .NET 6, this function calls the OS's underlying cryptographically secure pseudo-random number generator (CSPRNG) to generate 122 bits of strong entropy. In previous versions of .NET, the entropy is not guaranteed to be generated by a CSPRNG.

Incorrect emitted TS declaration

Hi! Thanks for the great library!

Since CodeGen was merged, I switched to the emitted declaration files instead of using manually created ones based on the source code, but the issue is, the nesting is wrong!

Right now the declarations are emitted like this:

export declare const dotnet: {
    BannerlordModuleManager: { }; // The C# bindings
    BootStatus: typeof BootStatus;
    ...
};

But dotnet.js exposes the variable via exports.BannerlordModuleManager instead of exports.dotnet.BannerlordModuleManager
So the correct declaration would be this:

export declare const BannerlordModuleManager: { }; // The C# bindings
export declare const dotnet: {
    BootStatus: typeof BootStatus;
    ...
};

PS:
Writing it here because Discussions aren't setup. Since we both are from Russia, I can't setup PayPal donations via Ko-Fi because Russia<-->Russia transactions are banned. Do you know any way to bypass the restriction?

Error on build of sample

The Sample project throws an error if published:

$ git clone https://github.com/Elringus/DotNetJS.git
$ cd DotNetJS/Samples/HelloWorld/
$ dotnet publish
Microsoft (R) Build Engine version 17.0.0+c9eb9dd64 for .NET
Copyright (C) Microsoft Corporation. All rights reserved.

  Determining projects to restore...
  Restored /workspaces/DotNetJS/Samples/HelloWorld/Project/HelloWorld.csproj (in 428 ms).
CSC : warning CS8032: An instance of analyzer Generator.SourceGenerator cannot be created from /var/nuget/dotnetjs/0.9.1/analyzers/dotnet/cs/Generator.dll : Could not load file or assembly 'Microsoft.CodeAnalysis, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'. The system cannot find the file specified.. [/workspaces/DotNetJS/Samples/HelloWorld/Project/HelloWorld.csproj]
/workspaces/DotNetJS/Samples/HelloWorld/Project/Program.cs(19,34): error CS8795: Partial method 'Program.GetHostName()' must have an implementation part because it has accessibility modifiers. [/workspaces/DotNetJS/Samples/HelloWorld/Project/HelloWorld.csproj]
dotnet --version
6.0.100

Improper generated enum values

Hi @elringus ,

There is a problem with enum values handling in the bootsharp. Let's say, I have the following backend:

public enum Foo
{
	Value1 = 1,
	
	Value2 = 2
}

public static partial class Program
{
	public static void Main ()
	{
	}

	[JSInvokable]
	public static Foo Test() => Foo.Value1;
}

It generates the following definitions:

const Global = {
    ...
    test: () => deserialize(getExports().Program.Test()),
    Foo: { "0": "Value1", "1": "Value2", "Value1": 0, "Value2": 1 }
};

So

console.log(Global.test() === Global.Foo.Value1);

prints false

Thank you!

Support ES module import in browser

Hi again,

I tested the library with angular and it is running into an error:

universalModuleDefinition:1 Uncaught ReferenceError: global is not defined
    at universalModuleDefinition:1:1
    at Object.9424 (universalModuleDefinition:1:1)
    at __webpack_require__ (bootstrap:19:1)
    at Module.1896 (home.component.html:14:263)
    at __webpack_require__ (bootstrap:19:1)
    at Module.721 (universalModuleDefinition:1:1)
    at __webpack_require__ (bootstrap:19:1)
    at Module.23 (app.component.html:6:8)
    at __webpack_require__ (bootstrap:19:1)
    at Module.8835 (environment.ts:16:71)

Which is expected as the angular team came to the conclusion to not support global in their webpack build for broad compatibility. angular/angular-cli#9827 (comment)

I think this library should not rely on some other process to supply the variable.
So going forward you could use:

Workaround is adding (window as any).global = window; to polyfill.ts

Error whili wrapping MozJpegSharp

Hi, thanks for this great project! I am trying to implement MozJpegSharp image compression based on the HelloWorld example from the ReadMe you provided, and I'm getting an error. Here are the relevant files:

using System;
using DotNetJS;
using Microsoft.JSInterop;
using MozJpegSharp;

namespace ImageCompression;

public partial class Program
{
    // Entry point is invoked by the JavaScript runtime on boot.
    public static void Main()
    {
        var hostName = GetHostName();
        Console.WriteLine($"Hello {hostName}, DotNet here!");
    }

    [JSInvokable]
    public static string CompressBase64(string base64, int quality)
    {
        var bytes = Helpers.Base64ToByteArray(base64);
        var result = MozJpeg.Recompress(bytes.AsSpan(), quality, TJSubsamplingOption.Chrominance420, TJFlags.None);
        return Helpers.ByteArrayToBase64(result);
    }
}

ImageCommpression.csproj


<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

    <PropertyGroup>
        <TargetFramework>net6.0</TargetFramework>
        <EmitSourceMap>true</EmitSourceMap>
        <EmitTypes>true</EmitTypes>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="DotNetJS" Version="0.3.12" />
        <PackageReference Include="MozJpegSharp" Version="2.1.12" />
        <PackageReference Include="MozJpegSharp.GdiPlus" Version="2.1.12" />
    </ItemGroup>

</Project>

JS function

<script src="bin/dotnet.js"></script>

<script>
	dotnet.ImageCompression.GetHostName = () => "Browser";

	window.onload = async function () {

		await dotnet.boot();
                var data = "<SOME BASE64 IMAGE>";
                const compressed = dotnet.ImageCompression.CompressBase64(data, 85);
		console.log(`Image: , ${compressed}!`);
	};
</script>

I ran dotnet publish to create the dotnet.js file and I am able to import the module, but I receive an error in console:

Uncaught (in promise) Error: System.DllNotFoundException: turbojpeg

When I run same program as a console application everything is working fine.
Do you have any ideas what might be a problem?

Interplatform compatibility?

Discussed in #133

Originally posted by CADBIMDeveloper January 6, 2024
Hi @elringus ,

Seems, I'm a bit annoying last days, sorry about that :-(

I faced an issue when the code generated by bootsharp on Windows was not working on Linux raising the following error:

MONO_WASM: The argument 'filename' must be a file URL object, file URL string, or absolute path string. Received 'file://dotnet.native.wasm'
TypeError [ERR_INVALID_ARG_VALUE]: The argument 'filename' must be a file URL object, file URL string, or absolute path string. Received 'file://dotnet.native.wasm'
    at new NodeError (node:internal/errors:405:5)
    at Module.createRequire (node:internal/modules/cjs/loader:1493:13)
   ...

I believe it happens because different code is generated on Windows and Linux machines:

On Windows:

...
.then((e=>e.createRequire("file://dotnet.native.wasm")))
...

On Linux:

...
.then((e=>e.createRequire("file:///dotnet.native.wasm")))
...

I solved it generating separated versions on Windows and Linux.

I'm not sure if it can be fixed, it's probably came from browser-wasm workload. What do you think? Thank you!

Error running HelloWorld example

Hi, thank you so much for creating this helpful project! I am trying to implement the simple HelloWorld example from the ReadMe in my Typescript project, and I'm getting an error. Here are the relevant files:

HelloWorld.cs:

using System;
using DotNetJS;
using Microsoft.JSInterop;

namespace HelloWorld
{

    class Program
    {
        public static void Main ()
        {
            var hostName = JS.Invoke<string>("GetName");
            Console.WriteLine($"Hello {hostName}, DotNet here!");
        }

        [JSInvokable]
        public static string GetName () => "DotNet";
    }
}

HelloWorld.csproj:

<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

    <PropertyGroup>
        <TargetFramework>net6.0</TargetFramework>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="DotNetJS" Version="*"/>
    </ItemGroup>

</Project>

MyTsFunction:

function myTsFunction(): string {
    const dotnet = require("./bin/dotnet");

    dotnet.HelloWorld.GetHostName = () => "Node.js";

    (async function () {
        await dotnet.boot();

        const guestName = dotnet.HelloWorld.GetName();
        console.log(`Welcome, ${guestName}! Enjoy your module space.`);
    })();

    return "";
}

I ran dotnet publish to create the dotnet.js file and I am able to import the module, but there is an error on the " dotnet.HelloWorld.GetHostName = () => "Node.js"; " line:

Cannot set properties of undefined (setting 'GetName')

Do you have any ideas on how I could fix this?

Export UMD and ESM

To use this library with Deno, ESM would be required as Deno does not support UMD.

And the easiest thing would be just to export also a dotnet.mjs file (with esm code inside).

Can we debug the C# code?

After generating the WASM and such, is it possible to attach the C# debugger to it? Is there some docs available for that?

Visual Studio Build Error

Just hope that maybe somebody else came across the same error:

error MSB4062: The "Bootsharp.Publish.BootsharpEmit" task could not be loaded from the assembly C:\Users\dnhb\.nuget\packages\bootsharp\0.1.3\build\..\tasks\Bootsharp.Publish.dll. Could not load file or assembly 'System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified. Confirm that the <UsingTask> declaration is correct, that the assembly and all its dependencies are available, and that the task contains a public class that implements Microsoft.Build.Framework.ITask.
4>Done building target "BootsharpEmit" in project "Project.csproj" -- FAILED

dotnet build is running just fine. Only VS gives problems.

`boot()` function typings are not matching with `<EmbedBinaries>false</EmbedBinaries>`

If the project gets published with <EmbedBinaries>false</EmbedBinaries> the generated dotnet.d.ts file does not match the requested function signature.

It is generating this line:

export declare function boot(): Promise<void>;

instead of:

export declare function boot(bootData: BootData): Promise<void>;

Seems to be about this line:
https://github.com/Elringus/DotNetJS/blob/4c7235770c4b78a43276b86b52e195f0903a23a0/DotNet/Packer/DeclarationGenerator/DeclarationGenerator.cs#L69
I don't know when it gets called.

Why does typescript definitions render any?

I have the following C# code:

[JSInvokable]
    public static EquationSystem.SolveResult Move(IEntity entity, Vector3 pos)
    {
        var editor = GetEditor();
        var moveTool = new MoveTool();
        return moveTool.Move(entity, editor.ActiveFeature, pos);
    }

Vector3 is a struct from System.Numerics.Vectors.dll.

This renders the following typescript:

export namespace Bindings {
    export function Move(entity: Bindings.IEntity, pos: any): Bindings.SolveResult;
}

Do you know why pos is of type any?

Migrate to stock .NET runtime

We are currently using a custom .NET runtime build and JS wrapper post-processing to support webpack UMD target. This limits the options when building the projects, such as AOT and module trimming.

There is an on-going work at the .NET runtime main branch to modularize the WASM runtime and make it work outside of browser, eg:
dotnet/runtime#61313
dotnet/runtime#62292
dotnet/runtime#47336

At some point, it may become possible to use the module distributed with the runtime SDK, which will resolve the limitations.

If someone happen to know an alternative solution to make current (.NET 6) WASM compatible with UMD target, please let us know here. Sharing updates regarding changes in the .NET runtime that could help with the issue is also appreciated.


Currently blocked by:

Bundled Javascript module errors due to strict mode

Hi!

I found that after bundling, the created module does not work (dotnet.boot() crashes). This is due to the usage of undeclared identifiers assigned in certain functions. This shouldn't be a problem, but since some bundlers seem to tack on 'use strict'; where they shouldn't, it becomes an issue, and fixing it shouldn't cause any problems.

The offending functions are:

_js_to_mono_obj : result is undeclared
bind_method : bodyJs is underclared
_handle_exception_and_produce_result_for_call : result is undeclared

TS Enum declarations loose values

Hi @elringus ,

There is a problem with exposing .Net enums to the TypeScript. For example, I have an enum in C# code like:

public enum Severity
{
    Warning = 1,

    Error = 2
}

Values are missed when it's translated into TS like:

export enum Severity {
	Warning,
	Error
}

Moreover, when I try to pass this like that:

dotnet.Something.someFunction({ severety: SomeNamespace.Severity.Error });

it fails with serialization exception, so I have to pass it like

dotnet.Something.someFunction({ severety: 2 });

I can try to prepare a PR to fix that, but want to discuss the details first

Solve the non-entry assembly loading mystery

Given there are multiple assemblies that require JavaScript interop (contain[JSInvokable] or [JSFunction] attributed methods), only the entry assembly is loaded, while when trying to invoke methods from the others, Assembly not loaded exception is thrown.

We are currently using following hack to force-load such assemblies:
https://github.com/Elringus/DotNetJS/blob/97647de97a9ffe0aafdc1941a947f79f9816b9d8/JavaScript/dotnet-runtime/test/csharp/Test.Main/Program.cs#L31

If the hack is removed, the JS tests that use methods from Test.Types assembly will fail.

We have to figure if that's by design and/or find a more elegant way to force-load the assemblies.

If someone have any related info, please let us know here.

Support generic function parameters

The following code leads to an invalid TypeScript type definition:

namespace Repro;

public class GenericType<T>
{
    public T Value { get; set; }
}

public static partial class Program
{
    public static void Main()
    {
    }

    [JSInvokable]
    public static void Function(GenericType<string> param)
    {
    }
}

This generates the following definitions in dotnet.d.ts:

export namespace Repro {
    export class GenericType`1 {
        value?: string;
    }
}
export namespace Repro {
    export function Function(model: Repro.GenericType`1): void;
}

I think it should be possible to generate generic type definitions. If it's not, however, a mechanism that would allow me to specify that a given type should just be typed as any would also work for me. First I thought about an attribute, but that wouldn't work for third party types, so it's probably not the best solution.

Detect when to emit nullable defines in auto-generated C# files

Due to #68 compiler now require #nullable enable defines when nullable reference type annotations are used in the auto-generated files.

I'm not aware of a reliable way to detect whether a nullable annotation is used in the analyzed syntax tree, so the generators always emit #nullable defines (#71), which could cause issues when the consumer doesn't have nullable reference types enabled.

If anyone have any idea how to detect whether the nullable define should be emitted (eg, by checking whether a nullable annotation is used anywhere in the analyzed syntax tree):

https://github.com/Elringus/DotNetJS/blob/1c11d8c8ac71d786fde85dad82f3c45a16f07f9a/DotNet/Generator/GeneratedClass.cs#L19-L21

— please let us know here.

Non Program methods not generated

I've noticed that methods are not generated for classes other than Program. So, if for example Program has a method that returns an object of type Foo, and Foo has a method Bar(), Bar() isn't generated for the typescript definition.

Is that by design or am I doing something wrong?

Consolidate namespace override mechanism

Currently, it's required to apply [JSNamespace] attribute to each assembly that has JS bindings and to the entry assembly.

That is because source generator gets the attribute from the assembly it's currently generating code for:

https://github.com/Elringus/DotNetJS/blob/cad9a87854fd860203d25991a55bb3a68fa11229/DotNet/Generator/GeneratedMethod.cs#L32

While packer gets the attribute from the entry assembly and applies the rules for all the other assemblies:

https://github.com/Elringus/DotNetJS/blob/cad9a87854fd860203d25991a55bb3a68fa11229/DotNet/Packer/PublishDotNetJS.cs#L64

Ideally, we have to find a way to make generator get the attribute from the entry assembly (if that's possible) or make packer resolve the attributes for each assembly individually to at least prevent cases when generated C# bindings are not in sync with JS counterparts.

Marshal via arrays

Instead of JSON, explore marshaling via arrays, similar to Embind (https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#value-types), eg:

public record Record (string Str, int Int, bool Bool, Record? Other);

[JSExport]
private static void ReceiveRecord ([JSMarshalAs<JSType.Array<JSType.Any>>] object?[] raw)
{
    var record = Unmarshal(raw);
    Console.WriteLine($"Record({record.Str}, {record.Int}, {record.Bool}, 
        Record({record.Other?.Str}, {record.Other?.Int}, {record.Other?.Bool}))");

    static Record Unmarshal (object?[] raw) => new(
        (string)raw[0]!,
        (int)(double)raw[1]!,
        (bool)raw[2]!,
        raw[3] != null ? Unmarshal(raw[3..7]) : null
    );
}
exports.Program.ReceiveRecord(["foo", 1, true, "bar", 2, false, null]);

Should probably be postponed until #138 is solved; otherwise, we'll break interop with tasks of custom data types, that are currently serialized to JSON.

Can I use this repo to call NODE.JS with NPM package from C# Winforms App?

Hello:
I want to know if I can find how can I call some Node.js script from C#, which call a npm package called ccxt, and return the truncated strings to C#. And one step further, how can I call similar Node.js script and pass some parameter and get the results back to C#.
My OS: Window 10 Pro (version 21H2)
Node.js: version: 19.0.0
CCXT: version: 2.1.33

The following is node.js script:
D:\nodejs\CCXT_Exchanges>type ccxt_all_exchanges.js
'use strict';
const ccxt = require('ccxt');
console.log(ccxt.version);
var all_exchanges = ccxt.exchanges
console.log(all_exchanges)
D:\nodejs\CCXT_Exchanges>
The output for running the node.js script:
D:\nodejs\CCXT_Exchanges>node ccxt_all_exchanges.js
2.1.33
[
'aax', 'alpaca', 'ascendex',
'bequant', 'bibox', 'bigone',
'binance', 'binancecoinm', 'binanceus',
'binanceusdm', 'bit2c', 'bitbank',
'bitbay', 'bitbns', 'bitcoincom',
'bitfinex', 'bitfinex2', 'bitflyer',
'bitforex', 'bitget', 'bithumb',
'bitmart', 'bitmex', 'bitopro',
'bitpanda', 'bitrue', 'bitso',
'bitstamp', 'bitstamp1', 'bittrex',
'bitvavo', 'bkex', 'bl3p',
'blockchaincom', 'btcalpha', 'btcbox',
'btcex', 'btcmarkets', 'btctradeua',
'btcturk', 'buda', 'bw',
'bybit', 'bytetrade', 'cex',
'coinbase', 'coinbaseprime', 'coinbasepro',
'coincheck', 'coinex', 'coinfalcon',
'coinmate', 'coinone', 'coinspot',
'crex24', 'cryptocom', 'currencycom',
'delta', 'deribit', 'digifinex',
'exmo', 'flowbtc', 'fmfwio',
'ftx', 'ftxus', 'gate',
'gateio', 'gemini', 'hitbtc',
'hitbtc3', 'hollaex', 'huobi',
'huobijp', 'huobipro', 'idex',
'independentreserve', 'indodax', 'itbit',
'kraken', 'kucoin', 'kucoinfutures',
'kuna', 'latoken', 'lbank',
'lbank2', 'liquid', 'luno',
'lykke', 'mercado', 'mexc',
'mexc3', 'ndax', 'novadax',
'oceanex', 'okcoin', 'okex',
'okex5', 'okx', 'paymium',
'phemex',
... 20 more items
]

D:\nodejs\CCXT_Exchanges>
The issue here is that console.log only shows the first 100 items, there are 20 more items will be returned. I want to use ClearScript to run the above node.js script and return all 120 items to C#.
How can I do this?

One step further, can I pass variable to async function using CCXT?

D:\nodejs\CCXT_Binance>type Binance1PairOrderBook.js
const ccxt = require('ccxt');
console.log(ccxt.version);

if (process.argv.length != 3)
{
console.error('Usage: BTC/USDT');
console.error('Please try again!');
process.exit(1);
}
const [nodejs, script1, pair1] = process.argv;

let exchange = new ccxt.binance
({
'adjustForTimeDifference': true,
defaultType: 'spot',
'verbose': false
});

const pair1OrderBook = async (pair1) => {
try
{
await exchange.loadMarkets();
const orderbook1 = await exchange.fetchOrderBook(pair1);
if (typeof orderbook1 !== 'undefined')
{
const json_orderbook = JSON.stringify(orderbook1);
console.log(json_orderbook);
}
else
{
const obj_orderbook0 = ({ 'Symbol': pair1, 'Data': [] });
const json_orderbook0 = JSON.stringify(obj_orderbook0);
console.log(json_orderbook0);
}
}
catch (err)
{
console.error(err)
}
};
pair1OrderBook(pair1);

D:\nodejs\CCXT_Binance>

To use this code, call this with one cryptocurrency trading pair, the biggest pair is: BTC/USDT, but the output contains too many rows, so calling it with not very active pairs, like this:
D:\nodejs\CCXT_Binance>node Binance1PairOrderBook.js FXS/USDT

Or some pairs, which do exist, but no trade volume here: ETH/USDC, like the following:
D:\nodejs\CCXT_Binance>node Binance1PairOrderBook.js ETH/USDC
2.1.33
(node:22912) ExperimentalWarning: The Fetch API is an experimental feature. This feature could change at any time
(Use node --trace-warnings ... to show where the warning was created)
{"symbol":"ETH/USDC","bids":[],"asks":[],"nonce":2090539859}

D:\nodejs\CCXT_Binance>

Please advise on if I can use the repo to do the job: call Node.js from C# WinForms App?
Thanks,

Error when publishing to custom output path

Publishing with -o, --output <OUTPUT_DIR> option breaks the PublishDotNetJS task.

<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
	<PropertyGroup>
		<OutputType>Exe</OutputType>
		<TargetFramework>net6.0</TargetFramework>
		<ImplicitUsings>enable</ImplicitUsings>
		<Nullable>enable</Nullable>
	</PropertyGroup>
	<ItemGroup>
		<PackageReference Include="DotNetJS" Version="0.23.1" />
	</ItemGroup>
</Project>
using Microsoft.JSInterop;

Console.WriteLine("Hello, World!");

[JSInvokable]
static string Test(string value) => value;
 dotnet publish -c "Release" -o "D:\ExampleApp\.deploy\test"
...\DotNetJS.targets(23,9): error MSB4018: The "PublishDotNetJS" task failed unexpectedly.
...\DotNetJS.targets(23,9): error MSB4018: System.IO.DirectoryNotFoundException: Could not find a part of the path 'D:\ExampleApp\ExampleApp\bin\Release\net6.0\publish\wwwroot\_framework'.
...\DotNetJS.targets(23,9): error MSB4018:    at System.IO.Enumeration.FileSystemEnumerator`1.CreateDirectoryHandle(String path, Boolean ignoreNotFound)
...\DotNetJS.targets(23,9): error MSB4018:    at System.IO.Enumeration.FileSystemEnumerator`1.Init()
...\DotNetJS.targets(23,9): error MSB4018:    at System.IO.Enumeration.FileSystemEnumerable`1..ctor(String directory, FindTransform transform, EnumerationOptions options, Boolean isNormalized)
...\DotNetJS.targets(23,9): error MSB4018:    at System.IO.Enumeration.FileSystemEnumerableFactory.UserFiles(String directory, String expression, EnumerationOptions options)
...\DotNetJS.targets(23,9): error MSB4018:    at System.IO.Directory.InternalEnumeratePaths(String path, String searchPattern, SearchTargetsearchTarget, EnumerationOptions options)
...\DotNetJS.targets(23,9): error MSB4018:    at System.IO.Directory.GetFiles(String path, String searchPattern, EnumerationOptions enumerationOptions)
...\DotNetJS.targets(23,9): error MSB4018:    at Packer.TypeUtilities.CreateLoadContext(String directory)
...\DotNetJS.targets(23,9): error MSB4018:    at Packer.NamespaceBuilder.CollectConverters(String outDir, String entryAssembly)
...\DotNetJS.targets(23,9): error MSB4018:    at Packer.PublishDotNetJS.CreateNamespaceBuilder()
...\DotNetJS.targets(23,9): error MSB4018:    at Packer.PublishDotNetJS.GenerateSources()
...\DotNetJS.targets(23,9): error MSB4018:    at Packer.PublishDotNetJS.Execute()
...\DotNetJS.targets(23,9): error MSB4018:    at Microsoft.Build.BackEnd.TaskExecutionHost.Microsoft.Build.BackEnd.ITaskExecutionHost.Execute()
...\DotNetJS.targets(23,9): error MSB4018:    at Microsoft.Build.BackEnd.TaskBuilder.ExecuteInstantiatedTask

Does this work on MacOS?

When I do dotnet publish for the Hello World sample on Window, everything works, and the UMD library is published. When I run it on a Mac, I get error CS8795: Partial method 'Program.GetHostName()' must have an implementation part because it has accessibility modifiers.

Support .NET -> JavaScript streaming

TypeScript declarations for IJSObjectReference

Anything that uses IJSObjectReference is set as any in the declaration files.
We could introduce an optional attribute JSObjectInterface that would add the necessary metadata for the declarations generator to expose the type correctly.

public sealed class JSObjectInterfaceAttribute : Attribute
{
    public JSInterfaceAttribute (Type type) { }
}

public interface IMyInterface
{
    void Method (string str);
}

public class Container
{
    [JSInterface(typeof(IMyInterface))]
    public IJSObjectReference Prop { get; set }

    public void Test ([param: JSObjectInterface(typeof(IMyInterface))] IJSObjectReference @param) { }
}

So we could have TS declarations in the following format

export interface IMyInterface {
    Method(str: string): void;
}

Another way would be to introduce generic interfaces that implement IJSObjectReference, but there are a lot of flavors, like IJSUnmarshalledObjectReference, IJSInProcessObjectReference, so while it adds some type safety for C#, the maintenance of those higher interfaces might be very costly.

Error in _framework folder location

Severity Code Description Project File Line Suppression State
Error The "PublishDotNetJS" task failed unexpectedly.
System.IO.DirectoryNotFoundException: Could not find a part of the path '...\bin\Release\net6.0\publish\wwwroot_framework'.
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.FileSystemEnumerableIterator1.CommonInit() at System.IO.FileSystemEnumerableIterator1..ctor(String path, String originalUserPath, String searchPattern, SearchOption searchOption, SearchResultHandler`1 resultHandler, Boolean checkHost)
at System.IO.Directory.GetFiles(String path, String searchPattern)
at DotNetJS.Packer.ProjectMetadata.LoadAssemblies(String directory)
at DotNetJS.Packer.PublishDotNetJS.Execute()
at Microsoft.Build.BackEnd.TaskExecutionHost.Microsoft.Build.BackEnd.ITaskExecutionHost.Execute()
at Microsoft.Build.BackEnd.TaskBuilder.d__26.MoveNext() vNextDashboard.Pathways 0

The folder is not:
....\bin\Release\net6.0\publish\wwwroot_framework
It's
...\bin\Release\net6.0\wwwroot_framework

I have not found a place to set a different location.

Sample/React. Cannot run the process on a background thread

I ran the Sample/React project and tried to keep the donut from lagging under stress. Enabling CreateWorker didn't work for me :/ After which I saw your comment: "After some testing I've figured worker interaction layer adds too much complexity and makes debugging harder. It'll make more sense and will probably be more performant to use threading on C# side (#79)."

So I tried using Task on C# side.

        public async Task StartStress()
        {
            cts?.Cancel();
            cts = new CancellationTokenSource();
            await Task.Run(() => Stress(cts.Token));
        }

        private async Task Stress(CancellationToken token)
        {
            while (!token.IsCancellationRequested)
            {
                var time = DateTime.Now;
                await Task.Run(() => ComputePrime(frontend.GetStressPower()), token);
                frontend.NotifyStressComplete((DateTime.Now - time).Milliseconds);
                await Task.Delay(1, token);
            }
        }   

I'm not strong in multithreading, but I thought this should work. Understandably, it didn't work

How can I run the process on a background thread? is it possible now? I will be very grateful for the answer

How does the bootData section work for web?

Hi,
I tried implementing the bootData section for your example project:

    // Providing implementation for 'GetHostName' function declared in 'HelloWorld' C# assembly.
    dotnet.HelloWorld.GetHostName = () => "Browser";

    async function loadfile(path) {
        response = await fetch(path);
        // Examine the text in the response
        buffer = await response.arrayBuffer();
        return Base64.fromUint8Array(new Uint8Array(buffer));
    }

    window.onload = async function () {
        allfiles = [
            "Microsoft.JSInterop.WebAssembly.dll",
            "System.Collections.Concurrent.dll",
            "System.Private.Runtime.InteropServices.JavaScript.dll",
            "System.Collections.dll",
            "Microsoft.AspNetCore.Components.Web.dll",
            "System.Memory.dll",
            "Microsoft.JSInterop.dll",
            "Microsoft.AspNetCore.Components.WebAssembly.dll",
            "System.Runtime.dll",
            "System.Text.Json.dll",
            "Microsoft.AspNetCore.Components.dll",
            "System.Private.Uri.dll",
            "System.Private.CoreLib.dll",
            "System.Runtime.CompilerServices.Unsafe.dll",
            "System.Console.dll",
            "DotNetJS.dll",
            "System.Text.Encodings.Web.dll",
            "HelloWorld.dll",
        ];
        assemblies = [];
        for (let i = 0; i < allfiles.length; i++) {
            assemblies.push({
                name: allfiles[i],
                data: await loadfile("Project/bin/Debug/net6.0/" + allfiles[i]),
            });
        }
        // Booting the DotNet runtime and invoking entry point.
        const bootData = {
            wasm: await loadfile("Project/bin/Debug/net6.0/dotnet.wasm"),
            assemblies: assemblies,
            entryAssemblyName: "HelloWorld.dll",
        };
        await dotnet.boot(bootData);
        // Invoking 'GetName()' C# method defined in 'HelloWorld' assembly.
        const guestName = dotnet.HelloWorld.GetName();
        console.log(`Welcome, ${guestName}! Enjoy your global space.`);
    };

But I always get this error, any idea why?

dotnet.js:488 Uncaught (in promise) RuntimeError: abort(TypeError: WebAssembly.instantiate(): Import #0 module="a" error: module is not an object or function). Build with -s ASSERTIONS=1 for more info.
    at abort (dotnet.js:488:25)
    at dotnet.js:542:25

Remark:
I also tried the Project/bin/Debug/net6.0/publish/wwwroot/_framework/ Path.

Git rid of eval() in WASM wrapper

Hi again!
I used DotNetJS to create a library that exposes some common functions that we share across C# executables and an Electron app to ensure the behaviour is identical.
The problem is, the electron's app (Vortex) is blocking eval execution due to it's CSP. I did earlier some investigation about this, but I would like to double check! Is eval critical for the runtime to work or is it used only for debugging? Could we strip it's usage with some conditionals?

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.