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); // 42The 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); // 42This 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
modulesoption is an object that maps module specifiers to their corresponding module descriptor. These modules are “pre-loaded” at time of construction. - The
moduleMapHookis a function that accepts a module specifier and may return a module descriptor ifimportorimportNowcannot complete without it. - The
importHookNowHookaccepts a module specifier and may return a module descriptor ifimportNowcannot complete without it. - The
importHookaccepts a module specifier and may return a module descriptor or a promise for a module descriptor ifimportcannot 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 asourceproperty. (Relatedly, we have renamed the@endo/static-module-recordpackage to@endo/module-sourceto 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
Compartmentimplementation included amodulemethod 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 anamespacemodule 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
sourcekey will construct a fresh instance of the source in this compartment. Module descriptors with thenamespacekey will link to a module instance in the designated compartment. -
Prior to version 1.6.0, a module descriptor with
recordproperty could be used to instantiate a module with a differentimport.meta.urlthan the associated module specifier. These descriptors should migrate tonamespacemodule descriptors and must explicitly designate thecompartmentproperty if they previously relied on the defaultcompartment. The default compartment fornamespaceandsourcedescriptors is the “parent compartment”: the compartment for whichCompartmentis the initial, intrinsiccompartment.globalThis.Compartment. The default compartment forrecorddescriptors is child compartment.Because you can otherwise only refer to a compartment with a reference to that compartment,
sourceandnamespacedescriptors that refer back to their own compartment, instead of the default parent compartment, are not expressible in themodulesoption 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=unsafeTo 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.