-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
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
Return rejected promise when stringify import specifier throws #15290
Conversation
Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/53692/ |
72e1fa8
to
814bf88
Compare
f5d81de
to
00446f1
Compare
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.
LGTM except for the toPrimitive
cases.
new Promise(r => r(${source})) | ||
.then(s => ${buildRequire(t.identifier("s"), file)}) | ||
(source => | ||
new Promise(r => r("" + source)) |
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.
new Promise(r => r("" + source)) | |
new Promise(r => r(${ | |
t.isTemplateLiteral(source) ? source : t.templateLiteral([], [source]) | |
})) |
The transform from ${x}
to "" + x
relies on the ignoreToPrimitiveHint
assumption.
Consider an exotic object:
var source = {
[Symbol.toPrimitive](hint) {
if (hint === 'string') {
return "foo"
}
return null
}
}
"" + source
will be null
while ${source}
will be "foo"
. The ToString operation should respect the toPrimitive
symbol.
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.
Fixed, and I go further to move the common logic to function buildDynamicImport
:
babel/packages/babel-helper-module-transforms/src/dynamic-import.ts
Lines 18 to 23 in 9b21005
export function buildDynamicImport( | |
node: t.CallExpression, | |
deferToThen: boolean, | |
wrapWithPromise: boolean, | |
builder: (specifier: t.Expression) => t.Expression, | |
): t.Expression { |
And in transform-modules-amd/commonjs/system
we only need to write code once:
babel/packages/babel-plugin-transform-modules-amd/src/index.ts
Lines 102 to 115 in 9b21005
buildDynamicImport( | |
path.node, | |
false, | |
false, | |
specifier => template.expression.ast` | |
new Promise((${resolveId}, ${rejectId}) => | |
${requireId}( | |
[${specifier}], | |
imported => ${t.cloneNode(resolveId)}(${result}), | |
${t.cloneNode(rejectId)} | |
) | |
) | |
`, | |
), |
babel/packages/babel-plugin-transform-modules-commonjs/src/dynamic-import.ts
Lines 24 to 26 in 9b21005
buildDynamicImport(path.node, true, false, specifier => | |
buildRequire(specifier, file), | |
), |
babel/packages/babel-plugin-transform-modules-systemjs/src/index.ts
Lines 277 to 285 in 9b21005
buildDynamicImport(path.node, false, true, specifier => | |
t.callExpression( | |
t.memberExpression( | |
t.identifier(state.contextIdent), | |
t.identifier("import"), | |
), | |
[specifier], | |
), | |
), |
@@ -1,3 +1,3 @@ | |||
define(["require"], function (_require) { | |||
var modP = new Promise((_resolve, _reject) => _require(["mod"], imported => _resolve(imported), _reject)); | |||
var modP = Promise.resolve().then(() => new Promise((_resolve, _reject) => _require(["mod"], imported => _resolve(imported), _reject))); |
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.
Since the AMD require
function is already asynchronous, we don't need to defer it to .then
— the original code was fine. Same for SystemJS.
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.
Ok, I just need to set the buildDynamicImport
's runInThen
deferToThen
to false
for it.
babel/packages/babel-plugin-transform-modules-amd/src/index.ts
Lines 102 to 115 in 9b21005
buildDynamicImport( | |
path.node, | |
false, | |
false, | |
specifier => template.expression.ast` | |
new Promise((${resolveId}, ${rejectId}) => | |
${requireId}( | |
[${specifier}], | |
imported => ${t.cloneNode(resolveId)}(${result}), | |
${t.cloneNode(rejectId)} | |
) | |
) | |
`, | |
), |
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.
Now the logic looks like this:
CommonJS (Deferred to then):
babel/packages/babel-helper-module-transforms/src/dynamic-import.ts
Lines 44 to 50 in 9b21005
if (deferToThen) { | |
return template.expression.ast` | |
(specifier => | |
new Promise(r => r(${specifierToString})) | |
.then(s => ${builder(t.identifier("s"))}) | |
)(${specifier}) | |
`; |
SystemJS (Needs to return a rejected promise when toString throws so wrap it in a promise):
babel/packages/babel-helper-module-transforms/src/dynamic-import.ts
Lines 51 to 56 in 9b21005
} else if (wrapWithPromise) { | |
return template.expression.ast` | |
(specifier => | |
new Promise(r => r(${builder(specifierToString)})) | |
)(${specifier}) | |
`; |
AMD (Builder already returns a new Promise
so do nothing):
babel/packages/babel-helper-module-transforms/src/dynamic-import.ts
Lines 57 to 60 in 9b21005
} else { | |
return template.expression.ast` | |
(specifier => ${builder(specifierToString)})(${specifier}) | |
`; |
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.
Thank you!
When using dynamic import (
import(specifier)
), thetoString
of the specifier should be evaluated immediately, not in promise. And if thetoString
throws,import()
should return a rejected promise instead of throwing it immediately.See #15261 (comment) for more context.
Tests
➕ Added
〰️ Output Changed