Skip to content

Commit

Permalink
Fixed Twitter badge, updated Jekyll dependencies, and added module in…
Browse files Browse the repository at this point in the history
…terop blog entry.
  • Loading branch information
ClearScriptLib committed Jan 26, 2023
1 parent 5f54c40 commit 6106c30
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 4 deletions.
2 changes: 1 addition & 1 deletion ReadMe.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[![Banner](https://github.com/microsoft/ClearScript/blob/master/docs/ClearScript7-ReadMe.png)](https://microsoft.github.io/ClearScript/)

[![Follow](https://img.shields.io/twitter/follow/ClearScriptLib?style=social&label=Follow)](https://twitter.com/ClearScriptLib)
[![Twitter Follow](https://img.shields.io/badge/Follow-%40ClearScriptLib-white?logo=twitter&style=social)](https://twitter.com/ClearScriptLib)

# Description
ClearScript is a library that makes it easy to add scripting to your .NET applications. It currently supports JavaScript (via [V8](https://developers.google.com/v8/) and [JScript](https://docs.microsoft.com/en-us/previous-versions//hbxc2t98(v=vs.85))) and [VBScript](https://docs.microsoft.com/en-us/previous-versions//t0aew7h6(v=vs.85)).
Expand Down
6 changes: 3 additions & 3 deletions docs/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
GEM
remote: https://rubygems.org/
specs:
activesupport (6.0.6)
activesupport (6.0.6.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
Expand All @@ -15,7 +15,7 @@ GEM
coffee-script-source (1.11.1)
colorator (1.1.0)
commonmarker (0.23.7)
concurrent-ruby (1.1.10)
concurrent-ruby (1.2.0)
dnsruby (1.61.9)
simpleidn (~> 0.1)
em-websocket (0.5.3)
Expand All @@ -25,7 +25,7 @@ GEM
ffi (>= 1.15.0)
eventmachine (1.2.7)
execjs (2.8.1)
faraday (2.7.2)
faraday (2.7.4)
faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4)
faraday-net_http (3.0.2)
Expand Down
130 changes: 130 additions & 0 deletions docs/_posts/2023-1-24-module-interop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
---
title: Module Interoperability in ClearScript 7.3.7
---
Standard (ES6) modules can now import CommonJS modules.

# Introduction

[JavaScript modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) offer a way to split complex scripts into independent functional units with well-defined interfaces. The [`import`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) and [`export`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export) declarations, as well as the [`import`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) operator, were introduced in ECMAScript 2015 (ES6) as a standard way for modules to share code and data.

This facility supersedes earlier module specifications such as [CommonJS](https://commonjs.org/). However, the latter remains in heavy use, and many popular libraries aren't available in any other form.

ClearScript 7.3.7 allows JavaScript modules to import resources from CommonJS libraries. In this post we'll walk through an example.

# Basic Setup

For this example, let's allow scripts to load documents and use the console:

{% highlight C# %}

engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading;
engine.AddHostType(typeof(Console));

{% endhighlight %}


# Document Categories

ClearScript uses [_document categories_](https://microsoft.github.io/ClearScript/Reference/html/T_Microsoft_ClearScript_DocumentCategory.htm) to distinguish between the following document types:
- JavaScript
module – [`ModuleCategory.Standard`](https://microsoft.github.io/ClearScript/Reference/html/P_Microsoft_ClearScript_JavaScript_ModuleCategory_Standard.htm)
- CommonJS module – [`ModuleCategory.CommonJS`](https://microsoft.github.io/ClearScript/Reference/html/P_Microsoft_ClearScript_JavaScript_ModuleCategory_CommonJS.htm)
- Normal script – [`DocumentCategory.Script`](https://microsoft.github.io/ClearScript/Reference/html/P_Microsoft_ClearScript_DocumentCategory_Script.htm)

However, it has no way to _detect_ the category of a document. Instead, the host must specify the category when it initiates script execution:

{% highlight C# %}

engine.Execute(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import { Rectangle } from 'Geometry';
Console.WriteLine('The area is {0}.', new Rectangle(3, 4).Area);
");

{% endhighlight %}

If the host doesn't provide a category, ClearScript assumes `DocumentCategory.Script`.

On the other hand, if a module is loaded on behalf of another module, it _inherits_ its category from the requesting module. In our example, **Geometry** inherits `ModuleCategory.Standard`.

# Overriding the Category

Now let's suppose that **Geometry** is actually a CommonJS module and looks something like this:

{% highlight JavaScript %}

// Geometry.js
exports.Rectangle = class {
constructor(width, height) {
this.width = width;
this.height = height;
}
get Area() {
return this.width * this.height;
}
}

{% endhighlight %}

Normally, the sample code above would result in a [`SyntaxError`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError) with a message such as "The requested module 'Geometry' does not provide an export named 'Rectangle'".

To allow our example to work, we must _override_ **Geometry**'s document category. To do that, we can use a [document load callback](https://microsoft.github.io/ClearScript/Reference/html/P_Microsoft_ClearScript_DocumentSettings_LoadCallback.htm):

{% highlight C# %}

engine.DocumentSettings.LoadCallback = (ref DocumentInfo info) => {
if (Path.GetFileNameWithoutExtension(info.Uri.AbsolutePath) == "Geometry") {
info.Category = ModuleCategory.CommonJS;
}
};

{% endhighlight %}

Note that we're using a simple file name comparison to assign the document category. A real-world host might use a more generic algorithm, basing the assignment on the document's location, its file name extension, an external manifest, or even the document's contents.

# A Final Hurdle

In ClearScript 7.3.7, `V8ScriptEngine` is capable of importing CommonJS resources via the standard `import` declaration and operator. However, by default, the document loader throws an exception if a newly loaded document is of an unexpected category.

In other words, simply overriding the category would make our example fail even earlier – at the document loading stage. ClearScript 7.3.7 retains that behavior for compatibility and safety reasons. In many cases, blocking unexpected document categories is the prudent option.

In _this_ case, however, we can use a new flag to relax that requirement:

{% highlight C# %}

engine.DocumentSettings.AccessFlags |= DocumentAccessFlags.AllowCategoryMismatch;

{% endhighlight %}

With this flag in place, the document loader allows **Geometry** to pass on to the script engine, which now supports the CommonJS module category and safely imports the requested resources.

# Putting It All Together

Here's the complete, working sample code:

{% highlight C# %}

engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading | DocumentAccessFlags.AllowCategoryMismatch;
engine.DocumentSettings.LoadCallback = (ref DocumentInfo info) => {
if (Path.GetFileNameWithoutExtension(info.Uri.AbsolutePath) == "Geometry") {
info.Category = ModuleCategory.CommonJS;
}
};
engine.AddHostType(typeof(Console));
engine.Execute(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import { Rectangle } from 'Geometry';
Console.WriteLine('The area is {0}.', new Rectangle(3, 4).Area);
");

{% endhighlight %}

Module interoperability allows newer scripts to use the standard JavaScript module facility while consuming existing CommonJS libraries.

# How About Reverse Interoperability?

Unfortunately, importing standard modules from CommonJS modules is _not_ possible. The problem has to do with synchronous vs. asynchronous execution modes.

Specifically, standard modules can be [asynchronous](https://github.com/tc39/proposal-top-level-await), so they can invoke both synchronous and asynchronous code at the top level. Even if a module doesn't use [`await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await), its top-level code can be effectively asynchronous if it imports any asynchronous modules.

The top-level code of a CommonJS module is executed as a normal (synchronous) function, so it cannot interoperate with asynchronous code.

Good luck!

0 comments on commit 6106c30

Please sign in to comment.