Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix dynamic importing of JSON modules #1837

Merged
merged 1 commit into from
Apr 20, 2024
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
100 changes: 100 additions & 0 deletions Jint.Tests/Runtime/ModuleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -663,4 +663,104 @@ protected override string LoadModuleContents(Engine engine, ResolvedSpecifier re
throw new NotImplementedException(); // no need in this test
}
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public void CanStaticallyImportJsonModule(bool importViaLoader)
{
const string JsonModuleSpecifier = "./test.json";
const string JsonModuleContent =
"""
{ "message": "hello" }
""";

const string MainModuleSpecifier = "./main.js";
const string MainModuleCode =
$$"""
import json from "{{JsonModuleSpecifier}}" with { type: "json" };
export const msg = json.message;
""";

var loaderModules = new Dictionary<string, Func<Engine, ResolvedSpecifier, Module>>();
var engine = new Engine(o => o.EnableModules(new TestModuleLoader(loaderModules)));

loaderModules.Add(JsonModuleSpecifier, (engine, resolved) => ModuleFactory.BuildJsonModule(engine, resolved, JsonModuleContent));
if (importViaLoader)
{
loaderModules.Add(MainModuleSpecifier, (engine, resolved) => ModuleFactory.BuildSourceTextModule(engine, resolved, MainModuleCode));
}
else
{
engine.Modules.Add(MainModuleSpecifier, MainModuleCode);
}

var mainModule = engine.Modules.Import(MainModuleSpecifier);

Assert.Equal("hello", mainModule.Get("msg").AsString());
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task CanDynamicallyImportJsonModule(bool importViaLoader)
{
const string JsonModuleSpecifier = "./test.json";
const string JsonModuleContent =
"""
{ "message": "hello" }
""";

const string MainModuleSpecifier = "./main.js";
const string MainModuleCode =
$$"""
const json = await import("{{JsonModuleSpecifier}}", { with: { type: "json" } });
callback(json.default.message);
""";

var completionTcs = new TaskCompletionSource<JsValue>(TaskCreationOptions.RunContinuationsAsynchronously);

var loaderModules = new Dictionary<string, Func<Engine, ResolvedSpecifier, Module>>();
var engine = new Engine(o => o.EnableModules(new TestModuleLoader(loaderModules)))
.SetValue("callback", new Action<JsValue>(value => completionTcs.SetResult(value)));

loaderModules.Add(JsonModuleSpecifier, (engine, resolved) => ModuleFactory.BuildJsonModule(engine, resolved, JsonModuleContent));
if (importViaLoader)
{
loaderModules.Add(MainModuleSpecifier, (engine, resolved) => ModuleFactory.BuildSourceTextModule(engine, resolved, MainModuleCode));
}
else
{
engine.Modules.Add(MainModuleSpecifier, MainModuleCode);
}

var mainModule = engine.Modules.Import(MainModuleSpecifier);

Assert.Equal("hello", (await completionTcs.Task).AsString());
}

private sealed class TestModuleLoader : IModuleLoader
{
private readonly Dictionary<string, Func<Engine, ResolvedSpecifier, Module>> _moduleFactories;

public TestModuleLoader(Dictionary<string, Func<Engine, ResolvedSpecifier, Module>> moduleFactories)
{
_moduleFactories = moduleFactories;
}

ResolvedSpecifier IModuleLoader.Resolve(string referencingModuleLocation, ModuleRequest moduleRequest)
{
return new ResolvedSpecifier(moduleRequest, moduleRequest.Specifier, Uri: null, SpecifierType.RelativeOrAbsolute);
}

Module IModuleLoader.LoadModule(Engine engine, ResolvedSpecifier resolved)
{
if (_moduleFactories.TryGetValue(resolved.ModuleRequest.Specifier, out var moduleFactory))
{
return moduleFactory(engine, resolved);
}

throw new ArgumentException(null, nameof(resolved));
}
}
}
1 change: 0 additions & 1 deletion Jint/Engine.Modules.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ public ModuleOperations(Engine engine, IModuleLoader moduleLoader)

internal Module Load(string? referencingModuleLocation, ModuleRequest request)
{
var specifier = request.Specifier;
var moduleResolution = ModuleLoader.Resolve(referencingModuleLocation, request);

if (_modules.TryGetValue(moduleResolution.Key, out var module))
Expand Down
2 changes: 1 addition & 1 deletion Jint/Runtime/Host.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ internal virtual void LoadImportedModule(IScriptOrModule? referrer, ModuleReques
try
{
// This should instead return the PromiseInstance returned by ModuleRecord.Evaluate (currently done in Engine.EvaluateModule), but until we have await this will do.
Engine.Modules.Import(moduleRequest.Specifier, referrer?.Location);
Engine.Modules.Import(moduleRequest, referrer?.Location);
promise.Resolve(JsValue.Undefined);
}
catch (JavaScriptException ex)
Expand Down
1 change: 1 addition & 0 deletions Jint/Runtime/Modules/SyntheticModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ internal SyntheticModule(Engine engine, Realm realm, JsValue obj, string? locati

public override void Link()
{
InnerModuleLinking(null!, 0);
}

public override JsValue Evaluate()
Expand Down