Skip to content

Commit

Permalink
Reify ModuleInstance
Browse files Browse the repository at this point in the history
  • Loading branch information
kriskowal committed Jun 10, 2022
1 parent 4ce940d commit 0c1798f
Showing 1 changed file with 101 additions and 9 deletions.
110 changes: 101 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ will prepare new, empty memos, and adopt the provided host-virtualization hooks.
The `loadHook` can still adopt entries from the host's memos by returning
a descriptor that refers to them by their full specifier.

The following sketch provisionally uses the TypeScript private field marker,
`#` to indicate internal slots.

```ts
type ModuleExportsNamespace = Record<string, unknown>;
type ModuleEnvironmentRecord = Record<string, unknown>;
Expand All @@ -175,18 +178,23 @@ type Binding =

// Loaders support ECMAScript modules and linkage to other kinds of modules,
// notably allowing for JSON or WASM.
// These must provide an initializer function and may declare bindings for
// Synthetic static module records are a *protocol* that loaders and module
// instances support for non-ECMAScript modules.
// These may provide an initializer function and may declare bindings for
// imported or exported names.
// The initializer is optional because reexport bindings may be sufficient
// definition for a synthetic static module record.
// The bindings correspond to the equivalent `import` and `export` declarations
// of an ECMAScript module.
type ThirdPartyStaticModuleRecord = {
type SyntheticStaticModuleRecord = {
bindings?: Array<Binding>,

// Initializes the module if it is imported.
// Initialize may return a promise, indicating that the module uses
// the equivalent of top-level-await.
// XXX The loader will leave that promise to dangle, so an eventual
// rejection will necessarily go unhandled.
initialize(environment: ModuleEnvironmentRecord, {
initialize?: (environment: ModuleEnvironmentRecord, {
import?: (importSpecifier: string) => Promise<ModuleExportsNamespace>,
importMeta?: Object,
// TODO: If module environment record also reflects the properties of
Expand All @@ -197,25 +205,48 @@ type ThirdPartyStaticModuleRecord = {
// If we add globalLexicals, to the proposal, they would need to be
// threaded through here as well, or layered on the module environment record.
globalThis,
}),
}) => void | Promise<void>,

// Indicates that initialize needs to receive a dynamic import function that
// closes over the referrer module specifier.
needsImport: boolean,
needsImport?: boolean,

// Indicates that initialize needs to receive an importMeta.
needsImportMeta: boolean,
needsImportMeta?: boolean,
};

// Static module records are an opaque token representing the compilation
// of a module that can be reused across multiple loaders.
// Static module records represent the compilation of a module that can be
// reused across multiple loaders.
// Some properties are both internal and reflected.
// Loaders consult the internal properties to avoid confusion and the
// reflected properties exist to empower user code.
// A reasonable alternative design would provide only a single public
// non-writable non-configurable data property for each.
interface StaticModuleRecord {
// Static module records can be constructed from source.
// XS allows third-party module records and source descriptors to
// be precompiled as well.
constructor(source: string);

// StaticModuleRecords capture a host-defined internal representation of the
// source with an internal slot.
#code: %StaticModuleRepresentation%;

// An internal slot capturing a representation of the import and export
// clauses of the source, in order.
#bindings: Array<Binding>;
// Static module records reflect their bindings for information only.
// Loaders use internal slots for the compiled code and bindings.
bindings: Array<Binding>;

// Indicates that initialize needs to receive a dynamic import function that
// closes over the referrer module specifier.
#needsImport: boolean;
needsImport: boolean;

// Indicates that initialize needs to receive an importMeta.
#needsImportMeta: boolean;
needsImportMeta: boolean;
}

// A ModuleDescriptor captures a static module record and per-loader metadata.
Expand Down Expand Up @@ -276,7 +307,7 @@ type ModuleDescriptor =
// where the behavior if the initialize function is async is analogous to
// top-level-await for a module compiled from source.
| {
record: ThirdPartyStaticModuleRecord,
record: SyntheticStaticModuleRecord,

// Properties to copy to the `import.meta` of the resulting module instance,
// if the `record` has a `true` `needsImportMeta` property.
Expand Down Expand Up @@ -384,6 +415,15 @@ type LoaderConstructorOptions = {
};

interface Loader {
#global: %Global%;
#resolveHook: ResolveHook,
#loadHook: LoadHook,
#importMetaHook: ImportMetaHook,
#recordMemo: Map<string, StaticModuleRecord | SyntheticStaticModuleRecord>;
#recordPromiseMemo: Map<string, Promise<StaticModuleRecord | SyntheticStaticModuleRecord>>;
#instanceMemo: Map<string, ModuleInstance>;
#instancePromiseMemo: Map<string, Promise<ModuleInstance>>;

// Note: This single-argument form differs from earlier proposal versions,
// implementations of SES shim, and Moddable's XS, which accept three arguments,
// including a final options bag.
Expand Down Expand Up @@ -434,6 +474,58 @@ interface Loader {

loadNow(fullSpecifier: string): void;
}

// Constructs a global environment with its own globalThis containing
// unique bindings for `eval` and `Function`.
interface %Global% {
constructor();
get globalThis: Object;
evaluate(string): unknown;

%eval%: EvalFunction;
%Function%: FunctionConstructor;
%Loader%: LoaderConstructor;
}

// ModuleInstance reifies an entangled pair of module environment record
// and module exports namespace from a particular array of bindings
// that correspond to the `import` and `export` declarations of a
// module.
interface %ModuleInstance% {
// Creates a module instance for either a static module record
// or a synthetic static module record.
// Distinguishes a synthetic by StaticModuleRecord brand check.
// Creates a module environment record that defers to the
// designated global environment record or that associated
// with the %ModuleInstance% constructor.
// The module instance will delegate to the given loader for dynamic import
// and will use the given loader's global environment for all eval and
// Function constructor behaviors.
constructor(
record: StaticModuleRecord | SyntheticStaticModuleRecord,
{
importMeta?: Object,
loader?: Loader,
}
);

namespace: ModuleExportsNamespace;

environment: ModuleEnvironmentRecord;

// Links the module instance with another module instance
// for one of the importSpecifiers declared in the constructed
// bindings.
link(importSpecifier: string, instance: %ModuleInstance%);

// Initializes a module instance.
// Throws an error if any of its invariants are not satisfied.
// The module instances for the given module environment record
// and their transitive module instance dependencies must be fully linked.
// Defers to the initialize function of a synthetic static module record,
// reifying and providing its own module environment record.
initialize(): void | Promise<void>;
}
```

## Motivating Examples and Design Rationales
Expand Down

0 comments on commit 0c1798f

Please sign in to comment.