mirror of
https://github.com/jerryscript-project/jerryscript.git
synced 2025-12-15 16:29:21 +00:00
MSVC doesn't support library constructor/destructor, let's disable the related unittest and clarify the documentation and error messages. JerryScript-DCO-1.0-Signed-off-by: Csaba Osztrogonác oszi@inf.u-szeged.hu
315 lines
12 KiB
Markdown
315 lines
12 KiB
Markdown
# 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: `FEATURE_INIT_FINI` build flag isn't supported on Windows, because Microsoft Visual C/C++ Compiler
|
|
doesn't support library constructors and destructors.
|
|
|
|
## 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**
|
|
|
|
```c
|
|
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 load
|
|
- `resolvers_p` - the list of resolvers to call in sequence
|
|
- `resolver_count` - the number of resolvers in `resolvers_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**
|
|
|
|
```c
|
|
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 JavaScript `undefined` to clear the entire cache
|
|
- `resolvers_p` - the list of resolvers to call in sequence
|
|
- `resolver_count` - the number of resolvers in `resolvers_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**
|
|
|
|
```c
|
|
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**
|
|
|
|
```c
|
|
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**
|
|
|
|
```c
|
|
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**
|
|
|
|
```c
|
|
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 the
|
|
`jerry_value_t` that will become the loaded module.
|
|
|
|
**Example**
|
|
```c
|
|
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_get_utf8_string_size (name);
|
|
jerry_char_t name_string[name_size + 1];
|
|
jerry_string_to_utf8_char_buffer (name, 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_create_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:
|
|
```c
|
|
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**
|
|
```c
|
|
#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 funtions. For example, when `module_name` is `example_module`, this results in the declaration of two functions `example_module_register()` and `example_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 type `jerryx_native_module_on_resolve_t` that will be called when the module needs to be
|
|
loaded.
|
|
|
|
**Example**
|
|
|
|
```c
|
|
#include "jerryscript.h"
|
|
#include "jerryscript-ext/module.h"
|
|
|
|
static jerry_value_t
|
|
my_module_on_resolve (void)
|
|
{
|
|
return jerry_create_external_function (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**
|
|
|
|
```c
|
|
#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;
|
|
}
|
|
```
|