Created
March 22, 2016 15:34
-
-
Save jbrantly/29969e8448d40538832a to your computer and use it in GitHub Desktop.
The problem with the CJS-ES6 gap
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ES6 exports | |
| export var foo = 1 | |
| export var bar = true | |
| // CJS analog | |
| module.exports = { | |
| foo: 1, | |
| bar: true | |
| } | |
| // Importing all exports from the above ES6 module | |
| import * as mod from 'module' | |
| // I think it can be argued that if you're allowing CJS to be imported using ES6 syntax, then | |
| // the above CJS module could also be imported using: | |
| import * as mod from 'module' | |
| // However, if you allow the above, then it in turn also means you can do this: | |
| // CJS module | |
| module.exports = function foo() {} | |
| // Importing... | |
| import * as func from 'module' |
Author
Actually there are a bunch of differences that are visible from ModuleNamespaceObjects, it should become pretty apparent for some cases like module.exports = Promise.resolve(1) since .then would fail (anything using this for that matter will probably have problems), primitives would be boxed as objects, the namespace is frozen so it is read-only and cannot be extended. Explaining hoisting is just something that will have to be done if we want named imports to work with CJS.
I heavily recommend people use import mydefault from 'foo' over * when doing interop with CJS.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@bmeck First off, just to clarify my position, I'm not really arguing for or against how things should be done. I'm trying to address the original question by @RyanCavanaugh:
This is how people arrive at that conclusion: conceptually, not technically. You make a lot of great technical points, but let me talk about some conceptual stuff for a second.
While technically true, this was not the original concept. From the CJS Spec:
So conceptually, CJS originally did export a dictionary (I use the term loosely, not technically) of multiple values that represented the API of the module. In fact, the early code typically looked like this:
I believe Node (not the spec) introduced the concept of setting the
exportsobject directly by usingmodule.exports =. It was also a debated topic.So I feel like people coming from a CJS background understand that while a CJS module might return a single value, often the conceptual intent is to be a dictionary of values that constitute the API of the module (eg, the original spec). I think this is in the same conceptual boat as
ModuleNamespaceObjectalbeit with some real technical differences that you've pointed out.So, if someone had that in mind and is being introduced to ES6 modules and they see something like
I think it's reasonable that they can immediately see the similarities with CJS there. Then they see
as the way to import the whole dictionary of exports from an ES6 module. It's not much of a leap to say that if you're doing CJS/ES6 interop, it might make sense that the same syntax would import the conceptual dictionary of exports from a CJS module as well.
So now
import *means, to them, the "whole" of the module. What do you do when you encounter a CJS module that usesmodule.exports =? Not a big leap to go to the "whole" of the module.Again, I'm not disputing the technical points that you've mentioned, I'm trying to explain how a normal JS coder (not someone who's deep into specs and interop loaders) could think that it's OK to do
import *to import a module that hasmodule.exports = function () {}, and how while that might be a technical issue, I think it's fair to say there is some conceptual basis to the argument.