Extension API: Module Support
Module API
This is a JerryScript extension that provides a means of loading modules. Fundamentally, a module is a name (stored as
a string) that resolves to a jerry_value_t
. This extension provides the function jerryx_module_resolve()
which
accepts the name of the module being requested as well as an array of so-called “resolvers” - structures containing two
function pointers: one for a function which computes a canonical name for the requested module or returns a reference
to the requested name, and one that converts a canonical name to a jerry_value_t
, thus “resolving” or “loading” the
requested module.
The resolvers are first called in sequence to each compute the canonical name of the requested module. This is
accomplished by calling the get_canonical_name
function pointer they provide. If the function pointer is NULL
, the
requested module name is assumed to be what the resolver considers to be its canonical name. jerryx_module_resolve
searches its cache of loaded modules for each canonical name as returned by a get_canonical_name
function pointer. If
one of the loaded modules in the cache corresponds to a canonical name, it is returned.
If no cached module is found, jerryx_module_resolve
calls each resolver’s resolve
function pointer, passing it its
previously computed interpretation of the requested module’s canonical name. If the resolver successfully creates the
jerry_value_t
that represents the loaded module, it returns true
and the jerry_value_t
in its out parameter.
When jerryx_module_resolve
receives a value of true
from a resolver, it stops iterating over the remaining
resolvers in the sequence and, if the jerry_value_t
returned from the resolver’s resolve
does not have the error
flag set, it will add the jerry_value_t
to its cache under the module’s canonical name and return it. Thus, on
subsequent calls to jerryx_module_resolve
with a module name whose canonical name is associated with the
jerry_value_t
, no resolve
callback need be called again.
The purpose of having resolvers is to be able to account for the fact that different types of modules may be structured differently and thus, for each type of module a module resolver must be supplied at the point where an instance of that type of module is requested.
Individual modules may be removed from the cache by calling jerryx_module_clear_cache
. This function behaves
identically to jerryx_module_resolve
in that it first checks the cache for the requested module, except that it
removes the module if found. Additionally, it clears the entire cache of all modules if called using a JavaScript value
of undefined
as its first parameter.
Additionally, this extension provides a means of easily defining so-called “native” JerryScript modules which can be
resolved using the native JerryScript module resolver jerryx_module_native_resolver
, which can be passed to
jerryx_module_resolve()
. Native modules are registered during application startup and by calling dlopen()
by means
of library constructors, support for which can be turned on using the FEATURE_INIT_FINI
build flag. In the absence of
such a flag, the module registration and unregistration functions are exposed as global symbols which can be called
explicitly. Note: On windows, FEATURE_INIT_FINI
build flag only supported with GNU toolchain or Microsoft Visual C/C++ Compiler
2008 and upper.
jerryx_module_resolve
Summary
Load a copy of a module into the current context or return one that was already loaded if it is found.
For each resolver passed in via resolvers_p
, its get_canonical_name
function pointer gets called in order to
establish the resolver’s interpretation of what the canonical name for the module should be. If get_canonical_name
is
NULL
, it is assumed that the requested module’s name as passed in is its canonical name.
Then, for each resolver passed in via resolvers_p
, its resolve
function pointer gets called with its interpretation
of what the module’s canonical name should be, as computed in the previous step.
If the resolver’s resolve
function pointer returns true
, the jerry_value_t
returned in its out-parameter will be
returned by jerryx_module_resolve
as the result of the request. If no error flag is set on the jerry_value_t
it
will be cached under its canonical name so as to avoid loading the same module twice in the event of a subsequent call
to jerryx_module_resolve
with a module name whose canonical name matches an already loaded module.
Prototype
jerry_value_t
jerryx_module_resolve (const jerry_value_t name,
const jerryx_module_resolver_t **resolvers_p,
size_t resolver_count);
name
- the name of the module to loadresolvers_p
- the list of resolvers to call in sequenceresolver_count
- the number of resolvers inresolvers_p
- return value -
jerry_value_t
representing the module that was loaded, or the error that occurred in the process.
jerryx_module_clear_cache
Summary
Remove a module from the current context’s cache, or clear the cache entirely.
Prototype
void
jerryx_module_clear_cache (const jerry_value_t name,
const jerryx_module_resolver_t **resolvers_p,
size_t resolver_count);
name
- the name of the module to remove from cache or a JavaScriptundefined
to clear the entire cacheresolvers_p
- the list of resolvers to call in sequenceresolver_count
- the number of resolvers inresolvers_p
jerryx_module_native_resolver
Summary
The resolver for native JerryScript modules. A pointer to this structure can be passed in the second parameter to
jerryx_module_resolve
to search for the module among the native JerryScript modules built into the binary. This
function is available only if the preprocessor directive JERRYX_NATIVE_MODULES_SUPPORTED
is defined.
Prototype
extern jerry_module_resolver_t jerryx_native_module_resolver;
Module data types
jerryx_module_get_canonical_name_t
Summary
The function pointer type for converting a module’s requested name to its canonical name.
Prototype
typedef jerry_value_t (*jerryx_module_get_canonical_name_t) (const jerry_value_t name);
jerryx_module_resolve_t
Summary
Function pointer type for module resolution.
Prototype
typedef bool (*jerryx_module_resolve_t) (const jerry_value_t canonical_name,
jerry_value_t *result);
jerryx_module_resolver_t
Summary
Structure defining a module resolver.
Prototype
typedef struct
{
jerryx_module_get_canonical_name_t get_canonical_name_p;
jerryx_module_resolve_t resolve_p;
} jerryx_module_resolver_t;
get_canonical_name_p
- function pointer to be called when the canonical name corresponding to the requested name of a module must be established.resolve_p
- function pointer to be called when a module with the given canonical name needs to be converted to thejerry_value_t
that will become the loaded module.
Example
static bool
load_and_evaluate_js_file (const jerry_value_t name, jerry_value_t *result)
{
bool return_value = false;
char *js_file_contents = NULL;
int file_size = 0;
jerry_size_t name_size = jerry_string_size (name, JERRY_ENCODING_UTF8);
jerry_char_t name_string[name_size + 1];
jerry_string_to_buffer (name, JERRY_ENCODING_UTF8, name_string, name_size);
name_string[name_size] = 0;
FILE *js_file = fopen (name_string, "r");
if (js_file)
{
/* We have successfully opened the file. Now, we establish its size. */
file_size = fseek (js_file, 0, SEEK_END);
fseek (js_file, 0, SEEK_SET);
/* We allocate enough memory to store the contents of the file. */
js_file_contents = malloc (file_size);
if (js_file_contents)
{
/* We read the file into memory and call jerry_eval (), assigning the result to the out-parameter. */
fread (js_file_contents, file_size, 1, js_file);
(*result) = jerry_eval (js_file_contents, file_size, JERRY_PARSE_NO_OPTS);
/* We release the memory holding the contents of the file. */
free (js_file_contents);
return_value = true;
}
/* We close the file. */
fclose (js_file);
}
return return_value;
}
static jerry_value_t
canonicalize_file_path (const jerry_value_t name)
{
jerry_value_t absolute_path;
/**
* Since a file on the file system can be referred to by multiple relative paths, but only by one absolute path, the
* absolute path becomes the canonical name for the module. Thus, to establish this canonical name, we must search
* name for "./" and "../", follow symlinks, etc., then create absolute_path via jerry_string () and return
* it, because it is the canonical name for this module. Thus, we avoid loading the same JavaScript file twice.
*/
return absolute_path;
}
static jerryx_module_resolver_t js_file_loader
{
canonicalize_file_path,
load_and_evaluate_js_file
};
We can now load JavaScript files:
static const jerryx_module_resolver_t *resolvers[] =
{
/*
* Consult the resolver for native JerryScript modules first, in case the requested module is a native JerryScript
* module.
*/
&jerryx_module_native_resolver,
/*
* If the requested module is not a native JerryScript module, assume it is a JavaScript file on disk and use the
* above-defined JavaScript file loader to load it.
*/
&js_file_loader
};
jerry_value_t js_module = jerryx_module_resolve (requested_module, resolvers, 2);
Module helper macros
JERRYX_NATIVE_MODULE
Summary
Helper macro to define a native JerryScript module. Currently declares a global static structure of type
jerryx_native_module_t
and a constructor/destructor pair that calls jerryx_native_module_register()
resp.
jerryx_native_module_unregister()
. If the extension is built without the FEATURE_INIT_FINI flag, indicating that
support for library constructors and destructors is absent, the constructor and destructor are declared as global
symbols so that they may be called explicitly from within the application.
Note: The helper macro must appear at the bottom of a source file, and no semicolon must follow it.
Prototype
#define JERRYX_NATIVE_MODULE(module_name, on_resolve_cb)
module_name
- the name of the module without quotes. This value is used as the prefix for the registration and unregistration functions. For example, whenmodule_name
isexample_module
, this results in the declaration of two functionsexample_module_register()
andexample_module_unregister()
. These functions are declared global if support for library constructors/destructors is absent, allowing you to call them from other parts of the code by first forward-declaring them.on_resolve_cb
- the function of typejerryx_native_module_on_resolve_t
that will be called when the module needs to be loaded.
Example
#include "jerryscript.h"
#include "jerryscript-ext/module.h"
static jerry_value_t
my_module_on_resolve (void)
{
return jerry_function_external (very_useful_function);
} /* my_module_on_resolve */
/* Note that there is no semicolon at the end of the next line. This is how it must be. */
JERRYX_NATIVE_MODULE (my_module, my_module_on_resolve)
Example Usage When Library Constructors Are Unavailable
#include "jerryscript.h"
#include "jerryscript-ext/module.h"
/**
* Forward-declare the module registration and unregistration function.
*/
extern void my_module_register (void);
extern void my_module_unregister (void);
int
main (int argc, char **argv)
{
jerryx_module_resolver_t resolvers[] =
{
jerryx_native_module_resolver
};
/* This plays the role of the library constructor. */
my_module_register ();
jerry_init (JERRY_INIT_EMPTY);
...
jerry_value_t my_module = jerryx_module_resolve ("my_module", resolvers, 1);
...
jerry_cleanup ();
/* This plays the role of the library destructor */
my_module_unregister();
return 0;
}