yantrajs / yantra

JavaScript Engine for .NET Standard
https://yantrajs.com
Apache License 2.0
222 stars 7 forks source link

How to pass ClrType to module for create instance in script #51

Closed ZverGuy closed 2 years ago

ZverGuy commented 2 years ago

Hello again i am trying to implement "moduleBuilder" like jint

   _context.RegisterModule("test", builder =>
            {
                builder.ExportType<TestClass>()
                    .ExportFunction("testFunc", TestMethod);
            } );

And when i try to create TestClass instance

 var barfromclass = await _context
                .RunScriptAsync("import {TestClass} from \"test\"\n export default new TestClass();",
                    String.Empty) as JSObject; 
            Assert.AreEqual("bar", barfromclass["Foo"].InvokeFunction(new Arguments()));

I am get exception

YantraJS.Core.JSException: undefined is not a function 

TestClass implementation

 public class TestClass
    {
        public string Foo() => "bar";
    }

ModuleBuilder implementation

public class ModuleBuilder
{
    private readonly KeyString _moduleName;
    private readonly JSModuleContext _moduleContext;
    private readonly JSObject _exportobj;

    internal ModuleBuilder(KeyString moduleName,JSModuleContext moduleContext)
    {
        _moduleName = moduleName;
        _moduleContext = moduleContext;
        _exportobj = new JSObject();
    }

    public ModuleBuilder ExportType<T>()
    {
        string name = typeof(T).Name;
        var type = ClrType.From(typeof(T));
        _exportobj[name] = type;
        return this;
    }

    public ModuleBuilder ExportValue(in KeyString name, object value)
    {
        _exportobj[name] = value.Marshal();
        return this;
    }

    public ModuleBuilder ExportFunction(in KeyString name, JSFunctionDelegate func)
    {
        _exportobj[name] = new JSFunction(func);
        return this;
    }

    internal void Build()
    {
        _moduleContext.RegisterModule(_moduleName, _exportobj);
    }

/// in JSModuleContext

 public void RegisterModule(in KeyString name, Action<ModuleBuilder> builder)
        {
            var modulebuilder = new ModuleBuilder(name ,this);
            builder.Invoke(modulebuilder);
            modulebuilder.Build();
        }

Maybe you can help what i do wrong?

upd some intresting information

ClrType return function() { [clr-native]}

изображение

On runing this return a VoidValueTask изображение

When i call testFunc() this is called but dont return value

var barfromclass = await _context
                .RunScriptAsync("import {testFunc} from \"test\"\n export default testFunc();",
                    String.Empty);

изображение изображение

ZverGuy commented 2 years ago

https://github.com/ZverGuy/yantra/commits/main if you want test - check my repo

I really hope that you can help, because I do not know why this is happening

ackava commented 2 years ago

You can use,


context.RegisterModule("test", ClrType.From(typeof(YourClrClass));

You can make module builder as follow,


public class ModuleBuilder {

   private List<(string name,Type type)> exportedTypes = new ();

   public ModuleBuilder Export<T>(string name = null) {
         exportedTypes.Add((name ?? typeof(T).Name, typeof(T)));
         return this;
   }

   public JSModuleContext Builder(JSModuleContext context) {
        foreach(var (name,type) in exportedTypes) {
              var exports = new JSObject();
              // currently YantraJS does not support synthetic default imports
              // so we should add "default" property in exports
              exports[KeyStrings.@default] = ClrType.From(type);
              context.RegisterModule(name, exports);
        }
   }

}

public static class ModuleBuilderExtensions {

        public static JSModuleContext Build(this JSModuleContext context, Action<ModuleBuilder> builder) {
               var mb = new ModuleBuilder();
               builder(mb);
               return mb.Build(context);
        }

}
ZverGuy commented 2 years ago

Big thanks

expect my pull request soon

ZverGuy commented 2 years ago

Check my pull request

ackava commented 2 years ago

You forgot to set default. Please note, we haven't added support for synthetic default modules like node, so as per ES6 we need to set default on exports to support "import test from .... " syntax. We are working on synthetic default module support.

Instead of JSFunction, use JSFunctionDelegate as JSFunction needs to be created per JSContext instance. I have added following code, which works correclty.

public class ModuleBuilder {

   private List<(string name,object value)> exportedObjects = new ();

   public ModuleBuilder ExportType<T>(string name = null) {
         exportedObjects .Add((name ?? typeof(T).Name, typeof(T)));
         return this;
   }

   public ModuleBuilder Export(string name, object value) {
         exportedObjects .Add((name , value));
         return this;
   }

   public ModuleBuilder ExportFunction(string name, JSFunctionDelegate @delegate) {
         exportedObjects .Add((name , @delegate));
         return this;
   }

   public JSModuleContext Builder(JSModuleContext context) {
        foreach(var (name,value) in exportedTypes) {
              var exports = new JSObject();
              var r = JSUndefined.Value;
              switch(value) {
                   case Type type:
                         r = ClrType.From(type);
                         break;
                   case JSFunctionDelegate @delegate:
                         r = new JSFunction(name, @delegate);
                         break;
                   case JSValue jv:
                         r = jv;
                         break;
                   default:
                         r = ClrProxy.Marshal(value);
                         break;
              }

              // currently YantraJS does not support synthetic default imports
              // so we should add "default" property in exports
              exports[KeyStrings.@default] = r;
              context.RegisterModule(name, exports);
        }
   }

}

public static class ModuleBuilderExtensions {

        public static JSModuleContext Build(this JSModuleContext context, Action<ModuleBuilder> builder) {
               var mb = new ModuleBuilder();
               builder(mb);
               return mb.Build(context);
        }

}
ZverGuy commented 2 years ago

but there is a flaw in your method. each exported object has its own module. I want to do for example

import {TestClass, testFunc} from 'test'

But with your method i cant do that Only

import TestClass from 'TestClass'
import testFunc from 'testFunc'
ackava commented 2 years ago
public class ModuleBuilder {

   private readonly string moduleName;

   public ModuleBuilder(string moduleName) {
        this.moduleName = moduleName;
   }

   private List<(KeyString name,object value)> exportedObjects = new ();

   public ModuleBuilder ExportType<T>(string name = null) {
         exportedObjects .Add((name ?? typeof(T).Name, typeof(T)));
         return this;
   }

   public ModuleBuilder Export(string name, object value) {
         exportedObjects .Add((name , value));
         return this;
   }

   public ModuleBuilder ExportFunction(string name, JSFunctionDelegate @delegate) {
         exportedObjects .Add((name , @delegate));
         return this;
   }

   public JSModuleContext Builder(JSModuleContext context) {
        var exports = new JSObject();
        foreach(var (name,value) in exportedTypes) {
              var r = JSUndefined.Value;
              switch(value) {
                   case Type type:
                         r = ClrType.From(type);
                         break;
                   case JSFunctionDelegate @delegate:
                         r = new JSFunction(name, @delegate);
                         break;
                   case JSValue jv:
                         r = jv;
                         break;
                   default:
                         r = ClrProxy.Marshal(value);
                         break;
              }

              exports[name] = r;
        }
        context.RegisterModule(moduleName, exports);
   }

}

public static class ModuleBuilderExtensions {

        public static JSModuleContext Build(this JSModuleContext context string moduleName, Action<ModuleBuilder> builder) {
               var mb = new ModuleBuilder(moduleName);
               builder(mb);
               return mb.Build(context);
        }

}

Usage


   context.Build("moduleName", (mb) => {
        mb.ExportType<SomeClrType>();
        mb.ExportFunction("SomeFunction", (in Arguments a) => JSUndefined.Value);
   });
   import { SomeClrType, SomeFunction } from "moduleName";
ZverGuy commented 2 years ago

i made simillar Its ok? Can be merged? (Not used boolean and commented code i delete before merge)

public class ModuleBuilder
{
    private List<(string name,object value)> exportedObjects = new ();
    private string _moduleName;
    private readonly bool _enableSyntheticDefaultModules;

    public ModuleBuilder ExportType<T>()
    {
        exportedObjects.Add((typeof(T).Name, typeof(T)));
        return this;
    }

    public ModuleBuilder(string moduleName, bool enableSyntheticDefaultModules = false)
    {
        _moduleName = moduleName;
        _enableSyntheticDefaultModules = enableSyntheticDefaultModules;
    }

    public ModuleBuilder ExportType(Type type, string name = null)
    {
        exportedObjects.Add((type.Name, type));
        return this;
    }

    public ModuleBuilder ExportValue(string name, object value)
    {
        exportedObjects.Add((name, value.Marshal()));
        return this;
    }

    public ModuleBuilder ExportFunction(string name, JSFunctionDelegate func)
    {
        exportedObjects.Add((name, new JSFunction(func)));
        return this;
    }
    public void AddModuleToContext(JSModuleContext context)
    {
        JSObject globalExport = new JSObject();
        foreach ((string name, object value)  in exportedObjects)
        {
            // if (_enableSyntheticDefaultModules)
            // {
            //     //Used for make Sythetic default import work around
            //     JSObject localexport = new JSObject();
            //     localexport[KeyStrings.@default] =  value switch
            //     {
            //         Type type => ClrType.From(type),
            //         JSFunctionDelegate @delegate => new JSFunction(@delegate),
            //         JSValue jsValue => globalExport[name] = jsValue,
            //         _ => ClrProxy.Marshal(value)
            //     };
            //     context.RegisterModule(name, localexport);
            // }

            globalExport[name] = value switch
            {
                Type type => ClrType.From(type),
                JSFunctionDelegate @delegate => new JSFunction(@delegate),
                JSValue jsValue => globalExport[name] = jsValue,
                _ => ClrProxy.Marshal(value)
            };
        }
        context.RegisterModule(_moduleName, globalExport);

    }
}

In JSModuleContext

 public void CreateModule(string moduleName,bool enableSyntheticDefaultImport, Action<ModuleBuilder> builder)
        {
            var mb = new ModuleBuilder(moduleName, enableSyntheticDefaultImport);
            builder(mb); 
            mb.AddModuleToContext(this);
        }
ackava commented 2 years ago

Why can't you use ModuleBuilder in your own code? Since we haven't finalized api for synthetic defaults, we don't want to add ModuleBuilder now.

ZverGuy commented 2 years ago

Okay But curently i fix stuff with "synthetic default import" and its works (if i understand your issue)

ackava commented 2 years ago

@ZverGuy Your code will only work inside ModuleBuilder where else we are planning to support CSX modules and may be some other languages as well, so I need to understand the formal specification of ES6 and I want to implement it correct way.

ZverGuy commented 2 years ago

@ZverGuy Your code will only work inside ModuleBuilder where else we are planning to support CSX modules and may be some other languages as well, so I need to understand the formal specification of ES6 and I want to implement it correct way.

I move ModuleBuilder to extension methods and other package

I go to test modulebuilder with csx module loader for check if something is broken or not

ZverGuy commented 2 years ago

@ZverGuy Your code will only work inside ModuleBuilder where else we are planning to support CSX modules and may be some other languages as well, so I need to understand the formal specification of ES6 and I want to implement it correct way.

Hmm Maybe i implement "ExportCSXModule" Method?