00:45
<Kris Kowal>
I’m working on a sketch of what a user code Compartment looks like through the lens of Module, ModuleSource, and ExecutionContext https://gist.github.com/kriskowal/f48fb0c68a70ccbde7cd32c85cddc63c
03:21
<Jack Works>
Have a question
03:22
<Jack Works>
for example, I have a const a = new Module(source, hook, meta)
03:23
<Jack Works>
in which level it will execute twice? in different Realm? or in different Module constructor?
03:35
<Kris Kowal>
Once per object identity.
03:58
<Jack Works>
even it is used in multiple realms?
03:58
<Kris Kowal>
That is my understanding.
03:59
<Kris Kowal>
My understanding at the moment is that a module block corresponds to a Module instance, so locally it will only initialize once. But, every time a Module gets serialized and deserialized, it emerges as a new Module instance on the far side. The only parts of the module instance that are serialized are the source and importMeta, to the extent that importMeta is serializable, and the module instance is not memoized by the receiver.
04:00
<Kris Kowal>
The new Module instance would inherit the receiver execution environment’s import hook.
04:01
<Kris Kowal>
The module instance also has the [[Context]] internal slot that binds it to the original realm, so the namespace’s objects are guaranteed to be from the originating realm.
04:02
<Kris Kowal>
If we’re talking about same-origin <iframe> realms, still once because single identity.
04:02
<Kris Kowal>
If we’re talking about shadow realms, once per transit.
04:04
<Kris Kowal>
Though with shadow realms, assuming Static Module Records are treated as immutable after construction, transmitting between shadow realms or even between threads of the same process can be optimized. You still get fresh instances of the Module object, though.
04:06
<Kris Kowal>
And the reasoning for module fragments is that every module fragment has a unique ModuleSource consisting of the fragment and all the fragments it transitively depends upon, so each fragment is effectively equivalent to a single module block.
04:08
<Kris Kowal>
I think there are still open questions about the local semantics, like whether each evaluation of a module fragment produces a new Module instance, thus a new identity, thus a new namespace instance.
04:09
<Kris Kowal>
I suspect that’s the only reasonable answer, since module instances are superficially mutable. Having a singleton would create a covert communication channel.
04:11
<Kris Kowal>
The one hard rule is that a module block can’t write itself into the remote module map using the local key.
05:08
<littledan>
even it is used in multiple realms?
I can’t understand a case where multiple realms would permit importing the same Module. It seems like it should typically throw.
05:13
<Jack Works>
I can’t understand a case where multiple realms would permit importing the same Module. It seems like it should typically throw.
Create 2 modules in 2 realms, and the import hook returns the same module object, does that module return by the import hook evaluate twice?
05:19
<littledan>
My contention is that a well-behaved importHook would not do that
05:21
<Jack Works>
But it is possible
05:22
<littledan>
It simplifies a lot of things to have each Module run at most once
05:23
<littledan>
It is possible to use a Proxy in a poorly behaved way. We will need to define some kind of semantics for this case, but it doesn’t need to be pretty/perfect IMO
05:24
<Kris Kowal>
You’re suggesting that the dynamic import from one realm would fail to import a module from a realm with the wrong [[Context]]? (This seems fine to me.)
05:25
<Kris Kowal>
I too am fine with rough edges for same-origin-iframes. The language doesn’t set a high bar for sensible interactions with mixed intrinsics.
05:27
<littledan>
+10000
05:28
<littledan>
We may want to think about if we want a generic mechanism to serialize a Module over a ShadowRealm boundary but the answer isn’t “it’s the same one”
05:29
<Kris Kowal>
I’m also not sure that there’s any meaningful way to distinguish dynamic import of a module from one realm to another. My impression is that using dynamic import to kick off the module system is not much more than a cute way to spell Module.prototype.importMe. The module object is important, the import “function” doesn’t exist.
05:31
<littledan>
Right, since this is done by the importHook
05:31
<Kris Kowal>
Caridy did pass an idea in my direction that the import syntax could convey a referrer. I think we both realized that it shouldn’t.
05:32
<Kris Kowal>
There’s just no need since the module has an associated importMeta, and it would cause more problems than it would solve.
05:33
<littledan>
As much as I love dynamic scoping, this situation calls for lexical
05:33
<Kris Kowal>
Can’t tell if joke, but I’ll chuckle anyway.
05:34
<littledan>
I do love dynamic scoping… I don’t imagine I have many allies here in that
05:35
<Kris Kowal>
I might have been at some point. Maybe when I’m writing bash. I’d like to think I’d enjoy abusing gensyms and dynamic scope in Lisp.
05:35
<Kris Kowal>
Good feature for doing donuts in the parking lot and feeling clever.
05:37
<littledan>
Well it’s kinda what react-redux is based on, and I can’t think of a better design
05:37
<littledan>
Factor makes heavy use of dynamic scoping too… helps when you have a stack-based language
05:38
<Kris Kowal>
What the hell, I’ll join you deep off-topic…
05:39
<Kris Kowal>
At some point when I was between jobs, I did an experimental component-based web framework I called Guten Tags.
05:40
<Kris Kowal>
It’s sort of a “What if HTML smelled more like Lisp”
05:41
<Kris Kowal>
So a tag on one level was effectively a function application that produced a component at that position in the surrounding context.
05:41
<Kris Kowal>
And on the level below that, tags were arguments, which would produce fragments.
05:42
<Kris Kowal>
And you’d just import tags from other Guten Tag modules, which of course also looked like HTML.
05:42
<Kris Kowal>
But it had a sort of hybrid of lexical and dynamic scoping inspired by CSS selectors like :hover.
05:43
<Kris Kowal>
So, imagine that HTML element id’s are lexically scoped.
05:44
<Kris Kowal>
But HTML elements are just instances of fragments from a different lexical closure that has its own id namespace.
05:44
<Kris Kowal>
So, it had this notation that was like lexicalId:dynamicId
05:47
<Kris Kowal>
Was sort-of like <repeat id=loop value=todos><input type="text" value=todos:iteration></repeat>, where iteration was a name identified in the lexical scope of the repetition.
05:49
<Kris Kowal>
That’d project into the real DOM as just a reactive sequence of inputs with their value reactively bound to the their respective iterand.
13:41
<Jack Works>
oh, looks like 3rd party SMR cannot fully emulate ES Module?
13:42
<Jack Works>

consider this code:

// a
import { f_b } from 'b'
console.log(f_b)
export function f_a() {}

// b
import { f_a } from 'a'
console.log(f_a)
export function f_b() {}
13:45
<Jack Works>

The best I can do using the ThirdPartyModuleRecord is this:

const a = {
    bindings: [{ export: "f_a" }, { import: "f_b", from: "b" }],
    initialize(env) {
        function f_a() {}
        env.f_a = f_a
        console.log(env.f_b)
    }
}

const b = {
    bindings: [{ export: "f_b" }, { import: "f_a", from: "a" }],
    initialize(env) {
        function f_b() {}
        env.f_b = f_b
        console.log(env.f_a)
    }
}
13:49
<Jack Works>

The best I can do using the ThirdPartyModuleRecord is this:

const a = {
    bindings: [{ export: "f_a" }, { import: "f_b", from: "b" }],
    initialize(env) {
        function f_a() {}
        env.f_a = f_a
        console.log(env.f_b)
    }
}

const b = {
    bindings: [{ export: "f_b" }, { import: "f_a", from: "a" }],
    initialize(env) {
        function f_b() {}
        env.f_b = f_b
        console.log(env.f_a)
    }
}
This cannot emulate the ES Module above, because it is basically the equivalent of export let f_a = ... instead of export function f_a() ...
13:50
<Jack Works>

And SystemJS format can emulate this behavior:

System.register([
    "b"
], function(_export, _context) {
    "use strict";
    var f_b;
    function f_a() {}
    _export("f_a", f_a);
    return {
        setters: [
            function(_b) {
                f_b = _b.f_b;
            }
        ],
        execute: function() {
            console.log(f_b);
        }
    };
});

There are 2 stages in system js, the hoisted function declaration is set in stage 1, and the actual runtime code is run in stage 2 (execute).

13:51
<Jack Works>
I wonder if we can solve this problem in ThirdPartyModuleRecord (or it's new name VirtualModule?)
13:59
<Jack Works>
Also, I cannot emulate both export var (No TDZ) and export let (TDZ) without 2 stage initialization
20:23
<Kris Kowal>
It looks like this could be addressed by having virtual module sources expose an object analogous to the SystemJS setters.
20:25
<Kris Kowal>
That is, { bindings: Bindings, initialize(readFromHere, {import, importMeta}) => void, writeToHere }, by whatever names.
20:26
<Kris Kowal>
(formally ignoring optionality and the needs* properties)
21:57
<Kris Kowal>
Hm, that’s not quite right. The writer can’t be a property of the static module record.
21:58
<Kris Kowal>
But, for one, being able to emulate ESM is a stretch goal for the virtual module record. The subset is sufficient for most cases.
21:58
<Kris Kowal>
The SES shim has a protocol analogous to SystemJS, though, but calls the setters notifiers.
22:30
<Kris Kowal>
Ah, I see. The trick to supporting ESM with the virtual source module protocol is that all access and mutation to variables in the “imports namespace” needs to be rewritten to properties of the the environment object. I haven’t checked into how realistic that is, but it likely means that all var and function hoisting would have to be physically hoisted.
22:31
<Kris Kowal>
I think it’s possible but not clean.
22:33
<Kris Kowal>
In SES, we have a self-imposed requirement that ESM sources need to be debuggable without source maps, with preservation of line numbers and, as often as possible, column numbers. That simply wouldn’t be possible with the virtual source protocol and requires write barriers / notifiers.