00:23
<rbuckton>
rbuckton: I do want to confer with you sometime in December (but not in the next few days) about the resource management, cancellation, and proxy revocation proposals. Do you attend SES's strategy sessions occasionally?
I haven't so far, mostly due to time constraints. I can make some time though.
00:28
<rbuckton>

My suggestion was to just close over regular bindings, and when serializing determine if the binding is:

  1. rewritable (i.e., globals)
  2. serializable (such as another module)
  3. non-portable (anything else)
    and throw if there are any non-portable values.

A side benefit would be that it would be an error to accidentally reference a variable that shadows a global.

00:29
<rbuckton>
To further clarify, we'd determine if a binding is rewritable or not rewritable, and if the binding is not rewritable, determine if its value is portable (i.e., serializable) or non-portable.
00:30
<littledan>
When blöcks was presented, there was a lot of skepticism about this particular aspect of the proposal. It is very deliberate that module blocks omits it.
00:31
<littledan>
I honestly don’t understand how we could make this mechanism work well enough, given how dynamic JS is and how this committee tries to meet expectations of all the edge cases working well (eg with eval)
00:31
<littledan>
I like that you have some details spelled out though
00:32
<rbuckton>
IIRC, the blöcks proposal wanted you to substitute the bindings explicitly when you instantiated the block
00:32
<littledan>
In particular I don’t know how “rewritable” would work. Module blocks is content with just reevaluating in the other realm.
00:33
<rbuckton>
In particular I don’t know how “rewritable” would work. Module blocks is content with just reevaluating in the other realm.
That's exactly what I mean by rewritable: "these are the things that you just reevaluate in the other realm"
00:33
<littledan>
Ah Ok
00:34
<rbuckton>
The current proposal allows you to lexically reference another module and explicitly breaks that model, since you're not just looking up that name in another realm.
00:34
<littledan>
So the other thing is, given module blocks, you can define a system which serializes a list of things and then passes them as arguments to a default-exported function. I guess a bit more unergonomic due to the duplication though.
00:35
<rbuckton>
So the other thing is, given module blocks, you can define a system which serializes a list of things and then passes them as arguments to a default-exported function. I guess a bit more unergonomic due to the duplication though.
I had thought of that approach when I discussed the proposal with my team on Monday.
00:35
<littledan>
The current proposal allows you to lexically reference another module and explicitly breaks that model, since you're not just looking up that name in another realm.
Yeah, I can empathize with this; this is why I raised the separate namespace idea above
00:36
<rbuckton>
Another approach would be to go the WASM route where you have to explicitly provide any external module references when instantiating the Module, which would mean no lexical scoping.
00:36
<rbuckton>
That would still work with bundlers, though they'd need to emit a chunk of code at the bottom of the file that linked all of the declarations together.
00:37
<littledan>
Yeah, Nicolo has sketched out this chunk of code in a gist. It would work with module expressions + the importHook in the module constructor
00:37
<littledan>
I got the impression that V8 was highly skeptical of this hook. Module declarations are more limited in expressiveness; more “static”
00:37
<littledan>
Anyway there are probably bundler use cases that need the dynamic hook
00:38
<rbuckton>
If there was no lexical closure for modules, you could still have import {} from id but you'd need to define those ids when instantiating the module, i.e. await import(module { import foo; }, { bindings: { foo: module {} } })
00:39
<Kris Kowal>
That would still work with bundlers, though they'd need to emit a chunk of code at the bottom of the file that linked all of the declarations together.
This is consistent with what’s possible with just module expressions and the Module constructor.
00:39
<rbuckton>
That would be roughly analogous to the blöcks proposal where you'd need to supply values for bindings.
00:41
<Kris Kowal>
Concretely, this is Nicolò’s sketch of a module expression and Module constructor bundle generator https://gist.github.com/nicolo-ribaudo/81f18db096659ac8447ca94f50f2c37a
00:41
<rbuckton>

You could even do that with module declarations and stitch them together using a with clause:

module foo { }
module bar { import foo } // NOTE: not a closure, just an unsatisfied binding
import bar with { bindings: { foo } };
00:42
<Kris Kowal>
In my opinion, module expressions fully solve the bundling problem.
00:42
<rbuckton>
Its definitely not as convenient
00:43
<Kris Kowal>
Pardon module expressions + Module constructor.
00:44
<Kris Kowal>
(ModuleSource constructor not being necessary to that end and if the committee is intransigent about ModuleSource providing a path to eval, I’m in favor of further separation of layers.)
00:44
<rbuckton>
In my opinion, module expressions fully solve the bundling problem.

I assume that a pure module expression solution would do something like:

const foo = module {}
const bar = module { import "#foo"; }
// replace resolver hook to resolve "#foo" with `foo`
// ...
// profit?
00:45
<Kris Kowal>
Though, in my opinion, ModuleSource is no more egregious a path to eval than <script type=module src=*>. It is certainly not a direct-eval evil.
00:46
<rbuckton>
I mean, there's always import("data:text/javascript,console.log('hello')") :)
00:46
<Kris Kowal>

I assume that a pure module expression solution would do something like:

const foo = module {}
const bar = module { import "#foo"; }
// replace resolver hook to resolve "#foo" with `foo`
// ...
// profit?
Close enough, though it won’t have to rewrite import specifiers. It can remap original specifiers to arbitrary other module blocks to effect the same linkage as occurred in the original physical locations. This is good because specifiers are typically scoped to not reveal the full physical location.
00:49
<Kris Kowal>
In any case, I think we get a lot of bang from the module harmony proposals even if ModuleSource is replaced with something like Module.parse that can confirm the integrity of a module string and reflect its import/export bindings. Bundlers need that and on the evaluator side, module expressions suffice. We would still want an ModuleSource inert constructor just to host the prototype, since module expressions have a source of that prototype. And, at the end of the day, that’s equivalent to what what we’d have with a full-power ModuleSource constructor under a no-unsafe-eval CSP.
00:50
<Kris Kowal>
Concretely though, new Module((module { import './foo.js' }).source, { importHook() { return fooModule; }) does the trick of remapping an import to a particular Module instance. You can extrapolate from there.
00:52
<Kris Kowal>
In fact, module expressions are preferable to module declarations as a vessel for bundles because the module can be injected verbatim between the braces of a module expression.
00:53
<Kris Kowal>
Though, no doubt in every case that matters, there will be a minification pass and a sourcemap, so that advantage isn’t likely to be realized in any case that matters.
00:54
<Kris Kowal>
Where module declarations shine though is that they do not require import() to trampoline into user space importHook functions, which shu hinted could limit their usefulness in production.
00:56
<Kris Kowal>
But the downside is that they place a new cognitive burden on developers, who will now need to choose between using lexical or stringy module specifiers on a case-by-case basis, depending on whether they want the bundle (or worker payload!) to come with its own singleton of a module or exit to the host’s singleton of the module.
00:57
<Kris Kowal>
That’ll be an attractive nuisance for the worker case, since each time you “add” an entrypoint module expression/declaration to a worker, that entrypoint will retain a separate instance of its module declaration graph and connect to shared instances from the host for any remaining stringy specifiers.
00:59
<Kris Kowal>
I think that’ll have some negative consequences for the ecosystem, where libraries will be compelled to export a module declaration instead of their own API, so that the consumer can decide whether they want to instantiate it or transport it.
00:59
<Kris Kowal>
And in fact, they do not need module declarations to have that choice. They get the same facility from module import reflection.
01:02
<Kris Kowal>
That is, postMessage can send an array of ModuleSources with a description of their linkage and the receiver can rehydrate the graph with Module and a little machinery, just like a bundle.
01:21
<littledan>
I think that’ll have some negative consequences for the ecosystem, where libraries will be compelled to export a module declaration instead of their own API, so that the consumer can decide whether they want to instantiate it or transport it.
Well, maybe reflective imports would be the bridge here… oh but this only covers the top level, not nested dependencies
01:24
<Kris Kowal>
Reflective imports would give you a module and module source, module bindings reflection would give you the shallow dependencies, import.meta.resolve would give you host resolution, recursive reflective imports would get you the whole working set.
01:25
<Kris Kowal>
Everything you need to construct a transportable artifact.
03:50
<littledan>
That would still work with bundlers, though they'd need to emit a chunk of code at the bottom of the file that linked all of the declarations together.
Yeah, it's exactly this chunk of code, which is implicit in what Kris is describing, that makes me feel uncertain about this direction, but in a way which is really hard to objectively back up at this point. Maybe, at some point, prototyping will give us more insight around performance issues about this. But beyond practicality, I was excited about having a fully declarative way to link modules together--probably the committee shouldn't attach much weight to this excitement.
05:19
<Kris Kowal>
Given that the stated scope of the proposal is bundling, limiting the reach of module expression and declaration imports to a single file would obviate all of my concerns. The names need not even be lexical, just file local.
07:57
<sffc>
Michael Ficarra: the Google i18n team is voicing explicit support for Well-Formed Unicode Strings
11:05
<Rob Palmer>
We are proposing to defer the January plenary meeting by one week to avoid overlap with Chinese New Year. https://github.com/tc39/Reflector/issues/453#issuecomment-1335074233
15:15
<littledan>
Given that the stated scope of the proposal is bundling, limiting the reach of module expression and declaration imports to a single file would obviate all of my concerns. The names need not even be lexical, just file local.
I keep hearing people say that module declarations are only for bundling, and although that was definitely the focus for my investigation there initially, the current proposal serves other goals as well. The most significant and commonly raised one is having server and client code in the same file. Anyway, this goal would be served by local and not lexical module declarations as well
15:16
<littledan>
I still don’t understand the concerns you have though, Kris
15:17
<littledan>
What I really disagree with is when people state that the feature is only for bundlers
16:18
<Kris Kowal>
Ah, Nicolò stated that the feature was primarily for bundlers. I agree that it would also be used for transmission to workers, which is really quite similar.
16:18
<Kris Kowal>
postMessage-as-bundler
16:20
<Kris Kowal>
To wit, if we could postMessage into a file and revive it over HTTP, structured clone would be a bundle format.
16:40
<littledan>
Primarily != only
16:42
<littledan>
I need to think more about this ecosystem implications issue. I guess we already have an ecosystem thing going on where some people distribute bundled packages…
17:14
<Kris Kowal>
I’ll make a point not to misrepresent the intent. Regardless of the intent, Hyrum’s Law applies. It’ll be used in any way it’s found useful.
22:23
<ljharb>
bakkot: is there a reason that union makes sure other has a callable has method, when it doesn't use it?
23:01
<bakkot>
ljharb: for consistency with the other methods
23:01
<bakkot>
we decided they should all have exactly the same expected arguments
23:02
<ljharb>
hm, ok. i haven't looked at the others yet, so i'm assuming some of the methods use all three, some two, some one, but all of them require all three as you indicated
23:03
<bakkot>
yup
23:03
<bakkot>
also some of them switch which ones get used depending on the relative sizes of the receiver vs the argument
23:04
<ljharb>
ah right, ok