diff --git a/Jint.Tests.Test262/Test262Harness.settings.json b/Jint.Tests.Test262/Test262Harness.settings.json
index 378084e038..e5d7088735 100644
--- a/Jint.Tests.Test262/Test262Harness.settings.json
+++ b/Jint.Tests.Test262/Test262Harness.settings.json
@@ -5,7 +5,6 @@
"Namespace": "Jint.Tests.Test262",
"Parallel": true,
"ExcludedFeatures": [
- "import-bytes",
"import-defer",
"regexp-lookbehind",
"regexp-modifiers",
diff --git a/Jint.Tests.Test262/Test262ModuleLoader.cs b/Jint.Tests.Test262/Test262ModuleLoader.cs
index 5d36e17253..0af891c7e9 100644
--- a/Jint.Tests.Test262/Test262ModuleLoader.cs
+++ b/Jint.Tests.Test262/Test262ModuleLoader.cs
@@ -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();
+ }
+ }
}
diff --git a/Jint/Runtime/Modules/DefaultModuleLoader.cs b/Jint/Runtime/Modules/DefaultModuleLoader.cs
index 4279009cb6..71385f8d04 100644
--- a/Jint/Runtime/Modules/DefaultModuleLoader.cs
+++ b/Jint/Runtime/Modules/DefaultModuleLoader.cs
@@ -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('/');
diff --git a/Jint/Runtime/Modules/ModuleFactory.cs b/Jint/Runtime/Modules/ModuleFactory.cs
index 35de4b6eb7..59f1338811 100644
--- a/Jint/Runtime/Modules/ModuleFactory.cs
+++ b/Jint/Runtime/Modules/ModuleFactory.cs
@@ -1,4 +1,5 @@
using Jint.Native;
+using Jint.Native.ArrayBuffer;
using Jint.Native.Json;
namespace Jint.Runtime.Modules;
@@ -87,4 +88,21 @@ public static Module BuildJsonModule(Engine engine, JsValue parsedJson, string?
{
return new SyntheticModule(engine, engine.Realm, parsedJson, location);
}
+
+ ///
+ /// Creates a for the usage within the given for the
+ /// provided bytes module data.
+ ///
+ ///
+ /// https://tc39.es/proposal-import-bytes/#sec-create-bytes-module
+ ///
+ 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);
+ }
}
diff --git a/Jint/Runtime/Modules/ModuleLoader.cs b/Jint/Runtime/Modules/ModuleLoader.cs
index 6b0a891ac7..d244703433 100644
--- a/Jint/Runtime/Modules/ModuleLoader.cs
+++ b/Jint/Runtime/Modules/ModuleLoader.cs
@@ -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
{
@@ -29,4 +45,13 @@ public Module LoadModule(Engine engine, ResolvedSpecifier resolved)
}
protected abstract string LoadModuleContents(Engine engine, ResolvedSpecifier resolved);
+
+ ///
+ /// Loads module contents as raw bytes. Override in derived classes for efficient binary loading.
+ ///
+ protected virtual byte[] LoadModuleContentsAsBytes(Engine engine, ResolvedSpecifier resolved)
+ {
+ var text = LoadModuleContents(engine, resolved);
+ return System.Text.Encoding.UTF8.GetBytes(text);
+ }
}
diff --git a/Jint/Runtime/Modules/ModuleRequestExtensions.cs b/Jint/Runtime/Modules/ModuleRequestExtensions.cs
index ee4da9a468..06d592845b 100644
--- a/Jint/Runtime/Modules/ModuleRequestExtensions.cs
+++ b/Jint/Runtime/Modules/ModuleRequestExtensions.cs
@@ -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));
}
+
+ ///
+ /// Returns true if the provided
+ /// is a bytes module, otherwise false.
+ ///
+ ///
+ /// The following JavaScript import statement imports a bytes module
+ /// for which this method would return true.
+ ///
+ /// import value from 'data.bin' with { type: 'bytes' }
+ ///
+ ///
+ 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));
+ }
}
diff --git a/README.md b/README.md
index 780844acfd..7fe19a070f 100644
--- a/README.md
+++ b/README.md
@@ -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