New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a spec mode to transform-es2015-modules-commonjs #4964
Changes from all commits
8296965
4a746ee
43bd0e9
3389e9f
17cd0b9
42d870e
477cb99
6fbf2ac
f15b506
906e8c1
b88c6c5
8f18792
60900bd
a57449f
d09ac54
cf5e102
538a585
ba377a9
adea187
62ade30
418ee6b
4afaba8
ad9fc26
b37066f
cf7a7e4
0efc438
ebfb274
c083987
0c2bd66
6fd580d
2e0f740
c428c52
f3b8e11
f18e5dc
56164e7
6340b24
f6c014a
a861f64
9ec0147
de10efb
2338252
cd388c9
3f2f1a7
f9dd485
f27fe08
2fddb7a
ba4d3d6
82455bc
33a956d
db72c87
8ba476d
97db049
31205a8
f6f291f
1861e4d
a371bb1
d8c8e86
c920f26
03c6ef9
4d54fdc
76eea10
8aa75b5
a8b094c
157ab45
79a87ca
0a6ead9
b2cb529
5eb912f
52f9b5b
60c0e9c
79941d8
a95a651
a77935c
6dbd228
789aeae
5f62061
3b7f443
c6aec5f
d212a37
77ae141
fe39f70
d6ce6ab
df80908
2241a88
9989e97
33f2325
ca4b63b
65680c9
70d0f9a
e233d40
27474f6
1d63392
ccd3895
ba41759
838fb7b
6097372
ba6dda0
1c5bbcc
d9336f7
6066a22
6fa02a8
d50b59d
4d96719
9c6550e
7b222ac
195d824
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -396,6 +396,9 @@ helpers.interopRequireDefault = template(` | |
}) | ||
`); | ||
|
||
// interopRequireDefault doesn't technically match spec, | ||
// but where it does not match is not observable | ||
|
||
helpers.interopRequireWildcard = template(` | ||
(function (obj) { | ||
if (obj && obj.__esModule) { | ||
|
@@ -413,6 +416,93 @@ helpers.interopRequireWildcard = template(` | |
}) | ||
`); | ||
|
||
helpers.specRequireInterop = template(` | ||
(function (obj) { | ||
if (obj && obj.__esModule) { | ||
return obj; | ||
} else { | ||
var newObj = Object.create ? Object.create(null, { | ||
default: { | ||
value: obj, | ||
writable: true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this necessary? |
||
enumerable: true | ||
}, | ||
__esModule: { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it worth also tagging this somehow as a spec __esModule, just in case a future iteration of the helper wants to care about it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we assume that nothing actually checks for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, that's true. it was just a thought - it can probably be inferred with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would keep __esModule, you can check enumerability here to double check it is a "fake" es module. I am also concerned to keep w/ things that were already compiled in babel from the past. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was originally suggesting having two keys - keeping There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
value: true | ||
} | ||
}) : { | ||
default: obj, | ||
__esModule: true | ||
}; | ||
if (typeof Symbol === "function" && Symbol.toStringTag) { | ||
Object.defineProperty(newObj, Symbol.toStringTag, { value: "Module" }) | ||
} | ||
return (Object.freeze || Object)(newObj); | ||
} | ||
}) | ||
`); | ||
|
||
helpers.specImportCheck = template(` | ||
(function (module, imports) { | ||
if (!module.__esModule) throw new Error("Only ES modules can be checked"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This means you can only There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. aha, ok, makes sense |
||
var invalid = imports.filter(function (i) { return !Object.prototype.propertyIsEnumerable.call(module, i) }); | ||
if (invalid.length > 0) { | ||
var error = new Error( | ||
"Unknown export" + (invalid.length > 1 ? "s " : " ") + | ||
JSON.stringify(invalid) + | ||
" imported" | ||
); | ||
error.module = module; | ||
throw error; | ||
} | ||
}) | ||
`); | ||
|
||
// The name && typeof Symbol === "function" && ... line is from helpers.typeof, which may be needed | ||
// when polyfilled. Helpers might not be transformed when using external-helpers, so can't rely on | ||
// the typeof being augmented when targeting ES5. | ||
helpers.specNamespaceGet = template(` | ||
(function (module, name) { | ||
if (!module.__esModule) throw new Error("Only ES modules can be checked"); | ||
if ( | ||
typeof name === "symbol" || | ||
name && typeof Symbol === "function" && name.constructor === Symbol && name !== Symbol.prototype | ||
) { | ||
return module[name]; | ||
} | ||
if (!Object.prototype.propertyIsEnumerable.call(module, name)) { | ||
throw new Error("Unknown export " + JSON.stringify(name) + " imported"); | ||
} | ||
return module[name]; | ||
}) | ||
`); | ||
|
||
// sameValue function based on core-js's SameValue implementation | ||
// https://github.com/zloirock/core-js/blob/693767b/modules/_same-value.js | ||
helpers.specNamespaceSpread = template(` | ||
(function (exports, ownExports, module) { | ||
if (!module.__esModule) throw new Error("Only ES modules can be spread"); | ||
for (var key in module) { | ||
if (!Object.prototype.hasOwnProperty.call(module, key)) continue; | ||
if (key === "__esModule" || key === "default" || ownExports.indexOf(key) >= 0) continue; | ||
if (key in exports && sameValue(exports[key], module[key])) continue; | ||
|
||
Object.defineProperty(exports, key, { | ||
enumerable: true, | ||
get: (function (key) { | ||
return function () { | ||
return module[key]; | ||
} | ||
})(key) | ||
}); | ||
} | ||
|
||
function sameValue(x, y) { | ||
return x === y ? x !== 0 || 1 / x === 1 / y : x != x && y != y; | ||
} | ||
}) | ||
`); | ||
|
||
helpers.newArrowCheck = template(` | ||
(function (innerThis, boundThis) { | ||
if (innerThis !== boundThis) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,7 +42,7 @@ npm install --save-dev babel-plugin-transform-es2015-modules-commonjs | |
{ | ||
"plugins": [ | ||
["transform-es2015-modules-commonjs", { | ||
"allowTopLevelThis": true | ||
"spec": true | ||
}] | ||
] | ||
} | ||
|
@@ -62,6 +62,125 @@ require("babel-core").transform("code", { | |
}); | ||
``` | ||
|
||
## Options `spec` | ||
|
||
By default, `babel` actually implements importing very loosely, by | ||
supporting treating a commonjs export as if it was a namespace export. | ||
The exported namespace is also not frozen and has an incorrect prototype. | ||
|
||
The `spec` option, when set to `true`, tries to generate code that is as | ||
close as possible to what is required by the ECMA262 spec without relying | ||
on `Proxy`. The exports will be frozen, and imports will always be treated | ||
like ES modules. All imported names will be checked, at import time, if | ||
they exist in the exports of the imported module. If `let` and `const` are | ||
not transformed to `var`, the exports will also implement TDZ. | ||
|
||
Importing a commonjs module (say, the standard `fs` module) will always | ||
wrap it in an ES module that has a single `default` export. This means that | ||
some imports that work in non-`spec` mode, like `import { readFile } from 'fs'`, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This won't necessarily be true if we end up with interop for named CJS exports - @bmeck, is this still a possibility? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Possible. Spec does not explicitly ban it from happening. Implementations have concerns and order of operations may be hard for codegen. Still in talks. |
||
will result errors in `spec` mode. | ||
|
||
Note that exports, under this mode, always require runtime support for | ||
getters. It also is not possible to access or write to the commonjs | ||
`exports` or `module` objects; attempts to access them will result in | ||
TDZ errors at runtime. | ||
|
||
### Input | ||
|
||
```javascript | ||
import 'a'; | ||
import defaultImport from 'b'; | ||
import * as namespace from 'c'; | ||
import { pick } from 'd'; | ||
|
||
defaultImport(namespace.foo, pick); | ||
|
||
export { pick } | ||
export default function () {} | ||
``` | ||
|
||
### Output | ||
|
||
```javascript | ||
'use strict'; | ||
|
||
const exports = module.exports = Object.create ? Object.create(null, { | ||
__esModule: { | ||
value: true | ||
} | ||
}) : { | ||
__esModule: true | ||
}; | ||
if (typeof Symbol === "function" && Symbol.toStringTag) { | ||
Object.defineProperty(exports, Symbol.toStringTag, { | ||
value: "Module" | ||
}); | ||
} | ||
|
||
Object.defineProperties(exports, { | ||
pick: { enumerable: true, get() { return _d.pick; } }, | ||
default: { enumerable: true, get() { return _default; } } | ||
}); | ||
let _default = { | ||
default: function () {} | ||
}.default; | ||
(Object.freeze || Object)(exports); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if you're requiring getters, then |
||
|
||
require('a'); | ||
|
||
const _b = babelHelpers.specRequireInterop(require('b')); | ||
|
||
babelHelpers.specImportCheck(_b, ['default']); | ||
|
||
const _c = babelHelpers.specRequireInterop(require('c')); | ||
|
||
babelHelpers.specImportCheck(_c, ['foo']); | ||
|
||
const _d = babelHelpers.specRequireInterop(require('d')); | ||
|
||
babelHelpers.specImportCheck(_d, ['pick']); | ||
|
||
|
||
(0, _b.default)(_c.foo, _d.pick); | ||
``` | ||
|
||
## Options `specImport` | ||
|
||
This option enables only the half of `spec` mode that affects the imports, without | ||
changing how exports are generated. This would allow the generation of code that | ||
may still be compatible with engines that do not support getters. | ||
|
||
Note that the require helpers do use `Object.getOwnPropertyDescriptor` and | ||
`Object.defineProperty`, so ES5 polyfills may still be required. When running on an | ||
old engine that does not support `Object.defineProperty`, a polyfill to fake it like | ||
`es5-sham` is required. | ||
|
||
This option is **ignored** if `spec` is enabled. Enabling `spec` implies that this | ||
option is also enabled. | ||
|
||
### Input | ||
|
||
```javascript | ||
import { pick } from 'module' | ||
|
||
export default pick() | ||
``` | ||
|
||
### Output | ||
|
||
```javascript | ||
'use strict'; | ||
|
||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
|
||
const _module = babelHelpers.specRequireInterop(require('module')); | ||
|
||
babelHelpers.specImportCheck(_module, ['pick']); | ||
exports.default = (0, _module.pick)(); | ||
``` | ||
|
||
## Options `loose` | ||
|
||
As per the spec, `import` and `export` are only allowed to be used at the top | ||
|
@@ -85,3 +204,16 @@ and instead of using `Object.defineProperty` an assignment will be used instead. | |
var foo = exports.foo = 5; | ||
exports.__esModule = true; | ||
``` | ||
|
||
The `loose` option is **ignored** if used in combination with `spec`. | ||
|
||
## Caveats | ||
|
||
Star reexports (`export * from 'module'`) always run before other imports or | ||
reexports, as this module's exports must be known before other modules execute. | ||
That is, they behave as if there was an `import 'module'` that runs before any | ||
of the other imports. | ||
|
||
This is particularly hard to avoid in the `spec` / `specImport` mode, as the | ||
exports must be frozen before importing other modules. Star reexports are the | ||
only exception. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't totally follow the logic here, could you clarify for me, what's the result of changing this check?