diff --git a/core/engine/src/module/mod.rs b/core/engine/src/module/mod.rs index 9bf195f35f..5190930f54 100644 --- a/core/engine/src/module/mod.rs +++ b/core/engine/src/module/mod.rs @@ -29,6 +29,7 @@ use std::rc::Rc; use rustc_hash::FxHashSet; +use boa_engine::js_string; use boa_gc::{Finalize, Gc, GcRefCell, Trace}; use boa_interner::Interner; use boa_parser::source::ReadChar; @@ -40,6 +41,7 @@ use source::SourceTextModule; pub use synthetic::{SyntheticModule, SyntheticModuleInitializer}; use crate::{ + builtins, builtins::promise::{PromiseCapability, PromiseState}, environments::DeclarativeEnvironment, object::{JsObject, JsPromise}, @@ -204,6 +206,41 @@ impl Module { } } + /// Create a [`Module`] from a `JsValue`, exporting that value as the default export. + /// This will clone the module everytime it is initialized. + pub fn from_value_as_default(value: JsValue, context: &mut Context) -> Self { + Module::synthetic( + &[js_string!("default")], + SyntheticModuleInitializer::from_copy_closure_with_captures( + move |m, value, _ctx| { + m.set_export(&js_string!("default"), value.clone())?; + Ok(()) + }, + value, + ), + None, + None, + context, + ) + } + + /// Create a module that exports a single JSON value as the default export, from its + /// JSON string. + /// + /// # Specification + /// This is a custom extension to the ECMAScript specification. The current proposal + /// for JSON modules is being considered in + /// and might differ from this implementation. + /// + /// This method is provided as a convenience for hosts to create JSON modules. + /// + /// # Errors + /// This will return an error if the JSON string is invalid or cannot be converted. + pub fn parse_json(json: JsString, context: &mut Context) -> JsResult { + let value = builtins::Json::parse(&JsValue::undefined(), &[json.into()], context)?; + Ok(Self::from_value_as_default(value, context)) + } + /// Gets the realm of this `Module`. #[inline] #[must_use] diff --git a/core/engine/tests/module.rs b/core/engine/tests/module.rs new file mode 100644 index 0000000000..09ec352258 --- /dev/null +++ b/core/engine/tests/module.rs @@ -0,0 +1,65 @@ +#![allow(unused_crate_dependencies, missing_docs)] + +use std::rc::Rc; + +use boa_engine::builtins::promise::PromiseState; +use boa_engine::module::{ModuleLoader, Referrer}; +use boa_engine::{js_string, Context, JsResult, JsString, Module, Source}; + +#[test] +fn test_json_module_from_str() { + struct TestModuleLoader(JsString); + impl ModuleLoader for TestModuleLoader { + fn load_imported_module( + &self, + _referrer: Referrer, + specifier: JsString, + finish_load: Box, &mut Context)>, + context: &mut Context, + ) { + assert_eq!(specifier.to_std_string_escaped(), "basic"); + + finish_load( + Ok(Module::parse_json(self.0.clone(), context).unwrap()), + context, + ); + } + } + + let json_string = js_string!(r#"{"key":"value","other":123}"#); + let mut context = Context::builder() + .module_loader(Rc::new(TestModuleLoader(json_string.clone()))) + .build() + .unwrap(); + + let source = Source::from_bytes( + b" + import basic_json from 'basic'; + export let json = basic_json; + ", + ); + + let module = Module::parse(source, None, &mut context).unwrap(); + let promise = module.load_link_evaluate(&mut context); + context.run_jobs(); + + match promise.state() { + PromiseState::Pending => {} + PromiseState::Fulfilled(v) => { + assert!(v.is_undefined()); + } + PromiseState::Rejected(e) => { + panic!("Unexpected error: {:?}", e.to_string(&mut context).unwrap()); + } + } + + let json = module + .namespace(&mut context) + .get(js_string!("json"), &mut context) + .unwrap(); + + assert_eq!( + JsString::from(json.to_json(&mut context).unwrap().to_string()), + json_string + ); +}