benjamn / reify

Enable ECMAScript 2015 modules in Node today. No caveats. Full stop.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Make way for async import.

jdalton opened this issue · comments

The esm built-in import() will be async. I ❤️ the module.import namespace as a userland alternative. The current module.import is synchronous so I propose renaming module.import to module.importSync (similar to the *Sync forms of other Node built-in methods) leaving room for module.import to be the async form to align more closely with built-in import().

Meteor has module.import and module.dynamicImport, for what it's worth. If the names were changed, I would suggest module.importSync and module.importAsync (since "dynamic" doesn't have a good antonym in this context).

I think aligning with Node's naming scheme makes more sense here. JS is going with an async import() so it's a nice fit to have a module.import be async too (it fits the JS and Node naming scheme).

I'm not up on Meteor's current use of reify but maybe a more Meteor-centric fork would be a way to continue to support them (like meteor-reify). That way instead of trying to ride the line of Node's and Meteor's ecosystems, they could have a package more tailored for their ecosystem and they won't take priority when it comes to decisions for something in Node's ecosystem.

Any idea if Node will implement a Module.prototype.import method? If they did that (or even if they seemed likely to), I'd be sold.

Any idea if Node will implement a Module.prototype.import method?

I pinged Bradley and he says probably not. I think that's a good thing because that leaves a clear user-land alternative that looks built-in enough without worrying about conflicts with built-ins.

Update:
Talking more to Bradley it looks like they plan to freeze all JS prototypes/objs including those of Module which presents problems for reify's current implementation.

Actually talking more to Bradley they plan to freeze all JS prototypes/objs including those of Module which presents problems for reify's current implementation.

That's seems unnecessarily hostile to existing JS conventions allowing .prototype extensions for built-in constructors. I strongly oppose that idea, @bmeck! What problems do you believe you're solving?

@benjamn preventing tampering which was heavily prevalent in CJS. In particular loading guarantees/isolation on a per package level are needed if we want to guarantee loading of signed packages. .prototype has global affects, use hooks instead.

You can always provide a wrapper object that has mutable prototypes and works on the frozen object in a Proxy-like behavior.

To put this into perspective, Meteor will literally ship a fork of Node that does not freeze those prototypes, if it comes to that. What you call "tampering," I call thoughtful interoperation with the existing CommonJS module system.

Edit: Looks like this won't be necessary. 🎉

@benjamn no clue on context of

"thoughtful interoperation with the existing CommonJS module system"

like I said, you can just wrap if you want to ship a mutable system. Default is aiming for tamper resistant to prevent the issues that were problematic from global mutation of the CJS loader. Even then, I view this as similar to mutating Element.prototype.* in browsers, which isn't always reliable if you are trying to change default behaviors.

Is a discussion of freezing prototypes happening somewhere currently so I can familiarize myself with what's motivating it?

@benjamn it has been talked about in CTC meetings, no issue open on it though.

you can just wrap if you want to ship a mutable system

Where would I do that wrapping? Currently Node offers no hooks for transforming the module object, short of recompiling the module via something like require.extensions[".js"]. If you're going to make it easier to use a custom Module wrapper simultaneously with this freezing plan, that would be somewhat reassuring.

Even then, I view this as similar to mutating Element.prototype.* in browsers, which isn't always reliable if you are trying to change default behaviors.

I view it as more like extending Array.prototype, which has a long and relatively happy history.

tamper resistant

If you can make it tamper-proof, then it may be a good idea. If the best you can do is "tamper resistant," please don't even bother.

Where would I do that wrapping? Currently Node offers no hooks for transforming the module object, short of recompiling the module via something like require.extensions[".js"]. If you're going to make it easier to use a custom Module wrapper simultaneously with this freezing plan, that would be somewhat reassuring.

In your entry point / bootstrap code you would wrap any loading operation and ship your own loader. Loader hooks are currently in discussion as ESM is a bit tricky to sort out isolation of hooks.

I view it as more like extending Array.prototype, which has a long and relatively happy history.

Disagree since this affects how host objects from the environment itself function. We also have the option of what ECMAScript spec did of setting a null prototype for ModuleNamespaceObject if that seems preferable. Though that would be a bit slower/memory hungry.

If you can make it tamper-proof, then it may be a good idea. If the best you can do is "tamper resistant," please don't even bother.

The goal is to be tamper-proof, there is no 100% proof of this though. I stick to resistant as the term until then.

In your entry point / bootstrap code you would wrap any loading operation and ship your own loader. Loader hooks are currently in discussion as ESM is a bit tricky to sort out isolation of hooks.

The problem with global loader hooks is that they don't automatically know the current module.id, which is what makes the Module.prototype object such a convenient place to put new methods that need that information.

We also have the option of what ECMAScript spec did of setting a null prototype for ModuleNamespaceObject if that seems preferable.

The namespace object is not what I'm talking about. In CommonJS terms, the namespace object is analogous to module.exports, whereas I'm interested in module itself.

I'm honestly surprised that Node wouldn't want to implement import(...) in terms of Module.prototype.import, since require is implemented in terms of Module.prototype.require.

The goal is to be tamper-proof, there is no 100% proof of this though. I stick to resistant as the term until then.

I won't keep arguing this now, because I know you understand my point. Breaking backwards compatibility for the sake of preventing something you can't truly prevent is a murky value proposition. Whenever you have a plan for this up for discussion, I would love to participate.

@bmeck is the plan to freeze them in esm only, leaving cjs as is?

By the way, @bmeck, I apologize if my tone seems hostile. I'm really just a bit surprised, and eager to understand the details. Thanks for all your responses here.

The problem with global loader hooks is that they don't automatically know the current module.id, which is what makes the Module.prototype object such a convenient place to put new methods that need that information.

ESM is not based upon the CJS require('module').Module. We are not changing any parts of the CJS loader (including not adding support to require(esm) [in light of import()]).

The namespace object is not what I'm talking about. In CommonJS terms, the namespace object is analogous to module.exports, whereas I'm interested in module itself.

I'm honestly surprised that Node wouldn't want to implement import(...) in terms of Module.prototype.import, since require is implemented in terms of Module.prototype.require.

Think this is covered by the above statement, not sure though.

I won't keep arguing this now, because I know you understand my point. Breaking backwards compatibility for the sake of preventing something you can't really fully prevent is a murky value proposition. Whenever you have a plan for this up for discussion, I would love to participate.

The goal is to not break any existing CJS system, this mostly means we cannot re-use much of the existing system since it was mutable and/or has different constraints than ESM.

Ok, I think I'm feeling better now! The Module.prototype.* extensions that I've added are all about simulating ES modules in CommonJS, and I have full confidence Reify won't be needed once everything is native ES modules. In the meantime, I would really appreciate it if require("module").Module.prototype remained mutable.

All CJS loader components (except CLI resolution on startup) will be untouched. require("module").Module.prototype will remain mutable.

Sorry for the fire drill @benjamn @bmeck. 🚒 💦 🔥

Moving to #85.