-
-
Notifications
You must be signed in to change notification settings - Fork 66
002 - Clarification on consuming CommonJS from ES modules #26
Comments
Why would it use import * as util from './util';
console.log(util.getName()); |
Well, assume it's strict mode. The equivalent export var name = "esmodule";
export function getName() {
return name;
} In which case, importing and calling just I could be wrong, but even if I am, it seems like the correct behavior for this case should be explicitly called out. |
@nzakas We have no way to create a reference for the console.log(getName()); // global.name |
On a slightly different note, it is possible to do if the vms did variable expansion so import {getName} from 'foo';
getName(); Approximately became: import * as $foo from 'foo';
$foo.getName(); Though I don't see how this would be possible using ECMA262 currently. |
I'm not terribly familiar with Node.js internals, but it seems like when registering ES module bindings, you would be able to do In any event, the high-level concern I had is that whatever the behavior will be, it's not very clear in the current proposal. |
@nzakas we could do that if we had clear timing on when exports change, we don't know that timing: exports.name = 'exported';
exports.foo = function () {return this.name;};
// something mutates exports.foo (bluebird.promisfyAll, APM, spy, idk) We would not know exactly when the export was changed, and it is unsafe for us to change all of the exports of a CJS module to a getter + the module.exports could be frozen. |
@bmeck hmm ok...I'm not sure I understand yet, but I'll dig through the spec another time rather than bothering you with more questions. Thanks for your time. |
@nzakas is it ok to close this issue, or do I need to clear up stuff still? |
I still think the proposal needs a precise description of what happens to the value of |
the this value is not bound, so normal access rules apply. by calling out that we will be delegating through an ObjectEnvironment however, the implied
I guess I am missing something [about the question]. Nothing happens to the value of |
will try and think of a way to phrase this. But, unsure how to phrase that simply, as no value for |
Simply: is |
@nzakas it is defined based upon access/invocation, it is not defined to an absolute value. Just like how In your specific example of: import { getName } from "./util";
console.log(getName());
|
@bmeck so why can't you just say that? |
@nzakas because it is not bound like console.log(getName.call({name:"not bound"})); logs |
Eh, I don't have enough energy to keep going around on this. My point remains: I believe the value of |
Something that might help clear things up would be an example of an ObjectEnvironment changing the const promprom = Promise.resolve('not bound');
function log(v) {
console.log(v);
}
with (promprom) {
then(log); // not bound
}
const then = promprom.then;
then(log); // #error I understand it is not well stated, but it is defined in the proposal. I just have no clear way to express it in plain english in order to amend the proposal. |
Hey everyone, the TypeScript team has looked at various different facets of the module loading interop between CommonJS and ES. Our perspective is shaped by the following needs:
After discussing with @bterlson, @bmeck, @ajklein, @caridy, we feel that the following ideas address the concerns brought up above, while keeping these needs in mind. No property pluckingThe current proposal set forward states that a CommonJS module is primarily made available as a default import. The proposal then further has the notion of property plucking, where properties on the default import are also made available as named imports. This process is also called "hoisting". This practice is likely to have certain negative consequences. One major issue is that it is not clear what host object a "plucked" named import is bound to (i.e. it isn't clear what the A more tangible issue for Node users is that this makes it difficult for library authors from migrate to ES modules, because naively doing so would cause breaks for ES consumers. For instance, consider the following file module.exports = function() {
// ...
};
module.exports.bar = "hello"; Imagine a consumer that is written using ES modules: import f, { bar } from "./foo.js";
// 'f' is callable.
f();
// 'f' has a member named 'bar'.
f.bar.toLowerCase();
// We can also use 'bar' as a named import.
bar.toLowerCase(); Notice that because of property plucking from the Now when export default function() {
// ...
};
export var bar = "hello"; However, breaks the usage of function d() {
// ...
};
d.bar = "hello";
export default d; However, this breaks the usage of We believe that by default, a CommonJS module should only be made available using a default import. Transpilation SupportInterop can work well enough natively between ES modules and any existing module system if it plans for it. That is, we can plan out the interop behavior between CommonJS and ES modules for the future, but that means that there is still a gap for people on older versions of Node. Transpilers like TypeScript and Babel fill in that gap so that users can still author in ES but target older versions of Node. If CommonJS modules are only brought in as a default, it becomes impossible to define named exports for ES consumers. Modules also need to be able to affect the shape of the namespace import. One way to enable this is to "pluck" properties as above, however:
The In the case that an For instance: // CJS library a.js
module.exports.greeting = "hello!";
// CJS library b.js
module.exports.farewell = "hello!";
module.exports.__esModule
//
// ES consumer:
//
import a from "./a.js";
import * as b from "./b.js";
// 'greeting' is accessible on the default import.
a.greeting.toLowerCase();
// 'farewell' is accessible on the namespace import.
b.farewell.toUpperCase(); Default Substitution for
|
Also please remember the transitivity between CommonJS <-> ES modules. |
Status update on transitivity and hoisting/this/etc: After several talks: VMs cannot implement property hoisting, it invalidates a lot of assumptions about what can happen on variable access and does not fit in current codegen models. It could theoretically be possible to have hoisting, but still unsure / has other issues. The main other issue is transitivity. Given: // a
export let foo = 0;
setInterval(() => { foo+= 1; }, 1000) // b
module.exports = require('a'); // c
import {foo} from 'b';
In the case of hoisting, this works, but has the Without hoisting, any import will need to have knowledge that something is really an ES module namespace. Like the Whatever the eventual solution, the browser will need to be compatible with Reflective Module Records. I am unclear that Still thinking about things though, just giving a small update / summary of current situation. |
Is there a reason we can't just assume that named exports from CommonJS are just treated as snapshots and not live bindings? Then it would be possible to do function binding as well etc. Yes it's limited, but doesn't that capture the majority use case where users need named exports, without heavy cost? Then if users really need live bindings, import the default export, and access the member property rather? Also my two cents on __esModule - it was very much to deal with transpilation interop. When real ES modules objects are run against this transpiled code, the plan was to extend the interop to check not just |
As mentioned in last CTC named imports from CJS are not going to land in the initial implementation due to inability to have property delegation and synchronization issues. |
In the ES modules proposal, there are several examples related to loading CommonJS modules from ES modules, but there is no example covering the use of
this
in an exported module. For example:The
getName()
method makes use ofthis
to determine the value to return. The proposal states that exports from CommonJS modules can be loaded in ES modules viaimport
using those names:My question is if the
this
binding remains ingetName()
or not? I would expect it to, but I don't see that mentioned in the proposal. In effect, that would mean the local bindinggetName
in this example would not be directly equal togetName
as exported fromutil.js
but rather equivalent togetName.bind(module.exports)
.Is that the way it's intended to work? If so, can that detail be added into the proposal?
The text was updated successfully, but these errors were encountered: