-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reify module instance #51
Conversation
README.md
Outdated
specifier: string, | ||
record: StaticModuleRecord | SyntheticStaticModuleRecord, | ||
loader: Loader, | ||
importMeta?: Object, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Threading a user-defined import(fullSpecifier: string)
or removing the specifier
argument and providing an import(importSpecifier: string)
bound to the implied fullSpecifier
might obviate one use of the Loader
.
For hardened JavaScript isolation, we would need a way to construct a reified global environment record and pass that here instead of Loader.
5f6a07e
to
93e9fa9
Compare
I think I’ve convinced myself that The Loader constructor makes it convenient to pass entries from its static record and instance memos from host to guest loader. We would be significantly more dependent on import reflection to obtain reified StaticModuleRecords (by any name). We would also lose Moddable’s support for passing static module records from host to guest by name. They would have to be passed by StaticModuleRecord object as obtained by import reflection instead. |
In short, I don’t support this design direction, but wanted to give it a run in good faith and will of course be receptive if more folks prefer module loading without a |
93e9fa9
to
1a68100
Compare
README.md
Outdated
bindings: Array<Binding>; | ||
|
||
// Indicates that initialize needs to receive a dynamic import function that | ||
// closes over the referrer module specifier. | ||
needsImport: boolean; | ||
|
||
// Indicates that initialize needs to receive an importMeta. | ||
needsImportMeta: boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These reflective properties go from “nice to have” to “essential” when the Loader
is user code and can’t reach into internal slots.
// Constructs a global environment with its own globalThis containing | ||
// unique bindings for `eval` and `Function`. | ||
interface Global { | ||
constructor(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please consider #38
README.md
Outdated
// Links the module instance with another module instance | ||
// for one of the importSpecifiers declared in the constructed | ||
// bindings. | ||
link(importSpecifier: string, instance: ModuleInstance); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How can this new design handles dynamic import?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A dynamic import hook must be passed as a ModuleInstance constructor option, which will throw if the static module record has needsImport
set and import
is not provided.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This design seems right to me - in that the HostImportModuleDynamically
hook effectively needs to be exposed as a function for each individual module instance in this way (along with the globals). Nice to see we agree on how it fleshes out!
Also agreed, the repeating of this information for all modules in the same compartment is clearly simplified by having a singular loader that can share these parameters between modules, and even in turn removes the need for the dynamic import hook even being necessary as well - and these are strong arguments for a loader over an unmanaged instance model.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’m arriving at the harmonious conclusion that the union of Loader and ModuleInstance is necessary for this specification regardless of which ones get revealed to the end user. Existence of %ModuleInstance% is implied by Loader, and Loader is implied by %Realm%, so we may as well have both in the sketch and litigate later about what we ought to reveal to user code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would you want to be able to get different results between dynamically and statically importing the same specifier? (modulo thenable modules)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You certainly wouldn't, which is also another possible argument for loader-level over instance-level management.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Cardiy is also a proponent of exposing ModuleInstance
and not exposing a Loader
. A difference he proposes is that ModuleInstance
would use an importHook(importSpecifier, moduleInstance) => Promise<ModuleInstance>
hook for both static and dynamic import. There would need to be a link()
method with no arguments that drives the importHook
for static imports before initialization.
Caridy believes it would be sufficient for the ModuleInstance
implementation to enforce local consistency of static and dynamic import by memoizing importHook
locally. I also find that sufficient, but because it would be a more significant memory burden, and because the importHook
would presumably want to enforce stronger consistency invariants among modules, I would be open to deferring the responsibility of maintaining the consistency invariant to the user-provided hook.
For the purposes of isolating guest code, the ModuleInstance
would likewise need to accept an optional alternate global environment and I have not reckoned out whether that would be sufficient to implement a compartmental loader in user code.
Please let us know what you think. I will probably sketch that alternative view for purposes of discussion. I’m attracted to the idea of not having to convince TC39 of the exact shape of module descriptors, if that’s a possibility, though the Compartment
API is still quite convenient.
README.md
Outdated
// Links the module instance with another module instance | ||
// for one of the importSpecifiers declared in the constructed | ||
// bindings. | ||
link(importSpecifier: string, instance: ModuleInstance); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method seems kind've strange given that it expects a specifier but the ModuleInstance
itself has no way to reflect on what specifiers are requested by the record
given to the constructor. Or even what the current link status of a given ModuleInstance
is.
It would be helpful to expose what specifiers still need binding, so that we more easily write a linker:
async function link(moduleInstance: ModuleInstance) {
// Need to know what specifiers still need linking so we can
// recurse and link all the child modules as well
for (const specifier of moduleInstance.unlinkedSpecifiers) {
const module = await resolveModuleSomehow(specifier, moduleInstance);
moduleInstance.link(specifier, module);
// Link child modules
await link(module);
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ModuleInstance captures the underlying StaticModuleRecord, which in turn has a bindings
array on which the Loader implementation (either user code or one built into the language), can derive a list of all the necessary linkage and track which one’s it has accounted for. Of course, the ModuleInstance will have to track all of this as well internally and maintain the invariant that a module specifier is only linked once, and that all links are satisfied before it is initialized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ModuleInstance captures the underlying StaticModuleRecord, which in turn has a bindings array on which the Loader implementation (either user code or one built into the language), can derive a list of all the necessary linkage and track which one’s it has accounted for.
It looks like the latest changes make ModuleInstance
a hidden spec object, so my initial comment is a moot point now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think we’re loosely converged on hiding %ModuleInstance%
at this moment, but I’ve concluded it’s useful to keep and continue to develop the concept as a spec fiction, with the option of revealing it if it receives a lot of support.
1a68100
to
0c1798f
Compare
I’ve pivoted the proposed change to introduce a |
I’ve captured the feedback from this conversation, I hope fairly and sufficiently tentatively. |
8ff9951
to
01e4a44
Compare
I’ve rebased this on |
I intend to reprise a pull request in this direction, per feedback from Caridy and positive responses from the Module Harmony call, but wish to leave this PR as-is to archive the state of our conversation. |
In this change, I propose the reification of ModuleInstance per @guybedford and @lucacasonato’s presentation at TC39 plenary today, June 8th. The premise is that ejecting ModuleInstance would allow us defer the definition of Loader to user-code, that
StaticModuleRecord
andModuleInstance
(by any other name) should be sufficient to construct a loader.I found that I was unable to eliminate
Loader
from the design for two reasons:eval
corresponds to the intrinsiceval
for that environment.