-
Notifications
You must be signed in to change notification settings - Fork 67
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
RFC: Realm.prototype.import #294
Comments
cc @caridy |
I'm confused, what would that binding option do in regular |
in regular import it wouldn't be very useful, but you can just do it to import the binding directly. It isn't very useful outside of the Realm API, just consistent. I'd be happy to discard this option. const { default: doSomething } = import('./my-code.js');
// Not a big pro, but avoids some destructuring
const doSomething = await import('./my-code', { binding: 'default' });
there is little justification for the dynamic import consistency case, but I still like when this model applies to realms |
How would this work with a thenable module? Would What happens if the resolved value is nullish? export function then(resolve) {
resolve(undefined);
} |
I need to refresh my brain on the evaluation of this but in any case, internal unwrapping before Whoever is requiring a binding, does it explicitly anyway. Let me run some tests. |
What I mean tho is, "binding" doesn't really make sense to me with I can see utility in filtering export names - that way, I could avoid a thenable module exercising that functionality - but that's not really "bindings". |
The name is fully open for bikeshedding. We could use something like |
I want to make sure I understand the semantics before suggesting a name :-) is it indeed a way to provide a list of export names, so that the resulting module namespace object I get will only contain a subset? |
yes, for Realms, we can't get a module ns object. |
Are you suggesting that code inside a realm that does |
code inside a realm should work normally without fingerprints. |
Whoever holds the Realm object should "own" the realm, presumably, so why couldn't they get that Considering they could |
The whole idea over the new Isolated Realms (#289) is to not transfer objects cross realms to also avoid identity discontinuity. We trigger code injection in the other Realm through #292 has an explainer being updated with that, the rendered specs already has that. How |
Clearly I'm missing a bunch of context here, apologies :-) If there's no way to get an object into, or out of, a realm, then it sounds like |
There is a lot to discuss about the Isolated Realms, we would probably benefit of having a sync call - maybe tc39 - so I can present the differences. Generally saying, My initial examples in this thread, shows some little usage for why we could still use |
A sync call sounds great; let's coordinate offline for that. In your initial examples, you can extract a function from the realm - that function could presumably return any possible object that exists inside the realm, unless that function was wrapped so that it could never throw an in-realm exception nor return an in-realm object (which would seem pretty complex and heavy-handed). |
This is all covered in the Isolated Realms API. :) |
Regarding the naming, there may be an alternative: importBindings(specifier:string , listOfExportedNames: string[]): Promise<PrimitiveValueOrCallable[]> That way the user can chose to import 0, 1 or multiple bindings using the same call. It'd force array destructuring for single imports, but I'm not sure if it's that bad. The resolved array would of course be from the local realm.
That's an interesting point. Currently a syntactic One way to remove the thenable surprise, at the cost of making the API a little more awkward, would be to split module import and binding resolution in separate calls. importModules(...specifiers: string[]): Promise<void>;
getModuleBindingValue(moduleSpecifier: string, bindingName: string): PrimitiveValueOrCallable; Using |
So why not have a wrapper module NS object? It can solves all problems here |
@mhofman right but you don't actually import a binding (even for a static, named import). you're importing a value, that might change out from under you (because static import is what makes the binding, not the act of importing) if it's from a non-const static named export. |
@ljharb, I'm not sure I follow your point. Do you mean that regular static import creates a binding, and the value might change, but for the isolated realms API we only have a plain value that won't change? I think that's why I like the separate calls I proposed last, as we can make it very obvious with the right function name that the value is a snapshot at the time of the call. Maybe foo.js: export let bar = 42;
export function inc() {
bar++;
} main.js: const r = new Realm();
await r.importModules('./foo.js');
let bar = r.getModuleExportedValue('./foo.js', 'bar');
const inc = r.getModuleExportedValue('./foo.js', 'inc');
console.log(bar); // 42
inc();
console.log(bar); // 42
bar = r.getModuleExportedValue('./foo.js', 'bar');
console.log(bar); // 43
// with the existing gotcha for callables since a
// new wrapper is created each time
assert(inc !== r.getModuleExportedValue('./foo.js', 'inc')); @Jack-Works, IMO creating a new module namespace wrapper is too complicated. We'd need to have getters to reflect the possibility of changing values as mentioned above, which would have to throw if the value is not a primitive or callable. It also wouldn't solve the problem of thenable modules: the |
@mhofman yes - i'm saying that dynamic import never creates a binding, it only provides a module namespace exotic object value, whose property values might change. If you're doing a non-static import, thus, that's the only value one should expect to get. Being forced to take a snapshot will break valid use cases (for which live bindings exist in the first place). |
I'm admittedly not very familiar with some of the history of the Realms API proposal, but my understanding is that the Isolated Realms API's concept as discussed in #289 is to not have complex objects shared directly between realms, but instead enable userland membrane libraries to create proxy objects representing objects from the other realm. Such a library can very well create a proxy to those module namespace exotic objects. What kind of use cases did you have in mind that would be broken by this low level API only allowing for the snapshot of module exports? |
Considering one const r = new Realm();
const value = await r.importBinding('./inside-code.js', 'value');
const other = await r.importBinding('./inside-code.js', 'otherValue'); // "cached" There are some good outtakes here: I'd like to merge @mhofman's idea into something a bit more ergonomic. I don't like providing the specifier all the time, I'd rather use the import return. I'm going with something that wrapps the namespace, and it's weird and it seems overkill: const r = new Realm();
const realmModule = await r.import('./inside-code.js');
// realmModule would have an internal list of the bindings you could "get"
const doSomething = realmModule.get('default');
const foo = realmModule.get('foo'); The pro here is requiring only 1 await to load the import. It resolves the problem of capturing binding values or not. This allows eventual extensions for The con is defining an interface for this A conservative approach without require a binding value would be just requiring a call to const r = new Realm();
const verifyIntegrity = await r.importValue('./installFakeDOM-framework.js', 'verifyIntegrity');
await r.import('./user-code.js');
const result = verifyIntegrity(); I don't often need any resolution for the user-code. |
yes |
I don't think so, it just an exotic object with a custom
In the old Realms API, this problem is already solved. The API returns |
During the SES meeting, it came to my mind the current status quo is also good enough if we layer things. It's a good refresher that Realms has just a low level API. We could layer modules out such as: module insideCode {
export { runTests } from 'test-framework';
import './my-tests.js';
}
const r = new Realm();
const runTests = await r.importValue(insideCode, 'runTests'); // renaming importBinding to importValue Or without the module blocks: // ./inside-code.js
export { runTests } from 'test-framework';
import './my-tests.js'; // from the incubator Realm
const r = new Realm();
const runTests = await r.importValue('./inside-code.js', 'runTests'); |
It seems like this suggestion hasn't quite caught on. I'm fine with sticking with |
@littledan this is correct. Thanks! |
Reflecting the explainer work from #292, I believe we might need to revive Realm#import, returning a promise that resolves to
undefined
to flag when the code injection is complete.This would allow use cases like the tests like:
Without this, it seems overkill to require
./my-tests.js
to export something because it would run in a new realm. Ideally, code should run seamlessly.The other option I like, suggested by @littledan, is reusing the
Realm#import
to set the binding within the options arg, fully replacingimportBinding
.This would match the ergonomics from import assertions.
This creates a new question to investigate if dynamic import should have a binding option regardless of it being in the Realms API.
The text was updated successfully, but these errors were encountered: