SES 1.6.0 improves compatibility with XS
Agoric has released ses
version
1.6.0 with changes that
improve compatibility with Moddable’s XS.
The version maintains backward-compatibility with existing usage and
some usage becomes deprecated, for which support will be removed in a future,
major version.
__options__
With this change, the Compartment
constructor now has a preferred
single-argument form, which will become the only accepted form in a future,
major version.
Until then, Compartment
accepts both the old and new signatures.
The deprecated form accepts three optional arguments:
const compartment = new Compartment(globals, modules, { name: 'my-compartment',});
The new form accepts a single options bag, and currently requires the
__options__
option to distinguish the global endowments from an options
object.
This is technically a breaking change, but we expect that it is vanishingly rare for a compartment to be endowed with an
__options__
global. If such code exists, it will need to adopt the new form.
const compartment = new Compartment({ __options__: true, name: 'my-compartment', globals, modules,});
In a future, major version, the __options__
field will become vestigial and the
three-argument form will be an error.
const compartment = new Compartment({ name: 'my-compartment', globals, modules,});
__noNamespaceBox__
Likewise, the import
method of compartments currently returns a promise
for an object with a namespace
property, which is the namespace.
This differs from XS and the behavior of standard, dynamic import
.
const compartment = new Compartment({ __options__: true, modules: { 'my-module-specifier': { namespace: {default: 42}, }, }});// Note the destructuring of namespace:const { namespace } = await compartment.import('my-module-specifier');console.log(namespace.default); // 42
The import
method should simply return a promise for the module namespace
object, just like dynamic import
and XS.
Version 1.6.0 introduces a __noNamespaceBox__
option that makes the eventual
correct behavior available today.
const compartment = new Compartment({ __options__: true, __noNamespaceBox__: true, modules: { 'my-module-specifier': { namespace: {default: 42}, }, }});// Note the absence of destructuring { namespace }:const namespace = await compartment.import('my-module-specifier');console.log(namespace.default); // 42
This will become the only supported behavior in a future, major version.
Module descriptors
Version 1.6.0 increases module descriptor parity with XS.
Module descriptors appear in several places in the Compartment
interface.
- The
modules
option is an object that maps module specifiers to their corresponding module descriptor. These modules are “pre-loaded” at time of construction. - The
moduleMapHook
is a function that accepts a module specifier and may return a module descriptor ifimport
orimportNow
cannot complete without it. - The
importHookNowHook
accepts a module specifier and may return a module descriptor ifimportNow
cannot complete without it. - The
importHook
accepts a module specifier and may return a module descriptor or a promise for a module descriptor ifimport
cannot complete without it.
With version 1.6.0, module descriptors can take most of the forms accepted by XS and code should begin migrating to prefer those forms.
-
Prior to version 1.6.0, a
StaticModuleRecord
,ModuleSource
, virtual static module record, or virtual module source could be used in place of a module descriptor. These should now be boxed in a module descriptor with asource
property. (Relatedly, we have renamed the@endo/static-module-record
package to@endo/module-source
to better reflect the direction of movement in the ECMAScript standardization process.)// before:import { StaticModuleRecord } from '@endo/static-module-record';const compartment = new Compartment({},{'my-module-specifier': new StaticModuleRecord(`export default 42;`),},);console.log(compartment.importNow('my-module-specifier').default); // 42// afterconst compartment = new Compartment({__options__: true,modules: {'my-module-specifier': {source: new ModuleSource(`export default 42;`),},},});console.log(compartment.importNow('my-module-specifier').default); // 42 -
Prior to version 1.6.0, a module namespace object could be used in place of a module descriptor. Particularly, the
Compartment
implementation included amodule
method that could be used to acquire the module namespace object of a module that has not yet been loaded, such that a module namespace could be used to link compartments or settle cyclic dependencies. This practice is deprecated now in favor of using anamespace
module descriptor.// before:const c1 = new Compartment({},{'c1-module-specifier': new StaticModuleRecord(`export default 42;`),},);const c2 = new Compartment({},{'c2-module-specifier': c1.module('c1-module-specifier'),},);console.log(c2.importNow('c2-module-specifier').default); // 42// afterconst c1 = new Compartment({__options__: true,modules: {'c1-module-specifier': new ModuleSource(`export default 42;`),},});const c2 = new Compartment({__options__: true,modules: {'c2-module-specifier': {namespace: 'c1-module-specifier',compartment: c1,},},});console.log(c2.importNow('c2-module-specifier').default); // 42Module descriptors with the
source
key will construct a fresh instance of the source in this compartment. Module descriptors with thenamespace
key will link to a module instance in the designated compartment. -
Prior to version 1.6.0, a module descriptor with
record
property could be used to instantiate a module with a differentimport.meta.url
than the associated module specifier. These descriptors should migrate tonamespace
module descriptors and must explicitly designate thecompartment
property if they previously relied on the defaultcompartment
. The default compartment fornamespace
andsource
descriptors is the “parent compartment”: the compartment for whichCompartment
is the initial, intrinsiccompartment.globalThis.Compartment
. The default compartment forrecord
descriptors is child compartment.Because you can otherwise only refer to a compartment with a reference to that compartment,
source
andnamespace
descriptors that refer back to their own compartment, instead of the default parent compartment, are not expressible in themodules
option and must move to a hook likemoduleMapHook
,importHook
, orimportNowHook
.// before:const compartment = new Compartment({},{'submodule/dependency': new StaticModuleRecord(`export default 42;`),dependent: {record: new StaticModuleRecord(`import meaning from 'dependency';export default meaning;`),specifier: 'submodule/dependent',},},{resolveHook(importSpecifier, referrerSpecifier) {const path = referrerSpecifier.split('/');path.pop();path.push(...importSpecifier.split('/'));return path.join('/');},},);console.log(compartment.importNow('dependent').default); // 42// afterconst compartment = new Compartment({__options__: true,modules: {'submodule/dependency': new ModuleSource(`export default 42;`),},importNowHook(specifier) {if (specifier === 'dependent') {return {source: new ModuleSource(`import meaning from 'dependency';export default meaning;`),specifier: 'submodule/dependent',compartment, // reflexive};}},resolveHook(importSpecifier, referrerSpecifier) {const path = referrerSpecifier.split('/');path.pop();path.push(...importSpecifier.split('/'));return path.join('/');},});console.log(compartment.importNow('dependent').default); // 42
Support for the deprecated forms will be removed in a future, major version.
Line numbers
When running transpiled code on Node, the SES error taming gives line-numbers into the generated JavaScript, which often don’t match the the original lines. This happens even with the normal development-time lockdown options setting,
errorTaming: 'unsafe'
or setting the environment variable
$ export LOCKDOWN_ERROR_TAMING=unsafe
To get the original line numbers, this release
adds 'unsafe-debug'
.
This errorTaming: 'unsafe-debug'
setting should be used during development
only when you can sacrifice more security for a better debugging experience,
as explained at
errorTaming
Options.
With this setting, when running transpiled code on Node (e.g. tests written in
TypeScript), the stacktrace line-numbers point back into the original source,
as they do on Node without SES.