DelSkayn / rquickjs

High level bindings to the quickjs javascript engine
MIT License
504 stars 63 forks source link

Module API redesign #142

Closed DelSkayn closed 1 year ago

DelSkayn commented 1 year ago

The current module API is unsound (see #117).

This PR implements an attempt to redesign the module API to be fully safe.

This PR fixes #91 and fixes #117.

Changes

It is now unsafe to hold onto unevaluated modules. As a result the new functions Module::define and Module::define_def, which both only declare modules but don't evaluate, don't return a Module.

The only way to safely retrieve a module object is via the Module::instantiate and Module::instantiate_def. These methods both evaluate the new module immediately after declaring them.

The Loader trait used to return an unevaluated module, as this is unsafe the loader trait is changed to.

pub trait Loader {
    /// Load module by name
    fn load<'js>(&mut self, ctx: Ctx<'js>, name: &str) -> Result<ModuleData>;
}

The trait now returns a ModuleData object which contains all the data for loading modules and is used by the implementation, after the load method returns, to create the new module.

The ModuleDef has also been changed to not use unevaluated modules, the trait is now defines as following:

pub trait ModuleDef {
    fn declare(define: &mut Declarations) -> Result<()> ;

    fn evaluate<'js>(_ctx: Ctx<'js>, exports: &mut Exports<'js>) -> Result<()> ;
}

Exports are first declared by using the Declarations struct and then, later, properly defined using the Exports struct. Neither struct actually holds onto a module but, instead, stores the declarations and exports to be used after the function returns.