-
-
Save jbrantly/29969e8448d40538832a to your computer and use it in GitHub Desktop.
// 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' |
@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:
Baffled by folks wanting to use ES6 import syntax to do things ES6 modules can't do (e.g. importing functions with "import * as fn ...")
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.
CJS always exports a single value which is the value of module.exports this does not have multiple values
While technically true, this was not the original concept. From the CJS Spec:
- In a module, there is a free variable called "exports", that is an object that the module may add its API to as it executes.
- modules must use the "exports" object as the only means of exporting.
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:
exports.foo = 1
exports.bar = true
I believe Node (not the spec) introduced the concept of setting the exports
object directly by using module.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 ModuleNamespaceObject
albeit 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
export var foo = 1
export var bar = true
I think it's reasonable that they can immediately see the similarities with CJS there. Then they see
import * as mod from 'module'
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 uses module.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 has module.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.
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.
Completely disagree as
*
imports the ModuleNamespaceObject, which is a dictionary of[identifier,value]
pairs, keys areidentifier
is a valid JS identifier, it is not any given string[[Get]]
so property access works.CJS always exports a single value which is the value of
module.exports
this does not have multiple values. A great example to illustrate is:null
is a single primitive immutable value, with no properties; it cannot represent a module namespace by its very nature since it cannot have property access.So! How do you turn this into a module namespace? It is a single value. Use the same facilities that
default
exports use.becomes a ModuleNamespaceObject ~=:
Another example:
Since ModuleNamespaceObject cannot use
[[Call]]
they cannot be treated as functions, once again, move it todefault
Another example:
not valid id
is not a valid JS identifier, it cannot be put onto a ModuleNamespaceObject.So! Why does
import {foo} from 'bar'
work?Hoisting. After getting
Transpilers and interop "hoist" the property onto the module namespace using getters (or raw property copy [this has problems if they don't use getters]).
So the namespace becomes: