How to call javascript in rust using boa-engine and esbuild?
Folder
Folder for esbuild
tests/modules/package.js
Esbuild
esbuild_tests.js
import { build } from "esbuild";
import { polyfillNode } from "esbuild-plugin-polyfill-node";
build({
entryPoints: ["package.js"],
bundle: true,
outfile: "tests/modules/package.js",
format: "esm",
// sourcemap: 'inline',
minify: true,
plugins: [polyfillNode()],
}).catch(() => process.exit(1));
Node packages
yarn add esbuild
yarn add esbuild-plugin-polyfill-node
package.json
"scripts": {
"esbuild-test": "node esbuild_tests.js"
}
Folders for js
js-api/hello.ts
export function helloWorld(): String {
return "Hello, world!";
}
package.js
import { helloWorld } from "./js-api/hello";
export { helloWorld };
Example
https://github.com/boa-dev/boa/blob/main/examples/src/bin/modules.rs
Imports
#![allow(unused)]
fn main() {
use std::env;
use std::path::PathBuf;
use std::{error::Error, path::Path, rc::Rc};
use boa_engine::{
builtins::promise::PromiseState, js_string, module::SimpleModuleLoader, Context, JsError,
JsNativeError, JsValue, Module, NativeFunction,
};
use boa_parser::Source;
}
#![allow(unused)]
fn main() {
let root = env::current_dir().unwrap();
println!("Project root: {:?}", root);
// Build path to package.js
let js_file_path: PathBuf = root.parent().unwrap().join("tests/modules/package.js");
println!("JS file path: {:?}", js_file_path);
let path: &Path = js_file_path.as_path();
println!("{:?}", path);
let source = Source::from_filepath(path).unwrap();
let module_pathbuf = root.parent().unwrap().join("tests/modules");
let module_path: &Path = module_pathbuf.as_path();
println!("{:?}", module_path);
// // println!("source: {:?}", source);
let loader = Rc::new(SimpleModuleLoader::new(module_path).unwrap());
// // Instantiate the execution context
let context = &mut Context::builder()
.module_loader(loader.clone())
.build()
.unwrap(); // // Add the runtime intrisics
let module = Module::parse(source, None, context).unwrap();
loader.insert(
Path::new(module_path)
.canonicalize()
.unwrap()
.join("main.mjs"),
module.clone(),
);
let promise_result = module
// Initial load that recursively loads the module's dependencies.
// This returns a `JsPromise` that will be resolved when loading finishes,
// which allows async loads and async fetches.
.load(context)
.then(
Some(
NativeFunction::from_copy_closure_with_captures(
|_, _, module, context| {
// After loading, link all modules by resolving the imports
// and exports on the full module graph, initializing module
// environments. This returns a plain `Err` since all modules
// must link at the same time.
module.link(context)?;
Ok(JsValue::undefined())
},
module.clone(),
)
.to_js_function(context.realm()),
),
None,
context,
)
.then(
Some(
NativeFunction::from_copy_closure_with_captures(
// Finally, evaluate the root module.
// This returns a `JsPromise` since a module could have
// top-level await statements, which defers module execution to the
// job queue.
|_, _, module, context| Ok(module.evaluate(context).into()),
module.clone(),
)
.to_js_function(context.realm()),
),
None,
context,
);
// Very important to push forward the job queue after queueing promises.
context.run_jobs();
match promise_result.state() {
PromiseState::Pending => {
println!("Module can't load");
}
PromiseState::Fulfilled(v) => {
assert_eq!(v, JsValue::undefined());
}
PromiseState::Rejected(err) => {
println!(
"{:?}",
JsError::from_opaque(err).try_native(context).unwrap()
);
}
}
// We can access the full namespace of the module with all its exports.
let namespace = module.namespace(context);
let hello_world_namespace = namespace.get(js_string!("helloWorld"), context).unwrap();
let hello_world = hello_world_namespace
.as_callable()
.ok_or_else(|| JsNativeError::typ().with_message("mix export wasn't a function!"))
.unwrap();
let result = hello_world
.call(&JsValue::undefined(), &[], context)
.unwrap();
println!("result = {}", result.display());
}
cargo test -- --nocapture