Requirejs implementation

Nov 13, 2013 at 8:18 PM
Hello everybody,
I am fond of Clearscript!
It ould be a greet idea to build a serveur side  clearscript" implémentation of requireJS.

What do you think about this?

Regards,
Guillaume.
Coordinator
Nov 14, 2013 at 5:02 AM
Greetings Guillaume!

Here's a trivial implementation that uses .NET 4.5 asynchronous I/O to load JavaScript modules from disk. Error handling is left as an exercise :)
public class ModuleLoader {
    private readonly ScriptEngine _engine;
    public ModuleLoader(ScriptEngine engine) {
        _engine = engine;
    }
    public async Task LoadModuleAsync(dynamic context, string name, string url) {
        using (var reader = File.OpenText(url)) {
            _engine.Execute(await reader.ReadToEndAsync());
            context.completeLoad(name);
        }
    }
}
and then:
engine.Script.moduleLoader = new ModuleLoader(engine);
engine.Execute(File.ReadAllText(@"C:\path\to\require.js"));
engine.Execute(@"require.load = function (context, name, url) {
    moduleLoader.LoadModuleAsync(context, name, url);
}");
We've verified that this works with V8 and the current version of RequireJS. A JScript-based implementation would be more complicated due to that script engine's thread affinity.

Cheers!
Nov 15, 2013 at 10:09 PM
Thank you very much,

I wasn't excpeted such quick and perfect answer !
Feb 12, 2014 at 12:44 PM
Edited Feb 12, 2014 at 7:15 PM
Hi,

Thanks for creating this excellent library. I am attempting to get the requirejs implemententation to work.

My code is as follows.
engine.Script.moduleLoader = new ModuleLoader(engine);
string currentDir = FileOperations.GetCurrentlyExecutingAssemblyLocation();
engine.Execute(File.ReadAllText(currentDir + @"\Scripts\require.js"));
engine.Execute(@"require.load = function (context, name, url) {
moduleLoader.LoadModuleAsync(context, name, url);
}");
The following exception is thrown when I execute "engine.Script.moduleLoader = new ModuleLoader(engine);"

First Chance Exception: RuntimeBinderException
Message: 'System.Dynamic.DynamicObject' does not contain a definition for 'moduleLoader'

The requirejs file is loaded and the objects it creates are visible in the engine.Script

Do you have any ideas why this exception is thrown? I am not 100% clear what the context variable is, how is context accessed or created. What does context mean?

I did find the following articles but I can't work out if they have relevance to the problem I am facing.

Adding properties or Dynamic objects in ClearScript
https://clearscript.codeplex.com/discussions/433131

https://stackoverflow.com/questions/2630370/c-sharp-dynamic-cannot-access-properties-from-anonymous-types-declared-in-anot

http://www.solutionevangelist.com/post/14

Due to the fact that V8 does not implement a DOM, loading libraries like jQuery can prove difficult since there are no window or document objects in the engine. I noticed the test projects have a class called MockDOM but this is a very barebones implementation. For something more fully featured I've been looking at using jsdom http://lab.arc90.com/2010/11/22/jsdom/ . jsdom seems to be developed specifically for node but I am hoping it will work in ClearScript also. Do you know of any techniques that will enable me to load jQuery or other libraries that require the DOM to load and execute in ClearScript?

Any help or suggestions you have in relation to these matters is appreciated.
Feb 12, 2014 at 2:16 PM
Edited Feb 12, 2014 at 2:35 PM
I think I may have found a solution for

First Chance Exception: RuntimeBinderException
Message: 'System.Dynamic.DynamicObject' does not contain a definition for 'moduleLoader'

http://www.codeproject.com/Articles/62839/Adventures-with-C-4-0-dynamic-ExpandoObject-Elasti
Type t = typeof (ModuleLoader);
dynamic moduleLoaderDefinedInClassLib = Activator.CreateInstance(t, engine);
engine.Script.moduleLoader = moduleLoaderDefinedInClassLib;
string currentDir = FileOperations.GetCurrentlyExecutingAssemblyLocation();
engine.Execute(File.ReadAllText(currentDir + @"\Scripts\require.js"));
engine.Execute(@"require.load = function (context, name, url) {
moduleLoader.LoadModuleAsync(context, name, url);
}");
In my case the ModuleLoader was defined in a class library project that the main executable referenced, so I think this was the reason the exception was thrown.

Still not clear what the context variable is though!
Coordinator
Feb 12, 2014 at 2:28 PM
Hello AustinDimmer!

We can't reproduce the exception you're seeing. Can you tell us more about your environment?

  • What version of Windows are you using, including the SKU, x64 vs. x86, etc.?
  • What version of Visual Studio are you using? Is it Visual Studio Express?
  • What version of ClearScript are you using? Did you build it, or are you using a NuGet package? Have you made any modifications?
  • What kind of project are you building - web, console, WPF, etc.?
  • What version of the .NET framework is your project targeting?
  • Is your ModuleLoader class defined exactly as above?
  • Can you share more of your code? We'd like to see how you're creating the script engine, what other objects you're exposing, etc.
  • How are you getting to the line that executes require.js if you're hitting an exception two lines before?
As for JSDOM, we haven't tried it but it looks like it might be exactly what you're looking for.

Good luck!
Coordinator
Feb 12, 2014 at 3:14 PM
Hi again,

In my case the ModuleLoader was defined in a class library project that the main executable referenced, so I think this was the reason the exception was thrown.

That doesn't seem likely. If you can successfully compile expressions such as typeof(ModuleLoader) and new ModuleLoader(engine), then you have a compile-time reference to ModuleLoader, and instantiation via Activator should be unnecessary. Indeed, the exception indicates a problem with dynamic property assignment rather than ModuleLoader instantiation. We'd like to understand the issue, but we can't reproduce it, so please provide any additional information you can.

Still not clear what the context variable is though!

The context object is just a JavaScript object that RequireJS passes to its load function. Among other things, context contains a completeLoad function that must be called to report that the requested resource has been loaded.
Feb 12, 2014 at 3:26 PM
Edited Feb 12, 2014 at 3:32 PM
Thanks for the quick answer.

What version of Windows are you using, including the SKU, x64 vs. x86, etc.? Windows 8.1 x64
What version of Visual Studio are you using? Is it Visual Studio Express? VS2013 Ultimate
What version of ClearScript are you using? Did you build it, or are you using a NuGet package? Have you made any modifications? Nuget <package id="ClearScript.V8" version="5.3.10.0" targetFramework="net45" />
What kind of project are you building - web, console, WPF, etc.? WPF Application
What version of the .NET framework is your project targeting? 4.5
Is your ModuleLoader class defined exactly as above? Yes
Can you share more of your code? We'd like to see how you're creating the script engine, what other objects you're exposing, etc.

Trying to load in jQuery or other libs that require a DOM, I am following the sample starter code given at http://lab.arc90.com/2010/11/22/jsdom/

Here is the code I have so far, more or less.
var engine = new V8ScriptEngine("engine", V8ScriptEngineFlags.EnableDebugging);
var typeCollection = new HostTypeCollection("mscorlib", "System", "System.Core");
engine.AddHostObject("clr", typeCollection);
//Now all the types defined in the standard mscorlib, System, and System.Core assemblies are exposed. Host type collections are hierarchical data structures where leaf nodes represent host types and parent nodes represent namespaces:
engine.Execute(@"var guid = clr.System.Guid.NewGuid();");
engine.Execute(@"var today = clr.System.DayOfWeek.Friday;");
//Note that, unlike C# types and namespaces, the nodes of a host type collection are objects. A script can copy them to the root level for more convenient access:
engine.Execute(@"var System = clr.System;");
engine.Execute(@"var System = clr.System;");
engine.Execute(@"var Guid = System.Guid;");
engine.Execute(@"var String = System.String;");
engine.AddHostType("Console", typeof(Console));
Type t = typeof (ModuleLoader);
dynamic moduleLoaderDefinedInClassLib = Activator.CreateInstance(t, engine);
engine.Script.moduleLoader = moduleLoaderDefinedInClassLib;
string currentDir = FileOperations.GetCurrentlyExecutingAssemblyLocation();
engine.Execute(File.ReadAllText(currentDir + @"\Scripts\require.js"));
engine.Execute(@"require.load = function (context, name, url) {
moduleLoader.LoadModuleAsync(context, name, url);
}");
string htmlTest = "<html><head></head><body><h1>Hello World</h1><p>This is a <em>stupid simple</em> example</p></body></html>";
engine.Script.htmlTest = htmlTest;
engine.Execute(@"var html = htmlTest");

// create a new window, with the above markup
string jsdomPath = currentDir + @"\Scripts\jsdom" + ".js";;
engine.Script.jsdomPath = jsdomPath;
engine.Execute(@"moduleLoader.LoadModuleAsync(this, 'jsdom', jsdomPath);");
engine.Execute(@"var window = require(jsdomPath).jsdom(html).createWindow();");

// create a new script tag to load up jQuery
engine.Execute(@"script = window.document.createElement('script');");

engine.Execute(@"script.src = 'http://code.jquery.com/jquery-1.4.2.js';");

// when jQuery finishes loading
engine.Execute(@"script.onload = function() {
// Since this is not a browser we need to specify the window that jQuery has
// been added to
// Output: 'H1 Contents: Hello World'
Console.WriteLine(window.jQuery('h1').text());

// do a simple replace on the `em` text above
window.jQuery('p em').text('silly');

// Output: 'P Contents: This is a silly example'
Console.WriteLine(window.jQuery('p').text());}}"
);
// Append the element to the head element of the document
engine.Execute(@"window.document.head.appendChild(script);");

How are you getting to the line that executes require.js if you're hitting an exception two lines before? I have a first chance exception handler, code still runs. Not 100% sure why the code behaves like this!
Coordinator
Feb 12, 2014 at 7:23 PM
Hi AustinDimmer!

You code looks fine, although you shouldn't need this line:
engine.Execute(@"moduleLoader.LoadModuleAsync(this, 'jsdom', jsdomPath);");
You shouldn't need that because the require call on the next line should call the module loader internally. Also, be careful with this:
engine.Execute(@"var String = System.String;");
That replaces the built-in JavaScript String function, which could affect subsequent scripts.

I have a first chance exception handler, code still runs. Not 100% sure why the code behaves like this!

How are you setting up this handler? It's not in the code above. We're glad you found a way forward, but we'd really like to understand your original issue, and we still can't reproduce it with ModuleLoader defined in a separate class library. Would it by any chance be possible for you to share your project?

Thanks!
Feb 12, 2014 at 8:04 PM
Edited Feb 12, 2014 at 8:08 PM
I've been looking into the context thing and I am having some progress. I found a decent example at the following url

https://stackoverflow.com/questions/19279196/context-and-nested-modules-in-requirejs

I should mention that ClearScript is being set up in a seperate AppDomain. My thinking is that this makes things a little bit more secure in that code executing in the child AppDomain can't execute more sensitive code in the ParentApp domain. The AppDomain that I am loading ClearScript into is isolated. Not sure if this may be the root cause for my observations. I suspect not.

I am setting up handlers in the AppDomain as follows.
AppDomain.CurrentDomain.FirstChanceException += CurrentDomain_FirstChanceException;
And the handler method is just printing to the console.
private static void CurrentDomain_FirstChanceException(object sender, FirstChanceExceptionEventArgs e)
{
Debug.WriteLine(String.Format("First Chance Exception: {0}\r\nMessage: {1}\r\n", e.Exception.GetType()
.Name, e.Exception.Message));
}
I can't share the whole codebase, but if you want to investigate further. I'll try and provide as much info as possible to enable you to replicate things at your end.

I've made some progress today and I am hoping that I will soon find the secret sauce that enables me to load jsdom into ClearScript using requirejs.

Thanks again!
Coordinator
Feb 12, 2014 at 11:36 PM
Hello again!

We've investigated this some more, and it turns out that the RuntimeBinderException is absolutely not a concern. It is thrown and handled internally within the C# Runtime Binder.

In other words, the exception doesn't make it out of .NET code, and that's why your code continues to run. Your AppDomain.FirstChanceException handler has nothing to do with it. From MSDN: "This event is only a notification. Handling this event does not handle the exception or affect subsequent exception handling in any way."

At first we thought we couldn't reproduce the exception because we didn't see it in the debugger, but that was because Visual Studio is configured to ignore RuntimeBinderException by default. Once we set up a logging handler similar to yours, we saw it.

The bottom line is that you can use the original simple form:
engine.Script.moduleLoader = new ModuleLoader(engine);
This is recommended over Activator.CreateInstance() and should perform much better.

Thanks for all your help!
Feb 13, 2014 at 10:54 AM
Hello,

I didn't known jsdom :)

The sound nicer than using phantomJS in order to generate page for SEO.

https://github.com/ithkuil/angular-on-server/wiki/Running-AngularJS-on-the-server-with-Node.js-and-jsdom

Do you know an easy way of implement XMLHttpRequest interface on the server C# side?

In order to make :
AngularJS
JSDom
JQuery

Work on with cleascript on serveur side?

Regards,

Guillaume Chervet.
Coordinator
Feb 13, 2014 at 1:17 PM
Hi Guillaume,

If you need to run existing JavaScript code that requires XMLHttpRequest, the best available option is to implement (the required portions of) XMLHttpRequest on top of something like System.Net.HttpWebRequest and expose it to the script engine.

Windows provides a COM implementation of XMLHttpRequest, and the next version of ClearScript will allow you to expose it for scripting. However, this solution may not support asynchronous operation and may therefore be of limited use on the server.

Cheers!
Feb 13, 2014 at 4:20 PM
Edited Feb 13, 2014 at 4:21 PM
It turns out that the jsdom dependencies on Node are going to make it impractical for me to use jsdom to get the functionality that I need. I could not get jsdom to load into ClearScript so I will now look for alternatives.

Thanks again for the help yesterday and keep up the good work!
Coordinator
Feb 13, 2014 at 8:20 PM
AustinDimmer, thank you for your kind words and for giving ClearScript a try!