Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Jint.Tests.Test262/Test262Harness.settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"Namespace": "Jint.Tests.Test262",
"Parallel": true,
"ExcludedFeatures": [
"import-bytes",
"import-defer",
"regexp-lookbehind",
"regexp-modifiers",
Expand Down
16 changes: 16 additions & 0 deletions Jint.Tests.Test262/Test262ModuleLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,20 @@ protected override string LoadModuleContents(Engine engine, ResolvedSpecifier re
return stream.ReadToEnd();
}
}

protected override byte[] LoadModuleContentsAsBytes(Engine engine, ResolvedSpecifier resolved)
{
lock (_fileSystem)
{
var fileName = Path.Combine(_basePath, resolved.Key).Replace('\\', '/');
if (!_fileSystem.FileExists(fileName))
{
Throw.ModuleResolutionException("Module Not Found", resolved.ModuleRequest.Specifier, parent: null, fileName);
}
using var stream = _fileSystem.OpenFile(fileName, FileMode.Open, FileAccess.Read);
using var ms = new MemoryStream();
stream.CopyTo(ms);
return ms.ToArray();
}
}
}
22 changes: 22 additions & 0 deletions Jint/Runtime/Modules/DefaultModuleLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,28 @@ protected override string LoadModuleContents(Engine engine, ResolvedSpecifier re
return File.ReadAllText(fileName);
}

protected override byte[] LoadModuleContentsAsBytes(Engine engine, ResolvedSpecifier resolved)
{
var specifier = resolved.ModuleRequest.Specifier;
if (resolved.Type != SpecifierType.RelativeOrAbsolute)
{
Throw.NotSupportedException($"The default module loader can only resolve files. You can define modules directly to allow imports using {nameof(Engine)}.{nameof(Engine.Modules.Add)}(). Attempted to resolve: '{specifier}'.");
}

if (resolved.Uri == null)
{
Throw.InvalidOperationException($"Module '{specifier}' of type '{resolved.Type}' has no resolved URI.");
}

var fileName = Uri.UnescapeDataString(resolved.Uri.AbsolutePath);
if (!File.Exists(fileName))
{
Throw.ModuleResolutionException("Module Not Found", specifier, parent: null, fileName);
}

return File.ReadAllBytes(fileName);
}

private static bool IsRelative(string specifier)
{
return specifier.StartsWith('.') || specifier.StartsWith('/');
Expand Down
18 changes: 18 additions & 0 deletions Jint/Runtime/Modules/ModuleFactory.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Jint.Native;
using Jint.Native.ArrayBuffer;
using Jint.Native.Json;

namespace Jint.Runtime.Modules;
Expand Down Expand Up @@ -87,4 +88,21 @@ public static Module BuildJsonModule(Engine engine, JsValue parsedJson, string?
{
return new SyntheticModule(engine, engine.Realm, parsedJson, location);
}

/// <summary>
/// Creates a <see cref="Module"/> for the usage within the given <paramref name="engine"/> for the
/// provided bytes module data.
/// </summary>
/// <remarks>
/// https://tc39.es/proposal-import-bytes/#sec-create-bytes-module
/// </remarks>
public static Module BuildBytesModule(Engine engine, ResolvedSpecifier resolved, byte[] bytes)
{
var arrayBuffer = engine.Realm.Intrinsics.ArrayBuffer.Construct(bytes);
arrayBuffer._isImmutable = true;

var uint8Array = engine.Realm.Intrinsics.Uint8Array.Construct([arrayBuffer], engine.Realm.Intrinsics.Uint8Array);

return new SyntheticModule(engine, engine.Realm, uint8Array, resolved.Uri?.LocalPath);
}
}
25 changes: 25 additions & 0 deletions Jint/Runtime/Modules/ModuleLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,22 @@ public abstract class ModuleLoader : IModuleLoader

public Module LoadModule(Engine engine, ResolvedSpecifier resolved)
{
if (resolved.ModuleRequest.IsBytesModule())
{
byte[] bytes;
try
{
bytes = LoadModuleContentsAsBytes(engine, resolved);
}
catch (Exception)
{
Throw.JavaScriptException(engine, $"Could not load module {resolved.ModuleRequest.Specifier}", AstExtensions.DefaultLocation);
return default!;
}

return ModuleFactory.BuildBytesModule(engine, resolved, bytes);
}

string code;
try
{
Expand All @@ -29,4 +45,13 @@ public Module LoadModule(Engine engine, ResolvedSpecifier resolved)
}

protected abstract string LoadModuleContents(Engine engine, ResolvedSpecifier resolved);

/// <summary>
/// Loads module contents as raw bytes. Override in derived classes for efficient binary loading.
/// </summary>
protected virtual byte[] LoadModuleContentsAsBytes(Engine engine, ResolvedSpecifier resolved)
{
var text = LoadModuleContents(engine, resolved);
return System.Text.Encoding.UTF8.GetBytes(text);
}
}
17 changes: 17 additions & 0 deletions Jint/Runtime/Modules/ModuleRequestExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,21 @@ public static bool IsJsonModule(this ModuleRequest request)
return request.Attributes != null
&& Array.Exists(request.Attributes, x => string.Equals(x.Key, "type", StringComparison.Ordinal) && string.Equals(x.Value, "json", StringComparison.Ordinal));
}

/// <summary>
/// Returns true if the provided <paramref name="request"/>
/// is a bytes module, otherwise false.
/// </summary>
/// <example>
/// The following JavaScript import statement imports a bytes module
/// for which this method would return true.
/// <code>
/// import value from 'data.bin' with { type: 'bytes' }
/// </code>
/// </example>
public static bool IsBytesModule(this ModuleRequest request)
{
return request.Attributes != null
&& Array.Exists(request.Attributes, x => string.Equals(x.Key, "type", StringComparison.Ordinal) && string.Equals(x.Value, "bytes", StringComparison.Ordinal));
}
}
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,11 @@ and many more.

#### ECMAScript proposals (no version yet)

- ✔ Await Dictionary (`Promise.allKeyed`, `Promise.allSettledKeyed`)
- ✔ `Error.isError`
- ✔ Explicit Resource Management (`using` and `await using`)
- ✔ Immutable Arraybuffers
- ✔ Import Bytes (`import x from './file' with { type: 'bytes' }`)
- ✔ Iterator Sequencing
- ✔ Joint Iteration
- ✔ JSON.parse source text access
Expand Down