Revised: July 18, 2022
XS implements most of the TC39 Compartment Proposal. Beware that the proposal is at en early stage and that the programming interface described in this document will likely evolve.
In XS, the real host is the application that creates an XS machine to evaluate scripts and import modules.
Compartments are lightweight virtual hosts that evaluate scripts and import modules separately from the real host and from other compartments.
Compartments have their own globalThis
object, their own global lexical scope and their own module map.
The module map binds module specifiers to modules. Inside compartments like inside the real host, the same module specifier always imports the same module.
By default:
- the
globalThis
object contains only the built-ins, - the global lexical scope is empty,
- the module map is empty.
Compartments run in the same XS machine:
- Except for
Compartment
,Function
andeval
, built-ins are shared. - Other objects can be shared thru the
globals
andglobalLexicals
options. - Modules can be shared thru the
modules
,loadHook
andloadNowHook
options.
For the sake of security, it is the responsibility of a real host that creates compartments to ensure that shared features are immutable.
A parent compartment can create child compartments.
const parent = new Compartment();
parent.evaluate(`
const child = new Compartment();
`);
A compartment can only provide to its child compartments the features provided by its parent compartment (and new features based on the features provided by its parent compartment).
XS implements compartments natively, without Module
and Evaluators
classes, and without modifying dynamic import. Maybe other proposals like the module
construct will eventually justify such a dramatic evolution of the ECMAScript module machinery. But compartments do not.
Returns a compartment, an instance of Compartment.prototype
.
If present, the options
argument must be an object with optional properties: globals
, globalLexicals
, modules
, loadHook
, loadNowHook
, resolveHook
.
The globals
option adds properties to the compartment globalThis
object.
If defined, the value of the globals
option must be an object.
The Compartment
constructor copies the globals
object properties into the compartment globalThis
object like Object.assign
.
A compartment does not keep a reference to the globals
object.
The globalLexicals
option initializes the compartment global lexical scope.
If defined, the value of the globalLexicals
option must be an object.
Each own enumerable named property of the globalLexicals
object become a variable or a constant:
- property names are variable or constant names,
- property values are variable or constant values.
If the property is writable, a variable (let
) is created, else a constant (const
) is created.
A compartment does not keep a reference to the globalLexicals
object.
The modules
option initializes the compartment module map.
If defined, the value of the modules
option must be an object.
Each own enumerable named property of the modules
object creates an entry in the compartment module map:
- property names are module specifiers,
- property values are module descriptors.
Modules are neither loaded nor initialized by the Compartment
constructor.
A compartment does not keep a reference to the modules
object.
The loadHook
option is an asynchronous function that takes a module specifier and returns a promise to a module descriptor.
The loadHook
function is only called directly or indirectly by Compartment.prototype.import
if the module map of the compartment has no entry for a module specifier.
The loadHook
function is useful if the module descriptor is unavailable when constructing the compartment, or to create a module descriptor dynamically.
A compartment keeps a reference to the loadHook
function.
The loadNowHook
option is a function that takes a module specifier and returns a module descriptor.
The loadNowHook
function is only called directly or indirectly by Compartment.prototype.importNow
if the module map of the compartment has no entry for a module specifier.
The loadNowHook
function is useful if the module descriptor is unavailable when constructing the compartment, or to create a module descriptor dynamically.
A compartment keeps a reference to the loadNowHook
function.
The resolveHook
option is a function that takes an import specifier and a referrer specifier and returns a module specifier.
The resolveHook
function is called indirectly by Compartment.prototype.import
and Compartment.prototype.importNow
when an imported module uses import
declarations or calls.
Typically the resolveHook
function combine a relative path and an absolute path or URI into an absolute path or URI. But the resolveHook
function can build arbitrary specifiers.
The default resolveHook
function calls the resolveHook
function of the parent compartment.
A compartment keeps a reference to the resolveHook
function.
Returns the globalThis
object of the compartment.
Evaluates the script in the compartment and returns its completion value.
Scripts are evaluated in strict mode, with the global lexical scope of the compartment, and with this
being the globalThis
object of the compartment.
Asynchronously loads and initializes a module into the compartment and returns a promise to its namespace.
The specifier is used to get a module:
- from the compartment module map,
- else with the compartment
loadHook
.
If necessary, the compartment loads and initializes the module. All import
declarations or calls are firstly resolved by the compartment resolveHook
, then follow the same process. Eventually the promise is fulfilled with the module namespace.
Synchronously loads and initializes a module into the compartment and returns its namespace.
The specifier is used to get a module:
- from the compartment module map,
- else with the compartment
loadNowHook
.
If necessary, the compartment loads and initializes the module. All import
declarations are firstly resolved by the compartment resolveHook
, then follow the same process. Eventually the module namespace is returned.
-
The motivation behind
importNow
is to avoid unnecessary delays and to allow applications to get rid of the promise machinery. Such applications support neitherasync
norawait
nor theimport
call. The implementation ofimportNow
must not depend on the availability of promises. -
Applications that support promises,
async
,await
and theimport
call can still useimportNow
. That is expected to be rare but, then,importNow
throws when initializing a module with top levelawait
.
The initial value of this property is the "Compartment"
string.
Returns a module source, an instance of ModuleSource.prototype
.
The source argument is coerced into a string, then parsed as a module and compiled into byte codes.
XS does not keep a reference to the source
string.
Returns the module source bindings.
XS stores bindings into a private compressed form. The bindings
getter decompresses and publishes the bindings into an array of module bindings.
Returns true if the module source uses the import
call.
Returns true if the module source uses the import.meta
object.
The initial value of this property is the "ModuleSource"
string.
Comparments can load and initialize module namespaces from module descriptors. Like property descriptors, module descriptors are ordinary objects with various forms.
-
If fhe value of the
source
property is a string, the parent compartment loads the module but the compartment itself initializes the module. -
Else if the value of the
source
property is a module source, the module is loaded and initialized from the module source. -
Else the value of the
source
property must be an object. The module is loaded and initialized from the object according to the virtual module source pattern,
If the importMeta
property is present, its value must be an object. The default importMeta
object is an empty object.
Compartments copy the importMeta
object properties into the module import.meta
object like Object.assign
.
If the specifier
property is present, its value is coerced into a string and becomes the referrer specifier of the module.
-
If fhe value of the
namespace
property is a string, the descriptor shares a module to be loaded and initialized by the compartment referred by thecompartment
property.- If the
compartment
property is present, its value must be a compartment. - If absent, the
compartment
property defaults to the compartment being constructed in themodules
option, or being hooked in theloadHook
andloadNowHook
options.
- If the
-
Else if the value of the
namespace
property is a module namepace, the descriptor shares a module that is already available. -
Else the value of
record
property must be an object. The module is loaded and initialized from the object according to the virtual module namespace pattern.
To construct a static module record from a mod. In Moddable runtime, mods are separate archives of modules and resources.
- The
archive
property must be an archive. - The
path
property is coerced into a string then used to find the module in the archive.
A module binding is an ordinary object with properties that mimick the import
and export
constructs. There are many forms of module bindings. See the
imports and exports tables.
Most bindings can be represented as JSON-like objects with export
, import
, as
, from
properties. Except *
bindings, which requires special forms because module namespace identifiers can be arbitrary.
Construct | Module Binding |
---|---|
export { x } | { export: "x" } |
export { x as y } | { export: "x", as: "y" } |
export { x } from "mod" | { export: "x", from: "mod" } |
export { x as y } from "mod" | { export: "x", as: "y", from: "mod" } |
export * from "mod" | { exportAllFrom: "mod" } |
export * as star from "mod" | { exportAllFrom: "mod", as: "star" } |
import x from "mod" | { import: "default", as: "x", from: "mod" } |
import { x } from "mod" | { import: "x", from: "mod" } |
import { x as y } from "mod" | { import: "x", as: "y", from: "mod" } |
import * as star from "mod" | { importAllFrom: "mod", as: "star" } |
A virtual module source is an ordinary object with execute
, bindings
, needsImport
and needsImportMeta
properties
- The
execute
property must be a function. - If defined, the
bindings
property must be an array of module bindings. The default is an empty array. - If defined, the
needsImport
property is coerced into a boolean. The default isfalse
. - If defined, the
needsImportMeta
property is coerced into a boolean. The default isfalse
.
The bindings
array allow the same expressiveness as import and export declarations without requiring a JavaScript parser. Compartments check the declarations and can throw a SyntaxError
.
Once the module is loaded and linked, the compartment calls the execute
function with three arguments
$
: the module environment record,Import
: a function equivalent to theimport
call in a module body. The argument isundefined
ifneedsImport
was false.ImportMeta
: an object equivalent to theimport.meta
object in a module body. The argument isundefined
ifneedsImportMeta
was false.
The module environment record is sealed:
- no properties can be created or deleted,
- export properties are writable,
- import properties are read-only,
- there are no reexports properties.
Like a module body, the execute
function can be asynchronous.
A virtual module namespace is an ordinary object posing as a module namespace.
When a compartment loads a virtual module namespace, each own enumerable named property of the object becomes an exported property of the module.