Closed guybedford closed 4 months ago
Interesting. Probably this would be implemented via proxies to swap out the live binding so we don't call execute multiple times?
I am moving more in the direction of defining a type of module that engines can defer safely (rather than having a compile hint) and is also useful for the mental model of authors. So that would be more implicit rather than less. I think that what you are proposing above might be unlocked by the module compartments proposal or possibly even import reflection. It is also a valid way of achieving lazy modules.
There's definitely a bunch of ways to skin it, I guess the hope is to explore and refine some of the options in the space.
To throw out some further random ideas:
lazy import { x } from 'y': export lazy () { return x() }
- turn all lazy bindings into functions that return the binding and idempotent executionimport lazy ns from 'y'
: lazy imports only support namespace imports - ns is no longer a ModuleNamespace
but now a DeferredModuleNamespace
proxy. Any access results in execution.lazy import { x } from 'y' with executor z;
z();
is explicitly called by the user as the executor, with x
being the live binding that carries through regardless. Accessing the x
binding before execution via z()
gives a TDZ error instead of performing execution.import lazy ns from 'y': lazy imports only support namespace imports - ns is no longer a ModuleNamespace but now a DeferredModuleNamespace proxy. Any access results in execution.
This sounds possibly the most interesting? I was thinking we would have a proxy somewhere in there, but making the namespace a different kind of name space that does this sounds like what we want to do here.
lazy import { x } from 'y': export lazy () { return x() } - turn all lazy bindings into functions that return the binding and idempotent execution
This is middle of the road, and might work. Though It results in a lot of code needing to be updated.
lazy import { x } from 'y' with executor z; z(); is explicitly called by the user as the executor, with x being the live binding that carries through regardless. Accessing the x binding before execution via z() gives a TDZ error instead of performing execution.
I think this might be too disruptive to the goal. Though, I will think some more on this. maybe it makes sense, but it feels like this level of control is better done in the module compartments structure.
This sounds possibly the most interesting? I was thinking we would have a proxy somewhere in there, but making the namespace a different kind of name space that does this sounds like what we want to do here.
I can definitely get behind it as well. It seems straightforward to understand for users, and interestingly it fits into the same model as import reflection in using a keyword to get back something that isn't a namespace and doesn't support named bindings.
With regards to the exact syntax for this, I am still discussing with @littledan but I think we may want to be careful here. I know that there is an argument that can be made for the assert
syntax that assert
is just a check, but in practice (and what developers will infer from working with it) is that it modifies the loading pattern. As a result, developers may be confused about when there should be a keyword, and when there should be an entry in the assert list. But we can discuss this more broadly together or in the loading call.
I was wondering if it's been considered for this proposal to make the execution an explicit functional execution to try to align it closer to current JS execution semantics.
That is - still having the lazy imports, but relying on an explicit function call to define the bindings.
Effectively replacing:
with something like:
or maybe even turning the binding itself into a synchronous function that returns the binding:
Would something like that get around some of the issues currently being discussed for this proposal? Would be great to continue these discussions further, whether or not this issue is on the right track!